gofront 1.7.0 → 1.9.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.
Files changed (194) hide show
  1. package/README.md +14 -14
  2. package/package.json +10 -38
  3. package/src/backend/js/expressions.js +2060 -389
  4. package/src/backend/js/index.js +586 -174
  5. package/src/backend/js/natives.js +383 -0
  6. package/src/backend/js/runtime.js +936 -1644
  7. package/src/backend/js/statements.js +1349 -132
  8. package/src/backend/js/stdlib/index.js +51 -94
  9. package/src/backend/js/templ.js +67 -118
  10. package/src/backend/js/type-info.js +436 -0
  11. package/src/backend/shared/fixed-float.js +20 -0
  12. package/src/backend/shared/fmt-types.js +80 -0
  13. package/src/backend/shared/host-unicode.js +36 -0
  14. package/src/backend/wasm/boundary.js +753 -872
  15. package/src/backend/wasm/emit-builtins.js +914 -237
  16. package/src/backend/wasm/emit-exprs.js +1456 -195
  17. package/src/backend/wasm/emit-maps.js +78 -380
  18. package/src/backend/wasm/emit-stmts.js +1659 -214
  19. package/src/backend/wasm/emit.js +614 -88
  20. package/src/backend/wasm/encode.js +43 -13
  21. package/src/backend/wasm/globals.js +491 -0
  22. package/src/backend/wasm/glue.js +2 -4
  23. package/src/backend/wasm/index.js +406 -3195
  24. package/src/backend/wasm/method-expr.js +392 -0
  25. package/src/backend/wasm/module-emitter.js +1615 -0
  26. package/src/backend/wasm/monomorph.js +164 -9
  27. package/src/backend/wasm/natives.js +596 -0
  28. package/src/backend/wasm/prescan.js +249 -0
  29. package/src/backend/wasm/prune-funcs.js +89 -0
  30. package/src/backend/wasm/stdlib/index.js +2 -37
  31. package/src/backend/wasm/stdlib/simd.js +293 -28
  32. package/src/backend/wasm/types.js +260 -27
  33. package/src/backend/wasm/variadic.js +93 -0
  34. package/src/backend/wasm/wat.js +47 -20
  35. package/src/{cli-core.js → cli/cli-core.js} +241 -11
  36. package/src/{test-runner.js → cli/test-runner.js} +188 -122
  37. package/src/{vendor.js → cli/vendor.js} +3 -4
  38. package/src/compiler.js +202 -386
  39. package/src/index.js +15 -169
  40. package/src/{lexer.js → lexer/lexer.js} +203 -62
  41. package/src/{templ-lexer.js → lexer/templ-lexer.js} +61 -18
  42. package/src/{tokens.js → lexer/tokens.js} +2 -0
  43. package/src/link/sourcemap.js +97 -0
  44. package/src/link/stdlib.js +737 -0
  45. package/src/link/type-names.js +148 -0
  46. package/src/link/wasm.js +318 -0
  47. package/src/lower/bounds.js +547 -0
  48. package/src/lower/boxing.js +1 -1
  49. package/src/lower/captures.js +267 -64
  50. package/src/lower/embedding.js +32 -3
  51. package/src/lower/errors-as.js +196 -0
  52. package/src/lower/fmt-formatters.js +905 -0
  53. package/src/lower/functions.js +2 -0
  54. package/src/lower/gen-types.js +274 -0
  55. package/src/lower/index.js +33 -19
  56. package/src/lower/json-codecs.js +1379 -0
  57. package/src/lower/ownership.js +47 -8
  58. package/src/lower/range.js +86 -1
  59. package/src/lower/reachability.js +150 -0
  60. package/src/lower/walk.js +12 -0
  61. package/src/parser/declarations.js +83 -15
  62. package/src/parser/expressions.js +110 -18
  63. package/src/parser/index.js +2 -1
  64. package/src/parser/statements.js +38 -22
  65. package/src/{templ-parser.js → parser/templ.js} +179 -17
  66. package/src/parser/types.js +70 -9
  67. package/src/resolver.js +21 -1
  68. package/src/typechecker/assignability.js +291 -19
  69. package/src/typechecker/const-fold.js +399 -0
  70. package/src/typechecker/constants.js +282 -0
  71. package/src/typechecker/expressions.js +1220 -183
  72. package/src/typechecker/index.js +659 -92
  73. package/src/typechecker/init-order.js +426 -0
  74. package/src/typechecker/operand-text.js +40 -0
  75. package/src/typechecker/resolve.js +613 -62
  76. package/src/typechecker/statements.js +212 -20
  77. package/src/typechecker/stdlib/excluded.js +902 -0
  78. package/src/typechecker/stdlib/index.js +38 -0
  79. package/src/typechecker/stdlib/simd.js +65 -15
  80. package/src/typechecker/termination.js +11 -2
  81. package/src/typechecker/types.js +268 -514
  82. package/stdlib/bytes/buffer.go +479 -0
  83. package/stdlib/bytes/bytes.go +1010 -0
  84. package/stdlib/bytes/reader.go +161 -0
  85. package/stdlib/cmp/cmp.go +65 -0
  86. package/stdlib/context/context.go +115 -0
  87. package/stdlib/crypto/rand/rand.go +34 -0
  88. package/stdlib/crypto/sha256/sha256.go +249 -0
  89. package/stdlib/encoding/base64/base64.go +331 -0
  90. package/stdlib/encoding/binary/binary.go +332 -0
  91. package/stdlib/encoding/hex/hex.go +140 -0
  92. package/stdlib/encoding/json/errors.go +31 -0
  93. package/stdlib/encoding/json/indent.go +71 -0
  94. package/stdlib/encoding/json/json.go +62 -0
  95. package/stdlib/encoding/json/number.go +12 -0
  96. package/stdlib/encoding/json/stream.go +173 -0
  97. package/stdlib/errors/errors.go +174 -0
  98. package/stdlib/fmt/errors.go +110 -0
  99. package/stdlib/fmt/format.go +732 -0
  100. package/stdlib/fmt/print.go +1232 -0
  101. package/stdlib/gom/gom.go +293 -0
  102. package/stdlib/hash/hash.go +53 -0
  103. package/stdlib/html/html.go +251 -0
  104. package/stdlib/internal/fmtrt/fmtrt.go +163 -0
  105. package/stdlib/internal/jsonrt/dec.go +871 -0
  106. package/stdlib/internal/jsonrt/enc.go +516 -0
  107. package/stdlib/internal/jsonrt/indent.go +135 -0
  108. package/stdlib/internal/jsonrt/number.go +27 -0
  109. package/stdlib/internal/jsonrt/reader.go +73 -0
  110. package/stdlib/internal/jsonrt/scanner.go +658 -0
  111. package/stdlib/io/io.go +502 -0
  112. package/stdlib/iter/iter.go +18 -0
  113. package/stdlib/maps/maps.go +131 -0
  114. package/stdlib/math/bits/bits.go +507 -0
  115. package/stdlib/math/extra.go +272 -0
  116. package/stdlib/math/math.go +439 -0
  117. package/stdlib/math/rand/rand.go +297 -0
  118. package/stdlib/os/os.go +93 -0
  119. package/stdlib/path/filepath/filepath.go +47 -0
  120. package/stdlib/path/path.go +394 -0
  121. package/stdlib/regexp/regexp.go +231 -0
  122. package/stdlib/slices/slices.go +526 -0
  123. package/stdlib/slices/sort.go +209 -0
  124. package/stdlib/slices/zsort_cmp.go +480 -0
  125. package/stdlib/slices/zsort_ordered.go +482 -0
  126. package/stdlib/sort/sort.go +500 -0
  127. package/stdlib/sort/zsort_func.go +480 -0
  128. package/stdlib/sort/zsort_iface.go +480 -0
  129. package/stdlib/strconv/float.go +1699 -0
  130. package/stdlib/strconv/isprint.go +752 -0
  131. package/stdlib/strconv/quote.go +526 -0
  132. package/stdlib/strconv/strconv.go +616 -0
  133. package/stdlib/strings/builder.go +95 -0
  134. package/stdlib/strings/reader.go +163 -0
  135. package/stdlib/strings/replacer.go +102 -0
  136. package/stdlib/strings/strings.go +807 -0
  137. package/stdlib/sync/sync.go +268 -0
  138. package/stdlib/testing/async_js.go +61 -0
  139. package/stdlib/testing/benchmark.go +297 -0
  140. package/stdlib/testing/testing.go +523 -0
  141. package/stdlib/time/duration.go +233 -0
  142. package/stdlib/time/format.go +745 -0
  143. package/stdlib/time/parse.go +790 -0
  144. package/stdlib/time/sleep_js.go +63 -0
  145. package/stdlib/time/time.go +834 -0
  146. package/stdlib/unicode/unicode.go +250 -0
  147. package/stdlib/unicode/utf8/utf8.go +464 -0
  148. package/src/backend/js/stdlib/base64.js +0 -12
  149. package/src/backend/js/stdlib/binary.js +0 -102
  150. package/src/backend/js/stdlib/bits.js +0 -46
  151. package/src/backend/js/stdlib/builder.js +0 -63
  152. package/src/backend/js/stdlib/bytes.js +0 -122
  153. package/src/backend/js/stdlib/errors.js +0 -44
  154. package/src/backend/js/stdlib/fmt.js +0 -178
  155. package/src/backend/js/stdlib/gom.js +0 -155
  156. package/src/backend/js/stdlib/hex.js +0 -17
  157. package/src/backend/js/stdlib/html.js +0 -20
  158. package/src/backend/js/stdlib/io.js +0 -137
  159. package/src/backend/js/stdlib/json.js +0 -972
  160. package/src/backend/js/stdlib/maps.js +0 -45
  161. package/src/backend/js/stdlib/math.js +0 -69
  162. package/src/backend/js/stdlib/os.js +0 -13
  163. package/src/backend/js/stdlib/path.js +0 -48
  164. package/src/backend/js/stdlib/rand.js +0 -32
  165. package/src/backend/js/stdlib/regexp.js +0 -54
  166. package/src/backend/js/stdlib/sha256.js +0 -12
  167. package/src/backend/js/stdlib/shared.js +0 -17
  168. package/src/backend/js/stdlib/simd.js +0 -24
  169. package/src/backend/js/stdlib/slices.js +0 -77
  170. package/src/backend/js/stdlib/sort.js +0 -30
  171. package/src/backend/js/stdlib/strconv.js +0 -74
  172. package/src/backend/js/stdlib/strings.js +0 -222
  173. package/src/backend/js/stdlib/sync.js +0 -13
  174. package/src/backend/js/stdlib/testing.js +0 -34
  175. package/src/backend/js/stdlib/time.js +0 -128
  176. package/src/backend/js/stdlib/unicode.js +0 -25
  177. package/src/backend/js/stdlib/utf8.js +0 -26
  178. package/src/backend/wasm/const-fold.js +0 -205
  179. package/src/backend/wasm/emit-stdlib.js +0 -13
  180. package/src/backend/wasm/stdlib/bytes.js +0 -5039
  181. package/src/backend/wasm/stdlib/core.js +0 -8903
  182. package/src/backend/wasm/stdlib/io.js +0 -2161
  183. package/src/backend/wasm/stdlib/packages.js +0 -2400
  184. package/src/backend/wasm/stdlib/pure.js +0 -720
  185. package/src/backend/wasm/stdlib/time.js +0 -1200
  186. package/src/typechecker/stdlib/core.js +0 -801
  187. package/src/typechecker/stdlib/extended.js +0 -914
  188. package/src/typechecker/stdlib.js +0 -22
  189. /package/src/{asset-manager.js → cli/asset-manager.js} +0 -0
  190. /package/src/{colors.js → cli/colors.js} +0 -0
  191. /package/src/{dev-server.js → cli/dev-server.js} +0 -0
  192. /package/src/{minifier.js → cli/minifier.js} +0 -0
  193. /package/src/{project-config.js → cli/project-config.js} +0 -0
  194. /package/src/{pwa.js → cli/pwa.js} +0 -0
