diff --git a/.yarn/versions/d07cb1af.yml b/.yarn/versions/d07cb1af.yml new file mode 100644 index 000000000000..90ec916dc491 --- /dev/null +++ b/.yarn/versions/d07cb1af.yml @@ -0,0 +1,23 @@ +releases: + "@yarnpkg/cli": patch + "@yarnpkg/plugin-workspace-tools": patch + +declined: + - "@yarnpkg/plugin-compat" + - "@yarnpkg/plugin-constraints" + - "@yarnpkg/plugin-dlx" + - "@yarnpkg/plugin-essentials" + - "@yarnpkg/plugin-init" + - "@yarnpkg/plugin-interactive-tools" + - "@yarnpkg/plugin-nm" + - "@yarnpkg/plugin-npm-cli" + - "@yarnpkg/plugin-pack" + - "@yarnpkg/plugin-patch" + - "@yarnpkg/plugin-pnp" + - "@yarnpkg/plugin-pnpm" + - "@yarnpkg/plugin-stage" + - "@yarnpkg/plugin-typescript" + - "@yarnpkg/plugin-version" + - "@yarnpkg/builder" + - "@yarnpkg/core" + - "@yarnpkg/doctor" diff --git a/packages/acceptance-tests/pkg-tests-core/sources/utils/tests.ts b/packages/acceptance-tests/pkg-tests-core/sources/utils/tests.ts index b7a19c7aee95..582521f82a63 100644 --- a/packages/acceptance-tests/pkg-tests-core/sources/utils/tests.ts +++ b/packages/acceptance-tests/pkg-tests-core/sources/utils/tests.ts @@ -29,16 +29,18 @@ const staticServer = serveStatic(npath.fromPortablePath(require(`pkg-tests-fixtu const TEST_MAJOR = process.env.TEST_MAJOR ? parseInt(process.env.TEST_MAJOR, 10) - : null; + : 4; -function isAtLeastMajor(major: number) { - return TEST_MAJOR !== null && TEST_MAJOR >= major; +function majorCheck(test: (major: number) => boolean) { + return TEST_MAJOR === null || test(TEST_MAJOR); } export const FEATURE_CHECKS = { - jsonLockfile: isAtLeastMajor(5), - prologConstraints: !isAtLeastMajor(5), - mergeConflictTheirs: isAtLeastMajor(5), + forEachWorktree: majorCheck(major => major <= 4), + forEachVerboseDone: majorCheck(major => major >= 5), + jsonLockfile: majorCheck(major => major >= 5), + prologConstraints: majorCheck(major => major <= 4), + mergeConflictTheirs: majorCheck(major => major >= 5), } as const; // Testing things inside a big-endian container takes forever diff --git a/packages/acceptance-tests/pkg-tests-specs/sources/commands/workspaces/__snapshots__/foreach.test.js.snap b/packages/acceptance-tests/pkg-tests-specs/sources/commands/workspaces/__snapshots__/foreach.test.js.snap deleted file mode 100644 index 8353b9034d86..000000000000 --- a/packages/acceptance-tests/pkg-tests-specs/sources/commands/workspaces/__snapshots__/foreach.test.js.snap +++ /dev/null @@ -1,362 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Commands workspace foreach --since --recursive runs on workspaces changed and their dependents 1`] = ` -{ - "code": 0, - "stderr": "", - "stdout": "Test Workspace A -Test Workspace B -Test Workspace D -Test Workspace E -Test Workspace F -Test Workspace C -Done -", -} -`; - -exports[`Commands workspace foreach --since runs on no workspaces if there are no staged or unstaged changes on the default branch 1`] = ` -{ - "code": 0, - "stderr": "", - "stdout": "Done -", -} -`; - -exports[`Commands workspace foreach --since runs on no workspaces if there have been no changes 1`] = ` -{ - "code": 0, - "stderr": "", - "stdout": "Done -", -} -`; - -exports[`Commands workspace foreach --since runs on workspaces changed since branching from the default branch 1`] = ` -{ - "code": 0, - "stderr": "", - "stdout": "Test Workspace B -Test Workspace C -Done -", -} -`; - -exports[`Commands workspace foreach --since runs on workspaces changed since commit 1`] = ` -{ - "code": 0, - "stderr": "", - "stdout": "Test Workspace B -Test Workspace C -Done -", -} -`; - -exports[`Commands workspace foreach --since runs only on changed workspaces 1`] = ` -{ - "code": 0, - "stderr": "", - "stdout": "Test Workspace A -Done -", -} -`; - -exports[`Commands workspace foreach can run on public workspaces only 1`] = ` -{ - "code": 0, - "stderr": "", - "stdout": "Test Workspace A -Test Workspace C -Done -", -} -`; - -exports[`Commands workspace foreach should execute 'node' command 1`] = ` -{ - "code": 0, - "orderedStdout": [ - "workspace-c", - "workspace-d", - "workspace-e", - "workspace-f", - "workspace-g", - ], - "stderr": "", -} -`; - -exports[`Commands workspace foreach should handle global scripts getting downgraded to a normal script 1`] = ` -{ - "code": 0, - "stderr": "", - "stdout": "root workspace -Test Workspace G -Done -", -} -`; - -exports[`Commands workspace foreach should include dependencies if using --recursive 1`] = ` -{ - "code": 0, - "stderr": "", - "stdout": "Test Workspace A -Test Workspace C -Test Workspace B -Done -", -} -`; - -exports[`Commands workspace foreach should include dependencies of workspaces matching the from filter if using --from and --recursive 1`] = ` -{ - "code": 0, - "stderr": "", - "stdout": "Test Workspace A -Test Workspace G -Test Workspace C -Test Workspace B -Done -", -} -`; - -exports[`Commands workspace foreach should never run the scripts on workspaces that match the --exclude list 1`] = ` -{ - "code": 0, - "stderr": "", - "stdout": "[workspace-c]: Process started -[workspace-c]: Test Workspace C -[workspace-c]: Process exited (exit code 0) - -[workspace-d]: Process started -[workspace-d]: Test Workspace D -[workspace-d]: Process exited (exit code 0) - -[workspace-e]: Process started -[workspace-e]: Test Workspace E -[workspace-e]: Process exited (exit code 0) - -[workspace-f]: Process started -[workspace-f]: Test Workspace F -[workspace-f]: Process exited (exit code 0) - -[workspace-g]: Process started -[workspace-g]: Test Workspace G -[workspace-g]: Process exited (exit code 0) -Done -", -} -`; - -exports[`Commands workspace foreach should not fall into endless loop if foreach cmd is the same as lifecycle script name 1`] = ` -{ - "code": 0, - "stderr": "", - "stdout": "Test Workspace A -Test Workspace B -Test Workspace C -Test Workspace D -Test Workspace E -Test Workspace F -Test Workspace G -Done -", -} -`; - -exports[`Commands workspace foreach should not include the prefix or a ➤ character when run with --no-verbose 1`] = ` -{ - "code": 0, - "stderr": "", - "stdout": "Test Workspace A -Test Workspace B -Test Workspace C -Test Workspace D -Test Workspace E -Test Workspace F -Test Workspace G -Done -", -} -`; - -exports[`Commands workspace foreach should only run the scripts on workspaces that match the --include list 1`] = ` -{ - "code": 0, - "stderr": "", - "stdout": "[workspace-a]: Process started -[workspace-a]: Test Workspace A -[workspace-a]: Process exited (exit code 0) - -[workspace-b]: Process started -[workspace-b]: Test Workspace B -[workspace-b]: Process exited (exit code 0) -Done -", -} -`; - -exports[`Commands workspace foreach should only run the scripts on workspaces that match the --include path list with globs 1`] = ` -{ - "code": 0, - "stderr": "", - "stdout": "[workspace-c]: Process started -[workspace-c]: Test Workspace C -[workspace-c]: Process exited (exit code 0) - -[workspace-d]: Process started -[workspace-d]: Test Workspace D -[workspace-d]: Process exited (exit code 0) - -[workspace-e]: Process started -[workspace-e]: Test Workspace E -[workspace-e]: Process exited (exit code 0) - -[workspace-f]: Process started -[workspace-f]: Test Workspace F -[workspace-f]: Process exited (exit code 0) - -[workspace-g]: Process started -[workspace-g]: Test Workspace G -[workspace-g]: Process exited (exit code 0) -Done -", -} -`; - -exports[`Commands workspace foreach should prefix the output and include timing information when run with -vv (two verbose levels) 1`] = ` -{ - "code": 0, - "stderr": "", - "stdout": "[workspace-a]: Process started -[workspace-a]: Test Workspace A -[workspace-a]: Process exited (exit code 0) - -[workspace-b]: Process started -[workspace-b]: Test Workspace B -[workspace-b]: Process exited (exit code 0) - -[workspace-c]: Process started -[workspace-c]: Test Workspace C -[workspace-c]: Process exited (exit code 0) - -[workspace-d]: Process started -[workspace-d]: Test Workspace D -[workspace-d]: Process exited (exit code 0) - -[workspace-e]: Process started -[workspace-e]: Test Workspace E -[workspace-e]: Process exited (exit code 0) - -[workspace-f]: Process started -[workspace-f]: Test Workspace F -[workspace-f]: Process exited (exit code 0) - -[workspace-g]: Process started -[workspace-g]: Test Workspace G -[workspace-g]: Process exited (exit code 0) -Done -", -} -`; - -exports[`Commands workspace foreach should prefix the output when run with one --verbose 1`] = ` -{ - "code": 0, - "stderr": "", - "stdout": "[workspace-a]: Test Workspace A -[workspace-b]: Test Workspace B -[workspace-c]: Test Workspace C -[workspace-d]: Test Workspace D -[workspace-e]: Test Workspace E -[workspace-f]: Test Workspace F -[workspace-g]: Test Workspace G -Done -", -} -`; - -exports[`Commands workspace foreach should run execute global scripts even on workspaces that don't declare them 1`] = ` -{ - "code": 0, - "stderr": "", - "stdout": "One execution -One execution -One execution -One execution -One execution -One execution -One execution -One execution -Done -", -} -`; - -exports[`Commands workspace foreach should run on current and descendant workspaces when --worktree is set 1`] = ` -{ - "code": 0, - "stderr": "", - "stdout": "Test Workspace C -Test Workspace D -Test Workspace F -Test Workspace G -Test Workspace E -Done -", -} -`; - -exports[`Commands workspace foreach should run scripts in parallel but following the topological order when run with --parallel --topological 1`] = ` -[ - "C", - "B", - "D", - "E", - "F", -] -`; - -exports[`Commands workspace foreach should run scripts in parallel but following the topological order when run with --parallel --topological 2`] = ` -{ - "code": 0, - "stderr": "", -} -`; - -exports[`Commands workspace foreach should run set INIT_CWD to each individual workspace cwd even with global scripts 1`] = ` -{ - "code": 0, - "stderr": "", - "stdout": " -packages/workspace-a -packages/workspace-b -packages/workspace-c -packages/workspace-c/packages/workspace-d -packages/workspace-c/packages/workspace-d/packages/workspace-e -packages/workspace-c/packages/workspace-f -packages/workspace-c/packages/workspace-g -Done -", -} -`; - -exports[`Commands workspace foreach should start all the processes at once when --jobs is unlimited 1`] = ` -{ - "code": 0, - "first7Lines": "[workspace-a]: Process started -[workspace-b]: Process started -[workspace-c]: Process started -[workspace-d]: Process started -[workspace-e]: Process started -[workspace-f]: Process started -[workspace-g]: Process started", - "stderr": "", -} -`; diff --git a/packages/acceptance-tests/pkg-tests-specs/sources/commands/workspaces/foreach.test.js b/packages/acceptance-tests/pkg-tests-specs/sources/commands/workspaces/foreach.test.js index fe405426a710..bcab1b9d1f7c 100644 --- a/packages/acceptance-tests/pkg-tests-specs/sources/commands/workspaces/foreach.test.js +++ b/packages/acceptance-tests/pkg-tests-specs/sources/commands/workspaces/foreach.test.js @@ -1,8 +1,15 @@ +const {npath, ppath, xfs} = require(`@yarnpkg/fslib`); + const { exec: {execFile}, fs: {writeJson, writeFile}, + tests: {testIf, FEATURE_CHECKS}, } = require(`pkg-tests-core`); +const forEachVerboseDone = FEATURE_CHECKS.forEachVerboseDone + ? [] + : [`Done\n`]; + async function setupWorkspaces(path) { await writeFile(`${path}/mutexes/workspace-a`, ``); await writeFile(`${path}/mutexes/workspace-b`, ``); @@ -92,7 +99,8 @@ async function setupWorkspaces(path) { describe(`Commands`, () => { describe(`workspace foreach`, () => { - test( + testIf( + `forEachWorktree`, `should run on current and descendant workspaces when --worktree is set`, makeTemporaryEnv( { @@ -103,12 +111,24 @@ describe(`Commands`, () => { await setupWorkspaces(path); await run(`install`); - await expect(run(`workspaces`, `foreach`, `--worktree`, `run`, `print`, {cwd: `${path}/packages/workspace-c`})).resolves.toMatchSnapshot(); + await expect(run(`workspaces`, `foreach`, `--worktree`, `run`, `print`, {cwd: `${path}/packages/workspace-c`})).resolves.toEqual({ + code: 0, + stderr: ``, + stdout: [ + `Test Workspace C\n`, + `Test Workspace D\n`, + `Test Workspace E\n`, + `Test Workspace F\n`, + `Test Workspace G\n`, + ...forEachVerboseDone, + ].join(``), + }); }, ), ); - test( + testIf( + `forEachWorktree`, `should support self referencing workspaces field`, makeTemporaryEnv( { @@ -118,13 +138,14 @@ describe(`Commands`, () => { async ({path, run}) => { await run(`install`); - await expect(run(`workspaces`, `foreach`, `--worktree`, `exec`, `echo`, `42`)).resolves.toMatchObject( - { - code: 0, - stdout: `42\nDone\n`, - stderr: ``, - }, - ); + await expect(run(`workspaces`, `foreach`, `--worktree`, `exec`, `echo`, `42`)).resolves.toMatchObject({ + code: 0, + stderr: ``, + stdout: [ + `42\n`, + ...forEachVerboseDone, + ].join(``), + }); }, ), ); @@ -134,21 +155,27 @@ describe(`Commands`, () => { makeTemporaryEnv( { private: true, - workspaces: [`packages/*`], + workspaces: [`packages/*`, `packages/*/packages/*`], }, async ({path, run}) => { await setupWorkspaces(path); await run(`install`); - const {code, stdout, stderr} = await run(`workspaces`, `foreach`, `--worktree`, `--parallel`, `--topological`, `node`, `-p`, `require("./package.json").name ?? "root"`, {cwd: `${path}/packages/workspace-c`}); - - const orderedStdout = stdout.trim().split(`\n`); - expect(orderedStdout.pop()).toContain(`Done`); - - // The exact order is unstable, so just make sure all the workspaces we expect to be there, are. - orderedStdout.sort(); - - expect({code, orderedStdout, stderr}).toMatchSnapshot(); + await expect(run(`workspaces`, `foreach`, `--all`, `node`, `-p`, `require("./package.json").name ?? "root"`)).resolves.toMatchObject({ + code: 0, + stderr: ``, + stdout: [ + `root\n`, + `workspace-a\n`, + `workspace-b\n`, + `workspace-c\n`, + `workspace-d\n`, + `workspace-e\n`, + `workspace-f\n`, + `workspace-g\n`, + ...forEachVerboseDone, + ].join(``), + }); }, ), ); @@ -158,7 +185,7 @@ describe(`Commands`, () => { makeTemporaryEnv( { private: true, - workspaces: [`packages/*`], + workspaces: [`packages/*`, `packages/*/packages/*`], }, async ({path, run}) => { await setupWorkspaces(path); @@ -171,8 +198,9 @@ describe(`Commands`, () => { let isInterlaced = false; - // Expect Done on the last line - expect(lines.pop()).toContain(`Done`); + if (!FEATURE_CHECKS.forEachVerboseDone) + expect(lines.pop()).toEqual(`Done`); + expect(lines.length).toBeGreaterThan(0); expect(code).toBe(0); expect(stderr).toBe(``); @@ -209,9 +237,15 @@ describe(`Commands`, () => { // A and G have the same precedence expect([order[0], order[1]]).toEqual(expect.arrayContaining([`A`, `G`])); - expect(order.slice(2)).toMatchSnapshot(); + expect(order.slice(2)).toEqual([ + `C`, + `B`, + `D`, + `E`, + `F`, + ]); - await expect({code, stderr}).toMatchSnapshot(); + expect({code, stderr}).toEqual({code: 0, stderr: ``}); }, ), ); @@ -228,7 +262,20 @@ describe(`Commands`, () => { await setupWorkspaces(path); await run(`install`); - await expect(run(`workspaces`, `foreach`, `--all`, `--verbose`, `run`, `print`)).resolves.toMatchSnapshot(); + await expect(run(`workspaces`, `foreach`, `--all`, `--verbose`, `run`, `print`)).resolves.toEqual({ + code: 0, + stderr: ``, + stdout: [ + `[workspace-a]: Test Workspace A\n`, + `[workspace-b]: Test Workspace B\n`, + `[workspace-c]: Test Workspace C\n`, + `[workspace-d]: Test Workspace D\n`, + `[workspace-e]: Test Workspace E\n`, + `[workspace-f]: Test Workspace F\n`, + `[workspace-g]: Test Workspace G\n`, + ...forEachVerboseDone, + ].join(``), + }); }, ), ); @@ -244,7 +291,40 @@ describe(`Commands`, () => { await setupWorkspaces(path); await run(`install`); - await expect(run(`workspaces`, `foreach`, `--all`, `-vv`, `run`, `print`)).resolves.toMatchSnapshot(); + await expect(run(`workspaces`, `foreach`, `--all`, `-vv`, `run`, `print`)).resolves.toEqual({ + code: 0, + stderr: ``, + stdout: [ + `[workspace-a]: Process started\n`, + `[workspace-a]: Test Workspace A\n`, + `[workspace-a]: Process exited (exit code 0)\n`, + `\n`, + `[workspace-b]: Process started\n`, + `[workspace-b]: Test Workspace B\n`, + `[workspace-b]: Process exited (exit code 0)\n`, + `\n`, + `[workspace-c]: Process started\n`, + `[workspace-c]: Test Workspace C\n`, + `[workspace-c]: Process exited (exit code 0)\n`, + `\n`, + `[workspace-d]: Process started\n`, + `[workspace-d]: Test Workspace D\n`, + `[workspace-d]: Process exited (exit code 0)\n`, + `\n`, + `[workspace-e]: Process started\n`, + `[workspace-e]: Test Workspace E\n`, + `[workspace-e]: Process exited (exit code 0)\n`, + `\n`, + `[workspace-f]: Process started\n`, + `[workspace-f]: Test Workspace F\n`, + `[workspace-f]: Process exited (exit code 0)\n`, + `\n`, + `[workspace-g]: Process started\n`, + `[workspace-g]: Test Workspace G\n`, + `[workspace-g]: Process exited (exit code 0)\n`, + ...forEachVerboseDone, + ].join(``), + }); }, ), ); @@ -260,7 +340,20 @@ describe(`Commands`, () => { await setupWorkspaces(path); await run(`install`); - await expect(run(`workspaces`, `foreach`, `--all`, `--no-verbose`, `run`, `print`)).resolves.toMatchSnapshot(); + await expect(run(`workspaces`, `foreach`, `--all`, `--no-verbose`, `run`, `print`)).resolves.toEqual({ + code: 0, + stderr: ``, + stdout: [ + `Test Workspace A\n`, + `Test Workspace B\n`, + `Test Workspace C\n`, + `Test Workspace D\n`, + `Test Workspace E\n`, + `Test Workspace F\n`, + `Test Workspace G\n`, + ...forEachVerboseDone, + ].join(``), + }); }, ), ); @@ -276,7 +369,20 @@ describe(`Commands`, () => { await setupWorkspaces(path); await run(`install`); - await expect(run(`workspaces`, `foreach`, `--all`, `-vv`, `--include`, `workspace-a`, `--include`, `packages/workspace-b`, `run`, `print`)).resolves.toMatchSnapshot(); + await expect(run(`workspaces`, `foreach`, `--all`, `-vv`, `--include`, `workspace-a`, `--include`, `packages/workspace-b`, `run`, `print`)).resolves.toEqual({ + code: 0, + stderr: ``, + stdout: [ + `[workspace-a]: Process started\n`, + `[workspace-a]: Test Workspace A\n`, + `[workspace-a]: Process exited (exit code 0)\n`, + `\n`, + `[workspace-b]: Process started\n`, + `[workspace-b]: Test Workspace B\n`, + `[workspace-b]: Process exited (exit code 0)\n`, + ...forEachVerboseDone, + ].join(``), + }); }, ), ); @@ -292,7 +398,32 @@ describe(`Commands`, () => { await setupWorkspaces(path); await run(`install`); - await expect(run(`workspaces`, `foreach`, `--all`, `-vv`, `--include`, `packages/workspace-c/**`, `run`, `print`)).resolves.toMatchSnapshot(); + await expect(run(`workspaces`, `foreach`, `--all`, `-vv`, `--include`, `packages/workspace-c/**`, `run`, `print`)).resolves.toEqual({ + code: 0, + stderr: ``, + stdout: [ + `[workspace-c]: Process started\n`, + `[workspace-c]: Test Workspace C\n`, + `[workspace-c]: Process exited (exit code 0)\n`, + `\n`, + `[workspace-d]: Process started\n`, + `[workspace-d]: Test Workspace D\n`, + `[workspace-d]: Process exited (exit code 0)\n`, + `\n`, + `[workspace-e]: Process started\n`, + `[workspace-e]: Test Workspace E\n`, + `[workspace-e]: Process exited (exit code 0)\n`, + `\n`, + `[workspace-f]: Process started\n`, + `[workspace-f]: Test Workspace F\n`, + `[workspace-f]: Process exited (exit code 0)\n`, + `\n`, + `[workspace-g]: Process started\n`, + `[workspace-g]: Test Workspace G\n`, + `[workspace-g]: Process exited (exit code 0)\n`, + ...forEachVerboseDone, + ].join(``), + }); }, ), ); @@ -308,7 +439,32 @@ describe(`Commands`, () => { await setupWorkspaces(path); await run(`install`); - await expect(run(`workspaces`, `foreach`, `--all`, `-vv`, `--exclude`, `workspace-a`, `--exclude`, `packages/workspace-b`, `run`, `print`)).resolves.toMatchSnapshot(); + await expect(run(`workspaces`, `foreach`, `--all`, `-vv`, `--exclude`, `workspace-a`, `--exclude`, `packages/workspace-b`, `run`, `print`)).resolves.toEqual({ + code: 0, + stderr: ``, + stdout: [ + `[workspace-c]: Process started\n`, + `[workspace-c]: Test Workspace C\n`, + `[workspace-c]: Process exited (exit code 0)\n`, + `\n`, + `[workspace-d]: Process started\n`, + `[workspace-d]: Test Workspace D\n`, + `[workspace-d]: Process exited (exit code 0)\n`, + `\n`, + `[workspace-e]: Process started\n`, + `[workspace-e]: Test Workspace E\n`, + `[workspace-e]: Process exited (exit code 0)\n`, + `\n`, + `[workspace-f]: Process started\n`, + `[workspace-f]: Test Workspace F\n`, + `[workspace-f]: Process exited (exit code 0)\n`, + `\n`, + `[workspace-g]: Process started\n`, + `[workspace-g]: Test Workspace G\n`, + `[workspace-g]: Process exited (exit code 0)\n`, + ...forEachVerboseDone, + ].join(``), + }); }, ), ); @@ -327,7 +483,20 @@ describe(`Commands`, () => { await setupWorkspaces(path); await run(`install`); - await expect(run(`print`)).resolves.toMatchSnapshot(); + await expect(run(`print`)).resolves.toEqual({ + code: 0, + stderr: ``, + stdout: [ + `Test Workspace A\n`, + `Test Workspace B\n`, + `Test Workspace C\n`, + `Test Workspace D\n`, + `Test Workspace E\n`, + `Test Workspace F\n`, + `Test Workspace G\n`, + ...forEachVerboseDone, + ].join(``), + }); }, ), ); @@ -344,7 +513,7 @@ describe(`Commands`, () => { await setupWorkspaces(path); await run(`install`); - await expect(run(`workspaces`, `foreach`, `--all`, `--jobs`, `2`, `run`, `print`)).rejects.toThrowError(/parallel must be set/); + await expect(run(`workspaces`, `foreach`, `--all`, `--jobs`, `2`, `run`, `print`)).rejects.toThrow(/parallel must be set/); }, ), ); @@ -360,7 +529,7 @@ describe(`Commands`, () => { await setupWorkspaces(path); await run(`install`); - await expect(run(`workspaces`, `foreach`, `--all`, `--parallel`, `--jobs`, `0`, `run`, `print`)).rejects.toThrowError(/to be at least 1 \(got 0\)/); + await expect(run(`workspaces`, `foreach`, `--all`, `--parallel`, `--jobs`, `0`, `run`, `print`)).rejects.toThrow(/to be at least 1 \(got 0\)/); }, ), ); @@ -376,12 +545,16 @@ describe(`Commands`, () => { await setupWorkspaces(path); await run(`install`); - const {code, stdout, stderr} = await run(`workspaces`, `foreach`, `--all`, `--parallel`, `--jobs`, `unlimited`, `-vv`, `run`, `print`); + const flagPath = ppath.join(path, `test`); + await xfs.writeFilePromise(flagPath, ``); + const nFlagPath = npath.fromPortablePath(flagPath); - // We don't care what order they start in, just that they all started at the beginning. - const first7Lines = stdout.split(`\n`).slice(0, 7).sort().join(`\n`); + // When they start, each job validates that the flag file exists. If it does, + // wait for 1s then removes it. The idea is that if the jobs aren't all running + // in parallel, then the file will be removed before queued jobs start. The sleep + // ensures we give a bit of time for all jobs to start. - await expect({code, first7Lines, stderr}).toMatchSnapshot(); + await run(`workspaces`, `foreach`, `--all`, `--parallel`, `--jobs`, `unlimited`, `-vv`, `node`, `-e`, `fs.readFileSync(${JSON.stringify(nFlagPath)}); setTimeout(() => {try {fs.unlinkSync(${JSON.stringify(nFlagPath)})} catch {}}, 1000)`); }, ), ); @@ -420,7 +593,15 @@ describe(`Commands`, () => { await run(`install`); - await expect(run(`workspaces`, `foreach`, `--all`, `--no-private`, `run`, `print`)).resolves.toMatchSnapshot(); + await expect(run(`workspaces`, `foreach`, `--all`, `--no-private`, `run`, `print`)).resolves.toEqual({ + code: 0, + stderr: ``, + stdout: [ + `Test Workspace A\n`, + `Test Workspace C\n`, + ...forEachVerboseDone, + ].join(``), + }); }, )); @@ -461,7 +642,21 @@ describe(`Commands`, () => { await setupWorkspaces(path); await run(`install`); - await expect(run(`workspaces`, `foreach`, `--all`, `--topological`, `run`, `test:colon`)).resolves.toMatchSnapshot(); + await expect(run(`workspaces`, `foreach`, `--all`, `--topological`, `run`, `test:colon`)).resolves.toEqual({ + code: 0, + stderr: ``, + stdout: [ + `One execution\n`, + `One execution\n`, + `One execution\n`, + `One execution\n`, + `One execution\n`, + `One execution\n`, + `One execution\n`, + `One execution\n`, + ...forEachVerboseDone, + ].join(``), + }); }, ), ); @@ -481,7 +676,21 @@ describe(`Commands`, () => { await setupWorkspaces(path); await run(`install`); - await expect(run(`test:foo`)).resolves.toMatchSnapshot(); + await expect(run(`test:foo`)).resolves.toEqual({ + code: 0, + stderr: ``, + stdout: [ + `\n`, + `packages/workspace-a\n`, + `packages/workspace-b\n`, + `packages/workspace-c\n`, + `packages/workspace-c/packages/workspace-d\n`, + `packages/workspace-c/packages/workspace-d/packages/workspace-e\n`, + `packages/workspace-c/packages/workspace-f\n`, + `packages/workspace-c/packages/workspace-g\n`, + ...forEachVerboseDone, + ].join(``), + }); }, ), ); @@ -500,7 +709,15 @@ describe(`Commands`, () => { await setupWorkspaces(path); await run(`install`); - await expect(run(`workspaces`, `foreach`, `--all`, `--topological`, `run`, `g:echo`)).resolves.toMatchSnapshot(); + await expect(run(`workspaces`, `foreach`, `--all`, `--topological`, `run`, `g:echo`)).resolves.toEqual({ + code: 0, + stderr: ``, + stdout: [ + `root workspace\n`, + `Test Workspace G\n`, + ...forEachVerboseDone, + ].join(``), + }); }, ), ); @@ -516,7 +733,16 @@ describe(`Commands`, () => { await setupWorkspaces(path); await run(`install`); - await expect(run(`workspaces`, `foreach`, `--recursive`, `--topological`, `run`, `print`, {cwd: `${path}/packages/workspace-b`})).resolves.toMatchSnapshot(); + await expect(run(`workspaces`, `foreach`, `--recursive`, `--topological`, `run`, `print`, {cwd: `${path}/packages/workspace-b`})).resolves.toEqual({ + code: 0, + stderr: ``, + stdout: [ + `Test Workspace A\n`, + `Test Workspace C\n`, + `Test Workspace B\n`, + ...forEachVerboseDone, + ].join(``), + }); }, ), ); @@ -532,7 +758,17 @@ describe(`Commands`, () => { await setupWorkspaces(path); await run(`install`); - await expect(run(`workspaces`, `foreach`, `--recursive`, `--topological`, `--from`, `{workspace-a,workspace-b,workspace-g}`, `run`, `print`, {cwd: path})).resolves.toMatchSnapshot(); + await expect(run(`workspaces`, `foreach`, `--recursive`, `--topological`, `--from`, `{workspace-a,workspace-b,workspace-g}`, `run`, `print`, {cwd: path})).resolves.toEqual({ + code: 0, + stderr: ``, + stdout: [ + `Test Workspace A\n`, + `Test Workspace C\n`, + `Test Workspace B\n`, + `Test Workspace G\n`, + ...forEachVerboseDone, + ].join(``), + }); }, ), ); @@ -540,7 +776,13 @@ describe(`Commands`, () => { test( `--since runs on no workspaces if there have been no changes`, makeWorkspacesForeachSinceEnv(async ({run}) => { - await expect(run(`workspaces`, `foreach`, `--since`, `run`, `print`)).resolves.toMatchSnapshot(); + await expect(run(`workspaces`, `foreach`, `--since`, `run`, `print`)).resolves.toEqual({ + code: 0, + stderr: ``, + stdout: [ + ...forEachVerboseDone, + ].join(``), + }); }), ); @@ -549,7 +791,14 @@ describe(`Commands`, () => { makeWorkspacesForeachSinceEnv(async ({path, run}) => { await writeJson(`${path}/packages/workspace-a/delta.json`, {}); - await expect(run(`workspaces`, `foreach`, `--since`, `run`, `print`)).resolves.toMatchSnapshot(); + await expect(run(`workspaces`, `foreach`, `--since`, `run`, `print`)).resolves.toEqual({ + code: 0, + stderr: ``, + stdout: [ + `Test Workspace A\n`, + ...forEachVerboseDone, + ].join(``), + }); }), ); @@ -561,7 +810,13 @@ describe(`Commands`, () => { await git(`add`, `.`); await git(`commit`, `-m`, `wip`); - await expect(run(`workspaces`, `foreach`, `--since`, `run`, `print`)).resolves.toMatchSnapshot(); + await expect(run(`workspaces`, `foreach`, `--since`, `run`, `print`)).resolves.toEqual({ + code: 0, + stderr: ``, + stdout: [ + ...forEachVerboseDone, + ].join(``), + }); }), ); @@ -578,7 +833,15 @@ describe(`Commands`, () => { await writeJson(`${path}/packages/workspace-b/delta.json`, {}); await writeJson(`${path}/packages/workspace-c/delta.json`, {}); - await expect(run(`workspaces`, `foreach`, `--since=${ref}`, `run`, `print`)).resolves.toMatchSnapshot(); + await expect(run(`workspaces`, `foreach`, `--since=${ref}`, `run`, `print`)).resolves.toEqual({ + code: 0, + stderr: ``, + stdout: [ + `Test Workspace B\n`, + `Test Workspace C\n`, + ...forEachVerboseDone, + ].join(``), + }); }), ); @@ -594,7 +857,15 @@ describe(`Commands`, () => { await writeJson(`${path}/packages/workspace-b/delta.json`, {}); await writeJson(`${path}/packages/workspace-c/delta.json`, {}); - await expect(run(`workspaces`, `foreach`, `--since`, `run`, `print`)).resolves.toMatchSnapshot(); + await expect(run(`workspaces`, `foreach`, `--since`, `run`, `print`)).resolves.toEqual({ + code: 0, + stderr: ``, + stdout: [ + `Test Workspace B\n`, + `Test Workspace C\n`, + ...forEachVerboseDone, + ].join(``), + }); }), ); @@ -603,7 +874,19 @@ describe(`Commands`, () => { makeWorkspacesForeachSinceEnv(async ({git, path, run}) => { await writeJson(`${path}/packages/workspace-a/delta.json`, {}); - await expect(run(`workspaces`, `foreach`, `--since`, `--recursive`, `run`, `print`)).resolves.toMatchSnapshot(); + await expect(run(`workspaces`, `foreach`, `--since`, `--recursive`, `run`, `print`)).resolves.toEqual({ + code: 0, + stderr: ``, + stdout: [ + `Test Workspace A\n`, + `Test Workspace B\n`, + `Test Workspace C\n`, + `Test Workspace D\n`, + `Test Workspace E\n`, + `Test Workspace F\n`, + ...forEachVerboseDone, + ].join(``), + }); }), ); diff --git a/packages/plugin-workspace-tools/sources/commands/foreach.ts b/packages/plugin-workspace-tools/sources/commands/foreach.ts index 946185c75357..576fc491e911 100644 --- a/packages/plugin-workspace-tools/sources/commands/foreach.ts +++ b/packages/plugin-workspace-tools/sources/commands/foreach.ts @@ -272,6 +272,10 @@ export default class WorkspacesForeachCommand extends BaseCommand { workspaces.push(workspace); } + workspaces.sort((a, b) => { + return structUtils.stringifyIdent(a.anchoredLocator).localeCompare(structUtils.stringifyIdent(b.anchoredLocator)); + }); + if (this.dryRun) return 0;