From 6228bd7a3133da1e763e9c42bd08372f0822e525 Mon Sep 17 00:00:00 2001 From: Brandon Lax Date: Sun, 25 May 2025 10:59:44 -0400 Subject: [PATCH 1/2] Add fail-fast flag for working with workspaces --- lib/commands/run.js | 5 +++++ .../test/lib/commands/config.js.test.cjs | 2 ++ .../test/lib/commands/run.js.test.cjs | 8 +++++++ tap-snapshots/test/lib/docs.js.test.cjs | 15 ++++++++++++- test/lib/commands/run.js | 22 +++++++++++++++++++ .../config/lib/definitions/definitions.js | 8 +++++++ 6 files changed, 59 insertions(+), 1 deletion(-) 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..5ceb7b19a78c0 100644 --- a/tap-snapshots/test/lib/docs.js.test.cjs +++ b/tap-snapshots/test/lib/docs.js.test.cjs @@ -556,6 +556,16 @@ 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 + +Tells npm when executing a npm script within a workspace, to fail on the +first package failing. Defailt behavior is to continue executing. + + + #### \`fetch-retries\` * Default: 2 @@ -2129,6 +2139,7 @@ Array [ "engine-strict", "expect-result-count", "expect-results", + "fail-fast", "fetch-retries", "fetch-retry-factor", "fetch-retry-maxtimeout", @@ -2389,6 +2400,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 +4050,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 +4069,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..bfb37b29b8693 100644 --- a/workspaces/config/lib/definitions/definitions.js +++ b/workspaces/config/lib/definitions/definitions.js @@ -623,6 +623,14 @@ 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: ` + Tells npm when executing a npm script within a workspace, + to fail on the first package failing. Defailt behavior is to continue executing. + ` + }), 'fetch-retries': new Definition('fetch-retries', { default: 2, type: Number, From 5642a12feb4199613b1f021e71f8b56e42a55c78 Mon Sep 17 00:00:00 2001 From: Brandon Lax Date: Sun, 25 May 2025 11:26:09 -0400 Subject: [PATCH 2/2] Update documentation --- tap-snapshots/test/lib/docs.js.test.cjs | 11 +++++++++-- workspaces/config/lib/definitions/definitions.js | 10 ++++++++-- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/tap-snapshots/test/lib/docs.js.test.cjs b/tap-snapshots/test/lib/docs.js.test.cjs index 5ceb7b19a78c0..a7a5f6eee038f 100644 --- a/tap-snapshots/test/lib/docs.js.test.cjs +++ b/tap-snapshots/test/lib/docs.js.test.cjs @@ -561,8 +561,15 @@ This config can not be used with: \`expect-result-count\` * Default: false * Type: Boolean -Tells npm when executing a npm script within a workspace, to fail on the -first package failing. Defailt behavior is to continue executing. +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. diff --git a/workspaces/config/lib/definitions/definitions.js b/workspaces/config/lib/definitions/definitions.js index bfb37b29b8693..bf1bcd6432adf 100644 --- a/workspaces/config/lib/definitions/definitions.js +++ b/workspaces/config/lib/definitions/definitions.js @@ -627,8 +627,14 @@ const definitions = { default: false, type: Boolean, description: ` - Tells npm when executing a npm script within a workspace, - to fail on the first package failing. Defailt behavior is to continue executing. + 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', {