as-test 1.1.5 → 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 +16 -1
- 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/coverage.js +4 -4
- package/transform/lib/log.js +9 -5
- package/assembly/util/json.ts +0 -112
|
@@ -8,254 +8,305 @@ import { buildWebRunnerSource } from "./web-runner-source.js";
|
|
|
8
8
|
const TARGETS = ["wasi", "bindings", "web"];
|
|
9
9
|
const EXAMPLE_MODES = ["minimal", "full", "none"];
|
|
10
10
|
export async function init(rawArgs) {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
}
|
|
28
|
-
: await runInteractiveOnboarding(options, rl);
|
|
29
|
-
if (!answers) {
|
|
30
|
-
console.log(chalk.bold.red("◆ Cancelled"));
|
|
31
|
-
return;
|
|
32
|
-
}
|
|
33
|
-
printPlan(answers.root, answers.target, answers.example, answers.fuzzExample, answers.installDependenciesNow);
|
|
34
|
-
if (!options.yes) {
|
|
35
|
-
const cont = await askYesNo("Continue with these changes?", rl, true);
|
|
36
|
-
if (!cont) {
|
|
37
|
-
console.log(chalk.bold.red("◆ Cancelled"));
|
|
38
|
-
return;
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
const summary = applyInit(answers.root, answers.target, answers.example, answers.fuzzExample, options.force);
|
|
42
|
-
printSummary(summary);
|
|
43
|
-
console.log(chalk.bold.green("◆ Finished!"));
|
|
44
|
-
if (answers.installDependenciesNow) {
|
|
45
|
-
installDependencies(answers.root);
|
|
46
|
-
console.log("\nNow, run " + chalk.italic.bold("npm test") + "\n");
|
|
47
|
-
}
|
|
48
|
-
else {
|
|
49
|
-
console.log("\nNow, run " + chalk.italic.bold("npm i && npm test") + "\n");
|
|
11
|
+
const options = parseInitArgs(rawArgs);
|
|
12
|
+
const rl = options.yes
|
|
13
|
+
? null
|
|
14
|
+
: createInterface({
|
|
15
|
+
input: process.stdin,
|
|
16
|
+
output: process.stdout,
|
|
17
|
+
});
|
|
18
|
+
try {
|
|
19
|
+
printOnboardingHeader();
|
|
20
|
+
const answers = options.yes
|
|
21
|
+
? {
|
|
22
|
+
root: path.resolve(process.cwd(), options.dir),
|
|
23
|
+
target: options.target ?? "wasi",
|
|
24
|
+
example: options.example ?? "minimal",
|
|
25
|
+
fuzzExample: options.fuzzExample ?? false,
|
|
26
|
+
installDependenciesNow: options.install ?? false,
|
|
50
27
|
}
|
|
28
|
+
: await runInteractiveOnboarding(options, rl);
|
|
29
|
+
if (!answers) {
|
|
30
|
+
console.log(chalk.bold.red("◆ Cancelled"));
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
printPlan(
|
|
34
|
+
answers.root,
|
|
35
|
+
answers.target,
|
|
36
|
+
answers.example,
|
|
37
|
+
answers.fuzzExample,
|
|
38
|
+
answers.installDependenciesNow,
|
|
39
|
+
);
|
|
40
|
+
if (!options.yes) {
|
|
41
|
+
const cont = await askYesNo("Continue with these changes?", rl, true);
|
|
42
|
+
if (!cont) {
|
|
43
|
+
console.log(chalk.bold.red("◆ Cancelled"));
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
51
46
|
}
|
|
52
|
-
|
|
53
|
-
|
|
47
|
+
const summary = applyInit(
|
|
48
|
+
answers.root,
|
|
49
|
+
answers.target,
|
|
50
|
+
answers.example,
|
|
51
|
+
answers.fuzzExample,
|
|
52
|
+
options.force,
|
|
53
|
+
);
|
|
54
|
+
printSummary(summary);
|
|
55
|
+
console.log(chalk.bold.green("◆ Finished!"));
|
|
56
|
+
if (answers.installDependenciesNow) {
|
|
57
|
+
installDependencies(answers.root);
|
|
58
|
+
console.log("\nNow, run " + chalk.italic.bold("npm test") + "\n");
|
|
59
|
+
} else {
|
|
60
|
+
console.log(
|
|
61
|
+
"\nNow, run " + chalk.italic.bold("npm i && npm test") + "\n",
|
|
62
|
+
);
|
|
54
63
|
}
|
|
64
|
+
} finally {
|
|
65
|
+
rl?.close();
|
|
66
|
+
}
|
|
55
67
|
}
|
|
56
68
|
function parseInitArgs(rawArgs) {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
}
|
|
70
|
-
if (arg == "--force") {
|
|
71
|
-
options.force = true;
|
|
72
|
-
continue;
|
|
73
|
-
}
|
|
74
|
-
if (arg == "--install") {
|
|
75
|
-
options.install = true;
|
|
76
|
-
continue;
|
|
77
|
-
}
|
|
78
|
-
if (arg == "--fuzz-example") {
|
|
79
|
-
options.fuzzExample = true;
|
|
80
|
-
continue;
|
|
81
|
-
}
|
|
82
|
-
if (arg == "--no-fuzz-example") {
|
|
83
|
-
options.fuzzExample = false;
|
|
84
|
-
continue;
|
|
85
|
-
}
|
|
86
|
-
if (arg == "--target") {
|
|
87
|
-
const next = rawArgs[i + 1];
|
|
88
|
-
if (next && !next.startsWith("-")) {
|
|
89
|
-
options.target = parseTarget(next);
|
|
90
|
-
i++;
|
|
91
|
-
continue;
|
|
92
|
-
}
|
|
93
|
-
throw new Error("--target requires a value: wasi|bindings|web");
|
|
94
|
-
}
|
|
95
|
-
if (arg.startsWith("--target=")) {
|
|
96
|
-
options.target = parseTarget(arg.slice("--target=".length));
|
|
97
|
-
continue;
|
|
98
|
-
}
|
|
99
|
-
if (arg == "--example") {
|
|
100
|
-
const next = rawArgs[i + 1];
|
|
101
|
-
if (next && !next.startsWith("-")) {
|
|
102
|
-
options.example = parseExampleMode(next);
|
|
103
|
-
i++;
|
|
104
|
-
continue;
|
|
105
|
-
}
|
|
106
|
-
throw new Error("--example requires a value: minimal|full|none");
|
|
107
|
-
}
|
|
108
|
-
if (arg.startsWith("--example=")) {
|
|
109
|
-
options.example = parseExampleMode(arg.slice("--example=".length));
|
|
110
|
-
continue;
|
|
111
|
-
}
|
|
112
|
-
if (arg == "--dir") {
|
|
113
|
-
const next = rawArgs[i + 1];
|
|
114
|
-
if (next && !next.startsWith("-")) {
|
|
115
|
-
options.dir = next;
|
|
116
|
-
options.dirExplicit = true;
|
|
117
|
-
i++;
|
|
118
|
-
continue;
|
|
119
|
-
}
|
|
120
|
-
throw new Error("--dir requires a path value");
|
|
121
|
-
}
|
|
122
|
-
if (arg.startsWith("--dir=")) {
|
|
123
|
-
options.dir = arg.slice("--dir=".length);
|
|
124
|
-
options.dirExplicit = true;
|
|
125
|
-
continue;
|
|
126
|
-
}
|
|
127
|
-
if (arg.startsWith("-")) {
|
|
128
|
-
throw new Error(`Unknown init flag: ${arg}`);
|
|
129
|
-
}
|
|
130
|
-
positional.push(arg);
|
|
69
|
+
const options = {
|
|
70
|
+
yes: false,
|
|
71
|
+
force: false,
|
|
72
|
+
dir: ".",
|
|
73
|
+
dirExplicit: false,
|
|
74
|
+
};
|
|
75
|
+
const positional = [];
|
|
76
|
+
for (let i = 0; i < rawArgs.length; i++) {
|
|
77
|
+
const arg = rawArgs[i];
|
|
78
|
+
if (arg == "--yes" || arg == "-y") {
|
|
79
|
+
options.yes = true;
|
|
80
|
+
continue;
|
|
131
81
|
}
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
options.dirExplicit = true;
|
|
82
|
+
if (arg == "--force") {
|
|
83
|
+
options.force = true;
|
|
84
|
+
continue;
|
|
136
85
|
}
|
|
137
|
-
if (
|
|
138
|
-
|
|
86
|
+
if (arg == "--install") {
|
|
87
|
+
options.install = true;
|
|
88
|
+
continue;
|
|
139
89
|
}
|
|
140
|
-
if (
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
options.example = positional.shift();
|
|
90
|
+
if (arg == "--fuzz-example") {
|
|
91
|
+
options.fuzzExample = true;
|
|
92
|
+
continue;
|
|
144
93
|
}
|
|
145
|
-
if (
|
|
146
|
-
|
|
94
|
+
if (arg == "--no-fuzz-example") {
|
|
95
|
+
options.fuzzExample = false;
|
|
96
|
+
continue;
|
|
147
97
|
}
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
{ value: "manual", label: "Manual (guided prompts)" },
|
|
157
|
-
{ value: "quick", label: "Quick (sensible defaults)" },
|
|
158
|
-
], face, "manual");
|
|
159
|
-
const workspacePrompt = "What do you want to set up? (default: ./)";
|
|
160
|
-
const defaultRoot = options.dir;
|
|
161
|
-
let selectedDir = defaultRoot;
|
|
162
|
-
if (options.dirExplicit || onboardingMode == "quick") {
|
|
163
|
-
selectedDir = options.dir;
|
|
98
|
+
if (arg == "--target") {
|
|
99
|
+
const next = rawArgs[i + 1];
|
|
100
|
+
if (next && !next.startsWith("-")) {
|
|
101
|
+
options.target = parseTarget(next);
|
|
102
|
+
i++;
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
throw new Error("--target requires a value: wasi|bindings|web");
|
|
164
106
|
}
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
selectedDir = enteredDir.length ? enteredDir : defaultRoot;
|
|
107
|
+
if (arg.startsWith("--target=")) {
|
|
108
|
+
options.target = parseTarget(arg.slice("--target=".length));
|
|
109
|
+
continue;
|
|
169
110
|
}
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
111
|
+
if (arg == "--example") {
|
|
112
|
+
const next = rawArgs[i + 1];
|
|
113
|
+
if (next && !next.startsWith("-")) {
|
|
114
|
+
options.example = parseExampleMode(next);
|
|
115
|
+
i++;
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
throw new Error("--example requires a value: minimal|full|none");
|
|
173
119
|
}
|
|
174
|
-
|
|
175
|
-
|
|
120
|
+
if (arg.startsWith("--example=")) {
|
|
121
|
+
options.example = parseExampleMode(arg.slice("--example=".length));
|
|
122
|
+
continue;
|
|
176
123
|
}
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
value: "bindings",
|
|
187
|
-
label: "bindings (default runner: node .as-test/runners/default.bindings.js)",
|
|
188
|
-
},
|
|
189
|
-
{
|
|
190
|
-
value: "web",
|
|
191
|
-
label: "web (default runner: node .as-test/runners/default.web.js)",
|
|
192
|
-
},
|
|
193
|
-
], face, "wasi"));
|
|
194
|
-
if (options.target || onboardingMode == "quick") {
|
|
195
|
-
printPromptAndSelectionLine("Build target", target);
|
|
196
|
-
}
|
|
197
|
-
const example = options.example ??
|
|
198
|
-
(onboardingMode == "quick"
|
|
199
|
-
? "minimal"
|
|
200
|
-
: await askMenuChoice("Example template", [
|
|
201
|
-
{ value: "minimal", label: "minimal (one short starter spec)" },
|
|
202
|
-
{ value: "full", label: "full (hooks, assertions, logs, suites)" },
|
|
203
|
-
{ value: "none", label: "none (config/runners only)" },
|
|
204
|
-
], face, "minimal"));
|
|
205
|
-
if (options.example || onboardingMode == "quick") {
|
|
206
|
-
printPromptAndSelectionLine("Example template", example);
|
|
124
|
+
if (arg == "--dir") {
|
|
125
|
+
const next = rawArgs[i + 1];
|
|
126
|
+
if (next && !next.startsWith("-")) {
|
|
127
|
+
options.dir = next;
|
|
128
|
+
options.dirExplicit = true;
|
|
129
|
+
i++;
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
throw new Error("--dir requires a path value");
|
|
207
133
|
}
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
if (options.fuzzExample !== undefined || onboardingMode == "quick") {
|
|
213
|
-
printPromptAndSelectionLine("Add a basic fuzzer example?", fuzzExample ? "Yes" : "No");
|
|
134
|
+
if (arg.startsWith("--dir=")) {
|
|
135
|
+
options.dir = arg.slice("--dir=".length);
|
|
136
|
+
options.dirExplicit = true;
|
|
137
|
+
continue;
|
|
214
138
|
}
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
? false
|
|
218
|
-
: await askYesNo("Install dependencies now?", face, false));
|
|
219
|
-
if (options.install !== undefined || onboardingMode == "quick") {
|
|
220
|
-
printPromptAndSelectionLine("Install dependencies now?", installDependenciesNow ? "Yes" : "No");
|
|
139
|
+
if (arg.startsWith("-")) {
|
|
140
|
+
throw new Error(`Unknown init flag: ${arg}`);
|
|
221
141
|
}
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
142
|
+
positional.push(arg);
|
|
143
|
+
}
|
|
144
|
+
// First positional argument is always the target directory.
|
|
145
|
+
if (positional.length > 0) {
|
|
146
|
+
options.dir = positional.shift();
|
|
147
|
+
options.dirExplicit = true;
|
|
148
|
+
}
|
|
149
|
+
if (!options.target && positional.length > 0 && isTarget(positional[0])) {
|
|
150
|
+
options.target = positional.shift();
|
|
151
|
+
}
|
|
152
|
+
if (
|
|
153
|
+
!options.example &&
|
|
154
|
+
positional.length > 0 &&
|
|
155
|
+
isExampleMode(positional[0])
|
|
156
|
+
) {
|
|
157
|
+
options.example = positional.shift();
|
|
158
|
+
}
|
|
159
|
+
if (positional.length > 0) {
|
|
160
|
+
throw new Error(
|
|
161
|
+
`Unknown init argument(s): ${positional.join(", ")}. Usage: init [dir] [--target wasi|bindings|web] [--example minimal|full|none] [--fuzz-example|--no-fuzz-example] [--install] [--yes] [--force] [--dir <path>]`,
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
return options;
|
|
165
|
+
}
|
|
166
|
+
async function runInteractiveOnboarding(options, face) {
|
|
167
|
+
printOnboardingIntro();
|
|
168
|
+
const acknowledged = await askYesNo(
|
|
169
|
+
"I understand this command writes files and can run package manager installs. Continue?",
|
|
170
|
+
face,
|
|
171
|
+
true,
|
|
172
|
+
);
|
|
173
|
+
if (!acknowledged) return null;
|
|
174
|
+
const onboardingMode = await askMenuChoice(
|
|
175
|
+
"Onboarding mode",
|
|
176
|
+
[
|
|
177
|
+
{ value: "manual", label: "Manual (guided prompts)" },
|
|
178
|
+
{ value: "quick", label: "Quick (sensible defaults)" },
|
|
179
|
+
],
|
|
180
|
+
face,
|
|
181
|
+
"manual",
|
|
182
|
+
);
|
|
183
|
+
const workspacePrompt = "What do you want to set up? (default: ./)";
|
|
184
|
+
const defaultRoot = options.dir;
|
|
185
|
+
let selectedDir = defaultRoot;
|
|
186
|
+
if (options.dirExplicit || onboardingMode == "quick") {
|
|
187
|
+
selectedDir = options.dir;
|
|
188
|
+
} else {
|
|
189
|
+
const defaultDisplay = defaultRoot == "." ? "./" : defaultRoot;
|
|
190
|
+
const enteredDir = (
|
|
191
|
+
await ask(
|
|
192
|
+
`${chalk.bold.blue(`◇ ${workspacePrompt}`)}\n│ `,
|
|
193
|
+
face,
|
|
194
|
+
defaultDisplay,
|
|
195
|
+
)
|
|
196
|
+
).trim();
|
|
197
|
+
selectedDir = enteredDir.length ? enteredDir : defaultRoot;
|
|
198
|
+
}
|
|
199
|
+
const resolvedRoot = path.resolve(process.cwd(), selectedDir);
|
|
200
|
+
if (options.dirExplicit || onboardingMode == "quick") {
|
|
201
|
+
printPromptAndSelectionLine(workspacePrompt, resolvedRoot);
|
|
202
|
+
} else {
|
|
203
|
+
printSelectionLine(resolvedRoot);
|
|
204
|
+
}
|
|
205
|
+
const target =
|
|
206
|
+
options.target ??
|
|
207
|
+
(onboardingMode == "quick"
|
|
208
|
+
? "wasi"
|
|
209
|
+
: await askMenuChoice(
|
|
210
|
+
"Build target",
|
|
211
|
+
[
|
|
212
|
+
{
|
|
213
|
+
value: "wasi",
|
|
214
|
+
label:
|
|
215
|
+
"wasi (default runner: node .as-test/runners/default.wasi.js)",
|
|
216
|
+
},
|
|
217
|
+
{
|
|
218
|
+
value: "bindings",
|
|
219
|
+
label:
|
|
220
|
+
"bindings (default runner: node .as-test/runners/default.bindings.js)",
|
|
221
|
+
},
|
|
222
|
+
{
|
|
223
|
+
value: "web",
|
|
224
|
+
label:
|
|
225
|
+
"web (default runner: node .as-test/runners/default.web.js)",
|
|
226
|
+
},
|
|
227
|
+
],
|
|
228
|
+
face,
|
|
229
|
+
"wasi",
|
|
230
|
+
));
|
|
231
|
+
if (options.target || onboardingMode == "quick") {
|
|
232
|
+
printPromptAndSelectionLine("Build target", target);
|
|
233
|
+
}
|
|
234
|
+
const example =
|
|
235
|
+
options.example ??
|
|
236
|
+
(onboardingMode == "quick"
|
|
237
|
+
? "minimal"
|
|
238
|
+
: await askMenuChoice(
|
|
239
|
+
"Example template",
|
|
240
|
+
[
|
|
241
|
+
{ value: "minimal", label: "minimal (one short starter spec)" },
|
|
242
|
+
{ value: "full", label: "full (hooks, assertions, logs, suites)" },
|
|
243
|
+
{ value: "none", label: "none (config/runners only)" },
|
|
244
|
+
],
|
|
245
|
+
face,
|
|
246
|
+
"minimal",
|
|
247
|
+
));
|
|
248
|
+
if (options.example || onboardingMode == "quick") {
|
|
249
|
+
printPromptAndSelectionLine("Example template", example);
|
|
250
|
+
}
|
|
251
|
+
const fuzzExample =
|
|
252
|
+
options.fuzzExample ??
|
|
253
|
+
(onboardingMode == "quick"
|
|
254
|
+
? false
|
|
255
|
+
: await askYesNo("Add a basic fuzzer example?", face, false));
|
|
256
|
+
if (options.fuzzExample !== undefined || onboardingMode == "quick") {
|
|
257
|
+
printPromptAndSelectionLine(
|
|
258
|
+
"Add a basic fuzzer example?",
|
|
259
|
+
fuzzExample ? "Yes" : "No",
|
|
260
|
+
);
|
|
261
|
+
}
|
|
262
|
+
const installDependenciesNow =
|
|
263
|
+
options.install ??
|
|
264
|
+
(onboardingMode == "quick"
|
|
265
|
+
? false
|
|
266
|
+
: await askYesNo("Install dependencies now?", face, false));
|
|
267
|
+
if (options.install !== undefined || onboardingMode == "quick") {
|
|
268
|
+
printPromptAndSelectionLine(
|
|
269
|
+
"Install dependencies now?",
|
|
270
|
+
installDependenciesNow ? "Yes" : "No",
|
|
271
|
+
);
|
|
272
|
+
}
|
|
273
|
+
return {
|
|
274
|
+
root: resolvedRoot,
|
|
275
|
+
target,
|
|
276
|
+
example,
|
|
277
|
+
fuzzExample,
|
|
278
|
+
installDependenciesNow,
|
|
279
|
+
};
|
|
229
280
|
}
|
|
230
281
|
function printOnboardingHeader() {
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
282
|
+
// console.log(
|
|
283
|
+
// chalk.bold.cyan(
|
|
284
|
+
// `as-test ${getCliVersion()} — AssemblyScript testing without runtime guesswork.`,
|
|
285
|
+
// ) + "\n",
|
|
286
|
+
// );
|
|
236
287
|
}
|
|
237
288
|
function printOnboardingIntro() {
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
289
|
+
console.log(chalk.bold.blue("╔═╗ ╔═╗ ╔═╗ ╔═╗ ╔═╗ ╔═╗"));
|
|
290
|
+
console.log(chalk.bold.blue("╠═╣ ╚═╗ ══ ║ ╠═ ╚═╗ ║ "));
|
|
291
|
+
console.log(chalk.bold.blue("╩ ╩ ╚═╝ ╩ ╚═╝ ╚═╝ ╩ "));
|
|
292
|
+
console.log("");
|
|
293
|
+
// console.log(chalk.bold("┌") + " " + chalk.bold.blueBright(""));
|
|
294
|
+
// console.log("│");
|
|
295
|
+
// printPanel("Security", [
|
|
296
|
+
// "Security warning — please read.",
|
|
297
|
+
// "",
|
|
298
|
+
// "as-test is a local developer tool and executes build/runtime commands from your project config.",
|
|
299
|
+
// "If the config is untrusted, those commands can run arbitrary programs on your machine.",
|
|
300
|
+
// "",
|
|
301
|
+
// "Recommended baseline:",
|
|
302
|
+
// "- Keep this tool scoped to trusted repositories.",
|
|
303
|
+
// "- Review runOptions.runtime.cmd and buildOptions.cmd before running.",
|
|
304
|
+
// "- Prefer least-privilege shells/environments for shared machines and CI.",
|
|
305
|
+
// "",
|
|
306
|
+
// "Run regularly: ast doctor and ast test --list",
|
|
307
|
+
// "Read docs: README.md (Configuration + Setup Diagnostics sections).",
|
|
308
|
+
// ]);
|
|
309
|
+
// console.log("│");
|
|
259
310
|
}
|
|
260
311
|
// function printPanel(title: string, lines: string[]): void {
|
|
261
312
|
// const innerWidth = Math.max(32, (process.stdout.columns ?? 80) - 6);
|
|
@@ -297,631 +348,691 @@ function printOnboardingIntro() {
|
|
|
297
348
|
// return lines;
|
|
298
349
|
// }
|
|
299
350
|
function printPromptAndSelectionLine(prompt, answer) {
|
|
300
|
-
|
|
301
|
-
|
|
351
|
+
console.log(chalk.bold.blue(`◇ ${prompt}`));
|
|
352
|
+
printSelectionLine(answer);
|
|
302
353
|
}
|
|
303
354
|
function printSelectionLine(answer) {
|
|
304
|
-
|
|
305
|
-
|
|
355
|
+
console.log(`│ ${chalk.gray(answer)}`);
|
|
356
|
+
console.log("│");
|
|
306
357
|
}
|
|
307
358
|
function parseTarget(value) {
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
359
|
+
if (!isTarget(value)) {
|
|
360
|
+
throw new Error(`Invalid target "${value}". Expected wasi|bindings|web`);
|
|
361
|
+
}
|
|
362
|
+
return value;
|
|
312
363
|
}
|
|
313
364
|
function parseExampleMode(value) {
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
365
|
+
if (!isExampleMode(value)) {
|
|
366
|
+
throw new Error(
|
|
367
|
+
`Invalid example mode "${value}". Expected minimal|full|none`,
|
|
368
|
+
);
|
|
369
|
+
}
|
|
370
|
+
return value;
|
|
318
371
|
}
|
|
319
372
|
function isTarget(value) {
|
|
320
|
-
|
|
373
|
+
return TARGETS.includes(value);
|
|
321
374
|
}
|
|
322
375
|
function isExampleMode(value) {
|
|
323
|
-
|
|
376
|
+
return EXAMPLE_MODES.includes(value);
|
|
324
377
|
}
|
|
325
378
|
function printPlan(root, target, example, fuzzExample, install) {
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
}
|
|
343
|
-
if (isDir) {
|
|
344
|
-
child.isDir = true;
|
|
345
|
-
}
|
|
346
|
-
return child;
|
|
347
|
-
};
|
|
348
|
-
const buildTree = (entries) => {
|
|
349
|
-
const rootNode = {
|
|
350
|
-
name: "",
|
|
351
|
-
relPath: "",
|
|
352
|
-
isDir: true,
|
|
353
|
-
children: [],
|
|
354
|
-
};
|
|
355
|
-
for (const entry of entries) {
|
|
356
|
-
const parts = entry.path.split("/").filter((part) => part.length > 0);
|
|
357
|
-
let cursor = rootNode;
|
|
358
|
-
let relPath = "";
|
|
359
|
-
for (let i = 0; i < parts.length; i++) {
|
|
360
|
-
const part = parts[i];
|
|
361
|
-
relPath = relPath ? `${relPath}/${part}` : part;
|
|
362
|
-
const isLeaf = i == parts.length - 1;
|
|
363
|
-
cursor = ensureChild(cursor, part, relPath, isLeaf ? entry.isDir : true);
|
|
364
|
-
}
|
|
365
|
-
}
|
|
366
|
-
return rootNode;
|
|
367
|
-
};
|
|
368
|
-
const renderBranch = (nodes, prefix) => {
|
|
369
|
-
for (let i = 0; i < nodes.length; i++) {
|
|
370
|
-
const node = nodes[i];
|
|
371
|
-
const isLast = i == nodes.length - 1;
|
|
372
|
-
const branch = isLast ? "└── " : "├── ";
|
|
373
|
-
const treeGlyphs = chalk.dim(`${prefix}${branch}`);
|
|
374
|
-
console.log(`│ ${treeGlyphs}${paintNode(node)}`);
|
|
375
|
-
if (node.children.length > 0) {
|
|
376
|
-
const childPrefix = `${prefix}${isLast ? " " : "│ "}`;
|
|
377
|
-
renderBranch(node.children, childPrefix);
|
|
378
|
-
}
|
|
379
|
-
}
|
|
380
|
-
};
|
|
381
|
-
const fileEntries = [
|
|
382
|
-
{ path: ".as-test", isDir: true },
|
|
383
|
-
{ path: ".as-test/build", isDir: true },
|
|
384
|
-
{ path: ".as-test/logs", isDir: true },
|
|
385
|
-
{ path: ".as-test/coverage", isDir: true },
|
|
386
|
-
{ path: ".as-test/snapshots", isDir: true },
|
|
387
|
-
{ path: "assembly", isDir: true },
|
|
388
|
-
{ path: "assembly/tsconfig.json", isDir: false },
|
|
389
|
-
{ path: "assembly/__tests__", isDir: true },
|
|
390
|
-
{ path: "as-test.config.json", isDir: false },
|
|
391
|
-
{ path: "package.json", isDir: false },
|
|
392
|
-
];
|
|
393
|
-
if (target == "wasi" || target == "bindings" || target == "web") {
|
|
394
|
-
fileEntries.push({ path: ".as-test/runners", isDir: true });
|
|
395
|
-
fileEntries.push({
|
|
396
|
-
path: ".as-test/runners/default.bindings.js",
|
|
397
|
-
isDir: false,
|
|
398
|
-
});
|
|
399
|
-
fileEntries.push({
|
|
400
|
-
path: ".as-test/runners/default.wasi.js",
|
|
401
|
-
isDir: false,
|
|
402
|
-
});
|
|
403
|
-
fileEntries.push({
|
|
404
|
-
path: ".as-test/runners/default.web.js",
|
|
405
|
-
isDir: false,
|
|
406
|
-
});
|
|
379
|
+
const displayRoot = () => {
|
|
380
|
+
const rel = path.relative(process.cwd(), root).split(path.sep).join("/");
|
|
381
|
+
if (!rel || rel == ".") return "./";
|
|
382
|
+
if (rel.startsWith("..")) return rel;
|
|
383
|
+
return `./${rel}`;
|
|
384
|
+
};
|
|
385
|
+
const statusColor = (relPath) =>
|
|
386
|
+
existsSync(path.join(root, relPath)) ? chalk.hex("#d29922") : chalk.green;
|
|
387
|
+
const paintNode = (node) =>
|
|
388
|
+
statusColor(node.relPath)(node.isDir ? `${node.name}/` : node.name);
|
|
389
|
+
const ensureChild = (parent, name, relPath, isDir) => {
|
|
390
|
+
let child = parent.children.find((entry) => entry.name == name);
|
|
391
|
+
if (!child) {
|
|
392
|
+
child = { name, relPath, isDir, children: [] };
|
|
393
|
+
parent.children.push(child);
|
|
394
|
+
return child;
|
|
407
395
|
}
|
|
408
|
-
if (
|
|
409
|
-
|
|
410
|
-
path: "assembly/__tests__/example.spec.ts",
|
|
411
|
-
isDir: false,
|
|
412
|
-
});
|
|
396
|
+
if (isDir) {
|
|
397
|
+
child.isDir = true;
|
|
413
398
|
}
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
399
|
+
return child;
|
|
400
|
+
};
|
|
401
|
+
const buildTree = (entries) => {
|
|
402
|
+
const rootNode = {
|
|
403
|
+
name: "",
|
|
404
|
+
relPath: "",
|
|
405
|
+
isDir: true,
|
|
406
|
+
children: [],
|
|
407
|
+
};
|
|
408
|
+
for (const entry of entries) {
|
|
409
|
+
const parts = entry.path.split("/").filter((part) => part.length > 0);
|
|
410
|
+
let cursor = rootNode;
|
|
411
|
+
let relPath = "";
|
|
412
|
+
for (let i = 0; i < parts.length; i++) {
|
|
413
|
+
const part = parts[i];
|
|
414
|
+
relPath = relPath ? `${relPath}/${part}` : part;
|
|
415
|
+
const isLeaf = i == parts.length - 1;
|
|
416
|
+
cursor = ensureChild(
|
|
417
|
+
cursor,
|
|
418
|
+
part,
|
|
419
|
+
relPath,
|
|
420
|
+
isLeaf ? entry.isDir : true,
|
|
421
|
+
);
|
|
422
|
+
}
|
|
420
423
|
}
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
424
|
+
return rootNode;
|
|
425
|
+
};
|
|
426
|
+
const renderBranch = (nodes, prefix) => {
|
|
427
|
+
for (let i = 0; i < nodes.length; i++) {
|
|
428
|
+
const node = nodes[i];
|
|
429
|
+
const isLast = i == nodes.length - 1;
|
|
430
|
+
const branch = isLast ? "└── " : "├── ";
|
|
431
|
+
const treeGlyphs = chalk.dim(`${prefix}${branch}`);
|
|
432
|
+
console.log(`│ ${treeGlyphs}${paintNode(node)}`);
|
|
433
|
+
if (node.children.length > 0) {
|
|
434
|
+
const childPrefix = `${prefix}${isLast ? " " : "│ "}`;
|
|
435
|
+
renderBranch(node.children, childPrefix);
|
|
436
|
+
}
|
|
432
437
|
}
|
|
433
|
-
|
|
438
|
+
};
|
|
439
|
+
const fileEntries = [
|
|
440
|
+
{ path: ".as-test", isDir: true },
|
|
441
|
+
{ path: ".as-test/build", isDir: true },
|
|
442
|
+
{ path: ".as-test/logs", isDir: true },
|
|
443
|
+
{ path: ".as-test/coverage", isDir: true },
|
|
444
|
+
{ path: ".as-test/snapshots", isDir: true },
|
|
445
|
+
{ path: "assembly", isDir: true },
|
|
446
|
+
{ path: "assembly/tsconfig.json", isDir: false },
|
|
447
|
+
{ path: "assembly/__tests__", isDir: true },
|
|
448
|
+
{ path: "as-test.config.json", isDir: false },
|
|
449
|
+
{ path: "package.json", isDir: false },
|
|
450
|
+
];
|
|
451
|
+
if (target == "wasi" || target == "bindings" || target == "web") {
|
|
452
|
+
fileEntries.push({ path: ".as-test/runners", isDir: true });
|
|
453
|
+
fileEntries.push({
|
|
454
|
+
path: ".as-test/runners/default.bindings.js",
|
|
455
|
+
isDir: false,
|
|
456
|
+
});
|
|
457
|
+
fileEntries.push({
|
|
458
|
+
path: ".as-test/runners/default.wasi.js",
|
|
459
|
+
isDir: false,
|
|
460
|
+
});
|
|
461
|
+
fileEntries.push({
|
|
462
|
+
path: ".as-test/runners/default.web.js",
|
|
463
|
+
isDir: false,
|
|
464
|
+
});
|
|
465
|
+
}
|
|
466
|
+
if (example != "none") {
|
|
467
|
+
fileEntries.push({
|
|
468
|
+
path: "assembly/__tests__/example.spec.ts",
|
|
469
|
+
isDir: false,
|
|
470
|
+
});
|
|
471
|
+
}
|
|
472
|
+
if (fuzzExample) {
|
|
473
|
+
fileEntries.push({ path: "assembly/__fuzz__", isDir: true });
|
|
474
|
+
fileEntries.push({
|
|
475
|
+
path: "assembly/__fuzz__/example.fuzz.ts",
|
|
476
|
+
isDir: false,
|
|
477
|
+
});
|
|
478
|
+
}
|
|
479
|
+
const treeRoot = buildTree(fileEntries);
|
|
480
|
+
console.log(chalk.bold.blue("◇ Planned Changes"));
|
|
481
|
+
console.log("│" + chalk.dim(` - Target: ${target}`));
|
|
482
|
+
console.log("│" + chalk.dim(` - Example: ${example}`));
|
|
483
|
+
console.log(
|
|
484
|
+
"│" + chalk.dim(` - Fuzzer example: ${fuzzExample ? "yes" : "no"}`),
|
|
485
|
+
);
|
|
486
|
+
console.log("│" + chalk.dim(` - Directory: ${displayRoot()}`));
|
|
487
|
+
console.log(
|
|
488
|
+
"│" + chalk.dim(` - Install dependencies: ${install ? "yes" : "no"}`),
|
|
489
|
+
);
|
|
490
|
+
console.log("│" + chalk.bold.blue(" File Changes"));
|
|
491
|
+
for (const topLevelNode of treeRoot.children) {
|
|
492
|
+
console.log(`│ ${paintNode(topLevelNode)}`);
|
|
493
|
+
renderBranch(topLevelNode.children, "");
|
|
494
|
+
}
|
|
495
|
+
console.log("│");
|
|
434
496
|
}
|
|
435
497
|
function applyInit(root, target, example, fuzzExample, force) {
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
498
|
+
const summary = {
|
|
499
|
+
created: [],
|
|
500
|
+
updated: [],
|
|
501
|
+
skipped: [],
|
|
502
|
+
};
|
|
503
|
+
ensureDir(root, ".as-test/build", summary);
|
|
504
|
+
ensureDir(root, ".as-test/logs", summary);
|
|
505
|
+
ensureDir(root, ".as-test/coverage", summary);
|
|
506
|
+
ensureDir(root, ".as-test/snapshots", summary);
|
|
507
|
+
ensureDir(root, "assembly/__tests__", summary);
|
|
508
|
+
if (fuzzExample) {
|
|
509
|
+
ensureDir(root, "assembly/__fuzz__", summary);
|
|
510
|
+
}
|
|
511
|
+
if (target == "wasi" || target == "bindings" || target == "web") {
|
|
512
|
+
ensureDir(root, ".as-test/runners", summary);
|
|
513
|
+
}
|
|
514
|
+
ensureGitignoreIncludesAsTestDirs(root, summary);
|
|
515
|
+
writeJson(
|
|
516
|
+
path.join(root, "assembly/tsconfig.json"),
|
|
517
|
+
buildAssemblyTsconfig(),
|
|
518
|
+
summary,
|
|
519
|
+
"assembly/tsconfig.json",
|
|
520
|
+
);
|
|
521
|
+
const configPath = path.join(root, "as-test.config.json");
|
|
522
|
+
const config = {
|
|
523
|
+
$schema: "node_modules/as-test/as-test.config.schema.json",
|
|
524
|
+
input: ["assembly/__tests__/*.spec.ts"],
|
|
525
|
+
output: ".as-test/",
|
|
526
|
+
config: "none",
|
|
527
|
+
coverage: false,
|
|
528
|
+
env: {},
|
|
529
|
+
...(fuzzExample
|
|
530
|
+
? {
|
|
531
|
+
fuzz: {
|
|
532
|
+
input: ["assembly/__fuzz__/*.fuzz.ts"],
|
|
533
|
+
runs: 1000,
|
|
534
|
+
target: "bindings",
|
|
535
|
+
corpusDir: ".as-test/corpus",
|
|
536
|
+
crashDir: ".as-test/crashes",
|
|
537
|
+
},
|
|
538
|
+
}
|
|
539
|
+
: {}),
|
|
540
|
+
buildOptions: {
|
|
541
|
+
target,
|
|
542
|
+
},
|
|
543
|
+
runOptions: {
|
|
544
|
+
runtime: {
|
|
545
|
+
cmd:
|
|
546
|
+
target == "wasi"
|
|
547
|
+
? "node .as-test/runners/default.wasi.js"
|
|
548
|
+
: target == "bindings"
|
|
549
|
+
? "node .as-test/runners/default.bindings.js"
|
|
550
|
+
: "node .as-test/runners/default.web.js",
|
|
551
|
+
},
|
|
552
|
+
reporter: "default",
|
|
553
|
+
},
|
|
554
|
+
modes:
|
|
555
|
+
target == "web"
|
|
556
|
+
? {
|
|
557
|
+
web: {
|
|
558
|
+
default: false,
|
|
559
|
+
runOptions: {
|
|
560
|
+
runtime: {
|
|
561
|
+
cmd: "node .as-test/runners/default.web.js",
|
|
470
562
|
},
|
|
471
|
-
|
|
472
|
-
: {}),
|
|
473
|
-
buildOptions: {
|
|
474
|
-
target,
|
|
475
|
-
},
|
|
476
|
-
runOptions: {
|
|
477
|
-
runtime: {
|
|
478
|
-
cmd: target == "wasi"
|
|
479
|
-
? "node .as-test/runners/default.wasi.js"
|
|
480
|
-
: target == "bindings"
|
|
481
|
-
? "node .as-test/runners/default.bindings.js"
|
|
482
|
-
: "node .as-test/runners/default.web.js",
|
|
563
|
+
},
|
|
483
564
|
},
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
default: false,
|
|
490
|
-
runOptions: {
|
|
491
|
-
runtime: {
|
|
492
|
-
cmd: "node .as-test/runners/default.web.js",
|
|
493
|
-
},
|
|
494
|
-
},
|
|
495
|
-
},
|
|
496
|
-
"web-headless": {
|
|
497
|
-
default: false,
|
|
498
|
-
runOptions: {
|
|
499
|
-
runtime: {
|
|
500
|
-
cmd: "node .as-test/runners/default.web.js --headless",
|
|
501
|
-
},
|
|
502
|
-
},
|
|
565
|
+
"web-headless": {
|
|
566
|
+
default: false,
|
|
567
|
+
runOptions: {
|
|
568
|
+
runtime: {
|
|
569
|
+
cmd: "node .as-test/runners/default.web.js --headless",
|
|
503
570
|
},
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
571
|
+
},
|
|
572
|
+
},
|
|
573
|
+
}
|
|
574
|
+
: {},
|
|
575
|
+
};
|
|
576
|
+
writeJson(configPath, config, summary, "as-test.config.json");
|
|
577
|
+
if (example != "none") {
|
|
578
|
+
const examplePath = path.join(root, "assembly/__tests__/example.spec.ts");
|
|
579
|
+
const content =
|
|
580
|
+
example == "minimal" ? buildMinimalExampleSpec() : buildFullExampleSpec();
|
|
581
|
+
writeManagedFile(
|
|
582
|
+
examplePath,
|
|
583
|
+
content,
|
|
584
|
+
force,
|
|
585
|
+
summary,
|
|
586
|
+
"assembly/__tests__/example.spec.ts",
|
|
587
|
+
);
|
|
588
|
+
}
|
|
589
|
+
if (fuzzExample) {
|
|
590
|
+
const fuzzPath = path.join(root, "assembly/__fuzz__/example.fuzz.ts");
|
|
591
|
+
writeManagedFile(
|
|
592
|
+
fuzzPath,
|
|
593
|
+
buildBasicFuzzerExample(),
|
|
594
|
+
force,
|
|
595
|
+
summary,
|
|
596
|
+
"assembly/__fuzz__/example.fuzz.ts",
|
|
597
|
+
);
|
|
598
|
+
}
|
|
599
|
+
if (target == "wasi" || target == "bindings" || target == "web") {
|
|
600
|
+
const runnerPath = path.join(root, ".as-test/runners/default.wasi.js");
|
|
601
|
+
writeManagedFile(
|
|
602
|
+
runnerPath,
|
|
603
|
+
buildWasiRunner(),
|
|
604
|
+
force,
|
|
605
|
+
summary,
|
|
606
|
+
".as-test/runners/default.wasi.js",
|
|
607
|
+
);
|
|
608
|
+
}
|
|
609
|
+
if (target == "wasi" || target == "bindings" || target == "web") {
|
|
610
|
+
const runnerPath = path.join(root, ".as-test/runners/default.bindings.js");
|
|
611
|
+
writeManagedFile(
|
|
612
|
+
runnerPath,
|
|
613
|
+
buildBindingsRunner(),
|
|
614
|
+
force,
|
|
615
|
+
summary,
|
|
616
|
+
".as-test/runners/default.bindings.js",
|
|
617
|
+
);
|
|
618
|
+
}
|
|
619
|
+
if (target == "wasi" || target == "bindings" || target == "web") {
|
|
620
|
+
const runnerPath = path.join(root, ".as-test/runners/default.web.js");
|
|
621
|
+
writeManagedFile(
|
|
622
|
+
runnerPath,
|
|
623
|
+
buildWebRunnerSource(),
|
|
624
|
+
force,
|
|
625
|
+
summary,
|
|
626
|
+
".as-test/runners/default.web.js",
|
|
627
|
+
);
|
|
628
|
+
}
|
|
629
|
+
const pkgPath = path.join(root, "package.json");
|
|
630
|
+
const pkg = existsSync(pkgPath)
|
|
631
|
+
? JSON.parse(readFileSync(pkgPath, "utf8"))
|
|
632
|
+
: {};
|
|
633
|
+
if (!pkg.scripts || typeof pkg.scripts != "object") {
|
|
634
|
+
pkg.scripts = {};
|
|
635
|
+
}
|
|
636
|
+
const scripts = pkg.scripts;
|
|
637
|
+
if (!scripts.test) {
|
|
638
|
+
scripts.test = "ast test";
|
|
639
|
+
}
|
|
640
|
+
if (fuzzExample && !scripts.fuzz) {
|
|
641
|
+
scripts.fuzz = "ast fuzz";
|
|
642
|
+
}
|
|
643
|
+
if (!pkg.type) {
|
|
644
|
+
pkg.type = "module";
|
|
645
|
+
}
|
|
646
|
+
if (!pkg.devDependencies || typeof pkg.devDependencies != "object") {
|
|
647
|
+
pkg.devDependencies = {};
|
|
648
|
+
}
|
|
649
|
+
const devDependencies = pkg.devDependencies;
|
|
650
|
+
if (!devDependencies["as-test"]) {
|
|
651
|
+
devDependencies["as-test"] = "^" + getCliVersion();
|
|
652
|
+
}
|
|
653
|
+
if (!hasDependency(pkg, "assemblyscript")) {
|
|
654
|
+
devDependencies["assemblyscript"] = "^0.28.9";
|
|
655
|
+
}
|
|
656
|
+
if (target == "wasi" && !devDependencies["@assemblyscript/wasi-shim"]) {
|
|
657
|
+
devDependencies["@assemblyscript/wasi-shim"] = "^0.1.0";
|
|
658
|
+
}
|
|
659
|
+
if (target == "bindings" && !pkg.type) {
|
|
660
|
+
pkg.type = "module";
|
|
661
|
+
}
|
|
662
|
+
writeJson(pkgPath, pkg, summary, "package.json");
|
|
663
|
+
return summary;
|
|
564
664
|
}
|
|
565
665
|
function hasDependency(pkg, dependency) {
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
}
|
|
574
|
-
return false;
|
|
666
|
+
const sections = ["dependencies", "devDependencies", "peerDependencies"];
|
|
667
|
+
for (const section of sections) {
|
|
668
|
+
const value = pkg[section];
|
|
669
|
+
if (!value || typeof value != "object" || Array.isArray(value)) continue;
|
|
670
|
+
if (dependency in value) return true;
|
|
671
|
+
}
|
|
672
|
+
return false;
|
|
575
673
|
}
|
|
576
674
|
function ensureDir(root, rel, summary) {
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
summary.created.push(rel + "/");
|
|
675
|
+
const full = path.join(root, rel);
|
|
676
|
+
if (existsSync(full)) return;
|
|
677
|
+
mkdirSync(full, { recursive: true });
|
|
678
|
+
summary.created.push(rel + "/");
|
|
582
679
|
}
|
|
583
680
|
function ensureGitignoreIncludesAsTestDirs(root, summary) {
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
output +=
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
681
|
+
const rel = ".gitignore";
|
|
682
|
+
const fullPath = path.join(root, rel);
|
|
683
|
+
const entries = [
|
|
684
|
+
"# Include essential as-test artifacts",
|
|
685
|
+
"!.as-test/",
|
|
686
|
+
".as-test/*",
|
|
687
|
+
"!.as-test/runners/",
|
|
688
|
+
"!.as-test/snapshots/",
|
|
689
|
+
];
|
|
690
|
+
const existed = existsSync(fullPath);
|
|
691
|
+
const source = existed ? readFileSync(fullPath, "utf8") : "";
|
|
692
|
+
const lines = source.split(/\r?\n/);
|
|
693
|
+
const missing = entries.filter(
|
|
694
|
+
(entry) => !lines.some((line) => line.trim() == entry),
|
|
695
|
+
);
|
|
696
|
+
if (!missing.length) {
|
|
697
|
+
return;
|
|
698
|
+
}
|
|
699
|
+
const eol = source.includes("\r\n") ? "\r\n" : "\n";
|
|
700
|
+
let output = source;
|
|
701
|
+
if (output.length && !output.endsWith("\n") && !output.endsWith("\r\n")) {
|
|
702
|
+
output += eol;
|
|
703
|
+
}
|
|
704
|
+
output += missing.join(eol) + eol;
|
|
705
|
+
writeFileSync(fullPath, output);
|
|
706
|
+
if (existed) summary.updated.push(rel);
|
|
707
|
+
else summary.created.push(rel);
|
|
611
708
|
}
|
|
612
709
|
function buildAssemblyTsconfig() {
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
710
|
+
return {
|
|
711
|
+
extends: "assemblyscript/std/assembly.json",
|
|
712
|
+
include: ["./**/*.ts"],
|
|
713
|
+
};
|
|
617
714
|
}
|
|
618
715
|
function writeJson(fullPath, value, summary, displayPath) {
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
summary.created.push(rel);
|
|
716
|
+
const rel =
|
|
717
|
+
displayPath ??
|
|
718
|
+
path.relative(process.cwd(), fullPath) ??
|
|
719
|
+
path.basename(fullPath);
|
|
720
|
+
const existed = existsSync(fullPath);
|
|
721
|
+
const data = JSON.stringify(value, null, 2) + "\n";
|
|
722
|
+
writeFileSync(fullPath, data);
|
|
723
|
+
if (existed) summary.updated.push(rel);
|
|
724
|
+
else summary.created.push(rel);
|
|
629
725
|
}
|
|
630
726
|
function writeManagedFile(fullPath, data, force, summary, displayPath) {
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
}
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
summary.created.push(rel);
|
|
727
|
+
const rel =
|
|
728
|
+
displayPath ??
|
|
729
|
+
path.relative(process.cwd(), fullPath) ??
|
|
730
|
+
path.basename(fullPath);
|
|
731
|
+
const existed = existsSync(fullPath);
|
|
732
|
+
if (existed && !force) {
|
|
733
|
+
summary.skipped.push(rel);
|
|
734
|
+
return;
|
|
735
|
+
}
|
|
736
|
+
if (!existsSync(path.dirname(fullPath))) {
|
|
737
|
+
mkdirSync(path.dirname(fullPath), { recursive: true });
|
|
738
|
+
}
|
|
739
|
+
writeFileSync(fullPath, data);
|
|
740
|
+
if (existed) summary.updated.push(rel);
|
|
741
|
+
else summary.created.push(rel);
|
|
647
742
|
}
|
|
648
743
|
function printSummary(summary) {
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
}
|
|
744
|
+
console.log("│");
|
|
745
|
+
if (summary.created.length) {
|
|
746
|
+
console.log(chalk.bold("│ Created:"));
|
|
747
|
+
for (const item of summary.created) {
|
|
748
|
+
console.log(`│ + ${item}`);
|
|
655
749
|
}
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
750
|
+
}
|
|
751
|
+
if (summary.updated.length) {
|
|
752
|
+
console.log(chalk.bold("│ Updated:"));
|
|
753
|
+
for (const item of summary.updated) {
|
|
754
|
+
console.log(`│ ~ ${item}`);
|
|
661
755
|
}
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
756
|
+
}
|
|
757
|
+
if (summary.skipped.length) {
|
|
758
|
+
console.log(chalk.bold("│ Skipped (exists, use --force to overwrite):"));
|
|
759
|
+
for (const item of summary.skipped) {
|
|
760
|
+
console.log(`│ = ${item}`);
|
|
667
761
|
}
|
|
668
|
-
|
|
762
|
+
}
|
|
763
|
+
console.log("│");
|
|
669
764
|
}
|
|
670
765
|
function ask(question, face, initialValue) {
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
face.write(initialValue);
|
|
686
|
-
}
|
|
766
|
+
if (!face) {
|
|
767
|
+
throw new Error(
|
|
768
|
+
"interactive input is unavailable; pass --yes with options",
|
|
769
|
+
);
|
|
770
|
+
}
|
|
771
|
+
return new Promise((res) => {
|
|
772
|
+
face.question(question, (answer) => {
|
|
773
|
+
const stdout = process.stdout;
|
|
774
|
+
if (stdout.isTTY) {
|
|
775
|
+
stdout.write("\x1b[1A");
|
|
776
|
+
stdout.write("\x1b[2K");
|
|
777
|
+
stdout.write("\r");
|
|
778
|
+
}
|
|
779
|
+
res(answer);
|
|
687
780
|
});
|
|
781
|
+
if (initialValue && initialValue.length) {
|
|
782
|
+
face.write(initialValue);
|
|
783
|
+
}
|
|
784
|
+
});
|
|
688
785
|
}
|
|
689
786
|
async function askChoice(label, choices, face, fallback) {
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
787
|
+
if (!face) {
|
|
788
|
+
return fallback;
|
|
789
|
+
}
|
|
790
|
+
const answer = (
|
|
791
|
+
await ask(
|
|
792
|
+
`${label} [${choices.join("/")}] (${fallback}) -> `,
|
|
793
|
+
face,
|
|
794
|
+
fallback,
|
|
795
|
+
)
|
|
796
|
+
)
|
|
797
|
+
.trim()
|
|
798
|
+
.toLowerCase();
|
|
799
|
+
if (!answer.length) return fallback;
|
|
800
|
+
if (choices.includes(answer)) return answer;
|
|
801
|
+
throw new Error(`Invalid choice "${answer}" for ${label}`);
|
|
701
802
|
}
|
|
702
803
|
async function askMenuChoice(label, choices, face, fallback) {
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
return askMenuChoiceWithArrows(label, choices, face, fallbackValue);
|
|
804
|
+
const fallbackValue = choices.some((choice) => choice.value == fallback)
|
|
805
|
+
? fallback
|
|
806
|
+
: choices[0].value;
|
|
807
|
+
if (!face) return fallbackValue;
|
|
808
|
+
if (!canUseArrowMenu(face)) {
|
|
809
|
+
const values = choices.map((choice) => choice.value);
|
|
810
|
+
return askChoice(label, values, face, fallbackValue);
|
|
811
|
+
}
|
|
812
|
+
return askMenuChoiceWithArrows(label, choices, face, fallbackValue);
|
|
713
813
|
}
|
|
714
814
|
async function askYesNo(label, face, fallback) {
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
815
|
+
if (!face) return fallback;
|
|
816
|
+
if (canUseArrowMenu(face)) {
|
|
817
|
+
const selected = await askMenuChoice(
|
|
818
|
+
label,
|
|
819
|
+
[
|
|
820
|
+
{ value: "yes", label: "Yes" },
|
|
821
|
+
{ value: "no", label: "No" },
|
|
822
|
+
],
|
|
823
|
+
face,
|
|
824
|
+
fallback ? "yes" : "no",
|
|
825
|
+
);
|
|
826
|
+
return selected == "yes";
|
|
827
|
+
}
|
|
828
|
+
const suffix = fallback ? "[Y/n]" : "[y/N]";
|
|
829
|
+
const defaultValue = fallback ? "yes" : "no";
|
|
830
|
+
const answer = (await ask(`${label} ${suffix} `, face, defaultValue))
|
|
831
|
+
.trim()
|
|
832
|
+
.toLowerCase();
|
|
833
|
+
if (!answer.length) return fallback;
|
|
834
|
+
if (answer == "y" || answer == "yes") return true;
|
|
835
|
+
if (answer == "n" || answer == "no") return false;
|
|
836
|
+
throw new Error(`Invalid answer "${answer}". Expected yes or no.`);
|
|
736
837
|
}
|
|
737
838
|
function canUseArrowMenu(face) {
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
839
|
+
if (!face) return false;
|
|
840
|
+
const stdin = process.stdin;
|
|
841
|
+
const stdout = process.stdout;
|
|
842
|
+
return (
|
|
843
|
+
Boolean(stdin.isTTY) &&
|
|
844
|
+
Boolean(stdout.isTTY) &&
|
|
845
|
+
typeof stdin.setRawMode == "function"
|
|
846
|
+
);
|
|
745
847
|
}
|
|
746
848
|
async function askMenuChoiceWithArrows(label, choices, face, fallback) {
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
};
|
|
761
|
-
|
|
762
|
-
const
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
849
|
+
const stdin = process.stdin;
|
|
850
|
+
const stdout = process.stdout;
|
|
851
|
+
const fallbackIndex = choices.findIndex((choice) => choice.value == fallback);
|
|
852
|
+
let selectedIndex = fallbackIndex == -1 ? 0 : fallbackIndex;
|
|
853
|
+
let renderedLineCount = 0;
|
|
854
|
+
const previousRawMode = Boolean(stdin.isRaw);
|
|
855
|
+
const lineWidth = Math.max(20, (stdout.columns ?? 80) - 2);
|
|
856
|
+
const clamp = (value, max) => {
|
|
857
|
+
if (value.length <= max) return value;
|
|
858
|
+
if (max <= 1) return value.slice(0, max);
|
|
859
|
+
return `${value.slice(0, max - 1)}…`;
|
|
860
|
+
};
|
|
861
|
+
const titleLine = () =>
|
|
862
|
+
chalk.bold.blue(`◆ ${clamp(label, Math.max(8, lineWidth - 3))}`);
|
|
863
|
+
const menuLines = () => {
|
|
864
|
+
const lines = [titleLine()];
|
|
865
|
+
for (let i = 0; i < choices.length; i++) {
|
|
866
|
+
const choice = choices[i];
|
|
867
|
+
const marker = i == selectedIndex ? chalk.blue("●") : chalk.dim("○");
|
|
868
|
+
lines.push(
|
|
869
|
+
`│ ${marker} ${clamp(choice.label, Math.max(8, lineWidth - 6))}`,
|
|
870
|
+
);
|
|
871
|
+
}
|
|
872
|
+
lines.push("│");
|
|
873
|
+
return lines;
|
|
874
|
+
};
|
|
875
|
+
const collapsedLines = () => {
|
|
876
|
+
const selected = choices[selectedIndex];
|
|
877
|
+
return [
|
|
878
|
+
`│ ${chalk.gray(clamp(selected.label, Math.max(8, lineWidth - 4)))}`,
|
|
879
|
+
];
|
|
880
|
+
};
|
|
881
|
+
const writeLines = (lines, collapse = false) => {
|
|
882
|
+
if (renderedLineCount > 0) {
|
|
883
|
+
process.stdout.write(`\x1b[${renderedLineCount}A`);
|
|
884
|
+
}
|
|
885
|
+
const totalLineCount = Math.max(lines.length, renderedLineCount);
|
|
886
|
+
for (let i = 0; i < totalLineCount; i++) {
|
|
887
|
+
process.stdout.write("\x1b[2K");
|
|
888
|
+
if (i < lines.length) {
|
|
889
|
+
process.stdout.write(lines[i]);
|
|
890
|
+
}
|
|
891
|
+
process.stdout.write("\n");
|
|
892
|
+
}
|
|
893
|
+
renderedLineCount = lines.length;
|
|
894
|
+
if (collapse) {
|
|
895
|
+
renderedLineCount = 0;
|
|
896
|
+
}
|
|
897
|
+
};
|
|
898
|
+
const collapseInPlace = () => {
|
|
899
|
+
const lines = collapsedLines();
|
|
900
|
+
if (renderedLineCount > 0) {
|
|
901
|
+
process.stdout.write(`\x1b[${renderedLineCount}A`);
|
|
902
|
+
}
|
|
903
|
+
const totalLineCount = Math.max(renderedLineCount, lines.length);
|
|
904
|
+
for (let i = 0; i < totalLineCount; i++) {
|
|
905
|
+
process.stdout.write("\r\x1b[2K");
|
|
906
|
+
if (i < lines.length) {
|
|
907
|
+
process.stdout.write(lines[i]);
|
|
908
|
+
}
|
|
909
|
+
process.stdout.write("\n");
|
|
910
|
+
}
|
|
911
|
+
const extraLines = totalLineCount - lines.length;
|
|
912
|
+
if (extraLines > 0) {
|
|
913
|
+
process.stdout.write(`\x1b[${extraLines}A`);
|
|
914
|
+
}
|
|
915
|
+
renderedLineCount = 0;
|
|
916
|
+
};
|
|
917
|
+
return new Promise((resolve, reject) => {
|
|
918
|
+
let settled = false;
|
|
919
|
+
const cleanup = () => {
|
|
920
|
+
stdin.off("data", onData);
|
|
921
|
+
if (stdin.isTTY) {
|
|
922
|
+
stdin.setRawMode(previousRawMode);
|
|
923
|
+
}
|
|
924
|
+
const isClosed = Boolean(face.closed);
|
|
925
|
+
if (!isClosed) {
|
|
926
|
+
try {
|
|
927
|
+
face.resume();
|
|
928
|
+
} catch {
|
|
929
|
+
// noop: readline may already be closed during shutdown/cancel paths.
|
|
768
930
|
}
|
|
769
|
-
|
|
770
|
-
return lines;
|
|
931
|
+
}
|
|
771
932
|
};
|
|
772
|
-
const
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
933
|
+
const finish = (value) => {
|
|
934
|
+
if (settled) return;
|
|
935
|
+
settled = true;
|
|
936
|
+
collapseInPlace();
|
|
937
|
+
cleanup();
|
|
938
|
+
resolve(value);
|
|
777
939
|
};
|
|
778
|
-
const
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
for (let i = 0; i < totalLineCount; i++) {
|
|
784
|
-
process.stdout.write("\x1b[2K");
|
|
785
|
-
if (i < lines.length) {
|
|
786
|
-
process.stdout.write(lines[i]);
|
|
787
|
-
}
|
|
788
|
-
process.stdout.write("\n");
|
|
789
|
-
}
|
|
790
|
-
renderedLineCount = lines.length;
|
|
791
|
-
if (collapse) {
|
|
792
|
-
renderedLineCount = 0;
|
|
793
|
-
}
|
|
940
|
+
const fail = (error) => {
|
|
941
|
+
if (settled) return;
|
|
942
|
+
settled = true;
|
|
943
|
+
cleanup();
|
|
944
|
+
reject(error);
|
|
794
945
|
};
|
|
795
|
-
const
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
if (extraLines > 0) {
|
|
810
|
-
process.stdout.write(`\x1b[${extraLines}A`);
|
|
811
|
-
}
|
|
812
|
-
renderedLineCount = 0;
|
|
813
|
-
};
|
|
814
|
-
return new Promise((resolve, reject) => {
|
|
815
|
-
let settled = false;
|
|
816
|
-
const cleanup = () => {
|
|
817
|
-
stdin.off("data", onData);
|
|
818
|
-
if (stdin.isTTY) {
|
|
819
|
-
stdin.setRawMode(previousRawMode);
|
|
820
|
-
}
|
|
821
|
-
const isClosed = Boolean(face.closed);
|
|
822
|
-
if (!isClosed) {
|
|
823
|
-
try {
|
|
824
|
-
face.resume();
|
|
825
|
-
}
|
|
826
|
-
catch {
|
|
827
|
-
// noop: readline may already be closed during shutdown/cancel paths.
|
|
828
|
-
}
|
|
829
|
-
}
|
|
830
|
-
};
|
|
831
|
-
const finish = (value) => {
|
|
832
|
-
if (settled)
|
|
833
|
-
return;
|
|
834
|
-
settled = true;
|
|
835
|
-
collapseInPlace();
|
|
836
|
-
cleanup();
|
|
837
|
-
resolve(value);
|
|
838
|
-
};
|
|
839
|
-
const fail = (error) => {
|
|
840
|
-
if (settled)
|
|
841
|
-
return;
|
|
842
|
-
settled = true;
|
|
843
|
-
cleanup();
|
|
844
|
-
reject(error);
|
|
845
|
-
};
|
|
846
|
-
const onData = (chunk) => {
|
|
847
|
-
const input = typeof chunk == "string" ? chunk : chunk.toString("utf8");
|
|
848
|
-
if (!input.length)
|
|
849
|
-
return;
|
|
850
|
-
if (input == "\u0003") {
|
|
851
|
-
fail(new Error(chalk.bold.red("◆ Cancelled")));
|
|
852
|
-
return;
|
|
853
|
-
}
|
|
854
|
-
if (input == "\x1b[A" ||
|
|
855
|
-
input == "\x1bOA" ||
|
|
856
|
-
input == "\x1b[D" ||
|
|
857
|
-
input == "\x1bOD") {
|
|
858
|
-
selectedIndex = (selectedIndex - 1 + choices.length) % choices.length;
|
|
859
|
-
writeLines(menuLines());
|
|
860
|
-
return;
|
|
861
|
-
}
|
|
862
|
-
if (input == "\x1b[B" ||
|
|
863
|
-
input == "\x1bOB" ||
|
|
864
|
-
input == "\x1b[C" ||
|
|
865
|
-
input == "\x1bOC") {
|
|
866
|
-
selectedIndex = (selectedIndex + 1) % choices.length;
|
|
867
|
-
writeLines(menuLines());
|
|
868
|
-
return;
|
|
869
|
-
}
|
|
870
|
-
if (input == "\r" || input == "\n") {
|
|
871
|
-
finish(choices[selectedIndex].value);
|
|
872
|
-
return;
|
|
873
|
-
}
|
|
874
|
-
};
|
|
875
|
-
face.pause();
|
|
876
|
-
if (stdin.isTTY) {
|
|
877
|
-
stdin.setRawMode(true);
|
|
878
|
-
}
|
|
879
|
-
stdin.resume();
|
|
880
|
-
stdin.on("data", onData);
|
|
946
|
+
const onData = (chunk) => {
|
|
947
|
+
const input = typeof chunk == "string" ? chunk : chunk.toString("utf8");
|
|
948
|
+
if (!input.length) return;
|
|
949
|
+
if (input == "\u0003") {
|
|
950
|
+
fail(new Error(chalk.bold.red("◆ Cancelled")));
|
|
951
|
+
return;
|
|
952
|
+
}
|
|
953
|
+
if (
|
|
954
|
+
input == "\x1b[A" ||
|
|
955
|
+
input == "\x1bOA" ||
|
|
956
|
+
input == "\x1b[D" ||
|
|
957
|
+
input == "\x1bOD"
|
|
958
|
+
) {
|
|
959
|
+
selectedIndex = (selectedIndex - 1 + choices.length) % choices.length;
|
|
881
960
|
writeLines(menuLines());
|
|
882
|
-
|
|
961
|
+
return;
|
|
962
|
+
}
|
|
963
|
+
if (
|
|
964
|
+
input == "\x1b[B" ||
|
|
965
|
+
input == "\x1bOB" ||
|
|
966
|
+
input == "\x1b[C" ||
|
|
967
|
+
input == "\x1bOC"
|
|
968
|
+
) {
|
|
969
|
+
selectedIndex = (selectedIndex + 1) % choices.length;
|
|
970
|
+
writeLines(menuLines());
|
|
971
|
+
return;
|
|
972
|
+
}
|
|
973
|
+
if (input == "\r" || input == "\n") {
|
|
974
|
+
finish(choices[selectedIndex].value);
|
|
975
|
+
return;
|
|
976
|
+
}
|
|
977
|
+
};
|
|
978
|
+
face.pause();
|
|
979
|
+
if (stdin.isTTY) {
|
|
980
|
+
stdin.setRawMode(true);
|
|
981
|
+
}
|
|
982
|
+
stdin.resume();
|
|
983
|
+
stdin.on("data", onData);
|
|
984
|
+
writeLines(menuLines());
|
|
985
|
+
});
|
|
883
986
|
}
|
|
884
987
|
function installDependencies(root) {
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
}
|
|
988
|
+
const install = resolveInstallCommand(root);
|
|
989
|
+
console.log(
|
|
990
|
+
"\n" +
|
|
991
|
+
chalk.dim(
|
|
992
|
+
`Installing dependencies with: ${install.command} ${install.args.join(" ")}`,
|
|
993
|
+
),
|
|
994
|
+
);
|
|
995
|
+
const child = spawnSync(install.command, install.args, {
|
|
996
|
+
cwd: root,
|
|
997
|
+
stdio: "inherit",
|
|
998
|
+
shell: process.platform == "win32",
|
|
999
|
+
});
|
|
1000
|
+
if (child.error) {
|
|
1001
|
+
throw new Error(`failed to run dependency install: ${child.error.message}`);
|
|
1002
|
+
}
|
|
1003
|
+
if (child.status !== 0) {
|
|
1004
|
+
throw new Error(
|
|
1005
|
+
`dependency installation failed with exit code ${String(child.status)}`,
|
|
1006
|
+
);
|
|
1007
|
+
}
|
|
899
1008
|
}
|
|
900
1009
|
function resolveInstallCommand(root) {
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
return { command: "
|
|
1010
|
+
if (existsSync(path.join(root, "pnpm-lock.yaml"))) {
|
|
1011
|
+
return { command: "pnpm", args: ["install"] };
|
|
1012
|
+
}
|
|
1013
|
+
if (existsSync(path.join(root, "yarn.lock"))) {
|
|
1014
|
+
return { command: "yarn", args: ["install"] };
|
|
1015
|
+
}
|
|
1016
|
+
if (
|
|
1017
|
+
existsSync(path.join(root, "bun.lockb")) ||
|
|
1018
|
+
existsSync(path.join(root, "bun.lock"))
|
|
1019
|
+
) {
|
|
1020
|
+
return { command: "bun", args: ["install"] };
|
|
1021
|
+
}
|
|
1022
|
+
const userAgent = process.env.npm_config_user_agent ?? "";
|
|
1023
|
+
if (userAgent.startsWith("pnpm")) {
|
|
1024
|
+
return { command: "pnpm", args: ["install"] };
|
|
1025
|
+
}
|
|
1026
|
+
if (userAgent.startsWith("yarn")) {
|
|
1027
|
+
return { command: "yarn", args: ["install"] };
|
|
1028
|
+
}
|
|
1029
|
+
if (userAgent.startsWith("bun")) {
|
|
1030
|
+
return { command: "bun", args: ["install"] };
|
|
1031
|
+
}
|
|
1032
|
+
return { command: "npm", args: ["install"] };
|
|
922
1033
|
}
|
|
923
1034
|
function buildMinimalExampleSpec() {
|
|
924
|
-
|
|
1035
|
+
return `import { describe, expect, test } from "as-test";
|
|
925
1036
|
|
|
926
1037
|
describe("example", () => {
|
|
927
1038
|
test("adds numbers", () => {
|
|
@@ -931,7 +1042,7 @@ describe("example", () => {
|
|
|
931
1042
|
`;
|
|
932
1043
|
}
|
|
933
1044
|
function buildFullExampleSpec() {
|
|
934
|
-
|
|
1045
|
+
return `import { afterAll, beforeAll, describe, expect, it, log, test } from "as-test";
|
|
935
1046
|
|
|
936
1047
|
beforeAll(() => {
|
|
937
1048
|
log("setup");
|
|
@@ -964,7 +1075,7 @@ describe("strings", () => {
|
|
|
964
1075
|
`;
|
|
965
1076
|
}
|
|
966
1077
|
function buildBasicFuzzerExample() {
|
|
967
|
-
|
|
1078
|
+
return `import { expect, fuzz, FuzzSeed } from "as-test";
|
|
968
1079
|
|
|
969
1080
|
fuzz("basic string fuzzer", (value: string): bool => {
|
|
970
1081
|
expect(value.length >= 0).toBe(true);
|
|
@@ -982,7 +1093,7 @@ fuzz("basic string fuzzer", (value: string): bool => {
|
|
|
982
1093
|
`;
|
|
983
1094
|
}
|
|
984
1095
|
function buildWasiRunner() {
|
|
985
|
-
|
|
1096
|
+
return `import { instantiate } from "as-test/lib";
|
|
986
1097
|
|
|
987
1098
|
const imports = {};
|
|
988
1099
|
|
|
@@ -997,7 +1108,7 @@ instantiate(imports)
|
|
|
997
1108
|
`;
|
|
998
1109
|
}
|
|
999
1110
|
function buildBindingsRunner() {
|
|
1000
|
-
|
|
1111
|
+
return `import { instantiate } from "as-test/lib";
|
|
1001
1112
|
|
|
1002
1113
|
const imports = {};
|
|
1003
1114
|
|