diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 78c2926afd6ed..3b622ed82fa78 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -79,6 +79,47 @@ jobs: env: DEPLOY_VERSION: testing + workspaces-tests: + strategy: + fail-fast: false + matrix: + node-version: [10.x, 12.x, 14.x, 16.x] + platform: + - os: ubuntu-latest + shell: bash + - os: macos-latest + shell: bash + - os: windows-latest + shell: bash + - os: windows-latest + shell: powershell + + runs-on: ${{ matrix.platform.os }} + defaults: + run: + shell: ${{ matrix.platform.shell }} + + steps: + # Checkout the npm/cli repo + - uses: actions/checkout@v2 + + # Installs the specific version of Node.js + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v1 + with: + node-version: ${{ matrix.node-version }} + + # Run the installer script + - name: Install dependencies + run: | + node . install --ignore-scripts --no-audit + node . rebuild + + - name: Run workspaces tests + run: node . test -w ./packages -- --no-check-coverage -t600 -Rbase -c + env: + DEPLOY_VERSION: testing + build: strategy: fail-fast: false diff --git a/AUTHORS b/AUTHORS index a8dfd8b6be682..1ee244b9f5838 100644 --- a/AUTHORS +++ b/AUTHORS @@ -783,3 +783,4 @@ rethab Spencer Wilson <5624115+spencerwilson@users.noreply.github.com> Daniel Park Daniel Park +Luke Karrys diff --git a/CHANGELOG.md b/CHANGELOG.md index e627d72ba2834..64f4e071ed259 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,79 @@ +## v7.18.0 (2021-06-17) + +## FEATURES + +* [`ae285b391`](https://github.com/npm/cli/commit/ae285b39191f3a0c4edfb045a334057bef4567b5) + [#3408](https://github.com/npm/cli/issues/3408) + feat(ls): support `--package-lock-only` flag + ([@G-Rath](https://github.com/G-Rath)) +* [`c984fb59c`](https://github.com/npm/cli/commit/c984fb59c5af087b91acd927cbbacad7c6a46576) + [#3420](https://github.com/npm/cli/issues/3420) + feat(pack): add pack-destination config + ([@wraithgar](https://github.com/wraithgar)) + +## BUG FIXES + +* [`40829ec40`](https://github.com/npm/cli/commit/40829ec40c33a6d23f18715e60e3395bdcb0467e) + [#2554](https://github.com/npm/cli/issues/2554) + [#3399](https://github.com/npm/cli/issues/3399) + fix(link): do not prune packages + ([@ruyadorno](https://github.com/ruyadorno)) +* [`102d4e6fb`](https://github.com/npm/cli/commit/102d4e6fb3c3b02148dbeee977a7d1e6372340d5) + [#3417](https://github.com/npm/cli/issues/3417) + fix(workspaces): explicitly error in global mode + ([@wraithgar](https://github.com/wraithgar)) +* [`993df3041`](https://github.com/npm/cli/commit/993df3041f5bdaa496c3c8d80f00d16b9cf0a1e6) + [#3423](https://github.com/npm/cli/issues/3423) + fix(docs): ls command usage instructions + ([@gurdiga](https://github.com/gurdiga)) +* [`dcc13662c`](https://github.com/npm/cli/commit/dcc13662c1d3e22eaf392647a9cddbb5b0710d24) + [#3418](https://github.com/npm/cli/issues/3418) + fix(config): update link definition + ([@wraithgar](https://github.com/wraithgar)) +* [`b19e56c2e`](https://github.com/npm/cli/commit/b19e56c2e54c035518165470c10480201cefa997) + [#3382](https://github.com/npm/cli/issues/3382) + [#3429](https://github.com/npm/cli/issues/3429) + fix(ls): respect prod config for workspaces + ([@ruyadorno](https://github.com/ruyadorno)) +* [`c99b8b53c`](https://github.com/npm/cli/commit/c99b8b53c3d7a9b0daa6d4416e9c40202ddd59a2) + [#3430](https://github.com/npm/cli/issues/3430) + fix(config): add flatOptions.npxCache + ([@wraithgar](https://github.com/wraithgar)) +* [`e5abf2a21`](https://github.com/npm/cli/commit/e5abf2a2171d95bafc0993f337230d2b6633a6ed) + [#3386](https://github.com/npm/cli/issues/3386) + chore(libnpmdiff): added as workspace + ([@ruyadorno](https://github.com/ruyadorno)) +* [`c6a8734d7`](https://github.com/npm/cli/commit/c6a8734d7d6e4b6d061110a01e45e1d418d56489) + [#3388](https://github.com/npm/cli/issues/3388) + chore(refactor): finish passing npm context + ([@wraithgar](https://github.com/wraithgar)) +* [`d16ee452a`](https://github.com/npm/cli/commit/d16ee452a4a034caada4e9b96faf5c453a658876) + [#3426](https://github.com/npm/cli/issues/3426) + chore(tests): use path.resolve + ([@wraithgar](https://github.com/wraithgar)) + +## DEPENDENCIES + +* [`6b951c042`](https://github.com/npm/cli/commit/6b951c042084e639be929a7ea783c2d85b311bad) + `libnpmversion@1.2.1`: + * fix(retrieve-tag): pass match in a way git accepts +* [`de820a021`](https://github.com/npm/cli/commit/de820a0213f54bbcd155dff25b05d072d5c4a57a) + `npm-package-arg@8.1.5`: + * fix: Make file: URLs (mostly) RFC 8909 compliant +* [`16a95c647`](https://github.com/npm/cli/commit/16a95c64731609c69630c17c45b16edb53ee81b2) + `@npmcli/arborist@2.6.3`: + * fix(inventory) handle old and british forms of 'license' + * fix: removes [_complete] check to apply correct metadata + * ensure node.fsParent is not set to node itself + * fix extraneous deps on load-actual +* [`d341bd86c`](https://github.com/npm/cli/commit/d341bd86ce05fabe44f3be5888ba2611b61914b4) + `make-fetch-happen@9.0.3`: + * fix: implement cache modes correctly +* [`c90612cf5`](https://github.com/npm/cli/commit/c90612cf566d563199553749900d8b05367e2532) + `libnpmexec@2.0.0`: + * use new npxCache option + + ## v7.17.0 (2021-06-10) ## FEATURES diff --git a/docs/content/commands/npm-audit.md b/docs/content/commands/npm-audit.md index 0771d897df90d..704d7a15fb8f1 100644 --- a/docs/content/commands/npm-audit.md +++ b/docs/content/commands/npm-audit.md @@ -250,8 +250,14 @@ Not supported by all npm commands. * Default: false * Type: Boolean -If set to true, it will update only the `package-lock.json`, instead of -checking `node_modules` and downloading dependencies. +If set to true, the current operation will only use the `package-lock.json`, +ignoring `node_modules`. + +For `update` this means only the `package-lock.json` will be updated, +instead of checking `node_modules` and downloading dependencies. + +For `list` this means the output will be based on the tree described by the +`package-lock.json`, rather than the contents of `node_modules`. #### `omit` diff --git a/docs/content/commands/npm-ls.md b/docs/content/commands/npm-ls.md index 3c662176327bf..1f401fa956ff8 100644 --- a/docs/content/commands/npm-ls.md +++ b/docs/content/commands/npm-ls.md @@ -155,18 +155,21 @@ variable will be set to `'production'` for all lifecycle scripts. * Default: false * Type: Boolean -If true, then local installs will link if there is a suitable globally -installed package. +Used with `npm ls`, limiting output to only those packages that are linked. -Note that this means that local installs can cause things to be installed -into the global space at the same time. The link is only done if one of the -two conditions are met: +#### `package-lock-only` -* The package is not already installed globally, or -* the globally installed version is identical to the version that is being - installed locally. +* Default: false +* Type: Boolean + +If set to true, the current operation will only use the `package-lock.json`, +ignoring `node_modules`. + +For `update` this means only the `package-lock.json` will be updated, +instead of checking `node_modules` and downloading dependencies. -When used with `npm ls`, only show packages that are linked. +For `list` this means the output will be based on the tree described by the +`package-lock.json`, rather than the contents of `node_modules`. #### `unicode` diff --git a/docs/content/commands/npm-pack.md b/docs/content/commands/npm-pack.md index 04a22a5d854b4..9507026278437 100644 --- a/docs/content/commands/npm-pack.md +++ b/docs/content/commands/npm-pack.md @@ -36,6 +36,13 @@ Whether or not to output JSON data, rather than the normal output. Not supported by all npm commands. +#### `pack-destination` + +* Default: "." +* Type: String + +Directory in which `npm pack` will save tarballs. + #### `workspace` * Default: diff --git a/docs/content/using-npm/config.md b/docs/content/using-npm/config.md index 44b79a801f15e..a33c66e145cd9 100644 --- a/docs/content/using-npm/config.md +++ b/docs/content/using-npm/config.md @@ -743,18 +743,7 @@ Use of `legacy-peer-deps` is not recommended, as it will not enforce the * Default: false * Type: Boolean -If true, then local installs will link if there is a suitable globally -installed package. - -Note that this means that local installs can cause things to be installed -into the global space at the same time. The link is only done if one of the -two conditions are met: - -* The package is not already installed globally, or -* the globally installed version is identical to the version that is being - installed locally. - -When used with `npm ls`, only show packages that are linked. +Used with `npm ls`, limiting output to only those packages that are linked. #### `local-address` @@ -878,6 +867,13 @@ when publishing or changing package permissions with `npm access`. If not set, and a registry response fails with a challenge for a one-time password, npm will prompt on the command line for one. +#### `pack-destination` + +* Default: "." +* Type: String + +Directory in which `npm pack` will save tarballs. + #### `package` * Default: diff --git a/lib/audit.js b/lib/audit.js index a97fd9b30abd4..54480d1f0cbf9 100644 --- a/lib/audit.js +++ b/lib/audit.js @@ -58,7 +58,7 @@ class Audit extends ArboristWorkspaceCmd { audit: true, path: this.npm.prefix, reporter, - workspaces: this.workspaces, + workspaces: this.workspaceNames, } const arb = new Arborist(opts) diff --git a/lib/base-command.js b/lib/base-command.js index 843fb2d4b1358..4077733a934b0 100644 --- a/lib/base-command.js +++ b/lib/base-command.js @@ -1,6 +1,7 @@ // Base class for npm.commands[cmd] const usageUtil = require('./utils/usage.js') const ConfigDefinitions = require('./utils/config/definitions.js') +const getWorkspaces = require('./workspaces/get-workspaces.js') class BaseCommand { constructor (npm) { @@ -72,5 +73,13 @@ class BaseCommand { { code: 'ENOWORKSPACES' } ) } + + async setWorkspaces (filters) { + // TODO npm guards workspaces/global mode so we should use this.npm.prefix? + const ws = await getWorkspaces(filters, { path: this.npm.localPrefix }) + this.workspaces = ws + this.workspaceNames = [...ws.keys()] + this.workspacePaths = [...ws.values()] + } } module.exports = BaseCommand diff --git a/lib/ci.js b/lib/ci.js index 3a2a2b316a150..3ff4b65badb49 100644 --- a/lib/ci.js +++ b/lib/ci.js @@ -55,7 +55,7 @@ class CI extends ArboristWorkspaceCmd { path: where, log: this.npm.log, save: false, // npm ci should never modify the lockfile or package.json - workspaces: this.workspaces, + workspaces: this.workspaceNames, } const arb = new Arborist(opts) diff --git a/lib/cli.js b/lib/cli.js index d4a67645858ae..fbceb459db97c 100644 --- a/lib/cli.js +++ b/lib/cli.js @@ -20,6 +20,7 @@ module.exports = (process) => { const npm = require('../lib/npm.js') const errorHandler = require('../lib/utils/error-handler.js') + errorHandler.setNpm(npm) // if npm is called as "npmg" or "npm_g", then // run in global mode. diff --git a/lib/dedupe.js b/lib/dedupe.js index 9a58316b80109..aaa7a30d10416 100644 --- a/lib/dedupe.js +++ b/lib/dedupe.js @@ -50,7 +50,7 @@ class Dedupe extends ArboristWorkspaceCmd { log: this.npm.log, path: where, dryRun, - workspaces: this.workspaces, + workspaces: this.workspaceNames, } const arb = new Arborist(opts) await arb.dedupe(opts) diff --git a/lib/diff.js b/lib/diff.js index d315551d443a5..01658c4664d05 100644 --- a/lib/diff.js +++ b/lib/diff.js @@ -8,7 +8,6 @@ const npmlog = require('npmlog') const pacote = require('pacote') const pickManifest = require('npm-pick-manifest') -const getWorkspaces = require('./workspaces/get-workspaces.js') const readPackageName = require('./utils/read-package-name.js') const BaseCommand = require('./base-command.js') @@ -90,9 +89,8 @@ class Diff extends BaseCommand { } async diffWorkspaces (args, filters) { - const workspaces = - await getWorkspaces(filters, { path: this.npm.localPrefix }) - for (const workspacePath of workspaces.values()) { + await this.setWorkspaces(filters) + for (const workspacePath of this.workspacePaths) { this.top = workspacePath this.prefix = workspacePath await this.diff(args) @@ -104,7 +102,6 @@ class Diff extends BaseCommand { async packageName (path) { let name try { - // TODO this won't work as expected in global mode name = await readPackageName(this.prefix) } catch (e) { npmlog.verbose('diff', 'could not read project dir package.json') diff --git a/lib/dist-tag.js b/lib/dist-tag.js index 11b1ad931e18b..e32dcf61fff80 100644 --- a/lib/dist-tag.js +++ b/lib/dist-tag.js @@ -5,7 +5,6 @@ const semver = require('semver') const otplease = require('./utils/otplease.js') const readPackageName = require('./utils/read-package-name.js') -const getWorkspaces = require('./workspaces/get-workspaces.js') const BaseCommand = require('./base-command.js') class DistTag extends BaseCommand { @@ -180,10 +179,9 @@ class DistTag extends BaseCommand { } async listWorkspaces (filters) { - const workspaces = - await getWorkspaces(filters, { path: this.npm.localPrefix }) + await this.setWorkspaces(filters) - for (const [name] of workspaces) { + for (const name of this.workspaceNames) { try { this.npm.output(`${name}:`) await this.list(npa(name), this.npm.flatOptions) diff --git a/lib/docs.js b/lib/docs.js index 24bbe9c854a62..69a19c35c3a13 100644 --- a/lib/docs.js +++ b/lib/docs.js @@ -2,7 +2,6 @@ const log = require('npmlog') const pacote = require('pacote') const openUrl = require('./utils/open-url.js') const hostedFromMani = require('./utils/hosted-git-info-from-manifest.js') -const getWorkspaces = require('./workspaces/get-workspaces.js') const BaseCommand = require('./base-command.js') class Docs extends BaseCommand { @@ -42,9 +41,8 @@ class Docs extends BaseCommand { } async docsWorkspaces (args, filters) { - const workspaces = - await getWorkspaces(filters, { path: this.npm.localPrefix }) - return this.docs([...workspaces.values()]) + await this.setWorkspaces(filters) + return this.docs(this.workspacePaths) } async getDocs (pkg) { diff --git a/lib/exec.js b/lib/exec.js index 8a87615d9749e..959fab66634bd 100644 --- a/lib/exec.js +++ b/lib/exec.js @@ -1,7 +1,6 @@ const libexec = require('libnpmexec') const BaseCommand = require('./base-command.js') const getLocationMsg = require('./exec/get-workspace-location-msg.js') -const getWorkspaces = require('./workspaces/get-workspaces.js') // it's like this: // @@ -68,7 +67,6 @@ class Exec extends BaseCommand { // can be named correctly async _exec (_args, { locationMsg, path, runPath }) { const args = [..._args] - const cache = this.npm.config.get('cache') const call = this.npm.config.get('call') const color = this.npm.config.get('color') const { @@ -89,7 +87,6 @@ class Exec extends BaseCommand { ...flatOptions, args, call, - cache, color, localBin, locationMsg, @@ -105,16 +102,15 @@ class Exec extends BaseCommand { } async _execWorkspaces (args, filters) { - const workspaces = - await getWorkspaces(filters, { path: this.npm.localPrefix }) + await this.setWorkspaces(filters) const color = this.npm.config.get('color') - for (const workspacePath of workspaces.values()) { - const locationMsg = await getLocationMsg({ color, path: workspacePath }) + for (const path of this.workspacePaths) { + const locationMsg = await getLocationMsg({ color, path }) await this._exec(args, { locationMsg, - path: workspacePath, - runPath: workspacePath, + path, + runPath: path, }) } } diff --git a/lib/explain.js b/lib/explain.js index de04c69857240..7d785d7bfcf44 100644 --- a/lib/explain.js +++ b/lib/explain.js @@ -46,8 +46,8 @@ class Explain extends ArboristWorkspaceCmd { const arb = new Arborist({ path: this.npm.prefix, ...this.npm.flatOptions }) const tree = await arb.loadActual() - if (this.workspaces && this.workspaces.length) - this.filterSet = arb.workspaceDependencySet(tree, this.workspaces) + if (this.workspaceNames && this.workspaceNames.length) + this.filterSet = arb.workspaceDependencySet(tree, this.workspaceNames) const nodes = new Set() for (const arg of args) { diff --git a/lib/fund.js b/lib/fund.js index 55d2f65dc4b55..92580a756e8af 100644 --- a/lib/fund.js +++ b/lib/fund.js @@ -95,7 +95,7 @@ class Fund extends ArboristWorkspaceCmd { const fundingInfo = getFundingInfo(tree, { ...this.flatOptions, log: this.npm.log, - workspaces: this.workspaces, + workspaces: this.workspaceNames, }) if (this.npm.config.get('json')) diff --git a/lib/init.js b/lib/init.js index 4dd091601e191..d34f92b882b32 100644 --- a/lib/init.js +++ b/lib/init.js @@ -106,7 +106,6 @@ class Init extends BaseCommand { } const newArgs = [packageName, ...otherArgs] - const cache = this.npm.config.get('cache') const { color } = this.npm.flatOptions const { flatOptions, @@ -128,7 +127,6 @@ class Init extends BaseCommand { await libexec({ ...flatOptions, args: newArgs, - cache, color, localBin, locationMsg, diff --git a/lib/install.js b/lib/install.js index 7c5f55bb9de8a..6611763978e61 100644 --- a/lib/install.js +++ b/lib/install.js @@ -144,7 +144,7 @@ class Install extends ArboristWorkspaceCmd { auditLevel: null, path: where, add: args, - workspaces: this.workspaces, + workspaces: this.workspaceNames, } const arb = new Arborist(opts) await arb.reify(opts) diff --git a/lib/link.js b/lib/link.js index 47fe4b17a272b..febd908718be3 100644 --- a/lib/link.js +++ b/lib/link.js @@ -134,17 +134,19 @@ class Link extends ArboristWorkspaceCmd { // reify all the pending names as symlinks there const localArb = new Arborist({ ...this.npm.flatOptions, + prune: false, log: this.npm.log, path: this.npm.prefix, save, }) await localArb.reify({ ...this.npm.flatOptions, + prune: false, path: this.npm.prefix, log: this.npm.log, add: names.map(l => `file:${resolve(globalTop, 'node_modules', l)}`), save, - workspaces: this.workspaces, + workspaces: this.workspaceNames, }) await reifyFinish(this.npm, localArb) diff --git a/lib/ls.js b/lib/ls.js index d92b73ddfcdbb..f146928a96b41 100644 --- a/lib/ls.js +++ b/lib/ls.js @@ -36,7 +36,7 @@ class LS extends ArboristWorkspaceCmd { /* istanbul ignore next - see test/lib/load-all-commands.js */ static get usage () { - return ['npm ls [[<@scope>/] ...]'] + return ['[[<@scope>/] ...]'] } /* istanbul ignore next - see test/lib/load-all-commands.js */ @@ -50,6 +50,7 @@ class LS extends ArboristWorkspaceCmd { 'depth', 'omit', 'link', + 'package-lock-only', 'unicode', ...super.params, ] @@ -79,6 +80,7 @@ class LS extends ArboristWorkspaceCmd { const prod = this.npm.config.get('prod') const production = this.npm.config.get('production') const unicode = this.npm.config.get('unicode') + const packageLockOnly = this.npm.config.get('package-lock-only') const path = global ? resolve(this.npm.globalDir, '..') : this.npm.prefix @@ -88,14 +90,14 @@ class LS extends ArboristWorkspaceCmd { legacyPeerDeps: false, path, }) - const tree = await this.initTree({arb, args }) + const tree = await this.initTree({arb, args, packageLockOnly }) // filters by workspaces nodes when using -w // We only have to filter the first layer of edges, so we don't // explore anything that isn't part of the selected workspace set. let wsNodes - if (this.workspaces && this.workspaces.length) - wsNodes = arb.workspaceNodes(tree, this.workspaces) + if (this.workspaceNames && this.workspaceNames.length) + wsNodes = arb.workspaceNodes(tree, this.workspaceNames) const filterBySelectedWorkspaces = edge => { if (!wsNodes || !wsNodes.length) return true @@ -139,6 +141,7 @@ class LS extends ArboristWorkspaceCmd { : [...(node.target || node).edgesOut.values()] .filter(filterBySelectedWorkspaces) .filter(filterByEdgesTypes({ + currentDepth, dev, development, link, @@ -216,8 +219,13 @@ class LS extends ArboristWorkspaceCmd { } } - async initTree ({ arb, args }) { - const tree = await arb.loadActual() + async initTree ({ arb, args, packageLockOnly }) { + const tree = await ( + packageLockOnly + ? arb.loadVirtual() + : arb.loadActual() + ) + tree[_include] = args.length === 0 tree[_depth] = 0 @@ -380,6 +388,7 @@ const getJsonOutputItem = (node, { global, long }) => { } const filterByEdgesTypes = ({ + currentDepth, dev, development, link, @@ -391,11 +400,11 @@ const filterByEdgesTypes = ({ }) => { // filter deps by type, allows for: `npm ls --dev`, `npm ls --prod`, // `npm ls --link`, `npm ls --only=dev`, etc - const filterDev = node === tree && + const filterDev = currentDepth === 0 && (dev || development || /^dev(elopment)?$/.test(only)) - const filterProd = node === tree && + const filterProd = currentDepth === 0 && (prod || production || /^prod(uction)?$/.test(only)) - const filterLink = node === tree && link + const filterLink = currentDepth === 0 && link return (edge) => (filterDev ? edge.dev : true) && diff --git a/lib/npm.js b/lib/npm.js index 5f8b2ff3d703d..937459501c0a5 100644 --- a/lib/npm.js +++ b/lib/npm.js @@ -108,6 +108,9 @@ const npm = module.exports = new class extends EventEmitter { this.output(impl.usage) cb() } else if (filterByWorkspaces) { + if (this.config.get('global')) + return cb(new Error('Workspaces not supported for global packages')) + impl.execWorkspaces(args, this.config.get('workspace'), er => { process.emit('timeEnd', `command:${cmd}`) cb(er) diff --git a/lib/outdated.js b/lib/outdated.js index 1be92b9349fe7..9d60d143d71ce 100644 --- a/lib/outdated.js +++ b/lib/outdated.js @@ -59,8 +59,10 @@ class Outdated extends ArboristWorkspaceCmd { this.list = [] this.tree = await arb.loadActual() - if (this.workspaces && this.workspaces.length) - this.filterSet = arb.workspaceDependencySet(this.tree, this.workspaces) + if (this.workspaceNames && this.workspaceNames.length) { + this.filterSet = + arb.workspaceDependencySet(this.tree, this.workspaceNames) + } if (args.length !== 0) { // specific deps diff --git a/lib/pack.js b/lib/pack.js index 52d4c3e7f900a..8fc89db1a0b2b 100644 --- a/lib/pack.js +++ b/lib/pack.js @@ -3,7 +3,7 @@ const log = require('npmlog') const pacote = require('pacote') const libpack = require('libnpmpack') const npa = require('npm-package-arg') -const getWorkspaces = require('./workspaces/get-workspaces.js') +const path = require('path') const { getContents, logTar } = require('./utils/tar.js') @@ -24,7 +24,13 @@ class Pack extends BaseCommand { /* istanbul ignore next - see test/lib/load-all-commands.js */ static get params () { - return ['dry-run', 'json', 'workspace', 'workspaces'] + return [ + 'dry-run', + 'json', + 'pack-destination', + 'workspace', + 'workspaces', + ] } /* istanbul ignore next - see test/lib/load-all-commands.js */ @@ -68,9 +74,10 @@ class Pack extends BaseCommand { for (const { arg, filename, manifest } of manifests) { const tarballData = await libpack(arg, this.npm.flatOptions) const pkgContents = await getContents(manifest, tarballData) + const tarballFilename = path.resolve(this.npm.config.get('pack-destination'), filename) if (!dryRun) - await writeFile(filename, tarballData) + await writeFile(tarballFilename, tarballData) tarballs.push(pkgContents) } @@ -97,9 +104,8 @@ class Pack extends BaseCommand { return this.pack(args) } - const workspaces = - await getWorkspaces(filters, { path: this.npm.localPrefix }) - return this.pack([...workspaces.values(), ...args.filter(a => a !== '.')]) + await this.setWorkspaces(filters) + return this.pack([...this.workspacePaths, ...args.filter(a => a !== '.')]) } } module.exports = Pack diff --git a/lib/prune.js b/lib/prune.js index a90e7595421ec..a91276fc4fa27 100644 --- a/lib/prune.js +++ b/lib/prune.js @@ -34,7 +34,7 @@ class Prune extends ArboristWorkspaceCmd { ...this.npm.flatOptions, path: where, log: this.npm.log, - workspaces: this.workspaces, + workspaces: this.workspaceNames, } const arb = new Arborist(opts) await arb.prune(opts) diff --git a/lib/publish.js b/lib/publish.js index 3cb8b0627e974..f35388a30f4ed 100644 --- a/lib/publish.js +++ b/lib/publish.js @@ -11,7 +11,6 @@ const chalk = require('chalk') const otplease = require('./utils/otplease.js') const { getContents, logTar } = require('./utils/tar.js') -const getWorkspaces = require('./workspaces/get-workspaces.js') // for historical reasons, publishConfig in package.json can contain ANY config // keys that npm supports in .npmrc files and elsewhere. We *may* want to @@ -138,7 +137,7 @@ class Publish extends BaseCommand { }) } - if (!this.workspaces) { + if (!this.suppressOutput) { if (!silent && json) this.npm.output(JSON.stringify(pkgContents, null, 2)) else if (!silent) @@ -150,17 +149,16 @@ class Publish extends BaseCommand { async publishWorkspaces (args, filters) { // Suppresses JSON output in publish() so we can handle it here - this.workspaces = true + this.suppressOutput = true const results = {} const json = this.npm.config.get('json') const silent = log.level === 'silent' const noop = a => a const color = this.npm.color ? chalk : { green: noop, bold: noop } - const workspaces = - await getWorkspaces(filters, { path: this.npm.localPrefix }) + await this.setWorkspaces(filters) - for (const [name, workspace] of workspaces.entries()) { + for (const [name, workspace] of this.workspaces.entries()) { let pkgContents try { pkgContents = await this.publish([workspace]) diff --git a/lib/rebuild.js b/lib/rebuild.js index ef88dc5168d0c..9aa0e27f87eb4 100644 --- a/lib/rebuild.js +++ b/lib/rebuild.js @@ -47,7 +47,7 @@ class Rebuild extends ArboristWorkspaceCmd { ...this.npm.flatOptions, path: where, // TODO when extending ReifyCmd - // workspaces: this.workspaces, + // workspaces: this.workspaceNames, }) if (args.length) { diff --git a/lib/repo.js b/lib/repo.js index 645c0eeae32fe..e0172d01f63d1 100644 --- a/lib/repo.js +++ b/lib/repo.js @@ -1,6 +1,5 @@ const log = require('npmlog') const pacote = require('pacote') -const getWorkspaces = require('./workspaces/get-workspaces.js') const { URL } = require('url') const hostedFromMani = require('./utils/hosted-git-info-from-manifest.js') @@ -44,9 +43,8 @@ class Repo extends BaseCommand { } async repoWorkspaces (args, filters) { - const workspaces = - await getWorkspaces(filters, { path: this.npm.localPrefix }) - return this.repo([...workspaces.values()]) + await this.setWorkspaces(filters) + return this.repo(this.workspacePaths) } async get (pkg) { diff --git a/lib/run-script.js b/lib/run-script.js index ec1042828fad8..b94d2fce07180 100644 --- a/lib/run-script.js +++ b/lib/run-script.js @@ -6,7 +6,6 @@ const rpj = require('read-package-json-fast') const log = require('npmlog') const didYouMean = require('./utils/did-you-mean.js') const isWindowsShell = require('./utils/is-windows-shell.js') -const getWorkspaces = require('./workspaces/get-workspaces.js') const cmdList = [ 'publish', @@ -195,10 +194,9 @@ class RunScript extends BaseCommand { async runWorkspaces (args, filters) { const res = [] - const workspaces = - await getWorkspaces(filters, { path: this.npm.localPrefix }) + await this.setWorkspaces(filters) - for (const workspacePath of workspaces.values()) { + for (const workspacePath of this.workspacePaths) { const pkg = await rpj(`${workspacePath}/package.json`) const runResult = await this.run(args, { path: workspacePath, @@ -227,15 +225,14 @@ class RunScript extends BaseCommand { } async listWorkspaces (args, filters) { - const workspaces = - await getWorkspaces(filters, { path: this.npm.localPrefix }) + await this.setWorkspaces(filters) if (log.level === 'silent') return if (this.npm.config.get('json')) { const res = {} - for (const workspacePath of workspaces.values()) { + for (const workspacePath of this.workspacePaths) { const { scripts, name } = await rpj(`${workspacePath}/package.json`) res[name] = { ...scripts } } @@ -244,7 +241,7 @@ class RunScript extends BaseCommand { } if (this.npm.config.get('parseable')) { - for (const workspacePath of workspaces.values()) { + for (const workspacePath of this.workspacePaths) { const { scripts, name } = await rpj(`${workspacePath}/package.json`) for (const [script, cmd] of Object.entries(scripts || {})) this.npm.output(`${name}:${script}:${cmd}`) @@ -252,7 +249,7 @@ class RunScript extends BaseCommand { return } - for (const workspacePath of workspaces.values()) + for (const workspacePath of this.workspacePaths) await this.list(args, workspacePath) } } diff --git a/lib/set-script.js b/lib/set-script.js index b31e123becd8b..cd01e28b56b06 100644 --- a/lib/set-script.js +++ b/lib/set-script.js @@ -3,7 +3,6 @@ const fs = require('fs') const parseJSON = require('json-parse-even-better-errors') const rpj = require('read-package-json-fast') const { resolve } = require('path') -const getWorkspaces = require('./workspaces/get-workspaces.js') const BaseCommand = require('./base-command.js') class SetScript extends BaseCommand { @@ -47,28 +46,27 @@ class SetScript extends BaseCommand { } exec (args, cb) { - this.set(args).then(() => cb()).catch(cb) + this.setScript(args).then(() => cb()).catch(cb) } - async set (args) { + async setScript (args) { this.validate(args) - const warn = this.setScript(this.npm.localPrefix, args[0], args[1]) + const warn = this.doSetScript(this.npm.localPrefix, args[0], args[1]) if (warn) log.warn('set-script', `Script "${args[0]}" was overwritten`) } execWorkspaces (args, filters, cb) { - this.setWorkspaces(args, filters).then(() => cb()).catch(cb) + this.setScriptWorkspaces(args, filters).then(() => cb()).catch(cb) } - async setWorkspaces (args, filters) { + async setScriptWorkspaces (args, filters) { this.validate(args) - const workspaces = - await getWorkspaces(filters, { path: this.npm.localPrefix }) + await this.setWorkspaces(filters) - for (const [name, path] of workspaces) { + for (const [name, path] of this.workspaces) { try { - const warn = this.setScript(path, args[0], args[1]) + const warn = this.doSetScript(path, args[0], args[1]) if (warn) { log.warn('set-script', `Script "${args[0]}" was overwritten`) log.warn(` in workspace: ${name}`) @@ -86,7 +84,7 @@ class SetScript extends BaseCommand { // returns a Boolean that will be true if // the requested script was overwritten // and false if it was set as a new script - setScript (path, name, value) { + doSetScript (path, name, value) { // Set the script let manifest let warn = false diff --git a/lib/uninstall.js b/lib/uninstall.js index cbbc62c2a4a75..fbb2cef0fbf18 100644 --- a/lib/uninstall.js +++ b/lib/uninstall.js @@ -66,7 +66,7 @@ class Uninstall extends ArboristWorkspaceCmd { path, log: this.npm.log, rm: args, - workspaces: this.workspaces, + workspaces: this.workspaceNames, } const arb = new Arborist(opts) await arb.reify(opts) diff --git a/lib/unpublish.js b/lib/unpublish.js index 1571fd88ef781..32a634013a7c4 100644 --- a/lib/unpublish.js +++ b/lib/unpublish.js @@ -6,7 +6,6 @@ const npmFetch = require('npm-registry-fetch') const libunpub = require('libnpmpublish').unpublish const readJson = util.promisify(require('read-package-json')) -const getWorkspaces = require('./workspaces/get-workspaces.js') const otplease = require('./utils/otplease.js') const getIdentity = require('./utils/get-identity.js') @@ -129,8 +128,7 @@ class Unpublish extends BaseCommand { } async unpublishWorkspaces (args, filters) { - const workspaces = - await getWorkspaces(filters, { path: this.npm.localPrefix }) + await this.setWorkspaces(filters) const force = this.npm.config.get('force') if (!force) { @@ -140,7 +138,7 @@ class Unpublish extends BaseCommand { ) } - for (const [name] of workspaces.entries()) + for (const name of this.workspaceNames) await this.unpublish([name]) } } diff --git a/lib/update.js b/lib/update.js index 3cdeb8ea7dd31..393c8f0f67e5f 100644 --- a/lib/update.js +++ b/lib/update.js @@ -66,7 +66,7 @@ class Update extends ArboristWorkspaceCmd { ...this.npm.flatOptions, log: this.npm.log, path: where, - workspaces: this.workspaces, + workspaces: this.workspaceNames, }) await arb.reify({ update }) diff --git a/lib/utils/cache-file.js b/lib/utils/cache-file.js deleted file mode 100644 index b33881e872ec2..0000000000000 --- a/lib/utils/cache-file.js +++ /dev/null @@ -1,66 +0,0 @@ -const npm = require('../npm.js') -const path = require('path') -const chownr = require('chownr') -const writeFileAtomic = require('write-file-atomic') -const mkdirp = require('mkdirp-infer-owner') -const fs = require('graceful-fs') - -let cache = null -let cacheUid = null -let cacheGid = null -let needChown = typeof process.getuid === 'function' - -const getCacheOwner = () => { - let st - try { - st = fs.lstatSync(cache) - } catch (er) { - if (er.code !== 'ENOENT') - throw er - - st = fs.lstatSync(path.dirname(cache)) - } - - cacheUid = st.uid - cacheGid = st.gid - - needChown = st.uid !== process.getuid() || - st.gid !== process.getgid() -} - -const writeOrAppend = (method, file, data) => { - if (!cache) - cache = npm.config.get('cache') - - // redundant if already absolute, but prevents non-absolute files - // from being written as if they're part of the cache. - file = path.resolve(cache, file) - - if (cacheUid === null && needChown) - getCacheOwner() - - const dir = path.dirname(file) - const firstMade = mkdirp.sync(dir) - - if (!needChown) - return method(file, data) - - let methodThrew = true - try { - method(file, data) - methodThrew = false - } finally { - // always try to leave it in the right ownership state, even on failure - // let the method error fail it instead of the chownr error, though - if (!methodThrew) - chownr.sync(firstMade || file, cacheUid, cacheGid) - else { - try { - chownr.sync(firstMade || file, cacheUid, cacheGid) - } catch (_) {} - } - } -} - -exports.append = (file, data) => writeOrAppend(fs.appendFileSync, file, data) -exports.write = (file, data) => writeOrAppend(writeFileAtomic.sync, file, data) diff --git a/lib/utils/config/definitions.js b/lib/utils/config/definitions.js index ce7702aaa4f79..d540b0fc67e82 100644 --- a/lib/utils/config/definitions.js +++ b/lib/utils/config/definitions.js @@ -322,6 +322,7 @@ define('cache', { `, flatten (key, obj, flatOptions) { flatOptions.cache = join(obj.cache, '_cacache') + flatOptions.npxCache = join(obj.cache, '_npx') }, }) @@ -1086,18 +1087,8 @@ define('link', { default: false, type: Boolean, description: ` - If true, then local installs will link if there is a suitable globally - installed package. - - Note that this means that local installs can cause things to be installed - into the global space at the same time. The link is only done if one of - the two conditions are met: - - * The package is not already installed globally, or - * the globally installed version is identical to the version that is - being installed locally. - - When used with \`npm ls\`, only show packages that are linked. + Used with \`npm ls\`, limiting output to only those packages that are + linked. `, }) @@ -1333,12 +1324,26 @@ define('package-lock-only', { default: false, type: Boolean, description: ` - If set to true, it will update only the \`package-lock.json\`, instead of - checking \`node_modules\` and downloading dependencies. + If set to true, the current operation will only use the \`package-lock.json\`, + ignoring \`node_modules\`. + + For \`update\` this means only the \`package-lock.json\` will be updated, + instead of checking \`node_modules\` and downloading dependencies. + + For \`list\` this means the output will be based on the tree described by the + \`package-lock.json\`, rather than the contents of \`node_modules\`. `, flatten, }) +define('pack-destination', { + default: '.', + type: String, + description: ` + Directory in which \`npm pack\` will save tarballs. + `, +}) + define('parseable', { default: false, type: Boolean, diff --git a/lib/utils/error-handler.js b/lib/utils/error-handler.js index da716679d2705..f40e1f04fb180 100644 --- a/lib/utils/error-handler.js +++ b/lib/utils/error-handler.js @@ -1,28 +1,27 @@ +let npm // set by the cli let cbCalled = false const log = require('npmlog') -const npm = require('../npm.js') let itWorked = false const path = require('path') +const writeFileAtomic = require('write-file-atomic') +const mkdirp = require('mkdirp-infer-owner') +const fs = require('graceful-fs') let wroteLogFile = false let exitCode = 0 const errorMessage = require('./error-message.js') const replaceInfo = require('./replace-info.js') -const cacheFile = require('./cache-file.js') - let logFileName const getLogFile = () => { + // we call this multiple times, so we need to treat it as a singleton because + // the date is part of the name if (!logFileName) logFileName = path.resolve(npm.config.get('cache'), '_logs', (new Date()).toISOString().replace(/[.:]/g, '_') + '-debug.log') return logFileName } -const timings = { - version: npm.version, - command: process.argv.slice(2), - logfile: null, -} +const timings = {} process.on('timing', (name, value) => { if (timings[name]) timings[name] += value @@ -35,9 +34,21 @@ process.on('exit', code => { log.disableProgress() if (npm.config && npm.config.loaded && npm.config.get('timing')) { try { - timings.logfile = getLogFile() - cacheFile.append('_timing.json', JSON.stringify(timings) + '\n') - } catch (_) { + const file = path.resolve(npm.config.get('cache'), '_timing.json') + const dir = path.dirname(npm.config.get('cache')) + mkdirp.sync(dir) + + fs.appendFileSync(file, JSON.stringify({ + command: process.argv.slice(2), + logfile: getLogFile(), + version: npm.version, + ...timings, + }) + '\n') + + const st = fs.lstatSync(path.dirname(npm.config.get('cache'))) + fs.chownSync(dir, st.uid, st.gid) + fs.chownSync(file, st.uid, st.gid) + } catch (ex) { // ignore } } @@ -174,7 +185,7 @@ const errorHandler = (er) => { log.error(k, v) } - const msg = errorMessage(er) + const msg = errorMessage(er, npm) for (const errline of [...msg.summary, ...msg.detail]) log.error(...errline) @@ -214,7 +225,15 @@ const writeLogFile = () => { logOutput += line + os.EOL }) }) - cacheFile.write(getLogFile(), logOutput) + + const file = getLogFile() + const dir = path.dirname(file) + mkdirp.sync(dir) + writeFileAtomic.sync(file, logOutput) + + const st = fs.lstatSync(path.dirname(npm.config.get('cache'))) + fs.chownSync(dir, st.uid, st.gid) + fs.chownSync(file, st.uid, st.gid) // truncate once it's been written. log.record.length = 0 @@ -226,3 +245,6 @@ const writeLogFile = () => { module.exports = errorHandler module.exports.exit = exit +module.exports.setNpm = (n) => { + npm = n +} diff --git a/lib/utils/error-message.js b/lib/utils/error-message.js index ac5a935dc8770..125cdf8c53581 100644 --- a/lib/utils/error-message.js +++ b/lib/utils/error-message.js @@ -1,12 +1,11 @@ -const npm = require('../npm.js') const { format } = require('util') const { resolve } = require('path') const nameValidator = require('validate-npm-package-name') const npmlog = require('npmlog') const replaceInfo = require('./replace-info.js') -const { report: explainEresolve } = require('./explain-eresolve.js') +const { report } = require('./explain-eresolve.js') -module.exports = (er) => { +module.exports = (er, npm) => { const short = [] const detail = [] @@ -19,7 +18,7 @@ module.exports = (er) => { case 'ERESOLVE': short.push(['ERESOLVE', er.message]) detail.push(['', '']) - detail.push(['', explainEresolve(er)]) + detail.push(['', report(er, npm.color, resolve(npm.cache, 'eresolve-report.txt'))]) break case 'ENOLOCK': { diff --git a/lib/utils/explain-eresolve.js b/lib/utils/explain-eresolve.js index cda77aff94113..b35a32c6f935d 100644 --- a/lib/utils/explain-eresolve.js +++ b/lib/utils/explain-eresolve.js @@ -1,20 +1,14 @@ // this is called when an ERESOLVE error is caught in the error-handler, // or when there's a log.warn('eresolve', msg, explanation), to turn it // into a human-intelligible explanation of what's wrong and how to fix. -// -// TODO: abstract out the explainNode methods into a separate util for -// use by a future `npm explain ` command. - -const npm = require('../npm.js') const { writeFileSync } = require('fs') -const { resolve } = require('path') const { explainEdge, explainNode, printNode } = require('./explain-dep.js') // expl is an explanation object that comes from Arborist. It looks like: // Depth is how far we want to want to descend into the object making a report. // The full report (ie, depth=Infinity) is always written to the cache folder // at ${cache}/eresolve-report.txt along with full json. -const explainEresolve = (expl, color, depth) => { +const explain = (expl, color, depth) => { const { edge, current, peerConflict, currentEdge } = expl const out = [] @@ -42,9 +36,7 @@ const explainEresolve = (expl, color, depth) => { } // generate a full verbose report and tell the user how to fix it -const report = (expl, depth = 4) => { - const fullReport = resolve(npm.cache, 'eresolve-report.txt') - +const report = (expl, color, fullReport) => { const orNoStrict = expl.strictPeerDeps ? '--no-strict-peer-deps, ' : '' const fix = `Fix the upstream dependency conflict, or retry this command with ${orNoStrict}--force, or --legacy-peer-deps @@ -54,7 +46,7 @@ to accept an incorrect (and potentially broken) dependency resolution.` ${new Date().toISOString()} -${explainEresolve(expl, false, Infinity)} +${explain(expl, false, Infinity)} ${fix} @@ -63,13 +55,10 @@ Raw JSON explanation object: ${JSON.stringify(expl, null, 2)} `, 'utf8') - return explainEresolve(expl, npm.color, depth) + + return explain(expl, color, 4) + `\n\n${fix}\n\nSee ${fullReport} for a full report.` } -// the terser explain method for the warning when using --force -const explain = (expl, depth = 2) => explainEresolve(expl, npm.color, depth) - module.exports = { explain, report, diff --git a/lib/utils/setup-log.js b/lib/utils/setup-log.js index 44e612d50dc9f..9ee79d192d9ea 100644 --- a/lib/utils/setup-log.js +++ b/lib/utils/setup-log.js @@ -14,10 +14,22 @@ module.exports = (config) => { const { warn } = log + const stdoutTTY = process.stdout.isTTY + const stderrTTY = process.stderr.isTTY + const dumbTerm = process.env.TERM === 'dumb' + const stderrNotDumb = stderrTTY && !dumbTerm + const enableColorStderr = color === 'always' ? true + : color === false ? false + : stderrTTY + + const enableColorStdout = color === 'always' ? true + : color === false ? false + : stdoutTTY + log.warn = (heading, ...args) => { if (heading === 'ERESOLVE' && args[1] && typeof args[1] === 'object') { warn(heading, args[0]) - return warn('', explain(args[1])) + return warn('', explain(args[1], enableColorStdout, 2)) } return warn(heading, ...args) } @@ -29,19 +41,6 @@ module.exports = (config) => { log.heading = config.get('heading') || 'npm' - const stdoutTTY = process.stdout.isTTY - const stderrTTY = process.stderr.isTTY - const dumbTerm = process.env.TERM === 'dumb' - const stderrNotDumb = stderrTTY && !dumbTerm - - const enableColorStderr = color === 'always' ? true - : color === false ? false - : stderrTTY - - const enableColorStdout = color === 'always' ? true - : color === false ? false - : stdoutTTY - if (enableColorStderr) log.enableColor() else diff --git a/lib/version.js b/lib/version.js index 3ef3801e74e81..f3680fe8b7a01 100644 --- a/lib/version.js +++ b/lib/version.js @@ -3,7 +3,6 @@ const { resolve } = require('path') const { promisify } = require('util') const readFile = promisify(require('fs').readFile) -const getWorkspaces = require('./workspaces/get-workspaces.js') const BaseCommand = require('./base-command.js') class Version extends BaseCommand { @@ -93,9 +92,8 @@ class Version extends BaseCommand { async changeWorkspaces (args, filters) { const prefix = this.npm.config.get('tag-version-prefix') - const workspaces = - await getWorkspaces(filters, { path: this.npm.localPrefix }) - for (const [name, path] of workspaces) { + await this.setWorkspaces(filters) + for (const [name, path] of this.workspaces) { this.npm.output(name) const version = await libnpmversion(args[0], { ...this.npm.flatOptions, @@ -128,11 +126,10 @@ class Version extends BaseCommand { async listWorkspaces (filters) { const results = {} - const workspaces = - await getWorkspaces(filters, { path: this.npm.localPrefix }) - for (const [, path] of workspaces) { + await this.setWorkspaces(filters) + for (const path of this.workspacePaths) { const pj = resolve(path, 'package.json') - // getWorkspaces has already parsed this so we know it won't error + // setWorkspaces has already parsed package.json so we know it won't error const pkg = await readFile(pj, 'utf8') .then(data => JSON.parse(data)) diff --git a/lib/view.js b/lib/view.js index 9cc1aed914488..788df3ed0b4d8 100644 --- a/lib/view.js +++ b/lib/view.js @@ -13,7 +13,6 @@ const semver = require('semver') const style = require('ansistyles') const { inspect, promisify } = require('util') const { packument } = require('pacote') -const getWorkspaces = require('./workspaces/get-workspaces.js') const readFile = promisify(fs.readFile) const readJson = async file => jsonParse(await readFile(file, 'utf8')) @@ -160,10 +159,9 @@ class View extends BaseCommand { args = [''] // getData relies on this } const results = {} - const workspaces = - await getWorkspaces(filters, { path: this.npm.localPrefix }) - for (const workspace of [...workspaces.entries()]) { - const wsPkg = `${workspace[0]}${pkg.slice(1)}` + await this.setWorkspaces(filters) + for (const name of this.workspaceNames) { + const wsPkg = `${name}${pkg.slice(1)}` const [pckmnt, data] = await this.getData(wsPkg, args) let reducedData = data.reduce(reducer, {}) @@ -177,7 +175,7 @@ class View extends BaseCommand { if (wholePackument) data.map((v) => this.prettyView(pckmnt, v[Object.keys(v)[0]][''])) else { - console.log(`${workspace[0]}:`) + console.log(`${name}:`) const msg = await this.jsonData(reducedData, pckmnt._id) if (msg !== '') console.log(msg) @@ -185,7 +183,7 @@ class View extends BaseCommand { } else { const msg = await this.jsonData(reducedData, pckmnt._id) if (msg !== '') - results[workspace[0]] = JSON.parse(msg) + results[name] = JSON.parse(msg) } } if (Object.keys(results).length > 0) diff --git a/lib/workspaces/arborist-cmd.js b/lib/workspaces/arborist-cmd.js index 337e7f9d8f932..cb6b66b8cb257 100644 --- a/lib/workspaces/arborist-cmd.js +++ b/lib/workspaces/arborist-cmd.js @@ -3,7 +3,6 @@ // be able to run a filtered Arborist.reify() at some point. const BaseCommand = require('../base-command.js') -const getWorkspaces = require('./get-workspaces.js') class ArboristCmd extends BaseCommand { /* istanbul ignore next - see test/lib/load-all-commands.js */ static get params () { @@ -14,10 +13,8 @@ class ArboristCmd extends BaseCommand { } execWorkspaces (args, filters, cb) { - getWorkspaces(filters, { path: this.npm.localPrefix }) - .then(workspaces => { - this.workspaces = [...workspaces.keys()] - this.workspacePaths = [...workspaces.values()] + this.setWorkspaces(filters) + .then(() => { this.exec(args, cb) }) .catch(er => cb(er)) 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 73a6f667e35db..5db11eb3832eb 100644 --- a/node_modules/@npmcli/arborist/lib/arborist/build-ideal-tree.js +++ b/node_modules/@npmcli/arborist/lib/arborist/build-ideal-tree.js @@ -7,7 +7,7 @@ const semver = require('semver') const promiseCallLimit = require('promise-call-limit') const getPeerSet = require('../peer-set.js') const realpath = require('../../lib/realpath.js') -const { resolve } = require('path') +const { resolve, dirname } = require('path') const { promisify } = require('util') const treeCheck = require('../tree-check.js') const readdir = promisify(require('readdir-scoped-modules')) @@ -661,7 +661,7 @@ module.exports = cls => class IdealTreeBuilder extends cls { const ancient = meta.ancientLockfile const old = meta.loadedFromDisk && !(meta.originalLockfileVersion >= 2) - if (inventory.size === 0 || !ancient && !(old && this[_complete])) + if (inventory.size === 0 || !ancient && !old) return // if the lockfile is from node v5 or earlier, then we'll have to reload @@ -688,10 +688,12 @@ This is a one-time fix-up, please be patient... this.log.silly('inflate', node.location) const { resolved, version, path, name, location, integrity } = node // don't try to hit the registry for linked deps - const useResolved = !version || - resolved && resolved.startsWith('file:') - const id = useResolved ? resolved : version - const spec = npa.resolve(name, id, path) + const useResolved = resolved && ( + !version || resolved.startsWith('file:') + ) + const id = useResolved ? resolved + : version || `file:${node.path}` + const spec = npa.resolve(name, id, dirname(path)) const sloc = location.substr('node_modules/'.length) const t = `idealTree:inflate:${sloc}` this.addTracker(t) diff --git a/node_modules/@npmcli/arborist/lib/arborist/index.js b/node_modules/@npmcli/arborist/lib/arborist/index.js index b119d3117bc23..94501cae12c84 100644 --- a/node_modules/@npmcli/arborist/lib/arborist/index.js +++ b/node_modules/@npmcli/arborist/lib/arborist/index.js @@ -28,7 +28,7 @@ const {resolve} = require('path') const {homedir} = require('os') -const procLog = require('../proc-log.js') +const procLog = require('proc-log') const { saveTypeMap } = require('../add-rm-pkg-deps.js') const mixins = [ diff --git a/node_modules/@npmcli/arborist/lib/arborist/load-actual.js b/node_modules/@npmcli/arborist/lib/arborist/load-actual.js index d9e7fb46d6df9..9fca7d6425da0 100644 --- a/node_modules/@npmcli/arborist/lib/arborist/load-actual.js +++ b/node_modules/@npmcli/arborist/lib/arborist/load-actual.js @@ -22,6 +22,7 @@ const _loadFSTree = Symbol('loadFSTree') const _loadFSChildren = Symbol('loadFSChildren') const _findMissingEdges = Symbol('findMissingEdges') const _findFSParents = Symbol('findFSParents') +const _resetDepFlags = Symbol('resetDepFlags') const _actualTreeLoaded = Symbol('actualTreeLoaded') const _rpcache = Symbol.for('realpathCache') @@ -74,6 +75,19 @@ module.exports = cls => class ActualLoader extends cls { this[_topNodes] = new Set() } + [_resetDepFlags] (tree, root) { + // reset all deps to extraneous prior to recalc + if (!root) { + for (const node of tree.inventory.values()) + node.extraneous = true + } + + // only reset root flags if we're not re-rooting, + // otherwise leave as-is + calcDepFlags(tree, !root) + return tree + } + // public method async loadActual (options = {}) { // allow the user to set options on the ctor as well. @@ -88,6 +102,7 @@ module.exports = cls => class ActualLoader extends cls { return this.actualTree ? this.actualTree : this[_actualTreePromise] ? this[_actualTreePromise] : this[_actualTreePromise] = this[_loadActual](options) + .then(tree => this[_resetDepFlags](tree, options.root)) .then(tree => this.actualTree = treeCheck(tree)) } @@ -152,8 +167,7 @@ module.exports = cls => class ActualLoader extends cls { root: this[_actualTree], }) await this[_loadWorkspaces](this[_actualTree]) - if (this[_actualTree].workspaces && this[_actualTree].workspaces.size) - calcDepFlags(this[_actualTree], !root) + this[_transplant](root) return this[_actualTree] } @@ -178,8 +192,6 @@ module.exports = cls => class ActualLoader extends cls { dependencies[name] = dependencies[name] || '*' actualRoot.package = { ...actualRoot.package, dependencies } } - // only reset root flags if we're not re-rooting, otherwise leave as-is - calcDepFlags(this[_actualTree], !root) return this[_actualTree] } diff --git a/node_modules/@npmcli/arborist/lib/arborist/reify.js b/node_modules/@npmcli/arborist/lib/arborist/reify.js index 58dc7c68a4a40..55360538b901a 100644 --- a/node_modules/@npmcli/arborist/lib/arborist/reify.js +++ b/node_modules/@npmcli/arborist/lib/arborist/reify.js @@ -2,7 +2,6 @@ const onExit = require('../signal-handling.js') const pacote = require('pacote') -const rpj = require('read-package-json-fast') const AuditReport = require('../audit-report.js') const {subset, intersects} = require('semver') const npa = require('npm-package-arg') @@ -57,7 +56,6 @@ const _extractOrLink = Symbol('extractOrLink') const _checkBins = Symbol.for('checkBins') const _symlink = Symbol('symlink') const _warnDeprecated = Symbol('warnDeprecated') -const _loadAncientPackageDetails = Symbol('loadAncientPackageDetails') const _loadBundlesAndUpdateTrees = Symbol.for('loadBundlesAndUpdateTrees') const _submitQuickAudit = Symbol('submitQuickAudit') const _awaitQuickAudit = Symbol('awaitQuickAudit') @@ -522,7 +520,6 @@ module.exports = cls => class Reifier extends cls { await this[_checkBins](node) await this[_extractOrLink](node) await this[_warnDeprecated](node) - await this[_loadAncientPackageDetails](node) }) return this[_handleOptionalFailure](node, p) @@ -583,32 +580,6 @@ module.exports = cls => class Reifier extends cls { this.log.warn('deprecated', `${_id}: ${deprecated}`) } - async [_loadAncientPackageDetails] (node, forceReload = false) { - // If we're loading from a v1 lockfile, load details from the package.json - // that weren't recorded in the old format. - const {meta} = this.idealTree - const ancient = meta.ancientLockfile - const old = meta.loadedFromDisk && !(meta.originalLockfileVersion >= 2) - - // already replaced with the manifest if it's truly ancient - if (node.path && (forceReload || (old && !ancient))) { - // XXX should have a shared location where package.json is read, - // so we don't ever read the same pj more than necessary. - let pkg - try { - pkg = await rpj(node.path + '/package.json') - } catch (err) {} - - if (pkg) { - node.package.bin = pkg.bin - node.package.os = pkg.os - node.package.cpu = pkg.cpu - node.package.engines = pkg.engines - meta.add(node) - } - } - } - // if the node is optional, then the failure of the promise is nonfatal // just add it and its optional set to the trash list. [_handleOptionalFailure] (node, p) { @@ -1079,12 +1050,6 @@ module.exports = cls => class Reifier extends cls { const { meta } = this.idealTree - // might have to update metadata for bins and stuff that gets lost - if (meta.loadedFromDisk && !(meta.originalLockfileVersion >= 2)) { - for (const node of this.idealTree.inventory.values()) - await this[_loadAncientPackageDetails](node, true) - } - return meta.save(saveOpt) } diff --git a/node_modules/@npmcli/arborist/lib/audit-report.js b/node_modules/@npmcli/arborist/lib/audit-report.js index 139a7aefd2489..8f7d6546d64f4 100644 --- a/node_modules/@npmcli/arborist/lib/audit-report.js +++ b/node_modules/@npmcli/arborist/lib/audit-report.js @@ -12,7 +12,7 @@ const _fixAvailable = Symbol('fixAvailable') const _checkTopNode = Symbol('checkTopNode') const _init = Symbol('init') const _omit = Symbol('omit') -const procLog = require('./proc-log.js') +const procLog = require('proc-log') const fetch = require('npm-registry-fetch') diff --git a/node_modules/@npmcli/arborist/lib/calc-dep-flags.js b/node_modules/@npmcli/arborist/lib/calc-dep-flags.js index d6ae266db3bb0..21d8ddcf7b442 100644 --- a/node_modules/@npmcli/arborist/lib/calc-dep-flags.js +++ b/node_modules/@npmcli/arborist/lib/calc-dep-flags.js @@ -22,6 +22,11 @@ const calcDepFlagsStep = (node) => { // Since we're only walking through deps that are not already flagged // as non-dev/non-optional, it's typically a very shallow traversal node.extraneous = false + resetParents(node, 'extraneous') + resetParents(node, 'dev') + resetParents(node, 'peer') + resetParents(node, 'devOptional') + resetParents(node, 'optional') // for links, map their hierarchy appropriately if (node.target) { @@ -29,8 +34,7 @@ const calcDepFlagsStep = (node) => { node.target.optional = node.optional node.target.devOptional = node.devOptional node.target.peer = node.peer - node.target.extraneous = false - node = node.target + return calcDepFlagsStep(node.target) } node.edgesOut.forEach(({peer, optional, dev, to}) => { @@ -71,6 +75,14 @@ const calcDepFlagsStep = (node) => { return node } +const resetParents = (node, flag) => { + if (node[flag]) + return + + for (let p = node; p && (p === node || p[flag]); p = p.resolveParent) + p[flag] = false +} + // typically a short walk, since it only traverses deps that // have the flag set. const unsetFlag = (node, flag) => { diff --git a/node_modules/@npmcli/arborist/lib/inventory.js b/node_modules/@npmcli/arborist/lib/inventory.js index 7578291885223..a4ae11c2ab41e 100644 --- a/node_modules/@npmcli/arborist/lib/inventory.js +++ b/node_modules/@npmcli/arborist/lib/inventory.js @@ -7,6 +7,20 @@ const _index = Symbol('_index') const defaultKeys = ['name', 'license', 'funding', 'realpath', 'packageName'] const { hasOwnProperty } = Object.prototype const debug = require('./debug.js') + +// handling for the outdated "licenses" array, just pick the first one +// also support the alternative spelling "licence" +const getLicense = pkg => { + if (pkg) { + const lic = pkg.license || pkg.licence + if (lic) + return lic + const lics = pkg.licenses || pkg.licences + if (Array.isArray(lics)) + return lics[0] + } +} + class Inventory extends Map { constructor (opt = {}) { const { primary, keys } = opt @@ -56,7 +70,9 @@ class Inventory extends Map { for (const [key, map] of this[_index].entries()) { // if the node has the value, but it's false, then use that const val_ = hasOwnProperty.call(node, key) ? node[key] - : node[key] || (node.package && node.package[key]) + : key === 'license' ? getLicense(node.package) + : node[key] ? node[key] + : node.package && node.package[key] const val = typeof val_ === 'string' ? val_ : !val_ || typeof val_ !== 'object' ? val_ : key === 'license' ? val_.type diff --git a/node_modules/@npmcli/arborist/lib/node.js b/node_modules/@npmcli/arborist/lib/node.js index 1683c7049504e..c21bc46cfb539 100644 --- a/node_modules/@npmcli/arborist/lib/node.js +++ b/node_modules/@npmcli/arborist/lib/node.js @@ -547,6 +547,8 @@ class Node { // try to find our parent/fsParent in the new root inventory for (const p of walkUp(dirname(this.path))) { + if (p === this.path) + continue const ploc = relpath(root.realpath, p) const parent = root.inventory.get(ploc) if (parent) { @@ -783,7 +785,13 @@ class Node { } get fsParent () { - return this[_fsParent] + const parent = this[_fsParent] + /* istanbul ignore next - should be impossible */ + debug(() => { + if (parent === this) + throw new Error('node set to its own fsParent') + }) + return parent } set fsParent (fsParent) { @@ -1009,7 +1017,13 @@ class Node { } get parent () { - return this[_parent] + const parent = this[_parent] + /* istanbul ignore next - should be impossible */ + debug(() => { + if (parent === this) + throw new Error('node set to its own parent') + }) + return parent } // This setter keeps everything in order when we move a node from diff --git a/node_modules/@npmcli/arborist/lib/proc-log.js b/node_modules/@npmcli/arborist/lib/proc-log.js deleted file mode 100644 index 52e0e466798ee..0000000000000 --- a/node_modules/@npmcli/arborist/lib/proc-log.js +++ /dev/null @@ -1,21 +0,0 @@ -// default logger. -// emits 'log' events on the process -const LEVELS = [ - 'notice', - 'error', - 'warn', - 'info', - 'verbose', - 'http', - 'silly', - 'pause', - 'resume', -] - -const log = level => (...args) => process.emit('log', level, ...args) - -const logger = {} -for (const level of LEVELS) - logger[level] = log(level) - -module.exports = logger diff --git a/node_modules/@npmcli/arborist/lib/shrinkwrap.js b/node_modules/@npmcli/arborist/lib/shrinkwrap.js index 0a19ef93005ad..9fb0528db497c 100644 --- a/node_modules/@npmcli/arborist/lib/shrinkwrap.js +++ b/node_modules/@npmcli/arborist/lib/shrinkwrap.js @@ -32,7 +32,7 @@ const mismatch = (a, b) => a && b && a !== b // After calling this.commit(), any nodes not present in the tree will have // been removed from the shrinkwrap data as well. -const procLog = require('./proc-log.js') +const procLog = require('proc-log') const YarnLock = require('./yarn-lock.js') const {promisify} = require('util') const rimraf = promisify(require('rimraf')) diff --git a/node_modules/@npmcli/arborist/lib/tracker.js b/node_modules/@npmcli/arborist/lib/tracker.js index 47267872ce780..aefd5fe1bbf58 100644 --- a/node_modules/@npmcli/arborist/lib/tracker.js +++ b/node_modules/@npmcli/arborist/lib/tracker.js @@ -1,6 +1,6 @@ const _progress = Symbol('_progress') const _onError = Symbol('_onError') -const procLog = require('./proc-log.js') +const procLog = require('proc-log') module.exports = cls => class Tracker extends cls { constructor (options = {}) { diff --git a/node_modules/@npmcli/arborist/package.json b/node_modules/@npmcli/arborist/package.json index 7c2622f49e93e..bd27e4bbffa20 100644 --- a/node_modules/@npmcli/arborist/package.json +++ b/node_modules/@npmcli/arborist/package.json @@ -1,6 +1,6 @@ { "name": "@npmcli/arborist", - "version": "2.6.2", + "version": "2.6.3", "description": "Manage node_modules trees", "dependencies": { "@npmcli/installed-package-contents": "^1.0.7", @@ -22,6 +22,7 @@ "npm-registry-fetch": "^11.0.0", "pacote": "^11.2.6", "parse-conflict-json": "^1.1.1", + "proc-log": "^1.0.0", "promise-all-reject-late": "^1.0.0", "promise-call-limit": "^1.0.1", "read-package-json-fast": "^2.0.2", diff --git a/node_modules/libnpmdiff b/node_modules/libnpmdiff new file mode 120000 index 0000000000000..ae8dd62893029 --- /dev/null +++ b/node_modules/libnpmdiff @@ -0,0 +1 @@ +../packages/libnpmdiff \ No newline at end of file diff --git a/node_modules/libnpmexec/lib/cache-install-dir.js b/node_modules/libnpmexec/lib/cache-install-dir.js index 9e30d62a1e102..4fb534f7dfe12 100644 --- a/node_modules/libnpmexec/lib/cache-install-dir.js +++ b/node_modules/libnpmexec/lib/cache-install-dir.js @@ -2,12 +2,12 @@ const crypto = require('crypto') const { resolve } = require('path') -const cacheInstallDir = ({ cache, packages }) => { - if (!cache) - throw new Error('Must provide a valid cache path') +const cacheInstallDir = ({ npxCache, packages }) => { + if (!npxCache) + throw new Error('Must provide a valid npxCache path') // only packages not found in ${prefix}/node_modules - return resolve(cache, '_npx', getHash(packages)) + return resolve(npxCache, getHash(packages)) } const getHash = (packages) => diff --git a/node_modules/libnpmexec/lib/index.js b/node_modules/libnpmexec/lib/index.js index 8c5181f397519..57c2a148d3489 100644 --- a/node_modules/libnpmexec/lib/index.js +++ b/node_modules/libnpmexec/lib/index.js @@ -124,8 +124,8 @@ const exec = async (opts) => { manis.some(manifest => manifestMissing({ tree, manifest })) if (needInstall) { - const { cache } = flatOptions - const installDir = cacheInstallDir({ cache, packages }) + const { npxCache } = flatOptions + const installDir = cacheInstallDir({ npxCache, packages }) await mkdirp(installDir) const arb = new Arborist({ ...flatOptions, diff --git a/node_modules/libnpmexec/package.json b/node_modules/libnpmexec/package.json index 2b3b488cf079f..dff91077d148a 100644 --- a/node_modules/libnpmexec/package.json +++ b/node_modules/libnpmexec/package.json @@ -1,6 +1,6 @@ { "name": "libnpmexec", - "version": "1.2.0", + "version": "2.0.0", "files": [ "lib" ], diff --git a/node_modules/libnpmversion/lib/retrieve-tag.js b/node_modules/libnpmversion/lib/retrieve-tag.js index 7aa2abfda8651..6adb6df317a8d 100644 --- a/node_modules/libnpmversion/lib/retrieve-tag.js +++ b/node_modules/libnpmversion/lib/retrieve-tag.js @@ -2,7 +2,7 @@ const { spawn } = require('@npmcli/git') const semver = require('semver') module.exports = async opts => { - const tag = (await spawn(['describe', '--tags', '--abbrev=0', '--match=\'*.*.*\''], opts)).stdout.trim() + const tag = (await spawn(['describe', '--tags', '--abbrev=0', '--match=*.*.*'], opts)).stdout.trim() const ver = semver.coerce(tag, { loose: true }) if (ver) { return ver.version diff --git a/node_modules/libnpmversion/package.json b/node_modules/libnpmversion/package.json index ebc88a1fc5754..1ee2ee5995a52 100644 --- a/node_modules/libnpmversion/package.json +++ b/node_modules/libnpmversion/package.json @@ -1,6 +1,6 @@ { "name": "libnpmversion", - "version": "1.2.0", + "version": "1.2.1", "main": "lib/index.js", "files": [ "lib/*.js" diff --git a/node_modules/make-fetch-happen/lib/cache/entry.js b/node_modules/make-fetch-happen/lib/cache/entry.js index 0df006fe34a3f..41f8a3d215ee1 100644 --- a/node_modules/make-fetch-happen/lib/cache/entry.js +++ b/node_modules/make-fetch-happen/lib/cache/entry.js @@ -145,6 +145,12 @@ class CacheEntry { return } + // a cache mode of 'reload' means to behave as though we have no cache + // on the way to the network. return undefined to allow cacheFetch to + // create a brand new request no matter what. + if (options.cache === 'reload') + return + // find the specific entry that satisfies the request let match for (const entry of matches) { diff --git a/node_modules/make-fetch-happen/lib/cache/index.js b/node_modules/make-fetch-happen/lib/cache/index.js index 00df31dd15023..cca93d9b4eb5d 100644 --- a/node_modules/make-fetch-happen/lib/cache/index.js +++ b/node_modules/make-fetch-happen/lib/cache/index.js @@ -7,7 +7,7 @@ const cacheFetch = async (request, options) => { // try to find a cached entry that satisfies this request const entry = await CacheEntry.find(request, options) if (!entry) { - // no cached result, if the cache mode is only-if-cached that's a failure + // no cached result, if the cache mode is 'only-if-cached' that's a failure if (options.cache === 'only-if-cached') throw new NotCachedError(request.url) @@ -17,22 +17,21 @@ const cacheFetch = async (request, options) => { return entry.store('miss') } - // we have a cached response that satisfies this request, however - // if the cache mode is reload the user explicitly wants us to revalidate - if (options.cache === 'reload') + // we have a cached response that satisfies this request, however if the cache + // mode is 'no-cache' then we send the revalidation request no matter what + if (options.cache === 'no-cache') return entry.revalidate(request, options) - // if the cache mode is either force-cache or only-if-cached we will only - // respond with a cached entry, even if it's stale. set the status to the - // appropriate value based on whether revalidation is needed and respond - // from the cache + // if the cached entry is not stale, or if the cache mode is 'force-cache' or + // 'only-if-cached' we can respond with the cached entry. set the status + // based on the result of needsRevalidation and respond const _needsRevalidation = entry.policy.needsRevalidation(request) if (options.cache === 'force-cache' || options.cache === 'only-if-cached' || !_needsRevalidation) return entry.respond(request.method, options, _needsRevalidation ? 'stale' : 'hit') - // cache entry might be stale, revalidate it and return a response + // if we got here, the cache entry is stale so revalidate it return entry.revalidate(request, options) } diff --git a/node_modules/make-fetch-happen/package.json b/node_modules/make-fetch-happen/package.json index af97a161c6088..44330998bb02f 100644 --- a/node_modules/make-fetch-happen/package.json +++ b/node_modules/make-fetch-happen/package.json @@ -1,6 +1,6 @@ { "name": "make-fetch-happen", - "version": "9.0.2", + "version": "9.0.3", "description": "Opinionated, caching, retrying fetch client", "main": "lib/index.js", "files": [ diff --git a/node_modules/npm-package-arg/npa.js b/node_modules/npm-package-arg/npa.js index 3a01d4d907192..191befeb5e69d 100644 --- a/node_modules/npm-package-arg/npa.js +++ b/node_modules/npm-package-arg/npa.js @@ -6,7 +6,7 @@ module.exports.Result = Result const url = require('url') const HostedGit = require('hosted-git-info') const semver = require('semver') -const path = require('path') +const path = global.FAKE_WINDOWS ? require('path').win32 : require('path') const validatePackageName = require('validate-npm-package-name') const { homedir } = require('os') @@ -151,42 +151,86 @@ function setGitCommittish (res, committish) { return res } -const isAbsolutePath = /^[/]|^[A-Za-z]:/ - -function resolvePath (where, spec) { - if (isAbsolutePath.test(spec)) - return spec - return path.resolve(where, spec) -} - -function isAbsolute (dir) { - if (dir[0] === '/') - return true - if (/^[A-Za-z]:/.test(dir)) - return true - return false -} - function fromFile (res, where) { if (!where) where = process.cwd() res.type = isFilename.test(res.rawSpec) ? 'file' : 'directory' res.where = where - const spec = res.rawSpec.replace(/\\/g, '/') - .replace(/^file:[/]*([A-Za-z]:)/, '$1') // drive name paths on windows - .replace(/^file:(?:[/]*(~\/|\.*\/|[/]))?/, '$1') - if (/^~[/]/.test(spec)) { - // this is needed for windows and for file:~/foo/bar - res.fetchSpec = resolvePath(homedir(), spec.slice(2)) - res.saveSpec = 'file:' + spec - } else { - res.fetchSpec = resolvePath(where, spec) - if (isAbsolute(spec)) - res.saveSpec = 'file:' + spec - else - res.saveSpec = 'file:' + path.relative(where, res.fetchSpec) + // always put the '/' on where when resolving urls, or else + // file:foo from /path/to/bar goes to /path/to/foo, when we want + // it to be /path/to/foo/bar + + let specUrl + let resolvedUrl + const prefix = (!/^file:/.test(res.rawSpec) ? 'file:' : '') + const rawWithPrefix = prefix + res.rawSpec + let rawNoPrefix = rawWithPrefix.replace(/^file:/, '') + try { + resolvedUrl = new url.URL(rawWithPrefix, `file://${path.resolve(where)}/`) + specUrl = new url.URL(rawWithPrefix) + } catch (originalError) { + const er = new Error('Invalid file: URL, must comply with RFC 8909') + throw Object.assign(er, { + raw: res.rawSpec, + spec: res, + where, + originalError, + }) + } + + // environment switch for testing + if (process.env.NPM_PACKAGE_ARG_8909_STRICT !== '1') { + // XXX backwards compatibility lack of compliance with 8909 + // Remove when we want a breaking change to come into RFC compliance. + if (resolvedUrl.host && resolvedUrl.host !== 'localhost') { + const rawSpec = res.rawSpec.replace(/^file:\/\//, 'file:///') + resolvedUrl = new url.URL(rawSpec, `file://${path.resolve(where)}/`) + specUrl = new url.URL(rawSpec) + rawNoPrefix = rawSpec.replace(/^file:/, '') + } + // turn file:/../foo into file:../foo + if (/^\/\.\.?(\/|$)/.test(rawNoPrefix)) { + const rawSpec = res.rawSpec.replace(/^file:\//, 'file:') + resolvedUrl = new url.URL(rawSpec, `file://${path.resolve(where)}/`) + specUrl = new url.URL(rawSpec) + rawNoPrefix = rawSpec.replace(/^file:/, '') + } + // XXX end 8909 violation backwards compatibility section } + + // file:foo - relative url to ./foo + // file:/foo - absolute path /foo + // file:///foo - absolute path to /foo, no authority host + // file://localhost/foo - absolute path to /foo, on localhost + // file://foo - absolute path to / on foo host (error!) + if (resolvedUrl.host && resolvedUrl.host !== 'localhost') { + const msg = `Invalid file: URL, must be absolute if // present` + throw Object.assign(new Error(msg), { + raw: res.rawSpec, + parsed: resolvedUrl, + }) + } + + // turn /C:/blah into just C:/blah on windows + let specPath = decodeURIComponent(specUrl.pathname) + let resolvedPath = decodeURIComponent(resolvedUrl.pathname) + if (isWindows) { + specPath = specPath.replace(/^\/+([a-z]:\/)/i, '$1') + resolvedPath = resolvedPath.replace(/^\/+([a-z]:\/)/i, '$1') + } + + // replace ~ with homedir, but keep the ~ in the saveSpec + // otherwise, make it relative to where param + if (/^\/~(\/|$)/.test(specPath)) { + res.saveSpec = `file:${specPath.substr(1)}` + resolvedPath = path.resolve(homedir(), specPath.substr(3)) + } else if (!path.isAbsolute(rawNoPrefix)) + res.saveSpec = `file:${path.relative(where, resolvedPath)}` + else + res.saveSpec = `file:${path.resolve(resolvedPath)}` + + res.fetchSpec = path.resolve(where, resolvedPath) return res } diff --git a/node_modules/npm-package-arg/package.json b/node_modules/npm-package-arg/package.json index a237928943ccb..bf5f597e6d8df 100644 --- a/node_modules/npm-package-arg/package.json +++ b/node_modules/npm-package-arg/package.json @@ -1,6 +1,6 @@ { "name": "npm-package-arg", - "version": "8.1.4", + "version": "8.1.5", "description": "Parse the things that can be arguments to `npm install`", "main": "npa.js", "directories": { diff --git a/package-lock.json b/package-lock.json index b393b7e3f18ef..b6c93b183bfd4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "npm", - "version": "7.17.0", + "version": "7.18.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "npm", - "version": "7.17.0", + "version": "7.18.0", "bundleDependencies": [ "@npmcli/arborist", "@npmcli/ci-detect", @@ -78,10 +78,11 @@ ], "license": "Artistic-2.0", "workspaces": [ - "docs" + "docs", + "packages/*" ], "dependencies": { - "@npmcli/arborist": "^2.6.1", + "@npmcli/arborist": "^2.6.3", "@npmcli/ci-detect": "^1.2.0", "@npmcli/config": "^2.2.0", "@npmcli/run-script": "^1.8.5", @@ -106,7 +107,7 @@ "leven": "^3.1.0", "libnpmaccess": "^4.0.2", "libnpmdiff": "^2.0.4", - "libnpmexec": "^1.2.0", + "libnpmexec": "^2.0.0", "libnpmfund": "^1.1.0", "libnpmhook": "^6.0.2", "libnpmorg": "^2.0.2", @@ -114,8 +115,8 @@ "libnpmpublish": "^4.0.1", "libnpmsearch": "^3.1.1", "libnpmteam": "^2.0.3", - "libnpmversion": "^1.2.0", - "make-fetch-happen": "^9.0.1", + "libnpmversion": "^1.2.1", + "make-fetch-happen": "^9.0.3", "minipass": "^3.1.3", "minipass-pipeline": "^1.2.4", "mkdirp": "^1.0.4", @@ -124,7 +125,7 @@ "node-gyp": "^7.1.2", "nopt": "^5.0.0", "npm-audit-report": "^2.1.5", - "npm-package-arg": "^8.1.4", + "npm-package-arg": "^8.1.5", "npm-pick-manifest": "^6.1.1", "npm-profile": "^5.0.3", "npm-registry-fetch": "^11.0.0", @@ -732,9 +733,9 @@ } }, "node_modules/@npmcli/arborist": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/@npmcli/arborist/-/arborist-2.6.2.tgz", - "integrity": "sha512-CAo0HSziRdlpGUUheERmOrADnKHfBYpLAl/HmWGwGCtWKB3BCxfgb0rJ7MsFg38wy7YF3+fDs7R9dMVCH89K/A==", + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/@npmcli/arborist/-/arborist-2.6.3.tgz", + "integrity": "sha512-R8U2dZ8+jeE7go+qNU4Mt6aiXyBu3mM75iRIugNCA4P0OWlsLOpuDPPhsaRcOVbtXheOGZXrqe36qP1g+M68KQ==", "inBundle": true, "dependencies": { "@npmcli/installed-package-contents": "^1.0.7", @@ -756,6 +757,7 @@ "npm-registry-fetch": "^11.0.0", "pacote": "^11.2.6", "parse-conflict-json": "^1.1.1", + "proc-log": "^1.0.0", "promise-all-reject-late": "^1.0.0", "promise-call-limit": "^1.0.1", "read-package-json-fast": "^2.0.2", @@ -798,7 +800,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/@npmcli/disparity-colors/-/disparity-colors-1.0.1.tgz", "integrity": "sha512-kQ1aCTTU45mPXN+pdAaRxlxr3OunkyztjbbxDY/aIcPS5CnCUrx+1+NvA6pTcYR7wmLZe37+Mi5v3nfbwPxq3A==", - "inBundle": true, "dependencies": { "ansi-styles": "^4.3.0" }, @@ -1380,7 +1381,6 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", - "inBundle": true, "engines": { "node": ">=8" } @@ -2236,7 +2236,6 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", - "inBundle": true, "engines": { "node": ">=0.3.1" } @@ -4632,28 +4631,13 @@ } }, "node_modules/libnpmdiff": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/libnpmdiff/-/libnpmdiff-2.0.4.tgz", - "integrity": "sha512-q3zWePOJLHwsLEUjZw3Kyu/MJMYfl4tWCg78Vl6QGSfm4aXBUSVzMzjJ6jGiyarsT4d+1NH4B1gxfs62/+y9iQ==", - "inBundle": true, - "dependencies": { - "@npmcli/disparity-colors": "^1.0.1", - "@npmcli/installed-package-contents": "^1.0.7", - "binary-extensions": "^2.2.0", - "diff": "^5.0.0", - "minimatch": "^3.0.4", - "npm-package-arg": "^8.1.1", - "pacote": "^11.3.0", - "tar": "^6.1.0" - }, - "engines": { - "node": ">=10" - } + "resolved": "packages/libnpmdiff", + "link": true }, "node_modules/libnpmexec": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/libnpmexec/-/libnpmexec-1.2.0.tgz", - "integrity": "sha512-LkxnH2wsMUI4thsgUK0r+EFZ5iCjKlp21J68dFY7AzD5uaaIPqO3lqVvYbyl1Umz1R4rY9t3vFa1fF3hzo6Y2Q==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/libnpmexec/-/libnpmexec-2.0.0.tgz", + "integrity": "sha512-9zHswx//Lp2ao+huWF2aL+6v4haMncyxNusk6Us2fbLNnPh3+rgSkv38LJ2v8gmKS2kAnkUmQf8pHjcZ+7Z3NA==", "inBundle": true, "dependencies": { "@npmcli/arborist": "^2.3.0", @@ -4763,9 +4747,9 @@ } }, "node_modules/libnpmversion": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/libnpmversion/-/libnpmversion-1.2.0.tgz", - "integrity": "sha512-0pfmobLZbOvq1cLIONZk8ISvEM1k3JdkNXWhMDZvUeH+ijBNvMVdPu/CPUr1eDFbNINS3b6R/0PbTIZDVz7thg==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/libnpmversion/-/libnpmversion-1.2.1.tgz", + "integrity": "sha512-AA7x5CFgBFN+L4/JWobnY5t4OAHjQuPbAwUYJ7/NtHuyLut5meb+ne/aj0n7PWNiTGCJcRw/W6Zd2LoLT7EZuQ==", "inBundle": true, "dependencies": { "@npmcli/git": "^2.0.7", @@ -4969,9 +4953,9 @@ } }, "node_modules/make-fetch-happen": { - "version": "9.0.2", - "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-9.0.2.tgz", - "integrity": "sha512-UkAWAuXPXSSlVviTjH2We20mtj1NnZW2Qq/oTY2dyMbRQ5CR3Xed3akCDMnM7j6axrMY80lhgM7loNE132PfAw==", + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-9.0.3.tgz", + "integrity": "sha512-uZ/9Cf2vKqsSWZyXhZ9wHHyckBrkntgbnqV68Bfe8zZenlf7D6yuGMXvHZQ+jSnzPkjosuNP1HGasj1J4h8OlQ==", "inBundle": true, "dependencies": { "agentkeepalive": "^4.1.3", @@ -5449,9 +5433,9 @@ "inBundle": true }, "node_modules/npm-package-arg": { - "version": "8.1.4", - "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-8.1.4.tgz", - "integrity": "sha512-xLokoCFqj/rPdr3LvcdDL6Kj6ipXGEDHD/QGpzwU6/pibYUOXmp5DBmg76yukFyx4ZDbrXNOTn+BPyd8TD4Jlw==", + "version": "8.1.5", + "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-8.1.5.tgz", + "integrity": "sha512-LhgZrg0n0VgvzVdSm1oiZworPbTxYHUJCgtsJW8mGvlDpxTM1vSJc3m5QZeUkhAHIzbz3VCHd/R4osi1L1Tg/Q==", "inBundle": true, "dependencies": { "hosted-git-info": "^4.0.1", @@ -10377,6 +10361,31 @@ "type": "github", "url": "https://github.com/sponsors/wooorm" } + }, + "packages/libnpmdiff": { + "version": "2.0.4", + "license": "ISC", + "dependencies": { + "@npmcli/disparity-colors": "^1.0.1", + "@npmcli/installed-package-contents": "^1.0.7", + "binary-extensions": "^2.2.0", + "diff": "^5.0.0", + "minimatch": "^3.0.4", + "npm-package-arg": "^8.1.4", + "pacote": "^11.3.4", + "tar": "^6.1.0" + }, + "devDependencies": { + "eslint": "^7.28.0", + "eslint-plugin-import": "^2.23.4", + "eslint-plugin-node": "^11.1.0", + "eslint-plugin-promise": "^5.1.0", + "eslint-plugin-standard": "^5.0.0", + "tap": "^15.0.9" + }, + "engines": { + "node": ">=10" + } } }, "dependencies": { @@ -10848,9 +10857,9 @@ "dev": true }, "@npmcli/arborist": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/@npmcli/arborist/-/arborist-2.6.2.tgz", - "integrity": "sha512-CAo0HSziRdlpGUUheERmOrADnKHfBYpLAl/HmWGwGCtWKB3BCxfgb0rJ7MsFg38wy7YF3+fDs7R9dMVCH89K/A==", + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/@npmcli/arborist/-/arborist-2.6.3.tgz", + "integrity": "sha512-R8U2dZ8+jeE7go+qNU4Mt6aiXyBu3mM75iRIugNCA4P0OWlsLOpuDPPhsaRcOVbtXheOGZXrqe36qP1g+M68KQ==", "requires": { "@npmcli/installed-package-contents": "^1.0.7", "@npmcli/map-workspaces": "^1.0.2", @@ -10871,6 +10880,7 @@ "npm-registry-fetch": "^11.0.0", "pacote": "^11.2.6", "parse-conflict-json": "^1.1.1", + "proc-log": "^1.0.0", "promise-all-reject-late": "^1.0.0", "promise-call-limit": "^1.0.1", "read-package-json-fast": "^2.0.2", @@ -13706,24 +13716,28 @@ } }, "libnpmdiff": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/libnpmdiff/-/libnpmdiff-2.0.4.tgz", - "integrity": "sha512-q3zWePOJLHwsLEUjZw3Kyu/MJMYfl4tWCg78Vl6QGSfm4aXBUSVzMzjJ6jGiyarsT4d+1NH4B1gxfs62/+y9iQ==", + "version": "file:packages/libnpmdiff", "requires": { "@npmcli/disparity-colors": "^1.0.1", "@npmcli/installed-package-contents": "^1.0.7", "binary-extensions": "^2.2.0", "diff": "^5.0.0", + "eslint": "^7.28.0", + "eslint-plugin-import": "^2.23.4", + "eslint-plugin-node": "^11.1.0", + "eslint-plugin-promise": "^5.1.0", + "eslint-plugin-standard": "^5.0.0", "minimatch": "^3.0.4", - "npm-package-arg": "^8.1.1", - "pacote": "^11.3.0", + "npm-package-arg": "^8.1.4", + "pacote": "^11.3.4", + "tap": "^15.0.9", "tar": "^6.1.0" } }, "libnpmexec": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/libnpmexec/-/libnpmexec-1.2.0.tgz", - "integrity": "sha512-LkxnH2wsMUI4thsgUK0r+EFZ5iCjKlp21J68dFY7AzD5uaaIPqO3lqVvYbyl1Umz1R4rY9t3vFa1fF3hzo6Y2Q==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/libnpmexec/-/libnpmexec-2.0.0.tgz", + "integrity": "sha512-9zHswx//Lp2ao+huWF2aL+6v4haMncyxNusk6Us2fbLNnPh3+rgSkv38LJ2v8gmKS2kAnkUmQf8pHjcZ+7Z3NA==", "requires": { "@npmcli/arborist": "^2.3.0", "@npmcli/ci-detect": "^1.3.0", @@ -13804,9 +13818,9 @@ } }, "libnpmversion": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/libnpmversion/-/libnpmversion-1.2.0.tgz", - "integrity": "sha512-0pfmobLZbOvq1cLIONZk8ISvEM1k3JdkNXWhMDZvUeH+ijBNvMVdPu/CPUr1eDFbNINS3b6R/0PbTIZDVz7thg==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/libnpmversion/-/libnpmversion-1.2.1.tgz", + "integrity": "sha512-AA7x5CFgBFN+L4/JWobnY5t4OAHjQuPbAwUYJ7/NtHuyLut5meb+ne/aj0n7PWNiTGCJcRw/W6Zd2LoLT7EZuQ==", "requires": { "@npmcli/git": "^2.0.7", "@npmcli/run-script": "^1.8.4", @@ -13975,9 +13989,9 @@ } }, "make-fetch-happen": { - "version": "9.0.2", - "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-9.0.2.tgz", - "integrity": "sha512-UkAWAuXPXSSlVviTjH2We20mtj1NnZW2Qq/oTY2dyMbRQ5CR3Xed3akCDMnM7j6axrMY80lhgM7loNE132PfAw==", + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-9.0.3.tgz", + "integrity": "sha512-uZ/9Cf2vKqsSWZyXhZ9wHHyckBrkntgbnqV68Bfe8zZenlf7D6yuGMXvHZQ+jSnzPkjosuNP1HGasj1J4h8OlQ==", "requires": { "agentkeepalive": "^4.1.3", "cacache": "^15.2.0", @@ -14323,9 +14337,9 @@ "integrity": "sha512-EPfafl6JL5/rU+ot6P3gRSCpPDW5VmIzX959Ob1+ySFUuuYHWHekXpwdUZcKP5C+DS4GEtdJluwBjnsNDl+fSA==" }, "npm-package-arg": { - "version": "8.1.4", - "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-8.1.4.tgz", - "integrity": "sha512-xLokoCFqj/rPdr3LvcdDL6Kj6ipXGEDHD/QGpzwU6/pibYUOXmp5DBmg76yukFyx4ZDbrXNOTn+BPyd8TD4Jlw==", + "version": "8.1.5", + "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-8.1.5.tgz", + "integrity": "sha512-LhgZrg0n0VgvzVdSm1oiZworPbTxYHUJCgtsJW8mGvlDpxTM1vSJc3m5QZeUkhAHIzbz3VCHd/R4osi1L1Tg/Q==", "requires": { "hosted-git-info": "^4.0.1", "semver": "^7.3.4", diff --git a/package.json b/package.json index 64568185861bd..37562f0e86acf 100644 --- a/package.json +++ b/package.json @@ -1,9 +1,10 @@ { - "version": "7.17.0", + "version": "7.18.0", "name": "npm", "description": "a package manager for JavaScript", "workspaces": [ - "docs" + "docs", + "packages/*" ], "files": [ "bin", @@ -52,7 +53,7 @@ "./package.json": "./package.json" }, "dependencies": { - "@npmcli/arborist": "^2.6.1", + "@npmcli/arborist": "^2.6.3", "@npmcli/ci-detect": "^1.2.0", "@npmcli/config": "^2.2.0", "@npmcli/run-script": "^1.8.5", @@ -77,7 +78,7 @@ "leven": "^3.1.0", "libnpmaccess": "^4.0.2", "libnpmdiff": "^2.0.4", - "libnpmexec": "^1.2.0", + "libnpmexec": "^2.0.0", "libnpmfund": "^1.1.0", "libnpmhook": "^6.0.2", "libnpmorg": "^2.0.2", @@ -85,8 +86,8 @@ "libnpmpublish": "^4.0.1", "libnpmsearch": "^3.1.1", "libnpmteam": "^2.0.3", - "libnpmversion": "^1.2.0", - "make-fetch-happen": "^9.0.1", + "libnpmversion": "^1.2.1", + "make-fetch-happen": "^9.0.3", "minipass": "^3.1.3", "minipass-pipeline": "^1.2.4", "mkdirp": "^1.0.4", @@ -95,7 +96,7 @@ "node-gyp": "^7.1.2", "nopt": "^5.0.0", "npm-audit-report": "^2.1.5", - "npm-package-arg": "^8.1.4", + "npm-package-arg": "^8.1.5", "npm-pick-manifest": "^6.1.1", "npm-profile": "^5.0.3", "npm-registry-fetch": "^11.0.0", diff --git a/packages/libnpmdiff/.eslintrc.json b/packages/libnpmdiff/.eslintrc.json new file mode 100644 index 0000000000000..6232a8f82187f --- /dev/null +++ b/packages/libnpmdiff/.eslintrc.json @@ -0,0 +1,207 @@ +{ + "parserOptions": { + "ecmaVersion": 2018, + "ecmaFeatures": {}, + "sourceType": "script" + }, + + "env": { + "es6": true, + "node": true + }, + + "plugins": [ + "import", + "node", + "promise", + "standard" + ], + + "globals": { + "document": "readonly", + "navigator": "readonly", + "window": "readonly" + }, + + "rules": { + "accessor-pairs": "error", + "array-bracket-spacing": ["error", "never"], + "arrow-spacing": ["error", { "before": true, "after": true }], + "block-spacing": ["error", "always"], + "brace-style": ["error", "1tbs", { "allowSingleLine": false }], + "camelcase": ["error", { "properties": "never" }], + "comma-dangle": ["error", { + "arrays": "always-multiline", + "objects": "always-multiline", + "imports": "always-multiline", + "exports": "always-multiline", + "functions": "never" + }], + "comma-spacing": ["error", { "before": false, "after": true }], + "comma-style": ["error", "last"], + "computed-property-spacing": ["error", "never"], + "constructor-super": "error", + "curly": ["error", "multi-or-nest"], + "dot-location": ["error", "property"], + "dot-notation": ["error", { "allowKeywords": true }], + "eol-last": "error", + "eqeqeq": ["error", "always", { "null": "ignore" }], + "func-call-spacing": ["error", "never"], + "generator-star-spacing": ["error", { "before": true, "after": true }], + "handle-callback-err": ["error", "^(err|error)$" ], + "indent": ["error", 2, { + "SwitchCase": 1, + "VariableDeclarator": 1, + "outerIIFEBody": 1, + "MemberExpression": 1, + "FunctionDeclaration": { "parameters": 1, "body": 1 }, + "FunctionExpression": { "parameters": 1, "body": 1 }, + "CallExpression": { "arguments": 1 }, + "ArrayExpression": 1, + "ObjectExpression": 1, + "ImportDeclaration": 1, + "flatTernaryExpressions": true, + "ignoreComments": false, + "ignoredNodes": ["TemplateLiteral *"] + }], + "key-spacing": ["error", { "beforeColon": false, "afterColon": true }], + "keyword-spacing": ["error", { "before": true, "after": true }], + "lines-between-class-members": ["error", "always", { "exceptAfterSingleLine": true }], + "new-cap": ["error", { "newIsCap": true, "capIsNew": false, "properties": true }], + "new-parens": "error", + "no-array-constructor": "error", + "no-async-promise-executor": "error", + "no-caller": "error", + "no-case-declarations": "error", + "no-class-assign": "error", + "no-compare-neg-zero": "error", + "no-cond-assign": "off", + "no-const-assign": "error", + "no-constant-condition": ["error", { "checkLoops": false }], + "no-control-regex": "error", + "no-debugger": "error", + "no-delete-var": "error", + "no-dupe-args": "error", + "no-dupe-class-members": "error", + "no-dupe-keys": "error", + "no-duplicate-case": "error", + "no-empty-character-class": "error", + "no-empty-pattern": "error", + "no-eval": "error", + "no-ex-assign": "error", + "no-extend-native": "error", + "no-extra-bind": "error", + "no-extra-boolean-cast": "error", + "no-extra-parens": ["error", "functions"], + "no-fallthrough": "error", + "no-floating-decimal": "error", + "no-func-assign": "error", + "no-global-assign": "error", + "no-implied-eval": "error", + "no-inner-declarations": ["error", "functions"], + "no-invalid-regexp": "error", + "no-irregular-whitespace": "error", + "no-iterator": "error", + "no-labels": ["error", { "allowLoop": true, "allowSwitch": false }], + "no-lone-blocks": "error", + "no-misleading-character-class": "error", + "no-prototype-builtins": "error", + "no-useless-catch": "error", + "no-mixed-operators": "off", + "no-mixed-spaces-and-tabs": "error", + "no-multi-spaces": "error", + "no-multi-str": "error", + "no-multiple-empty-lines": ["error", { "max": 1, "maxEOF": 0 }], + "no-negated-in-lhs": "error", + "no-new": "off", + "no-new-func": "error", + "no-new-object": "error", + "no-new-require": "error", + "no-new-symbol": "error", + "no-new-wrappers": "error", + "no-obj-calls": "error", + "no-octal": "error", + "no-octal-escape": "error", + "no-path-concat": "error", + "no-proto": "error", + "no-redeclare": ["error", { "builtinGlobals": false }], + "no-regex-spaces": "error", + "no-return-assign": "off", + "no-self-assign": "off", + "no-self-compare": "error", + "no-sequences": "error", + "no-shadow-restricted-names": "error", + "no-sparse-arrays": "error", + "no-tabs": "error", + "no-template-curly-in-string": "error", + "no-this-before-super": "error", + "no-throw-literal": "off", + "no-trailing-spaces": "error", + "no-undef": "error", + "no-undef-init": "error", + "no-unexpected-multiline": "error", + "no-unmodified-loop-condition": "error", + "no-unneeded-ternary": ["error", { "defaultAssignment": false }], + "no-unreachable": "error", + "no-unsafe-finally": 0, + "no-unsafe-negation": "error", + "no-unused-expressions": ["error", { "allowShortCircuit": true, "allowTernary": true, "allowTaggedTemplates": true }], + "no-unused-vars": ["error", { "vars": "all", "args": "none", "ignoreRestSiblings": true }], + "no-use-before-define": ["error", { "functions": false, "classes": false, "variables": false }], + "no-useless-call": "error", + "no-useless-computed-key": "error", + "no-useless-constructor": "error", + "no-useless-escape": "error", + "no-useless-rename": "error", + "no-useless-return": "error", + "no-void": "error", + "no-whitespace-before-property": "error", + "no-with": "error", + "nonblock-statement-body-position": [2, "below"], + "object-curly-newline": "off", + "object-curly-spacing": "off", + "object-property-newline": ["error", { "allowMultiplePropertiesPerLine": true }], + "one-var": ["error", { "initialized": "never" }], + "operator-linebreak": "off", + "padded-blocks": ["error", { "blocks": "never", "switches": "never", "classes": "never" }], + "prefer-const": ["error", {"destructuring": "all"}], + "prefer-promise-reject-errors": "error", + "quote-props": ["error", "as-needed"], + "quotes": ["error", "single", { "avoidEscape": true, "allowTemplateLiterals": true }], + "rest-spread-spacing": ["error", "never"], + "semi": ["error", "never"], + "semi-spacing": ["error", { "before": false, "after": true }], + "space-before-blocks": ["error", "always"], + "space-before-function-paren": ["error", "always"], + "space-in-parens": ["error", "never"], + "space-infix-ops": "error", + "space-unary-ops": ["error", { "words": true, "nonwords": false }], + "spaced-comment": ["error", "always", { + "line": { "markers": ["*package", "!", "/", ",", "="] }, + "block": { "balanced": true, "markers": ["*package", "!", ",", ":", "::", "flow-include"], "exceptions": ["*"] } + }], + "symbol-description": "error", + "template-curly-spacing": ["error", "never"], + "template-tag-spacing": ["error", "never"], + "unicode-bom": ["error", "never"], + "use-isnan": "error", + "valid-typeof": ["error", { "requireStringLiterals": true }], + "wrap-iife": ["error", "any", { "functionPrototypeMethods": true }], + "yield-star-spacing": ["error", "both"], + "yoda": ["error", "never"], + + "import/export": "error", + "import/first": "error", + "import/no-absolute-path": ["error", { "esmodule": true, "commonjs": true, "amd": false }], + "import/no-duplicates": "error", + "import/no-named-default": "error", + "import/no-webpack-loader-syntax": "error", + + "node/no-deprecated-api": "error", + "node/process-exit-as-throw": "error", + + "promise/param-names": "off", + + "standard/no-callback-literal": "error" + } +} diff --git a/packages/libnpmdiff/.gitignore b/packages/libnpmdiff/.gitignore new file mode 100644 index 0000000000000..0aba557bf2857 --- /dev/null +++ b/packages/libnpmdiff/.gitignore @@ -0,0 +1,99 @@ +# Logs +logs +*.log +npm-debug.log* +lerna-debug.log* + +# Diagnostic reports (https://nodejs.org/api/report.html) +report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage +*.lcov + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# TypeScript v1 declaration files +typings/ + +# TypeScript cache +*.tsbuildinfo + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Microbundle cache +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variables file +.env +.env.test + +# parcel-bundler cache (https://parceljs.org/) +.cache + +# next.js build output +.next + +# nuxt.js build output +.nuxt + +# gatsby files +.cache/ +public + +# vuepress build output +.vuepress/dist + +# Serverless directories +.serverless/ + +# FuseBox cache +.fusebox/ + +# DynamoDB Local files +.dynamodb/ + +# Editors +Session.vim diff --git a/packages/libnpmdiff/CHANGELOG.md b/packages/libnpmdiff/CHANGELOG.md new file mode 100644 index 0000000000000..b93b15b7b1113 --- /dev/null +++ b/packages/libnpmdiff/CHANGELOG.md @@ -0,0 +1,30 @@ +# Changelog + +## 2.0.3 + +- fix name of options sent by the npm cli + +## 2.0.2 + +- fix matching basename file filter + +## 2.0.1 + +- fix for tarballs not listing folder names + +## 2.0.0 + +- API rewrite: + - normalized all options + - specs to compare are now an array +- fix context=0 +- added support to filtering by folder names + +## 1.0.1 + +- fixed nameOnly option + +## 1.0.0 + +- Initial release + diff --git a/node_modules/libnpmdiff/LICENSE b/packages/libnpmdiff/LICENSE similarity index 100% rename from node_modules/libnpmdiff/LICENSE rename to packages/libnpmdiff/LICENSE diff --git a/packages/libnpmdiff/README.md b/packages/libnpmdiff/README.md new file mode 100644 index 0000000000000..bc260ad15ce12 --- /dev/null +++ b/packages/libnpmdiff/README.md @@ -0,0 +1,98 @@ +# libnpmdiff + +[![npm version](https://img.shields.io/npm/v/libnpmdiff.svg)](https://npm.im/libnpmdiff) +[![license](https://img.shields.io/npm/l/libnpmdiff.svg)](https://npm.im/libnpmdiff) +[![GitHub Actions](https://github.com/npm/libnpmdiff/workflows/node-ci/badge.svg)](https://github.com/npm/libnpmdiff/actions?query=workflow%3Anode-ci) +[![Coverage Status](https://coveralls.io/repos/github/npm/libnpmdiff/badge.svg?branch=main)](https://coveralls.io/github/npm/libnpmdiff?branch=main) + +The registry diff lib. + +## Table of Contents + +* [Example](#example) +* [Install](#install) +* [Contributing](#contributing) +* [API](#api) +* [LICENSE](#license) + +## Example + +```js +const libdiff = require('libnpmdiff') + +const patch = await libdiff([ + 'abbrev@1.1.0', + 'abbrev@1.1.1' +]) +console.log( + patch +) +``` + +Returns: + +```patch +diff --git a/package.json b/package.json +index v1.1.0..v1.1.1 100644 +--- a/package.json ++++ b/package.json +@@ -1,6 +1,6 @@ + { + "name": "abbrev", +- "version": "1.1.0", ++ "version": "1.1.1", + "description": "Like ruby's abbrev module, but in js", + "author": "Isaac Z. Schlueter ", + "main": "abbrev.js", + +``` + +## Install + +`$ npm install libnpmdiff` + +### Contributing + +The npm team enthusiastically welcomes contributions and project participation! +There's a bunch of things you can do if you want to contribute! The +[Contributor Guide](https://github.com/npm/cli/blob/latest/CONTRIBUTING.md) +outlines the process for community interaction and contribution. Please don't +hesitate to jump in if you'd like to, or even ask us questions if something +isn't clear. + +All participants and maintainers in this project are expected to follow the +[npm Code of Conduct](https://www.npmjs.com/policies/conduct), and just +generally be excellent to each other. + +Please refer to the [Changelog](CHANGELOG.md) for project history details, too. + +Happy hacking! + +### API + +#### `> libnpmdif([ a, b ], [opts]) -> Promise` + +Fetches the registry tarballs and compare files between a spec `a` and spec `b`. **npm** spec types are usually described in `@` form but multiple other types are alsos supported, for more info on valid specs take a look at [`npm-package-arg`](https://github.com/npm/npm-package-arg). + +**Options**: + +- `color `: Should add ANSI colors to string output? Defaults to `false`. +- `tagVersionPrefix `: What prefix should be used to define version numbers. Defaults to `v` +- `diffUnified `: How many lines of code to print before/after each diff. Defaults to `3`. +- `diffFiles >`: If set only prints patches for the files listed in this array (also accepts globs). Defaults to `undefined`. +- `diffIgnoreAllSpace `: Whether or not should ignore changes in whitespace (very useful to avoid indentation changes extra diff lines). Defaults to `false`. +- `diffNameOnly `: Prints only file names and no patch diffs. Defaults to `false`. +- `diffNoPrefix `: If true then skips printing any prefixes in filenames. Defaults to `false`. +- `diffSrcPrefix `: Prefix to be used in the filenames from `a`. Defaults to `a/`. +- `diffDstPrefix `: Prefix to be used in the filenames from `b`. Defaults to `b/`. +- `diffText `: Should treat all files as text and try to print diff for binary files. Defaults to `false`. +- ...`cache`, `registry`, `where` and other common options accepted by [pacote](https://github.com/npm/pacote#options) + +Returns a `Promise` that fullfils with a `String` containing the resulting patch diffs. + +Throws an error if either `a` or `b` are missing or if trying to diff more than two specs. + +## LICENSE + +[ISC](./LICENSE) + diff --git a/node_modules/libnpmdiff/index.js b/packages/libnpmdiff/index.js similarity index 100% rename from node_modules/libnpmdiff/index.js rename to packages/libnpmdiff/index.js diff --git a/node_modules/libnpmdiff/lib/format-diff.js b/packages/libnpmdiff/lib/format-diff.js similarity index 100% rename from node_modules/libnpmdiff/lib/format-diff.js rename to packages/libnpmdiff/lib/format-diff.js diff --git a/node_modules/libnpmdiff/lib/should-print-patch.js b/packages/libnpmdiff/lib/should-print-patch.js similarity index 100% rename from node_modules/libnpmdiff/lib/should-print-patch.js rename to packages/libnpmdiff/lib/should-print-patch.js diff --git a/node_modules/libnpmdiff/lib/tarball.js b/packages/libnpmdiff/lib/tarball.js similarity index 100% rename from node_modules/libnpmdiff/lib/tarball.js rename to packages/libnpmdiff/lib/tarball.js diff --git a/node_modules/libnpmdiff/lib/untar.js b/packages/libnpmdiff/lib/untar.js similarity index 100% rename from node_modules/libnpmdiff/lib/untar.js rename to packages/libnpmdiff/lib/untar.js diff --git a/node_modules/libnpmdiff/package.json b/packages/libnpmdiff/package.json similarity index 87% rename from node_modules/libnpmdiff/package.json rename to packages/libnpmdiff/package.json index aa13954c63010..53fd5d4befd5e 100644 --- a/node_modules/libnpmdiff/package.json +++ b/packages/libnpmdiff/package.json @@ -46,12 +46,12 @@ ] }, "devDependencies": { - "eslint": "^7.18.0", - "eslint-plugin-import": "^2.22.1", + "eslint": "^7.28.0", + "eslint-plugin-import": "^2.23.4", "eslint-plugin-node": "^11.1.0", - "eslint-plugin-promise": "^4.2.1", + "eslint-plugin-promise": "^5.1.0", "eslint-plugin-standard": "^5.0.0", - "tap": "^14.11.0" + "tap": "^15.0.9" }, "dependencies": { "@npmcli/disparity-colors": "^1.0.1", @@ -59,8 +59,8 @@ "binary-extensions": "^2.2.0", "diff": "^5.0.0", "minimatch": "^3.0.4", - "npm-package-arg": "^8.1.1", - "pacote": "^11.3.0", + "npm-package-arg": "^8.1.4", + "pacote": "^11.3.4", "tar": "^6.1.0" } } diff --git a/packages/libnpmdiff/tap-snapshots/test/format-diff.js.test.cjs b/packages/libnpmdiff/tap-snapshots/test/format-diff.js.test.cjs new file mode 100644 index 0000000000000..f735d8925820a --- /dev/null +++ b/packages/libnpmdiff/tap-snapshots/test/format-diff.js.test.cjs @@ -0,0 +1,152 @@ +/* IMPORTANT + * This snapshot file is auto-generated, but designed for humans. + * It should be checked into source control and tracked carefully. + * Re-generate by setting TAP_SNAPSHOT=1 and running tests. + * Make sure to inspect the output below. Do not ignore changes! + */ +'use strict' +exports[`test/format-diff.js TAP added file > should output expected added file diff result 1`] = ` +diff --git a/foo.js b/foo.js +new file mode 100755 +index v1.0.0..v2.0.0 +--- a/foo.js ++++ b/foo.js +@@ -0,0 +1,2 @@ ++"use strict" ++module.exports = "foo" +` + +exports[`test/format-diff.js TAP binary file > should output expected bin file diff result 1`] = ` +diff --git a/foo.jpg b/foo.jpg +index v1.0.0..v2.0.0 100644 +--- a/foo.jpg ++++ b/foo.jpg +` + +exports[`test/format-diff.js TAP changed file mode > should output expected changed file mode diff result 1`] = ` +diff --git a/foo.js b/foo.js +old mode 100644 +new mode 100755 +index v1.0.0..v2.0.0 +--- a/foo.js ++++ b/foo.js +` + +exports[`test/format-diff.js TAP colored output > should output expected colored diff result 1`] = ` +diff --git a/foo.js b/foo.js +index v1.0.0..v2.0.0 100644 +--- a/foo.js ++++ b/foo.js +@@ -1,2 +1,2 @@ + "use strict" +-module.exports = "foo" ++module.exports = "foobar" +` + +exports[`test/format-diff.js TAP diff options > should output expected diff result 1`] = ` +diff --git before/foo.js after/foo.js +index v1.0.0..v2.0.0 100644 +--- before/foo.js ++++ after/foo.js +@@ -4,4 +4,6 @@ + const c = "c" ++const d = "d" + module.exports = () => a+ + b+ +-c ++c+ ++d +` + +exports[`test/format-diff.js TAP diffUnified=0 > should output no context lines in output 1`] = ` +diff --git a/foo.js b/foo.js +index v1.0.0..v2.0.0 100644 +--- a/foo.js ++++ b/foo.js +@@ -3,2 +3,3 @@ +-const b = "b" +-const c = "c" ++ const b = "b" ++ const c = "c" ++ const d = "d" +@@ -7,1 +8,2 @@ +-c ++c+ ++d +` + +exports[`test/format-diff.js TAP format multiple files patch > should output expected result for multiple files 1`] = ` +diff --git a/foo.js b/foo.js +index v1.0.0..v1.1.1 100644 +--- a/foo.js ++++ b/foo.js +@@ -1,2 +1,2 @@ + "use strict" +-module.exports = "foo" ++module.exports = "foobar" +diff --git a/lib/utils.js b/lib/utils.js +index v1.0.0..v1.1.1 100644 +--- a/lib/utils.js ++++ b/lib/utils.js +@@ -1,3 +1,4 @@ + "use strict" + const bar = require("./bar.js") +-module.exports = () => bar ++module.exports = ++ () => bar + "util" +` + +exports[`test/format-diff.js TAP format removed file > should output expected removed file diff result 1`] = ` +diff --git a/foo.js b/foo.js +deleted file mode 100644 +index v1.0.0..v2.0.0 +--- a/foo.js ++++ b/foo.js +@@ -1,2 +0,0 @@ +-"use strict" +-module.exports = "foo" +/ No newline at end of file +` + +exports[`test/format-diff.js TAP format simple diff > should output expected diff result 1`] = ` +diff --git a/foo.js b/foo.js +index v1.0.0..v2.0.0 100644 +--- a/foo.js ++++ b/foo.js +@@ -1,2 +1,2 @@ + "use strict" +-module.exports = "foo" ++module.exports = "foobar" +` + +exports[`test/format-diff.js TAP noPrefix > should output result with no prefixes 1`] = ` +diff --git foo.js foo.js +index v1.0.0..v2.0.0 100644 +Index: foo.js +--- foo.js ++++ foo.js +@@ -1,2 +1,2 @@ + "use strict" +-module.exports = "foo" ++module.exports = "foobar" +` + +exports[`test/format-diff.js TAP nothing to diff > should output empty result 1`] = ` + +` + +exports[`test/format-diff.js TAP respect --tag-version-prefix option > should output expected diff result 1`] = ` +diff --git a/foo.js b/foo.js +index b1.0.0..b2.0.0 100644 +--- a/foo.js ++++ b/foo.js +@@ -1,2 +1,2 @@ + "use strict" +-module.exports = "foo" ++module.exports = "foobar" +` + +exports[`test/format-diff.js TAP using --name-only option > should output expected diff result 1`] = ` +foo.js +lib/utils.js +` diff --git a/packages/libnpmdiff/tap-snapshots/test/index.js.test.cjs b/packages/libnpmdiff/tap-snapshots/test/index.js.test.cjs new file mode 100644 index 0000000000000..21db3deac4d70 --- /dev/null +++ b/packages/libnpmdiff/tap-snapshots/test/index.js.test.cjs @@ -0,0 +1,115 @@ +/* IMPORTANT + * This snapshot file is auto-generated, but designed for humans. + * It should be checked into source control and tracked carefully. + * Re-generate by setting TAP_SNAPSHOT=1 and running tests. + * Make sure to inspect the output below. Do not ignore changes! + */ +'use strict' +exports[`test/index.js TAP compare two diff specs > should output expected diff 1`] = ` +diff --git a/index.js b/index.js +index v1.0.0..v2.0.0 100644 +--- a/index.js ++++ b/index.js +@@ -1,2 +1,2 @@ + module.exports = +- "a1" ++ "a2" +diff --git a/package.json b/package.json +index v1.0.0..v2.0.0 100644 +--- a/package.json ++++ b/package.json +@@ -1,4 +1,4 @@ + { + "name": "a", +- "version": "1.0.0" ++ "version": "2.0.0" + } +` + +exports[`test/index.js TAP folder in node_modules nested, absolute path > should output expected diff 1`] = ` +diff --git a/package.json b/package.json +index v2.0.0..v2.0.1 100644 +--- a/package.json ++++ b/package.json +@@ -1,6 +1,6 @@ + { + "name": "b", +- "version": "2.0.0", ++ "version": "2.0.1", + "scripts": { + "prepare": "node prepare.js" + } +diff --git a/prepare.js b/prepare.js +index v2.0.0..v2.0.1 100644 +--- a/prepare.js ++++ b/prepare.js +@@ -1,1 +0,0 @@ +-throw new Error("ERR") +/ No newline at end of file +` + +exports[`test/index.js TAP folder in node_modules nested, relative path > should output expected diff 1`] = ` +diff --git a/package.json b/package.json +index v2.0.0..v2.0.1 100644 +--- a/package.json ++++ b/package.json +@@ -1,6 +1,6 @@ + { + "name": "b", +- "version": "2.0.0", ++ "version": "2.0.1", + "scripts": { + "prepare": "node prepare.js" + } +diff --git a/prepare.js b/prepare.js +index v2.0.0..v2.0.1 100644 +--- a/prepare.js ++++ b/prepare.js +@@ -1,1 +0,0 @@ +-throw new Error("ERR") +/ No newline at end of file +` + +exports[`test/index.js TAP folder in node_modules top-level, absolute path > should output expected diff 1`] = ` +diff --git a/package.json b/package.json +index v1.0.0..v1.0.1 100644 +--- a/package.json ++++ b/package.json +@@ -1,6 +1,6 @@ + { + "name": "a", +- "version": "1.0.0", ++ "version": "1.0.1", + "scripts": { + "prepare": "node prepare.js" + } +diff --git a/prepare.js b/prepare.js +index v1.0.0..v1.0.1 100644 +--- a/prepare.js ++++ b/prepare.js +@@ -1,1 +0,0 @@ +-throw new Error("ERR") +/ No newline at end of file +` + +exports[`test/index.js TAP folder in node_modules top-level, relative path > should output expected diff 1`] = ` +diff --git a/package.json b/package.json +index v1.0.0..v1.0.1 100644 +--- a/package.json ++++ b/package.json +@@ -1,6 +1,6 @@ + { + "name": "a", +- "version": "1.0.0", ++ "version": "1.0.1", + "scripts": { + "prepare": "node prepare.js" + } +diff --git a/prepare.js b/prepare.js +index v1.0.0..v1.0.1 100644 +--- a/prepare.js ++++ b/prepare.js +@@ -1,1 +0,0 @@ +-throw new Error("ERR") +/ No newline at end of file +` diff --git a/packages/libnpmdiff/tap-snapshots/test/untar.js.test.cjs b/packages/libnpmdiff/tap-snapshots/test/untar.js.test.cjs new file mode 100644 index 0000000000000..b1092feb6ee8c --- /dev/null +++ b/packages/libnpmdiff/tap-snapshots/test/untar.js.test.cjs @@ -0,0 +1,134 @@ +/* IMPORTANT + * This snapshot file is auto-generated, but designed for humans. + * It should be checked into source control and tracked carefully. + * Re-generate by setting TAP_SNAPSHOT=1 and running tests. + * Make sure to inspect the output below. Do not ignore changes! + */ +'use strict' +exports[`test/untar.js TAP filter files > should return list of filenames 1`] = ` +LICENSE +README.md +` + +exports[`test/untar.js TAP filter files > should return map of filenames with valid contents 1`] = ` +a/LICENSE: true +a/README.md: true +` + +exports[`test/untar.js TAP filter files by exact filename > should return no filenames 1`] = ` + +` + +exports[`test/untar.js TAP filter files by exact filename > should return no filenames 2`] = ` + +` + +exports[`test/untar.js TAP filter files using glob expressions > should return list of filenames 1`] = ` +lib/index.js +lib/utils/b.js +package-lock.json +test/index.js +` + +exports[`test/untar.js TAP filter files using glob expressions > should return map of filenames with valid contents 1`] = ` +a/lib/index.js: true +a/lib/utils/b.js: true +a/package-lock.json: true +a/test/index.js: true +` + +exports[`test/untar.js TAP match files by end of filename > should return list of filenames 1`] = ` +lib/index.js +lib/utils/b.js +test/index.js +test/utils/b.js +` + +exports[`test/untar.js TAP match files by end of filename > should return map of filenames with valid contents 1`] = ` +a/lib/index.js: true +a/lib/utils/b.js: true +a/test/index.js: true +a/test/utils/b.js: true +` + +exports[`test/untar.js TAP match files by simple folder name > should return list of filenames 1`] = ` +lib/index.js +lib/utils/b.js +` + +exports[`test/untar.js TAP match files by simple folder name > should return map of filenames with valid contents 1`] = ` +a/lib/index.js: true +a/lib/utils/b.js: true +` + +exports[`test/untar.js TAP match files by simple folder name variation > should return list of filenames 1`] = ` +test/index.js +test/utils/b.js +` + +exports[`test/untar.js TAP match files by simple folder name variation > should return map of filenames with valid contents 1`] = ` +a/test/index.js: true +a/test/utils/b.js: true +` + +exports[`test/untar.js TAP untar package with folders > should have read contents 1`] = ` +module.exports = 'b' + +` + +exports[`test/untar.js TAP untar package with folders > should return list of filenames 1`] = ` +lib/index.js +lib/utils/b.js +package-lock.json +package.json +test/index.js +test/utils/b.js +` + +exports[`test/untar.js TAP untar package with folders > should return map of filenames to its contents 1`] = ` +a/lib/index.js: true +a/lib/utils/b.js: true +a/package-lock.json: true +a/package.json: true +a/test/index.js: true +a/test/utils/b.js: true +` + +exports[`test/untar.js TAP untar simple package > should have read contents 1`] = ` +The MIT License (MIT) + +Copyright (c) Ruy Adorno (ruyadorno.com) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +` + +exports[`test/untar.js TAP untar simple package > should return list of filenames 1`] = ` +LICENSE +index.js +package.json +README.md +` + +exports[`test/untar.js TAP untar simple package > should return map of filenames to its contents 1`] = ` +a/LICENSE: true +a/index.js: true +a/package.json: true +a/README.md: true +` diff --git a/packages/libnpmdiff/test/fixtures/archive.tgz b/packages/libnpmdiff/test/fixtures/archive.tgz new file mode 100644 index 0000000000000..843a611239bbb Binary files /dev/null and b/packages/libnpmdiff/test/fixtures/archive.tgz differ diff --git a/packages/libnpmdiff/test/fixtures/ruyadorno-simplistic-pkg-with-folders-1.0.0.tgz b/packages/libnpmdiff/test/fixtures/ruyadorno-simplistic-pkg-with-folders-1.0.0.tgz new file mode 100644 index 0000000000000..11bbb44c4e9a4 Binary files /dev/null and b/packages/libnpmdiff/test/fixtures/ruyadorno-simplistic-pkg-with-folders-1.0.0.tgz differ diff --git a/packages/libnpmdiff/test/fixtures/simple-output-2.2.1.tgz b/packages/libnpmdiff/test/fixtures/simple-output-2.2.1.tgz new file mode 100644 index 0000000000000..8d442f4c1c078 Binary files /dev/null and b/packages/libnpmdiff/test/fixtures/simple-output-2.2.1.tgz differ diff --git a/packages/libnpmdiff/test/format-diff.js b/packages/libnpmdiff/test/format-diff.js new file mode 100644 index 0000000000000..f2fc7c77d7f11 --- /dev/null +++ b/packages/libnpmdiff/test/format-diff.js @@ -0,0 +1,483 @@ +const t = require('tap') + +const formatDiff = require('../lib/format-diff.js') + +const normalizeWin = (str) => str + .replace(/\\+/g, '/') + .replace(/\r\n/g, '\n') + +t.cleanSnapshot = (str) => normalizeWin(str) + +t.test('format simple diff', t => { + const files = new Set([ + 'foo.js', + ]) + const refs = new Map(Object.entries({ + 'a/foo.js': { + content: '"use strict"\nmodule.exports = "foo"\n', + mode: '100644', + }, + 'b/foo.js': { + content: '"use strict"\nmodule.exports = "foobar"\n', + mode: '100644', + }, + })) + const versions = { + a: '1.0.0', + b: '2.0.0', + } + + t.matchSnapshot( + formatDiff({ + files, + refs, + versions, + }), + 'should output expected diff result' + ) + t.end() +}) + +t.test('nothing to diff', t => { + const files = new Set([ + 'foo.js', + ]) + const refs = new Map(Object.entries({ + 'a/foo.js': { + content: '"use strict"\nmodule.exports = "foo"\n', + mode: '100644', + }, + 'b/foo.js': { + content: '"use strict"\nmodule.exports = "foo"\n', + mode: '100644', + }, + })) + const versions = { + a: '1.0.0', + b: '1.0.0', + } + + t.matchSnapshot( + formatDiff({ + files, + refs, + versions, + }), + 'should output empty result' + ) + t.end() +}) + +t.test('format removed file', t => { + const files = new Set([ + 'foo.js', + ]) + const refs = new Map(Object.entries({ + 'a/foo.js': { + content: '"use strict"\nmodule.exports = "foo"\n', + mode: '100644', + }, + })) + const versions = { + a: '1.0.0', + b: '2.0.0', + } + + t.matchSnapshot( + formatDiff({ + files, + refs, + versions, + }), + 'should output expected removed file diff result' + ) + t.end() +}) + +t.test('changed file mode', t => { + const files = new Set([ + 'foo.js', + ]) + const refs = new Map(Object.entries({ + 'a/foo.js': { + content: '"use strict"\nmodule.exports = "foo"\n', + mode: '100644', + }, + 'b/foo.js': { + content: '"use strict"\nmodule.exports = "foo"\n', + mode: '100755', + }, + })) + const versions = { + a: '1.0.0', + b: '2.0.0', + } + + t.matchSnapshot( + formatDiff({ + files, + refs, + versions, + }), + 'should output expected changed file mode diff result' + ) + t.end() +}) + +t.test('added file', t => { + const files = new Set([ + 'foo.js', + ]) + const refs = new Map(Object.entries({ + 'b/foo.js': { + content: '"use strict"\nmodule.exports = "foo"\n', + mode: '100755', + }, + })) + const versions = { + a: '1.0.0', + b: '2.0.0', + } + + t.matchSnapshot( + formatDiff({ + files, + refs, + versions, + }), + 'should output expected added file diff result' + ) + t.end() +}) + +t.test('binary file', t => { + const files = new Set([ + 'foo.jpg', + ]) + const refs = new Map(Object.entries({ + 'a/foo.jpg': { + content: Buffer.from(''), + mode: '100644', + }, + 'b/foo.jpg': { + content: Buffer.from(''), + mode: '100644', + }, + })) + const versions = { + a: '1.0.0', + b: '2.0.0', + } + + t.matchSnapshot( + formatDiff({ + files, + refs, + versions, + }), + 'should output expected bin file diff result' + ) + t.end() +}) + +t.test('nothing to compare', t => { + const files = new Set([ + 'foo.jpg', + ]) + const refs = new Map(Object.entries({ + 'a/foo.jpg': {}, + 'b/foo.jpg': {}, + })) + const versions = { + a: '1.0.0', + b: '2.0.0', + } + + t.equal( + formatDiff({ + files, + refs, + versions, + }), + '', + 'should have no output' + ) + t.end() +}) + +t.test('colored output', t => { + const files = new Set([ + 'foo.js', + ]) + const refs = new Map(Object.entries({ + 'a/foo.js': { + content: '"use strict"\nmodule.exports = "foo"\n', + mode: '100644', + }, + 'b/foo.js': { + content: '"use strict"\nmodule.exports = "foobar"\n', + mode: '100644', + }, + })) + const versions = { + a: '1.0.0', + b: '2.0.0', + } + + t.matchSnapshot( + formatDiff({ + files, + refs, + versions, + opts: { + color: true, + }, + }), + 'should output expected colored diff result' + ) + t.end() +}) + +t.test('using --name-only option', t => { + const files = new Set([ + 'foo.js', + 'lib/bar.js', + 'lib/utils.js', + ]) + const refs = new Map(Object.entries({ + 'a/foo.js': { + content: '"use strict"\nmodule.exports = "foo"\n', + mode: '100644', + }, + 'b/foo.js': { + content: '"use strict"\nmodule.exports = "foobar"\n', + mode: '100644', + }, + 'a/lib/bar.js': { + content: '"use strict"\nmodule.exports = "bar"\n', + mode: '100644', + }, + 'b/lib/bar.js': { + content: '"use strict"\nmodule.exports = "bar"\n', + mode: '100644', + }, + 'a/lib/utils.js': { + content: '"use strict"\nconst bar = require("./bar.js")\n' + + 'module.exports = () => bar\n', + mode: '100644', + }, + 'b/lib/utils.js': { + content: '"use strict"\nconst bar = require("./bar.js")\n' + + 'module.exports =\n () => bar + "util"\n', + mode: '100644', + }, + })) + const versions = { + a: '1.0.0', + b: '2.0.0', + } + + t.matchSnapshot( + formatDiff({ + files, + refs, + versions, + opts: { + diffNameOnly: true, + }, + }), + 'should output expected diff result' + ) + t.end() +}) + +t.test('respect --tag-version-prefix option', t => { + const files = new Set([ + 'foo.js', + ]) + const refs = new Map(Object.entries({ + 'a/foo.js': { + content: '"use strict"\nmodule.exports = "foo"\n', + mode: '100644', + }, + 'b/foo.js': { + content: '"use strict"\nmodule.exports = "foobar"\n', + mode: '100644', + }, + })) + const versions = { + a: '1.0.0', + b: '2.0.0', + } + + t.matchSnapshot( + formatDiff({ + files, + refs, + versions, + opts: { + tagVersionPrefix: 'b', + }, + }), + 'should output expected diff result' + ) + t.end() +}) + +t.test('diff options', t => { + const files = new Set([ + 'foo.js', + ]) + const refs = new Map(Object.entries({ + 'a/foo.js': { + content: '"use strict"\nconst a = "a"\nconst b = "b"\n' + + 'const c = "c"\nmodule.exports = () => a+\nb+\nc\n', + mode: '100644', + }, + 'b/foo.js': { + content: '"use strict"\nconst a = "a"\n const b = "b"\n' + + ' const c = "c"\n const d = "d"\n' + + 'module.exports = () => a+\nb+\nc+\nd\n', + mode: '100644', + }, + })) + const versions = { + a: '1.0.0', + b: '2.0.0', + } + + t.matchSnapshot( + formatDiff({ + files, + refs, + versions, + opts: { + diffUnified: 1, + diffIgnoreAllSpace: true, + diffSrcPrefix: 'before/', + diffDstPrefix: 'after/', + }, + }), + 'should output expected diff result' + ) + t.end() +}) + +t.test('diffUnified=0', t => { + const files = new Set([ + 'foo.js', + ]) + const refs = new Map(Object.entries({ + 'a/foo.js': { + content: '"use strict"\nconst a = "a"\nconst b = "b"\n' + + 'const c = "c"\nmodule.exports = () => a+\nb+\nc\n', + mode: '100644', + }, + 'b/foo.js': { + content: '"use strict"\nconst a = "a"\n const b = "b"\n' + + ' const c = "c"\n const d = "d"\n' + + 'module.exports = () => a+\nb+\nc+\nd\n', + mode: '100644', + }, + })) + const versions = { + a: '1.0.0', + b: '2.0.0', + } + + t.matchSnapshot( + formatDiff({ + files, + refs, + versions, + opts: { + diffUnified: 0, + }, + }), + 'should output no context lines in output' + ) + t.end() +}) + +t.test('noPrefix', t => { + const files = new Set([ + 'foo.js', + ]) + const refs = new Map(Object.entries({ + 'a/foo.js': { + content: '"use strict"\nmodule.exports = "foo"\n', + mode: '100644', + }, + 'b/foo.js': { + content: '"use strict"\nmodule.exports = "foobar"\n', + mode: '100644', + }, + })) + const versions = { + a: '1.0.0', + b: '2.0.0', + } + + t.matchSnapshot( + formatDiff({ + files, + refs, + versions, + opts: { + diffNoPrefix: true, + }, + }), + 'should output result with no prefixes' + ) + t.end() +}) + +t.test('format multiple files patch', t => { + const files = new Set([ + 'foo.js', + 'lib/bar.js', + 'lib/utils.js', + ]) + const refs = new Map(Object.entries({ + 'a/foo.js': { + content: '"use strict"\nmodule.exports = "foo"\n', + mode: '100644', + }, + 'b/foo.js': { + content: '"use strict"\nmodule.exports = "foobar"\n', + mode: '100644', + }, + 'a/lib/bar.js': { + content: '"use strict"\nmodule.exports = "bar"\n', + mode: '100644', + }, + 'b/lib/bar.js': { + content: '"use strict"\nmodule.exports = "bar"\n', + mode: '100644', + }, + 'a/lib/utils.js': { + content: '"use strict"\nconst bar = require("./bar.js")\n' + + 'module.exports = () => bar\n', + mode: '100644', + }, + 'b/lib/utils.js': { + content: '"use strict"\nconst bar = require("./bar.js")\n' + + 'module.exports =\n () => bar + "util"\n', + mode: '100644', + }, + })) + const versions = { + a: '1.0.0', + b: '1.1.1', + } + + t.matchSnapshot( + formatDiff({ + files, + refs, + versions, + }), + 'should output expected result for multiple files' + ) + t.end() +}) diff --git a/packages/libnpmdiff/test/index.js b/packages/libnpmdiff/test/index.js new file mode 100644 index 0000000000000..88b474c111f15 --- /dev/null +++ b/packages/libnpmdiff/test/index.js @@ -0,0 +1,147 @@ +const { resolve } = require('path') + +const t = require('tap') + +const diff = require('../index.js') + +const normalizePath = p => p + .replace(/\\+/g, '/') + .replace(/\r\n/g, '\n') + +t.cleanSnapshot = (str) => normalizePath(str) + .replace(normalizePath(process.execPath), 'node') + +const json = (obj) => `${JSON.stringify(obj, null, 2)}\n` + +t.test('compare two diff specs', async t => { + const path = t.testdir({ + a1: { + 'package.json': json({ + name: 'a', + version: '1.0.0', + }), + 'index.js': 'module.exports =\n "a1"\n', + }, + a2: { + 'package.json': json({ + name: 'a', + version: '2.0.0', + }), + 'index.js': 'module.exports =\n "a2"\n', + }, + }) + + const a = `file:${resolve(path, 'a1')}` + const b = `file:${resolve(path, 'a2')}` + + t.resolveMatchSnapshot(diff([a, b], {}), 'should output expected diff') +}) + +t.test('using single arg', async t => { + await t.rejects( + diff(['abbrev@1.0.3']), + /libnpmdiff needs two arguments to compare/, + 'should throw EDIFFARGS error' + ) +}) + +t.test('too many args', async t => { + const args = ['abbrev@1.0.3', 'abbrev@1.0.4', 'abbrev@1.0.5'] + await t.rejects( + diff(args), + /libnpmdiff needs two arguments to compare/, + 'should output diff against cwd files' + ) +}) + +t.test('folder in node_modules', async t => { + const path = t.testdir({ + node_modules: { + a: { + 'package.json': json({ + name: 'a', + version: '1.0.0', + scripts: { + prepare: `${process.execPath} prepare.js`, + }, + }), + 'prepare.js': 'throw new Error("ERR")', + node_modules: { + b: { + 'package.json': json({ + name: 'b', + version: '2.0.0', + scripts: { + prepare: `${process.execPath} prepare.js`, + }, + }), + 'prepare.js': 'throw new Error("ERR")', + }, + }, + }, + }, + packages: { + a: { + 'package.json': json({ + name: 'a', + version: '1.0.1', + scripts: { + prepare: `${process.execPath} prepare.js`, + }, + }), + 'prepare.js': '', + }, + b: { + 'package.json': json({ + name: 'b', + version: '2.0.1', + scripts: { + prepare: `${process.execPath} prepare.js`, + }, + }), + 'prepare.js': '', + }, + }, + 'package.json': json({ + name: 'my-project', + version: '1.0.0', + }), + }) + + t.test('top-level, absolute path', async t => { + t.resolveMatchSnapshot(diff([ + `file:${resolve(path, 'node_modules/a')}`, + `file:${resolve(path, 'packages/a')}`, + ], { where: path }), 'should output expected diff') + }) + t.test('top-level, relative path', async t => { + const _cwd = process.cwd() + process.chdir(path) + t.teardown(() => { + process.chdir(_cwd) + }) + + t.resolveMatchSnapshot(diff([ + 'file:./node_modules/a', + 'file:./packages/a', + ], { where: path }), 'should output expected diff') + }) + t.test('nested, absolute path', async t => { + t.resolveMatchSnapshot(diff([ + `file:${resolve(path, 'node_modules/a/node_modules/b')}`, + `file:${resolve(path, 'packages/b')}`, + ], { where: path}), 'should output expected diff') + }) + t.test('nested, relative path', async t => { + const _cwd = process.cwd() + process.chdir(path) + t.teardown(() => { + process.chdir(_cwd) + }) + + t.resolveMatchSnapshot(diff([ + 'file:./node_modules/a/node_modules/b', + 'file:./packages/b', + ], { where: path }), 'should output expected diff') + }) +}) diff --git a/packages/libnpmdiff/test/should-print-patch.js b/packages/libnpmdiff/test/should-print-patch.js new file mode 100644 index 0000000000000..97b15787d3933 --- /dev/null +++ b/packages/libnpmdiff/test/should-print-patch.js @@ -0,0 +1,28 @@ +const t = require('tap') +const shouldPrintPatch = require('../lib/should-print-patch.js') + +t.test('valid filenames', t => { + t.ok(shouldPrintPatch('LICENSE')) + t.ok(shouldPrintPatch('.gitignore')) + t.ok(shouldPrintPatch('foo.md')) + t.ok(shouldPrintPatch('./bar.txt')) + t.ok(shouldPrintPatch('/a/b/c/bar.html')) + t.end() +}) + +t.test('invalid filenames', t => { + t.notOk(shouldPrintPatch('foo.exe')) + t.notOk(shouldPrintPatch('./foo.jpg')) + t.notOk(shouldPrintPatch('/a/b/c/bar.bin')) + t.end() +}) + +t.test('using --text/-a option', t => { + const opts = { + diffText: true, + } + t.ok(shouldPrintPatch('foo.exe', opts)) + t.ok(shouldPrintPatch('./foo.jpg', opts)) + t.ok(shouldPrintPatch('/a/b/c/bar.bin', opts)) + t.end() +}) diff --git a/packages/libnpmdiff/test/tarball.js b/packages/libnpmdiff/test/tarball.js new file mode 100644 index 0000000000000..3a959be6e53bc --- /dev/null +++ b/packages/libnpmdiff/test/tarball.js @@ -0,0 +1,96 @@ +const { resolve } = require('path') + +const t = require('tap') +const tar = require('tar') +const pacote = require('pacote') +pacote.tarball = () => { + throw new Error('Failed to detect node_modules tarball') +} + +const tarball = require('../lib/tarball.js') + +const json = (obj) => `${JSON.stringify(obj, null, 2)}\n` + +t.test('returns a tarball from node_modules', t => { + t.plan(2) + + const path = t.testdir({ + node_modules: { + a: { + 'package.json': json({ + name: 'a', + version: '1.0.0', + bin: { a: 'index.js' }, + }), + 'index.js': '', + }, + }, + }) + + const _cwd = process.cwd() + process.chdir(path) + t.teardown(() => { + process.chdir(_cwd) + }) + + tarball({ bin: { a: 'index.js' }, _resolved: resolve(path, 'node_modules/a') }, { where: path }) + .then(res => { + tar.list({ + filter: path => { + t.match( + path, + /package.json|index.js/, + 'should return tarball with expected files' + ) + }, + }) + .on('error', e => { + throw e + }) + .end(res) + }) +}) + +t.test('node_modules folder within a linked dir', async t => { + const path = t.testdir({ + node_modules: { + a: t.fixture('symlink', '../packages/a'), + }, + packages: { + a: { + node_modules: { + b: { + 'package.json': json({ + name: 'a', + version: '1.0.0', + }), + }, + }, + }, + }, + }) + + const link = await tarball({ _resolved: resolve(path, 'node_modules/a/node_modules/b') }, {}) + t.ok(link, 'should retrieve tarball from reading link') + + const target = await tarball({ _resolved: resolve(path, 'packages/a/node_modules/b') }, {}) + t.ok(target, 'should retrieve tarball from reading target') +}) + +t.test('pkg not in a node_modules folder', async t => { + const path = t.testdir({ + packages: { + a: { + 'package.json': json({ + name: 'a', + version: '1.0.0', + }), + }, + }, + }) + + t.throws( + () => tarball({ _resolved: resolve(path, 'packages/a') }, {}), + 'should call regular pacote.tarball method instead' + ) +}) diff --git a/packages/libnpmdiff/test/untar.js b/packages/libnpmdiff/test/untar.js new file mode 100644 index 0000000000000..62be1c6ba9003 --- /dev/null +++ b/packages/libnpmdiff/test/untar.js @@ -0,0 +1,231 @@ +const { resolve } = require('path') +const t = require('tap') +const pacote = require('pacote') +const untar = require('../lib/untar.js') + +t.test('untar simple package', async t => { + const item = + await pacote.tarball(resolve('./test/fixtures/simple-output-2.2.1.tgz')) + + const { + files, + refs, + } = await untar({ + item, + prefix: 'a/', + }) + + t.matchSnapshot([...files].join('\n'), 'should return list of filenames') + t.matchSnapshot( + [...refs.entries()].map(([k, v]) => `${k}: ${!!v}`).join('\n'), + 'should return map of filenames to its contents' + ) + t.matchSnapshot(refs.get('a/LICENSE').content, 'should have read contents') +}) + +t.test('untar package with folders', async t => { + const item = + await pacote.tarball(resolve('./test/fixtures/archive.tgz')) + + const { + files, + refs, + } = await untar({ + item, + prefix: 'a/', + }) + + t.matchSnapshot([...files].join('\n'), 'should return list of filenames') + t.matchSnapshot( + [...refs.entries()].map(([k, v]) => `${k}: ${!!v}`).join('\n'), + 'should return map of filenames to its contents' + ) + t.matchSnapshot( + refs.get('a/lib/utils/b.js').content, + 'should have read contents' + ) +}) + +t.test('filter files', async t => { + const item = + await pacote.tarball(resolve('./test/fixtures/simple-output-2.2.1.tgz')) + + const { + files, + refs, + } = await untar({ + item, + prefix: 'a/', + }, { + diffFiles: [ + './LICENSE', + 'missing-file', + 'README.md', + ], + }) + + t.matchSnapshot([...files].join('\n'), 'should return list of filenames') + t.matchSnapshot( + [...refs.entries()].map(([k, v]) => `${k}: ${!!v.content}`).join('\n'), + 'should return map of filenames with valid contents' + ) +}) + +t.test('filter files using glob expressions', async t => { + const item = + await pacote.tarball(resolve('./test/fixtures/archive.tgz')) + const cwd = t.testdir({ + lib: { + 'index.js': '', + utils: { + '/b.js': '', + }, + }, + 'package-lock.json': '', + 'package.json': '', + test: { + '/index.js': '', + utils: { + 'b.js': '', + }, + }, + }) + + const _cwd = process.cwd() + process.chdir(cwd) + t.teardown(() => { + process.chdir(_cwd) + }) + + const { + files, + refs, + } = await untar({ + item, + prefix: 'a/', + }, { + diffFiles: [ + './lib/**', + '*-lock.json', + 'test\\*', // windows-style sep should be normalized + ], + }) + + t.matchSnapshot([...files].join('\n'), 'should return list of filenames') + t.matchSnapshot( + [...refs.entries()].map(([k, v]) => `${k}: ${!!v.content}`).join('\n'), + 'should return map of filenames with valid contents' + ) +}) + +t.test('match files by end of filename', async t => { + const item = + await pacote.tarball(resolve('./test/fixtures/archive.tgz')) + + const { + files, + refs, + } = await untar({ + item, + prefix: 'a/', + }, { + diffFiles: [ + '*.js', + ], + }) + + t.matchSnapshot([...files].join('\n'), 'should return list of filenames') + t.matchSnapshot( + [...refs.entries()].map(([k, v]) => `${k}: ${!!v.content}`).join('\n'), + 'should return map of filenames with valid contents' + ) +}) + +t.test('filter files by exact filename', async t => { + const item = + await pacote.tarball(resolve('./test/fixtures/archive.tgz')) + + const { + files, + refs, + } = await untar({ + item, + prefix: 'a/', + }, { + diffFiles: [ + 'index.js', + ], + }) + + t.matchSnapshot([...files].join('\n'), 'should return no filenames') + t.matchSnapshot( + [...refs.entries()].map(([k, v]) => `${k}: ${!!v.content}`).join('\n'), + 'should return no filenames' + ) +}) + +t.test('match files by simple folder name', async t => { + const item = + await pacote.tarball(resolve('./test/fixtures/archive.tgz')) + + const { + files, + refs, + } = await untar({ + item, + prefix: 'a/', + }, { + diffFiles: [ + 'lib', + ], + }) + + t.matchSnapshot([...files].join('\n'), 'should return list of filenames') + t.matchSnapshot( + [...refs.entries()].map(([k, v]) => `${k}: ${!!v.content}`).join('\n'), + 'should return map of filenames with valid contents' + ) +}) + +t.test('match files by simple folder name variation', async t => { + const item = + await pacote.tarball(resolve('./test/fixtures/archive.tgz')) + + const { + files, + refs, + } = await untar({ + item, + prefix: 'a/', + }, { + diffFiles: [ + './test/', + ], + }) + + t.matchSnapshot([...files].join('\n'), 'should return list of filenames') + t.matchSnapshot( + [...refs.entries()].map(([k, v]) => `${k}: ${!!v.content}`).join('\n'), + 'should return map of filenames with valid contents' + ) +}) + +t.test('filter out all files', async t => { + const item = + await pacote.tarball(resolve('./test/fixtures/simple-output-2.2.1.tgz')) + + const { + files, + refs, + } = await untar({ + item, + prefix: 'a/', + }, { + diffFiles: [ + 'non-matching-pattern', + ], + }) + + t.equal(files.size, 0, 'should have no files') + t.equal(refs.size, 0, 'should have no refs') +}) diff --git a/scripts/bundle-and-gitignore-deps.js b/scripts/bundle-and-gitignore-deps.js index 84a3ab3ad9ef0..407b9e5982514 100644 --- a/scripts/bundle-and-gitignore-deps.js +++ b/scripts/bundle-and-gitignore-deps.js @@ -10,7 +10,10 @@ const shouldIgnore = [] arb.loadVirtual().then(tree => { for (const node of tree.children.values()) { - if (node.dev || node.isLink) { + const has = (obj, key) => Object.prototype.hasOwnProperty.call(obj, key) + const nonProdWorkspace = + node.isWorkspace && !(has(tree.package.dependencies, node.name)) + if (node.dev || nonProdWorkspace) { console.error('ignore', node.name) shouldIgnore.push(node.name) } else if (tree.edgesOut.has(node.name)) { diff --git a/tap-snapshots/test/lib/load-all-commands.js.test.cjs b/tap-snapshots/test/lib/load-all-commands.js.test.cjs index 097123d46a3cc..3575783a644b2 100644 --- a/tap-snapshots/test/lib/load-all-commands.js.test.cjs +++ b/tap-snapshots/test/lib/load-all-commands.js.test.cjs @@ -538,7 +538,7 @@ npm ll [[<@scope>/] ...] Options: [-a|--all] [--json] [-l|--long] [-p|--parseable] [-g|--global] [--depth ] [--omit [--omit ...]] [--link] -[--unicode] +[--package-lock-only] [--unicode] [-w|--workspace [-w|--workspace ...]] [-ws|--workspaces] @@ -583,12 +583,12 @@ npm ls List installed packages Usage: -npm ls npm ls [[<@scope>/] ...] +npm ls [[<@scope>/] ...] Options: [-a|--all] [--json] [-l|--long] [-p|--parseable] [-g|--global] [--depth ] [--omit [--omit ...]] [--link] -[--unicode] +[--package-lock-only] [--unicode] [-w|--workspace [-w|--workspace ...]] [-ws|--workspaces] @@ -657,7 +657,7 @@ Usage: npm pack [[<@scope>/]...] Options: -[--dry-run] [--json] +[--dry-run] [--json] [--pack-destination ] [-w|--workspace [-w|--workspace ...]] [-ws|--workspaces] diff --git a/tap-snapshots/test/lib/ls.js.test.cjs b/tap-snapshots/test/lib/ls.js.test.cjs index 2ed0b4b001376..3d56d1f432731 100644 --- a/tap-snapshots/test/lib/ls.js.test.cjs +++ b/tap-snapshots/test/lib/ls.js.test.cjs @@ -496,6 +496,7 @@ workspaces-tree@1.0.0 {CWD}/tap-testdir-ls-ls-loading-a-tree-containing-workspac exports[`test/lib/ls.js TAP ls loading a tree containing workspaces > should filter using workspace config 1`] = ` workspaces-tree@1.0.0 {CWD}/tap-testdir-ls-ls-loading-a-tree-containing-workspaces \`-- a@1.0.0 -> ./a + +-- baz@1.0.0 +-- c@1.0.0 \`-- d@1.0.0 -> ./d \`-- foo@1.1.1 @@ -506,6 +507,21 @@ workspaces-tree@1.0.0 {CWD}/tap-testdir-ls-ls-loading-a-tree-containing-workspac exports[`test/lib/ls.js TAP ls loading a tree containing workspaces > should list --all workspaces properly 1`] = ` workspaces-tree@1.0.0 {CWD}/tap-testdir-ls-ls-loading-a-tree-containing-workspaces +-- a@1.0.0 -> ./a +| +-- baz@1.0.0 +| +-- c@1.0.0 +| \`-- d@1.0.0 deduped -> ./d ++-- b@1.0.0 -> ./b ++-- d@1.0.0 -> ./d +| \`-- foo@1.1.1 +| \`-- bar@1.0.0 ++-- e@1.0.0 -> ./group/e +\`-- f@1.0.0 -> ./group/f + +` + +exports[`test/lib/ls.js TAP ls loading a tree containing workspaces > should list only prod deps of workspaces 1`] = ` +workspaces-tree@1.0.0 {CWD}/tap-testdir-ls-ls-loading-a-tree-containing-workspaces ++-- a@1.0.0 -> ./a | +-- c@1.0.0 | \`-- d@1.0.0 deduped -> ./d +-- b@1.0.0 -> ./b @@ -520,6 +536,7 @@ workspaces-tree@1.0.0 {CWD}/tap-testdir-ls-ls-loading-a-tree-containing-workspac exports[`test/lib/ls.js TAP ls loading a tree containing workspaces > should list workspaces properly with default configs 1`] = ` workspaces-tree@1.0.0 {CWD}/tap-testdir-ls-ls-loading-a-tree-containing-workspaces +-- a@1.0.0 -> ./a +| +-- baz@1.0.0 | +-- c@1.0.0 | \`-- d@1.0.0 deduped -> ./d +-- b@1.0.0 -> ./b diff --git a/tap-snapshots/test/lib/utils/config/definitions.js.test.cjs b/tap-snapshots/test/lib/utils/config/definitions.js.test.cjs index 32443c57af35b..35942fea64683 100644 --- a/tap-snapshots/test/lib/utils/config/definitions.js.test.cjs +++ b/tap-snapshots/test/lib/utils/config/definitions.js.test.cjs @@ -98,6 +98,7 @@ Array [ "package", "package-lock", "package-lock-only", + "pack-destination", "parseable", "prefer-offline", "prefer-online", diff --git a/tap-snapshots/test/lib/utils/config/describe-all.js.test.cjs b/tap-snapshots/test/lib/utils/config/describe-all.js.test.cjs index da8cd1794f2ac..daa071b642e94 100644 --- a/tap-snapshots/test/lib/utils/config/describe-all.js.test.cjs +++ b/tap-snapshots/test/lib/utils/config/describe-all.js.test.cjs @@ -622,18 +622,7 @@ Use of \`legacy-peer-deps\` is not recommended, as it will not enforce the * Default: false * Type: Boolean -If true, then local installs will link if there is a suitable globally -installed package. - -Note that this means that local installs can cause things to be installed -into the global space at the same time. The link is only done if one of the -two conditions are met: - -* The package is not already installed globally, or -* the globally installed version is identical to the version that is being - installed locally. - -When used with \`npm ls\`, only show packages that are linked. +Used with \`npm ls\`, limiting output to only those packages that are linked. #### \`local-address\` @@ -757,6 +746,13 @@ when publishing or changing package permissions with \`npm access\`. If not set, and a registry response fails with a challenge for a one-time password, npm will prompt on the command line for one. +#### \`pack-destination\` + +* Default: "." +* Type: String + +Directory in which \`npm pack\` will save tarballs. + #### \`package\` * Default: @@ -781,8 +777,14 @@ package-locks disabled use \`npm prune\`. * Default: false * Type: Boolean -If set to true, it will update only the \`package-lock.json\`, instead of -checking \`node_modules\` and downloading dependencies. +If set to true, the current operation will only use the \`package-lock.json\`, +ignoring \`node_modules\`. + +For \`update\` this means only the \`package-lock.json\` will be updated, +instead of checking \`node_modules\` and downloading dependencies. + +For \`list\` this means the output will be based on the tree described by the +\`package-lock.json\`, rather than the contents of \`node_modules\`. #### \`parseable\` diff --git a/tap-snapshots/test/lib/utils/error-handler.js.test.cjs b/tap-snapshots/test/lib/utils/error-handler.js.test.cjs index 909051cdab506..78a9eef217f35 100644 --- a/tap-snapshots/test/lib/utils/error-handler.js.test.cjs +++ b/tap-snapshots/test/lib/utils/error-handler.js.test.cjs @@ -8,7 +8,7 @@ exports[`test/lib/utils/error-handler.js TAP handles unknown error > should have expected log contents for unknown error 1`] = ` 0 verbose code 1 1 error foo A complete log of this run can be found in: -1 error foo {CWD}/cachefolder/_logs/expecteddate-debug.log +1 error foo {CWD}/test/lib/utils/tap-testdir-error-handler/_logs/expecteddate-debug.log 2 verbose stack Error: ERROR 3 verbose cwd {CWD} 4 verbose Foo 1.0.0 diff --git a/tap-snapshots/test/lib/utils/explain-eresolve.js.test.cjs b/tap-snapshots/test/lib/utils/explain-eresolve.js.test.cjs index e066680153021..4c84aaca4ceeb 100644 --- a/tap-snapshots/test/lib/utils/explain-eresolve.js.test.cjs +++ b/tap-snapshots/test/lib/utils/explain-eresolve.js.test.cjs @@ -5,7 +5,7 @@ * Make sure to inspect the output below. Do not ignore changes! */ 'use strict' -exports[`test/lib/utils/explain-eresolve.js TAP chain-conflict > explain with color 1`] = ` +exports[`test/lib/utils/explain-eresolve.js TAP chain-conflict > explain with color, depth of 2 1`] = ` While resolving: project@1.2.3 Found: @isaacs/testing-peer-dep-conflict-chain-d@2.0.0 node_modules/@isaacs/testing-peer-dep-conflict-chain-d @@ -75,25 +75,7 @@ to accept an incorrect (and potentially broken) dependency resolution. See \${REPORT} for a full report. ` -exports[`test/lib/utils/explain-eresolve.js TAP chain-conflict > report with color, depth only 2 1`] = ` -While resolving: project@1.2.3 -Found: @isaacs/testing-peer-dep-conflict-chain-d@2.0.0 -node_modules/@isaacs/testing-peer-dep-conflict-chain-d - @isaacs/testing-peer-dep-conflict-chain-d@"2" from the root project - -Could not resolve dependency: -peer @isaacs/testing-peer-dep-conflict-chain-d@"1" from @isaacs/testing-peer-dep-conflict-chain-c@1.0.0 -node_modules/@isaacs/testing-peer-dep-conflict-chain-c - @isaacs/testing-peer-dep-conflict-chain-c@"1" from the root project - -Fix the upstream dependency conflict, or retry -this command with --force, or --legacy-peer-deps -to accept an incorrect (and potentially broken) dependency resolution. - -See \${REPORT} for a full report. -` - -exports[`test/lib/utils/explain-eresolve.js TAP chain-conflict > report with no color, depth of 6 1`] = ` +exports[`test/lib/utils/explain-eresolve.js TAP chain-conflict > report with no color 1`] = ` While resolving: project@1.2.3 Found: @isaacs/testing-peer-dep-conflict-chain-d@2.0.0 node_modules/@isaacs/testing-peer-dep-conflict-chain-d @@ -111,7 +93,7 @@ to accept an incorrect (and potentially broken) dependency resolution. See \${REPORT} for a full report. ` -exports[`test/lib/utils/explain-eresolve.js TAP cycleNested > explain with color 1`] = ` +exports[`test/lib/utils/explain-eresolve.js TAP cycleNested > explain with color, depth of 2 1`] = ` Found: @isaacs/peer-dep-cycle-c@2.0.0 node_modules/@isaacs/peer-dep-cycle-c @isaacs/peer-dep-cycle-c@"2.x" from the root project @@ -208,31 +190,7 @@ to accept an incorrect (and potentially broken) dependency resolution. See \${REPORT} for a full report. ` -exports[`test/lib/utils/explain-eresolve.js TAP cycleNested > report with color, depth only 2 1`] = ` -Found: @isaacs/peer-dep-cycle-c@2.0.0 -node_modules/@isaacs/peer-dep-cycle-c - @isaacs/peer-dep-cycle-c@"2.x" from the root project - -Could not resolve dependency: -peer @isaacs/peer-dep-cycle-b@"1" from @isaacs/peer-dep-cycle-a@1.0.0 -node_modules/@isaacs/peer-dep-cycle-a - @isaacs/peer-dep-cycle-a@"1.x" from the root project - -Conflicting peer dependency: @isaacs/peer-dep-cycle-c@1.0.0 -node_modules/@isaacs/peer-dep-cycle-c - peer @isaacs/peer-dep-cycle-c@"1" from @isaacs/peer-dep-cycle-b@1.0.0 - node_modules/@isaacs/peer-dep-cycle-b - peer @isaacs/peer-dep-cycle-b@"1" from @isaacs/peer-dep-cycle-a@1.0.0 - node_modules/@isaacs/peer-dep-cycle-a - -Fix the upstream dependency conflict, or retry -this command with --no-strict-peer-deps, --force, or --legacy-peer-deps -to accept an incorrect (and potentially broken) dependency resolution. - -See \${REPORT} for a full report. -` - -exports[`test/lib/utils/explain-eresolve.js TAP cycleNested > report with no color, depth of 6 1`] = ` +exports[`test/lib/utils/explain-eresolve.js TAP cycleNested > report with no color 1`] = ` Found: @isaacs/peer-dep-cycle-c@2.0.0 node_modules/@isaacs/peer-dep-cycle-c @isaacs/peer-dep-cycle-c@"2.x" from the root project @@ -257,7 +215,7 @@ to accept an incorrect (and potentially broken) dependency resolution. See \${REPORT} for a full report. ` -exports[`test/lib/utils/explain-eresolve.js TAP gatsby > explain with color 1`] = ` +exports[`test/lib/utils/explain-eresolve.js TAP gatsby > explain with color, depth of 2 1`] = ` While resolving: gatsby-recipes@0.2.31 Found: ink@3.0.0-7 node_modules/ink @@ -366,29 +324,7 @@ to accept an incorrect (and potentially broken) dependency resolution. See \${REPORT} for a full report. ` -exports[`test/lib/utils/explain-eresolve.js TAP gatsby > report with color, depth only 2 1`] = ` -While resolving: gatsby-recipes@0.2.31 -Found: ink@3.0.0-7 -node_modules/ink - dev ink@"next" from gatsby-recipes@0.2.31 - node_modules/gatsby-recipes - gatsby-recipes@"^0.2.31" from gatsby-cli@2.12.107 - node_modules/gatsby-cli - -Could not resolve dependency: -peer ink@">=2.0.0" from ink-box@1.0.0 -node_modules/ink-box - ink-box@"^1.0.0" from gatsby-recipes@0.2.31 - node_modules/gatsby-recipes - -Fix the upstream dependency conflict, or retry -this command with --no-strict-peer-deps, --force, or --legacy-peer-deps -to accept an incorrect (and potentially broken) dependency resolution. - -See \${REPORT} for a full report. -` - -exports[`test/lib/utils/explain-eresolve.js TAP gatsby > report with no color, depth of 6 1`] = ` +exports[`test/lib/utils/explain-eresolve.js TAP gatsby > report with no color 1`] = ` While resolving: gatsby-recipes@0.2.31 Found: ink@3.0.0-7 node_modules/ink @@ -409,7 +345,6 @@ node_modules/ink-box node_modules/gatsby-cli gatsby-cli@"^2.12.107" from gatsby@2.24.74 node_modules/gatsby - gatsby@"" from the root project Fix the upstream dependency conflict, or retry this command with --no-strict-peer-deps, --force, or --legacy-peer-deps @@ -418,7 +353,7 @@ to accept an incorrect (and potentially broken) dependency resolution. See \${REPORT} for a full report. ` -exports[`test/lib/utils/explain-eresolve.js TAP no current node, but has current edge > explain with color 1`] = ` +exports[`test/lib/utils/explain-eresolve.js TAP no current node, but has current edge > explain with color, depth of 2 1`] = ` While resolving: eslint@7.22.0 Found: dev eslint@"file:." from the root project @@ -480,23 +415,7 @@ to accept an incorrect (and potentially broken) dependency resolution. See \${REPORT} for a full report. ` -exports[`test/lib/utils/explain-eresolve.js TAP no current node, but has current edge > report with color, depth only 2 1`] = ` -While resolving: eslint@7.22.0 -Found: dev eslint@"file:." from the root project - -Could not resolve dependency: -peer eslint@"^6.0.0" from eslint-plugin-jsdoc@22.2.0 -node_modules/eslint-plugin-jsdoc - dev eslint-plugin-jsdoc@"^22.1.0" from the root project - -Fix the upstream dependency conflict, or retry -this command with --force, or --legacy-peer-deps -to accept an incorrect (and potentially broken) dependency resolution. - -See \${REPORT} for a full report. -` - -exports[`test/lib/utils/explain-eresolve.js TAP no current node, but has current edge > report with no color, depth of 6 1`] = ` +exports[`test/lib/utils/explain-eresolve.js TAP no current node, but has current edge > report with no color 1`] = ` While resolving: eslint@7.22.0 Found: dev eslint@"file:." from the root project @@ -512,7 +431,7 @@ to accept an incorrect (and potentially broken) dependency resolution. See \${REPORT} for a full report. ` -exports[`test/lib/utils/explain-eresolve.js TAP no current node, no current edge, idk > explain with color 1`] = ` +exports[`test/lib/utils/explain-eresolve.js TAP no current node, no current edge, idk > explain with color, depth of 2 1`] = ` While resolving: eslint@7.22.0 Could not resolve dependency: @@ -570,22 +489,7 @@ to accept an incorrect (and potentially broken) dependency resolution. See \${REPORT} for a full report. ` -exports[`test/lib/utils/explain-eresolve.js TAP no current node, no current edge, idk > report with color, depth only 2 1`] = ` -While resolving: eslint@7.22.0 - -Could not resolve dependency: -peer eslint@"^6.0.0" from eslint-plugin-jsdoc@22.2.0 -node_modules/eslint-plugin-jsdoc - dev eslint-plugin-jsdoc@"^22.1.0" from the root project - -Fix the upstream dependency conflict, or retry -this command with --force, or --legacy-peer-deps -to accept an incorrect (and potentially broken) dependency resolution. - -See \${REPORT} for a full report. -` - -exports[`test/lib/utils/explain-eresolve.js TAP no current node, no current edge, idk > report with no color, depth of 6 1`] = ` +exports[`test/lib/utils/explain-eresolve.js TAP no current node, no current edge, idk > report with no color 1`] = ` While resolving: eslint@7.22.0 Could not resolve dependency: @@ -600,7 +504,7 @@ to accept an incorrect (and potentially broken) dependency resolution. See \${REPORT} for a full report. ` -exports[`test/lib/utils/explain-eresolve.js TAP withShrinkwrap > explain with color 1`] = ` +exports[`test/lib/utils/explain-eresolve.js TAP withShrinkwrap > explain with color, depth of 2 1`] = ` While resolving: @isaacs/peer-dep-cycle-b@1.0.0 Found: @isaacs/peer-dep-cycle-c@2.0.0 node_modules/@isaacs/peer-dep-cycle-c @@ -677,26 +581,7 @@ to accept an incorrect (and potentially broken) dependency resolution. See \${REPORT} for a full report. ` -exports[`test/lib/utils/explain-eresolve.js TAP withShrinkwrap > report with color, depth only 2 1`] = ` -While resolving: @isaacs/peer-dep-cycle-b@1.0.0 -Found: @isaacs/peer-dep-cycle-c@2.0.0 -node_modules/@isaacs/peer-dep-cycle-c - @isaacs/peer-dep-cycle-c@"2.x" from the root project - -Could not resolve dependency: -peer @isaacs/peer-dep-cycle-c@"1" from @isaacs/peer-dep-cycle-b@1.0.0 -node_modules/@isaacs/peer-dep-cycle-b - peer @isaacs/peer-dep-cycle-b@"1" from @isaacs/peer-dep-cycle-a@1.0.0 - node_modules/@isaacs/peer-dep-cycle-a - -Fix the upstream dependency conflict, or retry -this command with --no-strict-peer-deps, --force, or --legacy-peer-deps -to accept an incorrect (and potentially broken) dependency resolution. - -See \${REPORT} for a full report. -` - -exports[`test/lib/utils/explain-eresolve.js TAP withShrinkwrap > report with no color, depth of 6 1`] = ` +exports[`test/lib/utils/explain-eresolve.js TAP withShrinkwrap > report with no color 1`] = ` While resolving: @isaacs/peer-dep-cycle-b@1.0.0 Found: @isaacs/peer-dep-cycle-c@2.0.0 node_modules/@isaacs/peer-dep-cycle-c diff --git a/tap-snapshots/test/lib/utils/npm-usage.js.test.cjs b/tap-snapshots/test/lib/utils/npm-usage.js.test.cjs index 54f6c3d2feb2a..3987f6a732da5 100644 --- a/tap-snapshots/test/lib/utils/npm-usage.js.test.cjs +++ b/tap-snapshots/test/lib/utils/npm-usage.js.test.cjs @@ -639,7 +639,7 @@ All commands: Options: [-a|--all] [--json] [-l|--long] [-p|--parseable] [-g|--global] [--depth ] [--omit [--omit ...]] [--link] - [--unicode] + [--package-lock-only] [--unicode] [-w|--workspace [-w|--workspace ...]] [-ws|--workspaces] @@ -678,12 +678,12 @@ All commands: List installed packages Usage: - npm ls npm ls [[<@scope>/] ...] + npm ls [[<@scope>/] ...] Options: [-a|--all] [--json] [-l|--long] [-p|--parseable] [-g|--global] [--depth ] [--omit [--omit ...]] [--link] - [--unicode] + [--package-lock-only] [--unicode] [-w|--workspace [-w|--workspace ...]] [-ws|--workspaces] @@ -744,7 +744,7 @@ All commands: npm pack [[<@scope>/]...] Options: - [--dry-run] [--json] + [--dry-run] [--json] [--pack-destination ] [-w|--workspace [-w|--workspace ...]] [-ws|--workspaces] diff --git a/test/lib/cli.js b/test/lib/cli.js index 42e05cc5d14c3..d0a9e0bd49200 100644 --- a/test/lib/cli.js +++ b/test/lib/cli.js @@ -25,6 +25,7 @@ const unsupportedMock = { } let errorHandlerCalled = null +let errorHandlerNpm = null let errorHandlerCb const errorHandlerMock = (...args) => { errorHandlerCalled = args @@ -35,6 +36,9 @@ let errorHandlerExitCalled = null errorHandlerMock.exit = code => { errorHandlerExitCalled = code } +errorHandlerMock.setNpm = npm => { + errorHandlerNpm = npm +} const logs = [] const npmlogMock = { @@ -181,6 +185,7 @@ t.test('gracefully handles error printing usage', t => { npmock.argv = [] errorHandlerCb = () => { t.match(errorHandlerCalled, [], 'should call errorHandler with no args') + t.match(errorHandlerNpm, npmock, 'errorHandler npm is set') t.end() } cli(proc) diff --git a/test/lib/diff.js b/test/lib/diff.js index 993dfa4d60718..2fb38c9b127e4 100644 --- a/test/lib/diff.js +++ b/test/lib/diff.js @@ -549,12 +549,13 @@ t.test('single arg', t => { t.test('dir spec type', t => { t.plan(2) + const otherPath = resolve('/path/to/other-dir') libnpmdiff = async ([a, b], opts) => { - t.equal(a, 'file:/path/to/other-dir', 'should target dir') + t.equal(a, `file:${otherPath}`, 'should target dir') t.equal(b, `file:${fooPath}`, 'should compare to cwd') } - config.diff = ['/path/to/other-dir'] + config.diff = [otherPath] diff.exec([], err => { if (err) throw err diff --git a/test/lib/exec.js b/test/lib/exec.js index 6924783239b49..dff067619f2ce 100644 --- a/test/lib/exec.js +++ b/test/lib/exec.js @@ -24,11 +24,13 @@ let PROGRESS_ENABLED = true const LOG_WARN = [] let PROGRESS_IGNORED = false const flatOptions = { + npxCache: 'npx-cache-dir', + cache: 'cache-dir', legacyPeerDeps: false, package: [], } const config = { - cache: 'cache-dir', + cache: 'bad-cache-dir', // this should never show up passed into libnpmexec yes: true, call: '', package: [], @@ -134,6 +136,8 @@ t.test('npx foo, bin already exists locally', t => { t.match(RUN_SCRIPTS, [{ pkg: { scripts: { npx: 'foo' }}, args: ['one arg', 'two arg'], + cache: flatOptions.cache, + npxCache: flatOptions.npxCache, banner: false, path: process.cwd(), stdioString: true, @@ -325,7 +329,7 @@ t.test('npm exec , run interactive shell', t => { t.test('npm exec foo, not present locally or in central loc', t => { const path = t.testdir() - const installDir = resolve('cache-dir/_npx/f7fbba6e0636f890') + const installDir = resolve('npx-cache-dir/f7fbba6e0636f890') npm.localPrefix = path ARB_ACTUAL_TREE[path] = { children: new Map(), @@ -365,7 +369,7 @@ t.test('npm exec foo, not present locally or in central loc', t => { t.test('npm exec foo, not present locally but in central loc', t => { const path = t.testdir() - const installDir = resolve('cache-dir/_npx/f7fbba6e0636f890') + const installDir = resolve('npx-cache-dir/f7fbba6e0636f890') npm.localPrefix = path ARB_ACTUAL_TREE[path] = { children: new Map(), @@ -405,7 +409,7 @@ t.test('npm exec foo, not present locally but in central loc', t => { t.test('npm exec foo, present locally but wrong version', t => { const path = t.testdir() - const installDir = resolve('cache-dir/_npx/2badf4630f1cfaad') + const installDir = resolve('npx-cache-dir/2badf4630f1cfaad') npm.localPrefix = path ARB_ACTUAL_TREE[path] = { children: new Map(), @@ -601,7 +605,7 @@ t.test('run command with 2 packages, need install, verify sort', t => { config.package = packages const add = packages.map(p => `${p}@`).sort((a, b) => a.localeCompare(b, 'en')) const path = t.testdir() - const installDir = resolve('cache-dir/_npx/07de77790e5f40f2') + const installDir = resolve('npx-cache-dir/07de77790e5f40f2') npm.localPrefix = path ARB_ACTUAL_TREE[path] = { children: new Map(), @@ -756,7 +760,7 @@ t.test('prompt when installs are needed if not already present and shell is a TT const add = packages.map(p => `${p}@`).sort((a, b) => a.localeCompare(b, 'en')) const path = t.testdir() - const installDir = resolve('cache-dir/_npx/07de77790e5f40f2') + const installDir = resolve('npx-cache-dir/07de77790e5f40f2') npm.localPrefix = path ARB_ACTUAL_TREE[path] = { children: new Map(), @@ -825,7 +829,7 @@ t.test('skip prompt when installs are needed if not already present and shell is const add = packages.map(p => `${p}@`).sort((a, b) => a.localeCompare(b, 'en')) const path = t.testdir() - const installDir = resolve('cache-dir/_npx/07de77790e5f40f2') + const installDir = resolve('npx-cache-dir/07de77790e5f40f2') npm.localPrefix = path ARB_ACTUAL_TREE[path] = { children: new Map(), @@ -892,7 +896,7 @@ t.test('skip prompt when installs are needed if not already present and shell is const add = packages.map(p => `${p}@`).sort((a, b) => a.localeCompare(b, 'en')) const path = t.testdir() - const installDir = resolve('cache-dir/_npx/f7fbba6e0636f890') + const installDir = resolve('npx-cache-dir/f7fbba6e0636f890') npm.localPrefix = path ARB_ACTUAL_TREE[path] = { children: new Map(), @@ -950,7 +954,7 @@ t.test('abort if prompt rejected', t => { config.yes = undefined const path = t.testdir() - const installDir = resolve('cache-dir/_npx/07de77790e5f40f2') + const installDir = resolve('npx-cache-dir/07de77790e5f40f2') npm.localPrefix = path ARB_ACTUAL_TREE[path] = { children: new Map(), @@ -1008,7 +1012,7 @@ t.test('abort if prompt false', t => { config.yes = undefined const path = t.testdir() - const installDir = resolve('cache-dir/_npx/07de77790e5f40f2') + const installDir = resolve('npx-cache-dir/07de77790e5f40f2') npm.localPrefix = path ARB_ACTUAL_TREE[path] = { children: new Map(), @@ -1065,7 +1069,7 @@ t.test('abort if -n provided', t => { config.yes = false const path = t.testdir() - const installDir = resolve('cache-dir/_npx/07de77790e5f40f2') + const installDir = resolve('npx-cache-dir/07de77790e5f40f2') npm.localPrefix = path ARB_ACTUAL_TREE[path] = { children: new Map(), @@ -1103,7 +1107,7 @@ t.test('abort if -n provided', t => { t.test('forward legacyPeerDeps opt', t => { const path = t.testdir() - const installDir = resolve('cache-dir/_npx/f7fbba6e0636f890') + const installDir = resolve('npx-cache-dir/f7fbba6e0636f890') npm.localPrefix = path ARB_ACTUAL_TREE[path] = { children: new Map(), diff --git a/test/lib/init.js b/test/lib/init.js index 268b170cb4839..44a2af5bcc02b 100644 --- a/test/lib/init.js +++ b/test/lib/init.js @@ -12,10 +12,16 @@ const npmLog = { silly: () => null, } const config = { + cache: 'bad-cache-dir', 'init-module': '~/.npm-init.js', yes: true, } +const flatOptions = { + cache: 'test-config-dir/_cacache', + npxCache: 'test-config-dir/_npx', +} const npm = mockNpm({ + flatOptions, config, log: npmLog, }) @@ -82,16 +88,18 @@ t.test('classic interactive npm init', t => { }) t.test('npm init ', t => { - t.plan(1) + t.plan(3) npm.localPrefix = t.testdir({}) const Init = t.mock('../../lib/init.js', { - libnpmexec: ({ args }) => { + libnpmexec: ({ args, cache, npxCache }) => { t.same( args, ['create-react-app'], 'should npx with listed packages' ) + t.same(cache, flatOptions.cache) + t.same(npxCache, flatOptions.npxCache) }, }) const init = new Init(npm) diff --git a/test/lib/link.js b/test/lib/link.js index 3cad0ff90362d..64375cfc13c2c 100644 --- a/test/lib/link.js +++ b/test/lib/link.js @@ -1,4 +1,5 @@ const { resolve } = require('path') +const fs = require('fs') const Arborist = require('@npmcli/arborist') const t = require('tap') @@ -485,6 +486,55 @@ t.test('link pkg already in global space when prefix is a symlink', (t) => { }) }) +t.test('should not prune dependencies when linking packages', async t => { + const testdir = t.testdir({ + 'global-prefix': { + lib: { + node_modules: { + linked: t.fixture('symlink', '../../../linked'), + }, + }, + }, + linked: { + 'package.json': JSON.stringify({ + name: 'linked', + version: '1.0.0', + }), + }, + 'my-project': { + node_modules: { + foo: { + 'package.json': JSON.stringify({ name: 'foo', version: '1.0.0' }), + }, + }, + 'package.json': JSON.stringify({ + name: 'my-project', + version: '1.0.0', + }), + }, + }) + npm.globalDir = resolve(testdir, 'global-prefix', 'lib', 'node_modules') + npm.prefix = resolve(testdir, 'my-project') + reifyOutput = () => {} + + const _cwd = process.cwd() + process.chdir(npm.prefix) + + await new Promise((res, rej) => { + link.exec(['linked'], (err) => { + if (err) + rej(err) + res() + }) + }) + + t.ok( + fs.statSync(resolve(testdir, 'my-project/node_modules/foo')), + 'should not prune any extraneous dep when running npm link' + ) + process.chdir(_cwd) +}) + t.test('completion', async t => { const testdir = t.testdir({ 'global-prefix': { diff --git a/test/lib/load-all.js b/test/lib/load-all.js index 02736c18ccc38..4a975d49a490e 100644 --- a/test/lib/load-all.js +++ b/test/lib/load-all.js @@ -24,6 +24,7 @@ else { t.test('call the error handle so we dont freak out', t => { const errorHandler = require('../../lib/utils/error-handler.js') + errorHandler.setNpm(npm) errorHandler() t.end() }) diff --git a/test/lib/ls.js b/test/lib/ls.js index ecdede809df20..64ece6bd8a3fd 100644 --- a/test/lib/ls.js +++ b/test/lib/ls.js @@ -107,6 +107,7 @@ const config = { only: null, parseable: false, production: false, + 'package-lock-only': false, } const flatOptions = { } @@ -1448,6 +1449,9 @@ t.test('ls', (t) => { bar: { 'package.json': JSON.stringify({ name: 'bar', version: '1.0.0' }), }, + baz: { + 'package.json': JSON.stringify({ name: 'baz', version: '1.0.0' }), + }, }, a: { 'package.json': JSON.stringify({ @@ -1457,6 +1461,9 @@ t.test('ls', (t) => { c: '^1.0.0', d: '^1.0.0', }, + devDependencies: { + baz: '^1.0.0', + }, }), }, b: { @@ -1519,6 +1526,21 @@ t.test('ls', (t) => { }) }) + // --production + await new Promise((res, rej) => { + config.production = true + ls.exec([], (err) => { + if (err) + rej(err) + + t.matchSnapshot(redactCwd(result), + 'should list only prod deps of workspaces') + + config.production = false + res() + }) + }) + // filter out a single workspace using args await new Promise((res, rej) => { ls.exec(['d'], (err) => { @@ -4152,3 +4174,788 @@ t.test('ls --json', (t) => { t.end() }) + +t.test('ls --package-lock-only', (t) => { + config['package-lock-only'] = true + t.test('ls --package-lock-only --json', (t) => { + t.beforeEach(cleanUpResult) + config.json = true + config.parseable = false + t.test('no args', (t) => { + npm.prefix = t.testdir({ + 'package.json': JSON.stringify({ + name: 'test-npm-ls', + version: '1.0.0', + dependencies: { + foo: '^1.0.0', + chai: '^1.0.0', + }, + }), + 'package-lock.json': JSON.stringify({ + dependencies: { + foo: { + version: '1.0.0', + requires: { + dog: '^1.0.0', + }, + }, + dog: { + version: '1.0.0', + }, + chai: { + version: '1.0.0', + }, + }, + }), + }) + ls.exec([], (err) => { + t.error(err, 'npm ls') + t.same( + jsonParse(result), + { + name: 'test-npm-ls', + version: '1.0.0', + dependencies: { + foo: { + version: '1.0.0', + dependencies: { + dog: { + version: '1.0.0', + }, + }, + }, + chai: { + version: '1.0.0', + }, + }, + }, + 'should output json representation of dependencies structure' + ) + t.end() + }) + }) + + t.test('extraneous deps', (t) => { + npm.prefix = t.testdir({ + 'package.json': JSON.stringify({ + name: 'test-npm-ls', + version: '1.0.0', + dependencies: { + foo: '^1.0.0', + }, + }), + 'package-lock.json': JSON.stringify({ + dependencies: { + foo: { + version: '1.0.0', + requires: { + dog: '^1.0.0', + }, + }, + dog: { + version: '1.0.0', + }, + chai: { + version: '1.0.0', + }, + }, + }), + }) + ls.exec([], (err) => { + t.error(err) // should not error for extraneous + t.same( + jsonParse(result), + { + name: 'test-npm-ls', + version: '1.0.0', + dependencies: { + foo: { + version: '1.0.0', + dependencies: { + dog: { + version: '1.0.0', + }, + }, + }, + }, + }, + 'should output json containing no problem info' + ) + t.end() + }) + }) + + t.test('missing deps --long', (t) => { + config.long = true + npm.prefix = t.testdir({ + 'package.json': JSON.stringify({ + name: 'test-npm-ls', + version: '1.0.0', + dependencies: { + foo: '^1.0.0', + dog: '^1.0.0', + chai: '^1.0.0', + ipsum: '^1.0.0', + }, + }), + 'package-lock.json': JSON.stringify({ + dependencies: { + foo: { + version: '1.0.0', + requires: { + dog: '^1.0.0', + }, + }, + dog: { + version: '1.0.0', + }, + chai: { + version: '1.0.0', + }, + ipsum: { + version: '1.0.0', + }, + }, + }), + }) + ls.exec([], (err) => { + t.error(err, 'npm ls') + t.match( + jsonParse(result), + { + name: 'test-npm-ls', + version: '1.0.0', + }, + 'should output json containing no problems info' + ) + config.long = false + t.end() + }) + }) + + t.test('with filter arg', (t) => { + npm.prefix = t.testdir({ + 'package.json': JSON.stringify({ + name: 'test-npm-ls', + version: '1.0.0', + dependencies: { + foo: '^1.0.0', + chai: '^1.0.0', + }, + }), + 'package-lock.json': JSON.stringify({ + dependencies: { + foo: { + version: '1.0.0', + requires: { + dog: '^1.0.0', + }, + }, + dog: { + version: '1.0.0', + }, + chai: { + version: '1.0.0', + }, + ipsum: { + version: '1.0.0', + }, + }, + }), + }) + ls.exec(['chai'], (err) => { + t.error(err, 'npm ls') + t.same( + jsonParse(result), + { + name: 'test-npm-ls', + version: '1.0.0', + dependencies: { + chai: { + version: '1.0.0', + }, + }, + }, + 'should output json contaning only occurrences of filtered by package' + ) + t.equal( + process.exitCode, + 0, + 'should exit with error code 0' + ) + t.end() + }) + }) + + t.test('with filter arg nested dep', (t) => { + npm.prefix = t.testdir({ + 'package.json': JSON.stringify({ + name: 'test-npm-ls', + version: '1.0.0', + dependencies: { + foo: '^1.0.0', + chai: '^1.0.0', + }, + }), + 'package-lock.json': JSON.stringify({ + dependencies: { + foo: { + version: '1.0.0', + requires: { + dog: '^1.0.0', + }, + }, + dog: { + version: '1.0.0', + }, + chai: { + version: '1.0.0', + }, + ipsum: { + version: '1.0.0', + }, + }, + }), + }) + ls.exec(['dog'], (err) => { + t.error(err, 'npm ls') + t.same( + jsonParse(result), + { + name: 'test-npm-ls', + version: '1.0.0', + dependencies: { + foo: { + version: '1.0.0', + dependencies: { + dog: { + version: '1.0.0', + }, + }, + }, + }, + }, + 'should output json contaning only occurrences of filtered by package' + ) + t.end() + }) + }) + + t.test('with multiple filter args', (t) => { + npm.prefix = t.testdir({ + 'package.json': JSON.stringify({ + name: 'test-npm-ls', + version: '1.0.0', + dependencies: { + foo: '^1.0.0', + chai: '^1.0.0', + ipsum: '^1.0.0', + }, + }), + 'package-lock.json': JSON.stringify({ + dependencies: { + foo: { + version: '1.0.0', + requires: { + dog: '^1.0.0', + }, + }, + dog: { + version: '1.0.0', + }, + chai: { + version: '1.0.0', + }, + ipsum: { + version: '1.0.0', + }, + }, + }), + }) + ls.exec(['dog@*', 'chai@1.0.0'], (err) => { + t.error(err, 'npm ls') + t.same( + jsonParse(result), + { + version: '1.0.0', + name: 'test-npm-ls', + dependencies: { + foo: { + version: '1.0.0', + dependencies: { + dog: { + version: '1.0.0', + }, + }, + }, + chai: { + version: '1.0.0', + }, + }, + }, + 'should output json contaning only occurrences of multiple filtered packages and their ancestors' + ) + t.end() + }) + }) + + t.test('with missing filter arg', (t) => { + npm.prefix = t.testdir({ + 'package.json': JSON.stringify({ + name: 'test-npm-ls', + version: '1.0.0', + dependencies: { + foo: '^1.0.0', + chai: '^1.0.0', + }, + }), + 'package-lock.json': JSON.stringify({ + dependencies: { + foo: { + version: '1.0.0', + requires: { + dog: '^1.0.0', + }, + }, + dog: { + version: '1.0.0', + }, + chai: { + version: '1.0.0', + }, + }, + }), + }) + ls.exec(['notadep'], (err) => { + t.error(err, 'npm ls') + t.same( + jsonParse(result), + { + name: 'test-npm-ls', + version: '1.0.0', + }, + 'should output json containing no dependencies info' + ) + t.equal( + process.exitCode, + 1, + 'should exit with error code 1' + ) + process.exitCode = 0 + t.end() + }) + }) + + t.test('default --depth value should now be 0', (t) => { + config.all = false + config.depth = undefined + npm.prefix = t.testdir({ + 'package.json': JSON.stringify({ + name: 'test-npm-ls', + version: '1.0.0', + dependencies: { + foo: '^1.0.0', + chai: '^1.0.0', + }, + }), + 'package-lock.json': JSON.stringify({ + dependencies: { + foo: { + version: '1.0.0', + requires: { + dog: '^1.0.0', + }, + }, + dog: { + version: '1.0.0', + }, + chai: { + version: '1.0.0', + }, + }, + }), + }) + ls.exec([], (err) => { + t.error(err, 'npm ls') + t.same( + jsonParse(result), + { + name: 'test-npm-ls', + version: '1.0.0', + dependencies: { + foo: { + version: '1.0.0', + }, + chai: { + version: '1.0.0', + }, + }, + }, + 'should output json containing only top-level dependencies' + ) + config.all = true + config.depth = Infinity + t.end() + }) + }) + + t.test('--depth=0', (t) => { + config.all = false + config.depth = 0 + npm.prefix = t.testdir({ + 'package.json': JSON.stringify({ + name: 'test-npm-ls', + version: '1.0.0', + dependencies: { + foo: '^1.0.0', + chai: '^1.0.0', + }, + }), + 'package-lock.json': JSON.stringify({ + dependencies: { + foo: { + version: '1.0.0', + requires: { + dog: '^1.0.0', + }, + }, + dog: { + version: '1.0.0', + }, + chai: { + version: '1.0.0', + }, + }, + }), + }) + ls.exec([], (err) => { + t.error(err, 'npm ls') + t.same( + jsonParse(result), + { + name: 'test-npm-ls', + version: '1.0.0', + dependencies: { + foo: { + version: '1.0.0', + }, + chai: { + version: '1.0.0', + }, + }, + }, + 'should output json containing only top-level dependencies' + ) + config.all = true + config.depth = Infinity + t.end() + }) + }) + + t.test('--depth=1', (t) => { + config.all = false + config.depth = 1 + npm.prefix = t.testdir({ + 'package.json': JSON.stringify({ + name: 'test-npm-ls', + version: '1.0.0', + dependencies: { + foo: '^1.0.0', + chai: '^1.0.0', + }, + }), + 'package-lock.json': JSON.stringify({ + dependencies: { + foo: { + version: '1.0.0', + requires: { + dog: '^1.0.0', + }, + }, + dog: { + version: '1.0.0', + }, + chai: { + version: '1.0.0', + }, + }, + }), + }) + ls.exec([], (err) => { + t.error(err, 'npm ls') + t.same( + jsonParse(result), + { + name: 'test-npm-ls', + version: '1.0.0', + dependencies: { + foo: { + version: '1.0.0', + dependencies: { + dog: { + version: '1.0.0', + }, + }, + }, + chai: { + version: '1.0.0', + }, + }, + }, + 'should output json containing top-level deps and their deps only' + ) + config.all = true + config.depth = Infinity + t.end() + }) + }) + + t.test('missing/invalid/extraneous', (t) => { + npm.prefix = t.testdir({ + 'package.json': JSON.stringify({ + name: 'test-npm-ls', + version: '1.0.0', + dependencies: { + foo: '^2.0.0', + ipsum: '^1.0.0', + }, + }), + 'package-lock.json': JSON.stringify({ + dependencies: { + foo: { + version: '1.0.0', + requires: { + dog: '^1.0.0', + }, + }, + dog: { + version: '1.0.0', + }, + chai: { + version: '1.0.0', + }, + }, + }), + }) + ls.exec([], (err) => { + t.match(err, { code: 'ELSPROBLEMS' }, 'should list dep problems') + t.same( + jsonParse(result), + { + name: 'test-npm-ls', + version: '1.0.0', + problems: [ + 'invalid: foo@1.0.0 {CWD}/tap-testdir-ls-ls---package-lock-only-ls---package-lock-only---json-missing-invalid-extraneous/node_modules/foo', + 'missing: ipsum@^1.0.0, required by test-npm-ls@1.0.0', + ], + dependencies: { + foo: { + version: '1.0.0', + invalid: true, + problems: [ + 'invalid: foo@1.0.0 {CWD}/tap-testdir-ls-ls---package-lock-only-ls---package-lock-only---json-missing-invalid-extraneous/node_modules/foo', + ], + dependencies: { + dog: { + version: '1.0.0', + }, + }, + }, + ipsum: { + required: '^1.0.0', + missing: true, + problems: [ + 'missing: ipsum@^1.0.0, required by test-npm-ls@1.0.0', + ], + }, + }, + }, + 'should output json containing top-level deps and their deps only' + ) + t.end() + }) + }) + + t.test('from lockfile', (t) => { + npm.prefix = t.testdir({ + 'package-lock.json': JSON.stringify({ + name: 'dedupe-lockfile', + version: '1.0.0', + lockfileVersion: 2, + requires: true, + packages: { + '': { + name: 'dedupe-lockfile', + version: '1.0.0', + dependencies: { + '@isaacs/dedupe-tests-a': '1.0.1', + '@isaacs/dedupe-tests-b': '1||2', + }, + }, + 'node_modules/@isaacs/dedupe-tests-a': { + name: '@isaacs/dedupe-tests-a', + version: '1.0.1', + resolved: 'https://registry.npmjs.org/@isaacs/dedupe-tests-a/-/dedupe-tests-a-1.0.1.tgz', + integrity: 'sha512-8AN9lNCcBt5Xeje7fMEEpp5K3rgcAzIpTtAjYb/YMUYu8SbIVF6wz0WqACDVKvpQOUcSfNHZQNLNmue0QSwXOQ==', + dependencies: { + '@isaacs/dedupe-tests-b': '1', + }, + }, + 'node_modules/@isaacs/dedupe-tests-a/node_modules/@isaacs/dedupe-tests-b': { + name: '@isaacs/dedupe-tests-b', + version: '1.0.0', + resolved: 'https://registry.npmjs.org/@isaacs/dedupe-tests-b/-/dedupe-tests-b-1.0.0.tgz', + integrity: 'sha512-3nmvzIb8QL8OXODzipwoV3U8h9OQD9g9RwOPuSBQqjqSg9JZR1CCFOWNsDUtOfmwY8HFUJV9EAZ124uhqVxq+w==', + }, + 'node_modules/@isaacs/dedupe-tests-b': { + name: '@isaacs/dedupe-tests-b', + version: '2.0.0', + resolved: 'https://registry.npmjs.org/@isaacs/dedupe-tests-b/-/dedupe-tests-b-2.0.0.tgz', + integrity: 'sha512-KTYkpRv9EzlmCg4Gsm/jpclWmRYFCXow8GZKJXjK08sIZBlElTZEa5Bw/UQxIvEfcKmWXczSqItD49Kr8Ax4UA==', + }, + }, + dependencies: { + '@isaacs/dedupe-tests-a': { + version: '1.0.1', + resolved: 'https://registry.npmjs.org/@isaacs/dedupe-tests-a/-/dedupe-tests-a-1.0.1.tgz', + integrity: 'sha512-8AN9lNCcBt5Xeje7fMEEpp5K3rgcAzIpTtAjYb/YMUYu8SbIVF6wz0WqACDVKvpQOUcSfNHZQNLNmue0QSwXOQ==', + requires: { + '@isaacs/dedupe-tests-b': '1', + }, + dependencies: { + '@isaacs/dedupe-tests-b': { + version: '1.0.0', + resolved: 'https://registry.npmjs.org/@isaacs/dedupe-tests-b/-/dedupe-tests-b-1.0.0.tgz', + integrity: 'sha512-3nmvzIb8QL8OXODzipwoV3U8h9OQD9g9RwOPuSBQqjqSg9JZR1CCFOWNsDUtOfmwY8HFUJV9EAZ124uhqVxq+w==', + }, + }, + }, + '@isaacs/dedupe-tests-b': { + version: '2.0.0', + resolved: 'https://registry.npmjs.org/@isaacs/dedupe-tests-b/-/dedupe-tests-b-2.0.0.tgz', + integrity: 'sha512-KTYkpRv9EzlmCg4Gsm/jpclWmRYFCXow8GZKJXjK08sIZBlElTZEa5Bw/UQxIvEfcKmWXczSqItD49Kr8Ax4UA==', + }, + }, + }), + 'package.json': JSON.stringify({ + name: 'dedupe-lockfile', + version: '1.0.0', + dependencies: { + '@isaacs/dedupe-tests-a': '1.0.1', + '@isaacs/dedupe-tests-b': '1||2', + }, + }), + }) + ls.exec([], () => { + t.same( + jsonParse(result), + { + version: '1.0.0', + name: 'dedupe-lockfile', + dependencies: { + '@isaacs/dedupe-tests-a': { + version: '1.0.1', + resolved: 'https://registry.npmjs.org/@isaacs/dedupe-tests-a/-/dedupe-tests-a-1.0.1.tgz', + dependencies: { + '@isaacs/dedupe-tests-b': { + version: '1.0.0', + resolved: 'https://registry.npmjs.org/@isaacs/dedupe-tests-b/-/dedupe-tests-b-1.0.0.tgz', + }, + }, + }, + '@isaacs/dedupe-tests-b': { + version: '2.0.0', + resolved: 'https://registry.npmjs.org/@isaacs/dedupe-tests-b/-/dedupe-tests-b-2.0.0.tgz', + }, + }, + }, + 'should output json containing only prod deps' + ) + t.end() + }) + }) + + t.test('using aliases', (t) => { + npm.prefix = t.testdir({ + 'package.json': JSON.stringify({ + name: 'test-npm-ls', + version: '1.0.0', + dependencies: { + a: 'npm:b@1.0.0', + }, + }), + 'package-lock.json': JSON.stringify({ + dependencies: { + a: { + version: 'npm:b@1.0.0', + resolved: 'https://localhost:8080/abbrev/-/abbrev-1.0.0.tgz', + }, + }, + }), + }) + ls.exec([], () => { + t.same( + jsonParse(result), + { + name: 'test-npm-ls', + version: '1.0.0', + dependencies: { + a: { + version: '1.0.0', + resolved: 'https://localhost:8080/abbrev/-/abbrev-1.0.0.tgz', + }, + }, + }, + 'should output json containing aliases' + ) + t.end() + }) + }) + + t.test('resolved points to git ref', (t) => { + config.long = false + npm.prefix = t.testdir({ + 'package.json': JSON.stringify({ + name: 'test-npm-ls', + version: '1.0.0', + dependencies: { + abbrev: 'git+https://github.com/isaacs/abbrev-js.git', + }, + }), + 'package-lock.json': JSON.stringify({ + name: 'test-npm-ls', + version: '1.0.0', + lockfileVersion: 2, + requires: true, + dependencies: { + abbrev: { + version: 'git+ssh://git@github.com/isaacs/abbrev-js.git#b8f3a2fc0c3bb8ffd8b0d0072cc6b5a3667e963c', + from: 'abbrev@git+https://github.com/isaacs/abbrev-js.git', + }, + }, + } + ), + }) + ls.exec([], () => { + t.same( + jsonParse(result), + { + name: 'test-npm-ls', + version: '1.0.0', + dependencies: { + abbrev: { + resolved: 'git+ssh://git@github.com/isaacs/abbrev-js.git#b8f3a2fc0c3bb8ffd8b0d0072cc6b5a3667e963c', + }, + }, + }, + 'should output json containing git refs' + ) + t.end() + }) + }) + + t.end() + }) + + t.end() +}) diff --git a/test/lib/npm.js b/test/lib/npm.js index 6f8f8936d3f73..6909c43e4ff0e 100644 --- a/test/lib/npm.js +++ b/test/lib/npm.js @@ -355,7 +355,7 @@ t.test('npm.load', t => { await new Promise((res) => setTimeout(res)) }) - t.test('workpaces-aware configs and commands', async t => { + t.test('workspace-aware configs and commands', async t => { const dir = t.testdir({ packages: { a: { @@ -438,6 +438,60 @@ t.test('npm.load', t => { }) }) + t.test('workspaces in global mode', async t => { + const dir = t.testdir({ + packages: { + a: { + 'package.json': JSON.stringify({ + name: 'a', + version: '1.0.0', + scripts: { test: 'echo test a' }, + }), + }, + b: { + 'package.json': JSON.stringify({ + name: 'b', + version: '1.0.0', + scripts: { test: 'echo test b' }, + }), + }, + }, + 'package.json': JSON.stringify({ + name: 'root', + version: '1.0.0', + workspaces: ['./packages/*'], + }), + }) + const { execPath } = process + freshConfig({ + argv: [ + execPath, + process.argv[1], + '--userconfig', + resolve(dir, '.npmrc'), + '--color', + 'false', + '--workspaces', + '--global', + 'true', + ], + }) + await npm.load(er => { + if (er) + throw er + }) + npm.localPrefix = dir + await new Promise((res, rej) => { + // verify that calling the command with a short name still sets + // the npm.command property to the full canonical name of the cmd. + npm.command = null + npm.commands.run([], er => { + t.match(er, /Workspaces not supported for global packages/) + res() + }) + }) + }) + t.end() }) diff --git a/test/lib/pack.js b/test/lib/pack.js index ad5bbf3359182..523ba5d6b535d 100644 --- a/test/lib/pack.js +++ b/test/lib/pack.js @@ -1,6 +1,7 @@ const t = require('tap') const mockNpm = require('../fixtures/mock-npm') const pacote = require('pacote') +const path = require('path') const OUTPUT = [] const output = (...msg) => OUTPUT.push(msg) @@ -27,6 +28,7 @@ const mockPacote = { t.afterEach(() => OUTPUT.length = 0) t.test('should pack current directory with no arguments', (t) => { + let tarballFileName const Pack = t.mock('../../lib/pack.js', { libnpmpack, npmlog: { @@ -35,14 +37,46 @@ t.test('should pack current directory with no arguments', (t) => { clearProgress: () => {}, }, fs: { - writeFile: (file, data, cb) => cb(), + writeFile: (file, data, cb) => { + tarballFileName = file + cb() + }, + }, + }) + const npm = mockNpm({ + output, + }) + const pack = new Pack(npm) + + pack.exec([], err => { + t.error(err, { bail: true }) + + const filename = `npm-${require('../../package.json').version}.tgz` + t.strictSame(OUTPUT, [[filename]]) + t.strictSame(tarballFileName, path.resolve(filename)) + t.end() + }) +}) + +t.test('follows pack-destination config', (t) => { + let tarballFileName + const Pack = t.mock('../../lib/pack.js', { + libnpmpack, + npmlog: { + notice: () => {}, + showProgress: () => {}, + clearProgress: () => {}, + }, + fs: { + writeFile: (file, data, cb) => { + tarballFileName = file + cb() + }, }, }) const npm = mockNpm({ config: { - unicode: false, - json: false, - 'dry-run': false, + 'pack-destination': '/tmp/test', }, output, }) @@ -53,10 +87,10 @@ t.test('should pack current directory with no arguments', (t) => { const filename = `npm-${require('../../package.json').version}.tgz` t.strictSame(OUTPUT, [[filename]]) + t.strictSame(tarballFileName, path.resolve('/tmp/test', filename)) t.end() }) }) - t.test('should pack given directory', (t) => { const testDir = t.testdir({ 'package.json': JSON.stringify({ diff --git a/test/lib/utils/config/definitions.js b/test/lib/utils/config/definitions.js index 49e4152883795..a87698c181359 100644 --- a/test/lib/utils/config/definitions.js +++ b/test/lib/utils/config/definitions.js @@ -181,6 +181,7 @@ t.test('cache', t => { defsNix.cache.flatten('cache', { cache: '/some/cache/value' }, flat) const {join} = require('path') t.equal(flat.cache, join('/some/cache/value', '_cacache')) + t.equal(flat.npxCache, join('/some/cache/value', '_npx')) t.end() }) diff --git a/test/lib/utils/error-handler.js b/test/lib/utils/error-handler.js index a00bac76e11c2..9a681e52ce5db 100644 --- a/test/lib/utils/error-handler.js +++ b/test/lib/utils/error-handler.js @@ -1,6 +1,7 @@ /* eslint-disable no-extend-native */ /* eslint-disable no-global-assign */ const EventEmitter = require('events') +const writeFileAtomic = require('write-file-atomic') const t = require('tap') // NOTE: Although these unit tests may look like the rest on the surface, @@ -23,13 +24,10 @@ const redactCwd = (path) => { t.cleanSnapshot = (str) => redactCwd(str) // internal modules mocks -const cacheFile = { - append: () => null, - write: () => null, -} +const cacheFolder = t.testdir({}) const config = { values: { - cache: 'cachefolder', + cache: cacheFolder, timing: true, }, loaded: true, @@ -111,20 +109,20 @@ process = Object.assign( // in order for tap to exit properly t.teardown(() => { process = _process + npmlog.record.length = 0 }) const mocks = { npmlog, - '../../../lib/npm.js': npm, '../../../lib/utils/error-message.js': (err) => ({ ...err, summary: [['ERR', err.message]], detail: [['ERR', err.message]], }), - '../../../lib/utils/cache-file.js': cacheFile, } let errorHandler = t.mock('../../../lib/utils/error-handler.js', mocks) +errorHandler.setNpm(npm) t.test('default exit code', (t) => { t.plan(1) @@ -160,10 +158,14 @@ t.test('default exit code', (t) => { t.test('handles unknown error', (t) => { t.plan(2) - cacheFile.write = (filename, content) => { + const _toISOString = Date.prototype.toISOString + Date.prototype.toISOString = () => 'expecteddate' + + const sync = writeFileAtomic.sync + writeFileAtomic.sync = (filename, content) => { t.equal( redactCwd(filename), - '{CWD}/cachefolder/_logs/expecteddate-debug.log', + '{CWD}/test/lib/utils/tap-testdir-error-handler/_logs/expecteddate-debug.log', 'should use expected log filename' ) t.matchSnapshot( @@ -175,7 +177,8 @@ t.test('handles unknown error', (t) => { errorHandler(err) t.teardown(() => { - cacheFile.write = () => null + writeFileAtomic.sync = sync + Date.prototype.toISOString = _toISOString }) t.end() }) @@ -205,11 +208,14 @@ t.test('npm.config not ready', (t) => { t.test('fail to write logfile', (t) => { t.plan(1) - cacheFile.write = () => { - throw err - } + const badDir = t.testdir({ + _logs: 'is a file', + }) + + config.values.cache = badDir + t.teardown(() => { - cacheFile.write = () => null + config.values.cache = cacheFolder }) t.doesNotThrow( @@ -372,6 +378,7 @@ t.test('uses code from errno', (t) => { t.plan(1) errorHandler = t.mock('../../../lib/utils/error-handler.js', mocks) + errorHandler.setNpm(npm) npmlog.level = 'silent' const _exit = process.exit @@ -396,6 +403,7 @@ t.test('uses exitCode as code if using a number', (t) => { t.plan(1) errorHandler = t.mock('../../../lib/utils/error-handler.js', mocks) + errorHandler.setNpm(npm) npmlog.level = 'silent' const _exit = process.exit @@ -420,6 +428,7 @@ t.test('call errorHandler with no error', (t) => { t.plan(1) errorHandler = t.mock('../../../lib/utils/error-handler.js', mocks) + errorHandler.setNpm(npm) const _exit = process.exit process.exit = (code) => { @@ -478,6 +487,7 @@ t.test('set it worked', (t) => { t.plan(1) errorHandler = t.mock('../../../lib/utils/error-handler.js', mocks) + errorHandler.setNpm(npm) const _exit = process.exit process.exit = () => { diff --git a/test/lib/utils/error-message.js b/test/lib/utils/error-message.js index 7529aac2d4a4b..4f94645a4542d 100644 --- a/test/lib/utils/error-message.js +++ b/test/lib/utils/error-message.js @@ -1,4 +1,5 @@ const t = require('tap') +const path = require('path') // make a bunch of stuff consistent for snapshots @@ -48,7 +49,7 @@ const mocks = { return 'explanation' }, }, - '../../../lib/npm.js': require('../../../lib/npm.js'), + // XXX ??? get '../../../lib/utils/is-windows.js' () { return process.platform === 'win32' }, @@ -110,7 +111,7 @@ t.test('just simple messages', t => { file, stack, }) - t.matchSnapshot(errorMessage(er)) + t.matchSnapshot(errorMessage(er, npm)) }) }) @@ -128,7 +129,7 @@ t.test('replace message/stack sensistive info', t => { file, stack, }) - t.matchSnapshot(errorMessage(er)) + t.matchSnapshot(errorMessage(er, npm)) t.end() }) @@ -148,7 +149,7 @@ t.test('bad engine with config loaded', t => { file, stack, }) - t.matchSnapshot(errorMessage(er)) + t.matchSnapshot(errorMessage(er, npm)) t.end() }) @@ -162,7 +163,7 @@ t.test('enoent without a file', t => { pkgid, stack, }) - t.matchSnapshot(errorMessage(er)) + t.matchSnapshot(errorMessage(er, npm)) t.end() }) @@ -179,20 +180,20 @@ t.test('enolock without a command', t => { file, stack, }) - t.matchSnapshot(errorMessage(er)) + t.matchSnapshot(errorMessage(er, npm)) t.end() }) t.test('default message', t => { - t.matchSnapshot(errorMessage(new Error('error object'))) - t.matchSnapshot(errorMessage('error string')) + t.matchSnapshot(errorMessage(new Error('error object'), npm)) + t.matchSnapshot(errorMessage('error string'), npm) t.matchSnapshot(errorMessage(Object.assign(new Error('cmd err'), { cmd: 'some command', signal: 'SIGYOLO', args: ['a', 'r', 'g', 's'], stdout: 'stdout', stderr: 'stderr', - }))) + }), npm)) t.end() }) @@ -213,7 +214,7 @@ t.test('eacces/eperm', t => { stack: 'dummy stack trace', }) verboseLogs.length = 0 - t.matchSnapshot(errorMessage(er)) + t.matchSnapshot(errorMessage(er, npm)) t.matchSnapshot(verboseLogs) t.end() verboseLogs.length = 0 @@ -288,7 +289,7 @@ t.test('json parse', t => { t.matchSnapshot(errorMessage(Object.assign(new Error('conflicted'), { code: 'EJSONPARSE', file: resolve(dir, 'package.json'), - }))) + }), npm)) t.end() }) @@ -310,7 +311,7 @@ t.test('json parse', t => { t.matchSnapshot(errorMessage(Object.assign(new Error('not json'), { code: 'EJSONPARSE', file: resolve(dir, 'package.json'), - }))) + }), npm)) t.end() }) @@ -326,7 +327,7 @@ t.test('json parse', t => { t.matchSnapshot(errorMessage(Object.assign(new Error('not json'), { code: 'EJSONPARSE', file: `${dir}/blerg.json`, - }))) + }), npm)) t.end() }) @@ -337,21 +338,21 @@ t.test('eotp/e401', t => { t.test('401, no auth headers', t => { t.matchSnapshot(errorMessage(Object.assign(new Error('nope'), { code: 'E401', - }))) + }), npm)) t.end() }) t.test('401, no message', t => { t.matchSnapshot(errorMessage({ code: 'E401', - })) + }, npm)) t.end() }) t.test('one-time pass challenge code', t => { t.matchSnapshot(errorMessage(Object.assign(new Error('nope'), { code: 'EOTP', - }))) + }), npm)) t.end() }) @@ -359,7 +360,7 @@ t.test('eotp/e401', t => { const message = 'one-time pass' t.matchSnapshot(errorMessage(Object.assign(new Error(message), { code: 'E401', - }))) + }), npm)) t.end() }) @@ -379,7 +380,7 @@ t.test('eotp/e401', t => { }, code: 'E401', }) - t.matchSnapshot(errorMessage(er)) + t.matchSnapshot(errorMessage(er, npm)) t.end() }) } @@ -391,7 +392,7 @@ t.test('eotp/e401', t => { t.test('404', t => { t.test('no package id', t => { const er = Object.assign(new Error('404 not found'), { code: 'E404' }) - t.matchSnapshot(errorMessage(er)) + t.matchSnapshot(errorMessage(er, npm)) t.end() }) t.test('you should publish it', t => { @@ -399,7 +400,7 @@ t.test('404', t => { pkgid: 'yolo', code: 'E404', }) - t.matchSnapshot(errorMessage(er)) + t.matchSnapshot(errorMessage(er, npm)) t.end() }) t.test('name with warning', t => { @@ -407,7 +408,7 @@ t.test('404', t => { pkgid: new Array(215).fill('x').join(''), code: 'E404', }) - t.matchSnapshot(errorMessage(er)) + t.matchSnapshot(errorMessage(er, npm)) t.end() }) t.test('name with error', t => { @@ -415,7 +416,7 @@ t.test('404', t => { pkgid: 'node_modules', code: 'E404', }) - t.matchSnapshot(errorMessage(er)) + t.matchSnapshot(errorMessage(er, npm)) t.end() }) t.end() @@ -435,7 +436,7 @@ t.test('bad platform', t => { }, code: 'EBADPLATFORM', }) - t.matchSnapshot(errorMessage(er)) + t.matchSnapshot(errorMessage(er, npm)) t.end() }) t.test('array os/arch', t => { @@ -451,7 +452,7 @@ t.test('bad platform', t => { }, code: 'EBADPLATFORM', }) - t.matchSnapshot(errorMessage(er)) + t.matchSnapshot(errorMessage(er, npm)) t.end() }) @@ -462,7 +463,11 @@ t.test('explain ERESOLVE errors', t => { const er = Object.assign(new Error('could not resolve'), { code: 'ERESOLVE', }) - t.matchSnapshot(errorMessage(er)) - t.strictSame(EXPLAIN_CALLED, [[er]]) + t.matchSnapshot(errorMessage(er, npm)) + t.match(EXPLAIN_CALLED, [[ + er, + undefined, + path.resolve(npm.cache, 'eresolve-report.txt'), + ]]) t.end() }) diff --git a/test/lib/utils/explain-eresolve.js b/test/lib/utils/explain-eresolve.js index 90795bb4470b2..f9710ee889ab1 100644 --- a/test/lib/utils/explain-eresolve.js +++ b/test/lib/utils/explain-eresolve.js @@ -1,8 +1,6 @@ const t = require('tap') const npm = {} -const { explain, report } = t.mock('../../../lib/utils/explain-eresolve.js', { - '../../../lib/npm.js': npm, -}) +const { explain, report } = require('../../../lib/utils/explain-eresolve.js') const { statSync, readFileSync, unlinkSync } = require('fs') // strip out timestamps from reports const read = f => readFileSync(f, 'utf8') @@ -25,23 +23,18 @@ for (const [name, expl] of Object.entries(cases)) { t.cleanSnapshot = str => str.split(reportFile).join('${REPORT}') npm.color = true - t.matchSnapshot(report(expl), 'report with color') + t.matchSnapshot(report(expl, true, reportFile), 'report with color') const reportData = read(reportFile) t.matchSnapshot(reportData, 'report') unlinkSync(reportFile) - t.matchSnapshot(report(expl, 2), 'report with color, depth only 2') + + t.matchSnapshot(report(expl, false, reportFile), 'report with no color') t.equal(read(reportFile), reportData, 'same report written for object') unlinkSync(reportFile) - npm.color = false - t.matchSnapshot(report(expl, 6), 'report with no color, depth of 6') - t.equal(read(reportFile), reportData, 'same report written for object') - unlinkSync(reportFile) - npm.color = true - t.matchSnapshot(explain(expl), 'explain with color') + t.matchSnapshot(explain(expl, true, 2), 'explain with color, depth of 2') t.throws(() => statSync(reportFile), { code: 'ENOENT' }, 'no report') - npm.color = false - t.matchSnapshot(explain(expl, 6), 'explain with no color, depth of 6') + t.matchSnapshot(explain(expl, false, 6), 'explain with no color, depth of 6') t.throws(() => statSync(reportFile), { code: 'ENOENT' }, 'no report') t.end() diff --git a/test/lib/utils/setup-log.js b/test/lib/utils/setup-log.js index 3daf3b8a52f53..86befe6e29297 100644 --- a/test/lib/utils/setup-log.js +++ b/test/lib/utils/setup-log.js @@ -92,7 +92,7 @@ t.test('setup with color=always and unicode', t => { })), true) npmlog.warn('ERESOLVE', 'hello', { some: { other: 'object' } }) - t.strictSame(EXPLAIN_CALLED, [[{ some: { other: 'object' } }]], + t.strictSame(EXPLAIN_CALLED, [[{ some: { other: 'object' } }, true, 2]], 'log.warn(ERESOLVE) patched to call explainEresolve()') t.strictSame(WARN_CALLED, [ ['ERESOLVE', 'hello'], diff --git a/test/lib/workspaces/arborist-cmd.js b/test/lib/workspaces/arborist-cmd.js index 740ddb1ff0dc5..75ac8f4ebf804 100644 --- a/test/lib/workspaces/arborist-cmd.js +++ b/test/lib/workspaces/arborist-cmd.js @@ -49,7 +49,7 @@ t.test('arborist-cmd', async t => { // check filtering for a single workspace name cmd.exec = function (args, cb) { - t.same(this.workspaces, ['a'], 'should set array with single ws name') + t.same(this.workspaceNames, ['a'], 'should set array with single ws name') t.same(args, ['foo'], 'should get received args') cb() } @@ -59,7 +59,7 @@ t.test('arborist-cmd', async t => { // check filtering single workspace by path cmd.exec = function (args, cb) { - t.same(this.workspaces, ['a'], + t.same(this.workspaceNames, ['a'], 'should set array with single ws name from path') cb() } @@ -69,7 +69,7 @@ t.test('arborist-cmd', async t => { // check filtering single workspace by full path cmd.exec = function (args, cb) { - t.same(this.workspaces, ['a'], + t.same(this.workspaceNames, ['a'], 'should set array with single ws name from full path') cb() } @@ -79,7 +79,7 @@ t.test('arborist-cmd', async t => { // filtering multiple workspaces by name cmd.exec = function (args, cb) { - t.same(this.workspaces, ['a', 'c'], + t.same(this.workspaceNames, ['a', 'c'], 'should set array with multiple listed ws names') cb() } @@ -89,7 +89,7 @@ t.test('arborist-cmd', async t => { // filtering multiple workspaces by path names cmd.exec = function (args, cb) { - t.same(this.workspaces, ['a', 'c'], + t.same(this.workspaceNames, ['a', 'c'], 'should set array with multiple ws names from paths') cb() } @@ -99,7 +99,7 @@ t.test('arborist-cmd', async t => { // filtering multiple workspaces by parent path name cmd.exec = function (args, cb) { - t.same(this.workspaces, ['c', 'd'], + t.same(this.workspaceNames, ['c', 'd'], 'should set array with multiple ws names from a parent folder name') cb() }