as-test 1.0.15 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,9 +1,61 @@
1
1
  # Change Log
2
2
 
3
+ ## Unreleased
4
+
5
+ ### Upgrading to 1.1.0
6
+
7
+ - refresh generated runners with:
8
+
9
+ ```bash
10
+ rm -rf .as-test/runners && npx as-test init
11
+ ```
12
+
13
+ - generated runners now use a single file per target and import `instantiate(...)` from `as-test/lib`
14
+ - new bindings and web runners no longer use `*.hooks.js`
15
+ - named modes now support `default: false` to make a mode manual-only
16
+ - the repo examples and default config now use mode names like `node:wasi`, `node:bindings`, `chromium`, and `chromium:headless`
17
+
18
+ ### Runtime & Runners
19
+
20
+ - feat: replace the split bindings/web hooks model with single-file runners that import `instantiate(...)` from `as-test/lib`, keeping bindings, WASI, and web runner syntax aligned.
21
+ - feat: add `as-test/lib` as the shared JS runtime host layer for bindings, WASI, and web targets, with runtime artifact resolution happening out of sight before runner execution.
22
+ - feat: autodetect bindings helper shape at runtime support level (`raw`, `esm`, or `none`) and keep the generated runner surface minimal.
23
+ - fix: make `ast run` rebuild missing artifacts on demand instead of failing when only some selected outputs already exist.
24
+ - fix: report real lazy-build time in `ast run` summaries instead of always printing `0us build`.
25
+ - fix: remove build artifact copy/reuse shortcuts so each selected file/mode compiles directly, avoiding stale output reuse across modes.
26
+
27
+ ### Web Runtime
28
+
29
+ - feat: move headful web execution to a persistent single-browser-session architecture that opens one page, runs multiple binaries through it, and keeps browser-side runtime details hidden from the runner file.
30
+ - feat: redesign the non-headless browser page into a minimal macOS-inspired loading surface with light/dark mode support and simpler status messaging.
31
+ - feat: make headful web runs wait for the user to open the local session URL, and expose a browser-side exit control.
32
+ - fix: keep all browser bootstrap, asset, and websocket traffic on one local port.
33
+ - fix: improve browser discovery and launch behavior across Chromium, Firefox, and WebKit, including Playwright cache lookup, macOS app bundle resolution, paths with spaces, and owned-process teardown.
34
+ - fix: fail terminal-side runs when the browser side disconnects unexpectedly, and close the browser side when the websocket is lost.
35
+
36
+ ### WASI
37
+
38
+ - fix: make the WASI stdin transport retry only on retryable WASI read errors (`AGAIN` and `INTR`), which resolves intermittent snapshot reply corruption in `node:wasi` runs.
39
+
40
+ ### Modes & CLI
41
+
42
+ - feat: add per-mode `default: boolean` selection so modes can be included in implicit runs or kept manual-only.
43
+ - feat: add `ast clean` to remove configured build outputs, crash reports, and logs for the selected modes.
44
+ - fix: restore unnamed root-config execution alongside named default modes when `--mode` is omitted.
45
+ - fix: make `ast clean --mode ...` skip shared output paths that are still owned by unselected modes instead of deleting them.
46
+
47
+ ### Tests
48
+
49
+ - feat: add integration coverage for bindings (`raw`, `esm`, `none`), WASI, and web runtime paths, including browser-resolution regressions and single-origin web runner behavior.
50
+
51
+ ## 2026-05-08 - v1.0.16
52
+
53
+ - feat: modes inherit pre-declared properties if not explicitly overriden
54
+
3
55
  ## 2026-05-04 - v1.0.15
4
56
 
5
57
  - fix: path resolving
6
- -
58
+
7
59
  ## 2026-05-04 - v1.0.14
8
60
 
9
61
  ### Fuzzing
package/README.md CHANGED
@@ -2,6 +2,14 @@
2
2
  ╠═╣ ╚═╗ ══ ║ ╠═ ╚═╗ ║
