diff --git a/AUTHORS b/AUTHORS index e65d3efdc0466..3a59d25eaea33 100644 --- a/AUTHORS +++ b/AUTHORS @@ -800,3 +800,6 @@ Ayush Rawal Nate Green Jacob Yacovelli Caleb ツ Everett +gfyoung +Edward Thomson +Behnam Mohammadi diff --git a/CHANGELOG.md b/CHANGELOG.md index 0b44764879332..67d1f6be36d44 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,46 @@ +## v7.24.2 (2021-09-30) + +### BUG FIXES + +* [`56d6cfdc0`](https://github.com/npm/cli/commit/56d6cfdc0745fe919645389b94efb36760eb4179) + [#3804](https://github.com/npm/cli/issues/3804) + encode url before opening + ([@isaacs](https://github.com/isaacs)) +* [`075fe5056`](https://github.com/npm/cli/commit/075fe50565ae5c66df727cdd7df9dd5ed8cd4015) + [#3799](https://github.com/npm/cli/issues/3799) + restore exit code on "npm outdated" + ([@gfyoung](https://github.com/gfyoung)) +* [`dbb90f799`](https://github.com/npm/cli/commit/dbb90f7997900b8ae6026dddaa718efe9a1db2f4) + [#3809](https://github.com/npm/cli/issues/3809) + use Intl.Collator for string sorting when available + ([@isaacs](https://github.com/isaacs)) + +### DEPENDENCIES + +* [`69ab10bbf`](https://github.com/npm/cli/commit/69ab10bbf83d42a5d3b5d3f43e5e5b861f3dfed0) + `is-core-module@2.7.0` +* [`e94ddeaca`](https://github.com/npm/cli/commit/e94ddeaca1e75ecc8f54ebcb3df222965e3635d1) + `@npmcli/arborist@2.9.0`: + * fix: avoid infinite loops in peer dep replacements + * fix: use Intl.Collator for string sorting when available + * feat(vuln): expose isDirect +* [`8349c3c15`](https://github.com/npm/cli/commit/8349c3c1557ac52973ad08c10db492e3a5a30204) + [#3815](https://github.com/npm/cli/issues/3815) + `arborist@2.10.0`: + * includeWorkspaceRoot support + * workspacesEnabled=false support + +### DOCUMENTATION + +* [`f425950a6`](https://github.com/npm/cli/commit/f425950a6ca671b2df20703f70b59022c2858d4d) + [#3805](https://github.com/npm/cli/issues/3805) + remove npm Enterprise from documentation + ([@ethomson](https://github.com/ethomson)) +* [`bb0b2da6c`](https://github.com/npm/cli/commit/bb0b2da6c0275dd8c9eda894452ce45b2e8c4c51) + [#3699](https://github.com/npm/cli/issues/3699) + fix(docs): add note about workspace script order + ([@behnammodi](https://github.com/behnammodi)) + ## v7.24.1 (2021-09-23) ### DEPENDENCIES diff --git a/docs/content/using-npm/scope.md b/docs/content/using-npm/scope.md index 62b4a6851641a..911d7ea5177c9 100644 --- a/docs/content/using-npm/scope.md +++ b/docs/content/using-npm/scope.md @@ -98,7 +98,8 @@ desired, with `npm access` or on the npmjs.com website. Scopes can be associated with a separate registry. This allows you to seamlessly use a mix of packages from the primary npm registry and one or more -private registries, such as npm Enterprise. +private registries, such as [GitHub Packages](https://github.com/features/packages) or the open source [Verdaccio](https://verdaccio.org) +project. You can associate a scope with a registry at login, e.g. diff --git a/docs/content/using-npm/workspaces.md b/docs/content/using-npm/workspaces.md index ae834c0cc7e22..baf84f543f017 100644 --- a/docs/content/using-npm/workspaces.md +++ b/docs/content/using-npm/workspaces.md @@ -176,6 +176,22 @@ npm run test --workspaces Will run the `test` script in both `./packages/a` and `./packages/b`. +Commands will be run in each workspace in the order they appear in your `package.json` + +``` +{ + "workspaces": [ "packages/a", "packages/b" ] +} +``` + +Order of run is different with: + +``` +{ + "workspaces": [ "packages/b", "packages/a" ] +} +``` + ### Ignoring missing scripts It is not required for all of the workspaces to implement scripts run with the `npm run` command. diff --git a/lib/cache.js b/lib/cache.js index aed2cce31e63f..4a5665111949f 100644 --- a/lib/cache.js +++ b/lib/cache.js @@ -8,6 +8,7 @@ const semver = require('semver') const BaseCommand = require('./base-command.js') const npa = require('npm-package-arg') const jsonParse = require('json-parse-even-better-errors') +const localeCompare = require('@isaacs/string-locale-compare')('en') const searchCachePackage = async (path, spec, cacheKeys) => { const parsed = npa(spec) @@ -212,10 +213,10 @@ class Cache extends BaseCommand { for (const key of keySet) results.add(key) } - [...results].sort((a, b) => a.localeCompare(b, 'en')).forEach(key => this.npm.output(key)) + [...results].sort(localeCompare).forEach(key => this.npm.output(key)) return } - cacheKeys.sort((a, b) => a.localeCompare(b, 'en')).forEach(key => this.npm.output(key)) + cacheKeys.sort(localeCompare).forEach(key => this.npm.output(key)) } } diff --git a/lib/config.js b/lib/config.js index 2df7bf513437c..a1f706d930e6a 100644 --- a/lib/config.js +++ b/lib/config.js @@ -10,6 +10,7 @@ const writeFile = promisify(fs.writeFile) const { spawn } = require('child_process') const { EOL } = require('os') const ini = require('ini') +const localeCompare = require('@isaacs/string-locale-compare')('en') // take an array of `[key, value, k2=v2, k3, v3, ...]` and turn into // { key: value, k2: v2, k3: v3 } @@ -209,7 +210,7 @@ class Config extends BaseCommand { ; Configs like \`///:_authToken\` are auth that is restricted ; to the registry host specified. -${data.split('\n').sort((a, b) => a.localeCompare(b, 'en')).join('\n').trim()} +${data.split('\n').sort(localeCompare).join('\n').trim()} ;;;; ; all available options shown below with default values @@ -238,7 +239,7 @@ ${defData} if (where === 'default' && !long) continue - const keys = Object.keys(data).sort((a, b) => a.localeCompare(b, 'en')) + const keys = Object.keys(data).sort(localeCompare) if (!keys.length) continue diff --git a/lib/help.js b/lib/help.js index 8e4ff67bc284c..9a6f950e05953 100644 --- a/lib/help.js +++ b/lib/help.js @@ -3,6 +3,7 @@ const path = require('path') const openUrl = require('./utils/open-url.js') const { promisify } = require('util') const glob = promisify(require('glob')) +const localeCompare = require('@isaacs/string-locale-compare')('en') const BaseCommand = require('./base-command.js') @@ -82,7 +83,7 @@ class Help extends BaseCommand { if (aManNumber !== bManNumber) return aManNumber - bManNumber - return a.localeCompare(b, 'en') + return localeCompare(a, b) }) const man = mans[0] diff --git a/lib/ls.js b/lib/ls.js index 7e41892c53442..495b6348a0360 100644 --- a/lib/ls.js +++ b/lib/ls.js @@ -22,6 +22,7 @@ const _problems = Symbol('problems') const _required = Symbol('required') const _type = Symbol('type') const ArboristWorkspaceCmd = require('./workspaces/arborist-cmd.js') +const localeCompare = require('@isaacs/string-locale-compare')('en') class LS extends ArboristWorkspaceCmd { /* istanbul ignore next - see test/lib/load-all-commands.js */ @@ -503,8 +504,7 @@ const augmentNodesWithMetadata = ({ return node } -const sortAlphabetically = (a, b) => - a.pkgid.localeCompare(b.pkgid, 'en') +const sortAlphabetically = ({ pkgid: a }, { pkgid: b }) => localeCompare(a, b) const humanOutput = ({ color, result, seenItems, unicode }) => { // we need to traverse the entire tree in order to determine which items diff --git a/lib/outdated.js b/lib/outdated.js index 01e268fe96aee..b3b630421c488 100644 --- a/lib/outdated.js +++ b/lib/outdated.js @@ -6,6 +6,7 @@ const color = require('chalk') const styles = require('ansistyles') const npa = require('npm-package-arg') const pickManifest = require('npm-pick-manifest') +const localeCompare = require('@isaacs/string-locale-compare')('en') const Arborist = require('@npmcli/arborist') @@ -85,7 +86,10 @@ class Outdated extends ArboristWorkspaceCmd { })) // sorts list alphabetically - const outdated = this.list.sort((a, b) => a.name.localeCompare(b.name, 'en')) + const outdated = this.list.sort((a, b) => localeCompare(a.name, b.name)) + + if (outdated.length > 0) + process.exitCode = 1 // return if no outdated packages if (outdated.length === 0 && !this.npm.config.get('json')) diff --git a/lib/utils/completion/installed-deep.js b/lib/utils/completion/installed-deep.js index 2430688612cd4..590955a1ee520 100644 --- a/lib/utils/completion/installed-deep.js +++ b/lib/utils/completion/installed-deep.js @@ -1,5 +1,6 @@ const { resolve } = require('path') const Arborist = require('@npmcli/arborist') +const localeCompare = require('@isaacs/string-locale-compare')('en') const installedDeep = async (npm) => { const { @@ -15,8 +16,7 @@ const installedDeep = async (npm) => { return i }) .filter(i => (i.depth - 1) <= depth) - .sort((a, b) => a.depth - b.depth) - .sort((a, b) => a.depth === b.depth ? a.name.localeCompare(b.name, 'en') : 0) + .sort((a, b) => (a.depth - b.depth) || localeCompare(a.name, b.name)) const res = new Set() const gArb = new Arborist({ global: true, path: resolve(npm.globalDir, '..') }) diff --git a/lib/utils/config/describe-all.js b/lib/utils/config/describe-all.js index c8a973cc0fd21..23a10ae97783c 100644 --- a/lib/utils/config/describe-all.js +++ b/lib/utils/config/describe-all.js @@ -1,4 +1,5 @@ const definitions = require('./definitions.js') +const localeCompare = require('@isaacs/string-locale-compare')('en') const describeAll = () => { // sort not-deprecated ones to the top /* istanbul ignore next - typically already sorted in the definitions file, @@ -7,7 +8,7 @@ const describeAll = () => { const sort = ([keya, {deprecated: depa}], [keyb, {deprecated: depb}]) => { return depa && !depb ? 1 : !depa && depb ? -1 - : keya.localeCompare(keyb, 'en') + : localeCompare(keya, keyb) } return Object.entries(definitions).sort(sort) .map(([key, def]) => def.describe()) diff --git a/lib/utils/npm-usage.js b/lib/utils/npm-usage.js index ddb0bab0bc9a2..f6785867c0ae5 100644 --- a/lib/utils/npm-usage.js +++ b/lib/utils/npm-usage.js @@ -1,5 +1,6 @@ const { dirname } = require('path') const { cmdList } = require('./cmd-list') +const localeCompare = require('@isaacs/string-locale-compare')('en') module.exports = (npm) => { const usesBrowser = npm.config.get('viewer') === 'browser' @@ -62,7 +63,7 @@ const usages = (npm) => { maxLen = Math.max(maxLen, c.length) return set }, []) - .sort((a, b) => a[0].localeCompare(b[0], 'en')) + .sort(([a], [b]) => localeCompare(a, b)) .map(([c, usage]) => `\n ${c}${' '.repeat(maxLen - c.length + 1)}${ (usage.split('\n').join('\n' + ' '.repeat(maxLen + 5)))}`) .join('\n') diff --git a/lib/utils/open-url.js b/lib/utils/open-url.js index 41fac33ec66e9..331ca96fa96d0 100644 --- a/lib/utils/open-url.js +++ b/lib/utils/open-url.js @@ -4,6 +4,7 @@ const { URL } = require('url') // attempt to open URL in web-browser, print address otherwise: const open = async (npm, url, errMsg) => { + url = encodeURI(url) const browser = npm.config.get('browser') function printAlternateMsg () { diff --git a/lib/utils/tar.js b/lib/utils/tar.js index c3071c1bd47a5..0ff822370d4da 100644 --- a/lib/utils/tar.js +++ b/lib/utils/tar.js @@ -3,6 +3,10 @@ const ssri = require('ssri') const npmlog = require('npmlog') const formatBytes = require('./format-bytes.js') const columnify = require('columnify') +const localeCompare = require('@isaacs/string-locale-compare')('en', { + sensitivity: 'case', + numeric: true, +}) const logTar = (tarball, opts = {}) => { const { unicode = false, log = npmlog } = opts @@ -75,12 +79,7 @@ const getContents = async (manifest, tarball) => { algorithms: ['sha1', 'sha512'], }) - const comparator = (a, b) => { - return a.path.localeCompare(b.path, 'en', { - sensitivity: 'case', - numeric: true, - }) - } + const comparator = ({ path: a }, { path: b }) => localeCompare(a, b) const isUpper = (str) => { const ch = str.charAt(0) diff --git a/node_modules/@isaacs/string-locale-compare/LICENSE b/node_modules/@isaacs/string-locale-compare/LICENSE new file mode 100644 index 0000000000000..05eeeb88c2ef4 --- /dev/null +++ b/node_modules/@isaacs/string-locale-compare/LICENSE @@ -0,0 +1,15 @@ +The ISC License + +Copyright (c) Isaac Z. Schlueter + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR +IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. diff --git a/node_modules/@isaacs/string-locale-compare/index.js b/node_modules/@isaacs/string-locale-compare/index.js new file mode 100644 index 0000000000000..0f68ab6774e1a --- /dev/null +++ b/node_modules/@isaacs/string-locale-compare/index.js @@ -0,0 +1,42 @@ +const hasIntl = typeof Intl === 'object' && !!Intl +const Collator = hasIntl && Intl.Collator +const cache = new Map() + +const collatorCompare = (locale, opts) => { + const collator = new Collator(locale, opts) + return (a, b) => collator.compare(a, b) +} + +const localeCompare = (locale, opts) => (a, b) => a.localeCompare(b, locale, opts) + +const knownOptions = [ + 'sensitivity', + 'numeric', + 'ignorePunctuation', + 'caseFirst', +] + +const { hasOwnProperty } = Object.prototype + +module.exports = (locale, options = {}) => { + if (!locale || typeof locale !== 'string') + throw new TypeError('locale required') + + const opts = knownOptions.reduce((opts, k) => { + if (hasOwnProperty.call(options, k)) { + opts[k] = options[k] + } + return opts + }, {}) + const key = `${locale}\n${JSON.stringify(opts)}` + + if (cache.has(key)) + return cache.get(key) + + const compare = hasIntl + ? collatorCompare(locale, opts) + : localeCompare(locale, opts) + cache.set(key, compare) + + return compare +} diff --git a/node_modules/@isaacs/string-locale-compare/package.json b/node_modules/@isaacs/string-locale-compare/package.json new file mode 100644 index 0000000000000..58de848a00377 --- /dev/null +++ b/node_modules/@isaacs/string-locale-compare/package.json @@ -0,0 +1,28 @@ +{ + "name": "@isaacs/string-locale-compare", + "version": "1.1.0", + "files": [ + "index.js" + ], + "main": "index.js", + "description": "Compare strings with Intl.Collator if available, falling back to String.localeCompare otherwise", + "repository": { + "type": "git", + "url": "git+https://github.com/isaacs/string-locale-compare" + }, + "author": "Isaac Z. Schlueter (https://izs.me)", + "license": "ISC", + "scripts": { + "test": "tap", + "snap": "tap", + "preversion": "npm test", + "postversion": "npm publish", + "prepublishOnly": "git push origin --follow-tags" + }, + "tap": { + "check-coverage": true + }, + "devDependencies": { + "tap": "^15.0.9" + } +} diff --git a/node_modules/@npmcli/arborist/lib/add-rm-pkg-deps.js b/node_modules/@npmcli/arborist/lib/add-rm-pkg-deps.js index c1b64a461af8a..3c1cbd44abcc9 100644 --- a/node_modules/@npmcli/arborist/lib/add-rm-pkg-deps.js +++ b/node_modules/@npmcli/arborist/lib/add-rm-pkg-deps.js @@ -1,5 +1,7 @@ // add and remove dependency specs to/from pkg manifest +const localeCompare = require('@isaacs/string-locale-compare')('en') + const add = ({pkg, add, saveBundle, saveType, log}) => { for (const spec of add) { addSingle({pkg, spec, saveBundle, saveType, log}) @@ -79,7 +81,7 @@ const addSingle = ({pkg, spec, saveBundle, saveType, log}) => { // keep it sorted, keep it unique const bd = new Set(pkg.bundleDependencies || []) bd.add(spec.name) - pkg.bundleDependencies = [...bd].sort((a, b) => a.localeCompare(b, 'en')) + pkg.bundleDependencies = [...bd].sort(localeCompare) } } diff --git a/node_modules/@npmcli/arborist/lib/arborist/build-ideal-tree.js b/node_modules/@npmcli/arborist/lib/arborist/build-ideal-tree.js index c45024d16e86b..b7876b1147e10 100644 --- a/node_modules/@npmcli/arborist/lib/arborist/build-ideal-tree.js +++ b/node_modules/@npmcli/arborist/lib/arborist/build-ideal-tree.js @@ -1,4 +1,5 @@ // mixin implementing the buildIdealTree method +const localeCompare = require('@isaacs/string-locale-compare')('en') const rpj = require('read-package-json-fast') const npa = require('npm-package-arg') const pacote = require('pacote') @@ -771,7 +772,7 @@ This is a one-time fix-up, please be patient... // sort physically shallower deps up to the front of the queue, // because they'll affect things deeper in, then alphabetical this[_depsQueue].sort((a, b) => - (a.depth - b.depth) || a.path.localeCompare(b.path, 'en')) + (a.depth - b.depth) || localeCompare(a.path, b.path)) const node = this[_depsQueue].shift() const bd = node.package.bundleDependencies @@ -916,7 +917,7 @@ This is a one-time fix-up, please be patient... } const placeDeps = tasks - .sort((a, b) => a.edge.name.localeCompare(b.edge.name, 'en')) + .sort((a, b) => localeCompare(a.edge.name, b.edge.name)) .map(({ edge, dep }) => new PlaceDep({ edge, dep, @@ -993,8 +994,13 @@ This is a one-time fix-up, please be patient... return } - // lastly, also check for the missing deps of the node we placed + // lastly, also check for the missing deps of the node we placed, + // and any holes created by pruning out conflicted peer sets. this[_depsQueue].push(placed) + for (const dep of pd.needEvaluation) { + this[_depsSeen].delete(dep) + this[_depsQueue].push(dep) + } // pre-fetch any problem edges, since we'll need these soon // if it fails at this point, though, dont' worry because it @@ -1242,7 +1248,7 @@ This is a one-time fix-up, please be patient... // we typically only install non-optional peers, but we have to // factor them into the peerSet so that we can avoid conflicts .filter(e => e.peer && !(e.valid && e.to)) - .sort(({name: a}, {name: b}) => a.localeCompare(b, 'en')) + .sort(({name: a}, {name: b}) => localeCompare(a, b)) for (const edge of peerEdges) { // already placed this one, and we're happy with it. diff --git a/node_modules/@npmcli/arborist/lib/arborist/load-virtual.js b/node_modules/@npmcli/arborist/lib/arborist/load-virtual.js index fa0aa0746e11a..f1960116737e8 100644 --- a/node_modules/@npmcli/arborist/lib/arborist/load-virtual.js +++ b/node_modules/@npmcli/arborist/lib/arborist/load-virtual.js @@ -1,4 +1,5 @@ // mixin providing the loadVirtual method +const localeCompare = require('@isaacs/string-locale-compare')('en') const {resolve} = require('path') @@ -167,12 +168,12 @@ module.exports = cls => class VirtualLoader extends cls { ...depsToEdges('peerOptional', peerOptional), ...lockWS, ].sort(([atype, aname], [btype, bname]) => - atype.localeCompare(btype, 'en') || aname.localeCompare(bname, 'en')) + localeCompare(atype, btype) || localeCompare(aname, bname)) const rootEdges = [...root.edgesOut.values()] .map(e => [e.type, e.name, e.spec]) .sort(([atype, aname], [btype, bname]) => - atype.localeCompare(btype, 'en') || aname.localeCompare(bname, 'en')) + localeCompare(atype, btype) || localeCompare(aname, bname)) if (rootEdges.length !== lockEdges.length) { // something added or removed diff --git a/node_modules/@npmcli/arborist/lib/arborist/rebuild.js b/node_modules/@npmcli/arborist/lib/arborist/rebuild.js index 743794f4bda51..e48bdd76b538c 100644 --- a/node_modules/@npmcli/arborist/lib/arborist/rebuild.js +++ b/node_modules/@npmcli/arborist/lib/arborist/rebuild.js @@ -1,6 +1,7 @@ // Arborist.rebuild({path = this.path}) will do all the binlinks and // bundle building needed. Called by reify, and by `npm rebuild`. +const localeCompare = require('@isaacs/string-locale-compare')('en') const {depth: dfwalk} = require('treeverse') const promiseAllRejectLate = require('promise-all-reject-late') const rpj = require('read-package-json-fast') @@ -14,7 +15,8 @@ const { } = require('@npmcli/node-gyp') const boolEnv = b => b ? '1' : '' -const sortNodes = (a, b) => (a.depth - b.depth) || a.path.localeCompare(b.path, 'en') +const sortNodes = (a, b) => + (a.depth - b.depth) || localeCompare(a.path, b.path) const _workspaces = Symbol.for('workspaces') const _build = Symbol('build') diff --git a/node_modules/@npmcli/arborist/lib/audit-report.js b/node_modules/@npmcli/arborist/lib/audit-report.js index 2e6c207b33eb3..de97cdc29e3a5 100644 --- a/node_modules/@npmcli/arborist/lib/audit-report.js +++ b/node_modules/@npmcli/arborist/lib/audit-report.js @@ -1,6 +1,7 @@ // an object representing the set of vulnerabilities in a tree /* eslint camelcase: "off" */ +const localeCompare = require('@isaacs/string-locale-compare')('en') const npa = require('npm-package-arg') const pickManifest = require('npm-pick-manifest') @@ -79,7 +80,7 @@ class AuditReport extends Map { } obj.vulnerabilities = vulnerabilities - .sort(([a], [b]) => a.localeCompare(b, 'en')) + .sort(([a], [b]) => localeCompare(a, b)) .reduce((set, [name, vuln]) => { set[name] = vuln return set diff --git a/node_modules/@npmcli/arborist/lib/can-place-dep.js b/node_modules/@npmcli/arborist/lib/can-place-dep.js index 7e2e1a0e2d29b..6be59093c034f 100644 --- a/node_modules/@npmcli/arborist/lib/can-place-dep.js +++ b/node_modules/@npmcli/arborist/lib/can-place-dep.js @@ -35,6 +35,7 @@ // then we will return REPLACE rather than CONFLICT, and Arborist will queue // the replaced node for resolution elsewhere. +const localeCompare = require('@isaacs/string-locale-compare')('en') const semver = require('semver') const debug = require('./debug.js') const peerEntrySets = require('./peer-entry-sets.js') @@ -79,7 +80,7 @@ class CanPlaceDep { this._treeSnapshot = JSON.stringify([...target.root.inventory.entries()] .map(([loc, {packageName, version, resolved}]) => { return [loc, packageName, version, resolved] - }).sort(([a], [b]) => a.localeCompare(b, 'en'))) + }).sort(([a], [b]) => localeCompare(a, b))) }) // the result of whether we can place it or not @@ -119,7 +120,7 @@ class CanPlaceDep { const treeSnapshot = JSON.stringify([...target.root.inventory.entries()] .map(([loc, {packageName, version, resolved}]) => { return [loc, packageName, version, resolved] - }).sort(([a], [b]) => a.localeCompare(b, 'en'))) + }).sort(([a], [b]) => localeCompare(a, b))) /* istanbul ignore if */ if (this._treeSnapshot !== treeSnapshot) { throw Object.assign(new Error('tree changed in CanPlaceDep'), { diff --git a/node_modules/@npmcli/arborist/lib/place-dep.js b/node_modules/@npmcli/arborist/lib/place-dep.js index d7cc7d935afc8..6edd94a38e749 100644 --- a/node_modules/@npmcli/arborist/lib/place-dep.js +++ b/node_modules/@npmcli/arborist/lib/place-dep.js @@ -7,6 +7,7 @@ // and saves a set of what was placed and what needs re-evaluation as // a result. +const localeCompare = require('@isaacs/string-locale-compare')('en') const log = require('proc-log') const deepestNestingTarget = require('./deepest-nesting-target.js') const CanPlaceDep = require('./can-place-dep.js') @@ -63,6 +64,8 @@ class PlaceDep { this.parent = parent this.peerConflict = null + this.needEvaluation = new Set() + this.checks = new Map() this.place() @@ -365,6 +368,8 @@ class PlaceDep { } replaceOldDep () { + const target = this.oldDep.parent + // XXX handle replacing an entire peer group? // what about cases where we need to push some other peer groups deeper // into the tree? all the tree updating should be done here, and track @@ -383,8 +388,47 @@ class PlaceDep { oldDeps.push(...gatherDepSet([edge.to], e => e.to !== edge.to)) } } + + // gather all peer edgesIn which are at this level, and will not be + // satisfied by the new dependency. Those are the peer sets that need + // to be either warned about (if they cannot go deeper), or removed and + // re-placed (if they can). + const prunePeerSets = [] + for (const edge of this.oldDep.edgesIn) { + if (this.placed.satisfies(edge) || + !edge.peer || + edge.from.parent !== target || + edge.overridden) { + // not a peer dep, not invalid, or not from this level, so it's fine + // to just let it re-evaluate as a problemEdge later, or let it be + // satisfied by the new dep being placed. + continue + } + for (const entryEdge of peerEntrySets(edge.from).keys()) { + // either this one needs to be pruned and re-evaluated, or marked + // as overridden and warned about. If the entryEdge comes in from + // the root, then we have to leave it alone, and in that case, it + // will have already warned or crashed by getting to this point. + const entryNode = entryEdge.to + const deepestTarget = deepestNestingTarget(entryNode) + if (deepestTarget !== target && !entryEdge.from.isRoot) { + prunePeerSets.push(...gatherDepSet([entryNode], e => { + return e.to !== entryNode && !e.overridden + })) + } else { + this.warnPeerConflict(edge, this.dep) + } + } + } + this.placed.replace(this.oldDep) this.pruneForReplacement(this.placed, oldDeps) + for (const dep of prunePeerSets) { + for (const edge of dep.edgesIn) { + this.needEvaluation.add(edge.from) + } + dep.root = null + } } pruneForReplacement (node, oldDeps) { @@ -430,7 +474,7 @@ class PlaceDep { // sort these so that they're deterministically ordered // otherwise, resulting tree shape is dependent on the order // in which they happened to be resolved. - const nodeSort = (a, b) => a.location.localeCompare(b.location, 'en') + const nodeSort = (a, b) => localeCompare(a.location, b.location) const children = [...node.children.values()].sort(nodeSort) for (const child of children) { @@ -485,19 +529,22 @@ class PlaceDep { return false } - warnPeerConflict () { - this.edge.overridden = true - const expl = this.explainPeerConflict() + warnPeerConflict (edge, dep) { + edge = edge || this.edge + dep = dep || this.dep + edge.overridden = true + const expl = this.explainPeerConflict(edge, dep) log.warn('ERESOLVE', 'overriding peer dependency', expl) } - failPeerConflict () { - const expl = this.explainPeerConflict() + failPeerConflict (edge, dep) { + edge = edge || this.top.edge + dep = dep || this.top.dep + const expl = this.explainPeerConflict(edge, dep) throw Object.assign(new Error('could not resolve'), expl) } - explainPeerConflict () { - const { edge, dep } = this.top + explainPeerConflict (edge, dep) { const { from: node } = edge const curNode = node.resolve(edge.name) diff --git a/node_modules/@npmcli/arborist/lib/printable.js b/node_modules/@npmcli/arborist/lib/printable.js index af24ccb959288..74925d96d2587 100644 --- a/node_modules/@npmcli/arborist/lib/printable.js +++ b/node_modules/@npmcli/arborist/lib/printable.js @@ -1,6 +1,7 @@ // helper function to output a clearer visualization // of the current node and its descendents +const localeCompare = require('@isaacs/string-locale-compare')('en') const util = require('util') const relpath = require('./relpath.js') @@ -67,14 +68,14 @@ class ArboristNode { // edgesOut sorted by name if (tree.edgesOut.size) { this.edgesOut = new Map([...tree.edgesOut.entries()] - .sort(([a], [b]) => a.localeCompare(b, 'en')) + .sort(([a], [b]) => localeCompare(a, b)) .map(([name, edge]) => [name, new EdgeOut(edge)])) } // edgesIn sorted by location if (tree.edgesIn.size) { this.edgesIn = new Set([...tree.edgesIn] - .sort((a, b) => a.from.location.localeCompare(b.from.location, 'en')) + .sort((a, b) => localeCompare(a.from.location, b.from.location)) .map(edge => new EdgeIn(edge))) } @@ -86,14 +87,14 @@ class ArboristNode { // fsChildren sorted by path if (tree.fsChildren.size) { this.fsChildren = new Set([...tree.fsChildren] - .sort(({path: a}, {path: b}) => a.localeCompare(b, 'en')) + .sort(({path: a}, {path: b}) => localeCompare(a, b)) .map(tree => printableTree(tree, path))) } // children sorted by name if (tree.children.size) { this.children = new Map([...tree.children.entries()] - .sort(([a], [b]) => a.localeCompare(b, 'en')) + .sort(([a], [b]) => localeCompare(a, b)) .map(([name, tree]) => [name, printableTree(tree, path)])) } } diff --git a/node_modules/@npmcli/arborist/lib/shrinkwrap.js b/node_modules/@npmcli/arborist/lib/shrinkwrap.js index 6e7e0e31f166b..ed28130249ea0 100644 --- a/node_modules/@npmcli/arborist/lib/shrinkwrap.js +++ b/node_modules/@npmcli/arborist/lib/shrinkwrap.js @@ -9,6 +9,7 @@ // We cannot bump to v3 until npm v6 is out of common usage, and // definitely not before npm v8. +const localeCompare = require('@isaacs/string-locale-compare')('en') const lockfileVersion = 2 // for comparing nodes to yarn.lock entries @@ -911,7 +912,7 @@ class Shrinkwrap { /* istanbul ignore next - sort calling order is indeterminate */ return aloc.length > bloc.length ? 1 : bloc.length > aloc.length ? -1 - : aloc[aloc.length - 1].localeCompare(bloc[bloc.length - 1], 'en') + : localeCompare(aloc[aloc.length - 1], bloc[bloc.length - 1]) })[0] const res = consistentResolve(node.resolved, this.path, this.path, true) diff --git a/node_modules/@npmcli/arborist/lib/vuln.js b/node_modules/@npmcli/arborist/lib/vuln.js index da44e7c34d63c..a818cf318eb89 100644 --- a/node_modules/@npmcli/arborist/lib/vuln.js +++ b/node_modules/@npmcli/arborist/lib/vuln.js @@ -14,6 +14,7 @@ const {satisfies, simplifyRange} = require('semver') const semverOpt = { loose: true, includePrerelease: true } +const localeCompare = require('@isaacs/string-locale-compare')('en') const npa = require('npm-package-arg') const _range = Symbol('_range') const _simpleRange = Symbol('_simpleRange') @@ -81,6 +82,17 @@ class Vuln { } } + get isDirect () { + for (const node of this.nodes.values()) { + for (const edge of node.edgesIn) { + if (edge.from.isProjectRoot || edge.from.isWorkspace) { + return true + } + } + } + return false + } + testSpec (spec) { const specObj = npa(spec) if (!specObj.registry) { @@ -100,10 +112,10 @@ class Vuln { } toJSON () { - // sort so that they're always in a consistent order return { name: this.name, severity: this.severity, + isDirect: this.isDirect, // just loop over the advisories, since via is only Vuln objects, // and calculated advisories have all the info we need via: [...this.advisories].map(v => v.type === 'metavuln' ? v.dependency : { @@ -112,12 +124,10 @@ class Vuln { vulnerableVersions: undefined, id: undefined, }).sort((a, b) => - String(a.source || a).localeCompare(String(b.source || b, 'en'))), - effects: [...this.effects].map(v => v.name) - .sort(/* istanbul ignore next */(a, b) => a.localeCompare(b, 'en')), + localeCompare(String(a.source || a), String(b.source || b))), + effects: [...this.effects].map(v => v.name).sort(localeCompare), range: this.simpleRange, - nodes: [...this.nodes].map(n => n.location) - .sort(/* istanbul ignore next */(a, b) => a.localeCompare(b, 'en')), + nodes: [...this.nodes].map(n => n.location).sort(localeCompare), fixAvailable: this[_fixAvailable], } } diff --git a/node_modules/@npmcli/arborist/lib/yarn-lock.js b/node_modules/@npmcli/arborist/lib/yarn-lock.js index 384ba447d72fa..1eed0664083ac 100644 --- a/node_modules/@npmcli/arborist/lib/yarn-lock.js +++ b/node_modules/@npmcli/arborist/lib/yarn-lock.js @@ -28,13 +28,14 @@ // is an impenetrable 10kloc of webpack flow output, which is overkill // for something relatively simple and tailored to Arborist's use case. +const localeCompare = require('@isaacs/string-locale-compare')('en') const consistentResolve = require('./consistent-resolve.js') const {dirname} = require('path') const {breadth} = require('treeverse') // sort a key/value object into a string of JSON stringified keys and vals const sortKV = obj => Object.keys(obj) - .sort((a, b) => a.localeCompare(b, 'en')) + .sort(localeCompare) .map(k => ` ${JSON.stringify(k)} ${JSON.stringify(obj[k])}`) .join('\n') @@ -170,7 +171,7 @@ class YarnLock { toString () { return prefix + [...new Set([...this.entries.values()])] .map(e => e.toString()) - .sort((a, b) => a.localeCompare(b, 'en')).join('\n\n') + '\n' + .sort(localeCompare).join('\n\n') + '\n' } fromTree (tree) { @@ -180,7 +181,7 @@ class YarnLock { tree, visit: node => this.addEntryFromNode(node), getChildren: node => [...node.children.values(), ...node.fsChildren] - .sort((a, b) => a.depth - b.depth || a.name.localeCompare(b.name, 'en')), + .sort((a, b) => a.depth - b.depth || localeCompare(a.name, b.name)), }) return this } @@ -188,7 +189,7 @@ class YarnLock { addEntryFromNode (node) { const specs = [...node.edgesIn] .map(e => `${node.name}@${e.spec}`) - .sort((a, b) => a.localeCompare(b, 'en')) + .sort(localeCompare) // Note: // yarn will do excessive duplication in a case like this: @@ -321,7 +322,7 @@ class YarnLockEntry { toString () { // sort objects to the bottom, then alphabetical return ([...this[_specs]] - .sort((a, b) => a.localeCompare(b, 'en')) + .sort(localeCompare) .map(JSON.stringify).join(', ') + ':\n' + Object.getOwnPropertyNames(this) @@ -330,7 +331,7 @@ class YarnLockEntry { (a, b) => /* istanbul ignore next - sort call order is unpredictable */ (typeof this[a] === 'object') === (typeof this[b] === 'object') - ? a.localeCompare(b, 'en') + ? localeCompare(a, b) : typeof this[a] === 'object' ? 1 : -1) .map(prop => typeof this[prop] !== 'object' diff --git a/node_modules/@npmcli/arborist/package.json b/node_modules/@npmcli/arborist/package.json index 5d0e31af975d2..b39818d481503 100644 --- a/node_modules/@npmcli/arborist/package.json +++ b/node_modules/@npmcli/arborist/package.json @@ -1,8 +1,9 @@ { "name": "@npmcli/arborist", - "version": "2.8.3", + "version": "2.9.0", "description": "Manage node_modules trees", "dependencies": { + "@isaacs/string-locale-compare": "^1.0.1", "@npmcli/installed-package-contents": "^1.0.7", "@npmcli/map-workspaces": "^1.0.2", "@npmcli/metavuln-calculator": "^1.1.0", diff --git a/node_modules/is-core-module/core.json b/node_modules/is-core-module/core.json index ec741dd946f18..5cd90d1732b6b 100644 --- a/node_modules/is-core-module/core.json +++ b/node_modules/is-core-module/core.json @@ -1,105 +1,105 @@ { "assert": true, - "node:assert": ">= 16", + "node:assert": [">= 14.18 && < 15", ">= 16"], "assert/strict": ">= 15", "node:assert/strict": ">= 16", "async_hooks": ">= 8", - "node:async_hooks": ">= 16", + "node:async_hooks": [">= 14.18 && < 15", ">= 16"], "buffer_ieee754": "< 0.9.7", "buffer": true, - "node:buffer": ">= 16", + "node:buffer": [">= 14.18 && < 15", ">= 16"], "child_process": true, - "node:child_process": ">= 16", + "node:child_process": [">= 14.18 && < 15", ">= 16"], "cluster": true, - "node:cluster": ">= 16", + "node:cluster": [">= 14.18 && < 15", ">= 16"], "console": true, - "node:console": ">= 16", + "node:console": [">= 14.18 && < 15", ">= 16"], "constants": true, - "node:constants": ">= 16", + "node:constants": [">= 14.18 && < 15", ">= 16"], "crypto": true, - "node:crypto": ">= 16", + "node:crypto": [">= 14.18 && < 15", ">= 16"], "_debug_agent": ">= 1 && < 8", "_debugger": "< 8", "dgram": true, - "node:dgram": ">= 16", + "node:dgram": [">= 14.18 && < 15", ">= 16"], "diagnostics_channel": [">= 14.17 && < 15", ">= 15.1"], - "node:diagnostics_channel": ">= 16", + "node:diagnostics_channel": [">= 14.18 && < 15", ">= 16"], "dns": true, - "node:dns": ">= 16", + "node:dns": [">= 14.18 && < 15", ">= 16"], "dns/promises": ">= 15", "node:dns/promises": ">= 16", "domain": ">= 0.7.12", - "node:domain": ">= 16", + "node:domain": [">= 14.18 && < 15", ">= 16"], "events": true, - "node:events": ">= 16", + "node:events": [">= 14.18 && < 15", ">= 16"], "freelist": "< 6", "fs": true, - "node:fs": ">= 16", + "node:fs": [">= 14.18 && < 15", ">= 16"], "fs/promises": [">= 10 && < 10.1", ">= 14"], - "node:fs/promises": ">= 16", + "node:fs/promises": [">= 14.18 && < 15", ">= 16"], "_http_agent": ">= 0.11.1", - "node:_http_agent": ">= 16", + "node:_http_agent": [">= 14.18 && < 15", ">= 16"], "_http_client": ">= 0.11.1", - "node:_http_client": ">= 16", + "node:_http_client": [">= 14.18 && < 15", ">= 16"], "_http_common": ">= 0.11.1", - "node:_http_common": ">= 16", + "node:_http_common": [">= 14.18 && < 15", ">= 16"], "_http_incoming": ">= 0.11.1", - "node:_http_incoming": ">= 16", + "node:_http_incoming": [">= 14.18 && < 15", ">= 16"], "_http_outgoing": ">= 0.11.1", - "node:_http_outgoing": ">= 16", + "node:_http_outgoing": [">= 14.18 && < 15", ">= 16"], "_http_server": ">= 0.11.1", - "node:_http_server": ">= 16", + "node:_http_server": [">= 14.18 && < 15", ">= 16"], "http": true, - "node:http": ">= 16", + "node:http": [">= 14.18 && < 15", ">= 16"], "http2": ">= 8.8", - "node:http2": ">= 16", + "node:http2": [">= 14.18 && < 15", ">= 16"], "https": true, - "node:https": ">= 16", + "node:https": [">= 14.18 && < 15", ">= 16"], "inspector": ">= 8", - "node:inspector": ">= 16", + "node:inspector": [">= 14.18 && < 15", ">= 16"], "_linklist": "< 8", "module": true, - "node:module": ">= 16", + "node:module": [">= 14.18 && < 15", ">= 16"], "net": true, - "node:net": ">= 16", + "node:net": [">= 14.18 && < 15", ">= 16"], "node-inspect/lib/_inspect": ">= 7.6 && < 12", "node-inspect/lib/internal/inspect_client": ">= 7.6 && < 12", "node-inspect/lib/internal/inspect_repl": ">= 7.6 && < 12", "os": true, - "node:os": ">= 16", + "node:os": [">= 14.18 && < 15", ">= 16"], "path": true, - "node:path": ">= 16", + "node:path": [">= 14.18 && < 15", ">= 16"], "path/posix": ">= 15.3", "node:path/posix": ">= 16", "path/win32": ">= 15.3", "node:path/win32": ">= 16", "perf_hooks": ">= 8.5", - "node:perf_hooks": ">= 16", + "node:perf_hooks": [">= 14.18 && < 15", ">= 16"], "process": ">= 1", - "node:process": ">= 16", + "node:process": [">= 14.18 && < 15", ">= 16"], "punycode": true, - "node:punycode": ">= 16", + "node:punycode": [">= 14.18 && < 15", ">= 16"], "querystring": true, - "node:querystring": ">= 16", + "node:querystring": [">= 14.18 && < 15", ">= 16"], "readline": true, - "node:readline": ">= 16", + "node:readline": [">= 14.18 && < 15", ">= 16"], "repl": true, - "node:repl": ">= 16", + "node:repl": [">= 14.18 && < 15", ">= 16"], "smalloc": ">= 0.11.5 && < 3", "_stream_duplex": ">= 0.9.4", - "node:_stream_duplex": ">= 16", + "node:_stream_duplex": [">= 14.18 && < 15", ">= 16"], "_stream_transform": ">= 0.9.4", - "node:_stream_transform": ">= 16", + "node:_stream_transform": [">= 14.18 && < 15", ">= 16"], "_stream_wrap": ">= 1.4.1", - "node:_stream_wrap": ">= 16", + "node:_stream_wrap": [">= 14.18 && < 15", ">= 16"], "_stream_passthrough": ">= 0.9.4", - "node:_stream_passthrough": ">= 16", + "node:_stream_passthrough": [">= 14.18 && < 15", ">= 16"], "_stream_readable": ">= 0.9.4", - "node:_stream_readable": ">= 16", + "node:_stream_readable": [">= 14.18 && < 15", ">= 16"], "_stream_writable": ">= 0.9.4", - "node:_stream_writable": ">= 16", + "node:_stream_writable": [">= 14.18 && < 15", ">= 16"], "stream": true, - "node:stream": ">= 16", + "node:stream": [">= 14.18 && < 15", ">= 16"], "stream/consumers": ">= 16.7", "node:stream/consumers": ">= 16.7", "stream/promises": ">= 15", @@ -107,28 +107,28 @@ "stream/web": ">= 16.5", "node:stream/web": ">= 16.5", "string_decoder": true, - "node:string_decoder": ">= 16", + "node:string_decoder": [">= 14.18 && < 15", ">= 16"], "sys": [">= 0.6 && < 0.7", ">= 0.8"], - "node:sys": ">= 16", + "node:sys": [">= 14.18 && < 15", ">= 16"], "timers": true, - "node:timers": ">= 16", + "node:timers": [">= 14.18 && < 15", ">= 16"], "timers/promises": ">= 15", "node:timers/promises": ">= 16", "_tls_common": ">= 0.11.13", - "node:_tls_common": ">= 16", + "node:_tls_common": [">= 14.18 && < 15", ">= 16"], "_tls_legacy": ">= 0.11.3 && < 10", "_tls_wrap": ">= 0.11.3", - "node:_tls_wrap": ">= 16", + "node:_tls_wrap": [">= 14.18 && < 15", ">= 16"], "tls": true, - "node:tls": ">= 16", + "node:tls": [">= 14.18 && < 15", ">= 16"], "trace_events": ">= 10", - "node:trace_events": ">= 16", + "node:trace_events": [">= 14.18 && < 15", ">= 16"], "tty": true, - "node:tty": ">= 16", + "node:tty": [">= 14.18 && < 15", ">= 16"], "url": true, - "node:url": ">= 16", + "node:url": [">= 14.18 && < 15", ">= 16"], "util": true, - "node:util": ">= 16", + "node:util": [">= 14.18 && < 15", ">= 16"], "util/types": ">= 15.3", "node:util/types": ">= 16", "v8/tools/arguments": ">= 10 && < 12", @@ -139,12 +139,12 @@ "v8/tools/profile_view": [">= 4.4 && < 5", ">= 5.2 && < 12"], "v8/tools/splaytree": [">= 4.4 && < 5", ">= 5.2 && < 12"], "v8": ">= 1", - "node:v8": ">= 16", + "node:v8": [">= 14.18 && < 15", ">= 16"], "vm": true, - "node:vm": ">= 16", + "node:vm": [">= 14.18 && < 15", ">= 16"], "wasi": ">= 13.4 && < 13.5", "worker_threads": ">= 11.7", - "node:worker_threads": ">= 16", + "node:worker_threads": [">= 14.18 && < 15", ">= 16"], "zlib": true, - "node:zlib": ">= 16" + "node:zlib": [">= 14.18 && < 15", ">= 16"] } diff --git a/node_modules/is-core-module/package.json b/node_modules/is-core-module/package.json index a12bd4889a60d..2b58b2332cd8e 100644 --- a/node_modules/is-core-module/package.json +++ b/node_modules/is-core-module/package.json @@ -1,8 +1,9 @@ { "name": "is-core-module", - "version": "2.6.0", + "version": "2.7.0", "description": "Is this specifier a node.js core module?", "main": "index.js", + "sideEffects": false, "exports": { ".": [ { @@ -48,7 +49,7 @@ "has": "^1.0.3" }, "devDependencies": { - "@ljharb/eslint-config": "^17.6.0", + "@ljharb/eslint-config": "^18.0.0", "aud": "^1.1.5", "auto-changelog": "^2.3.0", "eslint": "^7.32.0", diff --git a/node_modules/is-core-module/test/index.js b/node_modules/is-core-module/test/index.js index 59a0055e72e99..392678e85c684 100644 --- a/node_modules/is-core-module/test/index.js +++ b/node_modules/is-core-module/test/index.js @@ -104,5 +104,27 @@ test('core modules', function (t) { st.end(); }); + t.test('Object.prototype pollution', function (st) { + /* eslint no-extend-native: 1 */ + var nonKey = 'not a core module'; + st.teardown(function () { + delete Object.prototype.fs; + delete Object.prototype.path; + delete Object.prototype.http; + delete Object.prototype[nonKey]; + }); + Object.prototype.fs = false; + Object.prototype.path = '>= 999999999'; + Object.prototype.http = data.http; + Object.prototype[nonKey] = true; + + st.equal(isCore('fs'), true, 'fs is a core module even if Object.prototype lies'); + st.equal(isCore('path'), true, 'path is a core module even if Object.prototype lies'); + st.equal(isCore('http'), true, 'path is a core module even if Object.prototype matches data'); + st.equal(isCore(nonKey), false, '"' + nonKey + '" is not a core module even if Object.prototype lies'); + + st.end(); + }); + t.end(); }); diff --git a/package-lock.json b/package-lock.json index e44c83feb822b..32923a4f68ff3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "npm", - "version": "7.24.1", + "version": "7.24.2", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "npm", - "version": "7.24.1", + "version": "7.24.2", "bundleDependencies": [ "@npmcli/arborist", "@npmcli/ci-detect", @@ -84,7 +84,8 @@ "packages/*" ], "dependencies": { - "@npmcli/arborist": "^2.8.3", + "@isaacs/string-locale-compare": "^1.1.0", + "@npmcli/arborist": "^2.9.0", "@npmcli/ci-detect": "^1.2.0", "@npmcli/config": "^2.3.0", "@npmcli/map-workspaces": "^1.0.4", @@ -618,6 +619,12 @@ "integrity": "sha512-wdppn25U8z/2yiaT6YGquE6X8sSv7hNMWSXYSSU1jGv/yd6XqjXgTDJ8KP4NgjTXfJ3GbRjeeb8RTV7a/VpM+w==", "dev": true }, + "node_modules/@isaacs/string-locale-compare": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@isaacs/string-locale-compare/-/string-locale-compare-1.1.0.tgz", + "integrity": "sha512-SQ7Kzhh9+D+ZW9MA0zkYv3VXhIDNx+LzM6EJ+/65I3QY+enU6Itte7E5XX7EWrqLW2FN4n06GWzBnPoC3th2aQ==", + "inBundle": true + }, "node_modules/@istanbuljs/load-nyc-config": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", @@ -764,11 +771,12 @@ } }, "node_modules/@npmcli/arborist": { - "version": "2.8.3", - "resolved": "https://registry.npmjs.org/@npmcli/arborist/-/arborist-2.8.3.tgz", - "integrity": "sha512-miFcxbZjmQqeFTeRSLLh+lc/gxIKDO5L4PVCp+dp+kmcwJmYsEJmF7YvHR2yi3jF+fxgvLf3CCFzboPIXAuabg==", + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/@npmcli/arborist/-/arborist-2.9.0.tgz", + "integrity": "sha512-21DTow2xC0GlkowlE4zOu99UY21nSymW14fHZmB0yeAqhagmttJPmCUZXU+ngJmJ/Dwe5YP9QJUTgEVRLqnwcg==", "inBundle": true, "dependencies": { + "@isaacs/string-locale-compare": "^1.0.1", "@npmcli/installed-package-contents": "^1.0.7", "@npmcli/map-workspaces": "^1.0.2", "@npmcli/metavuln-calculator": "^1.1.0", @@ -4029,9 +4037,9 @@ } }, "node_modules/is-core-module": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.6.0.tgz", - "integrity": "sha512-wShG8vs60jKfPWpF2KZRaAtvt3a20OAn7+IJ6hLPECpSABLcKtFKTTI4ZtH5QcBruBHlq+WsdHWyz0BCZW7svQ==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.7.0.tgz", + "integrity": "sha512-ByY+tjCciCr+9nLryBYcSD50EOGWt95c7tIsKTG1J2ixKKXPvF7Ej3AVd+UfDydAJom3biBGDBALaO79ktwgEQ==", "inBundle": true, "dependencies": { "has": "^1.0.3" @@ -10900,6 +10908,11 @@ "integrity": "sha512-wdppn25U8z/2yiaT6YGquE6X8sSv7hNMWSXYSSU1jGv/yd6XqjXgTDJ8KP4NgjTXfJ3GbRjeeb8RTV7a/VpM+w==", "dev": true }, + "@isaacs/string-locale-compare": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@isaacs/string-locale-compare/-/string-locale-compare-1.1.0.tgz", + "integrity": "sha512-SQ7Kzhh9+D+ZW9MA0zkYv3VXhIDNx+LzM6EJ+/65I3QY+enU6Itte7E5XX7EWrqLW2FN4n06GWzBnPoC3th2aQ==" + }, "@istanbuljs/load-nyc-config": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", @@ -11010,10 +11023,11 @@ "dev": true }, "@npmcli/arborist": { - "version": "2.8.3", - "resolved": "https://registry.npmjs.org/@npmcli/arborist/-/arborist-2.8.3.tgz", - "integrity": "sha512-miFcxbZjmQqeFTeRSLLh+lc/gxIKDO5L4PVCp+dp+kmcwJmYsEJmF7YvHR2yi3jF+fxgvLf3CCFzboPIXAuabg==", + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/@npmcli/arborist/-/arborist-2.9.0.tgz", + "integrity": "sha512-21DTow2xC0GlkowlE4zOu99UY21nSymW14fHZmB0yeAqhagmttJPmCUZXU+ngJmJ/Dwe5YP9QJUTgEVRLqnwcg==", "requires": { + "@isaacs/string-locale-compare": "^1.0.1", "@npmcli/installed-package-contents": "^1.0.7", "@npmcli/map-workspaces": "^1.0.2", "@npmcli/metavuln-calculator": "^1.1.0", @@ -13413,9 +13427,9 @@ } }, "is-core-module": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.6.0.tgz", - "integrity": "sha512-wShG8vs60jKfPWpF2KZRaAtvt3a20OAn7+IJ6hLPECpSABLcKtFKTTI4ZtH5QcBruBHlq+WsdHWyz0BCZW7svQ==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.7.0.tgz", + "integrity": "sha512-ByY+tjCciCr+9nLryBYcSD50EOGWt95c7tIsKTG1J2ixKKXPvF7Ej3AVd+UfDydAJom3biBGDBALaO79ktwgEQ==", "requires": { "has": "^1.0.3" } diff --git a/package.json b/package.json index 6ff77fdb35836..fcc93e8ee42d4 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "version": "7.24.1", + "version": "7.24.2", "name": "npm", "description": "a package manager for JavaScript", "workspaces": [ @@ -53,7 +53,8 @@ "./package.json": "./package.json" }, "dependencies": { - "@npmcli/arborist": "^2.8.3", + "@isaacs/string-locale-compare": "^1.1.0", + "@npmcli/arborist": "^2.9.0", "@npmcli/ci-detect": "^1.2.0", "@npmcli/config": "^2.3.0", "@npmcli/map-workspaces": "^1.0.4", diff --git a/smoke-tests/index.js b/smoke-tests/index.js index 5e2d5e071ae12..076c53e78a0b7 100644 --- a/smoke-tests/index.js +++ b/smoke-tests/index.js @@ -174,9 +174,13 @@ t.test('npm diff', async t => { t.test('npm outdated', async t => { const cmd = `${npmBin} outdated` - const cmdRes = await exec(cmd) + const cmdRes = await exec(cmd).catch(err => { + t.equal(err.code, 1, 'should exit with error code') + return err + }) - t.matchSnapshot(cmdRes, + t.not(cmdRes.stderr, '', 'should have stderr output') + t.matchSnapshot(String(cmdRes.stdout), 'should have expected outdated output') }) diff --git a/test/lib/outdated.js b/test/lib/outdated.js index 34a0aa6c9e03e..518436d0af4ec 100644 --- a/test/lib/outdated.js +++ b/test/lib/outdated.js @@ -102,6 +102,10 @@ const outdated = (dir, opts) => { t.beforeEach(() => logs = '') +const { exitCode } = process + +t.afterEach(() => process.exitCode = exitCode) + const redactCwd = (path) => { const normalizePath = p => p .replace(/\\+/g, '/') @@ -175,6 +179,7 @@ t.test('should display outdated deps', t => { outdated(null, { config: { global: true }, }).exec([], () => { + t.equal(process.exitCode, 1) t.matchSnapshot(logs) t.end() }) @@ -187,6 +192,7 @@ t.test('should display outdated deps', t => { }, color: true, }).exec([], () => { + t.equal(process.exitCode, 1) t.matchSnapshot(logs) t.end() }) @@ -200,6 +206,7 @@ t.test('should display outdated deps', t => { }, color: true, }).exec([], () => { + t.equal(process.exitCode, 1) t.matchSnapshot(logs) t.end() }) @@ -213,6 +220,7 @@ t.test('should display outdated deps', t => { }, color: true, }).exec([], () => { + t.equal(process.exitCode, 1) t.matchSnapshot(logs) t.end() }) @@ -226,6 +234,7 @@ t.test('should display outdated deps', t => { }, color: true, }).exec([], () => { + t.equal(process.exitCode, 1) t.matchSnapshot(logs) t.end() }) @@ -238,6 +247,7 @@ t.test('should display outdated deps', t => { long: true, }, }).exec([], () => { + t.equal(process.exitCode, 1) t.matchSnapshot(logs) t.end() }) @@ -250,6 +260,7 @@ t.test('should display outdated deps', t => { json: true, }, }).exec([], () => { + t.equal(process.exitCode, 1) t.matchSnapshot(logs) t.end() }) @@ -263,6 +274,7 @@ t.test('should display outdated deps', t => { long: true, }, }).exec([], () => { + t.equal(process.exitCode, 1) t.matchSnapshot(logs) t.end() }) @@ -275,6 +287,7 @@ t.test('should display outdated deps', t => { parseable: true, }, }).exec([], () => { + t.equal(process.exitCode, 1) t.matchSnapshot(logs) t.end() }) @@ -288,6 +301,7 @@ t.test('should display outdated deps', t => { long: true, }, }).exec([], () => { + t.equal(process.exitCode, 1) t.matchSnapshot(logs) t.end() }) @@ -299,6 +313,7 @@ t.test('should display outdated deps', t => { all: true, }, }).exec([], () => { + t.equal(process.exitCode, 1) t.matchSnapshot(logs) t.end() }) @@ -310,6 +325,7 @@ t.test('should display outdated deps', t => { global: false, }, }).exec(['cat'], () => { + t.equal(process.exitCode, 1) t.matchSnapshot(logs) t.end() }) @@ -540,6 +556,7 @@ t.test('workspaces', async t => { rej(err) t.matchSnapshot(logs, 'should display ws outdated deps human output') + t.equal(process.exitCode, 1) res() }) }) @@ -554,6 +571,7 @@ t.test('workspaces', async t => { rej(err) t.matchSnapshot(logs, 'should display ws outdated deps json output') + t.equal(process.exitCode, 1) res() }) }) @@ -568,6 +586,7 @@ t.test('workspaces', async t => { rej(err) t.matchSnapshot(logs, 'should display ws outdated deps parseable output') + t.equal(process.exitCode, 1) res() }) }) @@ -582,6 +601,7 @@ t.test('workspaces', async t => { rej(err) t.matchSnapshot(logs, 'should display all dependencies') + t.equal(process.exitCode, 1) res() }) }) @@ -594,6 +614,7 @@ t.test('workspaces', async t => { rej(err) t.matchSnapshot(logs, 'should highlight ws in dependend by section') + t.equal(process.exitCode, 1) res() }) }) @@ -604,6 +625,7 @@ t.test('workspaces', async t => { rej(err) t.matchSnapshot(logs, 'should display results filtered by ws') + t.equal(process.exitCode, 1) res() }) }) @@ -618,6 +640,7 @@ t.test('workspaces', async t => { rej(err) t.matchSnapshot(logs, 'should display json results filtered by ws') + t.equal(process.exitCode, 1) res() }) }) @@ -632,6 +655,7 @@ t.test('workspaces', async t => { rej(err) t.matchSnapshot(logs, 'should display parseable results filtered by ws') + t.equal(process.exitCode, 1) res() }) }) @@ -647,6 +671,7 @@ t.test('workspaces', async t => { t.matchSnapshot(logs, 'should display nested deps when filtering by ws and using --all') + t.equal(process.exitCode, 1) res() }) }) @@ -669,6 +694,7 @@ t.test('workspaces', async t => { t.matchSnapshot(logs, 'should display missing deps when filtering by ws') + t.equal(process.exitCode, 1) res() }) }) diff --git a/test/lib/utils/open-url.js b/test/lib/utils/open-url.js index a31a8cb6867df..36724d0adf7bb 100644 --- a/test/lib/utils/open-url.js +++ b/test/lib/utils/open-url.js @@ -47,11 +47,10 @@ t.test('returns error for non-https and non-file url', async (t) => { openerOpts = null OUTPUT.length = 0 }) - t.rejects(openUrl(npm, 'ftp://www.npmjs.com', 'npm home'), /Invalid URL/, 'got the correct error') + await t.rejects(openUrl(npm, 'ftp://www.npmjs.com', 'npm home'), /Invalid URL/, 'got the correct error') t.equal(openerUrl, null, 'did not open') t.same(openerOpts, null, 'did not open') t.same(OUTPUT, [], 'printed no output') - t.end() }) t.test('returns error for non-parseable url', async (t) => { @@ -60,11 +59,22 @@ t.test('returns error for non-parseable url', async (t) => { openerOpts = null OUTPUT.length = 0 }) - t.rejects(openUrl(npm, 'git+ssh://user@host:repo.git', 'npm home'), /Invalid URL/, 'got the correct error') + await t.rejects(openUrl(npm, 'git+ssh://user@host:repo.git', 'npm home'), /Invalid URL/, 'got the correct error') t.equal(openerUrl, null, 'did not open') t.same(openerOpts, null, 'did not open') t.same(OUTPUT, [], 'printed no output') - t.end() +}) + +t.test('encodes non-URL-safe characters in url provided', async (t) => { + t.teardown(() => { + openerUrl = null + openerOpts = null + OUTPUT.length = 0 + }) + await openUrl(npm, 'https://www.npmjs.com/|cat', 'npm home') + t.equal(openerUrl, 'https://www.npmjs.com/%7Ccat', 'opened the encoded url') + t.same(openerOpts, { command: null }, 'passed command as null (the default)') + t.same(OUTPUT, [], 'printed no output') }) t.test('opens a url with the given browser', async (t) => { @@ -79,7 +89,6 @@ t.test('opens a url with the given browser', async (t) => { t.equal(openerUrl, 'https://www.npmjs.com', 'opened the given url') t.same(openerOpts, { command: 'chrome' }, 'passed the given browser as command') t.same(OUTPUT, [], 'printed no output') - t.end() }) t.test('prints where to go when browser is disabled', async (t) => { @@ -96,7 +105,6 @@ t.test('prints where to go when browser is disabled', async (t) => { t.equal(OUTPUT.length, 1, 'got one logged message') t.equal(OUTPUT[0].length, 1, 'logged message had one value') t.matchSnapshot(OUTPUT[0][0], 'printed expected message') - t.end() }) t.test('prints where to go when browser is disabled and json is enabled', async (t) => { @@ -115,7 +123,6 @@ t.test('prints where to go when browser is disabled and json is enabled', async t.equal(OUTPUT.length, 1, 'got one logged message') t.equal(OUTPUT[0].length, 1, 'logged message had one value') t.matchSnapshot(OUTPUT[0][0], 'printed expected message') - t.end() }) t.test('prints where to go when given browser does not exist', async (t) => { @@ -133,7 +140,6 @@ t.test('prints where to go when given browser does not exist', async (t) => { t.equal(OUTPUT.length, 1, 'got one logged message') t.equal(OUTPUT[0].length, 1, 'logged message had one value') t.matchSnapshot(OUTPUT[0][0], 'printed expected message') - t.end() }) t.test('handles unknown opener error', async (t) => { @@ -146,5 +152,4 @@ t.test('handles unknown opener error', async (t) => { npm.config.set('browser', true) }) t.rejects(openUrl(npm, 'https://www.npmjs.com', 'npm home'), 'failed', 'got the correct error') - t.end() })