Skip to content

Commit 0da8122

Browse files
authored
Refactors yarn workspaces foreach (#5644)
**What's the problem this PR addresses?** The command default's behavior was "worktree" (ie to only run on the current workspace and whatever it listed in its own `workspaces` field), but it was rarely what users expected (since very few people use nested workspace setups). **How did you fix it?** The main functional change is that one of `--all`, `--worktree`, `--since`, or `--recursive` is now required. The `--since` flag doesn't automatically imply `--worktree` anymore. Using `--all` with one of `--worktree`, `--from`, `--since`, or `--recursive` doesn't work anymore, since they have incompatible meanings (it still accept `--include` and `--exclude` to further refine the list). **Checklist** <!--- Don't worry if you miss something, chores are automatically tested. --> <!--- This checklist exists to help you remember doing the chores when you submit a PR. --> <!--- Put an `x` in all the boxes that apply. --> - [x] I have read the [Contributing Guide](https://yarnpkg.com/advanced/contributing). <!-- See https://yarnpkg.com/advanced/contributing#preparing-your-pr-to-be-released for more details. --> <!-- Check with `yarn version check` and fix with `yarn version check -i` --> - [x] I have set the packages that need to be released for my changes to be effective. <!-- The "Testing chores" workflow validates that your PR follows our guidelines. --> <!-- If it doesn't pass, click on it to see details as to what your PR might be missing. --> - [x] I will check that all automated PR checks pass before the PR gets reviewed.
1 parent 8d70543 commit 0da8122

File tree

17 files changed

+282
-162
lines changed

17 files changed

+282
-162
lines changed

.pnp.cjs

Lines changed: 107 additions & 87 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
-26.9 KB
Binary file not shown.
27.5 KB
Binary file not shown.

.yarn/versions/bae4fa98.yml

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
releases:
2+
"@yarnpkg/cli": major
3+
"@yarnpkg/plugin-workspace-tools": major
4+
5+
declined:
6+
- "@yarnpkg/plugin-compat"
7+
- "@yarnpkg/plugin-constraints"
8+
- "@yarnpkg/plugin-dlx"
9+
- "@yarnpkg/plugin-essentials"
10+
- "@yarnpkg/plugin-init"
11+
- "@yarnpkg/plugin-interactive-tools"
12+
- "@yarnpkg/plugin-nm"
13+
- "@yarnpkg/plugin-npm-cli"
14+
- "@yarnpkg/plugin-pack"
15+
- "@yarnpkg/plugin-patch"
16+
- "@yarnpkg/plugin-pnp"
17+
- "@yarnpkg/plugin-pnpm"
18+
- "@yarnpkg/plugin-stage"
19+
- "@yarnpkg/plugin-typescript"
20+
- "@yarnpkg/plugin-version"
21+
- "@yarnpkg/builder"
22+
- "@yarnpkg/core"
23+
- "@yarnpkg/doctor"

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ Features in `master` can be tried out by running `yarn set version from sources`
1515
- Some important defaults have changed:
1616
- `yarn set version` will prefer using `packageManager` rather than `yarnPath` when possible.
1717
- `yarn init` will no longer use zero-install by default. You still can enable it, but it should make it easier to start one-of projects without having to rewrite the configuration afterwards.
18+
- `yarn workspaces foreach` now requires one of `--all`, `--recursive`, `--since`, or `--worktree` to be explicitly specified; the previous default was `--worktree`, but it was rarely what users expected.
1819

1920
- All official Yarn plugins are now included by default in the bundle we provide. You no longer need to run `yarn plugin import` for *official* plugins (you still need to do it for third-party plugins, of course).
2021
- This doesn't change anything to the plugin API we provide, which will keep being maintained.

packages/acceptance-tests/pkg-tests-core/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@
3838
"node": ">=18.12.0"
3939
},
4040
"dependencies": {
41-
"typanion": "^3.12.1",
41+
"typanion": "^3.14.0",
4242
"uuid": "^8.3.2"
4343
}
4444
}