3
3
  ╩ ╩ ╚═╝ ╩ ╚═╝ ╚═╝ ╩ </pre></h1>
4
4
 
5
+ > **Upgrading to 1.1.0**
6
+ >
7
+ > See [CHANGELOG.md](./CHANGELOG.md) for upgrade notes. In most projects, refreshing generated runners is enough:
8
+ >
9
+ > ```bash
10
+ > rm -rf .as-test/runners && npx as-test init
11
+ > ```
12
+
5
13
  <details>
6
14
  <summary>Table of Contents</summary>
7
15
 
@@ -77,7 +85,7 @@ Minimal `as-test.config.json`:
77
85
  },
78
86
  "runOptions": {
79
87
  "runtime": {
80
- "cmd": "node .as-test/runners/default.wasi.js <file>"
88
+ "cmd": "node .as-test/runners/default.wasi.js"
81
89
  }
82
90
  }
83
91
  }
@@ -323,7 +331,7 @@ For example, a simple WASI setup in `as-test.config.json` can look like this:
323
331
  },
324
332
  "runOptions": {
325
333
  "runtime": {
326
- "cmd": "node ./.as-test/runners/default.wasi.js <file>"
334
+ "cmd": "node ./.as-test/runners/default.wasi.js"
327
335
  }
328
336
  }
329
337
  }
@@ -342,22 +350,41 @@ If you want to keep more than one runtime around, use modes:
342
350
  "input": ["./assembly/__tests__/*.spec.ts"],
