@glubean/runner 0.8.1 → 0.8.4

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.
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Spawn-side of dry-run projection: launch the dry-run worker under tsx so it
3
+ * can import the user's TypeScript test modules, collect the projected shapes.
4
+ *
5
+ * Mirrors the TestExecutor spawn (tsx CLI + zero-project `--import` hook). The
6
+ * worker streams one sentinel line per file; a watchdog kills it if a body
7
+ * hangs (the request budget only bounds synthetic-HTTP loops — a pure-compute
8
+ * or non-HTTP data loop would otherwise block forever). Files that never report
9
+ * before the watchdog fires are returned as timeout errors, so partial results
10
+ * survive and `glubean dry-run` never hangs.
11
+ */
12
+ import type { TestShape } from "./dry-run.js";
13
+ export interface DryRunFilesResult {
14
+ shapes: Array<TestShape & {
15
+ file: string;
16
+ }>;
17
+ errors: Array<{
18
+ file: string;
19
+ message: string;
20
+ }>;
21
+ }
22
+ /**
23
+ * Project the shapes of every simple test exported from `files` by executing
24
+ * each body against a synthetic context in a tsx subprocess.
25
+ *
26
+ * @param files Absolute paths to test files.
27
+ * @param opts.cwd Working dir used to resolve the runner + zero-project mode
28
+ * (defaults to `process.cwd()`).
29
+ * @param opts.timeoutMs Watchdog budget for the whole run (default 60s).
30
+ */
31
+ export declare function dryRunFiles(files: string[], opts?: {
32
+ cwd?: string;
33
+ timeoutMs?: number;
34
+ }): Promise<DryRunFilesResult>;
35
+ //# sourceMappingURL=dry-run-spawn.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dry-run-spawn.d.ts","sourceRoot":"","sources":["../src/dry-run-spawn.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AASH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAE9C,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,KAAK,CAAC,SAAS,GAAG;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC5C,MAAM,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAClD;AA8FD;;;;;;;;GAQG;AACH,wBAAsB,WAAW,CAC/B,KAAK,EAAE,MAAM,EAAE,EACf,IAAI,GAAE;IAAE,GAAG,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAO,GAC9C,OAAO,CAAC,iBAAiB,CAAC,CA+G5B"}
@@ -0,0 +1,218 @@
1
+ /**
2
+ * Spawn-side of dry-run projection: launch the dry-run worker under tsx so it
3
+ * can import the user's TypeScript test modules, collect the projected shapes.
4
+ *
5
+ * Mirrors the TestExecutor spawn (tsx CLI + zero-project `--import` hook). The
6
+ * worker streams one sentinel line per file; a watchdog kills it if a body
7
+ * hangs (the request budget only bounds synthetic-HTTP loops — a pure-compute
8
+ * or non-HTTP data loop would otherwise block forever). Files that never report
9
+ * before the watchdog fires are returned as timeout errors, so partial results
10
+ * survive and `glubean dry-run` never hangs.
11
+ */
12
+ import { spawn } from "node:child_process";
13
+ import { existsSync, readdirSync, readFileSync } from "node:fs";
14
+ import { dirname, resolve } from "node:path";
15
+ import { fileURLToPath } from "node:url";
16
+ import { extractAliasesFromSource } from "@glubean/scanner";
17
+ import { prepareZeroProject, resolveRunnerRoot, resolveTsxPath } from "./runner-resolve.js";
18
+ import { DRY_RUN_SENTINEL } from "./dry-run-worker.js";
19
+ /** Default watchdog: max wall-clock for the whole worker run across all files. */
20
+ const DEFAULT_TIMEOUT_MS = 60_000;
21
+ /** Grace period between SIGTERM and SIGKILL. */
22
+ const KILL_GRACE_MS = 2_000;
23
+ const __filename = fileURLToPath(import.meta.url);
24
+ const __dirname = dirname(__filename);
25
+ const ALIAS_SKIP_DIRS = new Set(["node_modules", ".git", "dist", "build", ".next", "coverage", ".turbo"]);
26
+ /**
27
+ * Collect test.extend() aliases across the whole project so the worker can fold
28
+ * bareBranchCount even for tests that import an alias from a helper file NOT in
29
+ * the dry-run input set. Bounded (skips heavy dirs, caps files) — alias scanning
30
+ * is a cheap regex, never executes user code.
31
+ */
32
+ function collectProjectAliases(root, cap = 2000) {
33
+ const aliases = new Set();
34
+ let scanned = 0;
35
+ const walk = (dir) => {
36
+ if (scanned >= cap)
37
+ return;
38
+ let entries;
39
+ try {
40
+ entries = readdirSync(dir, { withFileTypes: true });
41
+ }
42
+ catch {
43
+ return;
44
+ }
45
+ for (const e of entries) {
46
+ if (scanned >= cap)
47
+ return;
48
+ if (e.isDirectory()) {
49
+ if (!ALIAS_SKIP_DIRS.has(e.name) && !e.name.startsWith("."))
50
+ walk(resolve(dir, e.name));
51
+ }
52
+ else if (e.isFile() && /\.(ts|tsx|mts|cts|js|jsx|mjs|cjs)$/.test(e.name) && !e.name.endsWith(".d.ts")) {
53
+ scanned++;
54
+ try {
55
+ for (const a of extractAliasesFromSource(readFileSync(resolve(dir, e.name), "utf8")))
56
+ aliases.add(a);
57
+ }
58
+ catch {
59
+ /* unreadable file → skip */
60
+ }
61
+ }
62
+ }
63
+ };
64
+ walk(root);
65
+ return [...aliases];
66
+ }
67
+ /** Run a node subprocess, killing it if it exceeds `timeoutMs`. */
68
+ function runNode(args, cwd, env, timeoutMs) {
69
+ return new Promise((resolveOut, reject) => {
70
+ const child = spawn("node", args, { cwd, env, stdio: ["ignore", "pipe", "inherit"] });
71
+ let stdout = "";
72
+ let timedOut = false;
73
+ const watchdog = setTimeout(() => {
74
+ timedOut = true;
75
+ child.kill("SIGTERM");
76
+ const hard = setTimeout(() => {
77
+ try {
78
+ child.kill("SIGKILL");
79
+ }
80
+ catch {
81
+ /* already gone */
82
+ }
83
+ }, KILL_GRACE_MS);
84
+ hard.unref?.();
85
+ }, timeoutMs);
86
+ watchdog.unref?.();
87
+ child.stdout.on("data", (chunk) => {
88
+ stdout += chunk.toString();
89
+ });
90
+ child.on("error", (err) => {
91
+ clearTimeout(watchdog);
92
+ reject(err);
93
+ });
94
+ child.on("close", (code, signal) => {
95
+ clearTimeout(watchdog);
96
+ resolveOut({ stdout, timedOut, code, signal });
97
+ });
98
+ });
99
+ }
100
+ /**
101
+ * Project the shapes of every simple test exported from `files` by executing
102
+ * each body against a synthetic context in a tsx subprocess.
103
+ *
104
+ * @param files Absolute paths to test files.
105
+ * @param opts.cwd Working dir used to resolve the runner + zero-project mode
106
+ * (defaults to `process.cwd()`).
107
+ * @param opts.timeoutMs Watchdog budget for the whole run (default 60s).
108
+ */
109
+ export async function dryRunFiles(files, opts = {}) {
110
+ if (files.length === 0)
111
+ return { shapes: [], errors: [] };
112
+ const cwd = opts.cwd ?? process.cwd();
113
+ const envTimeout = Number(process.env.GLUBEAN_DRYRUN_TIMEOUT_MS);
114
+ const timeoutMs = opts.timeoutMs ?? (Number.isFinite(envTimeout) && envTimeout > 0 ? envTimeout : DEFAULT_TIMEOUT_MS);
115
+ const bundledDistDir = __dirname;
116
+ const bundledPkgRoot = resolve(__dirname, "..");
117
+ const resolved = resolveRunnerRoot(cwd, bundledDistDir, bundledPkgRoot);
118
+ let distDir = resolved.distDir;
119
+ let pkgRoot = resolved.pkgRoot;
120
+ let workerPath = resolve(distDir, "dry-run-worker.js");
121
+ const oldProjectRunner = resolved.source === "project" && !existsSync(workerPath);
122
+ if (oldProjectRunner) {
123
+ // The resolved project runner predates dry-run support (has harness.js but
124
+ // no dry-run-worker.js). Fall back to the bundled worker — mirrors the
125
+ // load-harness fallback.
126
+ distDir = bundledDistDir;
127
+ pkgRoot = bundledPkgRoot;
128
+ workerPath = resolve(bundledDistDir, "dry-run-worker.js");
129
+ }
130
+ const zp = prepareZeroProject(cwd, distDir, pkgRoot);
131
+ // Split-SDK guard: the worker (+ its runWithRuntime carrier) runs from the
132
+ // BUNDLED runner — and thus the bundled @glubean/sdk — whenever resolveRunnerRoot
133
+ // had no usable PROJECT runner (`source === "bundled"`) or we fell back from an
134
+ // old project runner. If the project ALSO ships its own @glubean/sdk (empty
135
+ // tsxArgs ⟺ no vendored redirect), user modules import the PROJECT sdk while the
136
+ // worker installs the carrier on the BUNDLED sdk, so configure()/carrier helpers
137
+ // throw. Fail with an upgrade hint instead of projecting incorrectly. (A normal
138
+ // install where resolveRunnerRoot finds the project's own runner is `source ===
139
+ // "project"` and is NOT flagged, even if that runner is physically the bundled
140
+ // package.)
141
+ const workerFromBundled = resolved.source === "bundled" || oldProjectRunner;
142
+ if (workerFromBundled && zp.tsxArgs.length === 0) {
143
+ zp.cleanup();
144
+ return {
145
+ shapes: [],
146
+ errors: [
147
+ {
148
+ file: "",
149
+ message: "dry-run needs @glubean/runner with dry-run support, but the project's installed @glubean/runner predates `glubean dry-run`. Upgrade @glubean/runner in this project.",
150
+ },
151
+ ],
152
+ };
153
+ }
154
+ try {
155
+ const args = [resolveTsxPath(), ...zp.tsxArgs, workerPath, ...files];
156
+ const env = {
157
+ ...process.env,
158
+ ...zp.env,
159
+ // Project-wide test.extend() aliases so the worker folds bareBranchCount
160
+ // even for aliases imported from helper files outside the input set.
161
+ GLUBEAN_DRYRUN_ALIASES: JSON.stringify(collectProjectAliases(cwd)),
162
+ };
163
+ const { stdout, timedOut, code, signal } = await runNode(args, cwd, env, timeoutMs);
164
+ const shapes = [];
165
+ const errors = [];
166
+ const reported = new Set();
167
+ let sawRecord = false;
168
+ // Split on the sentinel rather than by line, so a record still parses when a
169
+ // user module wrote to stdout WITHOUT a trailing newline before it (the
170
+ // sentinel then sits mid-line). Each record is `SENTINEL{json}\n`.
171
+ const parts = stdout.split(DRY_RUN_SENTINEL);
172
+ for (let i = 1; i < parts.length; i++) {
173
+ const nl = parts[i].indexOf("\n");
174
+ const jsonStr = nl >= 0 ? parts[i].slice(0, nl) : parts[i];
175
+ let rec;
176
+ try {
177
+ rec = JSON.parse(jsonStr);
178
+ }
179
+ catch {
180
+ continue; // truncated (e.g. killed mid-write) — skip
181
+ }
182
+ sawRecord = true;
183
+ if (rec.file)
184
+ reported.add(rec.file);
185
+ if (rec.shapes?.length)
186
+ shapes.push(...rec.shapes);
187
+ if (rec.error)
188
+ errors.push({ file: rec.file, message: rec.error });
189
+ }
190
+ // Attribute every UNREPORTED file to a per-file error (never a blank path),
191
+ // so callers always know which projections are missing. A reported file —
192
+ // including a builder-only file that emits an empty-shapes record — is a
193
+ // valid result and is not flagged.
194
+ const unreported = files.filter((f) => !reported.has(f));
195
+ if (unreported.length > 0) {
196
+ let why;
197
+ if (timedOut) {
198
+ why = `projection timed out after ${timeoutMs}ms (possible infinite loop or unintercepted async wait)`;
199
+ }
200
+ else {
201
+ const abnormal = signal ? `signal ${signal}` : code !== 0 && code !== null ? `code ${code}` : null;
202
+ why = abnormal
203
+ ? `worker exited with ${abnormal}`
204
+ : !sawRecord
205
+ ? "worker produced no output"
206
+ : "worker exited early (a module likely called process.exit())";
207
+ }
208
+ for (const f of unreported) {
209
+ errors.push({ file: f, message: `not projected (${why})` });
210
+ }
211
+ }
212
+ return { shapes, errors };
213
+ }
214
+ finally {
215
+ zp.cleanup();
216
+ }
217
+ }
218
+ //# sourceMappingURL=dry-run-spawn.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dry-run-spawn.js","sourceRoot":"","sources":["../src/dry-run-spawn.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Dry-run worker entry — runs UNDER tsx (so it can import the user's `.ts` test
3
+ * modules) with the zero-project `--import` hook (so their `@glubean/*` imports
4
+ * resolve). Spawned by `dryRunFiles()` in dry-run-spawn.ts.
5
+ *
6
+ * argv: absolute test-file paths.
7
+ * stdout: ONE line per file `__GLUBEAN_DRYRUN__<json>` with
8
+ * `{ file, shapes, error? }`, streamed as each file completes. The streaming is
9
+ * deliberate: if a later file's body hangs and the parent's watchdog kills this
10
+ * process, the already-emitted files' projections survive. The sentinel prefix
11
+ * isolates our payload from any `console.log` a test module emits at import.
12
+ */
13
+ import { type TestShape } from "./dry-run.js";
14
+ export declare const DRY_RUN_SENTINEL = "__GLUBEAN_DRYRUN__";
15
+ /** One streamed line: the projection of a single file. */
16
+ export interface DryRunFileResult {
17
+ file: string;
18
+ shapes: Array<TestShape & {
19
+ file: string;
20
+ }>;
21
+ error?: string;
22
+ }
23
+ //# sourceMappingURL=dry-run-worker.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dry-run-worker.d.ts","sourceRoot":"","sources":["../src/dry-run-worker.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAMH,OAAO,EAAoC,KAAK,SAAS,EAAE,MAAM,cAAc,CAAC;AAEhF,eAAO,MAAM,gBAAgB,uBAAuB,CAAC;AAErD,0DAA0D;AAC1D,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,KAAK,CAAC,SAAS,GAAG;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC5C,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB"}
@@ -0,0 +1,137 @@
1
+ /**
2
+ * Dry-run worker entry — runs UNDER tsx (so it can import the user's `.ts` test
3
+ * modules) with the zero-project `--import` hook (so their `@glubean/*` imports
4
+ * resolve). Spawned by `dryRunFiles()` in dry-run-spawn.ts.
5
+ *
6
+ * argv: absolute test-file paths.
7
+ * stdout: ONE line per file `__GLUBEAN_DRYRUN__<json>` with
8
+ * `{ file, shapes, error? }`, streamed as each file completes. The streaming is
9
+ * deliberate: if a later file's body hangs and the parent's watchdog kills this
10
+ * process, the already-emitted files' projections survive. The sentinel prefix
11
+ * isolates our payload from any `console.log` a test module emits at import.
12
+ */
13
+ import { readFileSync, writeSync } from "node:fs";
14
+ import { pathToFileURL } from "node:url";
15
+ import { extractAliasesFromSource, extractFromSource } from "@glubean/scanner";
16
+ import { bootstrap } from "./bootstrap.js";
17
+ import { dryRunTest, installDryRunGlobals } from "./dry-run.js";
18
+ export const DRY_RUN_SENTINEL = "__GLUBEAN_DRYRUN__";
19
+ /** Duck-type a simple Test object (`{ meta:{id}, type:"simple", fn }`). */
20
+ function isSimpleTest(v) {
21
+ if (!v || typeof v !== "object")
22
+ return false;
23
+ const t = v;
24
+ return t.type === "simple" && typeof t.fn === "function" && typeof t.meta?.id === "string";
25
+ }
26
+ function emit(rec) {
27
+ // Synchronous write to fd 1 so a streamed line is flushed to the pipe even if
28
+ // a later file's body calls process.exit() before async stdout would drain.
29
+ writeSync(1, DRY_RUN_SENTINEL + JSON.stringify(rec) + "\n");
30
+ }
31
+ async function main() {
32
+ const files = process.argv.slice(2);
33
+ // Neutralize raw I/O globals (e.g. fetch) BEFORE importing user modules, so
34
+ // even import-time or non-ctx I/O performs no real network call.
35
+ installDryRunGlobals();
36
+ // Collect test.extend() aliases across ALL input files (union) up front, so an
37
+ // alias declared in one input file and used by another is still recognized
38
+ // when folding bareBranchCount. (Aliases imported from files OUTSIDE the input
39
+ // set are still covered by the CLI's full-project scan + fold.)
40
+ const srcByFile = new Map();
41
+ const aliasSet = new Set();
42
+ // Project-wide aliases collected by the spawn parent (covers helper files
43
+ // outside the input set), unioned with the input files' own aliases.
44
+ try {
45
+ for (const a of JSON.parse(process.env.GLUBEAN_DRYRUN_ALIASES ?? "[]")) {
46
+ if (typeof a === "string")
47
+ aliasSet.add(a);
48
+ }
49
+ }
50
+ catch {
51
+ /* malformed env → fall back to input-file aliases only */
52
+ }
53
+ for (const f of files) {
54
+ try {
55
+ const s = readFileSync(f, "utf8");
56
+ srcByFile.set(f, s);
57
+ for (const a of extractAliasesFromSource(s))
58
+ aliasSet.add(a);
59
+ }
60
+ catch {
61
+ /* unreadable file → its import below will report the error */
62
+ }
63
+ }
64
+ const aliases = [...aliasSet];
65
+ // Run project setup (glubean.setup.ts) first so plugin/matcher registrations
66
+ // are installed before user modules import — same as the harness/load paths.
67
+ // A setup failure is surfaced but doesn't abort: per-file imports still run
68
+ // (and will report their own errors if they depend on the missing setup).
69
+ try {
70
+ await bootstrap(process.cwd());
71
+ }
72
+ catch (err) {
73
+ emit({ file: "", shapes: [], error: `setup (glubean.setup.ts) failed: ${err?.message ?? String(err)}` });
74
+ }
75
+ for (const file of files) {
76
+ const shapes = [];
77
+ try {
78
+ // Static bare-branch counts per export (AST, no execution) so the public
79
+ // dryRunFiles() API folds them into projectionComplete on its own — not
80
+ // only when the CLI patches shapes after a separate scan().
81
+ const bareByExport = new Map();
82
+ try {
83
+ const src = srcByFile.get(file) ?? readFileSync(file, "utf8");
84
+ // Pass the union of test.extend() aliases (as the scanner does) so
85
+ // aliased tests still get a bareBranchCount; extractFromSource merges
86
+ // BASE_FNS.
87
+ for (const m of extractFromSource(src, aliases)) {
88
+ if (m.bareBranchCount)
89
+ bareByExport.set(m.exportName, m.bareBranchCount);
90
+ }
91
+ }
92
+ catch {
93
+ /* best-effort: a parse failure just means no static signal */
94
+ }
95
+ const mod = await import(pathToFileURL(file).href);
96
+ for (const [exportName, val] of Object.entries(mod)) {
97
+ // `test.each(...)` / `test.pick(...)` export an ARRAY of simple Tests —
98
+ // one per generated row, each with its OWN testId AND its OWN bound row
99
+ // data. Project EACH row separately: a row builds requests/skips/asserts
100
+ // from its data (e.g. `row.base`), so cloning the first row's shape would
101
+ // misreport the others. A full-snapshot sync needs every row's true
102
+ // projection (replace would otherwise delete or corrupt the omitted rows).
103
+ const candidates = Array.isArray(val) ? val.filter(isSimpleTest) : isSimpleTest(val) ? [val] : [];
104
+ for (const c of candidates) {
105
+ const shape = await dryRunTest(c, {
106
+ exportName,
107
+ bareBranchCount: bareByExport.get(exportName),
108
+ });
109
+ shapes.push({ file, ...shape });
110
+ }
111
+ }
112
+ emit({ file, shapes });
113
+ }
114
+ catch (err) {
115
+ emit({ file, shapes, error: err?.message ?? String(err) });
116
+ }
117
+ }
118
+ }
119
+ // Only run when invoked directly as the spawned entry — NOT when imported (e.g.
120
+ // dry-run-spawn imports this module for DRY_RUN_SENTINEL). Without this guard,
121
+ // main() would run in the importing process against ITS argv.
122
+ const isEntry = !!process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href;
123
+ if (isEntry) {
124
+ main()
125
+ .then(() => {
126
+ // Exit explicitly once every record is emitted (writeSync already flushed
127
+ // them). Otherwise an open handle left by an imported module / setup (timer,
128
+ // mock server, DB pool) would keep the process alive until the parent's
129
+ // watchdog kills it — making a completed projection appear to hang.
130
+ process.exit(0);
131
+ })
132
+ .catch((err) => {
133
+ emit({ file: "", shapes: [], error: String(err) });
134
+ process.exit(1);
135
+ });
136
+ }
137
+ //# sourceMappingURL=dry-run-worker.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dry-run-worker.js","sourceRoot":"","sources":["../src/dry-run-worker.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,80 @@
1
+ /**
2
+ * Dry-run projection (C2 / P2) — execute a simple test's function body with a
3
+ * SYNTHETIC context that records its SHAPE (assertions made, endpoints hit)
4
+ * without performing any real I/O. Powers `glubean dry-run`, whose output feeds
5
+ * the cloud "test definition" team-review view.
6
+ *
7
+ * This is a SEPARATE execution path from the runner harness and from the
8
+ * scanner: the scanner never executes user code (pure AST), while dry-run DOES
9
+ * import + invoke the test body. The synthetic ctx makes that safe — every
10
+ * effect is intercepted — and a request budget breaks data-dependent loops.
11
+ *
12
+ * Branch completeness: native `if`/`switch` only reveals the one arm the
13
+ * synthetic response happens to take, so authors should use `ctx.when()` /
14
+ * `ctx.switch()`, which the projector runs EXHAUSTIVELY (every arm, branch-
15
+ * tagged). The scanner's `bareBranchCount` is folded in to mark a projection
16
+ * partial when bare branches remain.
17
+ */
18
+ import type { Test } from "@glubean/sdk";
19
+ /** A single recorded assertion intent. */
20
+ export interface ProjAssertion {
21
+ /** assert | expect.<matcher> | fail | validate */
22
+ kind: string;
23
+ message?: string;
24
+ /** Branch path, e.g. "when#0:then" or "switch#1:case[2]" (nested joined by ">"). */
25
+ branch?: string;
26
+ }
27
+ /** A single recorded endpoint hit. */
28
+ export interface ProjEndpoint {
29
+ method: string;
30
+ url: string;
31
+ branch?: string;
32
+ /** Author-declared canonical endpoint pattern via `.track("GET /users/:id")` —
33
+ * the exact key for endpoint-coverage linking (collapses concrete URLs). */
34
+ pattern?: string;
35
+ }
36
+ /** The projected shape of one test — what it verifies and what it touches. */
37
+ export interface TestShape {
38
+ testId: string;
39
+ exportName: string;
40
+ /** Resolved declared tags (static + `test.each` `tagFields`, per generated row).
41
+ * Taken from the RUNTIME `test.meta.tags`, not the static scan, so data-driven
42
+ * row tags (e.g. `country:JP`) match what `glubean run --tag` sees. */
43
+ tags?: string[];
44
+ assertions: ProjAssertion[];
45
+ endpoints: ProjEndpoint[];
46
+ assertionCount: number;
47
+ /**
48
+ * True when the projector captured the test's full shape. False when a bare
49
+ * `if`/`switch` remained (only one arm followed), the request budget broke a
50
+ * loop, the body threw, or the test isn't a simple test.
51
+ */
52
+ projectionComplete: boolean;
53
+ /** Human-readable reason when `projectionComplete` is false. */
54
+ incompleteReason?: string;
55
+ /** True when the body called `ctx.skip()`. */
56
+ skipped?: boolean;
57
+ }
58
+ /**
59
+ * Patch I/O globals so a dry-run never performs real network I/O even when a
60
+ * test body bypasses `ctx.http` (e.g. raw `fetch`). Raw fetches route into the
61
+ * ALS-carried recorder of the active projection. Returns a release function;
62
+ * the stub is removed when the last holder releases (ref-counted). The worker
63
+ * installs once before imports and never releases (process-wide). (fs/db clients
64
+ * a test imports directly can't be generically stubbed — authoring guidance is
65
+ * to route I/O through `ctx`.)
66
+ */
67
+ export declare function installDryRunGlobals(): () => void;
68
+ /**
69
+ * Project one test's shape by executing its body against a synthetic ctx.
70
+ *
71
+ * @param test The Test object (from importing the test module).
72
+ * @param opts.exportName JS export name (for the shape label).
73
+ * @param opts.bareBranchCount Scanner-reported count of native `if`/`switch` in
74
+ * the body — when > 0 the projection is marked partial.
75
+ */
76
+ export declare function dryRunTest(test: Test, opts: {
77
+ exportName: string;
78
+ bareBranchCount?: number;
79
+ }): Promise<TestShape>;
80
+ //# sourceMappingURL=dry-run.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dry-run.d.ts","sourceRoot":"","sources":["../src/dry-run.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAGH,OAAO,KAAK,EAAE,IAAI,EAA2B,MAAM,cAAc,CAAC;AAGlE,0CAA0C;AAC1C,MAAM,WAAW,aAAa;IAC5B,kDAAkD;IAClD,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,oFAAoF;IACpF,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,sCAAsC;AACtC,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;iFAC6E;IAC7E,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,8EAA8E;AAC9E,MAAM,WAAW,SAAS;IACxB,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB;;4EAEwE;IACxE,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,UAAU,EAAE,aAAa,EAAE,CAAC;IAC5B,SAAS,EAAE,YAAY,EAAE,CAAC;IAC1B,cAAc,EAAE,MAAM,CAAC;IACvB;;;;OAIG;IACH,kBAAkB,EAAE,OAAO,CAAC;IAC5B,gEAAgE;IAChE,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,8CAA8C;IAC9C,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAkHD;;;;;;;;GAQG;AACH,wBAAgB,oBAAoB,IAAI,MAAM,IAAI,CAuCjD;AAkQD;;;;;;;GAOG;AACH,wBAAsB,UAAU,CAC9B,IAAI,EAAE,IAAI,EACV,IAAI,EAAE;IAAE,UAAU,EAAE,MAAM,CAAC;IAAC,eAAe,CAAC,EAAE,MAAM,CAAA;CAAE,GACrD,OAAO,CAAC,SAAS,CAAC,CA6FpB"}