@vnejs/monorepo 0.2.2 → 0.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/cli.js +1 -1
- package/src/run-changed/index.js +28 -16
- package/src/run-changed/schedule.js +33 -17
- package/src/run-changed/schedule.test.js +76 -0
package/package.json
CHANGED
package/src/cli.js
CHANGED
|
@@ -97,7 +97,7 @@ program
|
|
|
97
97
|
|
|
98
98
|
program
|
|
99
99
|
.command("run-changed")
|
|
100
|
-
.description("Run npm script in git-changed workspace packages (sequential by default)")
|
|
100
|
+
.description("Run npm script in git-changed workspace packages (dependency layers; sequential by default)")
|
|
101
101
|
.option("--script <script>", "npm script name from package.json")
|
|
102
102
|
.option("--ref <ref>", "git ref to compare against", "HEAD")
|
|
103
103
|
.option("--dry-run", "list changed packages without running the script", false)
|
package/src/run-changed/index.js
CHANGED
|
@@ -1,30 +1,39 @@
|
|
|
1
1
|
const path = require("path");
|
|
2
2
|
|
|
3
3
|
const { getChangedPackages } = require("../changed-packages");
|
|
4
|
-
const
|
|
4
|
+
const listWorkspacePackages = require("../workspace/list-packages");
|
|
5
|
+
const { buildDependencyGraph, restrictGraph } = require("../workspace/build-graph");
|
|
6
|
+
const { topoSortLayers } = require("../workspace/topo-sort");
|
|
5
7
|
const { runAsync } = require("../publish/utils");
|
|
6
8
|
const confirm = require("../utils/confirm");
|
|
7
9
|
const { formatPackageLine } = require("../utils/format-package");
|
|
8
10
|
const schedulePackages = require("./schedule");
|
|
9
11
|
|
|
10
|
-
const
|
|
11
|
-
const
|
|
12
|
-
const {
|
|
12
|
+
const groupChangedByGraph = (root, changedPackages) => {
|
|
13
|
+
const { packages: all } = listWorkspacePackages({ cwd: root });
|
|
14
|
+
const { graph } = buildDependencyGraph(all);
|
|
15
|
+
const byChangedName = new Map(changedPackages.map((pkg) => [pkg.name, pkg]));
|
|
16
|
+
const changedNames = [...byChangedName.keys()];
|
|
17
|
+
const layers = topoSortLayers(restrictGraph(graph, changedNames), changedNames).map((layer) => layer.map((name) => byChangedName.get(name)));
|
|
13
18
|
|
|
14
|
-
|
|
19
|
+
return { packages: layers.flat(), layers };
|
|
20
|
+
};
|
|
15
21
|
|
|
16
|
-
|
|
17
|
-
const
|
|
22
|
+
const printPackages = (layers) => {
|
|
23
|
+
const total = layers.reduce((sum, layer) => sum + layer.length, 0);
|
|
24
|
+
let index = 0;
|
|
18
25
|
|
|
19
|
-
|
|
20
|
-
};
|
|
26
|
+
layers.forEach((layer, layerIndex) => {
|
|
27
|
+
console.log(`\n=== Layer ${layerIndex + 1}/${layers.length} (${layer.length} packages) ===`);
|
|
21
28
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
29
|
+
layer.forEach((pkg) => {
|
|
30
|
+
index += 1;
|
|
31
|
+
console.log(formatPackageLine(pkg, { indent: " ", tag: `[${index}/${total}]` }));
|
|
32
|
+
});
|
|
25
33
|
});
|
|
34
|
+
|
|
26
35
|
console.log("");
|
|
27
|
-
console.log(`Total: ${
|
|
36
|
+
console.log(`Total: ${total}, layers: ${layers.length}`);
|
|
28
37
|
};
|
|
29
38
|
|
|
30
39
|
const parseConcurrency = (value) => {
|
|
@@ -71,16 +80,16 @@ module.exports = async (options = {}) => {
|
|
|
71
80
|
return;
|
|
72
81
|
}
|
|
73
82
|
|
|
74
|
-
const packages =
|
|
83
|
+
const { packages, layers } = groupChangedByGraph(root, changedPackages);
|
|
75
84
|
|
|
76
85
|
if (options.dryRun) {
|
|
77
|
-
printPackages(
|
|
86
|
+
printPackages(layers);
|
|
78
87
|
return;
|
|
79
88
|
}
|
|
80
89
|
|
|
81
90
|
if (options.confirm) {
|
|
82
91
|
console.log(`\nThe following packages will run "${script}":\n`);
|
|
83
|
-
printPackages(
|
|
92
|
+
printPackages(layers);
|
|
84
93
|
|
|
85
94
|
const confirmed = await confirm(`Continue? [y/N] `);
|
|
86
95
|
|
|
@@ -92,8 +101,11 @@ module.exports = async (options = {}) => {
|
|
|
92
101
|
console.log("");
|
|
93
102
|
}
|
|
94
103
|
|
|
104
|
+
console.log(`\n=== Run-changed: ${packages.length} packages, ${layers.length} layers ===`);
|
|
105
|
+
|
|
95
106
|
await schedulePackages({
|
|
96
107
|
packages,
|
|
108
|
+
layers,
|
|
97
109
|
concurrency,
|
|
98
110
|
parallelMs,
|
|
99
111
|
firstSerial,
|
|
@@ -2,7 +2,26 @@ const pLimit = require("p-limit").default;
|
|
|
2
2
|
|
|
3
3
|
const PARALLEL_WINDOW_ENDED = "Parallel window ended; concurrency set to 1";
|
|
4
4
|
|
|
5
|
-
const
|
|
5
|
+
const toGroups = (packages, layers) => {
|
|
6
|
+
const source = layers ?? (packages?.length ? [packages] : []);
|
|
7
|
+
|
|
8
|
+
return source.map((layer) => [...layer]).filter((layer) => layer.length);
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
const rejectFailed = (results, log) => {
|
|
12
|
+
const failed = results.filter((result) => result.status === "rejected");
|
|
13
|
+
|
|
14
|
+
if (!failed.length) return;
|
|
15
|
+
|
|
16
|
+
for (const result of failed) {
|
|
17
|
+
const reason = result.reason;
|
|
18
|
+
log(reason?.stack ?? String(reason));
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
throw new Error(`${failed.length} package script(s) failed.`);
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const schedulePackages = async ({ packages = [], layers, runFn, concurrency = 1, parallelMs, firstSerial = false, log = console.log }) => {
|
|
6
25
|
if (!Number.isInteger(concurrency) || concurrency < 1) {
|
|
7
26
|
throw new Error("concurrency must be an integer >= 1");
|
|
8
27
|
}
|
|
@@ -11,18 +30,20 @@ const schedulePackages = async ({ packages, runFn, concurrency = 1, parallelMs,
|
|
|
11
30
|
throw new Error("parallelMs must be a number >= 0");
|
|
12
31
|
}
|
|
13
32
|
|
|
14
|
-
|
|
33
|
+
const groups = toGroups(packages, layers);
|
|
34
|
+
|
|
35
|
+
if (!groups.length) return;
|
|
15
36
|
|
|
16
37
|
let startIndex = 0;
|
|
17
38
|
|
|
18
39
|
if (firstSerial) {
|
|
19
|
-
await runFn(
|
|
40
|
+
await runFn(groups[0][0], 0);
|
|
20
41
|
startIndex = 1;
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
const remaining = packages.slice(startIndex);
|
|
42
|
+
groups[0] = groups[0].slice(1);
|
|
24
43
|
|
|
25
|
-
|
|
44
|
+
if (!groups[0].length) groups.shift();
|
|
45
|
+
if (!groups.length) return;
|
|
46
|
+
}
|
|
26
47
|
|
|
27
48
|
const limit = pLimit(concurrency);
|
|
28
49
|
let timer;
|
|
@@ -35,18 +56,13 @@ const schedulePackages = async ({ packages, runFn, concurrency = 1, parallelMs,
|
|
|
35
56
|
}
|
|
36
57
|
|
|
37
58
|
try {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
59
|
+
for (const layer of groups) {
|
|
60
|
+
const layerStart = startIndex;
|
|
61
|
+
const results = await Promise.allSettled(layer.map((pkg, offset) => limit(() => runFn(pkg, layerStart + offset))));
|
|
41
62
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
for (const result of failed) {
|
|
45
|
-
const reason = result.reason;
|
|
46
|
-
log(reason?.stack ?? String(reason));
|
|
63
|
+
startIndex += layer.length;
|
|
64
|
+
rejectFailed(results, log);
|
|
47
65
|
}
|
|
48
|
-
|
|
49
|
-
throw new Error(`${failed.length} package script(s) failed.`);
|
|
50
66
|
} finally {
|
|
51
67
|
if (timer) clearTimeout(timer);
|
|
52
68
|
}
|
|
@@ -38,6 +38,18 @@ const flush = async () => {
|
|
|
38
38
|
await Promise.resolve();
|
|
39
39
|
};
|
|
40
40
|
|
|
41
|
+
const waitUntilStarted = async (started, expected) => {
|
|
42
|
+
for (let i = 0; i < 20; i++) {
|
|
43
|
+
if (started.length >= expected.length && expected.every((name, index) => started[index] === name)) {
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
await flush();
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
expect(started).toEqual(expected);
|
|
51
|
+
};
|
|
52
|
+
|
|
41
53
|
describe("schedulePackages", () => {
|
|
42
54
|
afterEach(() => {
|
|
43
55
|
vi.useRealTimers();
|
|
@@ -141,6 +153,70 @@ describe("schedulePackages", () => {
|
|
|
141
153
|
await done;
|
|
142
154
|
});
|
|
143
155
|
|
|
156
|
+
it("does not start a later layer until the previous layer finishes", async () => {
|
|
157
|
+
const { started, runFn, resolve } = createTrackedRunner();
|
|
158
|
+
const done = schedulePackages({
|
|
159
|
+
layers: [packages("a", "b"), packages("c")],
|
|
160
|
+
runFn,
|
|
161
|
+
concurrency: 2,
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
await flush();
|
|
165
|
+
expect(started).toEqual(["a", "b"]);
|
|
166
|
+
|
|
167
|
+
resolve("a");
|
|
168
|
+
await flush();
|
|
169
|
+
expect(started).toEqual(["a", "b"]);
|
|
170
|
+
|
|
171
|
+
resolve("b");
|
|
172
|
+
await waitUntilStarted(started, ["a", "b", "c"]);
|
|
173
|
+
|
|
174
|
+
resolve("c");
|
|
175
|
+
await done;
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
it("does not start the next layer when the current layer fails", async () => {
|
|
179
|
+
const { started, runFn, resolve, reject } = createTrackedRunner();
|
|
180
|
+
const logs = [];
|
|
181
|
+
const done = schedulePackages({
|
|
182
|
+
layers: [packages("a", "b"), packages("c")],
|
|
183
|
+
runFn,
|
|
184
|
+
concurrency: 2,
|
|
185
|
+
log: (message) => logs.push(message),
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
await flush();
|
|
189
|
+
reject("a", new Error("a failed"));
|
|
190
|
+
resolve("b");
|
|
191
|
+
|
|
192
|
+
await expect(done).rejects.toThrow("1 package script(s) failed.");
|
|
193
|
+
expect(started).toEqual(["a", "b"]);
|
|
194
|
+
expect(logs.some((message) => String(message).includes("a failed"))).toBe(true);
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
it("runs firstSerial from the first layer, then the rest of that layer", async () => {
|
|
198
|
+
const { started, runFn, resolve } = createTrackedRunner();
|
|
199
|
+
const done = schedulePackages({
|
|
200
|
+
layers: [packages("a", "b"), packages("c")],
|
|
201
|
+
runFn,
|
|
202
|
+
concurrency: 2,
|
|
203
|
+
firstSerial: true,
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
await flush();
|
|
207
|
+
expect(started).toEqual(["a"]);
|
|
208
|
+
|
|
209
|
+
resolve("a");
|
|
210
|
+
await flush();
|
|
211
|
+
expect(started).toEqual(["a", "b"]);
|
|
212
|
+
|
|
213
|
+
resolve("b");
|
|
214
|
+
await waitUntilStarted(started, ["a", "b", "c"]);
|
|
215
|
+
|
|
216
|
+
resolve("c");
|
|
217
|
+
await done;
|
|
218
|
+
});
|
|
219
|
+
|
|
144
220
|
it("waits for in-flight failures and then rejects", async () => {
|
|
145
221
|
const { started, runFn, resolve, reject } = createTrackedRunner();
|
|
146
222
|
const logs = [];
|