@@ -6,13 +6,12 @@ import { statSync } from "node:fs";
6
6
  import { createRequire } from "node:module";
7
7
  import { basename, dirname, join, resolve } from "node:path";
8
8
  import { pathToFileURL } from "node:url";
9
- import { HELPER_SPRINTF, HELPER_TESTING } from "./backend/js/runtime.js";
10
- import { colorEnabled, createColors, formatDiagnostic } from "./colors.js";
11
9
  import {
12
10
  compilePackageTests,
13
11
  compilePackageTestsWasm,
14
12
  gwFilesIn,
15
- } from "./compiler.js";
13
+ } from "../compiler.js";
14
+ import { colorEnabled, createColors, formatDiagnostic } from "./colors.js";
16
15
 
17
16
  // WasmGC + exception handling (what the wasm backend emits) ship enabled by
18
17
  // default from Node 22 (V8 11.9+). Older runtimes fail at instantiate time
@@ -30,11 +29,55 @@ export function checkWasmRuntime(version = process.version) {
30
29
  export function isTestFunc(decl) {
31
30
  if (decl.kind !== "FuncDecl") return false;
32
31
  if (!decl.name.startsWith("Test")) return false;
32
+ if (decl.name === "TestMain") return false;
33
+ if (decl.params?.length !== 1) return false;
34
+ const pType = decl.params[0].type;
35
+ if (
36
+ pType?.kind === "PointerType" &&
37
+ (pType.base?.name === "testing.T" || pType.base?.name === "T")
38
+ )
39
+ return true;
40
+ if (
41
+ pType?.kind === "TypeName" &&
42
+ (pType.name === "testing.T" || pType.name === "T")
43
+ )
44
+ return true;
45
+ return false;
46
+ }
47
+
48
+ export function isBenchmarkFunc(decl) {
49
+ if (decl.kind !== "FuncDecl") return false;
50
+ if (!decl.name.startsWith("Benchmark")) return false;
51
+ if (decl.params?.length !== 1) return false;
52
+ const pType = decl.params[0].type;
53
+ if (
54
+ pType?.kind === "PointerType" &&
55
+ (pType.base?.name === "testing.B" || pType.base?.name === "B")
56
+ )
57
+ return true;
58
+ if (
59
+ pType?.kind === "TypeName" &&
60
+ (pType.name === "testing.B" || pType.name === "B")
61
+ )
62
+ return true;
63
+ return false;
64
+ }
65
+
66
+ export function isTestMainFunc(decl) {
67
+ if (decl.kind !== "FuncDecl") return false;
68
+ if (decl.name !== "TestMain") return false;
33
69
  if (decl.params?.length !== 1) return false;
34
70
  const pType = decl.params[0].type;
35
- if (pType?.kind === "PointerType" && pType.base?.name === "testing.T")
71
+ if (
72
+ pType?.kind === "PointerType" &&
73
+ (pType.base?.name === "testing.M" || pType.base?.name === "M")
74
+ )
75
+ return true;
76
+ if (
77
+ pType?.kind === "TypeName" &&
78
+ (pType.name === "testing.M" || pType.name === "M")
79
+ )
36
80
  return true;
37
- if (pType?.kind === "TypeName" && pType.name === "testing.T") return true;
38
81
  return false;
39
82
  }
40
83
 
@@ -53,8 +96,89 @@ export function discoverTests(programs) {
53
96
  return testNames;
54
97
  }
55
98
 
56
- export function generateTestHarness(bundleJs, testNames, options = {}) {
57
- const runRegex = options.run ? options.run : null;
99
+ // The `async func Test…` names (js packages only).
100
+ export function discoverAsyncTests(programs) {
101
+ const names = [];
102
+ for (const p of programs) {
103
+ if (p._filename && !p._filename.endsWith("_test.go")) continue;
104
+ for (const d of p.decls) {
105
+ if (isTestFunc(d) && (d.async || d.isAsync) && !names.includes(d.name))
106
+ names.push(d.name);
107
+ }
108
+ }
109
+ return names;
110
+ }
111
+
112
+ export function discoverBenchmarks(programs) {
113
+ const benchNames = [];
114
+ for (const p of programs) {
115
+ if (p._filename && !p._filename.endsWith("_test.go")) continue;
116
+ for (const d of p.decls) {
117
+ if (isBenchmarkFunc(d) && !benchNames.includes(d.name)) {
118
+ benchNames.push(d.name);
119
+ }
120
+ }
121
+ }
122
+ return benchNames;
123
+ }
124
+
125
+ export function discoverTestMain(programs) {
126
+ for (const p of programs) {
127
+ if (p._filename && !p._filename.endsWith("_test.go")) continue;
128
+ for (const d of p.decls) {
129
+ if (isTestMainFunc(d)) return d.name;
130
+ }
131
+ }
132
+ return null;
133
+ }
134
+
135
+ // The Go source of the driver the runner compiles with a package's tests (see
136
+ // `testDriver` in compileFiles; it is never written to disk). `target` is the
137
+ // package's target: a `js` package is driven asynchronously so that async
138
+ // tests and subtests can be awaited; a TestMain gets the M and runs it
139
+ // itself.
140
+ export function generateTestDriver(
141
+ pkgName,
142
+ target,
143
+ { testNames, benchNames, testMain, asyncTests = [] },
144
+ ) {
145
+ if (testMain && asyncTests.length > 0) {
146
+ throw new Error(
147
+ `async test functions cannot be combined with TestMain (${asyncTests.join(", ")}): remove TestMain or make the tests synchronous`,
148
+ );
149
+ }
150
+ const entries = (names, _type) =>
151
+ names.map((n) => `\t\t{Name: ${JSON.stringify(n)}, F: ${n}},`).join("\n");
152
+ const table = (type, names) =>
153
+ names.length === 0
154
+ ? `[]testing.${type}{}`
155
+ : `[]testing.${type}{\n${entries(names)}\n\t}`;
156
+ const make = `\tm := testing.GofrontMain(${JSON.stringify(pkgName)}, run, bench, verbose, ${table("GofrontTest", testNames)}, ${table("GofrontBenchmark", benchNames)})`;
157
+ let body;
158
+ let head = "func";
159
+ if (testMain) {
160
+ body = `\tTestMain(m)\n\treturn m.ExitCode()`;
161
+ } else if (target === "js") {
162
+ head = "async func";
163
+ body = "\treturn await m.RunAsync()";
164
+ } else {
165
+ body = "\treturn m.Run()";
166
+ }
167
+ return `package ${pkgName}
168
+
169
+ import "testing"
170
+
171
+ ${head} GofrontRunTests(run, bench string, verbose bool) int {
172
+ ${make}
173
+ ${body}
174
+ }
175
+ `;
176
+ }
177
+
178
+ // The module the test child process runs: the compiled package (with its
179
+ // driver), then one call to GofrontRunTests. Everything else (running,
180
+ // filtering, reporting) happens inside the package.
181
+ export function generateTestHarness(bundleJs, options = {}) {
58
182
  const verbose = Boolean(options.verbose);
59
183
  const dom = Boolean(options.dom);
60
184
  const color = Boolean(options.color);
@@ -64,6 +188,12 @@ export function generateTestHarness(bundleJs, testNames, options = {}) {
64
188
  const jsdomSpec = options.jsdomPath
65
189
  ? pathToFileURL(options.jsdomPath).href
66
190
  : "jsdom";
191
+ const run = options.run ?? "";
192
+ const bench = options.bench
193
+ ? options.bench === true
194
+ ? "."
195
+ : options.bench
196
+ : "";
67
197
 
68
198
  return `
69
199
  ${
@@ -95,14 +225,12 @@ globalThis.customElements = __dom.window.customElements;
95
225
  globalThis.document = globalThis.document ?? null;
96
226
  `
97
227
  }
98
- globalThis.__gofront_verbose = ${verbose};
228
+ // The package line and colours the testing package prints come from here.
229
+ process.env.GOFRONT_TEST_LABEL = ${JSON.stringify(pkgName)};
230
+ process.env.GOFRONT_TEST_COLOR = ${color ? '"1"' : '"0"'};
99
231
  ${
100
232
  options.wasmBytes
101
- ? `
102
- // ── WASM module (tests run inside app.wasm; *testing.T stays in JS) ──
103
- ${HELPER_SPRINTF}
104
- ${HELPER_TESTING}
105
- globalThis.__GOFRONT_WASM_BYTES = Uint8Array.from(atob(${JSON.stringify(Buffer.from(options.wasmBytes).toString("base64"))}), (c) => c.charCodeAt(0));
233
+ ? `globalThis.__GOFRONT_WASM_BYTES = Uint8Array.from(atob(${JSON.stringify(Buffer.from(options.wasmBytes).toString("base64"))}), (c) => c.charCodeAt(0));
106
234
  `
107
235
  : ""
108
236
  }
@@ -110,106 +238,13 @@ globalThis.__GOFRONT_WASM_BYTES = Uint8Array.from(atob(${JSON.stringify(Buffer.f
110
238
  ${bundleJs}
111
239
  // ─────────────────────────────
112
240
 
113
- async function __runGoFrontSuite() {
114
- const allTests = ${JSON.stringify(testNames)};
115
- const runFilter = ${runRegex ? `new RegExp(${JSON.stringify(runRegex)})` : "null"};
116
- const verbose = ${verbose};
117
- const pkgName = ${JSON.stringify(pkgName)};
118
- const paint = ${color}
119
- ? (code) => (s) => "\\x1b[" + code + "m" + s + "\\x1b[0m"
120
- : () => (s) => s;
121
- const red = paint(31), green = paint(32), yellow = paint(33), dim = paint(2), bold = paint(1);
122
- const PASS = green("PASS"), FAIL = bold(red("FAIL")), SKIP = yellow("SKIP");
123
- const okLine = (name, t) => console.log(green("ok ") + "\\t" + name + "\\t" + dim(t + "s"));
124
-
125
- const testsToRun = runFilter
126
- ? allTests.filter((name) => runFilter.test(name))
127
- : allTests;
128
-
129
- if (testsToRun.length === 0) {
130
- if (allTests.length > 0 && runFilter) {
131
- console.log(yellow("testing: warning: no tests to run matching " + ${JSON.stringify(runRegex)}));
132
- }
133
- console.log(PASS);
134
- okLine(pkgName, "0.000");
135
- process.exit(0);
136
- }
137
-
138
- let totalFailures = 0;
139
- let totalSkipped = 0;
140
- const now = () => (typeof performance !== "undefined" ? performance.now() : Date.now());
141
- const suiteStart = now();
142
-
143
- // Go-style streaming report. In non-verbose mode only failures and skips are
144
- // shown, so the "=== RUN" header is emitted late for failures.
145
- const report = (t) => {
146
- const elapsed = dim("(" + ((now() - t._start) / 1000).toFixed(2) + "s)");
147
- if (t.skipped) {
148
- console.log("--- " + SKIP + ": " + t._name + " " + elapsed);
149
- for (const log of t._logs) console.log(" " + log);
150
- } else if (t.failed) {
151
- if (!verbose) console.log(dim("=== RUN ") + t._name);
152
- for (const log of t._logs) console.log(" " + red(log));
153
- console.log("--- " + FAIL + ": " + t._name + " " + elapsed);
154
- } else if (verbose) {
155
- for (const log of t._logs) console.log(" " + log);
156
- console.log("--- " + PASS + ": " + t._name + " " + elapsed);
157
- }
158
- };
159
-
160
- // FailNow/SkipNow are control flow; anything else is a panic → failure.
161
- const recordTestError = (t, e) => {
162
- if (e instanceof __GoFront_FailNow || e instanceof __GoFront_SkipNow) return;
163
- t.Fail();
164
- t.Log("panic: " + (e?.message ?? String(e)));
165
- };
166
-
167
- globalThis.__onSubtestStart = (name) => {
168
- if (verbose) console.log(dim("=== RUN ") + name);
169
- };
170
- globalThis.__onSubtestEnd = report;
171
-
172
- const __testRegistry = {
173
- ${testNames.map((n) => ` ${JSON.stringify(n)}: typeof ${n} !== "undefined" ? ${n} : null`).join(",\n")}
174
- };
175
-
176
- for (const name of testsToRun) {
177
- const fn = __testRegistry[name] ?? (typeof globalThis[name] === "function" ? globalThis[name] : null);
178
- if (typeof fn !== "function") {
179
- console.error(red("testing: cannot find test function " + name));
180
- totalFailures++;
181
- continue;
182
- }
183
-
184
- const t = new __GoFront_T(name);
185
- if (verbose) console.log(dim("=== RUN ") + name);
186
-
187
- try {
188
- const res = fn(t);
189
- if (res && typeof res.then === "function") await res;
190
- } catch (e) {
191
- recordTestError(t, e);
192
- }
193
-
194
- if (t.skipped) totalSkipped++;
195
- else if (t.failed) totalFailures++;
196
- report(t);
197
- }
198
-
199
- const suiteElapsed = ((now() - suiteStart) / 1000).toFixed(3);
200
-
201
- if (totalFailures > 0) {
202
- console.log(FAIL);
203
- console.log(FAIL + "\\t" + pkgName + "\\t" + dim(suiteElapsed + "s"));
204
- process.exit(1);
205
- } else {
206
- console.log(PASS);
207
- okLine(pkgName, suiteElapsed);
208
- process.exit(0);
209
- }
241
+ let __exitCode = 1;
242
+ try {
243
+ __exitCode = Number(await GofrontRunTests(${JSON.stringify(run)}, ${JSON.stringify(bench)}, ${verbose}));
244
+ } catch (e) {
245
+ console.error("panic: " + (e?.message ?? String(e)));
210
246
  }
211
-
212
- __runGoFrontSuite();
247
+ process.exit(__exitCode);
213
248
  `;
214
249
  }
215
250
 
@@ -324,10 +359,16 @@ function reportBuildFailure(err, resolvedDir, options) {
324
359
 
325
360
  // `both` packages run twice (JS and wasm) so the two backends are kept in
326
361
  // agreement; output of both runs is concatenated.
327
- async function runDualTarget(compiled, resolvedDir, runHarness, options) {
362
+ async function runDualTarget(
363
+ compiled,
364
+ resolvedDir,
365
+ runHarness,
366
+ compileOptions,
367
+ options,
368
+ ) {
328
369
  let wasmCompiled;
329
370
  try {
330
- wasmCompiled = compilePackageTestsWasm(resolvedDir, options);
371
+ wasmCompiled = compilePackageTestsWasm(resolvedDir, compileOptions);
331
372
  } catch (err) {
332
373
  return reportBuildFailure(err, resolvedDir, options);
333
374
  }
@@ -365,31 +406,50 @@ export async function runTests(targetDir, options = {}) {
365
406
  return reportEmptyTests(basename(resolvedDir), "no test files", options);
366
407
  }
367
408
 
409
+ // The driver is generated while the package compiles, once its files are
410
+ // parsed and its target is known.
411
+ let testNames = [];
412
+ let benchNames = [];
413
+ let testMain = null;
414
+ const compileOptions = {
415
+ ...options,
416
+ testDriver: (programs, target) => {
417
+ testNames = discoverTests(programs);
418
+ benchNames = discoverBenchmarks(programs);
419
+ testMain = discoverTestMain(programs);
420
+ if (testNames.length === 0 && benchNames.length === 0 && !testMain)
421
+ return null;
422
+ return generateTestDriver(programs[0].pkg.name, target, {
423
+ testNames,
424
+ benchNames,
425
+ testMain,
426
+ asyncTests: discoverAsyncTests(programs),
427
+ });
428
+ },
429
+ };
368
430
  let compiled;
369
431
  try {
370
- compiled = compilePackageTests(resolvedDir, options);
432
+ compiled = compilePackageTests(resolvedDir, compileOptions);
371
433
  } catch (err) {
372
434
  return reportBuildFailure(err, resolvedDir, options);
373
435
  }
374
436
 
375
- const { pkgName, js, programs } = compiled;
376
- const testNames = discoverTests(programs);
377
-
378
- if (testNames.length === 0) {
437
+ const { pkgName, js } = compiled;
438
+ if (testNames.length === 0 && benchNames.length === 0 && !testMain) {
379
439
  return reportEmptyTests(pkgName, "no tests to run", options);
380
440
  }
381
441
 
382
442
  const runHarness = (bundleJs, label, wasmBytes) =>
383
443
  spawnTestRunner(
384
444
  resolvedDir,
385
- generateTestHarness(bundleJs, testNames, {
445
+ generateTestHarness(bundleJs, {
386
446
  ...options,
387
447
  pkgName: label,
388
448
  jsdomPath,
389
449
  wasmBytes,
390
450
  color: colorsFor(options, options.stdout ?? process.stdout).enabled,
391
451
  }),
392
- testNames.length,
452
+ testNames.length + benchNames.length,
393
453
  options,
394
454
  );
395
455
 
@@ -403,7 +463,13 @@ export async function runTests(targetDir, options = {}) {
403
463
  }
404
464
  }
405
465
  if (compiled.target === "both") {
406
- return runDualTarget(compiled, resolvedDir, runHarness, options);
466
+ return runDualTarget(
467
+ compiled,
468
+ resolvedDir,
469
+ runHarness,
470
+ compileOptions,
471
+ options,
472
+ );
407
473
  }
408
474
  return runHarness(js, pkgName, compiled.wasm ?? null);
409
475
  }
@@ -442,10 +442,9 @@ export async function bundleVendor(projectDir = ".", options = {}) {
442
442
  }
443
443
 
444
444
  const entryCode = generateVendorEntry(packages, globals, namedExports);
445
- const entryFile = join(
446
- projectRoot,
447
- `.gofront-vendor-entry-${Date.now()}.mjs`,
448
- );
445
+ // A fixed name: the bundler records the entry path in vendor.js, so a
446
+ // per-run name would make every prep produce different bytes.
447
+ const entryFile = join(projectRoot, ".gofront-vendor-entry.mjs");
449
448
  writeFileSync(entryFile, entryCode);
450
449
 
451
450
  try {