@glubean/runner 0.2.3 → 0.2.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.
@@ -0,0 +1,214 @@
1
+ /**
2
+ * @module project-runner
3
+ *
4
+ * `ProjectRunner` — single top-level API for "run the tests of a project".
5
+ *
6
+ * Wraps the orchestration primitives (`loadProjectEnv`, `bootstrap`,
7
+ * `RunOrchestrator`, `TestExecutor`) into one coherent pipeline with a
8
+ * well-typed event stream. Consumers (CLI, MCP, VSCode extension,
9
+ * third-party embedders) all go through this facade rather than re-
10
+ * assembling the primitives themselves.
11
+ *
12
+ * **Scope boundary:**
13
+ * - Facade does: env load → bootstrap → per-file-batched TestExecutor
14
+ * loop with session setup/teardown, metric recording
15
+ * - Facade does NOT: console presentation, trace-file writing, upload,
16
+ * result.json formatting, CI-specific flag guards, summary judgment.
17
+ * Consumers build their own summary by observing events.
18
+ *
19
+ * Batching is fixed at per-file batched (one tsx subprocess per file,
20
+ * all of that file's testIds batched into `TestExecutor.run(fileUrl, "",
21
+ * ctx, {testIds})`).
22
+ */
23
+ import { resolve } from "node:path";
24
+ import { pathToFileURL } from "node:url";
25
+ import { loadProjectOverlays } from "@glubean/scanner";
26
+ import { bootstrap } from "./bootstrap.js";
27
+ import { TestExecutor } from "./executor.js";
28
+ import { discoverSessionFile, RunOrchestrator, } from "./orchestrator.js";
29
+ import { MetricCollector } from "./thresholds.js";
30
+ import { toSingleExecutionOptions } from "./config.js";
31
+ // =============================================================================
32
+ // ProjectRunner
33
+ // =============================================================================
34
+ export class ProjectRunner {
35
+ options;
36
+ constructor(options) {
37
+ this.options = options;
38
+ }
39
+ /**
40
+ * Run the full pipeline as an async event stream.
41
+ *
42
+ * @example
43
+ * ```ts
44
+ * const runner = new ProjectRunner({...});
45
+ * for await (const event of runner.run()) {
46
+ * switch (event.type) { ... }
47
+ * }
48
+ * ```
49
+ */
50
+ async *run() {
51
+ const { rootDir, sharedConfig, vars: envVars, secrets, tests, noSession = false, interactive = false, signal, } = this.options;
52
+ const sessionStartDir = this.options.sessionStartDir ?? rootDir;
53
+ const metricCollector = this.options.metricCollector ?? new MetricCollector();
54
+ // ── 1. Bootstrap ─────────────────────────────────────────────────
55
+ yield { type: "bootstrap:start", projectRoot: rootDir };
56
+ try {
57
+ await bootstrap(rootDir);
58
+ }
59
+ catch (err) {
60
+ const error = err instanceof Error ? err : new Error(String(err));
61
+ yield { type: "bootstrap:failed", error };
62
+ yield { type: "run:failed", reason: "bootstrap-failed", error: error.message };
63
+ return;
64
+ }
65
+ // attachment-model §7.4: eagerly load every `*.bootstrap.{ts,js,mjs}`
66
+ // so `contract.bootstrap()` calls register their overlays before any
67
+ // test runs. Idempotent — CLI already calls this; second visit
68
+ // short-circuits via the scanner's mtime-keyed module cache.
69
+ // Per-file errors are surfaced but don't abort the run; a broken
70
+ // overlay file shouldn't block unrelated tests. Cases that depend
71
+ // on the missing overlay will hard-error at execute-time per §5.1.
72
+ const overlayLoad = await loadProjectOverlays(rootDir);
73
+ for (const err of overlayLoad.errors) {
74
+ yield { type: "overlay:load:failed", file: err.file, error: err.error };
75
+ }
76
+ yield { type: "bootstrap:done" };
77
+ // ── 3. Group tests by file (per-file batching) ───────────────────
78
+ const fileGroups = new Map();
79
+ for (const t of tests) {
80
+ const group = fileGroups.get(t.filePath) || [];
81
+ group.push(t);
82
+ fileGroups.set(t.filePath, group);
83
+ }
84
+ yield { type: "discovery:done", totalFiles: fileGroups.size, totalTests: tests.length };
85
+ // ── 4. Build executor (shared across all files) ──────────────────
86
+ const executor = this.options.executor ?? TestExecutor.fromSharedConfig(sharedConfig, {
87
+ cwd: rootDir,
88
+ ...(this.options.inspectBrk !== undefined && { inspectBrk: this.options.inspectBrk }),
89
+ });
90
+ const orchestrator = new RunOrchestrator(executor);
91
+ const sessionState = {};
92
+ let sessionSetupSucceeded = false;
93
+ // Wrap all post-executor-construction work in try/finally so the
94
+ // executor's own cleanup path (zero-project scratch teardown, any
95
+ // future finalizers) always runs — even on early return from
96
+ // session-setup failure or signal abort. This also covers the case
97
+ // where the generator is abandoned by its caller (iterator .return()
98
+ // triggers the finally block).
99
+ try {
100
+ // ── 5. Session setup ───────────────────────────────────────────
101
+ const sessionFile = noSession ? undefined : discoverSessionFile(sessionStartDir, rootDir);
102
+ yield { type: "session:discovered", sessionFile };
103
+ if (sessionFile) {
104
+ yield { type: "session:setup:start", sessionFile };
105
+ let setupFailed = false;
106
+ let failureInfo;
107
+ for await (const event of orchestrator.runSessionSetup(sessionFile, { vars: envVars, secrets, interactive }, toSingleExecutionOptions(sharedConfig))) {
108
+ if (event.type === "session:set") {
109
+ sessionState[event.key] = event.value;
110
+ }
111
+ else if (event.type === "status" && event.status === "failed") {
112
+ setupFailed = true;
113
+ failureInfo = { error: event.error, stack: event.stack };
114
+ }
115
+ yield { type: "session:setup:event", event };
116
+ }
117
+ if (setupFailed) {
118
+ yield { type: "session:setup:failed", ...(failureInfo ?? {}) };
119
+ // Best-effort teardown before bailing.
120
+ yield { type: "session:teardown:start", sessionFile };
121
+ for await (const event of orchestrator.runSessionTeardown(sessionFile, { vars: envVars, secrets }, sessionState, toSingleExecutionOptions(sharedConfig))) {
122
+ yield { type: "session:teardown:event", event };
123
+ }
124
+ yield { type: "session:teardown:done" };
125
+ yield {
126
+ type: "run:failed",
127
+ reason: "session-setup-failed",
128
+ ...(failureInfo?.error !== undefined && { error: failureInfo.error }),
129
+ };
130
+ return;
131
+ }
132
+ sessionSetupSucceeded = true;
133
+ yield { type: "session:setup:done", stateKeys: Object.keys(sessionState) };
134
+ }
135
+ // ── 6. File loop (per-file batched) ────────────────────────────
136
+ let passedCount = 0;
137
+ let failedCount = 0;
138
+ let skippedCount = 0;
139
+ const failureLimit = sharedConfig.failAfter ??
140
+ (sharedConfig.failFast ? 1 : undefined);
141
+ for (const [filePath, fileTests] of fileGroups) {
142
+ if (signal?.aborted)
143
+ break;
144
+ if (failureLimit !== undefined && failedCount >= failureLimit)
145
+ break;
146
+ const testFileUrl = pathToFileURL(resolve(filePath)).href;
147
+ const testIds = fileTests.map((t) => t.meta.id);
148
+ const exportNames = {};
149
+ for (const t of fileTests)
150
+ exportNames[t.meta.id] = t.exportName;
151
+ yield { type: "file:start", filePath, testCount: fileTests.length };
152
+ const fileStart = Date.now();
153
+ for await (const event of executor.run(testFileUrl, "", {
154
+ vars: envVars,
155
+ secrets,
156
+ ...(Object.keys(sessionState).length > 0 && { session: sessionState }),
157
+ }, {
158
+ ...toSingleExecutionOptions(sharedConfig),
159
+ testIds,
160
+ exportNames,
161
+ ...(fileTests.some((t) => t.meta.parallel) && sharedConfig.concurrency > 1
162
+ ? { concurrency: sharedConfig.concurrency }
163
+ : {}),
164
+ })) {
165
+ if (event.type === "session:set") {
166
+ sessionState[event.key] = event.value;
167
+ }
168
+ if (event.type === "metric") {
169
+ metricCollector.add(event.name, event.value);
170
+ }
171
+ if (event.type === "status") {
172
+ if (event.status === "completed")
173
+ passedCount += 1;
174
+ else if (event.status === "skipped")
175
+ skippedCount += 1;
176
+ else
177
+ failedCount += 1;
178
+ }
179
+ yield { type: "file:event", filePath, event };
180
+ }
181
+ yield { type: "file:complete", filePath, duration: Date.now() - fileStart };
182
+ }
183
+ // ── 7. Session teardown ────────────────────────────────────────
184
+ if (sessionFile && sessionSetupSucceeded) {
185
+ yield { type: "session:teardown:start", sessionFile };
186
+ for await (const event of orchestrator.runSessionTeardown(sessionFile, { vars: envVars, secrets }, sessionState, toSingleExecutionOptions(sharedConfig))) {
187
+ yield { type: "session:teardown:event", event };
188
+ }
189
+ yield { type: "session:teardown:done" };
190
+ }
191
+ // ── 8. Done ────────────────────────────────────────────────────
192
+ yield { type: "run:complete", passedCount, failedCount, skippedCount };
193
+ }
194
+ finally {
195
+ // Drain executor.finalize() — zero-project scratch cleanup and any
196
+ // future executor-level finalizers. Safe to call even after session
197
+ // teardown already ran (executor.finalize() guards on _sessionSetupDone,
198
+ // which stays false when sessions are driven by RunOrchestrator rather
199
+ // than executor auto-session). Discard yielded events; this is pure
200
+ // cleanup, not part of the user-visible run.
201
+ try {
202
+ for await (const _ of executor.finalize()) {
203
+ // intentionally drain
204
+ }
205
+ }
206
+ catch {
207
+ // Finalize errors are non-fatal — surface silently to avoid masking
208
+ // the primary run outcome. Could be upgraded to a warning event
209
+ // later if needed.
210
+ }
211
+ }
212
+ }
213
+ }
214
+ //# sourceMappingURL=project-runner.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"project-runner.js","sourceRoot":"","sources":["../src/project-runner.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,137 @@
1
+ /**
2
+ * @module run-case
3
+ *
4
+ * Public programmatic wrapper for the runner-input-channel surface
5
+ * (attachment-model §8). Mirrors CLI `--input-json` / `--bootstrap-json` /
6
+ * `--force-standalone` and MCP `glubean_run_local_file`'s `inputJson` /
7
+ * `bootstrapInput` / `forceStandalone` parameters.
8
+ *
9
+ * Use cases:
10
+ * - Embedders / scripts that want to drive a single contract case with
11
+ * a specific input shape without going through the CLI.
12
+ * - Cookbook examples illustrating attachment-model §5.1 / §8 flows.
13
+ *
14
+ * Implementation strategy: serialize the per-test inputs into the same
15
+ * env vars the harness reads (`GLUBEAN_RUNNER_*`), then construct a
16
+ * `ProjectRunner` with a single test descriptor. Env vars are restored
17
+ * to their prior values in `finally`, so concurrent callers in the
18
+ * same process don't leak state.
19
+ */
20
+ import type { ProjectRunEvent } from "./project-runner.js";
21
+ import type { SharedRunConfig } from "./config.js";
22
+ /** Result of running a single case via {@link runCase}. */
23
+ export interface RunCaseResult {
24
+ /** Did the case pass? */
25
+ success: boolean;
26
+ /** Test id that ran. */
27
+ testId: string;
28
+ /** Project root resolved from the file path's ancestry. */
29
+ projectRoot: string;
30
+ /** All events emitted by `ProjectRunner` for this run, in order. */
31
+ events: ProjectRunEvent[];
32
+ /**
33
+ * If the run did not reach a per-test success/fail status (e.g.
34
+ * bootstrap or session setup failed), this carries the reason.
35
+ */
36
+ orchestrationError?: string;
37
+ }
38
+ /** Options for {@link runCase}. */
39
+ export interface RunCaseOptions {
40
+ /** Absolute (preferred) path to the test/contract file. */
41
+ filePath: string;
42
+ /** Specific testId to dispatch (e.g. `"orders.create.success"`). */
43
+ testId: string;
44
+ /** Display name for the test. Optional; falls back to `testId`. */
45
+ testName?: string;
46
+ /** Export name on the file. When omitted, `ProjectRunner` resolves it. */
47
+ exportName?: string;
48
+ /** Project root override. When omitted, `filePath`'s directory is used. */
49
+ rootDir?: string;
50
+ /** Shared run config (timeouts, schema inference, etc.). */
51
+ sharedConfig: SharedRunConfig;
52
+ /**
53
+ * Skip session setup/teardown. Defaults to `false` to match CLI / MCP
54
+ * / `ProjectRunner` defaults — a programmatic single-case run sees
55
+ * the same `session.ts` lifecycle a `glubean run --filter` would.
56
+ * Set to `true` only when the caller has a reason to bypass session
57
+ * (e.g. invoking from inside a test that already established its own
58
+ * session, or driving a fixture project with no `session.ts`).
59
+ */
60
+ noSession?: boolean;
61
+ /**
62
+ * Spike 3 attachment-model §8 — explicit case input. Validated
63
+ * against the case's `needs` schema; runs raw (overlay skipped).
64
+ */
65
+ input?: unknown;
66
+ /**
67
+ * Spike 3 attachment-model §8 — bootstrap params. Validated against
68
+ * the overlay's `params` schema and passed to `run(ctx, params)`.
69
+ */
70
+ bootstrapInput?: unknown;
71
+ /**
72
+ * §6.3 debug escape valve for `runnability.requireAttachment` on
73
+ * no-needs cases. Emits a runtime warning when triggered.
74
+ */
75
+ forceStandalone?: boolean;
76
+ /**
77
+ * Optional env map for `{{VAR}}` substitution inside `input` /
78
+ * `bootstrapInput` (§8). When omitted, the templating env is built
79
+ * from `{ ...vars, ...secrets, ...process.env }` matching CLI / MCP
80
+ * precedence (process.env wins, secrets win over vars).
81
+ */
82
+ templatingEnv?: Record<string, string | undefined>;
83
+ /**
84
+ * Project env-file basename to load (e.g. `".env"` or
85
+ * `".env.staging"`). Loads `<rootDir>/<envFile>` and
86
+ * `<rootDir>/<envFile>.secrets`. Both files are silently treated as
87
+ * empty when absent. Defaults to `".env"` to match CLI / MCP.
88
+ *
89
+ * Set to `null` to skip env-file loading entirely (useful for
90
+ * fixture-driven tests / scripts that don't want any project env).
91
+ */
92
+ envFile?: string | null;
93
+ /**
94
+ * Project vars to inject into `ProjectRunner` (and therefore into
95
+ * `ctx.vars` for the running case). When provided, MERGED ON TOP OF
96
+ * the env loaded from `envFile` (caller-supplied vars win over file
97
+ * vars). Use this to override or extend env values without touching
98
+ * the on-disk `.env`.
99
+ */
100
+ vars?: Record<string, string>;
101
+ /**
102
+ * Project secrets to inject into `ProjectRunner` (and therefore into
103
+ * `ctx.secrets`). Same merge semantics as `vars` — caller-supplied
104
+ * wins over `envFile` values.
105
+ */
106
+ secrets?: Record<string, string>;
107
+ }
108
+ /**
109
+ * Run a single contract case programmatically with optional explicit
110
+ * input or bootstrap params. Mirrors the runtime resolution algorithm
111
+ * (§5.1) used by the CLI and MCP surfaces.
112
+ *
113
+ * @example Run a needs-case with explicit input (overlay skipped)
114
+ * ```ts
115
+ * import { runCase } from "@glubean/runner";
116
+ *
117
+ * const result = await runCase({
118
+ * filePath: "/abs/path/to/users.contract.ts",
119
+ * testId: "users.get.ok",
120
+ * sharedConfig: { ...default... },
121
+ * input: { token: "tk-1", userId: "u-42" },
122
+ * });
123
+ * console.log(result.success);
124
+ * ```
125
+ *
126
+ * @example Run with bootstrap params
127
+ * ```ts
128
+ * const result = await runCase({
129
+ * filePath: "/abs/.../orders.contract.ts",
130
+ * testId: "orders.list.seeded",
131
+ * sharedConfig: { ...default... },
132
+ * bootstrapInput: { projectId: "p_42" },
133
+ * });
134
+ * ```
135
+ */
136
+ export declare function runCase(opts: RunCaseOptions): Promise<RunCaseResult>;
137
+ //# sourceMappingURL=run-case.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"run-case.d.ts","sourceRoot":"","sources":["../src/run-case.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAKH,OAAO,KAAK,EACV,eAAe,EAEhB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AA+CnD,2DAA2D;AAC3D,MAAM,WAAW,aAAa;IAC5B,yBAAyB;IACzB,OAAO,EAAE,OAAO,CAAC;IACjB,wBAAwB;IACxB,MAAM,EAAE,MAAM,CAAC;IACf,2DAA2D;IAC3D,WAAW,EAAE,MAAM,CAAC;IACpB,oEAAoE;IACpE,MAAM,EAAE,eAAe,EAAE,CAAC;IAC1B;;;OAGG;IACH,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC7B;AAED,mCAAmC;AACnC,MAAM,WAAW,cAAc;IAC7B,2DAA2D;IAC3D,QAAQ,EAAE,MAAM,CAAC;IACjB,oEAAoE;IACpE,MAAM,EAAE,MAAM,CAAC;IACf,mEAAmE;IACnE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,0EAA0E;IAC1E,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,2EAA2E;IAC3E,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,4DAA4D;IAC5D,YAAY,EAAE,eAAe,CAAC;IAC9B;;;;;;;OAOG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;IAEpB;;;OAGG;IACH,KAAK,CAAC,EAAE,OAAO,CAAC;IAEhB;;;OAGG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC;IAEzB;;;OAGG;IACH,eAAe,CAAC,EAAE,OAAO,CAAC;IAE1B;;;;;OAKG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC;IAEnD;;;;;;;;OAQG;IACH,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAExB;;;;;;OAMG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAE9B;;;;OAIG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAClC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,wBAAsB,OAAO,CAAC,IAAI,EAAE,cAAc,GAAG,OAAO,CAAC,aAAa,CAAC,CAmJ1E"}
@@ -0,0 +1,233 @@
1
+ /**
2
+ * @module run-case
3
+ *
4
+ * Public programmatic wrapper for the runner-input-channel surface
5
+ * (attachment-model §8). Mirrors CLI `--input-json` / `--bootstrap-json` /
6
+ * `--force-standalone` and MCP `glubean_run_local_file`'s `inputJson` /
7
+ * `bootstrapInput` / `forceStandalone` parameters.
8
+ *
9
+ * Use cases:
10
+ * - Embedders / scripts that want to drive a single contract case with
11
+ * a specific input shape without going through the CLI.
12
+ * - Cookbook examples illustrating attachment-model §5.1 / §8 flows.
13
+ *
14
+ * Implementation strategy: serialize the per-test inputs into the same
15
+ * env vars the harness reads (`GLUBEAN_RUNNER_*`), then construct a
16
+ * `ProjectRunner` with a single test descriptor. Env vars are restored
17
+ * to their prior values in `finally`, so concurrent callers in the
18
+ * same process don't leak state.
19
+ */
20
+ import { dirname, resolve } from "node:path";
21
+ import { existsSync, readFileSync } from "node:fs";
22
+ import { ProjectRunner } from "./project-runner.js";
23
+ import { applyEnvTemplating } from "./runner-input-templating.js";
24
+ import { loadProjectEnv } from "./env.js";
25
+ /**
26
+ * Walk up from `filePath` to find the **Glubean project root** — the
27
+ * nearest ancestor containing a `package.json` that declares
28
+ * `@glubean/sdk` (in deps or devDeps) OR carries a `glubean` config
29
+ * field. Mirrors `findProjectConfig` in CLI run.ts.
30
+ *
31
+ * The "Glubean project" filter matters in monorepos / nested-package
32
+ * setups: a contract under `apps/foo/tests/x.contract.ts` should resolve
33
+ * to the workspace root (where `glubean.setup.ts` / root `.env` /
34
+ * top-level overlay registrations live), NOT to `apps/foo/` if that
35
+ * subpackage's `package.json` doesn't depend on `@glubean/sdk`. Stopping
36
+ * at the first `package.json` would silently misroute everything.
37
+ *
38
+ * Fallback when no Glubean project is found anywhere up the tree:
39
+ * `dirname(filePath)`. This keeps ad-hoc / scratch test runs working
40
+ * (matches CLI's "scratch mode" — no glubean project found).
41
+ */
42
+ function findProjectRoot(filePath) {
43
+ let dir = dirname(filePath);
44
+ while (dir !== dirname(dir)) {
45
+ const pkgPath = resolve(dir, "package.json");
46
+ if (existsSync(pkgPath)) {
47
+ try {
48
+ const content = JSON.parse(readFileSync(pkgPath, "utf-8"));
49
+ const deps = { ...content.dependencies, ...content.devDependencies };
50
+ if ("@glubean/sdk" in deps || content.glubean !== undefined) {
51
+ return dir;
52
+ }
53
+ // Non-Glubean package.json — keep walking; might be a nested
54
+ // tooling package whose Glubean root is higher up.
55
+ }
56
+ catch {
57
+ // Parse error — skip this package.json and keep walking.
58
+ }
59
+ }
60
+ dir = dirname(dir);
61
+ }
62
+ return dirname(filePath);
63
+ }
64
+ /**
65
+ * Run a single contract case programmatically with optional explicit
66
+ * input or bootstrap params. Mirrors the runtime resolution algorithm
67
+ * (§5.1) used by the CLI and MCP surfaces.
68
+ *
69
+ * @example Run a needs-case with explicit input (overlay skipped)
70
+ * ```ts
71
+ * import { runCase } from "@glubean/runner";
72
+ *
73
+ * const result = await runCase({
74
+ * filePath: "/abs/path/to/users.contract.ts",
75
+ * testId: "users.get.ok",
76
+ * sharedConfig: { ...default... },
77
+ * input: { token: "tk-1", userId: "u-42" },
78
+ * });
79
+ * console.log(result.success);
80
+ * ```
81
+ *
82
+ * @example Run with bootstrap params
83
+ * ```ts
84
+ * const result = await runCase({
85
+ * filePath: "/abs/.../orders.contract.ts",
86
+ * testId: "orders.list.seeded",
87
+ * sharedConfig: { ...default... },
88
+ * bootstrapInput: { projectId: "p_42" },
89
+ * });
90
+ * ```
91
+ */
92
+ export async function runCase(opts) {
93
+ // §5.1 invariant: explicit input always wins; overlay never invoked.
94
+ // The two channels are mutually exclusive — surface boundary enforces
95
+ // it so the dispatcher never silently drops the bootstrap-params side.
96
+ if (opts.input !== undefined && opts.bootstrapInput !== undefined) {
97
+ throw new Error(`runCase: \`input\` and \`bootstrapInput\` are mutually exclusive. ` +
98
+ `Per attachment-model §5.1: explicit input bypasses the overlay, ` +
99
+ `so bootstrap params would be ignored. Pick one channel per call.`);
100
+ }
101
+ const filePath = resolve(opts.filePath);
102
+ // §7.4 / glubean.setup.ts location: project root must be the directory
103
+ // containing `package.json` and (typically) `glubean.setup.ts` — NOT
104
+ // just the directory of the contract file. A file under `tests/` or
105
+ // `contracts/` would otherwise miss the project's plugin bootstrap and
106
+ // env loading. Caller can pass an explicit `rootDir` to override; if
107
+ // omitted, walk up from the file looking for `package.json`.
108
+ const rootDir = opts.rootDir ?? findProjectRoot(filePath);
109
+ // Load project env (matches CLI / MCP behavior). `envFile: null` skips
110
+ // the load; otherwise default basename is `.env`. Caller-supplied
111
+ // `vars` / `secrets` merge on top (caller wins over file).
112
+ //
113
+ // No try/catch around `loadProjectEnv`: it treats missing `.env` /
114
+ // `.env.secrets` files as empty internally and only throws on real
115
+ // load failures (parse errors, IO errors). Letting those bubble up
116
+ // matches CLI / MCP behavior — silently empty-env-then-run is a
117
+ // worse failure mode than a clear parse error to the caller.
118
+ let loadedVars = {};
119
+ let loadedSecrets = {};
120
+ if (opts.envFile !== null) {
121
+ const envFileName = opts.envFile ?? ".env";
122
+ const loaded = await loadProjectEnv(rootDir, envFileName);
123
+ loadedVars = loaded.vars;
124
+ loadedSecrets = loaded.secrets;
125
+ }
126
+ const effectiveVars = { ...loadedVars, ...(opts.vars ?? {}) };
127
+ const effectiveSecrets = { ...loadedSecrets, ...(opts.secrets ?? {}) };
128
+ // Capture & set env vars BEFORE constructing ProjectRunner — the
129
+ // executor inherits parent env when spawning the harness subprocess.
130
+ const savedExplicit = process.env["GLUBEAN_RUNNER_EXPLICIT_INPUT_MAP"];
131
+ const savedBootstrap = process.env["GLUBEAN_RUNNER_BOOTSTRAP_INPUT_MAP"];
132
+ const savedForce = process.env["GLUBEAN_RUNNER_FORCE_STANDALONE_IDS"];
133
+ // §8 templating env — caller override, else built from
134
+ // `{ ...vars, ...secrets, ...process.env }` matching CLI / MCP
135
+ // precedence (process.env wins, secrets win over vars). Substitution
136
+ // happens before env-var serialization, so the harness sees ready-to-
137
+ // validate JSON.
138
+ const templatingEnv = opts.templatingEnv ?? {
139
+ ...effectiveVars,
140
+ ...effectiveSecrets,
141
+ ...process.env,
142
+ };
143
+ if (opts.input !== undefined) {
144
+ const templated = applyEnvTemplating(opts.input, templatingEnv);
145
+ process.env["GLUBEAN_RUNNER_EXPLICIT_INPUT_MAP"] = JSON.stringify({
146
+ [opts.testId]: templated,
147
+ });
148
+ }
149
+ if (opts.bootstrapInput !== undefined) {
150
+ const templated = applyEnvTemplating(opts.bootstrapInput, templatingEnv);
151
+ process.env["GLUBEAN_RUNNER_BOOTSTRAP_INPUT_MAP"] = JSON.stringify({
152
+ [opts.testId]: templated,
153
+ });
154
+ }
155
+ if (opts.forceStandalone === true) {
156
+ process.env["GLUBEAN_RUNNER_FORCE_STANDALONE_IDS"] = JSON.stringify([
157
+ opts.testId,
158
+ ]);
159
+ }
160
+ const restoreEnv = () => {
161
+ if (savedExplicit === undefined) {
162
+ delete process.env["GLUBEAN_RUNNER_EXPLICIT_INPUT_MAP"];
163
+ }
164
+ else {
165
+ process.env["GLUBEAN_RUNNER_EXPLICIT_INPUT_MAP"] = savedExplicit;
166
+ }
167
+ if (savedBootstrap === undefined) {
168
+ delete process.env["GLUBEAN_RUNNER_BOOTSTRAP_INPUT_MAP"];
169
+ }
170
+ else {
171
+ process.env["GLUBEAN_RUNNER_BOOTSTRAP_INPUT_MAP"] = savedBootstrap;
172
+ }
173
+ if (savedForce === undefined) {
174
+ delete process.env["GLUBEAN_RUNNER_FORCE_STANDALONE_IDS"];
175
+ }
176
+ else {
177
+ process.env["GLUBEAN_RUNNER_FORCE_STANDALONE_IDS"] = savedForce;
178
+ }
179
+ };
180
+ const test = {
181
+ filePath,
182
+ exportName: opts.exportName ?? "",
183
+ meta: {
184
+ id: opts.testId,
185
+ name: opts.testName ?? opts.testId,
186
+ },
187
+ };
188
+ const runner = new ProjectRunner({
189
+ rootDir,
190
+ sharedConfig: opts.sharedConfig,
191
+ vars: effectiveVars,
192
+ secrets: effectiveSecrets,
193
+ tests: [test],
194
+ noSession: opts.noSession ?? false,
195
+ });
196
+ const events = [];
197
+ let success = false;
198
+ let orchestrationError;
199
+ try {
200
+ for await (const evt of runner.run()) {
201
+ events.push(evt);
202
+ if (evt.type === "bootstrap:failed") {
203
+ orchestrationError = `Bootstrap failed: ${evt.error.message}`;
204
+ }
205
+ else if (evt.type === "session:setup:failed") {
206
+ orchestrationError = `Session setup failed${evt.error ? `: ${evt.error}` : ""}`;
207
+ }
208
+ else if (evt.type === "run:failed") {
209
+ if (!orchestrationError) {
210
+ orchestrationError = `Run failed (${evt.reason})${evt.error ? `: ${evt.error}` : ""}`;
211
+ }
212
+ }
213
+ else if (evt.type === "file:event") {
214
+ // ExecutionEvent's `status` event encodes the final per-test
215
+ // outcome: "completed" = pass, "failed" = fail, "skipped" = neither.
216
+ if (evt.event.type === "status" && evt.event.id === opts.testId) {
217
+ success = evt.event.status === "completed";
218
+ }
219
+ }
220
+ }
221
+ }
222
+ finally {
223
+ restoreEnv();
224
+ }
225
+ return {
226
+ success,
227
+ testId: opts.testId,
228
+ projectRoot: rootDir,
229
+ events,
230
+ ...(orchestrationError !== undefined ? { orchestrationError } : {}),
231
+ };
232
+ }
233
+ //# sourceMappingURL=run-case.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"run-case.js","sourceRoot":"","sources":["../src/run-case.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,33 @@
1
+ /**
2
+ * @module runner-input-templating
3
+ *
4
+ * `{{VAR}}` substitution for runner-supplied inputs (attachment-model
5
+ * §8). The runner interpolates string scalars before schema validation;
6
+ * `env` is never read inside bootstrap (§8).
7
+ *
8
+ * Patterns supported (per §8 spec — kept minimal in v0):
9
+ * - `{{VAR}}` — substitute the entire string with the env value
10
+ * (preserves type only when string; numeric envs stay strings)
11
+ * - `prefix-{{VAR}}-suffix` — string interpolation; result is a string
12
+ * - `{{VAR1}}{{VAR2}}` — multiple substitutions in one string
13
+ *
14
+ * Errors:
15
+ * - Missing required var → `Error("Templating: missing env var \"VAR\"")`
16
+ * - Whitespace inside braces is stripped; `{{ VAR }}` works.
17
+ *
18
+ * Scope:
19
+ * - Recursive across plain objects and arrays.
20
+ * - Strings get substitution; numbers / booleans / null pass through.
21
+ * - Functions / class instances / Maps / Sets / etc. are unsupported
22
+ * for v0 (the input is JSON-shaped by the time it reaches us, so
23
+ * this is fine).
24
+ */
25
+ /**
26
+ * Apply `{{VAR}}` substitution to all string scalars inside `value`,
27
+ * resolving variables from `env`. Returns a new value (does not mutate
28
+ * the input).
29
+ *
30
+ * @throws when a referenced var is missing in `env`.
31
+ */
32
+ export declare function applyEnvTemplating(value: unknown, env: Record<string, string | undefined>): unknown;
33
+ //# sourceMappingURL=runner-input-templating.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runner-input-templating.d.ts","sourceRoot":"","sources":["../src/runner-input-templating.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAIH;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAChC,KAAK,EAAE,OAAO,EACd,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,GACtC,OAAO,CAeT"}