packages/acceptance-tests/pkg-tests-specs/sources/commands/workspaces/__snapshots__/foreach.test.js.snap

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -39,10 +39,6 @@ exports[`Commands workspace foreach --since runs on workspaces changed since bra
3939
"stderr": "",
4040
"stdout": "Test Workspace B
4141
Test Workspace C
42-
Test Workspace D
43-
Test Workspace E
44-
Test Workspace F
45-
Test Workspace G
4642
Done
4743
",
4844
}
@@ -54,10 +50,6 @@ exports[`Commands workspace foreach --since runs on workspaces changed since com
5450
"stderr": "",
5551
"stdout": "Test Workspace B
5652
Test Workspace C
57-
Test Workspace D
58-
Test Workspace E
59-
Test Workspace F
60-
Test Workspace G
6153
Done
6254
",
6355
}
@@ -291,7 +283,7 @@ Done
291283
}
292284
`;
293285

294-
exports[`Commands workspace foreach should run on current and descendant workspaces by default 1`] = `
286+
exports[`Commands workspace foreach should run on current and descendant workspaces when --worktree is set 1`] = `
295287
{
296288
"code": 0,
297289
"stderr": "",

packages/acceptance-tests/pkg-tests-specs/sources/commands/workspaces/foreach.test.js

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ async function setupWorkspaces(path) {
9393
describe(`Commands`, () => {
9494
describe(`workspace foreach`, () => {
9595
test(
96-
`should run on current and descendant workspaces by default`,
96+
`should run on current and descendant workspaces when --worktree is set`,
9797
makeTemporaryEnv(
9898
{
9999
private: true,
@@ -103,7 +103,7 @@ describe(`Commands`, () => {
103103
await setupWorkspaces(path);
104104
await run(`install`);
105105

106-
await expect(run(`workspaces`, `foreach`, `run`, `print`, {cwd: `${path}/packages/workspace-c`})).resolves.toMatchSnapshot();
106+
await expect(run(`workspaces`, `foreach`, `--worktree`, `run`, `print`, {cwd: `${path}/packages/workspace-c`})).resolves.toMatchSnapshot();
107107
},
108108
),
109109
);
@@ -119,15 +119,15 @@ describe(`Commands`, () => {
119119
await setupWorkspaces(path);
120120
await run(`install`);
121121

122-
const {code, stdout, stderr} = await run(`workspaces`, `foreach`, `--parallel`, `--topological`, `node`, `-p`, `require("./package.json").name`, {cwd: `${path}/packages/workspace-c`});
122+
const {code, stdout, stderr} = await run(`workspaces`, `foreach`, `--worktree`, `--parallel`, `--topological`, `node`, `-p`, `require("./package.json").name ?? "root"`, {cwd: `${path}/packages/workspace-c`});
123123

124124
const orderedStdout = stdout.trim().split(`\n`);
125125
expect(orderedStdout.pop()).toContain(`Done`);
126126

127127
// The exact order is unstable, so just make sure all the workspaces we expect to be there, are.
128128
orderedStdout.sort();
129129

130-
await expect({code, orderedStdout, stderr}).toMatchSnapshot();
130+
expect({code, orderedStdout, stderr}).toMatchSnapshot();
131131
},
132132
),
133133
);
@@ -143,7 +143,7 @@ describe(`Commands`, () => {
143143
await setupWorkspaces(path);
144144
await run(`install`);
145145

146-
const {code, stdout, stderr} = await run(`workspaces`, `foreach`, `--parallel`, `--interlaced`, `--jobs`, `2`, `run`, `start`);
146+
const {code, stdout, stderr} = await run(`workspaces`, `foreach`, `--all`, `--parallel`, `--interlaced`, `--jobs`, `2`, `run`, `start`);
147147

148148
const lines = stdout.trim().split(`\n`);
149149
const firstLine = lines[0];
@@ -176,7 +176,7 @@ describe(`Commands`, () => {
176176
await setupWorkspaces(path);
177177
await run(`install`);
178178

179-
const {code, stdout, stderr} = await run(`workspaces`, `foreach`, `--parallel`, `--topological`, `--jobs`, `2`, `run`, `print`);
179+
const {code, stdout, stderr} = await run(`workspaces`, `foreach`, `--all`, `--parallel`, `--topological`, `--jobs`, `2`, `run`, `print`);
180180

181181
const extractWorkspaces = output => {
182182
const relevantOutput = output.split(`\n`).filter(output => output.includes(`Test Workspace`));
@@ -206,7 +206,7 @@ describe(`Commands`, () => {
206206
await setupWorkspaces(path);
207207
await run(`install`);
208208

209-
await expect(run(`workspaces`, `foreach`, `--verbose`, `run`, `print`)).resolves.toMatchSnapshot();
209+
await expect(run(`workspaces`, `foreach`, `--all`, `--verbose`, `run`, `print`)).resolves.toMatchSnapshot();
210210
},
211211
),
212212
);
@@ -222,7 +222,7 @@ describe(`Commands`, () => {
222222
await setupWorkspaces(path);
223223
await run(`install`);
224224

225-
await expect(run(`workspaces`, `foreach`, `--no-verbose`, `run`, `print`)).resolves.toMatchSnapshot();
225+
await expect(run(`workspaces`, `foreach`, `--all`, `--no-verbose`, `run`, `print`)).resolves.toMatchSnapshot();
226226
},
227227
),
228228
);
@@ -238,7 +238,7 @@ describe(`Commands`, () => {
238238
await setupWorkspaces(path);
239239
await run(`install`);
240240

241-
await expect(run(`workspaces`, `foreach`, `--verbose`, `--include`, `workspace-a`, `--include`, `packages/workspace-b`, `run`, `print`)).resolves.toMatchSnapshot();
241+
await expect(run(`workspaces`, `foreach`, `--all`, `--verbose`, `--include`, `workspace-a`, `--include`, `packages/workspace-b`, `run`, `print`)).resolves.toMatchSnapshot();
242242
},
243243
),
244244
);
@@ -254,7 +254,7 @@ describe(`Commands`, () => {
254254
await setupWorkspaces(path);
255255
await run(`install`);
256256

257-
await expect(run(`workspaces`, `foreach`, `--verbose`, `--include`, `packages/workspace-c/**`, `run`, `print`)).resolves.toMatchSnapshot();
257+
await expect(run(`workspaces`, `foreach`, `--all`, `--verbose`, `--include`, `packages/workspace-c/**`, `run`, `print`)).resolves.toMatchSnapshot();
258258
},
259259
),
260260
);
@@ -270,7 +270,7 @@ describe(`Commands`, () => {
270270
await setupWorkspaces(path);
271271
await run(`install`);
272272

273-
await expect(run(`workspaces`, `foreach`, `--verbose`, `--exclude`, `workspace-a`, `--exclude`, `packages/workspace-b`, `run`, `print`)).resolves.toMatchSnapshot();
273+
await expect(run(`workspaces`, `foreach`, `--all`, `--verbose`, `--exclude`, `workspace-a`, `--exclude`, `packages/workspace-b`, `run`, `print`)).resolves.toMatchSnapshot();
274274
},
275275
),
276276
);
@@ -306,7 +306,7 @@ describe(`Commands`, () => {
306306
await setupWorkspaces(path);
307307
await run(`install`);
308308

309-
await expect(run(`workspaces`, `foreach`, `--jobs`, `2`, `run`, `print`)).rejects.toThrowError(/parallel must be set/);
309+
await expect(run(`workspaces`, `foreach`, `--all`, `--jobs`, `2`, `run`, `print`)).rejects.toThrowError(/parallel must be set/);
310310
},
311311
),
312312
);
@@ -322,7 +322,7 @@ describe(`Commands`, () => {
322322
await setupWorkspaces(path);
323323
await run(`install`);
324324

325-
await expect(run(`workspaces`, `foreach`, `--parallel`, `--jobs`, `0`, `run`, `print`)).rejects.toThrowError(/to be at least 1 \(got 0\)/);
325+
await expect(run(`workspaces`, `foreach`, `--all`, `--parallel`, `--jobs`, `0`, `run`, `print`)).rejects.toThrowError(/to be at least 1 \(got 0\)/);
326326
},
327327
),
328328
);
@@ -338,7 +338,7 @@ describe(`Commands`, () => {
338338
await setupWorkspaces(path);
339339
await run(`install`);
340340

341-
const {code, stdout, stderr} = await run(`workspaces`, `foreach`, `--parallel`, `--jobs`, `unlimited`, `--verbose`, `run`, `print`);
341+
const {code, stdout, stderr} = await run(`workspaces`, `foreach`, `--all`, `--parallel`, `--jobs`, `unlimited`, `--verbose`, `run`, `print`);
342342

343343
// We don't care what order they start in, just that they all started at the beginning.
344344
const first7Lines = stdout.split(`\n`).slice(0, 7).sort().join(`\n`);
@@ -382,7 +382,7 @@ describe(`Commands`, () => {
382382

383383
await run(`install`);
384384

385-
await expect(run(`workspaces`, `foreach`, `--no-private`, `run`, `print`)).resolves.toMatchSnapshot();
385+
await expect(run(`workspaces`, `foreach`, `--all`, `--no-private`, `run`, `print`)).resolves.toMatchSnapshot();
386386
},
387387
));
388388

@@ -399,7 +399,7 @@ describe(`Commands`, () => {
399399
let code = 0;
400400
try {
401401
await run(`install`);
402-
await run(`workspaces`, `foreach`, `run`, `testExit`);
402+
await run(`workspaces`, `foreach`, `--all`, `run`, `testExit`);
403403
} catch (error) {
404404
({code} = error);
405405
}
@@ -423,7 +423,7 @@ describe(`Commands`, () => {
423423
await setupWorkspaces(path);
424424
await run(`install`);
425425

426-
await expect(run(`workspaces`, `foreach`, `--topological`, `run`, `test:colon`)).resolves.toMatchSnapshot();
426+
await expect(run(`workspaces`, `foreach`, `--all`, `--topological`, `run`, `test:colon`)).resolves.toMatchSnapshot();
427427
},
428428
),
429429
);
@@ -442,7 +442,7 @@ describe(`Commands`, () => {
442442
await setupWorkspaces(path);
443443
await run(`install`);
444444

445-
await expect(run(`workspaces`, `foreach`, `--topological`, `run`, `g:echo`)).resolves.toMatchSnapshot();
445+
await expect(run(`workspaces`, `foreach`, `--all`, `--topological`, `run`, `g:echo`)).resolves.toMatchSnapshot();
446446
},
447447
),
448448
);
@@ -560,7 +560,7 @@ describe(`Commands`, () => {
560560
async ({run}) => {
561561
await run(`install`);
562562

563-
await expect(run(`workspaces`, `foreach`, `run`, `has-bin-entries`, `binary-executed`)).resolves.toMatchObject({
563+
await expect(run(`workspaces`, `foreach`, `--all`, `run`, `has-bin-entries`, `binary-executed`)).resolves.toMatchObject({
564564
code: 0,
565565
stdout: expect.stringContaining(`binary-executed`),
566566
});

packages/docusaurus/docs/features/workspaces.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ To declare a workspace, all you have to do is add a `workspaces` array to the ro
4949

5050
Constraints are to monorepos what Eslint is to your source code. They let you declare rules that must apply to specific workspaces in your project. For example, you can use constraints to enforce that all dependencies in your project are synchronized, to prevent some dependencies from being used, or to enforce that some fields such as `license` or `engines.node` are properly set everywhere.
5151

52-
For more information and examples about constraints, check the [dedicated article](/).
52+
For more information and examples about constraints, check the [dedicated article](/features/constraints).
5353

5454
### Cross-references
5555

@@ -99,10 +99,10 @@ Global scripts are characterized by at least one colon (`:`) in the script name.
9999
Scripts from multiple workspaces can be run in parallel if they share the same name, by using `yarn workspaces foreach`. The following example shows you how to publish all packages in your project in parallel, but respecting topological order (so that a workspace only gets published once all other workspaces it depends on did):
100100

101101
```
102-
yarn workspaces foreach -pt npm publish
102+
yarn workspaces foreach --all -pt npm publish
103103
```
104104

105-
By default `yarn workspaces foreach` will run the commands on every workspace in the project, but it can be tweaked. In this example we use the `yarn workspaces foreach ! --since` flag to instead only select workspaces that were modified in the current branch compared to the [main branch](/):
105+
The `yarn workspaces foreach ! --all` flag will run the provided command on every workspace in the project, but it can be tweaked. In this example we use the `yarn workspaces foreach ! --since` flag to instead only select workspaces that were modified in the current branch compared to the [main branch](/):
106106

107107
```
108108
yarn workspaces foreach --since run lint

packages/plugin-essentials/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
"micromatch": "^4.0.2",
1919
"semver": "^7.1.2",
2020
"tslib": "^2.4.0",
21-
"typanion": "^3.12.1"
21+
"typanion": "^3.14.0"
2222
},
2323
"peerDependencies": {
2424
"@yarnpkg/cli": "workspace:^",

0 commit comments

Comments
 (0)