diff --git a/lib/commands/deprecate.js b/lib/commands/deprecate.js index c41546eb1b85e..1e1b8994c55a3 100644 --- a/lib/commands/deprecate.js +++ b/lib/commands/deprecate.js @@ -39,9 +39,7 @@ class Deprecate extends BaseCommand { // fetch the data and make sure it exists. const p = npa(pkg) - // npa makes the default spec "latest", but for deprecation - // "*" is the appropriate default. - const spec = p.rawSpec === '' ? '*' : p.fetchSpec + const spec = p.rawSpec === '*' ? '*' : p.fetchSpec if (semver.validRange(spec, true) === null) { throw new Error(`invalid version range: ${spec}`) diff --git a/lib/commands/diff.js b/lib/commands/diff.js index bbd6fae6680ca..c8fd734918d75 100644 --- a/lib/commands/diff.js +++ b/lib/commands/diff.js @@ -185,7 +185,7 @@ class Diff extends BaseCommand { // work from the top of the arborist tree to find the original semver // range declared in the package that depends on the package. let bSpec - if (spec.rawSpec) { + if (spec.rawSpec !== '*') { bSpec = spec.rawSpec } else { const bTargetVersion = @@ -269,7 +269,7 @@ class Diff extends BaseCommand { return specs.map(i => { const spec = npa(i) - if (spec.rawSpec) { + if (spec.rawSpec !== '*') { return i } diff --git a/lib/commands/init.js b/lib/commands/init.js index 3762a2c833afe..4822766908715 100644 --- a/lib/commands/init.js +++ b/lib/commands/init.js @@ -98,10 +98,7 @@ class Init extends BaseCommand { packageName = initerName .replace(user + '/' + project, user + '/create-' + project) } else if (req.registry) { - packageName = req.name.replace(/^(@[^/]+\/)?/, '$1create-') - if (req.rawSpec) { - packageName += '@' + req.rawSpec - } + packageName = `${req.name.replace(/^(@[^/]+\/)?/, '$1create-')}@${req.rawSpec}` } else { throw Object.assign(new Error( 'Unrecognized initializer: ' + initerName + diff --git a/lib/commands/rebuild.js b/lib/commands/rebuild.js index d06313ce483a9..df791106fdd21 100644 --- a/lib/commands/rebuild.js +++ b/lib/commands/rebuild.js @@ -39,7 +39,7 @@ class Rebuild extends ArboristWorkspaceCmd { const tree = await arb.loadActual() const specs = args.map(arg => { const spec = npa(arg) - if (spec.type === 'tag' && spec.rawSpec === '') { + if (spec.rawSpec === '*') { return spec } diff --git a/lib/commands/view.js b/lib/commands/view.js index 3b8524ad3fc0d..32b2d0f92a1a6 100644 --- a/lib/commands/view.js +++ b/lib/commands/view.js @@ -196,15 +196,16 @@ class View extends BaseCommand { // get the data about this package let version = this.npm.config.get('tag') // rawSpec is the git url if this is from git - if (spec.type !== 'git' && spec.type !== 'directory' && spec.rawSpec) { + if (spec.type !== 'git' && spec.type !== 'directory' && spec.rawSpec !== '*') { version = spec.rawSpec } const pckmnt = await packument(spec, opts) - if (pckmnt['dist-tags'] && pckmnt['dist-tags'][version]) { + if (pckmnt['dist-tags']?.[version]) { version = pckmnt['dist-tags'][version] } + if (pckmnt.time && pckmnt.time.unpublished) { const u = pckmnt.time.unpublished const er = new Error(`Unpublished on ${u.time}`) diff --git a/tap-snapshots/test/lib/commands/dist-tag.js.test.cjs b/tap-snapshots/test/lib/commands/dist-tag.js.test.cjs index 73cc223a9a0b9..da7944402afc6 100644 --- a/tap-snapshots/test/lib/commands/dist-tag.js.test.cjs +++ b/tap-snapshots/test/lib/commands/dist-tag.js.test.cjs @@ -21,7 +21,7 @@ latest: 1.0.0 ` exports[`test/lib/commands/dist-tag.js TAP ls on missing package > should log no dist-tag found msg 1`] = ` -dist-tag ls Couldn't get dist-tag data for foo@latest +dist-tag ls Couldn't get dist-tag data for foo@* ` diff --git a/test/lib/commands/diff.js b/test/lib/commands/diff.js index 0adaa6568d8f7..0ca9c3b8d078b 100644 --- a/test/lib/commands/diff.js +++ b/test/lib/commands/diff.js @@ -216,7 +216,7 @@ t.test('single arg', t => { }) libnpmdiff = async ([a, b], opts) => { - t.equal(a, 'simple-output@latest', 'should forward single spec') + t.equal(a, 'simple-output@*', 'should forward single spec') t.equal(b, `file:${path}`, 'should compare to cwd') t.match(opts, npm.flatOptions, 'should forward flat options') } @@ -460,7 +460,7 @@ t.test('single arg', t => { } }, libnpmdiff: async ([a, b], opts) => { - t.equal(a, 'lorem@latest', 'should target latest version of pkg name') + t.equal(a, 'lorem@*', 'should target any version of pkg name') t.equal(b, `file:${path}`, 'should target current cwd') }, }) @@ -479,7 +479,7 @@ t.test('single arg', t => { 'package.json': JSON.stringify({ version: '1.0.0' }), }) libnpmdiff = async ([a, b], opts) => { - t.equal(a, 'bar@latest', 'should target latest tag of name') + t.equal(a, 'bar@*', 'should target any version of pkg name') t.equal(b, `file:${path}`, 'should compare to cwd') } @@ -493,7 +493,7 @@ t.test('single arg', t => { t.plan(2) libnpmdiff = async ([a, b], opts) => { - t.equal(a, 'foo@latest', 'should target latest tag of name') + t.equal(a, 'foo@*', 'should target any version of pkg name') t.equal(b, `file:${fooPath}`, 'should compare to cwd') } @@ -592,7 +592,7 @@ t.test('first arg is a qualified spec', t => { libnpmdiff = async ([a, b], opts) => { t.equal(a, 'bar@1.0.0', 'should set expected first spec') - t.equal(b, 'bar-fork@latest', 'should target latest tag if not a dep') + t.equal(b, 'bar-fork@*', 'should target any version if not a dep') } config.diff = ['bar@1.0.0', 'bar-fork'] @@ -753,7 +753,7 @@ t.test('first arg is a known dependency name', async t => { `bar@file:${resolve(path, 'node_modules/bar')}`, 'should target local node_modules pkg' ) - t.equal(b, 'bar-fork@latest', 'should set expected second spec') + t.equal(b, 'bar-fork@*', 'should set expected second spec') } npm.prefix = path @@ -840,7 +840,7 @@ t.test('first arg is a valid semver range', t => { libnpmdiff = async ([a, b], opts) => { t.equal(a, 'bar@1.0.0', 'should use name from second arg') - t.equal(b, 'bar@latest', 'should compare against latest tag') + t.equal(b, 'bar@*', 'should compare against any version') } config.diff = ['1.0.0', 'bar'] @@ -884,7 +884,7 @@ t.test('first arg is an unknown dependency name', t => { t.plan(4) libnpmdiff = async ([a, b], opts) => { - t.equal(a, 'bar@latest', 'should set expected first spec') + t.equal(a, 'bar@*', 'should set expected first spec') t.equal(b, 'bar@2.0.0', 'should set expected second spec') t.match(opts, npm.flatOptions, 'should forward flat options') t.match(opts, { where: fooPath }, 'should forward pacote options') @@ -919,7 +919,7 @@ t.test('first arg is an unknown dependency name', t => { }) libnpmdiff = async ([a, b], opts) => { - t.equal(a, 'bar-fork@latest', 'should use latest tag') + t.equal(a, 'bar-fork@*', 'should use any version') t.equal( b, `bar@file:${resolve(path, 'node_modules/bar')}`, @@ -940,7 +940,7 @@ t.test('first arg is an unknown dependency name', t => { t.plan(2) libnpmdiff = async ([a, b], opts) => { - t.equal(a, 'bar@latest', 'should use latest tag') + t.equal(a, 'bar@*', 'should use any version') t.equal(b, 'bar@^1.0.0', 'should use name from first arg') } @@ -956,8 +956,8 @@ t.test('first arg is an unknown dependency name', t => { t.plan(2) libnpmdiff = async ([a, b], opts) => { - t.equal(a, 'bar@latest', 'should use latest tag') - t.equal(b, 'bar-fork@latest', 'should use latest tag') + t.equal(a, 'bar@*', 'should use any version') + t.equal(b, 'bar-fork@*', 'should use any version') } config.diff = ['bar', 'bar-fork'] @@ -973,8 +973,8 @@ t.test('first arg is an unknown dependency name', t => { const path = t.testdir({}) libnpmdiff = async ([a, b], opts) => { - t.equal(a, 'bar@latest', 'should use latest tag') - t.equal(b, 'bar-fork@latest', 'should use latest tag') + t.equal(a, 'bar@*', 'should use any version') + t.equal(b, 'bar-fork@*', 'should use any version') } config.diff = ['bar', 'bar-fork'] diff --git a/test/lib/commands/dist-tag.js b/test/lib/commands/dist-tag.js index eb13c7ff02fbe..464f5bc9392d8 100644 --- a/test/lib/commands/dist-tag.js +++ b/test/lib/commands/dist-tag.js @@ -277,7 +277,7 @@ t.test('workspaces', t => { await distTag.execWorkspaces([], []) t.equal(process.exitCode, 1, 'set the error status') process.exitCode = 0 - t.match(log, 'dist-tag ls Couldn\'t get dist-tag data for workspace-d@latest', 'logs the error') + t.match(log, 'dist-tag ls Couldn\'t get dist-tag data for workspace-d@*', 'logs the error') t.matchSnapshot(result, 'printed the expected output') }) diff --git a/test/lib/commands/init.js b/test/lib/commands/init.js index 2dcca5b7f0adc..d11e0091b7cff 100644 --- a/test/lib/commands/init.js +++ b/test/lib/commands/init.js @@ -85,7 +85,7 @@ t.test('npm init ', async t => { libnpmexec: ({ args, cache, npxCache }) => { t.same( args, - ['create-react-app'], + ['create-react-app@*'], 'should npx with listed packages' ) t.same(cache, flatOptions.cache) @@ -106,7 +106,7 @@ t.test('npm init -- other-args', async t => { libnpmexec: ({ args }) => { t.same( args, - ['create-react-app', 'my-path', '--some-option', 'some-value'], + ['create-react-app@*', 'my-path', '--some-option', 'some-value'], 'should npm exec with expected args' ) }, @@ -125,7 +125,7 @@ t.test('npm init @scope/name', async t => { libnpmexec: ({ args }) => { t.same( args, - ['@npmcli/create-something'], + ['@npmcli/create-something@*'], 'should npx with scoped packages' ) }, @@ -268,7 +268,7 @@ t.test('should not rewrite flatOptions', async t => { libnpmexec: async ({ args }) => { t.same( args, - ['create-react-app', 'my-app'], + ['create-react-app@*', 'my-app'], 'should npx with extra args' ) }, @@ -500,7 +500,7 @@ t.test('workspaces', t => { libnpmexec: ({ args, path }) => { t.same( args, - ['create-react-app'], + ['create-react-app@*'], 'should npx with listed packages' ) t.same( diff --git a/workspaces/arborist/lib/add-rm-pkg-deps.js b/workspaces/arborist/lib/add-rm-pkg-deps.js index 59d5e32547c29..c5cdc097a9fab 100644 --- a/workspaces/arborist/lib/add-rm-pkg-deps.js +++ b/workspaces/arborist/lib/add-rm-pkg-deps.js @@ -35,8 +35,8 @@ const add = ({ pkg, add, saveBundle, saveType }) => { const depType = saveTypeMap.get(addSaveType) pkg[depType] = pkg[depType] || {} - if (rawSpec !== '' || pkg[depType][name] === undefined) { - pkg[depType][name] = rawSpec || '*' + if (rawSpec !== '*' || pkg[depType][name] === undefined) { + pkg[depType][name] = rawSpec } if (addSaveType === 'optional') { // Affordance for previous npm versions that require this behaviour diff --git a/workspaces/arborist/lib/arborist/build-ideal-tree.js b/workspaces/arborist/lib/arborist/build-ideal-tree.js index 3b502acd10287..afcb67903da6d 100644 --- a/workspaces/arborist/lib/arborist/build-ideal-tree.js +++ b/workspaces/arborist/lib/arborist/build-ideal-tree.js @@ -254,13 +254,12 @@ module.exports = cls => class IdealTreeBuilder extends cls { for (const name of update.names) { const spec = npa(name) const validationError = - new TypeError(`Update arguments must not contain package version specifiers - -Try using the package name instead, e.g: + new TypeError(`Update arguments must only contain package names, eg: npm update ${spec.name}`) validationError.code = 'EUPDATEARGS' - if (spec.fetchSpec !== 'latest') { + // If they gave us anything other than a bare package name + if (spec.raw !== spec.name) { throw validationError } } diff --git a/workspaces/arborist/lib/consistent-resolve.js b/workspaces/arborist/lib/consistent-resolve.js index 5308dc7e2f95e..7c988048057c7 100644 --- a/workspaces/arborist/lib/consistent-resolve.js +++ b/workspaces/arborist/lib/consistent-resolve.js @@ -19,17 +19,23 @@ const consistentResolve = (resolved, fromPath, toPath, relPaths = false) => { rawSpec, raw, } = npa(resolved, fromPath) - const isPath = type === 'file' || type === 'directory' - return isPath && !relPaths ? `file:${fetchSpec.replace(/#/g, '%23')}` - : isPath ? 'file:' + (toPath ? relpath(toPath, fetchSpec.replace(/#/g, '%23')) : fetchSpec.replace(/#/g, '%23')) - : hosted ? `git+${ - hosted.auth ? hosted.https(hostedOpt) : hosted.sshurl(hostedOpt) - }` - : type === 'git' ? saveSpec - // always return something. 'foo' is interpreted as 'foo@' otherwise. - : rawSpec === '' && raw.slice(-1) !== '@' ? raw - // just strip off the name, but otherwise return as-is - : rawSpec + if (type === 'file' || type === 'directory') { + const cleanFetchSpec = fetchSpec.replace(/#/g, '%23') + if (relPaths && toPath) { + return `file:${relpath(toPath, cleanFetchSpec)}` + } + return `file:${cleanFetchSpec}` + } + if (hosted) { + return `git+${hosted.auth ? hosted.https(hostedOpt) : hosted.sshurl(hostedOpt)}` + } + if (type === 'git') { + return saveSpec + } + if (rawSpec === '*') { + return raw + } + return rawSpec } catch (_) { // whatever we passed in was not acceptable to npa. // leave it 100% untouched. diff --git a/workspaces/arborist/lib/edge.js b/workspaces/arborist/lib/edge.js index 5b248b9166208..84078ba050d10 100644 --- a/workspaces/arborist/lib/edge.js +++ b/workspaces/arborist/lib/edge.js @@ -166,7 +166,7 @@ class Edge { } get spec () { - if (this.overrides && this.overrides.value && this.overrides.name === this.name) { + if (this.overrides?.value && this.overrides.value !== '*' && this.overrides.name === this.name) { if (this.overrides.value.startsWith('$')) { const ref = this.overrides.value.slice(1) // we may be a virtual root, if we are we want to resolve reference overrides diff --git a/workspaces/arborist/lib/override-set.js b/workspaces/arborist/lib/override-set.js index e2e04e03e911e..742e3f08ec534 100644 --- a/workspaces/arborist/lib/override-set.js +++ b/workspaces/arborist/lib/override-set.js @@ -25,7 +25,7 @@ class OverrideSet { this.name = spec.name spec.name = '' this.key = key - this.keySpec = spec.rawSpec === '' ? '' : spec.toString() + this.keySpec = spec.toString() this.value = overrides['.'] || this.keySpec } @@ -50,8 +50,7 @@ class OverrideSet { continue } - if (rule.keySpec === '' || - semver.intersects(edge.spec, rule.keySpec)) { + if (semver.intersects(edge.spec, rule.keySpec)) { return rule } } @@ -65,8 +64,7 @@ class OverrideSet { continue } - if (rule.keySpec === '' || - semver.satisfies(node.version, rule.keySpec) || + if (semver.satisfies(node.version, rule.keySpec) || semver.satisfies(node.version, rule.value)) { return rule } @@ -81,8 +79,7 @@ class OverrideSet { continue } - if (rule.keySpec === '' || - semver.satisfies(node.version, rule.keySpec) || + if (semver.satisfies(node.version, rule.keySpec) || semver.satisfies(node.version, rule.value)) { return rule } diff --git a/workspaces/arborist/lib/query-selector-all.js b/workspaces/arborist/lib/query-selector-all.js index 87a67bbed1885..5d21c92df78e5 100644 --- a/workspaces/arborist/lib/query-selector-all.js +++ b/workspaces/arborist/lib/query-selector-all.js @@ -120,13 +120,13 @@ class Results { this.#pendingCombinator = combinators[String(this.currentAstNode)] } - // name selectors (i.e. #foo, #foo@1.0.0) + // name selectors (i.e. #foo) // css calls this id, we interpret it as name idType () { - const spec = npa(this.currentAstNode.value) + const name = this.currentAstNode.value const nextResults = this.initialItems.filter(node => - (node.name === spec.name || node.package.name === spec.name) && - (semver.satisfies(node.version, spec.fetchSpec) || !spec.rawSpec)) + (name === node.name) || (name === node.package.name) + ) this.processPendingCombinator(nextResults) } diff --git a/workspaces/arborist/tap-snapshots/test/node.js.test.cjs b/workspaces/arborist/tap-snapshots/test/node.js.test.cjs index 5c7918a762364..bc571fc9ae443 100644 --- a/workspaces/arborist/tap-snapshots/test/node.js.test.cjs +++ b/workspaces/arborist/tap-snapshots/test/node.js.test.cjs @@ -32,7 +32,7 @@ exports[`test/node.js TAP basic instantiation > just a lone root node 1`] = ` "foo" => OverrideSet { "children": Map {}, "key": "foo", - "keySpec": "", + "keySpec": "*", "name": "foo", "parent": <*ref_2>, "value": "1", diff --git a/workspaces/arborist/test/consistent-resolve.js b/workspaces/arborist/test/consistent-resolve.js index 6145758fa1fff..3792e886fb04e 100644 --- a/workspaces/arborist/test/consistent-resolve.js +++ b/workspaces/arborist/test/consistent-resolve.js @@ -101,12 +101,6 @@ t.test('remotes returned as-is', t => { t.end() }) -t.test('just a tag name interpreted as a tag name, not a name@', t => { - t.equal(cr('foo'), 'foo') - t.equal(cr('foo@'), '') - t.end() -}) - t.test('falsey resolved returns null', t => { t.equal(cr(null), null) t.equal(cr(0), null) @@ -115,6 +109,16 @@ t.test('falsey resolved returns null', t => { t.end() }) +t.test('tag returns tag', t => { + t.equal(cr('foo@latest'), 'latest') + t.end() +}) + +t.test('package name returns package name', t => { + t.equal(cr('foo'), 'foo') + t.end() +}) + t.test('invalid resolved returns as-is', t => { t.equal(cr('not ! a : v@lid t*A*g'), 'not ! a : v@lid t*A*g') t.end() diff --git a/workspaces/arborist/test/override-set.js b/workspaces/arborist/test/override-set.js index d46e212647b92..b5695d36b0553 100644 --- a/workspaces/arborist/test/override-set.js +++ b/workspaces/arborist/test/override-set.js @@ -124,26 +124,26 @@ t.test('constructor', async (t) => { const fooRule = ruleset.get('foo') // these are both empty because the foo rule does not actually override // anything directly, it only carries child rules through - t.equal(fooRule.keySpec, '', 'keySpec is empty') - t.equal(fooRule.value, '', 'value is empty') + t.equal(fooRule.keySpec, '*', 'keySpec is *') + t.equal(fooRule.value, '*', 'value is *') const barRule = ruleset.get('bar') - t.equal(barRule.keySpec, '', 'keySpec is empty') + t.equal(barRule.keySpec, '*', 'keySpec is *') t.equal(barRule.value, '2.0.0', 'got the correct override for bar') const bazRule = ruleset.get('baz') - t.equal(bazRule.keySpec, '', 'keySpec is empty') + t.equal(bazRule.keySpec, '*', 'keySpec is *') t.equal(bazRule.value, '2.0.0', 'got the correct override for baz') const childRule = edgeRule.getEdgeRule({ name: 'bar', spec: '^1.0.0' }) const childRuleSet = childRule.ruleset const childBazRule = childRuleSet.get('baz') - t.equal(childBazRule.keySpec, '', 'keySpec is empty') + t.equal(childBazRule.keySpec, '*', 'keySpec is *') t.equal(childBazRule.value, '3.0.0', 'got the correct override for nested baz') const childBarRule = childRuleSet.get('bar') - t.equal(childBarRule.keySpec, '', 'keySpec is empty') + t.equal(childBarRule.keySpec, '*', 'keySpec is *') t.equal(childBarRule.value, '2.0.0', 'got the correct override for nested bar') const childFooRule = childRuleSet.get('foo') - t.equal(childFooRule.keySpec, '', 'keySpec is empty') - t.equal(childFooRule.value, '', 'value is empty') + t.equal(childFooRule.keySpec, '*', 'keySpec is *') + t.equal(childFooRule.value, '*', 'value is *') }) t.test('coerces empty string to *', async (t) => { @@ -161,6 +161,6 @@ t.test('constructor', async (t) => { t.equal(edgeRule.value, '*', 'empty string was replaced with *') const barEdgeRule = overrides.getEdgeRule({ name: 'bar', spec: '^1' }) - t.equal(barEdgeRule.value, '', 'when rule is omitted entirely value is an empty string') + t.equal(barEdgeRule.value, '*', 'when rule is omitted entirely value is *') }) }) diff --git a/workspaces/libnpmexec/lib/index.js b/workspaces/libnpmexec/lib/index.js index 19733176040dc..7a224ab7f0a62 100644 --- a/workspaces/libnpmexec/lib/index.js +++ b/workspaces/libnpmexec/lib/index.js @@ -38,23 +38,21 @@ const getManifest = async (spec, flatOptions) => { // Returns the required manifest if the spec is missing from the tree // Returns the found node if it is in the tree const missingFromTree = async ({ spec, tree, flatOptions }) => { - if (spec.registry && (spec.rawSpec === '' || spec.type !== 'tag')) { + if (spec.registry && spec.type !== 'tag') { // registry spec that is not a specific tag. const nodesBySpec = tree.inventory.query('packageName', spec.name) for (const node of nodesBySpec) { - if (spec.type === 'tag') { - // package requested by name only + // package requested by name only (or name@*) + if (spec.rawSpec === '*') { + return { node } + } + // package requested by specific version + if (spec.type === 'version' && (node.pkgid === spec.raw)) { + return { node } + } + // package requested by version range, only remaining registry type + if (semver.satisfies(node.package.version, spec.rawSpec)) { return { node } - } else if (spec.type === 'version') { - // package requested by specific version - if (node.pkgid === spec.raw) { - return { node } - } - } else { - // package requested by version range, only remaining registry type - if (semver.satisfies(node.package.version, spec.rawSpec)) { - return { node } - } } } const manifest = await getManifest(spec, flatOptions)