as-test 1.1.6 → 1.1.7
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/CHANGELOG.md +13 -0
- package/README.md +4 -9
- package/assembly/index.ts +10 -15
- package/assembly/src/expectation.ts +11 -11
- package/assembly/src/fuzz.ts +11 -7
- package/assembly/src/log.ts +2 -2
- package/assembly/src/suite.ts +5 -5
- package/assembly/src/tests.ts +8 -8
- package/assembly/util/wipc.ts +5 -1
- package/bin/build-worker-pool.js +146 -142
- package/bin/build-worker.js +37 -34
- package/bin/commands/build-core.js +577 -465
- package/bin/commands/build.js +49 -29
- package/bin/commands/clean-core.js +120 -113
- package/bin/commands/clean.js +14 -8
- package/bin/commands/doctor-core.js +288 -289
- package/bin/commands/doctor.js +1 -1
- package/bin/commands/fuzz-core.js +467 -414
- package/bin/commands/fuzz.js +27 -10
- package/bin/commands/init-core.js +905 -794
- package/bin/commands/init.js +2 -2
- package/bin/commands/run-core.js +2675 -2344
- package/bin/commands/run.js +43 -25
- package/bin/commands/test.js +56 -32
- package/bin/commands/web-runner-source.js +1 -1
- package/bin/commands/web-session.js +516 -525
- package/bin/coverage-points.js +363 -341
- package/bin/crash-store.js +56 -66
- package/bin/index.js +4092 -3150
- package/bin/reporters/default.js +1090 -890
- package/bin/reporters/tap.js +319 -325
- package/bin/types.js +67 -67
- package/bin/util.js +1290 -1239
- package/bin/wipc.js +70 -73
- package/lib/build/index.d.ts +3 -1
- package/lib/build/index.js +1039 -1034
- package/lib/build/web-runner/client.js +1 -1
- package/lib/build/web-runner/html.js +1 -1
- package/lib/build/web-runner/worker.js +1 -1
- package/package.json +6 -3
- package/transform/lib/log.js +9 -5
- package/assembly/util/json.ts +0 -112
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
# Change Log
|
|
2
2
|
|
|
3
|
+
## 2026-05-18 - v1.1.7
|
|
4
|
+
|
|
5
|
+
### Modes & CLI
|
|
6
|
+
|
|
7
|
+
- fix: when a config declares named modes, the implicit base config no longer runs alongside them; only modes with `default !== false` are selected. Configs without any declared modes still fall back to the base mode.
|
|
8
|
+
- feat: add `--watch` to `ast test` to re-run on source or spec changes (150ms debounce, clear-screen, `Ctrl+C` to stop). Watches all base directories derived from `input` patterns plus `assembly/` and the resolved config file; ignores `node_modules`, `.git`, and the configured `outDir`.
|
|
9
|
+
|
|
10
|
+
### Runtime
|
|
11
|
+
|
|
12
|
+
- chore: replace the homegrown JSON serialization helpers (`quote`, `rawOrNull`, `stringifyValue`, `escape`, `unicodeEscape`, `stringifyArray`) with `json-as` across the AssemblyScript runtime and transform, and delete `assembly/util/json.ts`. The `LogTransform` now injects `import { JSON } from "json-as/assembly"` per instrumented source and inlines `JSON.stringify<T>()` into the log helper.
|
|
13
|
+
- chore: promote `json-as` to a required peer dependency (`>=1.0.0`). npm 7+ installs it automatically; pnpm and yarn users must add `json-as` to their own `devDependencies` alongside `as-test`.
|
|
14
|
+
- chore: remove the unused `__as_test_log_default` and `__as_test_json_value` exports.
|
|
15
|
+
|
|
3
16
|
## 2026-05-14 - v1.1.6
|
|
4
17
|
|
|
5
18
|
- fix: coverage `mode` and `dependencies` filtering now correctly handles AssemblyScript-normalized `~lib/<pkg>/...` paths, which are the actual runtime paths emitted for `node_modules` imports.
|
package/README.md
CHANGED
|
@@ -2,14 +2,6 @@
|
|
|
2
2
|
╠═╣ ╚═╗ ══ ║ ╠═ ╚═╗ ║
|
|
3
3
|
╩ ╩ ╚═╝ ╩ ╚═╝ ╚═╝ ╩ </pre></h1>
|
|
4
4
|
|
|
5
|
-
> **Upgrading to 1.1.0**
|
|
6
|
-
>
|
|
7
|
-
> See [CHANGELOG.md](./CHANGELOG.md) for upgrade notes. In most projects, refreshing generated runners is enough:
|
|
8
|
-
>
|
|
9
|
-
> ```bash
|
|
10
|
-
> rm -rf .as-test/runners && npx as-test init
|
|
11
|
-
> ```
|
|
12
|
-
|
|
13
5
|
<details>
|
|
14
6
|
<summary>Table of Contents</summary>
|
|
15
7
|
|
|
@@ -47,9 +39,12 @@ That gives you a basic config file, a sample test, and optionally a sample fuzze
|
|
|
47
39
|
If you already have a project and just want the package:
|
|
48
40
|
|
|
49
41
|
```bash
|
|
50
|
-
npm install --save-dev as-test
|
|
42
|
+
npm install --save-dev as-test json-as
|
|
51
43
|
```
|
|
52
44
|
|
|
45
|
+
`json-as` is a required peer dependency (used for value serialization in
|
|
46
|
+
assertions, snapshots, and `log()`)
|
|
47
|
+
|
|
53
48
|
## Docs
|
|
54
49
|
|
|
55
50
|
Full documentation lives at:
|
package/assembly/index.ts
CHANGED
|
@@ -14,8 +14,8 @@ import {
|
|
|
14
14
|
sendFileStart,
|
|
15
15
|
sendReport,
|
|
16
16
|
} from "./util/wipc";
|
|
17
|
-
import {
|
|
18
|
-
import { bold,
|
|
17
|
+
import { JSON } from "json-as/assembly";
|
|
18
|
+
import { bold, green, red } from "./util/format";
|
|
19
19
|
import {
|
|
20
20
|
createFuzzer,
|
|
21
21
|
FuzzerBase,
|
|
@@ -37,7 +37,6 @@ export {
|
|
|
37
37
|
StringOptions,
|
|
38
38
|
} from "./src/fuzz";
|
|
39
39
|
export { __as_test_deep_equal } from "./src/expectation";
|
|
40
|
-
export { __as_test_json_value } from "./util/json";
|
|
41
40
|
|
|
42
41
|
let entrySuites: Suite[] = [];
|
|
43
42
|
let entryFuzzers: FuzzerBase[] = [];
|
|
@@ -241,11 +240,7 @@ export function xexpect<T>(
|
|
|
241
240
|
*/
|
|
242
241
|
export function log<T>(data: T): void {
|
|
243
242
|
if (!__as_test_log_is_enabled()) return;
|
|
244
|
-
__as_test_log_serialized(
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
export function __as_test_log_default<T>(data: T): string {
|
|
248
|
-
return formatValue(data);
|
|
243
|
+
__as_test_log_serialized(JSON.stringify<T>(data));
|
|
249
244
|
}
|
|
250
245
|
|
|
251
246
|
export function __as_test_log_is_enabled(): bool {
|
|
@@ -556,23 +551,23 @@ class CoveragePointReport {
|
|
|
556
551
|
serialize(): string {
|
|
557
552
|
return (
|
|
558
553
|
'{"hash":' +
|
|
559
|
-
|
|
554
|
+
JSON.stringify<string>(this.hash) +
|
|
560
555
|
',"file":' +
|
|
561
|
-
|
|
556
|
+
JSON.stringify<string>(this.file) +
|
|
562
557
|
',"line":' +
|
|
563
558
|
this.line.toString() +
|
|
564
559
|
',"column":' +
|
|
565
560
|
this.column.toString() +
|
|
566
561
|
',"type":' +
|
|
567
|
-
|
|
562
|
+
JSON.stringify<string>(this.type) +
|
|
568
563
|
',"executed":' +
|
|
569
564
|
(this.executed ? "true" : "false") +
|
|
570
565
|
',"parentHash":' +
|
|
571
|
-
|
|
566
|
+
JSON.stringify<string>(this.parentHash) +
|
|
572
567
|
',"scopeKind":' +
|
|
573
|
-
|
|
568
|
+
JSON.stringify<string>(this.scopeKind) +
|
|
574
569
|
',"scopeName":' +
|
|
575
|
-
|
|
570
|
+
JSON.stringify<string>(this.scopeName) +
|
|
576
571
|
',"depth":' +
|
|
577
572
|
this.depth.toString() +
|
|
578
573
|
"}"
|
|
@@ -698,7 +693,7 @@ export class Result {
|
|
|
698
693
|
serialize(): string {
|
|
699
694
|
return (
|
|
700
695
|
'{"name":' +
|
|
701
|
-
|
|
696
|
+
JSON.stringify<string>(this.name) +
|
|
702
697
|
',"arg1":' +
|
|
703
698
|
this.arg1.toString() +
|
|
704
699
|
',"arg2":' +
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { visualize } from "../util/helpers";
|
|
2
2
|
import { Tests } from "./tests";
|
|
3
|
-
import {
|
|
3
|
+
import { JSON } from "json-as/assembly";
|
|
4
4
|
import { namedSnapshotKey, nextUnnamedSnapshotKey } from "..";
|
|
5
5
|
import {
|
|
6
6
|
sendAssertionFailure,
|
|
@@ -425,8 +425,8 @@ export class Expectation<T> extends Tests {
|
|
|
425
425
|
this._resolve(
|
|
426
426
|
passed,
|
|
427
427
|
"toContain",
|
|
428
|
-
|
|
429
|
-
|
|
428
|
+
JSON.stringify<T>(this._left),
|
|
429
|
+
JSON.stringify<valueof<T>>(value),
|
|
430
430
|
message,
|
|
431
431
|
);
|
|
432
432
|
return;
|
|
@@ -451,7 +451,7 @@ export class Expectation<T> extends Tests {
|
|
|
451
451
|
? namedSnapshotKey(this._snapshotKey, name)
|
|
452
452
|
: nextUnnamedSnapshotKey(this._snapshotKey);
|
|
453
453
|
|
|
454
|
-
const actual =
|
|
454
|
+
const actual = JSON.stringify<T>(this._left);
|
|
455
455
|
const res = snapshotAssert(key, actual);
|
|
456
456
|
this._resolve(res.ok, "toMatchSnapshot", actual, res.expected, message);
|
|
457
457
|
}
|
|
@@ -491,8 +491,8 @@ export class Expectation<T> extends Tests {
|
|
|
491
491
|
this._resolve(
|
|
492
492
|
passed,
|
|
493
493
|
"toBe",
|
|
494
|
-
|
|
495
|
-
|
|
494
|
+
JSON.stringify<T>(this._left),
|
|
495
|
+
JSON.stringify<T>(equals),
|
|
496
496
|
message,
|
|
497
497
|
);
|
|
498
498
|
}
|
|
@@ -505,8 +505,8 @@ export class Expectation<T> extends Tests {
|
|
|
505
505
|
this._resolve(
|
|
506
506
|
passed,
|
|
507
507
|
"toEqual",
|
|
508
|
-
|
|
509
|
-
|
|
508
|
+
JSON.stringify<T>(this._left),
|
|
509
|
+
JSON.stringify<T>(equals),
|
|
510
510
|
message,
|
|
511
511
|
);
|
|
512
512
|
}
|
|
@@ -519,8 +519,8 @@ export class Expectation<T> extends Tests {
|
|
|
519
519
|
this._resolve(
|
|
520
520
|
passed,
|
|
521
521
|
"toStrictEqual",
|
|
522
|
-
|
|
523
|
-
|
|
522
|
+
JSON.stringify<T>(this._left),
|
|
523
|
+
JSON.stringify<T>(equals),
|
|
524
524
|
message,
|
|
525
525
|
);
|
|
526
526
|
}
|
|
@@ -606,5 +606,5 @@ function isTruthy<T>(value: T): bool {
|
|
|
606
606
|
}
|
|
607
607
|
|
|
608
608
|
function q(value: string): string {
|
|
609
|
-
return
|
|
609
|
+
return JSON.stringify<string>(value);
|
|
610
610
|
}
|
package/assembly/src/fuzz.ts
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { JSON } from "json-as/assembly";
|
|
2
|
+
|
|
3
|
+
function quote(s: string): string {
|
|
4
|
+
return JSON.stringify<string>(s);
|
|
5
|
+
}
|
|
2
6
|
|
|
3
7
|
export class StringOptions {
|
|
4
8
|
charset: string = "ascii";
|
|
@@ -656,7 +660,7 @@ export class FuzzFailure {
|
|
|
656
660
|
',"seed":' +
|
|
657
661
|
this.seed.toString() +
|
|
658
662
|
',"input":' +
|
|
659
|
-
|
|
663
|
+
(this.input.length ? this.input : "null") +
|
|
660
664
|
"}"
|
|
661
665
|
);
|
|
662
666
|
}
|
|
@@ -877,7 +881,7 @@ export class Fuzzer1<A, R> extends FuzzerBase {
|
|
|
877
881
|
);
|
|
878
882
|
} else {
|
|
879
883
|
this.generator(seed, (a: A): R => {
|
|
880
|
-
__as_test_fuzz_input = "[" +
|
|
884
|
+
__as_test_fuzz_input = "[" + JSON.stringify<A>(a) + "]";
|
|
881
885
|
return changetype<(a: A) => R>(__fuzz_run1)(a);
|
|
882
886
|
});
|
|
883
887
|
}
|
|
@@ -948,7 +952,7 @@ export class Fuzzer2<A, B, R> extends FuzzerBase {
|
|
|
948
952
|
} else {
|
|
949
953
|
this.generator(seed, (a: A, b: B): R => {
|
|
950
954
|
__as_test_fuzz_input =
|
|
951
|
-
"[" +
|
|
955
|
+
"[" + JSON.stringify<A>(a) + "," + JSON.stringify<B>(b) + "]";
|
|
952
956
|
return changetype<(a: A, b: B) => R>(__fuzz_run2)(a, b);
|
|
953
957
|
});
|
|
954
958
|
}
|
|
@@ -1022,11 +1026,11 @@ export class Fuzzer3<A, B, C, R> extends FuzzerBase {
|
|
|
1022
1026
|
)(seed, (a: A, b: B, c: C): R => {
|
|
1023
1027
|
__as_test_fuzz_input =
|
|
1024
1028
|
"[" +
|
|
1025
|
-
|
|
1029
|
+
JSON.stringify<A>(a) +
|
|
1026
1030
|
"," +
|
|
1027
|
-
|
|
1031
|
+
JSON.stringify<B>(b) +
|
|
1028
1032
|
"," +
|
|
1029
|
-
|
|
1033
|
+
JSON.stringify<C>(c) +
|
|
1030
1034
|
"]";
|
|
1031
1035
|
return changetype<(a: A, b: B, c: C) => R>(__fuzz_run3)(a, b, c);
|
|
1032
1036
|
});
|
package/assembly/src/log.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { JSON } from "json-as/assembly";
|
|
2
2
|
|
|
3
3
|
import { sendLog } from "../util/wipc";
|
|
4
4
|
|
|
@@ -21,7 +21,7 @@ export class Log {
|
|
|
21
21
|
',"depth":' +
|
|
22
22
|
this.depth.toString() +
|
|
23
23
|
',"text":' +
|
|
24
|
-
|
|
24
|
+
JSON.stringify<string>(this.text) +
|
|
25
25
|
"}"
|
|
26
26
|
);
|
|
27
27
|
}
|
package/assembly/src/suite.ts
CHANGED
|
@@ -4,7 +4,7 @@ import { Tests } from "./tests";
|
|
|
4
4
|
import { Log } from "./log";
|
|
5
5
|
import { after_each_callback, before_each_callback } from "..";
|
|
6
6
|
import { sendSuiteEnd, sendSuiteStart } from "../util/wipc";
|
|
7
|
-
import {
|
|
7
|
+
import { JSON } from "json-as/assembly";
|
|
8
8
|
|
|
9
9
|
export class Suite {
|
|
10
10
|
public file: string = "unknown";
|
|
@@ -175,17 +175,17 @@ export class Suite {
|
|
|
175
175
|
serialize(): string {
|
|
176
176
|
let out = "{";
|
|
177
177
|
if (this.depth <= 0) {
|
|
178
|
-
out += '"file":' +
|
|
178
|
+
out += '"file":' + JSON.stringify<string>(this.file) + ",";
|
|
179
179
|
}
|
|
180
180
|
out += '"order":' + this.order.toString();
|
|
181
181
|
out += ',"time":' + this.time.serialize();
|
|
182
|
-
out += ',"description":' +
|
|
182
|
+
out += ',"description":' + JSON.stringify<string>(this.description);
|
|
183
183
|
out += ',"depth":' + this.depth.toString();
|
|
184
184
|
out += ',"suites":' + serializeSuites(this.suites);
|
|
185
185
|
out += ',"tests":' + serializeTests(this.tests);
|
|
186
186
|
out += ',"logs":' + serializeLogs(this.logs);
|
|
187
|
-
out += ',"kind":' +
|
|
188
|
-
out += ',"verdict":' +
|
|
187
|
+
out += ',"kind":' + JSON.stringify<string>(this.kind);
|
|
188
|
+
out += ',"verdict":' + JSON.stringify<string>(this.verdict);
|
|
189
189
|
out += "}";
|
|
190
190
|
return out;
|
|
191
191
|
}
|
package/assembly/src/tests.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { JSON } from "json-as/assembly";
|
|
2
2
|
|
|
3
3
|
export class Tests {
|
|
4
4
|
public order: i32 = 0;
|
|
@@ -15,19 +15,19 @@ export class Tests {
|
|
|
15
15
|
'{"order":' +
|
|
16
16
|
this.order.toString() +
|
|
17
17
|
',"type":' +
|
|
18
|
-
|
|
18
|
+
JSON.stringify<string>(this.type) +
|
|
19
19
|
',"verdict":' +
|
|
20
|
-
|
|
20
|
+
JSON.stringify<string>(this.verdict) +
|
|
21
21
|
',"left":' +
|
|
22
|
-
|
|
22
|
+
(this.left.length ? this.left : "null") +
|
|
23
23
|
',"right":' +
|
|
24
|
-
|
|
24
|
+
(this.right.length ? this.right : "null") +
|
|
25
25
|
',"instr":' +
|
|
26
|
-
|
|
26
|
+
JSON.stringify<string>(this.instr) +
|
|
27
27
|
',"message":' +
|
|
28
|
-
|
|
28
|
+
JSON.stringify<string>(this.message) +
|
|
29
29
|
',"location":' +
|
|
30
|
-
|
|
30
|
+
JSON.stringify<string>(this.location) +
|
|
31
31
|
"}"
|
|
32
32
|
);
|
|
33
33
|
}
|
package/assembly/util/wipc.ts
CHANGED
package/bin/build-worker-pool.js
CHANGED
|
@@ -1,155 +1,159 @@
|
|
|
1
1
|
import { spawn } from "child_process";
|
|
2
2
|
import { fileURLToPath } from "url";
|
|
3
3
|
export class BuildWorkerPool {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
4
|
+
constructor(size) {
|
|
5
|
+
this.pools = new Map();
|
|
6
|
+
this.nextId = 1;
|
|
7
|
+
this.closed = false;
|
|
8
|
+
this.size = Math.max(1, size);
|
|
9
|
+
}
|
|
10
|
+
buildFileMode(args) {
|
|
11
|
+
if (this.closed) {
|
|
12
|
+
return Promise.reject(new Error("build worker pool is closed"));
|
|
9
13
|
}
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
if (worker.child.exitCode != null || worker.child.killed) {
|
|
44
|
-
resolve();
|
|
45
|
-
return;
|
|
46
|
-
}
|
|
47
|
-
worker.child.once("exit", () => resolve());
|
|
48
|
-
worker.child.kill();
|
|
49
|
-
}));
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
await Promise.all(waits);
|
|
53
|
-
}
|
|
54
|
-
getPool(signature) {
|
|
55
|
-
let pool = this.pools.get(signature);
|
|
56
|
-
if (pool)
|
|
57
|
-
return pool;
|
|
58
|
-
pool = {
|
|
59
|
-
workers: Array.from({ length: this.size }, () => this.spawnWorker(signature)),
|
|
60
|
-
queue: [],
|
|
61
|
-
};
|
|
62
|
-
this.pools.set(signature, pool);
|
|
63
|
-
return pool;
|
|
64
|
-
}
|
|
65
|
-
spawnWorker(signature) {
|
|
66
|
-
const workerPath = fileURLToPath(new URL("./build-worker.js", import.meta.url));
|
|
67
|
-
const child = spawn(process.execPath, [workerPath], {
|
|
68
|
-
stdio: ["ignore", "ignore", "ignore", "ipc"],
|
|
69
|
-
});
|
|
70
|
-
const worker = {
|
|
71
|
-
child,
|
|
72
|
-
busy: false,
|
|
73
|
-
task: null,
|
|
74
|
-
};
|
|
75
|
-
child.on("message", (message) => {
|
|
76
|
-
this.onMessage(signature, worker, message);
|
|
77
|
-
});
|
|
78
|
-
child.on("exit", () => {
|
|
79
|
-
const pool = this.pools.get(signature);
|
|
80
|
-
const failedTask = worker.task;
|
|
81
|
-
worker.busy = false;
|
|
82
|
-
worker.task = null;
|
|
83
|
-
if (failedTask) {
|
|
84
|
-
const modeLabel = failedTask.modeName ?? "default";
|
|
85
|
-
const fileLabel = failedTask.file;
|
|
86
|
-
const commandText = failedTask.buildCommand?.trim().length
|
|
87
|
-
? `\nBuild command: ${failedTask.buildCommand}`
|
|
88
|
-
: "";
|
|
89
|
-
failedTask.reject(new Error(`build worker exited unexpectedly while building ${fileLabel} in mode ${modeLabel}${commandText}`));
|
|
90
|
-
}
|
|
91
|
-
if (!pool || this.closed)
|
|
92
|
-
return;
|
|
93
|
-
const index = pool.workers.indexOf(worker);
|
|
94
|
-
if (index >= 0) {
|
|
95
|
-
pool.workers[index] = this.spawnWorker(signature);
|
|
14
|
+
const featureToggles = args.featureToggles ?? {};
|
|
15
|
+
const overrides = args.overrides ?? {};
|
|
16
|
+
const signature = buildSignature(args.modeName, featureToggles, overrides);
|
|
17
|
+
const pool = this.getPool(signature);
|
|
18
|
+
return new Promise((resolve, reject) => {
|
|
19
|
+
pool.queue.push({
|
|
20
|
+
id: this.nextId++,
|
|
21
|
+
configPath: args.configPath,
|
|
22
|
+
file: args.file,
|
|
23
|
+
modeName: args.modeName,
|
|
24
|
+
buildCommand: args.buildCommand,
|
|
25
|
+
featureToggles,
|
|
26
|
+
overrides,
|
|
27
|
+
resolve,
|
|
28
|
+
reject,
|
|
29
|
+
});
|
|
30
|
+
this.pump(pool);
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
async close() {
|
|
34
|
+
this.closed = true;
|
|
35
|
+
const waits = [];
|
|
36
|
+
for (const pool of this.pools.values()) {
|
|
37
|
+
while (pool.queue.length) {
|
|
38
|
+
const task = pool.queue.shift();
|
|
39
|
+
task.reject(new Error("build worker pool closed"));
|
|
40
|
+
}
|
|
41
|
+
for (const worker of pool.workers) {
|
|
42
|
+
waits.push(
|
|
43
|
+
new Promise((resolve) => {
|
|
44
|
+
if (worker.child.exitCode != null || worker.child.killed) {
|
|
45
|
+
resolve();
|
|
46
|
+
return;
|
|
96
47
|
}
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
48
|
+
worker.child.once("exit", () => resolve());
|
|
49
|
+
worker.child.kill();
|
|
50
|
+
}),
|
|
51
|
+
);
|
|
52
|
+
}
|
|
100
53
|
}
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
54
|
+
await Promise.all(waits);
|
|
55
|
+
}
|
|
56
|
+
getPool(signature) {
|
|
57
|
+
let pool = this.pools.get(signature);
|
|
58
|
+
if (pool) return pool;
|
|
59
|
+
pool = {
|
|
60
|
+
workers: Array.from({ length: this.size }, () =>
|
|
61
|
+
this.spawnWorker(signature),
|
|
62
|
+
),
|
|
63
|
+
queue: [],
|
|
64
|
+
};
|
|
65
|
+
this.pools.set(signature, pool);
|
|
66
|
+
return pool;
|
|
67
|
+
}
|
|
68
|
+
spawnWorker(signature) {
|
|
69
|
+
const workerPath = fileURLToPath(
|
|
70
|
+
new URL("./build-worker.js", import.meta.url),
|
|
71
|
+
);
|
|
72
|
+
const child = spawn(process.execPath, [workerPath], {
|
|
73
|
+
stdio: ["ignore", "ignore", "ignore", "ipc"],
|
|
74
|
+
});
|
|
75
|
+
const worker = {
|
|
76
|
+
child,
|
|
77
|
+
busy: false,
|
|
78
|
+
task: null,
|
|
79
|
+
};
|
|
80
|
+
child.on("message", (message) => {
|
|
81
|
+
this.onMessage(signature, worker, message);
|
|
82
|
+
});
|
|
83
|
+
child.on("exit", () => {
|
|
84
|
+
const pool = this.pools.get(signature);
|
|
85
|
+
const failedTask = worker.task;
|
|
86
|
+
worker.busy = false;
|
|
87
|
+
worker.task = null;
|
|
88
|
+
if (failedTask) {
|
|
89
|
+
const modeLabel = failedTask.modeName ?? "default";
|
|
90
|
+
const fileLabel = failedTask.file;
|
|
91
|
+
const commandText = failedTask.buildCommand?.trim().length
|
|
92
|
+
? `\nBuild command: ${failedTask.buildCommand}`
|
|
93
|
+
: "";
|
|
94
|
+
failedTask.reject(
|
|
95
|
+
new Error(
|
|
96
|
+
`build worker exited unexpectedly while building ${fileLabel} in mode ${modeLabel}${commandText}`,
|
|
97
|
+
),
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
if (!pool || this.closed) return;
|
|
101
|
+
const index = pool.workers.indexOf(worker);
|
|
102
|
+
if (index >= 0) {
|
|
103
|
+
pool.workers[index] = this.spawnWorker(signature);
|
|
104
|
+
}
|
|
105
|
+
this.pump(pool);
|
|
106
|
+
});
|
|
107
|
+
return worker;
|
|
108
|
+
}
|
|
109
|
+
onMessage(signature, worker, message) {
|
|
110
|
+
const pool = this.pools.get(signature);
|
|
111
|
+
const task = worker.task;
|
|
112
|
+
if (!pool || !task || task.id !== message.id) return;
|
|
113
|
+
worker.busy = false;
|
|
114
|
+
worker.task = null;
|
|
115
|
+
if (message.type == "done") {
|
|
116
|
+
task.resolve();
|
|
117
|
+
} else {
|
|
118
|
+
task.reject(deserializeError(message.error));
|
|
115
119
|
}
|
|
116
|
-
pump(pool)
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
}
|
|
120
|
+
this.pump(pool);
|
|
121
|
+
}
|
|
122
|
+
pump(pool) {
|
|
123
|
+
for (const worker of pool.workers) {
|
|
124
|
+
if (worker.busy) continue;
|
|
125
|
+
const task = pool.queue.shift();
|
|
126
|
+
if (!task) return;
|
|
127
|
+
worker.busy = true;
|
|
128
|
+
worker.task = task;
|
|
129
|
+
worker.child.send({
|
|
130
|
+
type: "build-file",
|
|
131
|
+
id: task.id,
|
|
132
|
+
configPath: task.configPath,
|
|
133
|
+
file: task.file,
|
|
134
|
+
modeName: task.modeName,
|
|
135
|
+
featureToggles: task.featureToggles,
|
|
136
|
+
overrides: task.overrides,
|
|
137
|
+
});
|
|
135
138
|
}
|
|
139
|
+
}
|
|
136
140
|
}
|
|
137
141
|
function buildSignature(modeName, featureToggles, overrides) {
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
142
|
+
return JSON.stringify({
|
|
143
|
+
modeName: modeName ?? "default",
|
|
144
|
+
featureToggles,
|
|
145
|
+
overrides,
|
|
146
|
+
});
|
|
143
147
|
}
|
|
144
148
|
function deserializeError(payload) {
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
149
|
+
const error = new Error(
|
|
150
|
+
typeof payload.message == "string" ? payload.message : "unknown error",
|
|
151
|
+
);
|
|
152
|
+
error.name = typeof payload.name == "string" ? payload.name : "Error";
|
|
153
|
+
if (typeof payload.stack == "string") error.stack = payload.stack;
|
|
154
|
+
for (const [key, value] of Object.entries(payload)) {
|
|
155
|
+
if (key == "name" || key == "message" || key == "stack") continue;
|
|
156
|
+
error[key] = value;
|
|
157
|
+
}
|
|
158
|
+
return error;
|
|
155
159
|
}
|