as-test 1.1.6 → 1.1.8
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 +17 -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 +908 -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
|
@@ -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,694 @@ 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, "json-as")) {
|
|
654
|
+
devDependencies["json-as"] = "^1.3.6";
|
|
655
|
+
}
|
|
656
|
+
if (!hasDependency(pkg, "assemblyscript")) {
|
|
657
|
+
devDependencies["assemblyscript"] = "^0.28.9";
|
|
658
|
+
}
|
|
659
|
+
if (target == "wasi" && !devDependencies["@assemblyscript/wasi-shim"]) {
|
|
660
|
+
devDependencies["@assemblyscript/wasi-shim"] = "^0.1.0";
|
|
661
|
+
}
|
|
662
|
+
if (target == "bindings" && !pkg.type) {
|
|
663
|
+
pkg.type = "module";
|
|
664
|
+
}
|
|
665
|
+
writeJson(pkgPath, pkg, summary, "package.json");
|
|
666
|
+
return summary;
|
|
564
667
|
}
|
|
565
668
|
function hasDependency(pkg, dependency) {
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
}
|
|
574
|
-
return false;
|
|
669
|
+
const sections = ["dependencies", "devDependencies", "peerDependencies"];
|
|
670
|
+
for (const section of sections) {
|
|
671
|
+
const value = pkg[section];
|
|
672
|
+
if (!value || typeof value != "object" || Array.isArray(value)) continue;
|
|
673
|
+
if (dependency in value) return true;
|
|
674
|
+
}
|
|
675
|
+
return false;
|
|
575
676
|
}
|
|
576
677
|
function ensureDir(root, rel, summary) {
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
summary.created.push(rel + "/");
|
|
678
|
+
const full = path.join(root, rel);
|
|
679
|
+
if (existsSync(full)) return;
|
|
680
|
+
mkdirSync(full, { recursive: true });
|
|
681
|
+
summary.created.push(rel + "/");
|
|
582
682
|
}
|
|
583
683
|
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
|
-
|
|
684
|
+
const rel = ".gitignore";
|
|
685
|
+
const fullPath = path.join(root, rel);
|
|
686
|
+
const entries = [
|
|
687
|
+
"# Include essential as-test artifacts",
|
|
688
|
+
"!.as-test/",
|
|
689
|
+
".as-test/*",
|
|
690
|
+
"!.as-test/runners/",
|
|
691
|
+
"!.as-test/snapshots/",
|
|
692
|
+
];
|
|
693
|
+
const existed = existsSync(fullPath);
|
|
694
|
+
const source = existed ? readFileSync(fullPath, "utf8") : "";
|
|
695
|
+
const lines = source.split(/\r?\n/);
|
|
696
|
+
const missing = entries.filter(
|
|
697
|
+
(entry) => !lines.some((line) => line.trim() == entry),
|
|
698
|
+
);
|
|
699
|
+
if (!missing.length) {
|
|
700
|
+
return;
|
|
701
|
+
}
|
|
702
|
+
const eol = source.includes("\r\n") ? "\r\n" : "\n";
|
|
703
|
+
let output = source;
|
|
704
|
+
if (output.length && !output.endsWith("\n") && !output.endsWith("\r\n")) {
|
|
705
|
+
output += eol;
|
|
706
|
+
}
|
|
707
|
+
output += missing.join(eol) + eol;
|
|
708
|
+
writeFileSync(fullPath, output);
|
|
709
|
+
if (existed) summary.updated.push(rel);
|
|
710
|
+
else summary.created.push(rel);
|
|
611
711
|
}
|
|
612
712
|
function buildAssemblyTsconfig() {
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
713
|
+
return {
|
|
714
|
+
extends: "assemblyscript/std/assembly.json",
|
|
715
|
+
include: ["./**/*.ts"],
|
|
716
|
+
};
|
|
617
717
|
}
|
|
618
718
|
function writeJson(fullPath, value, summary, displayPath) {
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
summary.created.push(rel);
|
|
719
|
+
const rel =
|
|
720
|
+
displayPath ??
|
|
721
|
+
path.relative(process.cwd(), fullPath) ??
|
|
722
|
+
path.basename(fullPath);
|
|
723
|
+
const existed = existsSync(fullPath);
|
|
724
|
+
const data = JSON.stringify(value, null, 2) + "\n";
|
|
725
|
+
writeFileSync(fullPath, data);
|
|
726
|
+
if (existed) summary.updated.push(rel);
|
|
727
|
+
else summary.created.push(rel);
|
|
629
728
|
}
|
|
630
729
|
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);
|
|
730
|
+
const rel =
|
|
731
|
+
displayPath ??
|
|
732
|
+
path.relative(process.cwd(), fullPath) ??
|
|
733
|
+
path.basename(fullPath);
|
|
734
|
+
const existed = existsSync(fullPath);
|
|
735
|
+
if (existed && !force) {
|
|
736
|
+
summary.skipped.push(rel);
|
|
737
|
+
return;
|
|
738
|
+
}
|
|
739
|
+
if (!existsSync(path.dirname(fullPath))) {
|
|
740
|
+
mkdirSync(path.dirname(fullPath), { recursive: true });
|
|
741
|
+
}
|
|
742
|
+
writeFileSync(fullPath, data);
|
|
743
|
+
if (existed) summary.updated.push(rel);
|
|
744
|
+
else summary.created.push(rel);
|
|
647
745
|
}
|
|
648
746
|
function printSummary(summary) {
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
}
|
|
747
|
+
console.log("│");
|
|
748
|
+
if (summary.created.length) {
|
|
749
|
+
console.log(chalk.bold("│ Created:"));
|
|
750
|
+
for (const item of summary.created) {
|
|
751
|
+
console.log(`│ + ${item}`);
|
|
655
752
|
}
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
753
|
+
}
|
|
754
|
+
if (summary.updated.length) {
|
|
755
|
+
console.log(chalk.bold("│ Updated:"));
|
|
756
|
+
for (const item of summary.updated) {
|
|
757
|
+
console.log(`│ ~ ${item}`);
|
|
661
758
|
}
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
759
|
+
}
|
|
760
|
+
if (summary.skipped.length) {
|
|
761
|
+
console.log(chalk.bold("│ Skipped (exists, use --force to overwrite):"));
|
|
762
|
+
for (const item of summary.skipped) {
|
|
763
|
+
console.log(`│ = ${item}`);
|
|
667
764
|
}
|
|
668
|
-
|
|
765
|
+
}
|
|
766
|
+
console.log("│");
|
|
669
767
|
}
|
|
670
768
|
function ask(question, face, initialValue) {
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
face.write(initialValue);
|
|
686
|
-
}
|
|
769
|
+
if (!face) {
|
|
770
|
+
throw new Error(
|
|
771
|
+
"interactive input is unavailable; pass --yes with options",
|
|
772
|
+
);
|
|
773
|
+
}
|
|
774
|
+
return new Promise((res) => {
|
|
775
|
+
face.question(question, (answer) => {
|
|
776
|
+
const stdout = process.stdout;
|
|
777
|
+
if (stdout.isTTY) {
|
|
778
|
+
stdout.write("\x1b[1A");
|
|
779
|
+
stdout.write("\x1b[2K");
|
|
780
|
+
stdout.write("\r");
|
|
781
|
+
}
|
|
782
|
+
res(answer);
|
|
687
783
|
});
|
|
784
|
+
if (initialValue && initialValue.length) {
|
|
785
|
+
face.write(initialValue);
|
|
786
|
+
}
|
|
787
|
+
});
|
|
688
788
|
}
|
|
689
789
|
async function askChoice(label, choices, face, fallback) {
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
790
|
+
if (!face) {
|
|
791
|
+
return fallback;
|
|
792
|
+
}
|
|
793
|
+
const answer = (
|
|
794
|
+
await ask(
|
|
795
|
+
`${label} [${choices.join("/")}] (${fallback}) -> `,
|
|
796
|
+
face,
|
|
797
|
+
fallback,
|
|
798
|
+
)
|
|
799
|
+
)
|
|
800
|
+
.trim()
|
|
801
|
+
.toLowerCase();
|
|
802
|
+
if (!answer.length) return fallback;
|
|
803
|
+
if (choices.includes(answer)) return answer;
|
|
804
|
+
throw new Error(`Invalid choice "${answer}" for ${label}`);
|
|
701
805
|
}
|
|
702
806
|
async function askMenuChoice(label, choices, face, fallback) {
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
return askMenuChoiceWithArrows(label, choices, face, fallbackValue);
|
|
807
|
+
const fallbackValue = choices.some((choice) => choice.value == fallback)
|
|
808
|
+
? fallback
|
|
809
|
+
: choices[0].value;
|
|
810
|
+
if (!face) return fallbackValue;
|
|
811
|
+
if (!canUseArrowMenu(face)) {
|
|
812
|
+
const values = choices.map((choice) => choice.value);
|
|
813
|
+
return askChoice(label, values, face, fallbackValue);
|
|
814
|
+
}
|
|
815
|
+
return askMenuChoiceWithArrows(label, choices, face, fallbackValue);
|
|
713
816
|
}
|
|
714
817
|
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
|
-
|
|
818
|
+
if (!face) return fallback;
|
|
819
|
+
if (canUseArrowMenu(face)) {
|
|
820
|
+
const selected = await askMenuChoice(
|
|
821
|
+
label,
|
|
822
|
+
[
|
|
823
|
+
{ value: "yes", label: "Yes" },
|
|
824
|
+
{ value: "no", label: "No" },
|
|
825
|
+
],
|
|
826
|
+
face,
|
|
827
|
+
fallback ? "yes" : "no",
|
|
828
|
+
);
|
|
829
|
+
return selected == "yes";
|
|
830
|
+
}
|
|
831
|
+
const suffix = fallback ? "[Y/n]" : "[y/N]";
|
|
832
|
+
const defaultValue = fallback ? "yes" : "no";
|
|
833
|
+
const answer = (await ask(`${label} ${suffix} `, face, defaultValue))
|
|
834
|
+
.trim()
|
|
835
|
+
.toLowerCase();
|
|
836
|
+
if (!answer.length) return fallback;
|
|
837
|
+
if (answer == "y" || answer == "yes") return true;
|
|
838
|
+
if (answer == "n" || answer == "no") return false;
|
|
839
|
+
throw new Error(`Invalid answer "${answer}". Expected yes or no.`);
|
|
736
840
|
}
|
|
737
841
|
function canUseArrowMenu(face) {
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
842
|
+
if (!face) return false;
|
|
843
|
+
const stdin = process.stdin;
|
|
844
|
+
const stdout = process.stdout;
|
|
845
|
+
return (
|
|
846
|
+
Boolean(stdin.isTTY) &&
|
|
847
|
+
Boolean(stdout.isTTY) &&
|
|
848
|
+
typeof stdin.setRawMode == "function"
|
|
849
|
+
);
|
|
745
850
|
}
|
|
746
851
|
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
|
-
|
|
852
|
+
const stdin = process.stdin;
|
|
853
|
+
const stdout = process.stdout;
|
|
854
|
+
const fallbackIndex = choices.findIndex((choice) => choice.value == fallback);
|
|
855
|
+
let selectedIndex = fallbackIndex == -1 ? 0 : fallbackIndex;
|
|
856
|
+
let renderedLineCount = 0;
|
|
857
|
+
const previousRawMode = Boolean(stdin.isRaw);
|
|
858
|
+
const lineWidth = Math.max(20, (stdout.columns ?? 80) - 2);
|
|
859
|
+
const clamp = (value, max) => {
|
|
860
|
+
if (value.length <= max) return value;
|
|
861
|
+
if (max <= 1) return value.slice(0, max);
|
|
862
|
+
return `${value.slice(0, max - 1)}…`;
|
|
863
|
+
};
|
|
864
|
+
const titleLine = () =>
|
|
865
|
+
chalk.bold.blue(`◆ ${clamp(label, Math.max(8, lineWidth - 3))}`);
|
|
866
|
+
const menuLines = () => {
|
|
867
|
+
const lines = [titleLine()];
|
|
868
|
+
for (let i = 0; i < choices.length; i++) {
|
|
869
|
+
const choice = choices[i];
|
|
870
|
+
const marker = i == selectedIndex ? chalk.blue("●") : chalk.dim("○");
|
|
871
|
+
lines.push(
|
|
872
|
+
`│ ${marker} ${clamp(choice.label, Math.max(8, lineWidth - 6))}`,
|
|
873
|
+
);
|
|
874
|
+
}
|
|
875
|
+
lines.push("│");
|
|
876
|
+
return lines;
|
|
877
|
+
};
|
|
878
|
+
const collapsedLines = () => {
|
|
879
|
+
const selected = choices[selectedIndex];
|
|
880
|
+
return [
|
|
881
|
+
`│ ${chalk.gray(clamp(selected.label, Math.max(8, lineWidth - 4)))}`,
|
|
882
|
+
];
|
|
883
|
+
};
|
|
884
|
+
const writeLines = (lines, collapse = false) => {
|
|
885
|
+
if (renderedLineCount > 0) {
|
|
886
|
+
process.stdout.write(`\x1b[${renderedLineCount}A`);
|
|
887
|
+
}
|
|
888
|
+
const totalLineCount = Math.max(lines.length, renderedLineCount);
|
|
889
|
+
for (let i = 0; i < totalLineCount; i++) {
|
|
890
|
+
process.stdout.write("\x1b[2K");
|
|
891
|
+
if (i < lines.length) {
|
|
892
|
+
process.stdout.write(lines[i]);
|
|
893
|
+
}
|
|
894
|
+
process.stdout.write("\n");
|
|
895
|
+
}
|
|
896
|
+
renderedLineCount = lines.length;
|
|
897
|
+
if (collapse) {
|
|
898
|
+
renderedLineCount = 0;
|
|
899
|
+
}
|
|
900
|
+
};
|
|
901
|
+
const collapseInPlace = () => {
|
|
902
|
+
const lines = collapsedLines();
|
|
903
|
+
if (renderedLineCount > 0) {
|
|
904
|
+
process.stdout.write(`\x1b[${renderedLineCount}A`);
|
|
905
|
+
}
|
|
906
|
+
const totalLineCount = Math.max(renderedLineCount, lines.length);
|
|
907
|
+
for (let i = 0; i < totalLineCount; i++) {
|
|
908
|
+
process.stdout.write("\r\x1b[2K");
|
|
909
|
+
if (i < lines.length) {
|
|
910
|
+
process.stdout.write(lines[i]);
|
|
911
|
+
}
|
|
912
|
+
process.stdout.write("\n");
|
|
913
|
+
}
|
|
914
|
+
const extraLines = totalLineCount - lines.length;
|
|
915
|
+
if (extraLines > 0) {
|
|
916
|
+
process.stdout.write(`\x1b[${extraLines}A`);
|
|
917
|
+
}
|
|
918
|
+
renderedLineCount = 0;
|
|
919
|
+
};
|
|
920
|
+
return new Promise((resolve, reject) => {
|
|
921
|
+
let settled = false;
|
|
922
|
+
const cleanup = () => {
|
|
923
|
+
stdin.off("data", onData);
|
|
924
|
+
if (stdin.isTTY) {
|
|
925
|
+
stdin.setRawMode(previousRawMode);
|
|
926
|
+
}
|
|
927
|
+
const isClosed = Boolean(face.closed);
|
|
928
|
+
if (!isClosed) {
|
|
929
|
+
try {
|
|
930
|
+
face.resume();
|
|
931
|
+
} catch {
|
|
932
|
+
// noop: readline may already be closed during shutdown/cancel paths.
|
|
768
933
|
}
|
|
769
|
-
|
|
770
|
-
return lines;
|
|
934
|
+
}
|
|
771
935
|
};
|
|
772
|
-
const
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
936
|
+
const finish = (value) => {
|
|
937
|
+
if (settled) return;
|
|
938
|
+
settled = true;
|
|
939
|
+
collapseInPlace();
|
|
940
|
+
cleanup();
|
|
941
|
+
resolve(value);
|
|
777
942
|
};
|
|
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
|
-
}
|
|
943
|
+
const fail = (error) => {
|
|
944
|
+
if (settled) return;
|
|
945
|
+
settled = true;
|
|
946
|
+
cleanup();
|
|
947
|
+
reject(error);
|
|
794
948
|
};
|
|
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);
|
|
949
|
+
const onData = (chunk) => {
|
|
950
|
+
const input = typeof chunk == "string" ? chunk : chunk.toString("utf8");
|
|
951
|
+
if (!input.length) return;
|
|
952
|
+
if (input == "\u0003") {
|
|
953
|
+
fail(new Error(chalk.bold.red("◆ Cancelled")));
|
|
954
|
+
return;
|
|
955
|
+
}
|
|
956
|
+
if (
|
|
957
|
+
input == "\x1b[A" ||
|
|
958
|
+
input == "\x1bOA" ||
|
|
959
|
+
input == "\x1b[D" ||
|
|
960
|
+
input == "\x1bOD"
|
|
961
|
+
) {
|
|
962
|
+
selectedIndex = (selectedIndex - 1 + choices.length) % choices.length;
|
|
881
963
|
writeLines(menuLines());
|
|
882
|
-
|
|
964
|
+
return;
|
|
965
|
+
}
|
|
966
|
+
if (
|
|
967
|
+
input == "\x1b[B" ||
|
|
968
|
+
input == "\x1bOB" ||
|
|
969
|
+
input == "\x1b[C" ||
|
|
970
|
+
input == "\x1bOC"
|
|
971
|
+
) {
|
|
972
|
+
selectedIndex = (selectedIndex + 1) % choices.length;
|
|
973
|
+
writeLines(menuLines());
|
|
974
|
+
return;
|
|
975
|
+
}
|
|
976
|
+
if (input == "\r" || input == "\n") {
|
|
977
|
+
finish(choices[selectedIndex].value);
|
|
978
|
+
return;
|
|
979
|
+
}
|
|
980
|
+
};
|
|
981
|
+
face.pause();
|
|
982
|
+
if (stdin.isTTY) {
|
|
983
|
+
stdin.setRawMode(true);
|
|
984
|
+
}
|
|
985
|
+
stdin.resume();
|
|
986
|
+
stdin.on("data", onData);
|
|
987
|
+
writeLines(menuLines());
|
|
988
|
+
});
|
|
883
989
|
}
|
|
884
990
|
function installDependencies(root) {
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
}
|
|
991
|
+
const install = resolveInstallCommand(root);
|
|
992
|
+
console.log(
|
|
993
|
+
"\n" +
|
|
994
|
+
chalk.dim(
|
|
995
|
+
`Installing dependencies with: ${install.command} ${install.args.join(" ")}`,
|
|
996
|
+
),
|
|
997
|
+
);
|
|
998
|
+
const child = spawnSync(install.command, install.args, {
|
|
999
|
+
cwd: root,
|
|
1000
|
+
stdio: "inherit",
|
|
1001
|
+
shell: process.platform == "win32",
|
|
1002
|
+
});
|
|
1003
|
+
if (child.error) {
|
|
1004
|
+
throw new Error(`failed to run dependency install: ${child.error.message}`);
|
|
1005
|
+
}
|
|
1006
|
+
if (child.status !== 0) {
|
|
1007
|
+
throw new Error(
|
|
1008
|
+
`dependency installation failed with exit code ${String(child.status)}`,
|
|
1009
|
+
);
|
|
1010
|
+
}
|
|
899
1011
|
}
|
|
900
1012
|
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: "
|
|
1013
|
+
if (existsSync(path.join(root, "pnpm-lock.yaml"))) {
|
|
1014
|
+
return { command: "pnpm", args: ["install"] };
|
|
1015
|
+
}
|
|
1016
|
+
if (existsSync(path.join(root, "yarn.lock"))) {
|
|
1017
|
+
return { command: "yarn", args: ["install"] };
|
|
1018
|
+
}
|
|
1019
|
+
if (
|
|
1020
|
+
existsSync(path.join(root, "bun.lockb")) ||
|
|
1021
|
+
existsSync(path.join(root, "bun.lock"))
|
|
1022
|
+
) {
|
|
1023
|
+
return { command: "bun", args: ["install"] };
|
|
1024
|
+
}
|
|
1025
|
+
const userAgent = process.env.npm_config_user_agent ?? "";
|
|
1026
|
+
if (userAgent.startsWith("pnpm")) {
|
|
1027
|
+
return { command: "pnpm", args: ["install"] };
|
|
1028
|
+
}
|
|
1029
|
+
if (userAgent.startsWith("yarn")) {
|
|
1030
|
+
return { command: "yarn", args: ["install"] };
|
|
1031
|
+
}
|
|
1032
|
+
if (userAgent.startsWith("bun")) {
|
|
1033
|
+
return { command: "bun", args: ["install"] };
|
|
1034
|
+
}
|
|
1035
|
+
return { command: "npm", args: ["install"] };
|
|
922
1036
|
}
|
|
923
1037
|
function buildMinimalExampleSpec() {
|
|
924
|
-
|
|
1038
|
+
return `import { describe, expect, test } from "as-test";
|
|
925
1039
|
|
|
926
1040
|
describe("example", () => {
|
|
927
1041
|
test("adds numbers", () => {
|
|
@@ -931,7 +1045,7 @@ describe("example", () => {
|
|
|
931
1045
|
`;
|
|
932
1046
|
}
|
|
933
1047
|
function buildFullExampleSpec() {
|
|
934
|
-
|
|
1048
|
+
return `import { afterAll, beforeAll, describe, expect, it, log, test } from "as-test";
|
|
935
1049
|
|
|
936
1050
|
beforeAll(() => {
|
|
937
1051
|
log("setup");
|
|
@@ -964,7 +1078,7 @@ describe("strings", () => {
|
|
|
964
1078
|
`;
|
|
965
1079
|
}
|
|
966
1080
|
function buildBasicFuzzerExample() {
|
|
967
|
-
|
|
1081
|
+
return `import { expect, fuzz, FuzzSeed } from "as-test";
|
|
968
1082
|
|
|
969
1083
|
fuzz("basic string fuzzer", (value: string): bool => {
|
|
970
1084
|
expect(value.length >= 0).toBe(true);
|
|
@@ -982,7 +1096,7 @@ fuzz("basic string fuzzer", (value: string): bool => {
|
|
|
982
1096
|
`;
|
|
983
1097
|
}
|
|
984
1098
|
function buildWasiRunner() {
|
|
985
|
-
|
|
1099
|
+
return `import { instantiate } from "as-test/lib";
|
|
986
1100
|
|
|
987
1101
|
const imports = {};
|
|
988
1102
|
|
|
@@ -997,7 +1111,7 @@ instantiate(imports)
|
|
|
997
1111
|
`;
|
|
998
1112
|
}
|
|
999
1113
|
function buildBindingsRunner() {
|
|
1000
|
-
|
|
1114
|
+
return `import { instantiate } from "as-test/lib";
|
|
1001
1115
|
|
|
1002
1116
|
const imports = {};
|
|
1003
1117
|
|