343
351
  "modes": {
344
352
  "wasi": {
353
+ "default": true,
345
354
  "buildOptions": {
346
355
  "target": "wasi"
347
356
  },
348
357
  "runOptions": {
349
358
  "runtime": {
350
- "cmd": "node ./.as-test/runners/default.wasi.js <file>"
359
+ "cmd": "node ./.as-test/runners/default.wasi.js"
351
360
  }
352
361
  }
353
362
  },
354
363
  "bindings": {
364
+ "default": true,
355
365
  "buildOptions": {
356
366
  "target": "bindings"
357
367
  },
358
368
  "runOptions": {
359
369
  "runtime": {
360
- "cmd": "node ./.as-test/runners/default.bindings.js <file>"
370
+ "cmd": "node ./.as-test/runners/default.bindings.js"
371
+ }
372
+ }
373
+ }
374
+ }
375
+ }
376
+ ```
377
+
378
+ Set `"default": false` on a mode when you want to keep it available for explicit `--mode ...` runs without including it in normal runs:
379
+
380
+ ```json
381
+ {
382
+ "modes": {
383
+ "web": {
384
+ "default": false,
385
+ "runOptions": {
386
+ "runtime": {
387
+ "browser": "chromium"
361
388
  }
362
389
  }
363
390
  }
@@ -365,6 +392,20 @@ If you want to keep more than one runtime around, use modes:
365
392
  }
366
393
  ```
367
394
 
395
+ With that setup:
396
+
397
+ ```bash
398
+ npx ast test
399
+ ```
400
+
401
+ runs the root/default config plus any modes whose `"default"` flag is not `false`, while:
402
+
403
+ ```bash
404
+ npx ast test --mode web
405
+ ```
406
+
407
+ still runs the `web` mode explicitly.
408
+
368
409
  Modes can also be full config objects. That means a mode can override fuzzing, input globs, output aliases, runtime, build flags, and the rest of the normal config surface:
369
410
 
370
411
  ```json
@@ -305,6 +305,11 @@
305
305
  "type": "object",
306
306
  "additionalProperties": false,
307
307
  "properties": {
308
+ "default": {
309
+ "type": "boolean",
310
+ "description": "Include this mode when --mode is omitted. Defaults to true.",
311
+ "default": true
312
+ },
308
313
  "$schema": {
309
314
  "type": "string"
310
315
  },
@@ -34,6 +34,8 @@ const HEADER_SIZE: i32 = 9;
34
34
  const IOV_SIZE: usize = sizeof<usize>() * 2;
35
35
  const U32_SIZE: usize = sizeof<u32>();
36
36
  const REPORT_CHUNK_BYTES: i32 = 65536;
37
+ const WASI_ERRNO_AGAIN: i32 = 6;
38
+ const WASI_ERRNO_INTR: i32 = 27;
37
39
 
38
40
  // @ts-ignore
39
41
  const IS_BINDINGS: bool = isDefined(AS_TEST_BINDINGS);
@@ -319,10 +321,15 @@ function wasiRead(max: i32): ArrayBuffer {
319
321
 
320
322
  store<usize>(iovPtr, changetype<usize>(out), 0);
321
323
  store<usize>(iovPtr, <usize>max, sizeof<usize>());
322
- store<u32>(readPtr, 0, 0);
323
-
324
- const errno = wasi_fd_read(0, iovPtr, 1, readPtr);
325
- if (errno != 0) return new ArrayBuffer(0);
324
+ while (true) {
325
+ store<u32>(readPtr, 0, 0);
326
+ const errno = wasi_fd_read(0, iovPtr, 1, readPtr);
327
+ if (errno == WASI_ERRNO_AGAIN || errno == WASI_ERRNO_INTR) {
328
+ continue;
329
+ }
330
+ if (errno != 0) return new ArrayBuffer(0);
331
+ break;
332
+ }
326
333
 
327
334
  const size = <i32>load<u32>(readPtr, 0);
328
335
  if (size <= 0) return new ArrayBuffer(0);
@@ -0,0 +1,92 @@
1
+ import chalk from "chalk";
2
+ import { existsSync, rmSync } from "fs";
3
+ import * as path from "path";
4
+ import { applyMode, loadConfig } from "../util.js";
5
+ const DEFAULT_CONFIG_PATH = path.join(process.cwd(), "./as-test.config.json");
6
+ export async function clean(configPath = DEFAULT_CONFIG_PATH, modes = [undefined]) {
7
+ const loadedConfig = loadConfig(configPath, true);
8
+ const targets = new Map();
9
+ const ownership = buildOwnershipMap(loadedConfig);
10
+ for (const modeName of modes) {
11
+ const active = applyMode(loadedConfig, modeName).config;
12
+ collectTarget(targets, active.outDir, modeName, "build");
13
+ collectTarget(targets, active.fuzz.crashDir, modeName, "crashes");
14
+ collectTarget(targets, active.logs, modeName, "logs");
15
+ }
16
+ let removed = 0;
17
+ let skipped = 0;
18
+ for (const [targetPath, owners] of [...targets.entries()].sort((a, b) => a[0].localeCompare(b[0]))) {
19
+ const allOwners = ownership.get(targetPath) ?? owners;
20
+ const unselectedOwners = allOwners.filter((owner) => !owners.includes(owner));
21
+ if (unselectedOwners.length) {
22
+ skipped++;
23
+ process.stdout.write(`${chalk.dim("skip")} ${toRelativePath(targetPath)} ${chalk.dim(`(shared with ${unselectedOwners.join(", ")})`)}\n`);
24
+ continue;
25
+ }
26
+ if (!existsSync(targetPath)) {
27
+ skipped++;
28
+ process.stdout.write(`${chalk.dim("skip")} ${toRelativePath(targetPath)} ${chalk.dim(`(${owners.join(", ")})`)}\n`);
29
+ continue;
30
+ }
31
+ rmSync(targetPath, { recursive: true, force: true });
32
+ removed++;
33
+ process.stdout.write(`${chalk.bgGreenBright.black(" CLEAN ")} ${toRelativePath(targetPath)} ${chalk.dim(`(${owners.join(", ")})`)}\n`);
34
+ }
35
+ process.stdout.write(`${chalk.bold("Summary:")} removed ${removed} path(s), skipped ${skipped} missing path(s)\n`);
36
+ }
37
+ function buildOwnershipMap(loadedConfig) {
38
+ const ownership = new Map();
39
+ const modeNames = [
40
+ undefined,
41
+ ...Object.keys(loadedConfig.modes),
42
+ ];
43
+ for (const modeName of modeNames) {
44
+ const active = applyMode(loadedConfig, modeName).config;
45
+ collectOwnership(ownership, active.outDir, modeName, "build");
46
+ collectOwnership(ownership, active.fuzz.crashDir, modeName, "crashes");
47
+ collectOwnership(ownership, active.logs, modeName, "logs");
48
+ }
49
+ return ownership;
50
+ }
51
+ function collectOwnership(ownership, rawPath, modeName, kind) {
52
+ if (!rawPath || rawPath == "none")
53
+ return;
54
+ const resolved = path.resolve(process.cwd(), rawPath);
55
+ ensureSafeCleanPath(resolved, rawPath, kind);
56
+ const owner = `${modeName ?? "default"}:${kind}`;
57
+ const existing = ownership.get(resolved);
58
+ if (existing) {
59
+ if (!existing.includes(owner))
60
+ existing.push(owner);
61
+ return;
62
+ }
63
+ ownership.set(resolved, [owner]);
64
+ }
65
+ function collectTarget(targets, rawPath, modeName, kind) {
66
+ if (!rawPath || rawPath == "none")
67
+ return;
68
+ const resolved = path.resolve(process.cwd(), rawPath);
69
+ ensureSafeCleanPath(resolved, rawPath, kind);
70
+ const owner = `${modeName ?? "default"}:${kind}`;
71
+ const existing = targets.get(resolved);
72
+ if (existing) {
73
+ if (!existing.includes(owner))
74
+ existing.push(owner);
75
+ return;
76
+ }
77
+ targets.set(resolved, [owner]);
78
+ }
79
+ function ensureSafeCleanPath(resolvedPath, rawPath, kind) {
80
+ const cwd = path.resolve(process.cwd());
81
+ const relative = path.relative(cwd, resolvedPath);
82
+ if (!relative.length ||
83
+ relative == ".." ||
84
+ relative.startsWith(`..${path.sep}`) ||
85
+ path.parse(resolvedPath).root == resolvedPath) {
86
+ throw new Error(`refusing to clean unsafe ${kind} path "${rawPath}" (${resolvedPath})`);
87
+ }
88
+ }
89
+ function toRelativePath(targetPath) {
90
+ const relative = path.relative(process.cwd(), targetPath);
91
+ return relative.length ? relative : ".";
92
+ }
@@ -0,0 +1,6 @@
1
+ import { clean } from "./clean-core.js";
2
+ export { clean } from "./clean-core.js";
3
+ export async function executeCleanCommand(configPath, selectedModes, resolveExecutionModes) {
4
+ const modeTargets = resolveExecutionModes(configPath, selectedModes);
5
+ await clean(configPath, modeTargets);
6
+ }
@@ -188,7 +188,7 @@ async function runInteractiveOnboarding(options, face) {
188
188
  },
189
189
  {
190
190
  value: "web",
191
- label: "web (default runner: node .as-test/runners/default.web.js <file>)",
191
+ label: "web (default runner: node .as-test/runners/default.web.js)",
192
192
  },
193
193
  ], face, "wasi"));
