From 6166be2ba362642c871da2585e46f2e293aeac36 Mon Sep 17 00:00:00 2001 From: Ke Wu Date: Thu, 12 Aug 2021 13:30:09 +0800 Subject: [PATCH] fix: make npa error more clear (#352) * fix: make npa error more clear * test: fix unit test * fix: add deps path * test: fix unit test * fix: unit test * fix: refactor nested, add install context --- bin/install.js | 14 +++-- lib/alias.js | 6 +-- lib/context.js | 11 ++++ lib/dependencies.js | 20 ++++--- lib/global_install.js | 9 ++-- lib/install.js | 30 ++++++----- lib/local_install.js | 16 +++--- lib/nested.js | 36 +++++++++++++ lib/npa.js | 22 ++++++++ test/alias.test.js | 8 +++ test/context.test.js | 13 +++++ test/dependencies.test.js | 12 +++-- test/fixtures/npa-name-error/package.json | 5 ++ test/fixtures/npa-semver-error/package.json | 5 ++ test/nested.test.js | 42 +++++++++++++++ test/npa-error.test.js | 60 +++++++++++++++++++++ test/npminstall.js | 7 ++- 17 files changed, 269 insertions(+), 47 deletions(-) create mode 100644 lib/context.js create mode 100644 lib/nested.js create mode 100644 lib/npa.js create mode 100644 test/context.test.js create mode 100644 test/fixtures/npa-name-error/package.json create mode 100644 test/fixtures/npa-semver-error/package.json create mode 100644 test/nested.test.js create mode 100644 test/npa-error.test.js diff --git a/bin/install.js b/bin/install.js index a11840c5..25eed8a2 100755 --- a/bin/install.js +++ b/bin/install.js @@ -3,7 +3,7 @@ 'use strict'; const debug = require('debug')('npminstall:bin:install'); -const npa = require('npm-package-arg'); +const npa = require('../lib/npa'); const chalk = require('chalk'); const path = require('path'); const util = require('util'); @@ -20,6 +20,7 @@ const { REMOTE_TYPES, ALIAS_TYPES, } = require('../lib/npa_types'); +const Context = require('../lib/context'); const orignalArgv = process.argv.slice(2); const argv = parseArgs(orignalArgv, { @@ -144,11 +145,14 @@ if (process.env.NPMINSTALL_BY_UPDATE) { argv._ = []; } +const context = new Context(); for (const name of argv._) { + + context.nested.update([ name ]); const [ aliasPackageName, - ] = parsePackageName(name); - const p = npa(name, argv.root); + ] = parsePackageName(name, context.nested); + const p = npa(name, { where: argv.root, nested: context.nested }); pkgs.push({ name: p.name, // `mozilla/nunjucks#0f8b21b8df7e8e852b2e1889388653b7075f0d09` should be rawSpec @@ -316,7 +320,7 @@ debug('argv: %j, env: %j', argv, env); const meta = utils.getGlobalInstallMeta(argv.prefix); config.targetDir = meta.targetDir; config.binDir = meta.binDir; - await installGlobal(config); + await installGlobal(config, context); } else { if (pkgs.length === 0) { if (config.production) { @@ -384,7 +388,7 @@ debug('argv: %j, env: %j', argv, env); } } } - await installLocal(config); + await installLocal(config, context); if (pkgs.length > 0) { // support --save, --save-dev, --save-optional, --save-client, --save-build and --save-isomorphic const map = { diff --git a/lib/alias.js b/lib/alias.js index 112aac22..6d95824a 100644 --- a/lib/alias.js +++ b/lib/alias.js @@ -1,9 +1,9 @@ 'use strict'; -const npa = require('npm-package-arg'); +const npa = require('./npa'); -exports.parsePackageName = pkgName => { - const p = npa(pkgName); +exports.parsePackageName = (pkgName, nested) => { + const p = npa(pkgName, { nested }); const { name, type, diff --git a/lib/context.js b/lib/context.js new file mode 100644 index 00000000..178db028 --- /dev/null +++ b/lib/context.js @@ -0,0 +1,11 @@ +'use strict'; + +const Nested = require('./nested'); + +class Context { + constructor() { + this.nested = new Nested([]); + } +} + +module.exports = Context; diff --git a/lib/dependencies.js b/lib/dependencies.js index 98243951..bbf37038 100644 --- a/lib/dependencies.js +++ b/lib/dependencies.js @@ -1,11 +1,11 @@ 'use strict'; -const npa = require('npm-package-arg'); +const npa = require('./npa'); const { parsePackageName, } = require('./alias'); -module.exports = function dependencies(pkg, options) { +module.exports = function dependencies(pkg, options, nested) { const all = {}; const prod = {}; const client = {}; @@ -57,7 +57,7 @@ module.exports = function dependencies(pkg, options) { }, get all() { - return mergeOptional(all, optionalDependencies); + return mergeOptional(all, optionalDependencies, nested); }, get prodMap() { @@ -65,7 +65,7 @@ module.exports = function dependencies(pkg, options) { }, get prod() { - return mergeOptional(prod, optionalDependencies); + return mergeOptional(prod, optionalDependencies, nested); }, get clientMap() { @@ -73,13 +73,14 @@ module.exports = function dependencies(pkg, options) { }, get client() { - return mergeOptional(client, optionalDependencies); + return mergeOptional(client, optionalDependencies, nested); }, }; }; -function mergeOptional(deps, optional) { +function mergeOptional(deps, optional, nested) { const results = []; + for (const name in deps) { const version = deps[name]; const pkg = { @@ -88,16 +89,19 @@ function mergeOptional(deps, optional) { optional: optional.hasOwnProperty(name), }; + const raw = `${name}@${version}`; + nested.update([ raw ]); + const [ aliasPackageName, realPackageName, - ] = parsePackageName(`${name}@${version}`); + ] = parsePackageName(raw, nested); if (aliasPackageName) { const { name, fetchSpec, - } = npa(realPackageName); + } = npa(realPackageName, { nested }); pkg.alias = aliasPackageName; pkg.name = name; pkg.version = fetchSpec; diff --git a/lib/global_install.js b/lib/global_install.js index 422161ac..9eb12f80 100644 --- a/lib/global_install.js +++ b/lib/global_install.js @@ -8,14 +8,14 @@ const path = require('path'); const utility = require('utility'); const fs = require('mz/fs'); const chalk = require('chalk'); -const npa = require('npm-package-arg'); +const npa = require('./npa'); const installLocal = require('./local_install'); const utils = require('./utils'); const download = require('./download'); const bin = require('./bin'); const formatInstallOptions = require('./format_install_options'); -module.exports = async options => { +module.exports = async (options, context) => { const pkgs = options.pkgs || []; const globalTargetDir = options.targetDir; const globalBinDir = options.binDir; @@ -24,6 +24,7 @@ module.exports = async options => { options.binDir = null; const opts = Object.assign({}, options); + context.nested.update(pkgs.map(pkg => `${pkg.name}@${pkg.version}`)); for (const pkg of pkgs) { const { name: pkgName, @@ -49,7 +50,7 @@ module.exports = async options => { const logName = alias ? `${name}(${pkgName})` : `${pkgName}`; console.info(chalk.gray(`Downloading ${logName} to ${tmpDir}`)); - const p = npa(pkg.name ? `${pkg.name}@${pkg.version}` : pkg.version, options.root); + const p = npa(pkg.name ? `${pkg.name}@${pkg.version}` : pkg.version, { where: options.root, nested: context.nested }); const result = await download(p, installOptions); // read the real package.json and get the pakcage's name @@ -68,7 +69,7 @@ module.exports = async options => { production: true, }); - await installLocal(pkgOptions); + await installLocal(pkgOptions, context); // handle bin link pkgOptions.binDir = globalBinDir; diff --git a/lib/install.js b/lib/install.js index 2ffa9ef7..a31de604 100644 --- a/lib/install.js +++ b/lib/install.js @@ -3,7 +3,7 @@ const debug = require('debug')('npminstall:install'); const path = require('path'); const fs = require('mz/fs'); -const npa = require('npm-package-arg'); +const npa = require('./npa'); const chalk = require('chalk'); const semver = require('semver'); const pMap = require('p-map'); @@ -21,9 +21,9 @@ const { module.exports = install; -async function install(parentDir, pkg, ancestors, options) { +async function install(parentDir, pkg, ancestors, options, context) { try { - return await _install(parentDir, pkg, ancestors, options); + return await _install(parentDir, pkg, ancestors, options, context); } catch (err) { if (pkg.optional) { let message = err.stack; @@ -38,7 +38,7 @@ async function install(parentDir, pkg, ancestors, options) { } -async function _install(parentDir, pkg, ancestors, options) { +async function _install(parentDir, pkg, ancestors, options, context) { const rootPkgDependencies = options.production ? options.rootPkgDependencies.prodMap : options.rootPkgDependencies.allMap; const ancestorsWithRoot = [{ dependencies: rootPkgDependencies, name: 'root package.json' }].concat(ancestors); @@ -56,7 +56,7 @@ async function _install(parentDir, pkg, ancestors, options) { if (options.spinner) { options.spinner.text = `[${options.progresses.finishedInstallTasks}/${options.progresses.installTasks}] Installing ${pkg.name}@${pkg.version}`; } - let p = npa(pkg.name ? `${pkg.name}@${pkg.version}` : pkg.version, options.root); + let p = npa(pkg.name ? `${pkg.name}@${pkg.version}` : pkg.version, { where: options.root, nested: context.nested }); const displayName = p.displayName = utils.getDisplayName(pkg, ancestors); if (options.registryOnly && REGISTRY_TYPES.includes(p.type)) { @@ -64,7 +64,7 @@ async function _install(parentDir, pkg, ancestors, options) { } if (options.flatten || forceFlatten(pkg)) { - const res = await matchAncestorDependencies(p, ancestorsWithRoot, options); + const res = await matchAncestorDependencies(p, ancestorsWithRoot, options, context); if (res) { // output anti semver info if not the same version if (res.childResolved !== res.ancestorResolved) { @@ -81,7 +81,7 @@ async function _install(parentDir, pkg, ancestors, options) { ]); } // use ancestor's spec - p = npa(`${res.name}@${res.ancestorSpec}`, options.root); + p = npa(`${res.name}@${res.ancestorSpec}`, { where: options.root, nested: context.nested }); pkg = Object.assign({}, pkg, { version: res.ancestorSpec }); } } @@ -228,7 +228,7 @@ async function _install(parentDir, pkg, ancestors, options) { const bundledDependencies = await getBundleDependencies(realPkg, realPkgDir); await Promise.all(bundledDependencies.map(name => bundleBin(name, realPkgDir, options))); - const deps = dependencies(realPkg, options); + const deps = dependencies(realPkg, options, context.nested); const pkgs = deps.prod; const pkgMaps = deps.prodMap; @@ -240,14 +240,16 @@ async function _install(parentDir, pkg, ancestors, options) { const reverseAncestors = ancestorsWithRoot.slice().reverse(); for (const name in peerDependencies) { const version = peerDependencies[name]; + const raw = `${name}@${version}`; + context.nested.update([ raw ], p.raw); // don't need to check if peer dependency is in dependencies if (pkgMaps[name]) continue; // if we can get any matched version from ancestor // install it as dependency - const childPkg = npa(`${name}@${version}`, options.root); + const childPkg = npa(raw, { where: options.root, nested: context.nested }); // check in reverse - const res = await matchAncestorDependencies(childPkg, reverseAncestors, options); + const res = await matchAncestorDependencies(childPkg, reverseAncestors, options, context); if (res) { pkgs.push({ name, version: res.ancestorSpec, peer: true }); } else { @@ -266,12 +268,14 @@ async function _install(parentDir, pkg, ancestors, options) { if (pkgs.length > 0) { await utils.mkdirp(nodeModulesDir); const needPkgs = pkgs.filter(childPkg => !bundledDependencies.includes(childPkg.name)); + context.nested.update(needPkgs.map(pkg => `${pkg.name}@${pkg.version}`), `${realPkg.name}@${realPkg.version}`); + const mapper = async childPkg => { await install(realPkgDir, childPkg, ancestors.concat({ displayName: `${realPkg.name}@${realPkg.version}`, name: realPkg.name, dependencies: deps.prodMap, - }), options); + }), options, context); }; await pMap(needPkgs, mapper, 10); } @@ -320,14 +324,14 @@ async function bundleBin(name, parentDir, options) { await bin(parentDir, pkg, pkgDir, options); } -async function matchAncestorDependencies(childPkg, ancestors, options) { +async function matchAncestorDependencies(childPkg, ancestors, options, context) { // only need check npm types if (!REGISTRY_TYPES.includes(childPkg.type)) return; for (const ancestor of ancestors) { const ancestorVersion = ancestor.dependencies[childPkg.name]; if (!ancestorVersion) continue; - const ancestorPkg = npa(`${childPkg.name}@${ancestorVersion}`, options.root); + const ancestorPkg = npa(`${childPkg.name}@${ancestorVersion}`, { where: options.root, nested: context.nested }); if (!REGISTRY_TYPES.includes(ancestorPkg.type)) continue; ancestorPkg.parent = ancestor.name; const satisfied = await satisfiesRange(childPkg, ancestorPkg, options); diff --git a/lib/local_install.js b/lib/local_install.js index 2cfccf7b..4b1b8e46 100644 --- a/lib/local_install.js +++ b/lib/local_install.js @@ -49,8 +49,9 @@ const bin = require('./bin'); * - {Boolean} [trace] - show memory and cpu usages traces of installation * - {Boolean} [flatten] - flatten dependencies by matching ancestors' dependencies * - {Boolean} [disableDedupe] - disable dedupe mode, will back to npm@2 node_modules tree + * @param {Object} context - install context */ -module.exports = async options => { +module.exports = async (options, context) => { options = formatInstallOptions(options); options.spinner && options.spinner.start(); let traceTimer; @@ -88,7 +89,7 @@ module.exports = async options => { } try { - await _install(options); + await _install(options, context); } catch (err) { if (options.spinner) { options.spinner.fail(`Install fail! ${err}`); @@ -106,12 +107,12 @@ module.exports = async options => { exports.runPostInstallTasks = runPostInstallTasks; -async function _install(options) { +async function _install(options, context) { const rootPkgFile = path.join(options.root, 'package.json'); const rootPkg = await utils.readJSON(rootPkgFile); const displayName = `${rootPkg.name}@${rootPkg.version}`; let pkgs = options.pkgs; - const rootPkgDependencies = dependencies(rootPkg, options); + const rootPkgDependencies = dependencies(rootPkg, options, context.nested); options.rootPkgDependencies = rootPkgDependencies; options.resolution = createResolution(rootPkg, options); if (pkgs.length === 0) { @@ -136,6 +137,7 @@ async function _install(options) { if (options.installRoot) await preinstall(rootPkg, options.root, displayName, options); + context.nested.update(pkgs.map(pkg => `${pkg.name}@${pkg.version}`), rootPkg.name && rootPkg.version ? displayName : 'root'); const nodeModulesDir = path.join(options.targetDir, 'node_modules'); await utils.mkdirp(nodeModulesDir); const rootPkgsMap = new Map(); @@ -143,7 +145,7 @@ async function _install(options) { childPkg.name = childPkg.name || ''; rootPkgsMap.set(childPkg.name, true); options.progresses.installTasks++; - await installOne(options.targetDir, childPkg, options); + await installOne(options.targetDir, childPkg, options, context); }; await pMap(pkgs, mapper, 10); @@ -191,7 +193,7 @@ async function _install(options) { finishInstall(options); } -async function installOne(parentDir, childPkg, options) { +async function installOne(parentDir, childPkg, options, context) { if (!(await needInstall(parentDir, childPkg, options))) { options.progresses.finishedInstallTasks++; options.console.info( @@ -204,7 +206,7 @@ async function installOne(parentDir, childPkg, options) { return; } - const res = await install(parentDir, childPkg, [], options); + const res = await install(parentDir, childPkg, [], options, context); options.progresses.finishedInstallTasks++; if (res) { options.console.info( diff --git a/lib/nested.js b/lib/nested.js new file mode 100644 index 00000000..10520017 --- /dev/null +++ b/lib/nested.js @@ -0,0 +1,36 @@ +'use strict'; + + +class Nested { + constructor(pkgs, parent) { + this.depMap = new Map(); + this.update(pkgs, parent); + } + + update(pkgs, parent) { + pkgs.forEach(pkg => { + this.depMap.set(pkg, parent || 'root'); + }); + } + + showPath(raw) { + const raws = [ raw ]; + let currentRaw = raw; + + while (this.depMap.has(currentRaw)) { + const parent = this.depMap.get(currentRaw); + + // cycle nested deps + if (raws.includes(parent)) { + break; + } + + raws.unshift(parent); + currentRaw = parent; + } + + return raws.join(' › '); + } +} + +module.exports = Nested; diff --git a/lib/npa.js b/lib/npa.js new file mode 100644 index 00000000..e97754a1 --- /dev/null +++ b/lib/npa.js @@ -0,0 +1,22 @@ +'use strict'; + +const orginalNpa = require('npm-package-arg'); +const util = require('util'); + + +module.exports = function npa(arg, { where, nested } = {}) { + try { + return orginalNpa(arg, where); + } catch (error) { + const { code, message } = error; + + let depsPath = ''; + if (nested) { + depsPath = util.format(' package: %s', nested.showPath(arg)); + } + + const newError = new Error(util.format('%s%s', message, depsPath)); + newError.code = code; + throw newError; + } +}; diff --git a/test/alias.test.js b/test/alias.test.js index 3869c51c..f191774a 100644 --- a/test/alias.test.js +++ b/test/alias.test.js @@ -6,6 +6,7 @@ const { parsePackageName, getAliasPackageName, } = require('../lib/alias'); +const Nested = require('../lib/nested'); describe('test/alias.test.js', () => { describe('parsePackageName', () => { @@ -71,6 +72,13 @@ describe('test/alias.test.js', () => { assert.strictEqual(error.message, 'Invalid package name "__chair_latest": name cannot start with an underscore'); } }); + it('should throw errors when form of pkg is `alias@npm:name@version`', () => { + try { + parsePackageName('__chair_latest@npm:chair@release-1.5', new Nested([ '__chair_latest@npm:chair@release-1.5' ])); + } catch (error) { + assert.strictEqual(error.message, 'Invalid package name "__chair_latest": name cannot start with an underscore package: root › __chair_latest@npm:chair@release-1.5'); + } + }); }); describe('getAliasPackageName', () => { diff --git a/test/context.test.js b/test/context.test.js new file mode 100644 index 00000000..9a5c2a66 --- /dev/null +++ b/test/context.test.js @@ -0,0 +1,13 @@ +'use strict'; + +const Conext = require('../lib/context'); +const assert = require('assert'); + +describe('test/context.test.js', () => { + it('should context work', () => { + const context = new Conext(); + + assert(context.nested); + assert(context.nested.depMap); + }); +}); diff --git a/test/dependencies.test.js b/test/dependencies.test.js index 2a11a7d2..c49616a1 100644 --- a/test/dependencies.test.js +++ b/test/dependencies.test.js @@ -2,8 +2,10 @@ const dependencies = require('../lib/dependencies'); const assert = require('assert'); +const Nested = require('../lib/nested'); describe('test/dependencies.test.js', () => { + const nested = new Nested([]); it('should work with dependencies and devDependencies', () => { const pkg = { dependencies: { @@ -17,7 +19,7 @@ describe('test/dependencies.test.js', () => { }, }; - const parsed = dependencies(pkg, {}); + const parsed = dependencies(pkg, {}, nested); assert.deepEqual(parsed.all, [ { name: 'koa', version: '1', optional: false }, { name: 'express', version: '2', optional: false }, @@ -49,7 +51,7 @@ describe('test/dependencies.test.js', () => { }, }; - const parsed = dependencies(pkg, {}); + const parsed = dependencies(pkg, {}, nested); assert.deepEqual(parsed.all, [ { name: 'koa', version: '1', optional: false }, { name: 'express', version: '3', optional: true }, @@ -83,7 +85,7 @@ describe('test/dependencies.test.js', () => { }, }; - const parsed = dependencies(pkg, { ignoreOptionalDependencies: true }); + const parsed = dependencies(pkg, { ignoreOptionalDependencies: true }, nested); assert.deepEqual(parsed.all, [ { name: 'koa', version: '1', optional: false }, { name: 'connect', version: '3', optional: false }, @@ -120,7 +122,7 @@ describe('test/dependencies.test.js', () => { }, }; - const parsed = dependencies(pkg, {}); + const parsed = dependencies(pkg, {}, nested); assert.deepEqual(parsed.all, [ { name: 'koa', version: '1', optional: false }, { name: 'express', version: '2', optional: false }, @@ -196,7 +198,7 @@ describe('test/dependencies.test.js', () => { }; try { - dependencies(pkg, {}); + dependencies(pkg, {}, nested); throw new Error('should not excute'); } catch (err) { assert(err.message === `duplicate dependencies error, put isomorphic dependency into isomorphicDependencies: diff --git a/test/fixtures/npa-name-error/package.json b/test/fixtures/npa-name-error/package.json new file mode 100644 index 00000000..54b3d664 --- /dev/null +++ b/test/fixtures/npa-name-error/package.json @@ -0,0 +1,5 @@ +{ + "dependencies": { + "lodash.has ": "^4" + } +} \ No newline at end of file diff --git a/test/fixtures/npa-semver-error/package.json b/test/fixtures/npa-semver-error/package.json new file mode 100644 index 00000000..9efee8ae --- /dev/null +++ b/test/fixtures/npa-semver-error/package.json @@ -0,0 +1,5 @@ +{ + "dependencies": { + "create-table-picker": "0.1.2" + } +} \ No newline at end of file diff --git a/test/nested.test.js b/test/nested.test.js new file mode 100644 index 00000000..56eb46cd --- /dev/null +++ b/test/nested.test.js @@ -0,0 +1,42 @@ +'use strict'; + +const Nested = require('../lib/nested'); +const assert = require('assert'); + + +const mockPkgs = [ + 'a@1.0.0', + 'b@2.0.0', +]; + + +describe('test/nested.test.js', () => { + it('should nested success, when pkgs is Array', () => { + const nested = new Nested(mockPkgs); + assert.strictEqual(nested.showPath('a@1.0.0'), 'root › a@1.0.0'); + }); + + it('should nested success, when update', () => { + const nested = new Nested(mockPkgs); + nested.update([ 'c@3.0.0' ]); + assert.strictEqual(nested.showPath('c@3.0.0'), 'root › c@3.0.0'); + + }); + + it('should showPath sucess', () => { + const nested = new Nested([]); + nested.depMap.set('a@^1', 'b@^1'); + nested.depMap.set('b@^1', 'c@^1'); + nested.depMap.set('c@^1', 'root'); + assert.strictEqual(nested.showPath('a@^1'), 'root › c@^1 › b@^1 › a@^1'); + }); + + it('should break on cycle nested deps', () => { + const nested = new Nested([]); + nested.depMap.set('a@^1', 'b@^1'); + nested.depMap.set('b@^1', 'c@^1'); + nested.depMap.set('c@^1', 'a@^1'); + assert.strictEqual(nested.showPath('a@^1'), 'c@^1 › b@^1 › a@^1'); + + }); +}); diff --git a/test/npa-error.test.js b/test/npa-error.test.js new file mode 100644 index 00000000..df550b7d --- /dev/null +++ b/test/npa-error.test.js @@ -0,0 +1,60 @@ +'use strict'; + + +const coffee = require('coffee'); +const helper = require('./helper'); + + +describe('npa throw error', () => { + const [ tmp, cleanup ] = helper.tmp(); + + beforeEach(cleanup); + afterEach(cleanup); + it('should throw error when package name is invalid', async () => { + await coffee.fork(helper.npminstall, [ + `--prefix=${tmp}`, + '-g', + 'lodash-has @^4', + ]) + .debug() + .expect('stderr', /Error: Invalid package name "lodash-has ": name cannot contain leading or trailing spaces; name can only contain URL-friendly characters package: root › lodash-has @\^4/) + .expect('code', 1) + .end(); + }); + + + it('should throw error when package name is invalid, and print deps in global installation', async () => { + await coffee.fork(helper.npminstall, [ + 'create-table-picker@0.1.2', + '-g', + ]) + .debug() + .expect('stderr', /Error: Invalid tag name ">=\^16.0.0": Tags may not have any characters that encodeURIComponent encodes. package: root › create-table-picker@0.1.2 › react-hovertable@\^0.3.0 › react@>=\^16.0.0/) + .expect('code', 1) + .end(); + }); + + it('shold show package when version is invalid', async () => { + const root = helper.fixtures('npa-semver-error'); + await coffee.fork(helper.npminstall, [], { + cwd: root, + pkgs: [], + }) + .debug() + .expect('stderr', /Error: Invalid tag name ">=\^16.0.0": Tags may not have any characters that encodeURIComponent encodes. package: root › create-table-picker@0.1.2 › react-hovertable@\^0.3.0 › react@>=\^16.0.0/) + .expect('code', 1) + .end(); + }); + + it('shold show package when name is invalid', async () => { + const root = helper.fixtures('npa-name-error'); + await coffee.fork(helper.npminstall, [], { + cwd: root, + pkgs: [], + }) + .debug() + .expect('stderr', /Error: Invalid package name "lodash.has ": name cannot contain leading or trailing spaces; name can only contain URL-friendly characters package: root › lodash.has @\^4/) + .expect('code', 1) + .end(); + }); +}); diff --git a/test/npminstall.js b/test/npminstall.js index 61653d41..fc24ff37 100644 --- a/test/npminstall.js +++ b/test/npminstall.js @@ -3,15 +3,18 @@ const npminstall = require('..'); const config = require('../lib/config'); const utils = require('../lib/utils'); +const Context = require('../lib/context'); + +const context = new Context(); module.exports = async options => { await formatOptions(options); - return await npminstall(options); + return await npminstall(options, context); }; module.exports.installGlobal = async options => { await formatOptions(options); - return await npminstall.installGlobal(options); + return await npminstall.installGlobal(options, context); }; async function formatOptions(options) {