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.
Files changed (43) hide show
  1. package/CHANGELOG.md +16 -1
  2. package/README.md +4 -9
  3. package/assembly/index.ts +10 -15
  4. package/assembly/src/expectation.ts +11 -11
  5. package/assembly/src/fuzz.ts +11 -7
  6. package/assembly/src/log.ts +2 -2
  7. package/assembly/src/suite.ts +5 -5
  8. package/assembly/src/tests.ts +8 -8
  9. package/assembly/util/wipc.ts +5 -1
  10. package/bin/build-worker-pool.js +146 -142
  11. package/bin/build-worker.js +37 -34
  12. package/bin/commands/build-core.js +577 -465
  13. package/bin/commands/build.js +49 -29
  14. package/bin/commands/clean-core.js +120 -113
  15. package/bin/commands/clean.js +14 -8
  16. package/bin/commands/doctor-core.js +288 -289
  17. package/bin/commands/doctor.js +1 -1
  18. package/bin/commands/fuzz-core.js +467 -414
  19. package/bin/commands/fuzz.js +27 -10
  20. package/bin/commands/init-core.js +905 -794
  21. package/bin/commands/init.js +2 -2
  22. package/bin/commands/run-core.js +2675 -2344
  23. package/bin/commands/run.js +43 -25
  24. package/bin/commands/test.js +56 -32
  25. package/bin/commands/web-runner-source.js +1 -1
  26. package/bin/commands/web-session.js +516 -525
  27. package/bin/coverage-points.js +363 -341
  28. package/bin/crash-store.js +56 -66
  29. package/bin/index.js +4092 -3150
  30. package/bin/reporters/default.js +1090 -890
  31. package/bin/reporters/tap.js +319 -325
  32. package/bin/types.js +67 -67
  33. package/bin/util.js +1290 -1239
  34. package/bin/wipc.js +70 -73
  35. package/lib/build/index.d.ts +3 -1
  36. package/lib/build/index.js +1039 -1034
  37. package/lib/build/web-runner/client.js +1 -1
  38. package/lib/build/web-runner/html.js +1 -1
  39. package/lib/build/web-runner/worker.js +1 -1
  40. package/package.json +6 -3
  41. package/transform/lib/coverage.js +4 -4
  42. package/transform/lib/log.js +9 -5
  43. 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
