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.
Files changed (42) hide show
  1. package/CHANGELOG.md +17 -0
  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 +908 -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/log.js +9 -5
  42. 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,694 @@ 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, "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
- 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;
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
- const full = path.join(root, rel);
578
- if (existsSync(full))
579
- return;
580
- mkdirSync(full, { recursive: true });
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
- 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);
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
- return {
614
- extends: "assemblyscript/std/assembly.json",
615
- include: ["./**/*.ts"],
616
- };
713
+ return {
714
+ extends: "assemblyscript/std/assembly.json",
715
+ include: ["./**/*.ts"],
716
+ };
617
717
  }
618
718
  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);
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
- 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);
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
- 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
- }
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
- if (summary.updated.length) {
657
- console.log(chalk.bold("│ Updated:"));
658
- for (const item of summary.updated) {
659
- console.log(`│ ~ ${item}`);
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
- 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
- }
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
- console.log("│");
765
+ }
766
+ console.log("│");
669
767
  }
670
768
  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
- }
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
- 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}`);
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
- 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);
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
- 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.`);
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
- 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");
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
- 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))}`);
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
- lines.push("│");
770
- return lines;
934
+ }
771
935
  };
772
- const collapsedLines = () => {
773
- const selected = choices[selectedIndex];
774
- return [
775
- `│ ${chalk.gray(clamp(selected.label, Math.max(8, lineWidth - 4)))}`,
776
- ];
936
+ const finish = (value) => {
937
+ if (settled) return;
938
+ settled = true;
939
+ collapseInPlace();
940
+ cleanup();
941
+ resolve(value);
777
942
  };
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
- }
943
+ const fail = (error) => {
944
+ if (settled) return;
945
+ settled = true;
946
+ cleanup();
947
+ reject(error);
794
948
  };
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);
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
- 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
- }
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
- 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"] };
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
- return `import { describe, expect, test } from "as-test";
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
- return `import { afterAll, beforeAll, describe, expect, it, log, test } from "as-test";
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
- return `import { expect, fuzz, FuzzSeed } from "as-test";
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
- return `import { instantiate } from "as-test/lib";
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
- return `import { instantiate } from "as-test/lib";
1114
+ return `import { instantiate } from "as-test/lib";
1001
1115
 
1002
1116
  const imports = {};
1003
1117