@c9up/helix 0.1.3 → 0.1.5

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 (235) hide show
  1. package/dist/cli/coverage/aggregate.d.ts +24 -0
  2. package/dist/cli/coverage/aggregate.d.ts.map +1 -0
  3. package/dist/cli/coverage/aggregate.js +215 -0
  4. package/dist/cli/coverage/aggregate.js.map +1 -0
  5. package/dist/cli/coverage/collect.d.ts +13 -0
  6. package/dist/cli/coverage/collect.d.ts.map +1 -0
  7. package/dist/cli/coverage/collect.js +66 -0
  8. package/dist/cli/coverage/collect.js.map +1 -0
  9. package/dist/cli/coverage/diff/base.d.ts +17 -0
  10. package/dist/cli/coverage/diff/base.d.ts.map +1 -0
  11. package/dist/cli/coverage/diff/base.js +44 -0
  12. package/dist/cli/coverage/diff/base.js.map +1 -0
  13. package/dist/cli/coverage/diff/index.d.ts +25 -0
  14. package/dist/cli/coverage/diff/index.d.ts.map +1 -0
  15. package/dist/cli/coverage/diff/index.js +119 -0
  16. package/dist/cli/coverage/diff/index.js.map +1 -0
  17. package/dist/cli/coverage/diff/overlay.d.ts +19 -0
  18. package/dist/cli/coverage/diff/overlay.d.ts.map +1 -0
  19. package/dist/cli/coverage/diff/overlay.js +58 -0
  20. package/dist/cli/coverage/diff/overlay.js.map +1 -0
  21. package/dist/cli/coverage/diff/parse.d.ts +27 -0
  22. package/dist/cli/coverage/diff/parse.d.ts.map +1 -0
  23. package/dist/cli/coverage/diff/parse.js +105 -0
  24. package/dist/cli/coverage/diff/parse.js.map +1 -0
  25. package/dist/cli/coverage/diff/reporters.d.ts +12 -0
  26. package/dist/cli/coverage/diff/reporters.d.ts.map +1 -0
  27. package/dist/cli/coverage/diff/reporters.js +59 -0
  28. package/dist/cli/coverage/diff/reporters.js.map +1 -0
  29. package/dist/cli/coverage/diff/types.d.ts +46 -0
  30. package/dist/cli/coverage/diff/types.d.ts.map +1 -0
  31. package/dist/cli/coverage/diff/types.js +15 -0
  32. package/dist/cli/coverage/diff/types.js.map +1 -0
  33. package/dist/cli/coverage/filter.d.ts +13 -0
  34. package/dist/cli/coverage/filter.d.ts.map +1 -0
  35. package/dist/cli/coverage/filter.js +60 -0
  36. package/dist/cli/coverage/filter.js.map +1 -0
  37. package/dist/cli/coverage/glob.d.ts +4 -0
  38. package/dist/cli/coverage/glob.d.ts.map +1 -0
  39. package/dist/cli/coverage/glob.js +0 -0
  40. package/dist/cli/coverage/glob.js.map +1 -0
  41. package/dist/cli/coverage/index.d.ts +40 -0
  42. package/dist/cli/coverage/index.d.ts.map +1 -0
  43. package/dist/cli/coverage/index.js +85 -0
  44. package/dist/cli/coverage/index.js.map +1 -0
  45. package/dist/cli/coverage/reporters/json.d.ts +13 -0
  46. package/dist/cli/coverage/reporters/json.d.ts.map +1 -0
  47. package/dist/cli/coverage/reporters/json.js +37 -0
  48. package/dist/cli/coverage/reporters/json.js.map +1 -0
  49. package/dist/cli/coverage/reporters/lcov.d.ts +13 -0
  50. package/dist/cli/coverage/reporters/lcov.d.ts.map +1 -0
  51. package/dist/cli/coverage/reporters/lcov.js +51 -0
  52. package/dist/cli/coverage/reporters/lcov.js.map +1 -0
  53. package/dist/cli/coverage/reporters/text.d.ts +8 -0
  54. package/dist/cli/coverage/reporters/text.d.ts.map +1 -0
  55. package/dist/cli/coverage/reporters/text.js +39 -0
  56. package/dist/cli/coverage/reporters/text.js.map +1 -0
  57. package/dist/cli/coverage/thresholds.d.ts +14 -0
  58. package/dist/cli/coverage/thresholds.d.ts.map +1 -0
  59. package/dist/cli/coverage/thresholds.js +58 -0
  60. package/dist/cli/coverage/thresholds.js.map +1 -0
  61. package/dist/cli/coverage/types.d.ts +118 -0
  62. package/dist/cli/coverage/types.d.ts.map +1 -0
  63. package/dist/cli/coverage/types.js +8 -0
  64. package/dist/cli/coverage/types.js.map +1 -0
  65. package/dist/cli/discover.d.ts +27 -0
  66. package/dist/cli/discover.d.ts.map +1 -0
  67. package/dist/cli/discover.js +142 -0
  68. package/dist/cli/discover.js.map +1 -0
  69. package/dist/cli/native.d.ts +43 -0
  70. package/dist/cli/native.d.ts.map +1 -0
  71. package/dist/cli/native.js +64 -0
  72. package/dist/cli/native.js.map +1 -0
  73. package/dist/cli/pool.d.ts +55 -0
  74. package/dist/cli/pool.d.ts.map +1 -0
  75. package/dist/cli/pool.js +404 -0
  76. package/dist/cli/pool.js.map +1 -0
  77. package/dist/cli/reporter.d.ts +51 -0
  78. package/dist/cli/reporter.d.ts.map +1 -0
  79. package/dist/cli/reporter.js +124 -0
  80. package/dist/cli/reporter.js.map +1 -0
  81. package/dist/cli/run.d.ts +61 -0
  82. package/dist/cli/run.d.ts.map +1 -0
  83. package/dist/cli/run.js +302 -0
  84. package/dist/cli/run.js.map +1 -0
  85. package/dist/cli/summary.d.ts +18 -0
  86. package/dist/cli/summary.d.ts.map +1 -0
  87. package/dist/cli/summary.js +20 -0
  88. package/dist/cli/summary.js.map +1 -0
  89. package/dist/cli/watch/loop.d.ts +26 -0
  90. package/dist/cli/watch/loop.d.ts.map +1 -0
  91. package/dist/cli/watch/loop.js +133 -0
  92. package/dist/cli/watch/loop.js.map +1 -0
  93. package/dist/cli/watch/types.d.ts +22 -0
  94. package/dist/cli/watch/types.d.ts.map +1 -0
  95. package/dist/cli/watch/types.js +6 -0
  96. package/dist/cli/watch/types.js.map +1 -0
  97. package/dist/cli/watch/watcher.d.ts +25 -0
  98. package/dist/cli/watch/watcher.d.ts.map +1 -0
  99. package/dist/cli/watch/watcher.js +122 -0
  100. package/dist/cli/watch/watcher.js.map +1 -0
  101. package/dist/container/index.d.ts +7 -0
  102. package/dist/container/index.d.ts.map +1 -0
  103. package/dist/container/index.js +6 -0
  104. package/dist/container/index.js.map +1 -0
  105. package/{src/container/override.ts → dist/container/override.d.ts} +6 -46
  106. package/dist/container/override.d.ts.map +1 -0
  107. package/dist/container/override.js +67 -0
  108. package/dist/container/override.js.map +1 -0
  109. package/{src/container/spy.ts → dist/container/spy.d.ts} +2 -6
  110. package/dist/container/spy.d.ts.map +1 -0
  111. package/dist/container/spy.js +23 -0
  112. package/dist/container/spy.js.map +1 -0
  113. package/dist/index.d.ts +20 -0
  114. package/dist/index.d.ts.map +1 -0
  115. package/{src/index.ts → dist/index.js} +2 -25
  116. package/dist/index.js.map +1 -0
  117. package/dist/runtime/assertion-error.d.ts +20 -0
  118. package/dist/runtime/assertion-error.d.ts.map +1 -0
  119. package/dist/runtime/assertion-error.js +23 -0
  120. package/dist/runtime/assertion-error.js.map +1 -0
  121. package/dist/runtime/cli-worker.d.ts +25 -0
  122. package/dist/runtime/cli-worker.d.ts.map +1 -0
  123. package/dist/runtime/cli-worker.js +128 -0
  124. package/dist/runtime/cli-worker.js.map +1 -0
  125. package/dist/runtime/equals.d.ts +25 -0
  126. package/dist/runtime/equals.d.ts.map +1 -0
  127. package/dist/runtime/equals.js +343 -0
  128. package/dist/runtime/equals.js.map +1 -0
  129. package/dist/runtime/expect.d.ts +32 -0
  130. package/dist/runtime/expect.d.ts.map +1 -0
  131. package/dist/runtime/expect.js +121 -0
  132. package/dist/runtime/expect.js.map +1 -0
  133. package/{src/runtime/index.ts → dist/runtime/index.d.ts} +5 -27
  134. package/dist/runtime/index.d.ts.map +1 -0
  135. package/dist/runtime/index.js +17 -0
  136. package/dist/runtime/index.js.map +1 -0
  137. package/{src/runtime/lifecycle.ts → dist/runtime/lifecycle.d.ts} +2 -8
  138. package/dist/runtime/lifecycle.d.ts.map +1 -0
  139. package/dist/runtime/lifecycle.js +10 -0
  140. package/dist/runtime/lifecycle.js.map +1 -0
  141. package/dist/runtime/matchers.d.ts +42 -0
  142. package/dist/runtime/matchers.d.ts.map +1 -0
  143. package/dist/runtime/matchers.js +375 -0
  144. package/dist/runtime/matchers.js.map +1 -0
  145. package/dist/runtime/run.d.ts +55 -0
  146. package/dist/runtime/run.d.ts.map +1 -0
  147. package/dist/runtime/run.js +456 -0
  148. package/dist/runtime/run.js.map +1 -0
  149. package/dist/runtime/suite.d.ts +91 -0
  150. package/dist/runtime/suite.d.ts.map +1 -0
  151. package/dist/runtime/suite.js +206 -0
  152. package/dist/runtime/suite.js.map +1 -0
  153. package/dist/runtime/test-context.d.ts +25 -0
  154. package/dist/runtime/test-context.d.ts.map +1 -0
  155. package/dist/runtime/test-context.js +52 -0
  156. package/dist/runtime/test-context.js.map +1 -0
  157. package/dist/runtime/vi/fake-timers.d.ts +48 -0
  158. package/dist/runtime/vi/fake-timers.d.ts.map +1 -0
  159. package/dist/runtime/vi/fake-timers.js +317 -0
  160. package/dist/runtime/vi/fake-timers.js.map +1 -0
  161. package/dist/runtime/vi/index.d.ts +70 -0
  162. package/dist/runtime/vi/index.d.ts.map +1 -0
  163. package/dist/runtime/vi/index.js +209 -0
  164. package/dist/runtime/vi/index.js.map +1 -0
  165. package/dist/runtime/vi/spy.d.ts +54 -0
  166. package/dist/runtime/vi/spy.d.ts.map +1 -0
  167. package/dist/runtime/vi/spy.js +159 -0
  168. package/dist/runtime/vi/spy.js.map +1 -0
  169. package/dist/runtime/vi/spyOn.d.ts +28 -0
  170. package/dist/runtime/vi/spyOn.d.ts.map +1 -0
  171. package/dist/runtime/vi/spyOn.js +121 -0
  172. package/dist/runtime/vi/spyOn.js.map +1 -0
  173. package/dist/runtime/vi/system-time.d.ts +30 -0
  174. package/dist/runtime/vi/system-time.d.ts.map +1 -0
  175. package/dist/runtime/vi/system-time.js +100 -0
  176. package/dist/runtime/vi/system-time.js.map +1 -0
  177. package/dist/runtime/worker.d.ts +26 -0
  178. package/dist/runtime/worker.d.ts.map +1 -0
  179. package/dist/runtime/worker.js +195 -0
  180. package/dist/runtime/worker.js.map +1 -0
  181. package/dist/time/freeze.d.ts +80 -0
  182. package/dist/time/freeze.d.ts.map +1 -0
  183. package/dist/time/freeze.js +184 -0
  184. package/dist/time/freeze.js.map +1 -0
  185. package/{src/time/index.ts → dist/time/index.d.ts} +1 -1
  186. package/dist/time/index.d.ts.map +1 -0
  187. package/dist/time/index.js +15 -0
  188. package/dist/time/index.js.map +1 -0
  189. package/index.darwin-arm64.node +0 -0
  190. package/index.darwin-x64.node +0 -0
  191. package/index.linux-arm64-gnu.node +0 -0
  192. package/index.linux-x64-gnu.node +0 -0
  193. package/index.win32-x64-msvc.node +0 -0
  194. package/package.json +2 -2
  195. package/src/cli/coverage/aggregate.ts +0 -231
  196. package/src/cli/coverage/collect.ts +0 -63
  197. package/src/cli/coverage/diff/base.ts +0 -46
  198. package/src/cli/coverage/diff/index.ts +0 -160
  199. package/src/cli/coverage/diff/overlay.ts +0 -62
  200. package/src/cli/coverage/diff/parse.ts +0 -121
  201. package/src/cli/coverage/diff/reporters.ts +0 -82
  202. package/src/cli/coverage/diff/types.ts +0 -46
  203. package/src/cli/coverage/filter.ts +0 -71
  204. package/src/cli/coverage/glob.ts +0 -0
  205. package/src/cli/coverage/index.ts +0 -126
  206. package/src/cli/coverage/reporters/json.ts +0 -40
  207. package/src/cli/coverage/reporters/lcov.ts +0 -54
  208. package/src/cli/coverage/reporters/text.ts +0 -48
  209. package/src/cli/coverage/thresholds.ts +0 -73
  210. package/src/cli/coverage/types.ts +0 -93
  211. package/src/cli/discover.ts +0 -174
  212. package/src/cli/native.ts +0 -104
  213. package/src/cli/pool.ts +0 -486
  214. package/src/cli/reporter.ts +0 -155
  215. package/src/cli/run.ts +0 -440
  216. package/src/cli/summary.ts +0 -42
  217. package/src/cli/watch/loop.ts +0 -159
  218. package/src/cli/watch/types.ts +0 -22
  219. package/src/cli/watch/watcher.ts +0 -145
  220. package/src/container/index.ts +0 -16
  221. package/src/runtime/assertion-error.ts +0 -38
  222. package/src/runtime/cli-worker.ts +0 -140
  223. package/src/runtime/equals.ts +0 -400
  224. package/src/runtime/expect.ts +0 -173
  225. package/src/runtime/matchers.ts +0 -452
  226. package/src/runtime/run.ts +0 -573
  227. package/src/runtime/suite.ts +0 -310
  228. package/src/runtime/test-context.ts +0 -59
  229. package/src/runtime/vi/fake-timers.ts +0 -410
  230. package/src/runtime/vi/index.ts +0 -254
  231. package/src/runtime/vi/spy.ts +0 -224
  232. package/src/runtime/vi/spyOn.ts +0 -155
  233. package/src/runtime/vi/system-time.ts +0 -121
  234. package/src/runtime/worker.ts +0 -239
  235. package/src/time/freeze.ts +0 -229
