@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.
- package/dist/bootstrap.d.ts +84 -0
- package/dist/bootstrap.d.ts.map +1 -0
- package/dist/bootstrap.js +169 -0
- package/dist/bootstrap.js.map +1 -0
- package/dist/env.d.ts +103 -0
- package/dist/env.d.ts.map +1 -0
- package/dist/env.js +134 -0
- package/dist/env.js.map +1 -0
- package/dist/harness.js +81 -1
- package/dist/harness.js.map +1 -1
- package/dist/index.d.ts +85 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +90 -5
- package/dist/index.js.map +1 -1
- package/dist/project-runner.d.ts +186 -0
- package/dist/project-runner.d.ts.map +1 -0
- package/dist/project-runner.js +214 -0
- package/dist/project-runner.js.map +1 -0
- package/dist/run-case.d.ts +137 -0
- package/dist/run-case.d.ts.map +1 -0
- package/dist/run-case.js +233 -0
- package/dist/run-case.js.map +1 -0
- package/dist/runner-input-templating.d.ts +33 -0
- package/dist/runner-input-templating.d.ts.map +1 -0
- package/dist/runner-input-templating.js +65 -0
- package/dist/runner-input-templating.js.map +1 -0
- package/package.json +4 -2
|
@@ -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"}
|
package/dist/run-case.js
ADDED
|
@@ -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"}
|