- 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,
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
- finally {
53
- rl?.close();
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
- const options = {
58
- yes: false,
59
- force: false,
60
- dir: ".",
61
- dirExplicit: false,
62
- };
63
- const positional = [];
64
- for (let i = 0; i < rawArgs.length; i++) {
65
- const arg = rawArgs[i];
66
- if (arg == "--yes" || arg == "-y") {
67
- options.yes = true;
68
- continue;
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
- // First positional argument is always the target directory.
133
- if (positional.length > 0) {
134
- options.dir = positional.shift();
135
- options.dirExplicit = true;
82
+ if (arg == "--force") {
83
+ options.force = true;
84
+ continue;
136
85
  }
137
- if (!options.target && positional.length > 0 && isTarget(positional[0])) {
138
- options.target = positional.shift();
86
+ if (arg == "--install") {
87
+ options.install = true;
88
+ continue;
139
89
  }
140
- if (!options.example &&
141
- positional.length > 0 &&
142
- isExampleMode(positional[0])) {
143
- options.example = positional.shift();
90
+ if (arg == "--fuzz-example") {
91
+ options.fuzzExample = true;
92
+ continue;
144
93
  }
145
- if (positional.length > 0) {
146
- throw new Error(`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>]`);
94
+ if (arg == "--no-fuzz-example") {
95
+ options.fuzzExample = false;
96
+ continue;
147
97
  }
148
- return options;
149
- }
150
- async function runInteractiveOnboarding(options, face) {
151
- printOnboardingIntro();
152
- const acknowledged = await askYesNo("I understand this command writes files and can run package manager installs. Continue?", face, true);
153
- if (!acknowledged)
154
- return null;
155
- const onboardingMode = await askMenuChoice("Onboarding mode", [
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
- else {
166
- const defaultDisplay = defaultRoot == "." ? "./" : defaultRoot;
167
- const enteredDir = (await ask(`${chalk.bold.blue(`◇ ${workspacePrompt}`)}\n│ `, face, defaultDisplay)).trim();
168
- selectedDir = enteredDir.length ? enteredDir : defaultRoot;
107
+ if (arg.startsWith("--target=")) {
108
+ options.target = parseTarget(arg.slice("--target=".length));
109
+ continue;
169
110
  }
170
- const resolvedRoot = path.resolve(process.cwd(), selectedDir);
171
- if (options.dirExplicit || onboardingMode == "quick") {
172
- printPromptAndSelectionLine(workspacePrompt, resolvedRoot);
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
- else {
175
- printSelectionLine(resolvedRoot);
120
+ if (arg.startsWith("--example=")) {
121
+ options.example = parseExampleMode(arg.slice("--example=".length));
122
+ continue;
176
123
  }
177
- const target = options.target ??
178
- (onboardingMode == "quick"
179
- ? "wasi"
180
- : await askMenuChoice("Build target", [
181
- {
182
- value: "wasi",
183
- label: "wasi (default runner: node .as-test/runners/default.wasi.js)",
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
- const fuzzExample = options.fuzzExample ??
209
- (onboardingMode == "quick"
210
- ? false
211
- : await askYesNo("Add a basic fuzzer example?", face, false));
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
- const installDependenciesNow = options.install ??
216
- (onboardingMode == "quick"
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
- return {
223
- root: resolvedRoot,
224
- target,
225
- example,
226
- fuzzExample,
227
- installDependenciesNow,
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
- // console.log(
232
- // chalk.bold.cyan(
233
- // `as-test ${getCliVersion()} — AssemblyScript testing without runtime guesswork.`,
234
- // ) + "\n",
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
- console.log(chalk.bold.blue("╔═╗ ╔═╗ ╔═╗ ╔═╗ ╔═╗ ╔═╗"));
239
- console.log(chalk.bold.blue("╠═╣ ╚═╗ ══ ║ ╠═ ╚═╗ ║ "));
240
- console.log(chalk.bold.blue("╩ ╩ ╚═╝ ╩ ╚═╝ ╚═╝ ╩ "));
241
- console.log("");
242
- // console.log(chalk.bold("┌") + " " + chalk.bold.blueBright(""));
243
- // console.log("│");
244
- // printPanel("Security", [
245
- // "Security warning — please read.",
246
- // "",
247
- // "as-test is a local developer tool and executes build/runtime commands from your project config.",
248
- // "If the config is untrusted, those commands can run arbitrary programs on your machine.",
249
- // "",
250
- // "Recommended baseline:",
251
- // "- Keep this tool scoped to trusted repositories.",
252
- // "- Review runOptions.runtime.cmd and buildOptions.cmd before running.",
253
- // "- Prefer least-privilege shells/environments for shared machines and CI.",
254
- // "",
255
- // "Run regularly: ast doctor and ast test --list",
256
- // "Read docs: README.md (Configuration + Setup Diagnostics sections).",
257
- // ]);
258
- // console.log("│");
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
- console.log(chalk.bold.blue(`◇ ${prompt}`));
301
- printSelectionLine(answer);
351
+ console.log(chalk.bold.blue(`◇ ${prompt}`));
352
+ printSelectionLine(answer);
302
353
  }
303
354
  function printSelectionLine(answer) {
304
- console.log(`│ ${chalk.gray(answer)}`);
305
- console.log("│");
355
+ console.log(`│ ${chalk.gray(answer)}`);
356
+ console.log("│");
306
357
  }
307
358
  function parseTarget(value) {
308
- if (!isTarget(value)) {
309
- throw new Error(`Invalid target "${value}". Expected wasi|bindings|web`);
310
- }
311
- return value;
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
- if (!isExampleMode(value)) {
315
- throw new Error(`Invalid example mode "${value}". Expected minimal|full|none`);
316
- }
317
- return value;
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
- return TARGETS.includes(value);
373
+ return TARGETS.includes(value);
321
374
  }
322
375
  function isExampleMode(value) {
323
- return EXAMPLE_MODES.includes(value);
376
+ return EXAMPLE_MODES.includes(value);
324
377
  }
325
378
  function printPlan(root, target, example, fuzzExample, install) {
326
- const displayRoot = () => {
327
- const rel = path.relative(process.cwd(), root).split(path.sep).join("/");
328
- if (!rel || rel == ".")
329
- return "./";
330
- if (rel.startsWith(".."))
331
- return rel;
332
- return `./${rel}`;
333
- };
334
- const statusColor = (relPath) => existsSync(path.join(root, relPath)) ? chalk.hex("#d29922") : chalk.green;
335
- const paintNode = (node) => statusColor(node.relPath)(node.isDir ? `${node.name}/` : node.name);
336
- const ensureChild = (parent, name, relPath, isDir) => {
337
- let child = parent.children.find((entry) => entry.name == name);
338
- if (!child) {
339
- child = { name, relPath, isDir, children: [] };
340
- parent.children.push(child);
341
- return child;
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 (example != "none") {
409
- fileEntries.push({
410
- path: "assembly/__tests__/example.spec.ts",
411
- isDir: false,
412
- });
396
+ if (isDir) {
397
+ child.isDir = true;
413
398
  }
414
- if (fuzzExample) {
415
- fileEntries.push({ path: "assembly/__fuzz__", isDir: true });
416
- fileEntries.push({
417
- path: "assembly/__fuzz__/example.fuzz.ts",
418
- isDir: false,
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
- const treeRoot = buildTree(fileEntries);
422
- console.log(chalk.bold.blue("◇ Planned Changes"));
423
- console.log("│" + chalk.dim(` - Target: ${target}`));
424
- console.log("│" + chalk.dim(` - Example: ${example}`));
425
- console.log("│" + chalk.dim(` - Fuzzer example: ${fuzzExample ? "yes" : "no"}`));
426
- console.log("│" + chalk.dim(` - Directory: ${displayRoot()}`));
427
- console.log("│" + chalk.dim(` - Install dependencies: ${install ? "yes" : "no"}`));
428
- console.log("│" + chalk.bold.blue(" File Changes"));
429
- for (const topLevelNode of treeRoot.children) {
430
- console.log(`│ ${paintNode(topLevelNode)}`);
431
- renderBranch(topLevelNode.children, "");
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
- console.log("│");
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
- const summary = {
437
- created: [],
438
- updated: [],
439
- skipped: [],
440
- };
441
- ensureDir(root, ".as-test/build", summary);
442
- ensureDir(root, ".as-test/logs", summary);
443
- ensureDir(root, ".as-test/coverage", summary);
444
- ensureDir(root, ".as-test/snapshots", summary);
445
- ensureDir(root, "assembly/__tests__", summary);
446
- if (fuzzExample) {
447
- ensureDir(root, "assembly/__fuzz__", summary);
448
- }
449
- if (target == "wasi" || target == "bindings" || target == "web") {
450
- ensureDir(root, ".as-test/runners", summary);
451
- }
452
- ensureGitignoreIncludesAsTestDirs(root, summary);
453
- writeJson(path.join(root, "assembly/tsconfig.json"), buildAssemblyTsconfig(), summary, "assembly/tsconfig.json");
454
- const configPath = path.join(root, "as-test.config.json");
455
- const config = {
456
- $schema: "node_modules/as-test/as-test.config.schema.json",
457
- input: ["assembly/__tests__/*.spec.ts"],
458
- output: ".as-test/",
459
- config: "none",
460
- coverage: false,
461
- env: {},
462
- ...(fuzzExample
463
- ? {
464
- fuzz: {
465
- input: ["assembly/__fuzz__/*.fuzz.ts"],
466
- runs: 1000,
467
- target: "bindings",
468
- corpusDir: ".as-test/corpus",
469
- crashDir: ".as-test/crashes",
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
- reporter: "default",
485
- },
486
- modes: target == "web"
487
- ? {
488
- web: {
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
- writeJson(configPath, config, summary, "as-test.config.json");
508
- if (example != "none") {
509
- const examplePath = path.join(root, "assembly/__tests__/example.spec.ts");
510
- const content = example == "minimal" ? buildMinimalExampleSpec() : buildFullExampleSpec();
511
- writeManagedFile(examplePath, content, force, summary, "assembly/__tests__/example.spec.ts");
512
- }
513
- if (fuzzExample) {
514
- const fuzzPath = path.join(root, "assembly/__fuzz__/example.fuzz.ts");
515
- writeManagedFile(fuzzPath, buildBasicFuzzerExample(), force, summary, "assembly/__fuzz__/example.fuzz.ts");
516
- }
517
- if (target == "wasi" || target == "bindings" || target == "web") {
518
- const runnerPath = path.join(root, ".as-test/runners/default.wasi.js");
519
- writeManagedFile(runnerPath, buildWasiRunner(), force, summary, ".as-test/runners/default.wasi.js");
520
- }
521
- if (target == "wasi" || target == "bindings" || target == "web") {
522
- const runnerPath = path.join(root, ".as-test/runners/default.bindings.js");
523
- writeManagedFile(runnerPath, buildBindingsRunner(), force, summary, ".as-test/runners/default.bindings.js");
524
- }
525
- if (target == "wasi" || target == "bindings" || target == "web") {
526
- const runnerPath = path.join(root, ".as-test/runners/default.web.js");
527
- writeManagedFile(runnerPath, buildWebRunnerSource(), force, summary, ".as-test/runners/default.web.js");
528
- }
529
- const pkgPath = path.join(root, "package.json");
530
- const pkg = existsSync(pkgPath)
531
- ? JSON.parse(readFileSync(pkgPath, "utf8"))
532
- : {};
533
- if (!pkg.scripts || typeof pkg.scripts != "object") {
534
- pkg.scripts = {};
535
- }
536
- const scripts = pkg.scripts;
537
- if (!scripts.test) {
538
- scripts.test = "ast test";
539
- }
540
- if (fuzzExample && !scripts.fuzz) {
541
- scripts.fuzz = "ast fuzz";
542
- }
543
- if (!pkg.type) {
544
- pkg.type = "module";
545
- }
546
- if (!pkg.devDependencies || typeof pkg.devDependencies != "object") {
547
- pkg.devDependencies = {};
548
- }
549
- const devDependencies = pkg.devDependencies;
550
- if (!devDependencies["as-test"]) {
551
- devDependencies["as-test"] = "^" + getCliVersion();
552
- }
553
- if (!hasDependency(pkg, "assemblyscript")) {
554
- devDependencies["assemblyscript"] = "^0.28.9";
555
- }
556
- if (target == "wasi" && !devDependencies["@assemblyscript/wasi-shim"]) {
557
- devDependencies["@assemblyscript/wasi-shim"] = "^0.1.0";
558
- }
559
- if (target == "bindings" && !pkg.type) {
560
- pkg.type = "module";
561
- }
562
- writeJson(pkgPath, pkg, summary, "package.json");
563
- return summary;
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
- const sections = ["dependencies", "devDependencies", "peerDependencies"];
567
- for (const section of sections) {
568
- const value = pkg[section];
569
- if (!value || typeof value != "object" || Array.isArray(value))
570
- continue;
571
- if (dependency in value)
572
- return true;
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
- const full = path.join(root, rel);
578
- if (existsSync(full))
579
- return;
580
- mkdirSync(full, { recursive: true });
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
- const rel = ".gitignore";
585
- const fullPath = path.join(root, rel);
586
- const entries = [
587
- "# Include essential as-test artifacts",
588
- "!.as-test/",
589
- ".as-test/*",
590
- "!.as-test/runners/",
591
- "!.as-test/snapshots/",
592
- ];
593
- const existed = existsSync(fullPath);
594
- const source = existed ? readFileSync(fullPath, "utf8") : "";
595
- const lines = source.split(/\r?\n/);
596
- const missing = entries.filter((entry) => !lines.some((line) => line.trim() == entry));
597
- if (!missing.length) {
598
- return;
599
- }
600
- const eol = source.includes("\r\n") ? "\r\n" : "\n";
601
- let output = source;
602
- if (output.length && !output.endsWith("\n") && !output.endsWith("\r\n")) {
603
- output += eol;
604
- }
605
- output += missing.join(eol) + eol;
606
- writeFileSync(fullPath, output);
607
- if (existed)
608
- summary.updated.push(rel);
609
- else
610
- summary.created.push(rel);
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
- return {
614
- extends: "assemblyscript/std/assembly.json",
615
- include: ["./**/*.ts"],
616
- };
710
+ return {
711
+ extends: "assemblyscript/std/assembly.json",
712
+ include: ["./**/*.ts"],
713
+ };
617
714
  }
618
715
  function writeJson(fullPath, value, summary, displayPath) {
619
- const rel = displayPath ??
620
- path.relative(process.cwd(), fullPath) ??
621
- path.basename(fullPath);
622
- const existed = existsSync(fullPath);
623
- const data = JSON.stringify(value, null, 2) + "\n";
624
- writeFileSync(fullPath, data);
625
- if (existed)
626
- summary.updated.push(rel);
627
- else
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
- const rel = displayPath ??
632
- path.relative(process.cwd(), fullPath) ??
633
- path.basename(fullPath);
634
- const existed = existsSync(fullPath);
635
- if (existed && !force) {
636
- summary.skipped.push(rel);
637
- return;
638
- }
639
- if (!existsSync(path.dirname(fullPath))) {
640
- mkdirSync(path.dirname(fullPath), { recursive: true });
641
- }
642
- writeFileSync(fullPath, data);
643
- if (existed)
644
- summary.updated.push(rel);
645
- else
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
- console.log("│");
650
- if (summary.created.length) {
651
- console.log(chalk.bold("│ Created:"));
652
- for (const item of summary.created) {
653
- console.log(`│ + ${item}`);
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
- if (summary.updated.length) {
657
- console.log(chalk.bold("│ Updated:"));
658
- for (const item of summary.updated) {
659
- console.log(`│ ~ ${item}`);
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
- if (summary.skipped.length) {
663
- console.log(chalk.bold("│ Skipped (exists, use --force to overwrite):"));
664
- for (const item of summary.skipped) {
665
- console.log(`│ = ${item}`);
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
- console.log("│");
762
+ }
763
+ console.log("│");
669
764
  }
670
765
  function ask(question, face, initialValue) {
671
- if (!face) {
672
- throw new Error("interactive input is unavailable; pass --yes with options");
673
- }
674
- return new Promise((res) => {
675
- face.question(question, (answer) => {
676
- const stdout = process.stdout;
677
- if (stdout.isTTY) {
678
- stdout.write("\x1b[1A");
679
- stdout.write("\x1b[2K");
680
- stdout.write("\r");
681
- }
682
- res(answer);
683
- });
684
- if (initialValue && initialValue.length) {
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
- if (!face) {
691
- return fallback;
692
- }
693
- const answer = (await ask(`${label} [${choices.join("/")}] (${fallback}) -> `, face, fallback))
694
- .trim()
695
- .toLowerCase();
696
- if (!answer.length)
697
- return fallback;
698
- if (choices.includes(answer))
699
- return answer;
700
- throw new Error(`Invalid choice "${answer}" for ${label}`);
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
- const fallbackValue = choices.some((choice) => choice.value == fallback)
704
- ? fallback
705
- : choices[0].value;
706
- if (!face)
707
- return fallbackValue;
708
- if (!canUseArrowMenu(face)) {
709
- const values = choices.map((choice) => choice.value);
710
- return askChoice(label, values, face, fallbackValue);
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
- if (!face)
716
- return fallback;
717
- if (canUseArrowMenu(face)) {
718
- const selected = await askMenuChoice(label, [
719
- { value: "yes", label: "Yes" },
720
- { value: "no", label: "No" },
721
- ], face, fallback ? "yes" : "no");
722
- return selected == "yes";
723
- }
724
- const suffix = fallback ? "[Y/n]" : "[y/N]";
725
- const defaultValue = fallback ? "yes" : "no";
726
- const answer = (await ask(`${label} ${suffix} `, face, defaultValue))
727
- .trim()
728
- .toLowerCase();
729
- if (!answer.length)
730
- return fallback;
731
- if (answer == "y" || answer == "yes")
732
- return true;
733
- if (answer == "n" || answer == "no")
734
- return false;
735
- throw new Error(`Invalid answer "${answer}". Expected yes or no.`);
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
- if (!face)
739
- return false;
740
- const stdin = process.stdin;
741
- const stdout = process.stdout;
742
- return (Boolean(stdin.isTTY) &&
743
- Boolean(stdout.isTTY) &&
744
- typeof stdin.setRawMode == "function");
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
- const stdin = process.stdin;
748
- const stdout = process.stdout;
749
- const fallbackIndex = choices.findIndex((choice) => choice.value == fallback);
750
- let selectedIndex = fallbackIndex == -1 ? 0 : fallbackIndex;
751
- let renderedLineCount = 0;
752
- const previousRawMode = Boolean(stdin.isRaw);
753
- const lineWidth = Math.max(20, (stdout.columns ?? 80) - 2);
754
- const clamp = (value, max) => {
755
- if (value.length <= max)
756
- return value;
757
- if (max <= 1)
758
- return value.slice(0, max);
759
- return `${value.slice(0, max - 1)}…`;
760
- };
761
- const titleLine = () => chalk.bold.blue(`◆ ${clamp(label, Math.max(8, lineWidth - 3))}`);
762
- const menuLines = () => {
763
- const lines = [titleLine()];
764
- for (let i = 0; i < choices.length; i++) {
765
- const choice = choices[i];
766
- const marker = i == selectedIndex ? chalk.blue("●") : chalk.dim("○");
767
- lines.push(`│ ${marker} ${clamp(choice.label, Math.max(8, lineWidth - 6))}`);
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
- lines.push("│");
770
- return lines;
931
+ }
771
932
  };
772
- const collapsedLines = () => {
773
- const selected = choices[selectedIndex];
774
- return [
775
- `│ ${chalk.gray(clamp(selected.label, Math.max(8, lineWidth - 4)))}`,
776
- ];
933
+ const finish = (value) => {
934
+ if (settled) return;
935
+ settled = true;
936
+ collapseInPlace();
937
+ cleanup();
938
+ resolve(value);
777
939
  };
778
- const writeLines = (lines, collapse = false) => {
779
- if (renderedLineCount > 0) {
780
- process.stdout.write(`\x1b[${renderedLineCount}A`);
781
- }
782
- const totalLineCount = Math.max(lines.length, renderedLineCount);
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 collapseInPlace = () => {
796
- const lines = collapsedLines();
797
- if (renderedLineCount > 0) {
798
- process.stdout.write(`\x1b[${renderedLineCount}A`);
799
- }
800
- const totalLineCount = Math.max(renderedLineCount, lines.length);
801
- for (let i = 0; i < totalLineCount; i++) {
802
- process.stdout.write("\r\x1b[2K");
803
- if (i < lines.length) {
804
- process.stdout.write(lines[i]);
805
- }
806
- process.stdout.write("\n");
807
- }
808
- const extraLines = totalLineCount - lines.length;
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
- const install = resolveInstallCommand(root);
886
- console.log("\n" +
887
- chalk.dim(`Installing dependencies with: ${install.command} ${install.args.join(" ")}`));
888
- const child = spawnSync(install.command, install.args, {
889
- cwd: root,
890
- stdio: "inherit",
891
- shell: process.platform == "win32",
892
- });
893
- if (child.error) {
894
- throw new Error(`failed to run dependency install: ${child.error.message}`);
895
- }
896
- if (child.status !== 0) {
897
- throw new Error(`dependency installation failed with exit code ${String(child.status)}`);
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
- if (existsSync(path.join(root, "pnpm-lock.yaml"))) {
902
- return { command: "pnpm", args: ["install"] };
903
- }
904
- if (existsSync(path.join(root, "yarn.lock"))) {
905
- return { command: "yarn", args: ["install"] };
906
- }
907
- if (existsSync(path.join(root, "bun.lockb")) ||
908
- existsSync(path.join(root, "bun.lock"))) {
909
- return { command: "bun", args: ["install"] };
910
- }
911
- const userAgent = process.env.npm_config_user_agent ?? "";
912
- if (userAgent.startsWith("pnpm")) {
913
- return { command: "pnpm", args: ["install"] };
914
- }
915
- if (userAgent.startsWith("yarn")) {
916
- return { command: "yarn", args: ["install"] };
917
- }
918
- if (userAgent.startsWith("bun")) {
919
- return { command: "bun", args: ["install"] };
920
- }
921
- return { command: "npm", args: ["install"] };
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
- return `import { describe, expect, test } from "as-test";
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
- return `import { afterAll, beforeAll, describe, expect, it, log, test } from "as-test";
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
- return `import { expect, fuzz, FuzzSeed } from "as-test";
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
- return `import { instantiate } from "as-test/lib";
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
- return `import { instantiate } from "as-test/lib";
1111
+ return `import { instantiate } from "as-test/lib";
1001
1112
 
1002
1113
  const imports = {};
1003
1114