as-test 1.1.1 → 1.1.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/CHANGELOG.md +11 -3
- package/README.md +14 -7
- package/as-test.config.schema.json +142 -142
- package/assembly/__fuzz__/math.fuzz.ts +17 -14
- package/assembly/__fuzz__/seed-perf.fuzz.ts +4 -2
- package/assembly/coverage.ts +12 -0
- package/assembly/index.ts +17 -4
- package/assembly/src/expectation.ts +7 -1
- package/assembly/src/fuzz.ts +44 -23
- package/assembly/src/suite.ts +2 -0
- package/assembly/test.ts +85 -0
- package/assembly/util/format.ts +10 -1
- package/assembly/util/wipc.ts +1 -2
- package/bin/build-worker-pool.js +7 -1
- package/bin/commands/build-core.js +6 -1
- package/bin/commands/build.js +1 -1
- package/bin/commands/clean-core.js +3 -1
- package/bin/commands/clean.js +0 -37
- package/bin/commands/fuzz-core.js +2 -2
- package/bin/commands/run-core.js +45 -24
- package/bin/commands/run.js +3 -1
- package/bin/commands/test.js +3 -1
- package/bin/commands/web-runner-source.js +14 -14
- package/bin/commands/web-session.js +6 -1
- package/bin/coverage-points.js +207 -2
- package/bin/crash-store.js +3 -1
- package/bin/index.js +357 -125
- package/bin/reporters/default.js +295 -64
- package/bin/util.js +36 -11
- package/lib/build/index.js +74 -24
- package/lib/src/index.ts +96 -35
- package/package.json +2 -1
- package/transform/lib/coverage.js +222 -71
package/assembly/src/fuzz.ts
CHANGED
|
@@ -79,14 +79,24 @@ export class FuzzSeed {
|
|
|
79
79
|
|
|
80
80
|
i8(options: IntegerOptions<i8> | null = null): i8 {
|
|
81
81
|
if (options == null) {
|
|
82
|
-
return this.nextI8InRange(
|
|
82
|
+
return this.nextI8InRange(
|
|
83
|
+
i8.MIN_VALUE,
|
|
84
|
+
i8.MAX_VALUE,
|
|
85
|
+
EMPTY_I8_EXCLUDE,
|
|
86
|
+
false,
|
|
87
|
+
);
|
|
83
88
|
}
|
|
84
89
|
return this.nextI8InRange(options.min, options.max, options.exclude, true);
|
|
85
90
|
}
|
|
86
91
|
|
|
87
92
|
u8(options: IntegerOptions<u8> | null = null): u8 {
|
|
88
93
|
if (options == null) {
|
|
89
|
-
return this.nextU8InRange(
|
|
94
|
+
return this.nextU8InRange(
|
|
95
|
+
u8.MIN_VALUE,
|
|
96
|
+
u8.MAX_VALUE,
|
|
97
|
+
EMPTY_U8_EXCLUDE,
|
|
98
|
+
false,
|
|
99
|
+
);
|
|
90
100
|
}
|
|
91
101
|
return this.nextU8InRange(options.min, options.max, options.exclude, true);
|
|
92
102
|
}
|
|
@@ -186,7 +196,9 @@ export class FuzzSeed {
|
|
|
186
196
|
if (!exclude.length) {
|
|
187
197
|
for (let i = 0; i < length; i++) {
|
|
188
198
|
unchecked(
|
|
189
|
-
(out[i] = <u8>
|
|
199
|
+
(out[i] = <u8>(
|
|
200
|
+
unchecked(include[this.nextRange(0, include.length - 1)])
|
|
201
|
+
)),
|
|
190
202
|
);
|
|
191
203
|
}
|
|
192
204
|
return out;
|
|
@@ -299,7 +311,9 @@ export class FuzzSeed {
|
|
|
299
311
|
return <i32>this.nextU32();
|
|
300
312
|
}
|
|
301
313
|
if (!exclude.length) {
|
|
302
|
-
return max <= min
|
|
314
|
+
return max <= min
|
|
315
|
+
? min
|
|
316
|
+
: min + <i32>(this.nextU32() % <u32>(max - min + 1));
|
|
303
317
|
}
|
|
304
318
|
for (let attempts = 0; attempts < 1024; attempts++) {
|
|
305
319
|
const value =
|
|
@@ -324,12 +338,16 @@ export class FuzzSeed {
|
|
|
324
338
|
const right = <i32>max;
|
|
325
339
|
if (!exclude.length) {
|
|
326
340
|
return <i8>(
|
|
327
|
-
right <= left
|
|
341
|
+
(right <= left
|
|
342
|
+
? left
|
|
343
|
+
: left + <i32>(this.nextU32() % <u32>(right - left + 1)))
|
|
328
344
|
);
|
|
329
345
|
}
|
|
330
346
|
for (let attempts = 0; attempts < 1024; attempts++) {
|
|
331
347
|
const value = <i8>(
|
|
332
|
-
right <= left
|
|
348
|
+
(right <= left
|
|
349
|
+
? left
|
|
350
|
+
: left + <i32>(this.nextU32() % <u32>(right - left + 1)))
|
|
333
351
|
);
|
|
334
352
|
if (!containsValue<i8>(exclude, value)) return value;
|
|
335
353
|
}
|
|
@@ -350,11 +368,13 @@ export class FuzzSeed {
|
|
|
350
368
|
const left = <u32>min;
|
|
351
369
|
const right = <u32>max;
|
|
352
370
|
if (!exclude.length) {
|
|
353
|
-
return <u8>(
|
|
371
|
+
return <u8>(
|
|
372
|
+
(right <= left ? left : left + (this.nextU32() % (right - left + 1)))
|
|
373
|
+
);
|
|
354
374
|
}
|
|
355
375
|
for (let attempts = 0; attempts < 1024; attempts++) {
|
|
356
376
|
const value = <u8>(
|
|
357
|
-
right <= left ? left : left + (this.nextU32() % (right - left + 1))
|
|
377
|
+
(right <= left ? left : left + (this.nextU32() % (right - left + 1)))
|
|
358
378
|
);
|
|
359
379
|
if (!containsValue<u8>(exclude, value)) return value;
|
|
360
380
|
}
|
|
@@ -376,12 +396,16 @@ export class FuzzSeed {
|
|
|
376
396
|
const right = <i32>max;
|
|
377
397
|
if (!exclude.length) {
|
|
378
398
|
return <i16>(
|
|
379
|
-
right <= left
|
|
399
|
+
(right <= left
|
|
400
|
+
? left
|
|
401
|
+
: left + <i32>(this.nextU32() % <u32>(right - left + 1)))
|
|
380
402
|
);
|
|
381
403
|
}
|
|
382
404
|
for (let attempts = 0; attempts < 1024; attempts++) {
|
|
383
405
|
const value = <i16>(
|
|
384
|
-
right <= left
|
|
406
|
+
(right <= left
|
|
407
|
+
? left
|
|
408
|
+
: left + <i32>(this.nextU32() % <u32>(right - left + 1)))
|
|
385
409
|
);
|
|
386
410
|
if (!containsValue<i16>(exclude, value)) return value;
|
|
387
411
|
}
|
|
@@ -402,11 +426,13 @@ export class FuzzSeed {
|
|
|
402
426
|
const left = <u32>min;
|
|
403
427
|
const right = <u32>max;
|
|
404
428
|
if (!exclude.length) {
|
|
405
|
-
return <u16>(
|
|
429
|
+
return <u16>(
|
|
430
|
+
(right <= left ? left : left + (this.nextU32() % (right - left + 1)))
|
|
431
|
+
);
|
|
406
432
|
}
|
|
407
433
|
for (let attempts = 0; attempts < 1024; attempts++) {
|
|
408
434
|
const value = <u16>(
|
|
409
|
-
right <= left ? left : left + (this.nextU32() % (right - left + 1))
|
|
435
|
+
(right <= left ? left : left + (this.nextU32() % (right - left + 1)))
|
|
410
436
|
);
|
|
411
437
|
if (!containsValue<u16>(exclude, value)) return value;
|
|
412
438
|
}
|
|
@@ -448,9 +474,7 @@ export class FuzzSeed {
|
|
|
448
474
|
const left = this.toOrderedU64(min);
|
|
449
475
|
const right = this.toOrderedU64(max);
|
|
450
476
|
if (!exclude.length) {
|
|
451
|
-
return this.fromOrderedU64(
|
|
452
|
-
left + this.nextU64Offset(left, right),
|
|
453
|
-
);
|
|
477
|
+
return this.fromOrderedU64(left + this.nextU64Offset(left, right));
|
|
454
478
|
}
|
|
455
479
|
for (let attempts = 0; attempts < 1024; attempts++) {
|
|
456
480
|
const value = this.fromOrderedU64(left + this.nextU64Offset(left, right));
|
|
@@ -529,7 +553,7 @@ export class FuzzSeed {
|
|
|
529
553
|
}
|
|
530
554
|
|
|
531
555
|
private toOrderedU64(value: i64): u64 {
|
|
532
|
-
return <u64>value ^ I64_SIGN_MASK;
|
|
556
|
+
return (<u64>value) ^ I64_SIGN_MASK;
|
|
533
557
|
}
|
|
534
558
|
|
|
535
559
|
private fromOrderedU64(value: u64): i64 {
|
|
@@ -543,13 +567,11 @@ const DIGIT_ALPHABET: i32[] = rangeChars(48, 57);
|
|
|
543
567
|
const HEX_ALPHABET: i32[] = DIGIT_ALPHABET.concat(rangeChars(97, 102));
|
|
544
568
|
const ALNUM_ALPHABET: i32[] = ALPHA_ALPHABET.concat(DIGIT_ALPHABET);
|
|
545
569
|
const BASE64_ALPHABET: i32[] = ALPHA_ALPHABET.concat(DIGIT_ALPHABET).concat([
|
|
546
|
-
43,
|
|
547
|
-
47,
|
|
548
|
-
61,
|
|
570
|
+
43, 47, 61,
|
|
549
571
|
]);
|
|
550
|
-
const IDENTIFIER_ALPHABET: i32[] = [95]
|
|
551
|
-
|
|
552
|
-
);
|
|
572
|
+
const IDENTIFIER_ALPHABET: i32[] = [95]
|
|
573
|
+
.concat(ALPHA_ALPHABET)
|
|
574
|
+
.concat(DIGIT_ALPHABET);
|
|
553
575
|
const WHITESPACE_ALPHABET: i32[] = [9, 10, 13, 32];
|
|
554
576
|
|
|
555
577
|
export abstract class FuzzerBase {
|
|
@@ -1113,7 +1135,6 @@ function removeFirst(values: i32[], needle: i32): void {
|
|
|
1113
1135
|
if (index >= 0) values.splice(index, 1);
|
|
1114
1136
|
}
|
|
1115
1137
|
|
|
1116
|
-
|
|
1117
1138
|
function validateLengthRange(label: string, min: i32, max: i32): void {
|
|
1118
1139
|
if (min < 0 || max < 0) panic();
|
|
1119
1140
|
if (max < min) panic();
|
package/assembly/src/suite.ts
CHANGED
package/assembly/test.ts
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { describe, expect, it } from "as-test";
|
|
2
|
+
// Import JSON and bs directly from json-as.
|
|
3
|
+
// bs import prevents json-as transform from adding broken pnpm paths.
|
|
4
|
+
import { JSON } from "json-as/assembly";
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@json
|
|
8
|
+
class SimpleData {
|
|
9
|
+
name: string = "";
|
|
10
|
+
count: i32 = 0;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@json
|
|
15
|
+
class NestedData {
|
|
16
|
+
id: string = "";
|
|
17
|
+
items: string[] = [];
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
describe("JSON", () => {
|
|
21
|
+
describe("stringify", () => {
|
|
22
|
+
it("should serialize a simple class", () => {
|
|
23
|
+
const data = new SimpleData();
|
|
24
|
+
data.name = "test";
|
|
25
|
+
data.count = 42;
|
|
26
|
+
|
|
27
|
+
const json = JSON.stringify(data);
|
|
28
|
+
|
|
29
|
+
expect(json).toBe('{"name":"test","count":42}');
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it("should serialize a class with arrays", () => {
|
|
33
|
+
const data = new NestedData();
|
|
34
|
+
data.id = "abc123";
|
|
35
|
+
data.items = ["item1", "item2"];
|
|
36
|
+
|
|
37
|
+
const json = JSON.stringify(data);
|
|
38
|
+
|
|
39
|
+
expect(json).toBe('{"id":"abc123","items":["item1","item2"]}');
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it("should serialize primitive types", () => {
|
|
43
|
+
expect(JSON.stringify<i32>(42)).toBe("42");
|
|
44
|
+
expect(JSON.stringify<bool>(true)).toBe("true");
|
|
45
|
+
expect(JSON.stringify<string>("hello")).toBe('"hello"');
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it("should serialize arrays", () => {
|
|
49
|
+
const arr: i32[] = [1, 2, 3];
|
|
50
|
+
expect(JSON.stringify(arr)).toBe("[1,2,3]");
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
describe("parse", () => {
|
|
55
|
+
it("should deserialize a simple class", () => {
|
|
56
|
+
const json = '{"name":"test","count":42}';
|
|
57
|
+
const data = JSON.parse<SimpleData>(json);
|
|
58
|
+
|
|
59
|
+
expect(data.name).toBe("test");
|
|
60
|
+
expect(data.count).toBe(42);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it("should deserialize a class with arrays", () => {
|
|
64
|
+
const json = '{"id":"abc123","items":["item1","item2"]}';
|
|
65
|
+
const data = JSON.parse<NestedData>(json);
|
|
66
|
+
|
|
67
|
+
expect(data.id).toBe("abc123");
|
|
68
|
+
expect(data.items.length).toBe(2);
|
|
69
|
+
expect(data.items[0]).toBe("item1");
|
|
70
|
+
expect(data.items[1]).toBe("item2");
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it("should round-trip data correctly", () => {
|
|
74
|
+
const original = new SimpleData();
|
|
75
|
+
original.name = "round-trip";
|
|
76
|
+
original.count = 123;
|
|
77
|
+
|
|
78
|
+
const json = JSON.stringify(original);
|
|
79
|
+
const restored = JSON.parse<SimpleData>(json);
|
|
80
|
+
|
|
81
|
+
expect(restored.name).toBe(original.name);
|
|
82
|
+
expect(restored.count).toBe(original.count);
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
});
|
package/assembly/util/format.ts
CHANGED
|
@@ -35,7 +35,10 @@ export function formatValue<T>(value: T, deep: boolean = false): string {
|
|
|
35
35
|
let out = "Map(" + keys.length.toString() + ") { ";
|
|
36
36
|
for (let i = 0; i < keys.length; i++) {
|
|
37
37
|
if (i) out += ", ";
|
|
38
|
-
out += formatValue(
|
|
38
|
+
out += formatValue(
|
|
39
|
+
changetype<valueof<typeof keys>>(unchecked(keys[i])),
|
|
40
|
+
true,
|
|
41
|
+
);
|
|
39
42
|
out += " => ";
|
|
40
43
|
out += formatValue(
|
|
41
44
|
changetype<valueof<typeof values>>(unchecked(values[i])),
|
|
@@ -73,31 +76,37 @@ export function formatValue<T>(value: T, deep: boolean = false): string {
|
|
|
73
76
|
return nameof<T>();
|
|
74
77
|
}
|
|
75
78
|
|
|
79
|
+
|
|
76
80
|
@inline
|
|
77
81
|
export function colorText(format: i32[], text: string): string {
|
|
78
82
|
return `\u001b[${format[0].toString()}m${text}\u001b[${format[1].toString()}m`;
|
|
79
83
|
}
|
|
80
84
|
|
|
85
|
+
|
|
81
86
|
@inline
|
|
82
87
|
export function red(text: string): string {
|
|
83
88
|
return colorText([31, 39], text);
|
|
84
89
|
}
|
|
85
90
|
|
|
91
|
+
|
|
86
92
|
@inline
|
|
87
93
|
export function green(text: string): string {
|
|
88
94
|
return colorText([32, 39], text);
|
|
89
95
|
}
|
|
90
96
|
|
|
97
|
+
|
|
91
98
|
@inline
|
|
92
99
|
export function bgRed(text: string): string {
|
|
93
100
|
return colorText([41, 49], text);
|
|
94
101
|
}
|
|
95
102
|
|
|
103
|
+
|
|
96
104
|
@inline
|
|
97
105
|
export function bgGreen(text: string): string {
|
|
98
106
|
return colorText([42, 49], text);
|
|
99
107
|
}
|
|
100
108
|
|
|
109
|
+
|
|
101
110
|
@inline
|
|
102
111
|
export function bold(text: string): string {
|
|
103
112
|
return colorText([1, 22], text);
|
package/assembly/util/wipc.ts
CHANGED
|
@@ -154,8 +154,7 @@ export function requestFuzzConfig(): FuzzConfigReply {
|
|
|
154
154
|
const runs = body.slice(0, first);
|
|
155
155
|
const seed =
|
|
156
156
|
second >= 0 ? body.slice(first + 1, second) : body.slice(first + 1);
|
|
157
|
-
const kind =
|
|
158
|
-
second >= 0 && third >= 0 ? body.slice(second + 1, third) : "";
|
|
157
|
+
const kind = second >= 0 && third >= 0 ? body.slice(second + 1, third) : "";
|
|
159
158
|
const value = third >= 0 ? body.slice(third + 1) : "";
|
|
160
159
|
if (runs.length) reply.runs = I32.parseInt(runs);
|
|
161
160
|
if (seed.length) reply.seed = U64.parseInt(seed);
|
package/bin/build-worker-pool.js
CHANGED
|
@@ -21,6 +21,7 @@ export class BuildWorkerPool {
|
|
|
21
21
|
configPath: args.configPath,
|
|
22
22
|
file: args.file,
|
|
23
23
|
modeName: args.modeName,
|
|
24
|
+
buildCommand: args.buildCommand,
|
|
24
25
|
featureToggles,
|
|
25
26
|
overrides,
|
|
26
27
|
resolve,
|
|
@@ -80,7 +81,12 @@ export class BuildWorkerPool {
|
|
|
80
81
|
worker.busy = false;
|
|
81
82
|
worker.task = null;
|
|
82
83
|
if (failedTask) {
|
|
83
|
-
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}`));
|
|
84
90
|
}
|
|
85
91
|
if (!pool || this.closed)
|
|
86
92
|
return;
|
|
@@ -8,7 +8,7 @@ import { applyMode, getPkgRunner, loadConfig, tokenizeCommand, resolveProjectMod
|
|
|
8
8
|
import { persistCrashRecord } from "../crash-store.js";
|
|
9
9
|
import { BuildWorkerPool } from "../build-worker-pool.js";
|
|
10
10
|
const DEFAULT_CONFIG_PATH = path.join(process.cwd(), "./as-test.config.json");
|
|
11
|
-
class BuildFailureError extends Error {
|
|
11
|
+
export class BuildFailureError extends Error {
|
|
12
12
|
constructor(args) {
|
|
13
13
|
super(args.message);
|
|
14
14
|
this.name = "BuildFailureError";
|
|
@@ -18,6 +18,7 @@ class BuildFailureError extends Error {
|
|
|
18
18
|
this.stdout = args.stdout;
|
|
19
19
|
this.stderr = args.stderr;
|
|
20
20
|
this.kind = args.kind;
|
|
21
|
+
this.crashLogPath = args.crashLogPath;
|
|
21
22
|
}
|
|
22
23
|
}
|
|
23
24
|
export async function build(configPath = DEFAULT_CONFIG_PATH, selectors = [], modeName, featureToggles = {}, overrides = {}, resolvedConfig) {
|
|
@@ -49,10 +50,13 @@ export async function build(configPath = DEFAULT_CONFIG_PATH, selectors = [], mo
|
|
|
49
50
|
!hasCustomBuildCommand(config)) {
|
|
50
51
|
const pool = getSerialBuildWorkerPool();
|
|
51
52
|
for (const file of inputFiles) {
|
|
53
|
+
const outFile = `${config.outDir}/${resolveArtifactFileName(file, config.buildOptions.target, modeName, duplicateSpecBasenames)}`;
|
|
54
|
+
const invocation = getBuildCommand(config, pkgRunner, file, outFile, modeName, featureToggles);
|
|
52
55
|
await pool.buildFileMode({
|
|
53
56
|
configPath,
|
|
54
57
|
file,
|
|
55
58
|
modeName,
|
|
59
|
+
buildCommand: formatInvocation(invocation),
|
|
56
60
|
featureToggles,
|
|
57
61
|
overrides,
|
|
58
62
|
});
|
|
@@ -90,6 +94,7 @@ export async function build(configPath = DEFAULT_CONFIG_PATH, selectors = [], mo
|
|
|
90
94
|
stdout,
|
|
91
95
|
stderr,
|
|
92
96
|
kind,
|
|
97
|
+
crashLogPath: crash.logPath,
|
|
93
98
|
message: `Failed to build ${path.basename(file)} in mode ${modeLabel} with ${stderr || stdout || "unknown build error"}\n` +
|
|
94
99
|
`Build command: ${buildCommand}\n` +
|
|
95
100
|
`Crash log: ${crash.logPath}`,
|
package/bin/commands/build.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { closeSerialBuildWorkerPool, } from "./build-core.js";
|
|
2
2
|
export { build } from "./build-core.js";
|
|
3
|
-
export { formatInvocation, getBuildInvocationPreview, getBuildReuseInfo, } from "./build-core.js";
|
|
3
|
+
export { BuildFailureError, formatInvocation, getBuildInvocationPreview, getBuildReuseInfo, } from "./build-core.js";
|
|
4
4
|
export async function executeBuildCommand(rawArgs, configPath, selectedModes, deps) {
|
|
5
5
|
const commandArgs = deps.resolveCommandArgs(rawArgs, "build");
|
|
6
6
|
const listFlags = deps.resolveListFlags(rawArgs, "build");
|
|
@@ -112,7 +112,9 @@ function pruneNestedTargets(targets) {
|
|
|
112
112
|
if (targetPath == otherPath)
|
|
113
113
|
continue;
|
|
114
114
|
const relative = path.relative(targetPath, otherPath);
|
|
115
|
-
if (!relative.length ||
|
|
115
|
+
if (!relative.length ||
|
|
116
|
+
relative == ".." ||
|
|
117
|
+
relative.startsWith(`..${path.sep}`)) {
|
|
116
118
|
continue;
|
|
117
119
|
}
|
|
118
120
|
targets.delete(otherPath);
|
package/bin/commands/clean.js
CHANGED
|
@@ -1,16 +1,10 @@
|
|
|
1
|
-
import chalk from "chalk";
|
|
2
|
-
import { createInterface } from "readline";
|
|
3
1
|
import { clean } from "./clean-core.js";
|
|
4
2
|
import { loadConfig } from "../util.js";
|
|
5
3
|
export { clean } from "./clean-core.js";
|
|
6
4
|
export async function executeCleanCommand(rawArgs, configPath, selectedModes, resolveExecutionModes) {
|
|
7
|
-
const force = rawArgs.includes("-f") || rawArgs.includes("--force");
|
|
8
5
|
const modeTargets = selectedModes.length > 0
|
|
9
6
|
? resolveExecutionModes(configPath, selectedModes)
|
|
10
7
|
: resolveAllCleanModes(configPath);
|
|
11
|
-
if (!force && selectedModes.length == 0) {
|
|
12
|
-
await confirmFullClean(configPath);
|
|
13
|
-
}
|
|
14
8
|
await clean(configPath, modeTargets, selectedModes.length == 0);
|
|
15
9
|
}
|
|
16
10
|
function resolveAllCleanModes(configPath) {
|
|
@@ -18,34 +12,3 @@ function resolveAllCleanModes(configPath) {
|
|
|
18
12
|
const config = loadConfig(resolvedConfigPath, true);
|
|
19
13
|
return [undefined, ...Object.keys(config.modes)];
|
|
20
14
|
}
|
|
21
|
-
async function confirmFullClean(configPath) {
|
|
22
|
-
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
23
|
-
throw new Error('clean without --mode requires confirmation. Re-run with "-f" or "--force" to skip the prompt.');
|
|
24
|
-
}
|
|
25
|
-
const target = configPath ? ` in ${configPath}` : "";
|
|
26
|
-
process.stdout.write(chalk.bold.blue("◇ Confirm Clean") +
|
|
27
|
-
"\n" +
|
|
28
|
-
`│ This will remove configured build outputs, crash reports, and logs for every mode${target}.\n` +
|
|
29
|
-
"│\n");
|
|
30
|
-
const answer = await promptLine("Continue? [Y/n] ");
|
|
31
|
-
const normalized = answer.trim().toLowerCase();
|
|
32
|
-
if (normalized == "" || normalized == "y" || normalized == "yes")
|
|
33
|
-
return;
|
|
34
|
-
if (normalized == "n" || normalized == "no") {
|
|
35
|
-
process.stdout.write(chalk.dim("clean cancelled\n"));
|
|
36
|
-
process.exit(0);
|
|
37
|
-
}
|
|
38
|
-
throw new Error(`invalid answer "${answer}". Expected yes or no.`);
|
|
39
|
-
}
|
|
40
|
-
function promptLine(question) {
|
|
41
|
-
return new Promise((resolve) => {
|
|
42
|
-
const rl = createInterface({
|
|
43
|
-
input: process.stdin,
|
|
44
|
-
output: process.stdout,
|
|
45
|
-
});
|
|
46
|
-
rl.question(question, (answer) => {
|
|
47
|
-
rl.close();
|
|
48
|
-
resolve(answer);
|
|
49
|
-
});
|
|
50
|
-
});
|
|
51
|
-
}
|
|
@@ -292,10 +292,10 @@ function buildFuzzCrashEntryKey(file, modeName) {
|
|
|
292
292
|
return `${path.basename(file).replace(/\.ts$/, "")}.${sanitizeEntryName(modeName)}`;
|
|
293
293
|
}
|
|
294
294
|
function sanitizeEntryName(name) {
|
|
295
|
-
return name
|
|
295
|
+
return (name
|
|
296
296
|
.toLowerCase()
|
|
297
297
|
.replace(/[^a-z0-9]+/g, "-")
|
|
298
|
-
.replace(/^-+|-+$/g, "") || "fuzzer";
|
|
298
|
+
.replace(/^-+|-+$/g, "") || "fuzzer");
|
|
299
299
|
}
|
|
300
300
|
function captureFrames(onFrame) {
|
|
301
301
|
const originalWrite = process.stdout.write.bind(process.stdout);
|