From 46a9bcbcb0bb2435dca6f45a61b8631f580c7f06 Mon Sep 17 00:00:00 2001 From: Kevin Cormier Date: Fri, 21 May 2021 10:13:35 -0400 Subject: [PATCH 1/9] fix(docs): proper postinstall script file name I think this change was incorrect: https://github.com/npm/cli/pull/2024 The point of this example is that the same script is being used for two different stages (`install` and `post-install`) so it would be a good idea to look at the `npm_lifecycle_event` environment variable inside this script to determine which stage is being run. PR-URL: https://github.com/npm/cli/pull/3282 Credit: @KevinFCormier Close: #3282 Reviewed-by: @wraithgar --- docs/content/using-npm/scripts.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/content/using-npm/scripts.md b/docs/content/using-npm/scripts.md index 82cde7d79094d..3869334f6cc5a 100644 --- a/docs/content/using-npm/scripts.md +++ b/docs/content/using-npm/scripts.md @@ -304,7 +304,7 @@ For example, if your package.json contains this: { "scripts" : { "install" : "scripts/install.js", - "postinstall" : "scripts/postinstall.js", + "postinstall" : "scripts/install.js", "uninstall" : "scripts/uninstall.js" } } From 83590d40f94347f21714dbd158b9ddcad9c82de9 Mon Sep 17 00:00:00 2001 From: isaacs Date: Thu, 20 May 2021 10:53:10 -0700 Subject: [PATCH 2/9] fix(ls): show relative paths from root Change the output in `npm ls` for symlink dependencies (including workspaces) to show the relative path from the project root, rather than the absolute path of the link target. This makes the output much less noisy and more ergonomic when many workspaces and link dependencies are in use, especially when paths are long. It is arguable that this output might be slightly misleading, since the _actual_ workspace symlink from `${root}/node_modules/b` to `${root}/packages/b` has a link value of `../packages/b`, not just `packages/b`. (Or on Windows, always the full absolute path, because junctions.) Thus, `npm ls b` will not show the same output as `ls -l node_modules/b`. However, `npm ls` shows the logical tree, not the physical tree, so presenting the user with a path that they can use and interpret is more important than presenting them with the strictly accurate details of their filesystem. PR-URL: https://github.com/npm/cli/pull/3272 Credit: @isaacs Close: #3272 Reviewed-by: @darcyclarke --- lib/ls.js | 12 +++++-- tap-snapshots/test/lib/ls.js.test.cjs | 46 +++++++++++++-------------- test/lib/ls.js | 7 +++- 3 files changed, 39 insertions(+), 26 deletions(-) diff --git a/lib/ls.js b/lib/ls.js index 4e504912a0175..d92b73ddfcdbb 100644 --- a/lib/ls.js +++ b/lib/ls.js @@ -1,4 +1,5 @@ -const { resolve } = require('path') +const { resolve, relative, sep } = require('path') +const relativePrefix = `.${sep}` const { EOL } = require('os') const archy = require('archy') @@ -298,6 +299,9 @@ const getHumanOutputItem = (node, { args, color, global, long }) => { ? chalk.yellow.bgBlack : chalk.red.bgBlack const missingMsg = `UNMET ${isOptional(node) ? 'OPTIONAL ' : ''}DEPENDENCY` + const targetLocation = node.root + ? relative(node.root.realpath, node.realpath) + : node.targetLocation const label = ( node[_missing] @@ -321,7 +325,7 @@ const getHumanOutputItem = (node, { args, color, global, long }) => { : '' ) + (isGitNode(node) ? ` (${node.resolved})` : '') + - (node.isLink ? ` -> ${node.realpath}` : '') + + (node.isLink ? ` -> ${relativePrefix}${targetLocation}` : '') + (long ? `${EOL}${node.package.description || ''}` : '') return augmentItemWithIncludeMetadata(node, { label, nodes: [] }) @@ -445,6 +449,9 @@ const augmentNodesWithMetadata = ({ // revisit that node in tree traversal logic, so we make it so that // we have a diff obj for deduped nodes: if (seenNodes.has(node.path)) { + const { realpath, root } = node + const targetLocation = root ? relative(root.realpath, realpath) + : node.targetLocation node = { name: node.name, version: node.version, @@ -453,6 +460,7 @@ const augmentNodesWithMetadata = ({ path: node.path, isLink: node.isLink, realpath: node.realpath, + targetLocation, [_type]: node[_type], [_invalid]: node[_invalid], [_missing]: node[_missing], diff --git a/tap-snapshots/test/lib/ls.js.test.cjs b/tap-snapshots/test/lib/ls.js.test.cjs index f443d9caba895..2ed0b4b001376 100644 --- a/tap-snapshots/test/lib/ls.js.test.cjs +++ b/tap-snapshots/test/lib/ls.js.test.cjs @@ -70,7 +70,7 @@ test-npm-ls@1.0.0 {CWD}/tap-testdir-ls-ls---dev exports[`test/lib/ls.js TAP ls --link > should output tree containing linked deps 1`] = ` test-npm-ls@1.0.0 {CWD}/tap-testdir-ls-ls---link -\`-- linked-dep@1.0.0 -> {CWD}/tap-testdir-ls-ls---link/linked-dep +\`-- linked-dep@1.0.0 -> ./linked-dep ` @@ -480,24 +480,24 @@ exports[`test/lib/ls.js TAP ls json read problems > should print empty result 1` exports[`test/lib/ls.js TAP ls loading a tree containing workspaces > should filter by parent folder workspace config 1`] = ` workspaces-tree@1.0.0 {CWD}/tap-testdir-ls-ls-loading-a-tree-containing-workspaces -+-- e@1.0.0 -> {CWD}/tap-testdir-ls-ls-loading-a-tree-containing-workspaces/group/e -\`-- f@1.0.0 -> {CWD}/tap-testdir-ls-ls-loading-a-tree-containing-workspaces/group/f ++-- 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 filter single workspace 1`] = ` workspaces-tree@1.0.0 {CWD}/tap-testdir-ls-ls-loading-a-tree-containing-workspaces -+-- a@1.0.0 -> {CWD}/tap-testdir-ls-ls-loading-a-tree-containing-workspaces/a -| \`-- d@1.0.0 deduped -> {CWD}/tap-testdir-ls-ls-loading-a-tree-containing-workspaces/d -\`-- d@1.0.0 -> {CWD}/tap-testdir-ls-ls-loading-a-tree-containing-workspaces/d ++-- a@1.0.0 -> ./a +| \`-- d@1.0.0 deduped -> ./d +\`-- d@1.0.0 -> ./d ` 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 -> {CWD}/tap-testdir-ls-ls-loading-a-tree-containing-workspaces/a +\`-- a@1.0.0 -> ./a +-- c@1.0.0 - \`-- d@1.0.0 -> {CWD}/tap-testdir-ls-ls-loading-a-tree-containing-workspaces/d + \`-- d@1.0.0 -> ./d \`-- foo@1.1.1 \`-- bar@1.0.0 @@ -505,34 +505,34 @@ 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 -> {CWD}/tap-testdir-ls-ls-loading-a-tree-containing-workspaces/a ++-- a@1.0.0 -> ./a | +-- c@1.0.0 -| \`-- d@1.0.0 deduped -> {CWD}/tap-testdir-ls-ls-loading-a-tree-containing-workspaces/d -+-- b@1.0.0 -> {CWD}/tap-testdir-ls-ls-loading-a-tree-containing-workspaces/b -+-- d@1.0.0 -> {CWD}/tap-testdir-ls-ls-loading-a-tree-containing-workspaces/d +| \`-- 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 -> {CWD}/tap-testdir-ls-ls-loading-a-tree-containing-workspaces/group/e -\`-- f@1.0.0 -> {CWD}/tap-testdir-ls-ls-loading-a-tree-containing-workspaces/group/f ++-- 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 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 -> {CWD}/tap-testdir-ls-ls-loading-a-tree-containing-workspaces/a ++-- a@1.0.0 -> ./a | +-- c@1.0.0 -| \`-- d@1.0.0 deduped -> {CWD}/tap-testdir-ls-ls-loading-a-tree-containing-workspaces/d -+-- b@1.0.0 -> {CWD}/tap-testdir-ls-ls-loading-a-tree-containing-workspaces/b -+-- d@1.0.0 -> {CWD}/tap-testdir-ls-ls-loading-a-tree-containing-workspaces/d +| \`-- d@1.0.0 deduped -> ./d ++-- b@1.0.0 -> ./b ++-- d@1.0.0 -> ./d | \`-- foo@1.1.1 -+-- e@1.0.0 -> {CWD}/tap-testdir-ls-ls-loading-a-tree-containing-workspaces/group/e -\`-- f@1.0.0 -> {CWD}/tap-testdir-ls-ls-loading-a-tree-containing-workspaces/group/f ++-- 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 print all tree and filter by dep within only the ws subtree 1`] = ` workspaces-tree@1.0.0 {CWD}/tap-testdir-ls-ls-loading-a-tree-containing-workspaces -\`-- d@1.0.0 -> {CWD}/tap-testdir-ls-ls-loading-a-tree-containing-workspaces/d +\`-- d@1.0.0 -> ./d \`-- foo@1.1.1 \`-- bar@1.0.0 @@ -567,8 +567,8 @@ test-npm-ls@1.0.0 {CWD}/tap-testdir-ls-ls-no-args exports[`test/lib/ls.js TAP ls print deduped symlinks > should output tree containing linked deps 1`] = ` print-deduped-symlinks@1.0.0 {CWD}/tap-testdir-ls-ls-print-deduped-symlinks +-- a@1.0.0 -| \`-- b@1.0.0 deduped -> {CWD}/tap-testdir-ls-ls-print-deduped-symlinks/b -\`-- b@1.0.0 -> {CWD}/tap-testdir-ls-ls-print-deduped-symlinks/b +| \`-- b@1.0.0 deduped -> ./b +\`-- b@1.0.0 -> ./b ` diff --git a/test/lib/ls.js b/test/lib/ls.js index 2cde319463a9e..ecdede809df20 100644 --- a/test/lib/ls.js +++ b/test/lib/ls.js @@ -90,7 +90,12 @@ const diffDepTypesNmFixture = { } let result = '' -const LS = require('../../lib/ls.js') +const LS = t.mock('../../lib/ls.js', { + path: { + ...require('path'), + sep: '/', + }, +}) const config = { all: true, color: false, From dbbc151a3bcf89e2627dc267063edd185ead1cb8 Mon Sep 17 00:00:00 2001 From: Gar Date: Wed, 26 May 2021 13:04:03 -0700 Subject: [PATCH 3/9] npm-audit-report@2.1.5 * fix(exit-code): account for null auditLevel default (#46) --- node_modules/npm-audit-report/CHANGELOG.md | 81 ---------------------- node_modules/npm-audit-report/lib/index.js | 4 +- node_modules/npm-audit-report/package.json | 6 +- package-lock.json | 14 ++-- package.json | 2 +- 5 files changed, 14 insertions(+), 93 deletions(-) delete mode 100644 node_modules/npm-audit-report/CHANGELOG.md diff --git a/node_modules/npm-audit-report/CHANGELOG.md b/node_modules/npm-audit-report/CHANGELOG.md deleted file mode 100644 index 58819a43b4d11..0000000000000 --- a/node_modules/npm-audit-report/CHANGELOG.md +++ /dev/null @@ -1,81 +0,0 @@ -# Change Log - -All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. - - -## [1.3.3](https://github.com/npm/npm-audit-report/compare/v1.3.2...v1.3.3) (2020-03-26) - - - - -## [1.3.2](https://github.com/npm/npm-audit-report/compare/v1.3.1...v1.3.2) (2018-12-18) - - -### Bug Fixes - -* **parseable:** add support for critical vulns and more resolves on update/install action ([#28](https://github.com/npm/npm-audit-report/issues/28)) ([5e27893](https://github.com/npm/npm-audit-report/commit/5e27893)) -* **security:** audit fix ([ff9faf3](https://github.com/npm/npm-audit-report/commit/ff9faf3)) -* **urls:** Replace hardcoded URL to advisory with a URL from audit response ([#34](https://github.com/npm/npm-audit-report/issues/34)) ([e2fe95b](https://github.com/npm/npm-audit-report/commit/e2fe95b)) - - - - -## [1.3.1](https://github.com/npm/npm-audit-report/compare/v1.3.0...v1.3.1) (2018-07-10) - - - - -# [1.3.0](https://github.com/npm/npm-audit-report/compare/v1.2.1...v1.3.0) (2018-07-09) - - -### Bug Fixes - -* **deps:** remove object.values dependency ([2c5374a](https://github.com/npm/npm-audit-report/commit/2c5374a)) -* **detail:** Fix info-level severity ([#18](https://github.com/npm/npm-audit-report/issues/18)) ([807db5a](https://github.com/npm/npm-audit-report/commit/807db5a)) -* **tests:** a test should not cause side-effects in other tests ([#23](https://github.com/npm/npm-audit-report/issues/23)) ([a94449f](https://github.com/npm/npm-audit-report/commit/a94449f)) - - -### Features - -* **output:** add `parseable` tabular output format support ([#21](https://github.com/npm/npm-audit-report/issues/21)) ([1c9aaf4](https://github.com/npm/npm-audit-report/commit/1c9aaf4)) - - - - -## [1.2.1](https://github.com/npm/npm-audit-report/compare/v1.2.0...v1.2.1) (2018-05-17) - - -### Bug Fixes - -* **detail:** count id+path instead of just id ([99880fd](https://github.com/npm/npm-audit-report/commit/99880fd)) - - - - -# [1.2.0](https://github.com/npm/npm-audit-report/compare/v1.1.0...v1.2.0) (2018-05-16) - - -### Bug Fixes - -* **full-report:** Fix install flag for devDependencies ([#14](https://github.com/npm/npm-audit-report/issues/14)) ([30e5f30](https://github.com/npm/npm-audit-report/commit/30e5f30)) - - -### Features - -* **detail:** consistified full report with install report ([#15](https://github.com/npm/npm-audit-report/issues/15)) ([6df6810](https://github.com/npm/npm-audit-report/commit/6df6810)) -* **install:** include `npm audit` recommendation too ([32fb153](https://github.com/npm/npm-audit-report/commit/32fb153)) - - - - -# [1.1.0](https://github.com/npm/npm-audit-report/compare/v1.0.9...v1.1.0) (2018-05-10) - - -### Bug Fixes - -* **install:** not enough data for this conditional ([6ddc30c](https://github.com/npm/npm-audit-report/commit/6ddc30c)) - - -### Features - -* **report:** compress and reformat human-readable install report ([74d5203](https://github.com/npm/npm-audit-report/commit/74d5203)) diff --git a/node_modules/npm-audit-report/lib/index.js b/node_modules/npm-audit-report/lib/index.js index 464004c17518a..9ee86be7915d8 100644 --- a/node_modules/npm-audit-report/lib/index.js +++ b/node_modules/npm-audit-report/lib/index.js @@ -15,9 +15,11 @@ module.exports = Object.assign((data, options = {}) => { color = true, unicode = true, indent = 2, - auditLevel = 'low' } = options + // CLI defaults this to `null` so the defaulting method above doesn't work + const auditLevel = options.auditLevel || 'low' + if (!data) throw Object.assign( new TypeError('ENOAUDITDATA'), diff --git a/node_modules/npm-audit-report/package.json b/node_modules/npm-audit-report/package.json index 66b4a6aa74b2c..c819b9608412a 100644 --- a/node_modules/npm-audit-report/package.json +++ b/node_modules/npm-audit-report/package.json @@ -1,6 +1,6 @@ { "name": "npm-audit-report", - "version": "2.1.4", + "version": "2.1.5", "description": "Given a response from the npm security api, render it into a variety of security reports", "main": "lib/index.js", "scripts": { @@ -26,8 +26,8 @@ "chalk": "^4.0.0" }, "devDependencies": { - "tap": "^14.10.7", - "require-inject": "^1.4.4" + "require-inject": "^1.4.4", + "tap": "^14.10.7" }, "directories": { "lib": "lib", diff --git a/package-lock.json b/package-lock.json index 5c511c49f581a..3d172f460621d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -120,7 +120,7 @@ "ms": "^2.1.2", "node-gyp": "^7.1.2", "nopt": "^5.0.0", - "npm-audit-report": "^2.1.4", + "npm-audit-report": "^2.1.5", "npm-package-arg": "^8.1.2", "npm-pick-manifest": "^6.1.1", "npm-profile": "^5.0.3", @@ -5372,9 +5372,9 @@ } }, "node_modules/npm-audit-report": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/npm-audit-report/-/npm-audit-report-2.1.4.tgz", - "integrity": "sha512-Tz7rnfskSdZ0msTzt2mENC/B+H2QI8u0jN0ck7o3zDsQYIQrek/l3MjEc+CARer+64LsVTU6ZIqNuh0X55QPhw==", + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/npm-audit-report/-/npm-audit-report-2.1.5.tgz", + "integrity": "sha512-YB8qOoEmBhUH1UJgh1xFAv7Jg1d+xoNhsDYiFQlEFThEBui0W1vIz2ZK6FVg4WZjwEdl7uBQlm1jy3MUfyHeEw==", "inBundle": true, "dependencies": { "chalk": "^4.0.0" @@ -14251,9 +14251,9 @@ "dev": true }, "npm-audit-report": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/npm-audit-report/-/npm-audit-report-2.1.4.tgz", - "integrity": "sha512-Tz7rnfskSdZ0msTzt2mENC/B+H2QI8u0jN0ck7o3zDsQYIQrek/l3MjEc+CARer+64LsVTU6ZIqNuh0X55QPhw==", + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/npm-audit-report/-/npm-audit-report-2.1.5.tgz", + "integrity": "sha512-YB8qOoEmBhUH1UJgh1xFAv7Jg1d+xoNhsDYiFQlEFThEBui0W1vIz2ZK6FVg4WZjwEdl7uBQlm1jy3MUfyHeEw==", "requires": { "chalk": "^4.0.0" } diff --git a/package.json b/package.json index d7f46589a4d2b..00209b624538c 100644 --- a/package.json +++ b/package.json @@ -84,7 +84,7 @@ "ms": "^2.1.2", "node-gyp": "^7.1.2", "nopt": "^5.0.0", - "npm-audit-report": "^2.1.4", + "npm-audit-report": "^2.1.5", "npm-package-arg": "^8.1.2", "npm-pick-manifest": "^6.1.1", "npm-profile": "^5.0.3", From a574b518ae5b8f0664ed388cf1be6288d8c2e68d Mon Sep 17 00:00:00 2001 From: Nariyasu Heseri Date: Tue, 25 May 2021 23:41:39 +0900 Subject: [PATCH 4/9] fix(completion): restore IFS even if `npm completion` returns error PR-URL: https://github.com/npm/cli/pull/3304 Credit: @NariyasuHeseri Close: #3304 Reviewed-by: @wraithgar --- lib/utils/completion.sh | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/lib/utils/completion.sh b/lib/utils/completion.sh index c549b31c96493..a3e5143991edd 100755 --- a/lib/utils/completion.sh +++ b/lib/utils/completion.sh @@ -18,11 +18,15 @@ if type complete &>/dev/null; then fi local si="$IFS" - IFS=$'\n' COMPREPLY=($(COMP_CWORD="$cword" \ + if ! IFS=$'\n' COMPREPLY=($(COMP_CWORD="$cword" \ COMP_LINE="$COMP_LINE" \ COMP_POINT="$COMP_POINT" \ npm completion -- "${words[@]}" \ - 2>/dev/null)) || return $? + 2>/dev/null)); then + local ret=$? + IFS="$si" + return $ret + fi IFS="$si" if type __ltrim_colon_completions &>/dev/null; then __ltrim_colon_completions "${words[cword]}" @@ -49,11 +53,16 @@ elif type compctl &>/dev/null; then read -l line read -ln point si="$IFS" - IFS=$'\n' reply=($(COMP_CWORD="$cword" \ + if ! IFS=$'\n' reply=($(COMP_CWORD="$cword" \ COMP_LINE="$line" \ COMP_POINT="$point" \ npm completion -- "${words[@]}" \ - 2>/dev/null)) || return $? + 2>/dev/null)); then + + local ret=$? + IFS="$si" + return $ret + fi IFS="$si" } compctl -K _npm_completion npm From 3c53d631f557cf2484e2f6a6172c44e36aea4817 Mon Sep 17 00:00:00 2001 From: rethab Date: Wed, 26 May 2021 14:57:24 +0200 Subject: [PATCH 5/9] fix(docs): typo in package-lock.json docs PR-URL: https://github.com/npm/cli/pull/3307 Credit: @rethab Close: #3307 Reviewed-by: @wraithgar --- docs/content/configuring-npm/package-lock-json.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/content/configuring-npm/package-lock-json.md b/docs/content/configuring-npm/package-lock-json.md index 4d994bbc8c0a2..c06540fb3ffae 100644 --- a/docs/content/configuring-npm/package-lock-json.md +++ b/docs/content/configuring-npm/package-lock-json.md @@ -36,8 +36,8 @@ various purposes: Both of these files have the same format, and perform similar functions in the root of a project. -The difference is that `package-lock.json` is that it cannot be published, -and it will be ignored if found in any place other than the root project. +The difference is that `package-lock.json` cannot be published, and it will +be ignored if found in any place other than the root project. In contrast, [npm-shrinkwrap.json](/configuring-npm/npm-shrinkwrap-json) allows publication, and defines the dependency tree from the point encountered. From 554e8a5cd7034052a59a9ada31e4b8f73712211a Mon Sep 17 00:00:00 2001 From: isaacs Date: Wed, 26 May 2021 12:30:57 -0700 Subject: [PATCH 6/9] fix: set audit exit code properly When running 'npm audit', we properly exited correctly with the appropriate exitCode based on the audit level config and the report results. However, when going through the reifyFinish() function (as we do for 'npm audit fix'), we were not setting that properly if the auditLevel was not set. Furthermore, if the auditLevel WAS set, we were setting the exit code to non-zero for *other* reify commands (install, update, etc.), where the audit information should be strictly advisory. When --json and --loglevel=silent were set, the exitCode was never being set properly. This fixes all these problems. PR-URL: https://github.com/npm/cli/pull/3311 Credit: @isaacs Close: #3311 Reviewed-by: @wraithgar --- lib/utils/reify-output.js | 34 +++++-- test/lib/utils/reify-output.js | 165 ++++++++++++++++++++++++++++----- 2 files changed, 171 insertions(+), 28 deletions(-) diff --git a/lib/utils/reify-output.js b/lib/utils/reify-output.js index ddad32121e8b4..bf3fa7fb2e13d 100644 --- a/lib/utils/reify-output.js +++ b/lib/utils/reify-output.js @@ -18,10 +18,6 @@ const auditError = require('./audit-error.js') // TODO: output JSON if flatOptions.json is true const reifyOutput = (npm, arb) => { - // don't print any info in --silent mode - if (log.levels[log.level] > log.levels.error) - return - const { diff, actualTree } = arb // note: fails and crashes if we're running audit fix and there was an error @@ -29,6 +25,13 @@ const reifyOutput = (npm, arb) => { // stuff in that case! const auditReport = auditError(npm, arb.auditReport) ? null : arb.auditReport + // don't print any info in --silent mode, but we still need to + // set the exitCode properly from the audit report, if we have one. + if (log.levels[log.level] > log.levels.error) { + getAuditReport(npm, auditReport) + return + } + const summary = { added: 0, removed: 0, @@ -68,6 +71,8 @@ const reifyOutput = (npm, arb) => { if (npm.flatOptions.json) { if (auditReport) { + // call this to set the exit code properly + getAuditReport(npm, auditReport) summary.audit = npm.command === 'audit' ? auditReport : auditReport.toJSON().metadata } @@ -83,11 +88,25 @@ const reifyOutput = (npm, arb) => { // at the end if there's still stuff, because it's silly for `npm audit` // to tell you to run `npm audit` for details. otherwise, use the summary // report. if we get here, we know it's not quiet or json. +// If the loglevel is set higher than 'error', then we just run the report +// to get the exitCode set appropriately. const printAuditReport = (npm, report) => { + const res = getAuditReport(npm, report) + if (!res || !res.report) + return + npm.output(`\n${res.report}`) +} + +const getAuditReport = (npm, report) => { if (!report) return - const reporter = npm.command !== 'audit' ? 'install' : 'detail' + // when in silent mode, we print nothing. the JSON output is + // going to just JSON.stringify() the report object. + const reporter = log.levels[log.level] > log.levels.error ? 'quiet' + : npm.flatOptions.json ? 'quiet' + : npm.command !== 'audit' ? 'install' + : 'detail' const defaultAuditLevel = npm.command !== 'audit' ? 'none' : 'low' const auditLevel = npm.flatOptions.auditLevel || defaultAuditLevel @@ -96,8 +115,9 @@ const printAuditReport = (npm, report) => { ...npm.flatOptions, auditLevel, }) - process.exitCode = process.exitCode || res.exitCode - npm.output('\n' + res.report) + if (npm.command === 'audit') + process.exitCode = process.exitCode || res.exitCode + return res } const packagesChangedMessage = (npm, { added, removed, changed, audited }) => { diff --git a/test/lib/utils/reify-output.js b/test/lib/utils/reify-output.js index 2142566b90dea..3ffbdf86a2989 100644 --- a/test/lib/utils/reify-output.js +++ b/test/lib/utils/reify-output.js @@ -187,31 +187,154 @@ t.test('print appropriate message for many packages', (t) => { }) }) -t.test('no output when silent', t => { - npm.output = out => { - t.fail('should not get output when silent', { actual: out }) - } - t.teardown(() => log.level = 'warn') - log.level = 'silent' - reifyOutput(npm, { - actualTree: { inventory: { size: 999 }, children: [] }, - auditReport: { - toJSON: () => { - throw new Error('this should not get called') - }, - vulnerabilities: {}, - metadata: { - vulnerabilities: { - total: 99, - }, +t.test('showing and not showing audit report', async t => { + const auditReport = { + toJSON: () => auditReport, + auditReportVersion: 2, + vulnerabilities: { + minimist: { + name: 'minimist', + severity: 'low', + via: [ + { + id: 1179, + url: 'https://npmjs.com/advisories/1179', + title: 'Prototype Pollution', + severity: 'low', + vulnerable_versions: '<0.2.1 || >=1.0.0 <1.2.3', + }, + ], + effects: [], + range: '<0.2.1 || >=1.0.0 <1.2.3', + nodes: [ + 'node_modules/minimist', + ], + fixAvailable: true, }, }, - diff: { - children: [ - { action: 'ADD', ideal: { location: 'loc' } }, - ], + metadata: { + vulnerabilities: { + info: 0, + low: 1, + moderate: 0, + high: 0, + critical: 0, + total: 1, + }, + dependencies: { + prod: 1, + dev: 0, + optional: 0, + peer: 0, + peerOptional: 0, + total: 1, + }, }, + } + + t.test('no output when silent', t => { + npm.output = out => { + t.fail('should not get output when silent', { actual: out }) + } + t.teardown(() => log.level = 'warn') + log.level = 'silent' + reifyOutput(npm, { + actualTree: { inventory: { size: 999 }, children: [] }, + auditReport, + diff: { + children: [ + { action: 'ADD', ideal: { location: 'loc' } }, + ], + }, + }) + t.end() }) + + t.test('output when not silent', t => { + const OUT = [] + npm.output = out => { + OUT.push(out) + } + reifyOutput(npm, { + actualTree: { inventory: new Map(), children: [] }, + auditReport, + diff: { + children: [ + { action: 'ADD', ideal: { location: 'loc' } }, + ], + }, + }) + t.match(OUT.join('\n'), /Run `npm audit` for details\.$/, 'got audit report') + t.end() + }) + + for (const json of [true, false]) { + t.test(`json=${json}`, t => { + t.teardown(() => { + delete npm.flatOptions.json + }) + npm.flatOptions.json = json + t.test('set exit code when cmd is audit', t => { + npm.output = () => {} + const { exitCode } = process + const { command } = npm + npm.flatOptions.auditLevel = 'low' + t.teardown(() => { + delete npm.flatOptions.auditLevel + npm.command = command + // only set exitCode back if we're passing tests + if (t.passing()) + process.exitCode = exitCode + }) + + process.exitCode = 0 + npm.command = 'audit' + reifyOutput(npm, { + actualTree: { inventory: new Map(), children: [] }, + auditReport, + diff: { + children: [ + { action: 'ADD', ideal: { location: 'loc' } }, + ], + }, + }) + + t.equal(process.exitCode, 1, 'set exit code') + t.end() + }) + + t.test('do not set exit code when cmd is install', t => { + npm.output = () => {} + const { exitCode } = process + const { command } = npm + npm.flatOptions.auditLevel = 'low' + t.teardown(() => { + delete npm.flatOptions.auditLevel + npm.command = command + // only set exitCode back if we're passing tests + if (t.passing()) + process.exitCode = exitCode + }) + + process.exitCode = 0 + npm.command = 'install' + reifyOutput(npm, { + actualTree: { inventory: new Map(), children: [] }, + auditReport, + diff: { + children: [ + { action: 'ADD', ideal: { location: 'loc' } }, + ], + }, + }) + + t.equal(process.exitCode, 0, 'did not set exit code') + t.end() + }) + t.end() + }) + } + t.end() }) From 96367f93f46c24494d084c8b8d34e4de9cd375da Mon Sep 17 00:00:00 2001 From: isaacs Date: Wed, 26 May 2021 14:21:19 -0700 Subject: [PATCH 7/9] docs: rebuild npm-pack doc --- docs/content/commands/npm-pack.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/docs/content/commands/npm-pack.md b/docs/content/commands/npm-pack.md index ff90bd74472b4..04a22a5d854b4 100644 --- a/docs/content/commands/npm-pack.md +++ b/docs/content/commands/npm-pack.md @@ -27,6 +27,15 @@ commands that modify your local installation, eg, `install`, `update`, Note: This is NOT honored by other network related commands, eg `dist-tags`, `owner`, etc. +#### `json` + +* Default: false +* Type: Boolean + +Whether or not to output JSON data, rather than the normal output. + +Not supported by all npm commands. + #### `workspace` * Default: From 399ff8cbccd5198f637518ccafa86c43bab47a4a Mon Sep 17 00:00:00 2001 From: isaacs Date: Wed, 26 May 2021 14:16:36 -0700 Subject: [PATCH 8/9] feat(link): add workspace support PR-URL: https://github.com/npm/cli/pull/3312 Credit: @isaacs Close: #3312 Reviewed-by: @wraithgar --- docs/content/commands/npm-link.md | 42 +++++ lib/base-command.js | 1 + lib/link.js | 11 +- lib/workspaces/arborist-cmd.js | 1 + tap-snapshots/test/lib/link.js.test.cjs | 15 ++ .../test/lib/load-all-commands.js.test.cjs | 2 + .../test/lib/utils/npm-usage.js.test.cjs | 2 + test/lib/link.js | 172 ++++++++++++++++++ 8 files changed, 243 insertions(+), 3 deletions(-) diff --git a/docs/content/commands/npm-link.md b/docs/content/commands/npm-link.md index e48be396ade1b..b1c6066768a99 100644 --- a/docs/content/commands/npm-link.md +++ b/docs/content/commands/npm-link.md @@ -99,6 +99,16 @@ relevant metadata by running `npm install --package-lock-only`. If you _want_ to save the `file:` reference in your `package.json` and `package-lock.json` files, you can use `npm link --save` to do so. +### Workspace Usage + +`npm link --workspace ` will link the relevant package as a +dependency of the specified workspace(s). Note that It may actually be +linked into the parent project's `node_modules` folder, if there are no +conflicting dependencies. + +`npm link --workspace ` will create a global link to the specified +workspace(s). + ### Configuration @@ -261,6 +271,38 @@ commands that modify your local installation, eg, `install`, `update`, Note: This is NOT honored by other network related commands, eg `dist-tags`, `owner`, etc. +#### `workspace` + +* Default: +* Type: String (can be set multiple times) + +Enable running a command in the context of the configured workspaces of the +current project while filtering by running only the workspaces defined by +this configuration option. + +Valid values for the `workspace` config are either: + +* Workspace names +* Path to a workspace directory +* Path to a parent workspace directory (will result to selecting all of the + nested workspaces) + +When set for the `npm init` command, this may be set to the folder of a +workspace which does not yet exist, to create the folder and set it up as a +brand new workspace within the project. + +This value is not exported to the environment for child processes. + +#### `workspaces` + +* Default: false +* Type: Boolean + +Enable running a command in the context of **all** the configured +workspaces. + +This value is not exported to the environment for child processes. + ### See Also diff --git a/lib/base-command.js b/lib/base-command.js index e1efcff5832b3..843fb2d4b1358 100644 --- a/lib/base-command.js +++ b/lib/base-command.js @@ -7,6 +7,7 @@ class BaseCommand { this.wrapWidth = 80 this.npm = npm this.workspaces = null + this.workspacePaths = null } get name () { diff --git a/lib/link.js b/lib/link.js index 60665a9964fab..47fe4b17a272b 100644 --- a/lib/link.js +++ b/lib/link.js @@ -10,8 +10,8 @@ const semver = require('semver') const reifyFinish = require('./utils/reify-finish.js') -const BaseCommand = require('./base-command.js') -class Link extends BaseCommand { +const ArboristWorkspaceCmd = require('./workspaces/arborist-cmd.js') +class Link extends ArboristWorkspaceCmd { /* istanbul ignore next - see test/lib/load-all-commands.js */ static get description () { return 'Symlink a package folder' @@ -46,6 +46,7 @@ class Link extends BaseCommand { 'bin-links', 'fund', 'dry-run', + ...super.params, ] } @@ -143,12 +144,16 @@ class Link extends BaseCommand { log: this.npm.log, add: names.map(l => `file:${resolve(globalTop, 'node_modules', l)}`), save, + workspaces: this.workspaces, }) await reifyFinish(this.npm, localArb) } async linkPkg () { + const wsp = this.workspacePaths + const paths = wsp && wsp.length ? wsp : [this.npm.prefix] + const add = paths.map(path => `file:${path}`) const globalTop = resolve(this.npm.globalDir, '..') const arb = new Arborist({ ...this.npm.flatOptions, @@ -157,7 +162,7 @@ class Link extends BaseCommand { global: true, }) await arb.reify({ - add: [`file:${this.npm.prefix}`], + add, log: this.npm.log, }) await reifyFinish(this.npm, arb) diff --git a/lib/workspaces/arborist-cmd.js b/lib/workspaces/arborist-cmd.js index b12c0ee47b19f..337e7f9d8f932 100644 --- a/lib/workspaces/arborist-cmd.js +++ b/lib/workspaces/arborist-cmd.js @@ -17,6 +17,7 @@ class ArboristCmd extends BaseCommand { getWorkspaces(filters, { path: this.npm.localPrefix }) .then(workspaces => { this.workspaces = [...workspaces.keys()] + this.workspacePaths = [...workspaces.values()] this.exec(args, cb) }) .catch(er => cb(er)) diff --git a/tap-snapshots/test/lib/link.js.test.cjs b/tap-snapshots/test/lib/link.js.test.cjs index d6dd376593b4d..0e20bcd994e3a 100644 --- a/tap-snapshots/test/lib/link.js.test.cjs +++ b/tap-snapshots/test/lib/link.js.test.cjs @@ -14,6 +14,16 @@ exports[`test/lib/link.js TAP link global linked pkg to local nm when using args ` +exports[`test/lib/link.js TAP link global linked pkg to local workspace using args > should create a local symlink to global pkg 1`] = ` +{CWD}/test/lib/tap-testdir-link-link-global-linked-pkg-to-local-workspace-using-args/my-project/node_modules/@myscope/bar -> {CWD}/test/lib/tap-testdir-link-link-global-linked-pkg-to-local-workspace-using-args/global-prefix/lib/node_modules/@myscope/bar +{CWD}/test/lib/tap-testdir-link-link-global-linked-pkg-to-local-workspace-using-args/my-project/node_modules/@myscope/linked -> {CWD}/test/lib/tap-testdir-link-link-global-linked-pkg-to-local-workspace-using-args/scoped-linked +{CWD}/test/lib/tap-testdir-link-link-global-linked-pkg-to-local-workspace-using-args/my-project/node_modules/a -> {CWD}/test/lib/tap-testdir-link-link-global-linked-pkg-to-local-workspace-using-args/global-prefix/lib/node_modules/a +{CWD}/test/lib/tap-testdir-link-link-global-linked-pkg-to-local-workspace-using-args/my-project/node_modules/link-me-too -> {CWD}/test/lib/tap-testdir-link-link-global-linked-pkg-to-local-workspace-using-args/link-me-too +{CWD}/test/lib/tap-testdir-link-link-global-linked-pkg-to-local-workspace-using-args/my-project/node_modules/test-pkg-link -> {CWD}/test/lib/tap-testdir-link-link-global-linked-pkg-to-local-workspace-using-args/test-pkg-link +{CWD}/test/lib/tap-testdir-link-link-global-linked-pkg-to-local-workspace-using-args/my-project/node_modules/x -> {CWD}/test/lib/tap-testdir-link-link-global-linked-pkg-to-local-workspace-using-args/my-project/packages/x + +` + exports[`test/lib/link.js TAP link pkg already in global space > should create a local symlink to global pkg 1`] = ` {CWD}/test/lib/tap-testdir-link-link-pkg-already-in-global-space/my-project/node_modules/@myscope/linked -> {CWD}/test/lib/tap-testdir-link-link-pkg-already-in-global-space/scoped-linked @@ -28,3 +38,8 @@ exports[`test/lib/link.js TAP link to globalDir when in current working dir of p {CWD}/test/lib/tap-testdir-link-link-to-globalDir-when-in-current-working-dir-of-pkg-and-no-args/global-prefix/lib/node_modules/test-pkg-link -> {CWD}/test/lib/tap-testdir-link-link-to-globalDir-when-in-current-working-dir-of-pkg-and-no-args/test-pkg-link ` + +exports[`test/lib/link.js TAP link ws to globalDir when workspace specified and no args > should create a global link to current pkg 1`] = ` +{CWD}/test/lib/tap-testdir-link-link-ws-to-globalDir-when-workspace-specified-and-no-args/global-prefix/lib/node_modules/a -> {CWD}/test/lib/tap-testdir-link-link-ws-to-globalDir-when-workspace-specified-and-no-args/test-pkg-link/packages/a + +` 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 f2d40d4161494..d40be42868184 100644 --- a/tap-snapshots/test/lib/load-all-commands.js.test.cjs +++ b/tap-snapshots/test/lib/load-all-commands.js.test.cjs @@ -521,6 +521,8 @@ Options: [--strict-peer-deps] [--package-lock] [--omit [--omit ...]] [--ignore-scripts] [--audit] [--bin-links] [--fund] [--dry-run] +[-w|--workspace [-w|--workspace ...]] +[-ws|--workspaces] alias: ln 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 a81a8f44b30b9..7fdcf0c5d2dba 100644 --- a/tap-snapshots/test/lib/utils/npm-usage.js.test.cjs +++ b/tap-snapshots/test/lib/utils/npm-usage.js.test.cjs @@ -624,6 +624,8 @@ All commands: [--strict-peer-deps] [--package-lock] [--omit [--omit ...]] [--ignore-scripts] [--audit] [--bin-links] [--fund] [--dry-run] + [-w|--workspace [-w|--workspace ...]] + [-ws|--workspaces] alias: ln diff --git a/test/lib/link.js b/test/lib/link.js index d3e66185280ae..3cad0ff90362d 100644 --- a/test/lib/link.js +++ b/test/lib/link.js @@ -84,6 +84,60 @@ t.test('link to globalDir when in current working dir of pkg and no args', (t) = }) }) +t.test('link ws to globalDir when workspace specified and no args', (t) => { + t.plan(2) + + const testdir = t.testdir({ + 'global-prefix': { + lib: { + node_modules: { + a: { + 'package.json': JSON.stringify({ + name: 'a', + version: '1.0.0', + }), + }, + }, + }, + }, + 'test-pkg-link': { + 'package.json': JSON.stringify({ + name: 'test-pkg-link', + version: '1.0.0', + workspaces: ['packages/*'], + }), + packages: { + a: { + 'package.json': JSON.stringify({ + name: 'a', + version: '1.0.0', + }), + }, + }, + }, + }) + npm.globalDir = resolve(testdir, 'global-prefix', 'lib', 'node_modules') + npm.prefix = resolve(testdir, 'test-pkg-link') + npm.localPrefix = resolve(testdir, 'test-pkg-link') + + reifyOutput = async () => { + reifyOutput = undefined + + const links = await printLinks({ + path: resolve(npm.globalDir, '..'), + global: true, + }) + + t.matchSnapshot(links, 'should create a global link to current pkg') + } + + // link.workspaces = ['a'] + // link.workspacePaths = [resolve(testdir, 'test-pkg-link/packages/a')] + link.execWorkspaces([], ['a'], (err) => { + t.error(err, 'should not error out') + }) +}) + t.test('link global linked pkg to local nm when using args', (t) => { t.plan(2) @@ -192,6 +246,124 @@ t.test('link global linked pkg to local nm when using args', (t) => { }) }) +t.test('link global linked pkg to local workspace using args', (t) => { + t.plan(2) + + const testdir = t.testdir({ + 'global-prefix': { + lib: { + node_modules: { + '@myscope': { + foo: { + 'package.json': JSON.stringify({ + name: '@myscope/foo', + version: '1.0.0', + }), + }, + bar: { + 'package.json': JSON.stringify({ + name: '@myscope/bar', + version: '1.0.0', + }), + }, + linked: t.fixture('symlink', '../../../../scoped-linked'), + }, + a: { + 'package.json': JSON.stringify({ + name: 'a', + version: '1.0.0', + }), + }, + b: { + 'package.json': JSON.stringify({ + name: 'b', + version: '1.0.0', + }), + }, + 'test-pkg-link': t.fixture('symlink', '../../../test-pkg-link'), + }, + }, + }, + 'test-pkg-link': { + 'package.json': JSON.stringify({ + name: 'test-pkg-link', + version: '1.0.0', + }), + }, + 'link-me-too': { + 'package.json': JSON.stringify({ + name: 'link-me-too', + version: '1.0.0', + }), + }, + 'scoped-linked': { + 'package.json': JSON.stringify({ + name: '@myscope/linked', + version: '1.0.0', + }), + }, + 'my-project': { + 'package.json': JSON.stringify({ + name: 'my-project', + version: '1.0.0', + workspaces: ['packages/*'], + }), + packages: { + x: { + 'package.json': JSON.stringify({ + name: 'x', + version: '1.0.0', + dependencies: { + foo: '^1.0.0', + }, + }), + }, + }, + node_modules: { + foo: { + 'package.json': JSON.stringify({ + name: 'foo', + version: '1.0.0', + }), + }, + }, + }, + }) + npm.globalDir = resolve(testdir, 'global-prefix', 'lib', 'node_modules') + npm.prefix = resolve(testdir, 'my-project') + npm.localPrefix = resolve(testdir, 'my-project') + + const _cwd = process.cwd() + process.chdir(npm.prefix) + + reifyOutput = async () => { + reifyOutput = undefined + process.chdir(_cwd) + + const links = await printLinks({ + path: npm.prefix, + }) + + t.matchSnapshot(links, 'should create a local symlink to global pkg') + } + + // installs examples for: + // - test-pkg-link: pkg linked to globalDir from local fs + // - @myscope/linked: scoped pkg linked to globalDir from local fs + // - @myscope/bar: prev installed scoped package available in globalDir + // - a: prev installed package available in globalDir + // - file:./link-me-too: pkg that needs to be reified in globalDir first + link.execWorkspaces([ + 'test-pkg-link', + '@myscope/linked', + '@myscope/bar', + 'a', + 'file:../link-me-too', + ], ['x'], (err) => { + t.error(err, 'should not error out') + }) +}) + t.test('link pkg already in global space', (t) => { t.plan(3) From 64b13dd1082b6ca7eac4e8e329bfdd8cd8daf157 Mon Sep 17 00:00:00 2001 From: Spencer Wilson <5624115+spencerwilson@users.noreply.github.com> Date: Wed, 26 May 2021 15:39:23 -0700 Subject: [PATCH 9/9] docs: Drop stale Python 3<->node-gyp remark PR-URL: https://github.com/npm/cli/pull/3313 Credit: @spencerwilson Close: #3313 Reviewed-by: @wraithgar --- docs/content/commands/npm.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/docs/content/commands/npm.md b/docs/content/commands/npm.md index 2d86aa62c0080..7ff1cc490707e 100644 --- a/docs/content/commands/npm.md +++ b/docs/content/commands/npm.md @@ -62,10 +62,8 @@ requires compiling of C++ Code, npm will use [node-gyp](https://github.com/nodejs/node-gyp) for that task. For a Unix system, [node-gyp](https://github.com/nodejs/node-gyp) needs Python, make and a buildchain like GCC. On Windows, -Python and Microsoft Visual Studio C++ are needed. Python 3 is -not supported by [node-gyp](https://github.com/nodejs/node-gyp). -For more information visit -[the node-gyp repository](https://github.com/nodejs/node-gyp) and +Python and Microsoft Visual Studio C++ are needed. For more information +visit [the node-gyp repository](https://github.com/nodejs/node-gyp) and the [node-gyp Wiki](https://github.com/nodejs/node-gyp/wiki). ### Directories