Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/npm/cli.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--docs/content/commands/npm-adduser.md2
-rw-r--r--docs/content/commands/npm-bin.md2
-rw-r--r--docs/content/commands/npm-cache.md2
-rw-r--r--docs/content/commands/npm-completion.md2
-rw-r--r--docs/content/commands/npm-config.md2
-rw-r--r--docs/content/commands/npm-deprecate.md2
-rw-r--r--docs/content/commands/npm-doctor.md2
-rw-r--r--docs/content/commands/npm-edit.md2
-rw-r--r--docs/content/commands/npm-explore.md2
-rw-r--r--docs/content/commands/npm-help-search.md2
-rw-r--r--docs/content/commands/npm-help.md2
-rw-r--r--docs/content/commands/npm-hook.md2
-rw-r--r--docs/content/commands/npm-logout.md2
-rw-r--r--docs/content/commands/npm-org.md2
-rw-r--r--docs/content/commands/npm-owner.md2
-rw-r--r--docs/content/commands/npm-ping.md2
-rw-r--r--docs/content/commands/npm-prefix.md2
-rw-r--r--docs/content/commands/npm-profile.md2
-rw-r--r--docs/content/commands/npm-run-script.md85
-rw-r--r--docs/content/commands/npm-search.md2
-rw-r--r--docs/content/commands/npm-shrinkwrap.md2
-rw-r--r--docs/content/commands/npm-star.md2
-rw-r--r--docs/content/commands/npm-stars.md2
-rw-r--r--docs/content/commands/npm-team.md2
-rw-r--r--docs/content/commands/npm-token.md4
-rw-r--r--docs/content/commands/npm-unstar.md2
-rw-r--r--docs/content/commands/npm-whoami.md2
-rw-r--r--docs/content/using-npm/workspaces.md46
-rw-r--r--lib/base-command.js7
-rw-r--r--lib/cli.js4
-rw-r--r--lib/npm.js9
-rw-r--r--lib/run-script.js156
-rw-r--r--lib/utils/config/definitions.js27
-rw-r--r--lib/utils/did-you-mean.js9
-rw-r--r--lib/utils/lifecycle-cmd.js4
-rw-r--r--tap-snapshots/test-lib-utils-config-definitions.js-TAP.test.js2
-rw-r--r--tap-snapshots/test-lib-utils-config-describe-all.js-TAP.test.js21
-rw-r--r--tap-snapshots/test-lib-utils-config-index.js-TAP.test.js6
-rw-r--r--test/lib/npm.js78
-rw-r--r--test/lib/run-script.js466
-rw-r--r--test/lib/utils/did-you-mean.js20
-rw-r--r--test/lib/utils/lifecycle-cmd.js6
42 files changed, 964 insertions, 36 deletions
diff --git a/docs/content/commands/npm-adduser.md b/docs/content/commands/npm-adduser.md
index 7960869ad..d0ddd68c2 100644
--- a/docs/content/commands/npm-adduser.md
+++ b/docs/content/commands/npm-adduser.md
@@ -12,6 +12,8 @@ npm adduser [--registry=url] [--scope=@orgname] [--always-auth] [--auth-type=leg
aliases: login, add-user
```
+Note: This command is unaware of workspaces.
+
### Description
Create or verify a user named `<username>` in the specified registry, and
diff --git a/docs/content/commands/npm-bin.md b/docs/content/commands/npm-bin.md
index 4303040e7..c835784f6 100644
--- a/docs/content/commands/npm-bin.md
+++ b/docs/content/commands/npm-bin.md
@@ -10,6 +10,8 @@ description: Display npm bin folder
npm bin [-g|--global]
```
+Note: This command is unaware of workspaces.
+
### Description
Print the folder where npm will install executables.
diff --git a/docs/content/commands/npm-cache.md b/docs/content/commands/npm-cache.md
index 13386f2c4..bcc2989b7 100644
--- a/docs/content/commands/npm-cache.md
+++ b/docs/content/commands/npm-cache.md
@@ -18,6 +18,8 @@ aliases: npm cache clear, npm cache rm
npm cache verify
```
+Note: This command is unaware of workspaces.
+
### Description
Used to add, list, or clean the npm cache folder.
diff --git a/docs/content/commands/npm-completion.md b/docs/content/commands/npm-completion.md
index 53737c803..9dbd96091 100644
--- a/docs/content/commands/npm-completion.md
+++ b/docs/content/commands/npm-completion.md
@@ -10,6 +10,8 @@ description: Tab Completion for npm
source <(npm completion)
```
+Note: This command is unaware of workspaces.
+
### Description
Enables tab-completion in all npm commands.
diff --git a/docs/content/commands/npm-config.md b/docs/content/commands/npm-config.md
index 51caa5a61..31629a6b7 100644
--- a/docs/content/commands/npm-config.md
+++ b/docs/content/commands/npm-config.md
@@ -18,6 +18,8 @@ npm get [<key> [<key> ...]]
alias: c
```
+Note: This command is unaware of workspaces.
+
### Description
npm gets its config settings from the command line, environment
diff --git a/docs/content/commands/npm-deprecate.md b/docs/content/commands/npm-deprecate.md
index 139441856..060379766 100644
--- a/docs/content/commands/npm-deprecate.md
+++ b/docs/content/commands/npm-deprecate.md
@@ -10,6 +10,8 @@ description: Deprecate a version of a package
npm deprecate <pkg>[@<version range>] <message>
```
+Note: This command is unaware of workspaces.
+
### Description
This command will update the npm registry entry for a package, providing a
diff --git a/docs/content/commands/npm-doctor.md b/docs/content/commands/npm-doctor.md
index 2aceee239..9416818a4 100644
--- a/docs/content/commands/npm-doctor.md
+++ b/docs/content/commands/npm-doctor.md
@@ -10,6 +10,8 @@ description: Check your npm environment
npm doctor
```
+Note: This command is unaware of workspaces.
+
### Description
`npm doctor` runs a set of checks to ensure that your npm installation has
diff --git a/docs/content/commands/npm-edit.md b/docs/content/commands/npm-edit.md
index 40fac0408..20788aafb 100644
--- a/docs/content/commands/npm-edit.md
+++ b/docs/content/commands/npm-edit.md
@@ -10,6 +10,8 @@ description: Edit an installed package
npm edit <pkg>
```
+Note: This command is unaware of workspaces.
+
### Description
Selects a dependency in the current project and opens the package folder in
diff --git a/docs/content/commands/npm-explore.md b/docs/content/commands/npm-explore.md
index e467a7557..7e2004b84 100644
--- a/docs/content/commands/npm-explore.md
+++ b/docs/content/commands/npm-explore.md
@@ -10,6 +10,8 @@ description: Browse an installed package
npm explore <pkg> [ -- <command>]
```
+Note: This command is unaware of workspaces.
+
### Description
Spawn a subshell in the directory of the installed package specified.
diff --git a/docs/content/commands/npm-help-search.md b/docs/content/commands/npm-help-search.md
index e10638efa..51c7b43fb 100644
--- a/docs/content/commands/npm-help-search.md
+++ b/docs/content/commands/npm-help-search.md
@@ -10,6 +10,8 @@ description: Search npm help documentation
npm help-search <text>
```
+Note: This command is unaware of workspaces.
+
### Description
This command will search the npm markdown documentation files for the terms
diff --git a/docs/content/commands/npm-help.md b/docs/content/commands/npm-help.md
index 56e466455..57c5efc8e 100644
--- a/docs/content/commands/npm-help.md
+++ b/docs/content/commands/npm-help.md
@@ -10,6 +10,8 @@ description: Get help on npm
npm help <term> [<terms..>]
```
+Note: This command is unaware of workspaces.
+
### Description
If supplied a topic, then show the appropriate documentation page.
diff --git a/docs/content/commands/npm-hook.md b/docs/content/commands/npm-hook.md
index 2ac548ada..6effc9b7d 100644
--- a/docs/content/commands/npm-hook.md
+++ b/docs/content/commands/npm-hook.md
@@ -13,6 +13,8 @@ npm hook update <id> <url> [secret]
npm hook rm <id>
```
+Note: This command is unaware of workspaces.
+
### Description
Allows you to manage [npm
diff --git a/docs/content/commands/npm-logout.md b/docs/content/commands/npm-logout.md
index 7fa858a99..1172a3f0f 100644
--- a/docs/content/commands/npm-logout.md
+++ b/docs/content/commands/npm-logout.md
@@ -10,6 +10,8 @@ description: Log out of the registry
npm logout [--registry=<url>] [--scope=<@scope>]
```
+Note: This command is unaware of workspaces.
+
### Description
When logged into a registry that supports token-based authentication, tell
diff --git a/docs/content/commands/npm-org.md b/docs/content/commands/npm-org.md
index 18047d109..384f5b99f 100644
--- a/docs/content/commands/npm-org.md
+++ b/docs/content/commands/npm-org.md
@@ -12,6 +12,8 @@ npm org rm <orgname> <username>
npm org ls <orgname> [<username>]
```
+Note: This command is unaware of workspaces.
+
### Example
Add a new developer to an org:
diff --git a/docs/content/commands/npm-owner.md b/docs/content/commands/npm-owner.md
index 69eba56af..b30bbc8dc 100644
--- a/docs/content/commands/npm-owner.md
+++ b/docs/content/commands/npm-owner.md
@@ -14,6 +14,8 @@ npm owner ls [<@scope>/]<pkg>
aliases: author
```
+Note: This command is unaware of workspaces.
+
### Description
Manage ownership of published packages.
diff --git a/docs/content/commands/npm-ping.md b/docs/content/commands/npm-ping.md
index 8de06aa18..f640bf060 100644
--- a/docs/content/commands/npm-ping.md
+++ b/docs/content/commands/npm-ping.md
@@ -10,6 +10,8 @@ description: Ping npm registry
npm ping [--registry <registry>]
```
+Note: This command is unaware of workspaces.
+
### Description
Ping the configured or given npm registry and verify authentication.
diff --git a/docs/content/commands/npm-prefix.md b/docs/content/commands/npm-prefix.md
index 9c33bb189..4e3edf190 100644
--- a/docs/content/commands/npm-prefix.md
+++ b/docs/content/commands/npm-prefix.md
@@ -10,6 +10,8 @@ description: Display prefix
npm prefix [-g]
```
+Note: This command is unaware of workspaces.
+
### Description
Print the local prefix to standard output. This is the closest parent directory
diff --git a/docs/content/commands/npm-profile.md b/docs/content/commands/npm-profile.md
index 88edf26d8..b4e2fdaee 100644
--- a/docs/content/commands/npm-profile.md
+++ b/docs/content/commands/npm-profile.md
@@ -14,6 +14,8 @@ npm profile enable-2fa [auth-and-writes|auth-only]
npm profile disable-2fa
```
+Note: This command is unaware of workspaces.
+
### Description
Change your profile information on the registry. Note that this command
diff --git a/docs/content/commands/npm-run-script.md b/docs/content/commands/npm-run-script.md
index 8b89435e1..076dfd7ad 100644
--- a/docs/content/commands/npm-run-script.md
+++ b/docs/content/commands/npm-run-script.md
@@ -8,6 +8,8 @@ description: Run arbitrary package scripts
```bash
npm run-script <command> [--if-present] [--silent] [-- <args>]
+npm run-script <command> [--workspace=<workspace-name>]
+npm run-script <command> [--workspaces]
aliases: run, rum, urn
```
@@ -78,6 +80,65 @@ If you try to run a script without having a `node_modules` directory and it
fails, you will be given a warning to run `npm install`, just in case you've
forgotten.
+### Workspaces support
+
+You may use the `workspace` or `workspaces` configs in order to run an
+arbitrary command from a package's `"scripts"` object in the context of the
+specified workspaces. If no `"command"` is provided, it will list the available
+scripts for each of these configured workspaces.
+
+Given a project with configured workspaces, e.g:
+
+```
+.
++-- package.json
+`-- packages
+ +-- a
+ | `-- package.json
+ +-- b
+ | `-- package.json
+ `-- c
+ `-- package.json
+```
+
+Assuming the workspace configuration is properly set up at the root level
+`package.json` file. e.g:
+
+```
+{
+ "workspaces": [ "./packages/*" ]
+}
+```
+
+And that each of the configured workspaces has a configured `test` script,
+we can run tests in all of them using the `workspaces` config:
+
+```
+npm test --workspaces
+```
+
+#### Filtering workspaces
+
+It's also possible to run a script in a single workspace using the `workspace`
+config along with a name or directory path:
+
+```
+npm test --workspace=a
+```
+
+The `workspace` config can also be specified multiple times in order to run a
+specific script in the context of multiple workspaces. When defining values for
+the `workspace` config in the command line, it also possible to use `-w` as a
+shorthand, e.g:
+
+```
+npm test -w a -w b
+```
+
+This last command will run `test` in both `./packages/a` and `./packages/b`
+packages.
+
+
### Configuration
#### if-present
@@ -111,6 +172,30 @@ to `/bin/sh` on Unix, defaults to `env.comspec` or `cmd.exe` on Windows.
You can use the `--silent` flag to prevent showing `npm ERR!` output on error.
+#### workspace
+
+* Alias: `-w`
+* Type: Array
+* Default: `[]`
+
+Enable running scripts in the context of workspaces while also filtering by
+the provided names or paths provided.
+
+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
+children workspaces)
+
+#### workspaces
+
+* Alias: `-ws`
+* Type: Boolean
+* Default: `false`
+
+Run scripts in the context of all configured workspaces for the current
+project.
+
### See Also
* [npm scripts](/using-npm/scripts)
diff --git a/docs/content/commands/npm-search.md b/docs/content/commands/npm-search.md
index 35178bcb0..046c9334f 100644
--- a/docs/content/commands/npm-search.md
+++ b/docs/content/commands/npm-search.md
@@ -12,6 +12,8 @@ npm search [-l|--long] [--json] [--parseable] [--no-description] [search terms .
aliases: s, se, find
```
+Note: This command is unaware of workspaces.
+
### Description
Search the registry for packages matching the search terms. `npm search`
diff --git a/docs/content/commands/npm-shrinkwrap.md b/docs/content/commands/npm-shrinkwrap.md
index dce50b784..678622946 100644
--- a/docs/content/commands/npm-shrinkwrap.md
+++ b/docs/content/commands/npm-shrinkwrap.md
@@ -10,6 +10,8 @@ description: Lock down dependency versions for publication
npm shrinkwrap
```
+Note: This command is unaware of workspaces.
+
### Description
This command repurposes `package-lock.json` into a publishable
diff --git a/docs/content/commands/npm-star.md b/docs/content/commands/npm-star.md
index aab6e1077..e624b9248 100644
--- a/docs/content/commands/npm-star.md
+++ b/docs/content/commands/npm-star.md
@@ -10,6 +10,8 @@ description: Mark your favorite packages
npm star [<pkg>...]
```
+Note: This command is unaware of workspaces.
+
### Description
"Starring" a package means that you have some interest in it. It's
diff --git a/docs/content/commands/npm-stars.md b/docs/content/commands/npm-stars.md
index dab11bc66..80217ee04 100644
--- a/docs/content/commands/npm-stars.md
+++ b/docs/content/commands/npm-stars.md
@@ -9,6 +9,8 @@ description: View packages marked as favorites
npm stars [<user>]
```
+Note: This command is unaware of workspaces.
+
### Description
If you have starred a lot of neat things and want to find them again
diff --git a/docs/content/commands/npm-team.md b/docs/content/commands/npm-team.md
index 96aacd8ae..04e1d7f9e 100644
--- a/docs/content/commands/npm-team.md
+++ b/docs/content/commands/npm-team.md
@@ -16,6 +16,8 @@ npm team rm <scope:team> <user>
npm team ls <scope>|<scope:team>
```
+Note: This command is unaware of workspaces.
+
### Description
Used to manage teams in organizations, and change team memberships. Does not
diff --git a/docs/content/commands/npm-token.md b/docs/content/commands/npm-token.md
index 652079453..bafc7fc45 100644
--- a/docs/content/commands/npm-token.md
+++ b/docs/content/commands/npm-token.md
@@ -9,7 +9,9 @@ description: Manage your authentication tokens
npm token list [--json|--parseable]
npm token create [--read-only] [--cidr=1.1.1.1/24,2.2.2.2/16]
npm token revoke <id|token>
- ```
+```
+
+Note: This command is unaware of workspaces.
### Description
diff --git a/docs/content/commands/npm-unstar.md b/docs/content/commands/npm-unstar.md
index 5471d9080..bad191759 100644
--- a/docs/content/commands/npm-unstar.md
+++ b/docs/content/commands/npm-unstar.md
@@ -10,6 +10,8 @@ description: Remove an item from your favorite packages
npm unstar [<pkg>...]
```
+Note: This command is unaware of workspaces.
+
### Description
"Unstarring" a package is the opposite of [`npm star`](/commands/npm-star),
diff --git a/docs/content/commands/npm-whoami.md b/docs/content/commands/npm-whoami.md
index 43b301c51..892adeea3 100644
--- a/docs/content/commands/npm-whoami.md
+++ b/docs/content/commands/npm-whoami.md
@@ -10,6 +10,8 @@ description: Display npm username
npm whoami [--registry <registry>]
```
+Note: This command is unaware of workspaces.
+
### Description
Print the `username` config to standard output.
diff --git a/docs/content/using-npm/workspaces.md b/docs/content/using-npm/workspaces.md
index 2024627c7..28fccd220 100644
--- a/docs/content/using-npm/workspaces.md
+++ b/docs/content/using-npm/workspaces.md
@@ -88,8 +88,54 @@ This demonstrates how the nature of `node_modules` resolution allows for
in such a way that is also easy to [publish](/commands/npm-publish) these
nested workspaces to be consumed elsewhere.
+### Running commands in the context of workspaces
+
+You man use the `workspace` configuration option to run commands in the context
+of a configured workspace.
+
+Following is a quick example on how to use the `npm run` command in the context
+of nested workspaces. For a project containing multiple workspaces, e.g:
+
+```
+.
++-- package.json
+`-- packages
+ +-- a
+ | `-- package.json
+ `-- b
+ `-- package.json
+```
+
+By running a command using the `workspace` option, it's possible to run the
+given command in the context of that specific workspace. e.g:
+
+```
+npm run test --workspace=a
+```
+
+This will run the `test` script defined within the
+`./packages/a/package.json` file.
+
+Please note that you can also specify this argument multiple times in the
+command-line in order to target multiple workspaces, e.g:
+
+```
+npm run test --workspace=a --workspace=b
+```
+
+It's also possible to use the `workspaces` (plural) configuration option to
+enable the same behavior but running that command in the context of **all**
+configured workspaces. e.g:
+
+```
+npm run test --workspaces
+```
+
+Will run the `test` script in both `./packages/a` and `./packages/b`.
+
### See also
* [npm install](/commands/npm-install)
* [npm publish](/commands/npm-publish)
+* [npm run-script](/commands/npm-run-script)
diff --git a/lib/base-command.js b/lib/base-command.js
index b8497fd44..a142b3336 100644
--- a/lib/base-command.js
+++ b/lib/base-command.js
@@ -42,5 +42,12 @@ class BaseCommand {
code: 'EUSAGE',
})
}
+
+ execWorkspaces (args, filters, cb) {
+ throw Object.assign(
+ new Error('This command does not support workspaces.'),
+ { code: 'ENOWORKSPACES' }
+ )
+ }
}
module.exports = BaseCommand
diff --git a/lib/cli.js b/lib/cli.js
index 837d0876c..086335725 100644
--- a/lib/cli.js
+++ b/lib/cli.js
@@ -66,12 +66,14 @@ module.exports = (process) => {
npm.log.level = 'silent'
if (cmd) {
const didYouMean = require('./utils/did-you-mean.js')
- const suggestions = await didYouMean(npm, cmd)
+ console.error(npm.localPrefix)
+ const suggestions = await didYouMean(npm, npm.localPrefix, cmd)
npm.output(suggestions)
} else
npm.output(npm.usage)
process.exitCode = 1
} catch (err) {
+ console.error(err)
errorHandler(err)
}
}
diff --git a/lib/npm.js b/lib/npm.js
index 78b6ba034..bc65cc79b 100644
--- a/lib/npm.js
+++ b/lib/npm.js
@@ -100,9 +100,18 @@ const npm = module.exports = new class extends EventEmitter {
})
}
+ const workspacesEnabled = this.config.get('workspaces')
+ const workspacesFilters = this.config.get('workspace')
+ const filterByWorkspaces = workspacesEnabled || workspacesFilters.length > 0
+
if (this.config.get('usage')) {
this.output(impl.usage)
cb()
+ } else if (filterByWorkspaces) {
+ impl.execWorkspaces(args, this.config.get('workspace'), er => {
+ process.emit('timeEnd', `command:${cmd}`)
+ cb(er)
+ })
} else {
impl.exec(args, er => {
process.emit('timeEnd', `command:${cmd}`)
diff --git a/lib/run-script.js b/lib/run-script.js
index a7202548c..e59340225 100644
--- a/lib/run-script.js
+++ b/lib/run-script.js
@@ -1,8 +1,11 @@
+const { resolve } = require('path')
+const chalk = require('chalk')
const runScript = require('@npmcli/run-script')
+const mapWorkspaces = require('@npmcli/map-workspaces')
const { isServerPackage } = runScript
-const readJson = require('read-package-json-fast')
-const { resolve } = require('path')
+const rpj = require('read-package-json-fast')
const log = require('npmlog')
+const minimatch = require('minimatch')
const didYouMean = require('./utils/did-you-mean.js')
const isWindowsShell = require('./utils/is-windows-shell.js')
@@ -17,6 +20,14 @@ const cmdList = [
'version',
].reduce((l, p) => l.concat(['pre' + p, p, 'post' + p]), [])
+const nocolor = {
+ reset: s => s,
+ bold: s => s,
+ dim: s => s,
+ blue: s => s,
+ green: s => s,
+}
+
const BaseCommand = require('./base-command.js')
class RunScript extends BaseCommand {
/* istanbul ignore next - see test/lib/load-all-commands.js */
@@ -39,7 +50,7 @@ class RunScript extends BaseCommand {
if (argv.length === 2) {
// find the script name
const json = resolve(this.npm.localPrefix, 'package.json')
- const { scripts = {} } = await readJson(json).catch(er => ({}))
+ const { scripts = {} } = await rpj(json).catch(er => ({}))
return Object.keys(scripts)
}
}
@@ -51,14 +62,19 @@ class RunScript extends BaseCommand {
this.list(args).then(() => cb()).catch(cb)
}
- async run (args) {
- const path = this.npm.localPrefix
- const event = args.shift()
+ execWorkspaces (args, filters, cb) {
+ if (args.length)
+ this.runWorkspaces(args, filters).then(() => cb()).catch(cb)
+ else
+ this.listWorkspaces(args, filters).then(() => cb()).catch(cb)
+ }
+
+ async run ([event, ...args], { path = this.npm.localPrefix, pkg } = {}) {
// this || undefined is because runScript will be unhappy with the default
// null value
const scriptShell = this.npm.config.get('script-shell') || undefined
- const pkg = await readJson(`${path}/package.json`)
+ pkg = pkg || (await rpj(`${path}/package.json`))
const { scripts = {} } = pkg
if (event === 'restart' && !scripts.restart)
@@ -75,7 +91,7 @@ class RunScript extends BaseCommand {
if (this.npm.config.get('if-present'))
return
- const suggestions = await didYouMean(this.npm, event)
+ const suggestions = await didYouMean(this.npm, path, event)
throw new Error(suggestions)
}
@@ -108,9 +124,11 @@ class RunScript extends BaseCommand {
}
}
- async list () {
- const path = this.npm.localPrefix
- const { scripts, name } = await readJson(`${path}/package.json`)
+ async list (args, path) {
+ path = path || this.npm.localPrefix
+ const { scripts, name, _id } = await rpj(`${path}/package.json`)
+ const pkgid = _id || name
+ const color = !!this.npm.color
if (!scripts)
return []
@@ -139,22 +157,122 @@ class RunScript extends BaseCommand {
const list = cmdList.includes(script) ? cmds : runScripts
list.push(script)
}
+ const colorize = color ? chalk : nocolor
- if (cmds.length)
- this.npm.output(`Lifecycle scripts included in ${name}:`)
+ if (cmds.length) {
+ this.npm.output(`${
+ colorize.reset(colorize.bold('Lifecycle scripts'))} included in ${
+ colorize.green(pkgid)}:`)
+ }
for (const script of cmds)
- this.npm.output(prefix + script + indent + scripts[script])
+ this.npm.output(prefix + script + indent + colorize.dim(scripts[script]))
- if (!cmds.length && runScripts.length)
- this.npm.output(`Scripts available in ${name} via \`npm run-script\`:`)
- else if (runScripts.length)
- this.npm.output('\navailable via `npm run-script`:')
+ if (!cmds.length && runScripts.length) {
+ this.npm.output(`${
+ colorize.bold('Scripts')
+ } available in ${colorize.green(pkgid)} via \`${
+ colorize.blue('npm run-script')}\`:`)
+ } else if (runScripts.length)
+ this.npm.output(`\navailable via \`${colorize.blue('npm run-script')}\`:`)
for (const script of runScripts)
- this.npm.output(prefix + script + indent + scripts[script])
+ this.npm.output(prefix + script + indent + colorize.dim(scripts[script]))
+ this.npm.output('')
return allScripts
}
+
+ async workspaces (filters) {
+ const cwd = this.npm.localPrefix
+ const pkg = await rpj(resolve(cwd, 'package.json'))
+ const workspaces = await mapWorkspaces({ cwd, pkg })
+ const res = filters.length ? new Map() : workspaces
+
+ for (const filterArg of filters) {
+ for (const [key, path] of workspaces.entries()) {
+ if (filterArg === key
+ || resolve(cwd, filterArg) === path
+ || minimatch(path, `${resolve(cwd, filterArg)}/*`))
+ res.set(key, path)
+ }
+ }
+
+ if (!res.size) {
+ let msg = '!'
+ if (filters.length) {
+ msg = `:\n ${filters.reduce(
+ (res, filterArg) => `${res} --workspace=${filterArg}`, '')}`
+ }
+
+ throw new Error(`No workspaces found${msg}`)
+ }
+
+ return res
+ }
+
+ async runWorkspaces (args, filters) {
+ log.disableProgress()
+
+ const res = []
+ const workspaces = await this.workspaces(filters)
+
+ for (const workspacePath of workspaces.values()) {
+ const pkg = await rpj(`${workspacePath}/package.json`)
+ const runResult = await this.run(args, {
+ path: workspacePath,
+ pkg,
+ }).catch(err => {
+ log.error(`Lifecycle script \`${args[0]}\` failed with error:`)
+ log.error(err)
+ log.error(` in workspace: ${pkg._id || pkg.name}`)
+ log.error(` at location: ${workspacePath}`)
+
+ const scriptMissing = err.message.startsWith('Unknown command')
+
+ // avoids exiting with error code in case there's scripts missing
+ // in some workspaces since other scripts might have succeeded
+ if (!scriptMissing)
+ process.exitCode = 1
+
+ return scriptMissing
+ })
+ res.push(runResult)
+ }
+
+ // in case **all** tests are missing, then it should exit with error code
+ if (res.every(Boolean))
+ throw new Error(`Missing script: ${args[0]}`)
+ }
+
+ async listWorkspaces (args, filters) {
+ const workspaces = await this.workspaces(filters)
+
+ if (log.level === 'silent')
+ return
+
+ if (this.npm.config.get('json')) {
+ const res = {}
+ for (const workspacePath of workspaces.values()) {
+ const { scripts, name } = await rpj(`${workspacePath}/package.json`)
+ res[name] = { ...scripts }
+ }
+ this.npm.output(JSON.stringify(res, null, 2))
+ return
+ }
+
+ if (this.npm.config.get('parseable')) {
+ for (const workspacePath of workspaces.values()) {
+ const { scripts, name } = await rpj(`${workspacePath}/package.json`)
+ for (const [script, cmd] of Object.entries(scripts || {}))
+ this.npm.output(`${name}:${script}:${cmd}`)
+ }
+ return
+ }
+
+ for (const workspacePath of workspaces.values())
+ await this.list(args, workspacePath)
+ }
}
+
module.exports = RunScript
diff --git a/lib/utils/config/definitions.js b/lib/utils/config/definitions.js
index 1ad3a166b..4dd1abe09 100644
--- a/lib/utils/config/definitions.js
+++ b/lib/utils/config/definitions.js
@@ -2003,6 +2003,33 @@ define('viewer', {
`,
})
+define('workspace', {
+ default: [],
+ type: [String, Array],
+ short: 'w',
+ description: `
+ 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)
+ `,
+})
+
+define('workspaces', {
+ default: false,
+ type: Boolean,
+ short: 'ws',
+ description: `
+ Enable running a command in the context of **all** the configured
+ workspaces.
+ `,
+})
+
define('yes', {
default: false,
type: Boolean,
diff --git a/lib/utils/did-you-mean.js b/lib/utils/did-you-mean.js
index 3d8c6677c..5e41af67a 100644
--- a/lib/utils/did-you-mean.js
+++ b/lib/utils/did-you-mean.js
@@ -2,23 +2,22 @@ const leven = require('leven')
const readJson = require('read-package-json-fast')
const { cmdList } = require('./cmd-list.js')
-const didYouMean = async (npm, scmd) => {
+const didYouMean = async (npm, path, scmd) => {
const bestCmd = cmdList
- .filter(cmd => leven(scmd, cmd) < scmd.length * 0.4)
+ .filter(cmd => leven(scmd, cmd) < scmd.length * 0.4 && scmd !== cmd)
.map(str => ` npm ${str} # ${npm.commands[str].description}`)
- const path = npm.localPrefix
const pkg = await readJson(`${path}/package.json`)
const { scripts } = pkg
// We would already be suggesting this in `npm x` so omit them here
const runScripts = ['stop', 'start', 'test', 'restart']
- const bestRun = Object.keys(scripts)
+ const bestRun = Object.keys(scripts || {})
.filter(cmd => leven(scmd, cmd) < scmd.length * 0.4 &&
!runScripts.includes(cmd))
.map(str => ` npm run ${str} # run the "${str}" package script`)
const { bin } = pkg
- const bestBin = Object.keys(bin)
+ const bestBin = Object.keys(bin || {})
.filter(cmd => leven(scmd, cmd) < scmd.length * 0.4)
.map(str => ` npm exec ${str} # run the "${str}" command from either this or a remote npm package`)
diff --git a/lib/utils/lifecycle-cmd.js b/lib/utils/lifecycle-cmd.js
index 1917bef36..2c5b89dfc 100644
--- a/lib/utils/lifecycle-cmd.js
+++ b/lib/utils/lifecycle-cmd.js
@@ -10,5 +10,9 @@ class LifecycleCmd extends BaseCommand {
exec (args, cb) {
this.npm.commands['run-script']([this.constructor.name, ...args], cb)
}
+
+ execWorkspaces (args, filters, cb) {
+ this.npm.commands['run-script']([this.constructor.name, ...args], cb)
+ }
}
module.exports = LifecycleCmd
diff --git a/tap-snapshots/test-lib-utils-config-definitions.js-TAP.test.js b/tap-snapshots/test-lib-utils-config-definitions.js-TAP.test.js
index 3e82da699..d6a72b99b 100644
--- a/tap-snapshots/test-lib-utils-config-definitions.js-TAP.test.js
+++ b/tap-snapshots/test-lib-utils-config-definitions.js-TAP.test.js
@@ -144,6 +144,8 @@ Array [
"version",
"versions",
"viewer",
+ "workspace",
+ "workspaces",
"yes",
]
`
diff --git a/tap-snapshots/test-lib-utils-config-describe-all.js-TAP.test.js b/tap-snapshots/test-lib-utils-config-describe-all.js-TAP.test.js
index ca758c701..909b31f83 100644
--- a/tap-snapshots/test-lib-utils-config-describe-all.js-TAP.test.js
+++ b/tap-snapshots/test-lib-utils-config-describe-all.js-TAP.test.js
@@ -1192,6 +1192,27 @@ The program to use to view help content.
Set to \`"browser"\` to view html help content in the default web browser.
+#### \`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)
+
+#### \`workspaces\`
+
+* Default: false
+* Type: Boolean
+
+Enable running a command in the context of **all** the configured
+workspaces.
+
#### \`yes\`
* Default: false
diff --git a/tap-snapshots/test-lib-utils-config-index.js-TAP.test.js b/tap-snapshots/test-lib-utils-config-index.js-TAP.test.js
index 1044b3063..1e5ca2324 100644
--- a/tap-snapshots/test-lib-utils-config-index.js-TAP.test.js
+++ b/tap-snapshots/test-lib-utils-config-index.js-TAP.test.js
@@ -120,6 +120,12 @@ Object {
"--loglevel",
"verbose",
],
+ "w": Array [
+ "--workspace",
+ ],
+ "ws": Array [
+ "--workspaces",
+ ],
"y": Array [
"--yes",
],
diff --git a/test/lib/npm.js b/test/lib/npm.js
index eb0f8ab27..de0dcaa1c 100644
--- a/test/lib/npm.js
+++ b/test/lib/npm.js
@@ -348,6 +348,84 @@ t.test('npm.load', t => {
await new Promise((res) => setTimeout(res))
})
+ t.test('workpaces-aware configs and commands', 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/*'],
+ }),
+ '.npmrc': '',
+ })
+
+ const { log } = console
+ const consoleLogs = []
+ console.log = (...msg) => consoleLogs.push(msg)
+
+ const { execPath } = process
+ t.teardown(() => {
+ console.log = log
+ })
+
+ freshConfig({
+ argv: [
+ execPath,
+ process.argv[1],
+ '--userconfig',
+ resolve(dir, '.npmrc'),
+ '--color',
+ 'false',
+ '--workspaces',
+ 'true',
+ ],
+ })
+
+ await npm.load(er => {
+ if (er)
+ throw er
+ })
+
+ npm.localPrefix = dir
+
+ await new Promise((res, rej) => {
+ npm.commands['run-script']([], er => {
+ if (er)
+ rej(er)
+
+ t.match(
+ consoleLogs,
+ [
+ ['Lifecycle scripts included in a@1.0.0:'],
+ [' test\n echo test a'],
+ [''],
+ ['Lifecycle scripts included in b@1.0.0:'],
+ [' test\n echo test b'],
+ [''],
+ ],
+ 'should exec workspaces version of commands'
+ )
+
+ res()
+ })
+ })
+ })
+
t.end()
})
diff --git a/test/lib/run-script.js b/test/lib/run-script.js
index d2cac2f42..db1fc4b5c 100644
--- a/test/lib/run-script.js
+++ b/test/lib/run-script.js
@@ -1,7 +1,15 @@
+const { resolve } = require('path')
const t = require('tap')
const requireInject = require('require-inject')
const mockNpm = require('../fixtures/mock-npm')
+const normalizePath = p => p
+ .replace(/\\+/g, '/')
+ .replace(/\r\n/g, '\n')
+
+const cleanOutput = (str) => normalizePath(str)
+ .replace(normalizePath(process.cwd()), '{CWD}')
+
const RUN_SCRIPTS = []
const flatOptions = {
scriptShell: undefined,
@@ -20,21 +28,33 @@ const npm = mockNpm({
help: {
description: 'test help description',
},
+ test: {
+ description: 'test test description',
+ },
},
output: (...msg) => output.push(msg),
})
const output = []
+const npmlog = {
+ disableProgress: () => null,
+ level: 'warn',
+ error: () => null,
+}
+
t.afterEach(cb => {
+ npm.color = false
+ npmlog.level = 'warn'
+ npmlog.error = () => null
output.length = 0
RUN_SCRIPTS.length = 0
+ config['if-present'] = false
config.json = false
config.parseable = false
cb()
})
-const npmlog = { level: 'warn' }
const getRS = windows => {
const RunScript = requireInject('../../lib/run-script.js', {
'@npmcli/run-script': Object.assign(async opts => {
@@ -298,7 +318,7 @@ t.test('try to run missing script', t => {
})
})
t.test('with --if-present', t => {
- npm.config.set('if-present', true)
+ config['if-present'] = true
runScript.exec(['goodbye'], er => {
if (er)
throw er
@@ -461,13 +481,14 @@ t.test('list scripts', t => {
if (er)
throw er
t.strictSame(output, [
- ['Lifecycle scripts included in x:'],
+ ['Lifecycle scripts included in x@1.2.3:'],
[' test\n exit 2'],
[' start\n node server.js'],
[' stop\n node kill-server.js'],
['\navailable via `npm run-script`:'],
[' preenv\n echo before the env'],
[' postenv\n echo after the env'],
+ [''],
], 'basic report')
t.end()
})
@@ -540,8 +561,9 @@ t.test('list scripts, only commands', t => {
if (er)
throw er
t.strictSame(output, [
- ['Lifecycle scripts included in x:'],
+ ['Lifecycle scripts included in x@1.2.3:'],
[' preversion\n echo doing the version dance'],
+ [''],
])
t.end()
})
@@ -560,9 +582,443 @@ t.test('list scripts, only non-commands', t => {
if (er)
throw er
t.strictSame(output, [
- ['Scripts available in x via `npm run-script`:'],
+ ['Scripts available in x@1.2.3 via `npm run-script`:'],
[' glorp\n echo doing the glerp glop'],
+ [''],
])
t.end()
})
})
+
+t.test('workspaces', t => {
+ npm.localPrefix = t.testdir({
+ packages: {
+ a: {
+ 'package.json': JSON.stringify({
+ name: 'a',
+ version: '1.0.0',
+ scripts: { glorp: 'echo a doing the glerp glop' },
+ }),
+ },
+ b: {
+ 'package.json': JSON.stringify({
+ name: 'b',
+ version: '2.0.0',
+ scripts: { glorp: 'echo b doing the glerp glop' },
+ }),
+ },
+ c: {
+ 'package.json': JSON.stringify({
+ name: 'c',
+ version: '1.0.0',
+ scripts: {
+ test: 'exit 0',
+ posttest: 'echo posttest',
+ lorem: 'echo c lorem',
+ },
+ }),
+ },
+ d: {
+ 'package.json': JSON.stringify({
+ name: 'd',
+ version: '1.0.0',
+ scripts: {
+ test: 'exit 0',
+ posttest: 'echo posttest',
+ },
+ }),
+ },
+ e: {
+ 'package.json': JSON.stringify({
+ name: 'e',
+ scripts: { test: 'exit 0', start: 'echo start something' },
+ }),
+ },
+ noscripts: {
+ 'package.json': JSON.stringify({
+ name: 'noscripts',
+ version: '1.0.0',
+ }),
+ },
+ },
+ 'package.json': JSON.stringify({
+ name: 'x',
+ version: '1.2.3',
+ workspaces: ['packages/*'],
+ }),
+ })
+
+ t.test('list all scripts', t => {
+ runScript.execWorkspaces([], [], er => {
+ if (er)
+ throw er
+ t.strictSame(output, [
+ ['Scripts available in a@1.0.0 via `npm run-script`:'],
+ [' glorp\n echo a doing the glerp glop'],
+ [''],
+ ['Scripts available in b@2.0.0 via `npm run-script`:'],
+ [' glorp\n echo b doing the glerp glop'],
+ [''],
+ ['Lifecycle scripts included in c@1.0.0:'],
+ [' test\n exit 0'],
+ [' posttest\n echo posttest'],
+ ['\navailable via `npm run-script`:'],
+ [' lorem\n echo c lorem'],
+ [''],
+ ['Lifecycle scripts included in d@1.0.0:'],
+ [' test\n exit 0'],
+ [' posttest\n echo posttest'],
+ [''],
+ ['Lifecycle scripts included in e:'],
+ [' test\n exit 0'],
+ [' start\n echo start something'],
+ [''],
+ ])
+ t.end()
+ })
+ })
+
+ t.test('list regular scripts, filtered by name', t => {
+ runScript.execWorkspaces([], ['a', 'b'], er => {
+ if (er)
+ throw er
+ t.strictSame(output, [
+ ['Scripts available in a@1.0.0 via `npm run-script`:'],
+ [' glorp\n echo a doing the glerp glop'],
+ [''],
+ ['Scripts available in b@2.0.0 via `npm run-script`:'],
+ [' glorp\n echo b doing the glerp glop'],
+ [''],
+ ])
+ t.end()
+ })
+ })
+
+ t.test('list regular scripts, filtered by path', t => {
+ runScript.execWorkspaces([], ['./packages/a'], er => {
+ if (er)
+ throw er
+ t.strictSame(output, [
+ ['Scripts available in a@1.0.0 via `npm run-script`:'],
+ [' glorp\n echo a doing the glerp glop'],
+ [''],
+ ])
+ t.end()
+ })
+ })
+
+ t.test('list regular scripts, filtered by parent folder', t => {
+ runScript.execWorkspaces([], ['./packages'], er => {
+ if (er)
+ throw er
+ t.strictSame(output, [
+ ['Scripts available in a@1.0.0 via `npm run-script`:'],
+ [' glorp\n echo a doing the glerp glop'],
+ [''],
+ ['Scripts available in b@2.0.0 via `npm run-script`:'],
+ [' glorp\n echo b doing the glerp glop'],
+ [''],
+ ['Lifecycle scripts included in c@1.0.0:'],
+ [' test\n exit 0'],
+ [' posttest\n echo posttest'],
+ ['\navailable via `npm run-script`:'],
+ [' lorem\n echo c lorem'],
+ [''],
+ ['Lifecycle scripts included in d@1.0.0:'],
+ [' test\n exit 0'],
+ [' posttest\n echo posttest'],
+ [''],
+ ['Lifecycle scripts included in e:'],
+ [' test\n exit 0'],
+ [' start\n echo start something'],
+ [''],
+ ])
+ t.end()
+ })
+ })
+
+ t.test('list all scripts with colors', t => {
+ npm.color = true
+ runScript.execWorkspaces([], [], er => {
+ if (er)
+ throw er
+ t.strictSame(output, [
+ [
+ '\u001b[1mScripts\u001b[22m available in \x1B[32ma@1.0.0\x1B[39m via `\x1B[34mnpm run-script\x1B[39m`:',
+ ],
+ [' glorp\n \x1B[2mecho a doing the glerp glop\x1B[22m'],
+ [''],
+ [
+ '\u001b[1mScripts\u001b[22m available in \x1B[32mb@2.0.0\x1B[39m via `\x1B[34mnpm run-script\x1B[39m`:',
+ ],
+ [' glorp\n \x1B[2mecho b doing the glerp glop\x1B[22m'],
+ [''],
+ [
+ '\x1B[0m\x1B[1mLifecycle scripts\x1B[22m\x1B[0m included in \x1B[32mc@1.0.0\x1B[39m:',
+ ],
+ [' test\n \x1B[2mexit 0\x1B[22m'],
+ [' posttest\n \x1B[2mecho posttest\x1B[22m'],
+ ['\navailable via `\x1B[34mnpm run-script\x1B[39m`:'],
+ [' lorem\n \x1B[2mecho c lorem\x1B[22m'],
+ [''],
+ [
+ '\x1B[0m\x1B[1mLifecycle scripts\x1B[22m\x1B[0m included in \x1B[32md@1.0.0\x1B[39m:',
+ ],
+ [' test\n \x1B[2mexit 0\x1B[22m'],
+ [' posttest\n \x1B[2mecho posttest\x1B[22m'],
+ [''],
+ [
+ '\x1B[0m\x1B[1mLifecycle scripts\x1B[22m\x1B[0m included in \x1B[32me\x1B[39m:',
+ ],
+ [' test\n \x1B[2mexit 0\x1B[22m'],
+ [' start\n \x1B[2mecho start something\x1B[22m'],
+ [''],
+ ])
+ t.end()
+ })
+ })
+
+ t.test('list all scripts --json', t => {
+ config.json = true
+ runScript.execWorkspaces([], [], er => {
+ if (er)
+ throw er
+ t.strictSame(output, [
+ [
+ '{\n' +
+ ' "a": {\n' +
+ ' "glorp": "echo a doing the glerp glop"\n' +
+ ' },\n' +
+ ' "b": {\n' +
+ ' "glorp": "echo b doing the glerp glop"\n' +
+ ' },\n' +
+ ' "c": {\n' +
+ ' "test": "exit 0",\n' +
+ ' "posttest": "echo posttest",\n' +
+ ' "lorem": "echo c lorem"\n' +
+ ' },\n' +
+ ' "d": {\n' +
+ ' "test": "exit 0",\n' +
+ ' "posttest": "echo posttest"\n' +
+ ' },\n' +
+ ' "e": {\n' +
+ ' "test": "exit 0",\n' +
+ ' "start": "echo start something"\n' +
+ ' },\n' +
+ ' "noscripts": {}\n' +
+ '}',
+ ],
+ ])
+ t.end()
+ })
+ })
+
+ t.test('list all scripts --parseable', t => {
+ config.parseable = true
+ runScript.execWorkspaces([], [], er => {
+ if (er)
+ throw er
+ t.strictSame(output, [
+ ['a:glorp:echo a doing the glerp glop'],
+ ['b:glorp:echo b doing the glerp glop'],
+ ['c:test:exit 0'],
+ ['c:posttest:echo posttest'],
+ ['c:lorem:echo c lorem'],
+ ['d:test:exit 0'],
+ ['d:posttest:echo posttest'],
+ ['e:test:exit 0'],
+ ['e:start:echo start something'],
+ ])
+ t.end()
+ })
+ })
+
+ t.test('list no scripts --loglevel=silent', t => {
+ npmlog.level = 'silent'
+ runScript.execWorkspaces([], [], er => {
+ if (er)
+ throw er
+ t.strictSame(output, [])
+ t.end()
+ })
+ })
+
+ t.test('run scripts across all workspaces', t => {
+ runScript.execWorkspaces(['test'], [], er => {
+ if (er)
+ throw er
+
+ t.match(RUN_SCRIPTS, [
+ {
+ path: resolve(npm.localPrefix, 'packages/c'),
+ pkg: { name: 'c', version: '1.0.0' },
+ event: 'test',
+ },
+ {
+ path: resolve(npm.localPrefix, 'packages/c'),
+ pkg: { name: 'c', version: '1.0.0' },
+ event: 'posttest',
+ },
+ {
+ path: resolve(npm.localPrefix, 'packages/d'),
+ pkg: { name: 'd', version: '1.0.0' },
+ event: 'test',
+ },
+ {
+ path: resolve(npm.localPrefix, 'packages/d'),
+ pkg: { name: 'd', version: '1.0.0' },
+ event: 'posttest',
+ },
+ {
+ path: resolve(npm.localPrefix, 'packages/e'),
+ pkg: { name: 'e' },
+ event: 'test',
+ },
+ ])
+ t.end()
+ })
+ })
+
+ t.test('missing scripts in all workspaces', t => {
+ const LOG = []
+ npmlog.error = (err) => {
+ LOG.push(String(err))
+ }
+ runScript.execWorkspaces(['missing-script'], [], er => {
+ t.match(
+ er,
+ /Missing script: missing-script/,
+ 'should throw missing script error'
+ )
+
+ process.exitCode = 0 // clean exit code
+
+ t.match(RUN_SCRIPTS, [])
+ t.strictSame(LOG.map(cleanOutput), [
+ 'Lifecycle script `missing-script` failed with error:',
+ 'Error: Unknown command: "missing-script"',
+ ' in workspace: a@1.0.0',
+ ' at location: {CWD}/test/lib/run-script-workspaces/packages/a',
+ 'Lifecycle script `missing-script` failed with error:',
+ 'Error: Unknown command: "missing-script"',
+ ' in workspace: b@2.0.0',
+ ' at location: {CWD}/test/lib/run-script-workspaces/packages/b',
+ 'Lifecycle script `missing-script` failed with error:',
+ 'Error: Unknown command: "missing-script"',
+ ' in workspace: c@1.0.0',
+ ' at location: {CWD}/test/lib/run-script-workspaces/packages/c',
+ 'Lifecycle script `missing-script` failed with error:',
+ 'Error: Unknown command: "missing-script"',
+ ' in workspace: d@1.0.0',
+ ' at location: {CWD}/test/lib/run-script-workspaces/packages/d',
+ 'Lifecycle script `missing-script` failed with error:',
+ 'Error: Unknown command: "missing-script"',
+ ' in workspace: e',
+ ' at location: {CWD}/test/lib/run-script-workspaces/packages/e',
+ 'Lifecycle script `missing-script` failed with error:',
+ 'Error: Unknown command: "missing-script"',
+ ' in workspace: noscripts@1.0.0',
+ ' at location: {CWD}/test/lib/run-script-workspaces/packages/noscripts',
+ ], 'should log error msgs for each workspace script')
+
+ t.end()
+ })
+ })
+
+ t.test('missing scripts in some workspaces', t => {
+ const LOG = []
+ npmlog.error = (err) => {
+ LOG.push(String(err))
+ }
+ runScript.execWorkspaces(['test'], ['a', 'b', 'c', 'd'], er => {
+ if (er)
+ throw er
+
+ t.match(RUN_SCRIPTS, [])
+ t.strictSame(LOG.map(cleanOutput), [
+ 'Lifecycle script `test` failed with error:',
+ 'Error: Unknown command: "test"',
+ ' in workspace: a@1.0.0',
+ ' at location: {CWD}/test/lib/run-script-workspaces/packages/a',
+ 'Lifecycle script `test` failed with error:',
+ 'Error: Unknown command: "test"',
+ ' in workspace: b@2.0.0',
+ ' at location: {CWD}/test/lib/run-script-workspaces/packages/b',
+ ], 'should log error msgs for each workspace script')
+ t.end()
+ })
+ })
+
+ t.test('no workspaces when filtering by user args', t => {
+ runScript.execWorkspaces([], ['foo', 'bar'], er => {
+ t.equal(
+ er.message,
+ 'No workspaces found:\n --workspace=foo --workspace=bar',
+ 'should throw error msg'
+ )
+ t.end()
+ })
+ })
+
+ t.test('no workspaces', t => {
+ const _prevPrefix = npm.localPrefix
+ npm.localPrefix = t.testdir({
+ 'package.json': JSON.stringify({
+ name: 'foo',
+ version: '1.0.0',
+ }),
+ })
+
+ runScript.execWorkspaces([], [], er => {
+ t.match(er, /No workspaces found!/, 'should throw error msg')
+ npm.localPrefix = _prevPrefix
+ t.end()
+ })
+ })
+
+ t.test('single failed workspace run', t => {
+ const RunScript = requireInject('../../lib/run-script.js', {
+ '@npmcli/run-script': () => {
+ throw new Error('err')
+ },
+ npmlog,
+ '../../lib/utils/is-windows-shell.js': false,
+ })
+ const runScript = new RunScript(npm)
+
+ runScript.execWorkspaces(['test'], ['c'], er => {
+ t.ok('should complete running all targets')
+ process.exitCode = 0 // clean up exit code
+ t.end()
+ })
+ })
+
+ t.test('failed workspace run with succeeded runs', t => {
+ const RunScript = requireInject('../../lib/run-script.js', {
+ '@npmcli/run-script': async opts => {
+ if (opts.pkg.name === 'a')
+ throw new Error('ERR')
+
+ RUN_SCRIPTS.push(opts)
+ },
+ npmlog,
+ '../../lib/utils/is-windows-shell.js': false,
+ })
+ const runScript = new RunScript(npm)
+
+ runScript.execWorkspaces(['glorp'], ['a', 'b'], er => {
+ t.match(RUN_SCRIPTS, [
+ {
+ path: resolve(npm.localPrefix, 'packages/b'),
+ pkg: { name: 'b', version: '2.0.0' },
+ event: 'glorp',
+ },
+ ])
+
+ process.exitCode = 0 // clean up exit code
+ t.end()
+ })
+ })
+
+ t.end()
+})
diff --git a/test/lib/utils/did-you-mean.js b/test/lib/utils/did-you-mean.js
index 48b6d4027..898806aa1 100644
--- a/test/lib/utils/did-you-mean.js
+++ b/test/lib/utils/did-you-mean.js
@@ -7,25 +7,37 @@ t.test('did-you-mean', t => {
npm.load(err => {
t.notOk(err)
t.test('nistall', async t => {
- const result = await dym(npm, 'nistall')
+ const result = await dym(npm, npm.localPrefix, 'nistall')
t.match(result, 'Unknown command')
t.match(result, 'npm install')
})
t.test('sttest', async t => {
- const result = await dym(npm, 'sttest')
+ const result = await dym(npm, npm.localPrefix, 'sttest')
t.match(result, 'Unknown command')
t.match(result, 'npm test')
t.match(result, 'npm run posttest')
})
t.test('npz', async t => {
- const result = await dym(npm, 'npxx')
+ const result = await dym(npm, npm.localPrefix, 'npxx')
t.match(result, 'Unknown command')
t.match(result, 'npm exec npx')
})
t.test('qwuijbo', async t => {
- const result = await dym(npm, 'qwuijbo')
+ const result = await dym(npm, npm.localPrefix, 'qwuijbo')
t.match(result, 'Unknown command')
})
t.end()
})
})
+
+t.test('missing bin and script properties', async t => {
+ const path = t.testdir({
+ 'package.json': JSON.stringify({
+ name: 'missing-bin',
+ }),
+ })
+
+ const result = await dym(npm, path, 'nistall')
+ t.match(result, 'Unknown command')
+ t.match(result, 'npm install')
+})
diff --git a/test/lib/utils/lifecycle-cmd.js b/test/lib/utils/lifecycle-cmd.js
index 3e3a7da43..862c87a8e 100644
--- a/test/lib/utils/lifecycle-cmd.js
+++ b/test/lib/utils/lifecycle-cmd.js
@@ -10,6 +10,7 @@ const npm = {
},
}
t.test('create a lifecycle command', t => {
+ t.plan(5)
class TestStage extends LifecycleCmd {
static get name () {
return 'test-stage'
@@ -20,6 +21,9 @@ t.test('create a lifecycle command', t => {
cmd.exec(['some', 'args'], (er, result) => {
t.same(runArgs, ['test-stage', 'some', 'args'])
t.strictSame(result, 'called npm.commands.run')
- t.end()
+ })
+ cmd.execWorkspaces(['some', 'args'], [], (er, result) => {
+ t.same(runArgs, ['test-stage', 'some', 'args'])
+ t.strictSame(result, 'called npm.commands.run')
})
})