@@ -1,145 +0,0 @@
1
- /**
2
- * Recursive file watcher for `helix test --watch`. Wraps Node's
3
- * `fs.watch(root, { recursive: true })` with a glob filter (reusing the
4
- * coverage glob translator) and a single-timer debounce that coalesces
5
- * burst events into one `onChange` call.
6
- *
7
- * The shape (`createWatcher` returning a `{ close }` handle) is the
8
- * abstraction a future Rust `notify`-NAPI implementation slots into
9
- * without touching the loop.
10
- */
11
-
12
- import { type FSWatcher, watch as fsWatch, realpathSync } from "node:fs";
13
- import path from "node:path";
14
- import { compileGlobs, matchesAnyGlob } from "../coverage/glob.js";
15
-
16
- export interface CreateWatcherOptions {
17
- root: string;
18
- include: string[];
19
- exclude: string[];
20
- debounceMs: number;
21
- onChange: (paths: Set<string>) => void;
22
- /** Surfaces `FSWatcher` errors (recursive unsupported, EMFILE, etc.)
23
- * so the loop can stop instead of waiting forever on a dead handle. */
24
- onError?: (err: Error) => void;
25
- }
26
-
27
- export interface WatcherHandle {
28
- close(): Promise<void>;
29
- }
30
-
31
- const DEBOUNCE_MIN = 1;
32
- const DEBOUNCE_MAX = 5_000;
33
-
34
- function tryRealpath(p: string): string {
35
- try {
36
- return realpathSync(p);
37
- } catch {
38
- return p;
39
- }
40
- }
41
-
42
- export function createWatcher(opts: CreateWatcherOptions): WatcherHandle {
43
- if (
44
- opts.debounceMs < DEBOUNCE_MIN ||
45
- opts.debounceMs > DEBOUNCE_MAX ||
46
- !Number.isFinite(opts.debounceMs)
47
- ) {
48
- throw new Error(
49
- `createWatcher: debounceMs must be in [${DEBOUNCE_MIN}, ${DEBOUNCE_MAX}], got ${opts.debounceMs}`,
50
- );
51
- }
52
- if (opts.include.length === 0) {
53
- throw new Error(
54
- "createWatcher: include must be non-empty (an empty include matches nothing — silent watcher).",
55
- );
56
- }
57
-
58
- const include = compileGlobs(opts.include);
59
- const exclude = compileGlobs(opts.exclude);
60
- // Realpath the root so events delivered with the canonical path
61
- // (macOS `/tmp` → `/private/tmp`, Docker bind-mounts, pnpm worktree
62
- // symlinks) don't get filtered out as "outside root". Mirrors what
63
- // `coverage/filter.ts` does for the same reason.
64
- const root = tryRealpath(path.resolve(opts.root));
65
-
66
- const pending = new Set<string>();
67
- let timer: NodeJS.Timeout | undefined;
68
- let closed = false;
69
-
70
- function flush(): void {
71
- timer = undefined;
72
- if (closed) return;
73
- if (pending.size === 0) return;
74
- const batch = new Set(pending);
75
- pending.clear();
76
- opts.onChange(batch);
77
- }
78
-
79
- function arm(): void {
80
- if (timer) clearTimeout(timer);
81
- timer = setTimeout(flush, opts.debounceMs);
82
- }
83
-
84
- function shouldNotify(filename: string | null): string | undefined {
85
- if (!filename) return undefined;
86
- const abs = path.resolve(root, filename);
87
- const rel = path.relative(root, abs).split(path.sep).join("/");
88
- if (rel.length === 0 || rel.startsWith("..") || path.isAbsolute(rel)) {
89
- return undefined;
90
- }
91
- if (matchesAnyGlob(exclude, rel)) return undefined;
92
- if (!matchesAnyGlob(include, rel)) return undefined;
93
- return abs;
94
- }
95
-
96
- let watcher: FSWatcher;
97
- try {
98
- watcher = fsWatch(root, { recursive: true }, (_event, filename) => {
99
- if (closed) return;
100
- const abs = shouldNotify(typeof filename === "string" ? filename : null);
101
- if (!abs) return;
102
- pending.add(abs);
103
- arm();
104
- });
105
- } catch (err) {
106
- throw new Error(
107
- `createWatcher: fs.watch failed on ${root}: ${
108
- err instanceof Error ? err.message : String(err)
109
- }`,
110
- );
111
- }
112
-
113
- // On `'error'`, surface the failure to the caller and shut down so
114
- // the loop doesn't sit on a dead handle. Without this, recursive
115
- // watch failures on Linux<20 leave the user staring at a "watching…"
116
- // banner that will never tick.
117
- watcher.on("error", (err) => {
118
- if (closed) return;
119
- closed = true;
120
- if (timer) {
121
- clearTimeout(timer);
122
- timer = undefined;
123
- }
124
- try {
125
- watcher.close();
126
- } catch {
127
- /* already torn down */
128
- }
129
- opts.onError?.(err instanceof Error ? err : new Error(String(err)));
130
- });
131
-
132
- return {
133
- async close(): Promise<void> {
134
- if (closed) return;
135
- closed = true;
136
- if (timer) {
137
- clearTimeout(timer);
138
- timer = undefined;
139
- }
140
- pending.clear();
141
- watcher.close();
142
- await new Promise<void>((resolve) => setImmediate(resolve));
143
- },
144
- };
145
- }
@@ -1,16 +0,0 @@
1
- /**
2
- * `@c9up/helix/container` — barrel for the test-container facade.
3
- */
4
-
5
- export type {
6
- ContainerLike,
7
- ContainerToken,
8
- } from "../runtime/vi/index.js";
9
- export {
10
- clearActiveContainer,
11
- type HelixContainer,
12
- override,
13
- overrideOn,
14
- useContainer,
15
- } from "./override.js";
16
- export { spy } from "./spy.js";
@@ -1,38 +0,0 @@
1
- /**
2
- * Assertion error thrown by `expect` — carries structured fields so runners
3
- * (Vitest-compatible reporters, IDE integrations) can render diffs.
4
- */
5
- export interface AssertionErrorInit {
6
- message: string;
7
- actual?: unknown;
8
- expected?: unknown;
9
- operator?: string;
10
- showDiff?: boolean;
11
- }
12
-
13
- export class AssertionError extends Error {
14
- readonly actual: unknown;
15
- readonly expected: unknown;
16
- readonly operator: string | undefined;
17
- readonly showDiff: boolean;
18
-
19
- constructor(init: AssertionErrorInit) {
20
- super(init.message);
21
- this.name = "AssertionError";
22
- this.actual = init.actual;
23
- this.expected = init.expected;
24
- this.operator = init.operator;
25
- this.showDiff = init.showDiff ?? true;
26
- // V8 only: strip framework frames so the stack points at user code.
27
- const capture = (
28
- Error as { captureStackTrace?: (target: object, ctor: object) => void }
29
- ).captureStackTrace;
30
- if (typeof capture === "function") {
31
- capture(this, AssertionError);
32
- }
33
- }
34
- }
35
-
36
- export function isAssertionError(value: unknown): value is AssertionError {
37
- return value instanceof AssertionError;
38
- }
@@ -1,140 +0,0 @@
1
- /**
2
- * CLI worker entry — what the orchestrator spawns as a child process.
3
- *
4
- * Protocol:
5
- * 1. Parent writes one `{ "type":"run", "file":"...", "timeoutMs":n, "nonce":"..." }`
6
- * line to stdin, then closes stdin.
7
- * 2. We run the file via `runTestFile`.
8
- * 3. We emit a single framed line on **stderr** prefixed with
9
- * `__HELIX_RESULT__`, with the parent's `nonce` echoed back so a
10
- * test that happens to print that prefix can't spoof the result.
11
- * 4. Exit with code 0 on `result`, 1 on `error`.
12
- *
13
- * Pre-handshake errors (e.g. malformed instruction on stdin) are emitted
14
- * with the `__helix_pre_handshake__` magic nonce — the parent accepts
15
- * those only for `type === "error"` so a fixture can't use the magic to
16
- * fake success.
17
- *
18
- * Flush discipline: on the happy path we await the stream drain callback
19
- * before setting `process.exitCode` so the frame always reaches the
20
- * parent. For the fatal `unhandledRejection` path we use `fs.writeSync`
21
- * on fd 2 to guarantee the bytes hit the pipe before Node tears the
22
- * process down.
23
- */
24
-
25
- import { writeSync } from "node:fs";
26
- import { createInterface } from "node:readline";
27
- import { runTestFile } from "./worker.js";
28
-
29
- const FRAME_PREFIX = "__HELIX_RESULT__";
30
- const PRE_HANDSHAKE_NONCE = "__helix_pre_handshake__";
31
-
32
- interface RunMessage {
33
- type: "run";
34
- file: string;
35
- timeoutMs?: number;
36
- nonce: string;
37
- }
38
-
39
- async function emit(msg: unknown): Promise<void> {
40
- const line = `${FRAME_PREFIX}${JSON.stringify(msg)}\n`;
41
- await new Promise<void>((resolve, reject) => {
42
- process.stderr.write(line, (err) => {
43
- if (err) reject(err);
44
- else resolve();
45
- });
46
- });
47
- }
48
-
49
- /**
50
- * Synchronous emit used from fatal handlers (unhandledRejection) where
51
- * we can't afford to lose the frame to the process tear-down racing the
52
- * pipe drain. `writeSync` blocks until the kernel accepts the bytes.
53
- */
54
- function emitSync(msg: unknown): void {
55
- try {
56
- writeSync(2, `${FRAME_PREFIX}${JSON.stringify(msg)}\n`);
57
- } catch {
58
- /* pipe broken — nothing we can do */
59
- }
60
- }
61
-
62
- async function readInstruction(): Promise<RunMessage> {
63
- const rl = createInterface({ input: process.stdin });
64
- for await (const line of rl) {
65
- const trimmed = line.trim();
66
- if (!trimmed) continue;
67
- // Parse inside try so a bad JSON line propagates as a typed error
68
- // rather than leaking a SyntaxError that escapes `for await`.
69
- let parsed: unknown;
70
- try {
71
- parsed = JSON.parse(trimmed);
72
- } catch (err) {
73
- throw new Error(
74
- `malformed instruction JSON: ${err instanceof Error ? err.message : String(err)}`,
75
- );
76
- }
77
- if (
78
- parsed &&
79
- typeof parsed === "object" &&
80
- (parsed as { type?: unknown }).type === "run" &&
81
- typeof (parsed as { file?: unknown }).file === "string" &&
82
- typeof (parsed as { nonce?: unknown }).nonce === "string"
83
- ) {
84
- return parsed as RunMessage;
85
- }
86
- throw new Error(`invalid instruction shape: ${trimmed}`);
87
- }
88
- throw new Error("stdin closed before instruction arrived");
89
- }
90
-
91
- async function main(): Promise<void> {
92
- let instr: RunMessage;
93
- try {
94
- instr = await readInstruction();
95
- } catch (err) {
96
- // We don't know the real nonce yet — emit with the pre-handshake
97
- // magic so the parent accepts this specific error path.
98
- await emit({
99
- type: "error",
100
- file: undefined,
101
- message: err instanceof Error ? err.message : String(err),
102
- stack: err instanceof Error ? err.stack : undefined,
103
- nonce: PRE_HANDSHAKE_NONCE,
104
- }).catch(() => {});
105
- process.exitCode = 1;
106
- return;
107
- }
108
-
109
- // Installed AFTER the instruction is read so we can echo the right nonce.
110
- // Synchronous write so Node's default "crash on unhandledRejection"
111
- // behaviour can't evict the frame before the parent reads it.
112
- process.on("unhandledRejection", (reason) => {
113
- emitSync({
114
- type: "error",
115
- file: instr.file,
116
- message: `unhandledRejection: ${reason instanceof Error ? reason.message : String(reason)}`,
117
- stack: reason instanceof Error ? reason.stack : undefined,
118
- nonce: instr.nonce,
119
- });
120
- });
121
-
122
- try {
123
- const result = await runTestFile(instr.file, {
124
- timeoutMs: instr.timeoutMs,
125
- });
126
- await emit({ type: "result", result, nonce: instr.nonce });
127
- process.exitCode = result.totals.fail > 0 ? 1 : 0;
128
- } catch (err) {
129
- await emit({
130
- type: "error",
131
- file: instr.file,
132
- message: err instanceof Error ? err.message : String(err),
133
- stack: err instanceof Error ? err.stack : undefined,
134
- nonce: instr.nonce,
135
- }).catch(() => {});
136
- process.exitCode = 1;
137
- }
138
- }
139
-
140
- void main();