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/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: 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 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. 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" } } 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/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/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 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/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/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", 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/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/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) 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, 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() })