194
194
  if (options.target || onboardingMode == "quick") {
@@ -396,10 +396,6 @@ function printPlan(root, target, example, fuzzExample, install) {
396
396
  path: ".as-test/runners/default.bindings.js",
397
397
  isDir: false,
398
398
  });
399
- fileEntries.push({
400
- path: ".as-test/runners/default.bindings.hooks.js",
401
- isDir: false,
402
- });
403
399
  fileEntries.push({
404
400
  path: ".as-test/runners/default.wasi.js",
405
401
  isDir: false,
@@ -408,10 +404,6 @@ function printPlan(root, target, example, fuzzExample, install) {
408
404
  path: ".as-test/runners/default.web.js",
409
405
  isDir: false,
410
406
  });
411
- fileEntries.push({
412
- path: ".as-test/runners/default.web.hooks.js",
413
- isDir: false,
414
- });
415
407
  }
416
408
  if (example != "none") {
417
409
  fileEntries.push({
@@ -484,19 +476,28 @@ function applyInit(root, target, example, fuzzExample, force) {
484
476
  runOptions: {
485
477
  runtime: {
486
478
  cmd: target == "wasi"
487
- ? "node .as-test/runners/default.wasi.js <file>"
479
+ ? "node .as-test/runners/default.wasi.js"
488
480
  : target == "bindings"
489
- ? "node .as-test/runners/default.bindings.js <file>"
490
- : "node .as-test/runners/default.web.js <file>",
481
+ ? "node .as-test/runners/default.bindings.js"
482
+ : "node .as-test/runners/default.web.js",
491
483
  },
492
484
  reporter: "default",
493
485
  },
494
486
  modes: target == "web"
495
487
  ? {
488
+ web: {
489
+ default: false,
490
+ runOptions: {
491
+ runtime: {
492
+ cmd: "node .as-test/runners/default.web.js",
493
+ },
494
+ },
495
+ },
496
496
  "web-headless": {
497
+ default: false,
497
498
  runOptions: {
498
499
  runtime: {
499
- cmd: "node .as-test/runners/default.web.js --headless <file>",
500
+ cmd: "node .as-test/runners/default.web.js --headless",
500
501
  },
501
502
  },
502
503
  },
@@ -521,18 +522,10 @@ function applyInit(root, target, example, fuzzExample, force) {
521
522
  const runnerPath = path.join(root, ".as-test/runners/default.bindings.js");
522
523
  writeManagedFile(runnerPath, buildBindingsRunner(), force, summary, ".as-test/runners/default.bindings.js");
523
524
  }
524
- if (target == "wasi" || target == "bindings" || target == "web") {
525
- const hooksPath = path.join(root, ".as-test/runners/default.bindings.hooks.js");
526
- writeManagedFile(hooksPath, buildBindingsRunnerHooks(), force, summary, ".as-test/runners/default.bindings.hooks.js");
527
- }
528
525
  if (target == "wasi" || target == "bindings" || target == "web") {
529
526
  const runnerPath = path.join(root, ".as-test/runners/default.web.js");
530
527
  writeManagedFile(runnerPath, buildWebRunnerSource(), force, summary, ".as-test/runners/default.web.js");
531
528
  }
532
- if (target == "wasi" || target == "bindings" || target == "web") {
533
- const hooksPath = path.join(root, ".as-test/runners/default.web.hooks.js");
534
- writeManagedFile(hooksPath, buildWebRunnerHooks(), force, summary, ".as-test/runners/default.web.hooks.js");
535
- }
536
529
  const pkgPath = path.join(root, "package.json");
537
530
  const pkg = existsSync(pkgPath)
538
531
  ? JSON.parse(readFileSync(pkgPath, "utf8"))
@@ -989,217 +982,32 @@ fuzz("basic string fuzzer", (value: string): bool => {
989
982
  `;
990
983
  }
991
984
  function buildWasiRunner() {
992
- return `import { readFileSync } from "fs";
993
- import { WASI } from "wasi";
985
+ return `import { instantiate } from "as-test/lib";
994
986
 
995
- const originalEmitWarning = process.emitWarning.bind(process);
996
- process.emitWarning = ((warning, ...args) => {
997
- const type = typeof args[0] == "string" ? args[0] : "";
998
- const name = typeof warning?.name == "string" ? warning.name : type;
999
- const message =
1000
- typeof warning == "string" ? warning : String(warning?.message ?? "");
1001
- if (
1002
- name == "ExperimentalWarning" &&
1003
- message.includes("WASI is an experimental feature")
1004
- ) {
1005
- return;
1006
- }
1007
- return originalEmitWarning(warning, ...args);
1008
- });
1009
-
1010
- const wasmPath = process.argv[2];
1011
- if (!wasmPath) {
1012
- process.stderr.write("usage: node ./.as-test/runners/default.wasi.js <file.wasm>\\n");
1013
- process.exit(1);
1014
- }
987
+ const imports = {};
1015
988
 
1016
- try {
1017
- const wasi = new WASI({
1018
- version: "preview1",
1019
- args: [wasmPath],
1020
- env: process.env,
1021
- preopens: {},
989
+ instantiate(imports)
990
+ .then((instance) => {
991
+ instance.exports.start?.();
992
+ // Add extra startup logic here when needed.
993
+ })
994
+ .catch((error) => {
995
+ throw new Error("Failed to run WASI module: " + String(error));
1022
996
  });
1023
-
1024
- const binary = readFileSync(wasmPath);
1025
- const module = new WebAssembly.Module(binary);
1026
- const instance = new WebAssembly.Instance(module, {
1027
- env: {
1028
- __as_test_request_fuzz_config() {
1029
- return 0;
1030
- },
1031
- },
1032
- wasi_snapshot_preview1: wasi.wasiImport,
1033
- });
1034
- wasi.start(instance);
1035
- } catch (error) {
1036
- process.stderr.write("failed to run WASI module: " + String(error) + "\\n");
1037
- process.exit(1);
1038
- }
1039
997
  `;
1040
998
  }
1041
999
  function buildBindingsRunner() {
1042
- return `import fs from "fs";
1043
- import path from "path";
1044
- import { pathToFileURL } from "url";
1045
-
1046
- const HOOKS_PATH = path.resolve(
1047
- path.dirname(new URL(import.meta.url).pathname),
1048
- "./default.bindings.hooks.js",
1049
- );
1000
+ return `import { instantiate } from "as-test/lib";
1050
1001
 
1051
- function readExact(length) {
1052
- const out = Buffer.alloc(length);
1053
- let offset = 0;
1054
- while (offset < length) {
1055
- let read = 0;
1056
- try {
1057
- read = fs.readSync(0, out, offset, length - offset, null);
1058
- } catch (error) {
1059
- if (error && error.code === "EAGAIN") {
1060
- continue;
1061
- }
1062
- throw error;
1063
- }
1064
- if (!read) break;
1065
- offset += read;
1066
- }
1067
- const view = out.subarray(0, offset);
1068
- return view.buffer.slice(view.byteOffset, view.byteOffset + view.byteLength);
1069
- }
1070
-
1071
- function writeRaw(data) {
1072
- const view = Buffer.from(data);
1073
- fs.writeSync(1, view);
1074
- }
1075
-
1076
- function createRunnerContext({ wasmPath, module, helperPath }) {
1077
- return {
1078
- wasmPath,
1079
- helperPath,
1080
- module,
1081
- argv: process.argv.slice(2),
1082
- env: process.env,
1083
- readFrame(size) {
1084
- return readExact(Number(size ?? 0));
1085
- },
1086
- writeFrame(data) {
1087
- writeRaw(data);
1088
- return true;
1089
- },
1090
- };
1091
- }
1002
+ const imports = {};
1092
1003
 
1093
- function createAsTestImports(ctx) {
1094
- const originalWrite = process.stdout.write.bind(process.stdout);
1095
- process.stdout.write = (chunk, ...args) => {
1096
- if (chunk instanceof ArrayBuffer) {
1097
- return ctx.writeFrame(chunk);
1098
- }
1099
- return originalWrite(chunk, ...args);
1100
- };
1101
- process.stdin.read = (size) => ctx.readFrame(size);
1102
- return {};
1103
- }
1104
-
1105
- function mergeImports(...groups) {
1106
- const out = {};
1107
- for (const group of groups) {
1108
- if (!group || typeof group != "object") continue;
1109
- for (const moduleName of Object.keys(group)) {
1110
- out[moduleName] = Object.assign(out[moduleName] || {}, group[moduleName]);
1111
- }
1112
- }
1113
- return out;
1114
- }
1115
-
1116
- async function loadRunnerHooks() {
1117
- if (!fs.existsSync(HOOKS_PATH)) {
1118
- return {
1119
- createUserImports() {
1120
- return {};
1121
- },
1122
- async runModule(_exports, _ctx) {},
1123
- };
1124
- }
1125
- const mod = await import(pathToFileURL(HOOKS_PATH).href + "?t=" + Date.now());
1126
- return {
1127
- createUserImports:
1128
- typeof mod.createUserImports == "function"
1129
- ? mod.createUserImports
1130
- : () => ({}),
1131
- runModule:
1132
- typeof mod.runModule == "function" ? mod.runModule : async () => {},
1133
- };
1134
- }
1135
-
1136
- async function instantiateModule(ctx, hooks) {
1137
- const helper = await import(pathToFileURL(ctx.helperPath).href);
1138
- if (typeof helper.instantiate !== "function") {
1139
- throw new Error("bindings helper missing instantiate export");
1140
- }
1141
- const imports = mergeImports(
1142
- createAsTestImports(ctx),
1143
- await hooks.createUserImports(ctx),
1144
- );
1145
- return helper.instantiate(ctx.module, imports);
1146
- }
1147
-
1148
- const wasmPathArg = process.argv[2];
1149
- if (!wasmPathArg) {
1150
- process.stderr.write("usage: node ./.as-test/runners/default.bindings.js <file.wasm>\\n");
1151
- process.exit(1);
1152
- }
1153
-
1154
- const wasmPath = path.resolve(process.cwd(), wasmPathArg);
1155
- const jsPath = wasmPath.replace(/\\.wasm$/, ".js");
1156
-
1157
- try {
1158
- const binary = fs.readFileSync(wasmPath);
1159
- const module = new WebAssembly.Module(binary);
1160
- const ctx = createRunnerContext({ wasmPath, module, helperPath: jsPath });
1161
- const hooks = await loadRunnerHooks();
1162
- const exports = await instantiateModule(ctx, hooks);
1163
- await hooks.runModule(exports, ctx);
1164
- } catch (error) {
1165
- process.stderr.write("failed to run bindings module: " + String(error) + "\\n");
1166
- process.exit(1);
1167
- }
1168
- `;
1169
- }
1170
- function buildBindingsRunnerHooks() {
1171
- return `export function createUserImports(_ctx) {
1172
- return {
1173
- // env: {
1174
- // now_ms: () => Date.now(),
1175
- // },
1176
- };
1177
- }
1178
-
1179
- export async function runModule(_exports, _ctx) {
1180
- // The generated bindings helper already calls exports._start().
1181
- // Add extra startup calls here when your module exposes them.
1182
- //
1183
- // Example:
1184
- // _exports.run?.();
1185
- }
1186
- `;
1187
- }
1188
- function buildWebRunnerHooks() {
1189
- return `export function createUserImports(_ctx) {
1190
- return {
1191
- // env: {
1192
- // now_ms: () => performance.now(),
1193
- // },
1194
- };
1195
- }
1196
-
1197
- export async function runModule(_exports, _ctx) {
1198
- // The generated bindings helper already calls exports._start().
1199
- // Add extra startup calls here when your module exposes them.
1200
- //
1201
- // Example:
1202
- // _exports.run?.();
1203
- }
1004
+ instantiate(imports)
1005
+ .then((instance) => {
1006
+ instance.exports.start?.();
1007
+ // Add extra startup logic here when needed.
1008
+ })
1009
+ .catch((error) => {
1010
+ throw new Error("Failed to run bindings module: " + String(error));
1011
+ });
1204
1012
  `;
1205
1013
  }