diff --git a/lib/commands/run.js b/lib/commands/run.js index d89cb4d93bb7f..753fbb450a69e 100644 --- a/lib/commands/run.js +++ b/lib/commands/run.js @@ -14,6 +14,7 @@ class RunScript extends BaseCommand { 'ignore-scripts', 'foreground-scripts', 'script-shell', + 'fail-fast' ] static name = 'run' @@ -49,6 +50,7 @@ class RunScript extends BaseCommand { async execWorkspaces (args) { await this.setWorkspaces() + const shouldFailFast = this.npm.config.get('fail-fast') ?? false; const ws = [...this.workspaces.entries()] for (const [workspace, path] of ws) { @@ -80,6 +82,9 @@ class RunScript extends BaseCommand { if (!last) { output.error('') } + if (shouldFailFast) { + return; + } } } } diff --git a/tap-snapshots/test/lib/commands/config.js.test.cjs b/tap-snapshots/test/lib/commands/config.js.test.cjs index bc0f406166a9f..c4f09d746a0cc 100644 --- a/tap-snapshots/test/lib/commands/config.js.test.cjs +++ b/tap-snapshots/test/lib/commands/config.js.test.cjs @@ -48,6 +48,7 @@ exports[`test/lib/commands/config.js TAP config list --json > output matches sna "engine-strict": false, "expect-result-count": null, "expect-results": null, + "fail-fast": false, "fetch-retries": 2, "fetch-retry-factor": 10, "fetch-retry-maxtimeout": 60000, @@ -214,6 +215,7 @@ editor = "{EDITOR}" engine-strict = false expect-result-count = null expect-results = null +fail-fast = false fetch-retries = 2 fetch-retry-factor = 10 fetch-retry-maxtimeout = 60000 diff --git a/tap-snapshots/test/lib/commands/run.js.test.cjs b/tap-snapshots/test/lib/commands/run.js.test.cjs index 367415db8fe07..5f2ce8e75aa71 100644 --- a/tap-snapshots/test/lib/commands/run.js.test.cjs +++ b/tap-snapshots/test/lib/commands/run.js.test.cjs @@ -50,6 +50,14 @@ Scripts available in x@1.2.3 via \`npm run\`: echo doing the glerp glop ` +exports[`test/lib/commands/run.js TAP workspaces failed workspace run fails fast > should log error msgs for each workspace script 1`] = ` +Lifecycle script \`glorp\` failed with error: +code ERR +workspace a@1.0.0 +location {CWD}/prefix/packages/a +ERR +` + exports[`test/lib/commands/run.js TAP workspaces failed workspace run with succeeded runs > should log error msgs for each workspace script 1`] = ` Lifecycle script \`glorp\` failed with error: code ERR diff --git a/tap-snapshots/test/lib/docs.js.test.cjs b/tap-snapshots/test/lib/docs.js.test.cjs index da8009949f716..a7a5f6eee038f 100644 --- a/tap-snapshots/test/lib/docs.js.test.cjs +++ b/tap-snapshots/test/lib/docs.js.test.cjs @@ -556,6 +556,23 @@ true (expect some results) or false (expect no results). This config can not be used with: \`expect-result-count\` +#### \`fail-fast\` + +* Default: false +* Type: Boolean + +Designed to be used with the \`--workspaces\` or multiple \`--workspace\` +option. + +If true, when executing commands with the \`run\` across a workspace. Rather +than the default behavior of running the command in all packages in the +workspace and logging the failure, on the first failing package, exit with +the error code of the failing command. This is helpful if packages have +relationship and the work should not continue if the previous package +failed. + + + #### \`fetch-retries\` * Default: 2 @@ -2129,6 +2146,7 @@ Array [ "engine-strict", "expect-result-count", "expect-results", + "fail-fast", "fetch-retries", "fetch-retry-factor", "fetch-retry-maxtimeout", @@ -2389,6 +2407,7 @@ exports[`test/lib/docs.js TAP config > keys that are not flattened 1`] = ` Array [ "expect-result-count", "expect-results", + "fail-fast", "init-author-email", "init-author-name", "init-author-url", @@ -4038,7 +4057,7 @@ npm run [-- ] Options: [-w|--workspace [-w|--workspace ...]] [--workspaces] [--include-workspace-root] [--if-present] [--ignore-scripts] -[--foreground-scripts] [--script-shell ] +[--foreground-scripts] [--script-shell ] [--fail-fast] aliases: run-script, rum, urn @@ -4057,6 +4076,7 @@ aliases: run-script, rum, urn #### \`ignore-scripts\` #### \`foreground-scripts\` #### \`script-shell\` +#### \`fail-fast\` ` exports[`test/lib/docs.js TAP usage sbom > must match snapshot 1`] = ` diff --git a/test/lib/commands/run.js b/test/lib/commands/run.js index 2fd58b356dc40..3ef7829c3bcf1 100644 --- a/test/lib/commands/run.js +++ b/test/lib/commands/run.js @@ -759,4 +759,26 @@ t.test('workspaces', async t => { }, ]) }) + + t.test('failed workspace run fails fast', async t => { + const { cleanLogs, RUN_SCRIPTS, prefix } = await mockWorkspaces(t, { + runScript: (opts) => { + if (opts.pkg.name === 'a') { + throw new Error('ERR') + } + }, + exec: ['glorp'], + workspaces: ['a', 'b'], + 'fail-fast': true + }) + + t.matchSnapshot( + cleanLogs(), + 'should log error msgs for each workspace script' + ) + + t.match(RUN_SCRIPTS(), []) + }) }) + + diff --git a/workspaces/config/lib/definitions/definitions.js b/workspaces/config/lib/definitions/definitions.js index d703840e0e928..bf1bcd6432adf 100644 --- a/workspaces/config/lib/definitions/definitions.js +++ b/workspaces/config/lib/definitions/definitions.js @@ -623,6 +623,20 @@ const definitions = { Can be either true (expect some results) or false (expect no results). `, }), + 'fail-fast': new Definition('fail-fast', { + default: false, + type: Boolean, + description: ` + Designed to be used with the \`--workspaces\` or multiple \`--workspace\` option. + + If true, when executing commands with the \`run\` across a workspace. + Rather than the default behavior of running the command in all packages + in the workspace and logging the failure, on the first failing package, + exit with the error code of the failing command. This is helpful if + packages have relationship and the work should not continue if the + previous package failed. + ` + }), 'fetch-retries': new Definition('fetch-retries', { default: 2, type: Number,