@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,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module bootstrap
|
|
3
|
+
*
|
|
4
|
+
* Project-level plugin bootstrap. Any SDK consumer that depends on plugin
|
|
5
|
+
* registration state (matchers, protocol adapters) must call `bootstrap()`
|
|
6
|
+
* at the start of its process — before scanning, running tests, handling
|
|
7
|
+
* MCP requests, or emitting metadata.
|
|
8
|
+
*
|
|
9
|
+
* The bootstrap contract is the single point where "which plugins does this
|
|
10
|
+
* project use" gets resolved. It locates a `glubean.setup.(ts|js|mjs)` file
|
|
11
|
+
* via walk-up from the given start directory and dynamically imports it.
|
|
12
|
+
* That file is expected to call `installPlugin(...)` at module top level.
|
|
13
|
+
*
|
|
14
|
+
* Idempotent by design — calling `bootstrap()` multiple times (across
|
|
15
|
+
* entry points, across sub-scans within one process) is safe and cheap.
|
|
16
|
+
*
|
|
17
|
+
* **TypeScript setup files**: Loading a `.ts` setup file requires the
|
|
18
|
+
* calling process to have a TypeScript module resolver active (tsx,
|
|
19
|
+
* ts-node, etc.). All first-party Glubean entry points (runner, scanner,
|
|
20
|
+
* CLI, MCP server, VSCode extension) run under tsx already, so this is
|
|
21
|
+
* transparent. Third-party embeds that cannot load `.ts` should ship a
|
|
22
|
+
* `glubean.setup.js` or `glubean.setup.mjs` instead.
|
|
23
|
+
*
|
|
24
|
+
* @see {@link installPlugin} in `./install-plugin.js`
|
|
25
|
+
*/
|
|
26
|
+
/**
|
|
27
|
+
* Walk up from `startDir` searching for a Glubean setup file. Returns the
|
|
28
|
+
* absolute path of the first match found, or `undefined` if no setup file
|
|
29
|
+
* exists anywhere between `startDir` and the filesystem root (or `stopDir`).
|
|
30
|
+
*
|
|
31
|
+
* Setup files checked per directory, in priority order:
|
|
32
|
+
* `glubean.setup.ts` → `glubean.setup.js` → `glubean.setup.mjs`.
|
|
33
|
+
*
|
|
34
|
+
* @param startDir Directory to begin searching from (absolute or relative to cwd).
|
|
35
|
+
* @param stopDir Optional upper bound for the walk (defaults to filesystem root).
|
|
36
|
+
*/
|
|
37
|
+
export declare function discoverSetupFile(startDir: string, stopDir?: string): string | undefined;
|
|
38
|
+
/**
|
|
39
|
+
* Locate and import the project's `glubean.setup` file, triggering the
|
|
40
|
+
* `installPlugin(...)` calls inside it.
|
|
41
|
+
*
|
|
42
|
+
* This function is the **bootstrap contract** for SDK consumers: every
|
|
43
|
+
* entry point that observes plugin-registered state (scanner for
|
|
44
|
+
* `.contract.ts` extraction, runner for test execution, MCP server for
|
|
45
|
+
* metadata, VSCode for scan) **must** await `bootstrap()` before doing
|
|
46
|
+
* its own work. Failing to do so causes a silent split where scan-time
|
|
47
|
+
* sees an empty adapter registry while runtime sees the full one.
|
|
48
|
+
*
|
|
49
|
+
* **Behavior:**
|
|
50
|
+
* - No setup file found → no-op (projects without plugins are fine, silent).
|
|
51
|
+
* - Setup file found and not yet imported this process → import it and
|
|
52
|
+
* await the returned promise (the setup file may call `await installPlugin(...)`).
|
|
53
|
+
* - Setup file found and already imported successfully → no-op (idempotent).
|
|
54
|
+
* - Setup file found but `import()` throws → the error is recorded **and**
|
|
55
|
+
* re-thrown. Every subsequent `bootstrap()` call that resolves to the same
|
|
56
|
+
* file re-throws the remembered error rather than silently succeeding.
|
|
57
|
+
* This is consistent with `installPlugin`'s "setup failure is
|
|
58
|
+
* process-unrecoverable" contract: later scanner / runner / MCP callers
|
|
59
|
+
* MUST see the failure, not a false success.
|
|
60
|
+
*
|
|
61
|
+
* @param startDir Starting directory for the walk-up search. Typically the
|
|
62
|
+
* project root or the process cwd. Caller decides — the SDK
|
|
63
|
+
* does not assume `process.cwd()`.
|
|
64
|
+
* @param stopDir Optional upper bound for the walk (defaults to filesystem root).
|
|
65
|
+
*
|
|
66
|
+
* @example
|
|
67
|
+
* ```ts
|
|
68
|
+
* // runner startup
|
|
69
|
+
* import { bootstrap } from "@glubean/sdk";
|
|
70
|
+
* await bootstrap(projectRoot);
|
|
71
|
+
* // ... now safe to import test files / .contract.ts files
|
|
72
|
+
* ```
|
|
73
|
+
*/
|
|
74
|
+
export declare function bootstrap(startDir: string, stopDir?: string): Promise<void>;
|
|
75
|
+
/**
|
|
76
|
+
* Test-only: clear the "already bootstrapped" cache so a subsequent
|
|
77
|
+
* `bootstrap()` call will re-import the setup file. Does **not** reset any
|
|
78
|
+
* plugin state on the globals — combine with
|
|
79
|
+
* `__resetInstalledPluginsForTesting()` if you need a full reset.
|
|
80
|
+
*
|
|
81
|
+
* @internal
|
|
82
|
+
*/
|
|
83
|
+
export declare function __resetBootstrapForTesting(): void;
|
|
84
|
+
//# sourceMappingURL=bootstrap.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bootstrap.d.ts","sourceRoot":"","sources":["../src/bootstrap.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AA8CH;;;;;;;;;;GAUG;AACH,wBAAgB,iBAAiB,CAC/B,QAAQ,EAAE,MAAM,EAChB,OAAO,CAAC,EAAE,MAAM,GACf,MAAM,GAAG,SAAS,CAiCpB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AACH,wBAAsB,SAAS,CAC7B,QAAQ,EAAE,MAAM,EAChB,OAAO,CAAC,EAAE,MAAM,GACf,OAAO,CAAC,IAAI,CAAC,CAyBf;AAED;;;;;;;GAOG;AACH,wBAAgB,0BAA0B,IAAI,IAAI,CAEjD"}
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module bootstrap
|
|
3
|
+
*
|
|
4
|
+
* Project-level plugin bootstrap. Any SDK consumer that depends on plugin
|
|
5
|
+
* registration state (matchers, protocol adapters) must call `bootstrap()`
|
|
6
|
+
* at the start of its process — before scanning, running tests, handling
|
|
7
|
+
* MCP requests, or emitting metadata.
|
|
8
|
+
*
|
|
9
|
+
* The bootstrap contract is the single point where "which plugins does this
|
|
10
|
+
* project use" gets resolved. It locates a `glubean.setup.(ts|js|mjs)` file
|
|
11
|
+
* via walk-up from the given start directory and dynamically imports it.
|
|
12
|
+
* That file is expected to call `installPlugin(...)` at module top level.
|
|
13
|
+
*
|
|
14
|
+
* Idempotent by design — calling `bootstrap()` multiple times (across
|
|
15
|
+
* entry points, across sub-scans within one process) is safe and cheap.
|
|
16
|
+
*
|
|
17
|
+
* **TypeScript setup files**: Loading a `.ts` setup file requires the
|
|
18
|
+
* calling process to have a TypeScript module resolver active (tsx,
|
|
19
|
+
* ts-node, etc.). All first-party Glubean entry points (runner, scanner,
|
|
20
|
+
* CLI, MCP server, VSCode extension) run under tsx already, so this is
|
|
21
|
+
* transparent. Third-party embeds that cannot load `.ts` should ship a
|
|
22
|
+
* `glubean.setup.js` or `glubean.setup.mjs` instead.
|
|
23
|
+
*
|
|
24
|
+
* @see {@link installPlugin} in `./install-plugin.js`
|
|
25
|
+
*/
|
|
26
|
+
import { dirname, isAbsolute, parse, relative, resolve } from "node:path";
|
|
27
|
+
import { existsSync } from "node:fs";
|
|
28
|
+
import { pathToFileURL } from "node:url";
|
|
29
|
+
/**
|
|
30
|
+
* Setup file names checked in priority order during walk-up. A directory
|
|
31
|
+
* containing any of these files is the project root for plugin purposes.
|
|
32
|
+
* The first hit in each directory wins.
|
|
33
|
+
*/
|
|
34
|
+
const SETUP_FILE_NAMES = [
|
|
35
|
+
"glubean.setup.ts",
|
|
36
|
+
"glubean.setup.js",
|
|
37
|
+
"glubean.setup.mjs",
|
|
38
|
+
];
|
|
39
|
+
const loadState = new Map();
|
|
40
|
+
/**
|
|
41
|
+
* Return true iff `descendant` is the same as or nested inside `ancestor`.
|
|
42
|
+
* Both inputs must be absolute, normalized paths.
|
|
43
|
+
* @internal
|
|
44
|
+
*/
|
|
45
|
+
function isAncestorOrSame(ancestor, descendant) {
|
|
46
|
+
const rel = relative(ancestor, descendant);
|
|
47
|
+
// Empty string → equal. Relative path that is not ".." prefixed and not
|
|
48
|
+
// absolute → descendant is inside ancestor. Anything else → outside.
|
|
49
|
+
return rel === "" || (!rel.startsWith("..") && !isAbsolute(rel));
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Walk up from `startDir` searching for a Glubean setup file. Returns the
|
|
53
|
+
* absolute path of the first match found, or `undefined` if no setup file
|
|
54
|
+
* exists anywhere between `startDir` and the filesystem root (or `stopDir`).
|
|
55
|
+
*
|
|
56
|
+
* Setup files checked per directory, in priority order:
|
|
57
|
+
* `glubean.setup.ts` → `glubean.setup.js` → `glubean.setup.mjs`.
|
|
58
|
+
*
|
|
59
|
+
* @param startDir Directory to begin searching from (absolute or relative to cwd).
|
|
60
|
+
* @param stopDir Optional upper bound for the walk (defaults to filesystem root).
|
|
61
|
+
*/
|
|
62
|
+
export function discoverSetupFile(startDir, stopDir) {
|
|
63
|
+
const startAbs = resolve(startDir);
|
|
64
|
+
let root;
|
|
65
|
+
if (stopDir !== undefined) {
|
|
66
|
+
const stopAbs = resolve(stopDir);
|
|
67
|
+
// Reject a stopDir that is not an ancestor of (or equal to) startDir.
|
|
68
|
+
// Silently walking past a non-ancestor stopDir could pick up an unrelated
|
|
69
|
+
// setup file above it — reviewer-flagged bug. Fail loud instead.
|
|
70
|
+
if (!isAncestorOrSame(stopAbs, startAbs)) {
|
|
71
|
+
throw new Error(`discoverSetupFile: stopDir "${stopAbs}" is not an ancestor of startDir "${startAbs}". ` +
|
|
72
|
+
"stopDir must be equal to or above startDir in the directory tree.");
|
|
73
|
+
}
|
|
74
|
+
root = stopAbs;
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
root = parse(startAbs).root;
|
|
78
|
+
}
|
|
79
|
+
let dir = startAbs;
|
|
80
|
+
while (true) {
|
|
81
|
+
for (const name of SETUP_FILE_NAMES) {
|
|
82
|
+
const candidate = resolve(dir, name);
|
|
83
|
+
if (existsSync(candidate)) {
|
|
84
|
+
return candidate;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
if (dir === root)
|
|
88
|
+
break;
|
|
89
|
+
const parent = dirname(dir);
|
|
90
|
+
if (parent === dir)
|
|
91
|
+
break;
|
|
92
|
+
dir = parent;
|
|
93
|
+
}
|
|
94
|
+
return undefined;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Locate and import the project's `glubean.setup` file, triggering the
|
|
98
|
+
* `installPlugin(...)` calls inside it.
|
|
99
|
+
*
|
|
100
|
+
* This function is the **bootstrap contract** for SDK consumers: every
|
|
101
|
+
* entry point that observes plugin-registered state (scanner for
|
|
102
|
+
* `.contract.ts` extraction, runner for test execution, MCP server for
|
|
103
|
+
* metadata, VSCode for scan) **must** await `bootstrap()` before doing
|
|
104
|
+
* its own work. Failing to do so causes a silent split where scan-time
|
|
105
|
+
* sees an empty adapter registry while runtime sees the full one.
|
|
106
|
+
*
|
|
107
|
+
* **Behavior:**
|
|
108
|
+
* - No setup file found → no-op (projects without plugins are fine, silent).
|
|
109
|
+
* - Setup file found and not yet imported this process → import it and
|
|
110
|
+
* await the returned promise (the setup file may call `await installPlugin(...)`).
|
|
111
|
+
* - Setup file found and already imported successfully → no-op (idempotent).
|
|
112
|
+
* - Setup file found but `import()` throws → the error is recorded **and**
|
|
113
|
+
* re-thrown. Every subsequent `bootstrap()` call that resolves to the same
|
|
114
|
+
* file re-throws the remembered error rather than silently succeeding.
|
|
115
|
+
* This is consistent with `installPlugin`'s "setup failure is
|
|
116
|
+
* process-unrecoverable" contract: later scanner / runner / MCP callers
|
|
117
|
+
* MUST see the failure, not a false success.
|
|
118
|
+
*
|
|
119
|
+
* @param startDir Starting directory for the walk-up search. Typically the
|
|
120
|
+
* project root or the process cwd. Caller decides — the SDK
|
|
121
|
+
* does not assume `process.cwd()`.
|
|
122
|
+
* @param stopDir Optional upper bound for the walk (defaults to filesystem root).
|
|
123
|
+
*
|
|
124
|
+
* @example
|
|
125
|
+
* ```ts
|
|
126
|
+
* // runner startup
|
|
127
|
+
* import { bootstrap } from "@glubean/sdk";
|
|
128
|
+
* await bootstrap(projectRoot);
|
|
129
|
+
* // ... now safe to import test files / .contract.ts files
|
|
130
|
+
* ```
|
|
131
|
+
*/
|
|
132
|
+
export async function bootstrap(startDir, stopDir) {
|
|
133
|
+
const setupFile = discoverSetupFile(startDir, stopDir);
|
|
134
|
+
if (!setupFile)
|
|
135
|
+
return;
|
|
136
|
+
const state = loadState.get(setupFile);
|
|
137
|
+
if (state?.status === "ok") {
|
|
138
|
+
// Fast path: already loaded successfully.
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
if (state?.status === "failed") {
|
|
142
|
+
// Previous attempt threw. Re-throw the remembered error so downstream
|
|
143
|
+
// callers (scanner / runner / MCP) don't silently proceed with a
|
|
144
|
+
// half-initialized plugin registry.
|
|
145
|
+
throw state.error;
|
|
146
|
+
}
|
|
147
|
+
const url = pathToFileURL(setupFile).href;
|
|
148
|
+
try {
|
|
149
|
+
await import(url);
|
|
150
|
+
loadState.set(setupFile, { status: "ok" });
|
|
151
|
+
}
|
|
152
|
+
catch (err) {
|
|
153
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
154
|
+
loadState.set(setupFile, { status: "failed", error });
|
|
155
|
+
throw error;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Test-only: clear the "already bootstrapped" cache so a subsequent
|
|
160
|
+
* `bootstrap()` call will re-import the setup file. Does **not** reset any
|
|
161
|
+
* plugin state on the globals — combine with
|
|
162
|
+
* `__resetInstalledPluginsForTesting()` if you need a full reset.
|
|
163
|
+
*
|
|
164
|
+
* @internal
|
|
165
|
+
*/
|
|
166
|
+
export function __resetBootstrapForTesting() {
|
|
167
|
+
loadState.clear();
|
|
168
|
+
}
|
|
169
|
+
//# sourceMappingURL=bootstrap.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bootstrap.js","sourceRoot":"","sources":["../src/bootstrap.ts"],"names":[],"mappings":"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"}
|
package/dist/env.d.ts
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module env
|
|
3
|
+
*
|
|
4
|
+
* Canonical project-env loader for all Glubean entry points (CLI `run`,
|
|
5
|
+
* MCP tool handlers, VSCode extension, any future consumer).
|
|
6
|
+
*
|
|
7
|
+
* Historical background: CLI and MCP used to each have their own env loader
|
|
8
|
+
* (CLI via `loadEnvFile` without expansion, MCP via a handwritten
|
|
9
|
+
* `parseEnvContent`). Both dropped `${NAME}` expansion from the production
|
|
10
|
+
* path despite `expandVars` being implemented in CLI's shared lib — a silent
|
|
11
|
+
* regression from the original design. This module is the single place all
|
|
12
|
+
* entry points load env files from now on, with full expansion semantics.
|
|
13
|
+
*
|
|
14
|
+
* This lives in `@glubean/runner` because it's tool-level runtime
|
|
15
|
+
* infrastructure (same category as `bootstrap()`), not design-time SDK API.
|
|
16
|
+
*/
|
|
17
|
+
/**
|
|
18
|
+
* Load a single `.env`-style file and return its parsed key-value pairs.
|
|
19
|
+
*
|
|
20
|
+
* - File missing (ENOENT) → returns `{}` silently (consumers decide whether
|
|
21
|
+
* missing is an error)
|
|
22
|
+
* - File unreadable (other IO error) → returns `{}` with a warning to stderr
|
|
23
|
+
* - Content is parsed with the standard `dotenv` package (no expansion)
|
|
24
|
+
*
|
|
25
|
+
* This is the low-level primitive. Most callers want
|
|
26
|
+
* {@link loadProjectEnv} instead.
|
|
27
|
+
*/
|
|
28
|
+
export declare function loadEnvFile(envPath: string): Promise<Record<string, string>>;
|
|
29
|
+
/**
|
|
30
|
+
* Expand `${NAME}` references in env values.
|
|
31
|
+
*
|
|
32
|
+
* Lookup order per reference:
|
|
33
|
+
* 1. Already-resolved values from this same expansion pass (supports
|
|
34
|
+
* forward references — keys defined earlier in insertion order).
|
|
35
|
+
* 2. `process.env[NAME]` (host environment variables).
|
|
36
|
+
* 3. Empty string fallback.
|
|
37
|
+
*
|
|
38
|
+
* Iteration is insertion order. This means:
|
|
39
|
+
* - Within a single file, a later key can reference earlier keys.
|
|
40
|
+
* - When called on a merged `{ ...vars, ...secrets }` object, secrets-only
|
|
41
|
+
* keys can reference any key from `vars` (because vars keys are inserted
|
|
42
|
+
* first). Vars keys referencing secrets-only keys **will not resolve**
|
|
43
|
+
* in a single pass — they'd need a multi-pass resolver.
|
|
44
|
+
*
|
|
45
|
+
* The multi-pass limitation is accepted — callers who need full
|
|
46
|
+
* topological expansion should use the SDK's `{{NAME}}` template at
|
|
47
|
+
* runtime via `resolveTemplate`, which resolves lazily in test execution
|
|
48
|
+
* context and can pull from vars / secrets / session dynamically.
|
|
49
|
+
*/
|
|
50
|
+
export declare function expandVars(vars: Record<string, string>): Record<string, string>;
|
|
51
|
+
/**
|
|
52
|
+
* Result of loading a project's env + secrets, with `${NAME}` references
|
|
53
|
+
* fully expanded.
|
|
54
|
+
*
|
|
55
|
+
* `vars` and `secrets` are kept as separate objects (never merged) so
|
|
56
|
+
* downstream layers can apply redaction / masking to secrets without
|
|
57
|
+
* affecting vars.
|
|
58
|
+
*
|
|
59
|
+
* On key collision between `.env` and `.env.secrets`, the secret wins and
|
|
60
|
+
* the key appears **only** in `secrets`, not in `vars` (no duplication).
|
|
61
|
+
*/
|
|
62
|
+
export interface ProjectEnv {
|
|
63
|
+
vars: Record<string, string>;
|
|
64
|
+
secrets: Record<string, string>;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Load a project's `.env` + `.env.secrets` with full `${NAME}` expansion.
|
|
68
|
+
*
|
|
69
|
+
* This is the canonical entry point for loading project env in any Glubean
|
|
70
|
+
* tool. CLI, MCP, and future consumers should all go through this function.
|
|
71
|
+
*
|
|
72
|
+
* ### Behavior
|
|
73
|
+
*
|
|
74
|
+
* 1. Reads `<rootDir>/<envFileName>` (default `.env`) and
|
|
75
|
+
* `<rootDir>/<envFileName>.secrets` (`.env.secrets` by default). Missing
|
|
76
|
+
* files are silently treated as empty.
|
|
77
|
+
* 2. Merges them temporarily (secrets override vars on key collision) and
|
|
78
|
+
* runs {@link expandVars} over the merged set so `${NAME}` references
|
|
79
|
+
* can cross between vars and secrets in either direction (subject to
|
|
80
|
+
* insertion-order single-pass limitation — see `expandVars` docs).
|
|
81
|
+
* 3. Splits the expanded merged map back into `vars` and `secrets`,
|
|
82
|
+
* preserving the invariant: a key on collision appears only in
|
|
83
|
+
* `secrets`, never duplicated into `vars`.
|
|
84
|
+
*
|
|
85
|
+
* ### Naming convention
|
|
86
|
+
*
|
|
87
|
+
* `.env` → `.env.secrets`
|
|
88
|
+
* `.env.staging` → `.env.staging.secrets`
|
|
89
|
+
* `.env.ci` → `.env.ci.secrets`
|
|
90
|
+
*
|
|
91
|
+
* The secrets path is always `<envFileName>.secrets` in the same directory.
|
|
92
|
+
*
|
|
93
|
+
* @example
|
|
94
|
+
* ```ts
|
|
95
|
+
* import { loadProjectEnv } from "@glubean/runner";
|
|
96
|
+
*
|
|
97
|
+
* const { vars, secrets } = await loadProjectEnv(projectRoot);
|
|
98
|
+
* const { vars: stagingVars, secrets: stagingSecrets } =
|
|
99
|
+
* await loadProjectEnv(projectRoot, ".env.staging");
|
|
100
|
+
* ```
|
|
101
|
+
*/
|
|
102
|
+
export declare function loadProjectEnv(rootDir: string, envFileName?: string): Promise<ProjectEnv>;
|
|
103
|
+
//# sourceMappingURL=env.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"env.d.ts","sourceRoot":"","sources":["../src/env.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAMH;;;;;;;;;;GAUG;AACH,wBAAsB,WAAW,CAC/B,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAWjC;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,UAAU,CACxB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAC3B,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAQxB;AAED;;;;;;;;;;GAUG;AACH,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC7B,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACjC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AACH,wBAAsB,cAAc,CAClC,OAAO,EAAE,MAAM,EACf,WAAW,SAAS,GACnB,OAAO,CAAC,UAAU,CAAC,CA2BrB"}
|
package/dist/env.js
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module env
|
|
3
|
+
*
|
|
4
|
+
* Canonical project-env loader for all Glubean entry points (CLI `run`,
|
|
5
|
+
* MCP tool handlers, VSCode extension, any future consumer).
|
|
6
|
+
*
|
|
7
|
+
* Historical background: CLI and MCP used to each have their own env loader
|
|
8
|
+
* (CLI via `loadEnvFile` without expansion, MCP via a handwritten
|
|
9
|
+
* `parseEnvContent`). Both dropped `${NAME}` expansion from the production
|
|
10
|
+
* path despite `expandVars` being implemented in CLI's shared lib — a silent
|
|
11
|
+
* regression from the original design. This module is the single place all
|
|
12
|
+
* entry points load env files from now on, with full expansion semantics.
|
|
13
|
+
*
|
|
14
|
+
* This lives in `@glubean/runner` because it's tool-level runtime
|
|
15
|
+
* infrastructure (same category as `bootstrap()`), not design-time SDK API.
|
|
16
|
+
*/
|
|
17
|
+
import { readFile } from "node:fs/promises";
|
|
18
|
+
import { resolve } from "node:path";
|
|
19
|
+
import { parse as parseDotenv } from "dotenv";
|
|
20
|
+
/**
|
|
21
|
+
* Load a single `.env`-style file and return its parsed key-value pairs.
|
|
22
|
+
*
|
|
23
|
+
* - File missing (ENOENT) → returns `{}` silently (consumers decide whether
|
|
24
|
+
* missing is an error)
|
|
25
|
+
* - File unreadable (other IO error) → returns `{}` with a warning to stderr
|
|
26
|
+
* - Content is parsed with the standard `dotenv` package (no expansion)
|
|
27
|
+
*
|
|
28
|
+
* This is the low-level primitive. Most callers want
|
|
29
|
+
* {@link loadProjectEnv} instead.
|
|
30
|
+
*/
|
|
31
|
+
export async function loadEnvFile(envPath) {
|
|
32
|
+
try {
|
|
33
|
+
const content = await readFile(envPath, "utf-8");
|
|
34
|
+
return parseDotenv(content);
|
|
35
|
+
}
|
|
36
|
+
catch (error) {
|
|
37
|
+
if (error.code === "ENOENT") {
|
|
38
|
+
return {};
|
|
39
|
+
}
|
|
40
|
+
console.warn(`Warning: Could not read env file ${envPath}: ${error.message}`);
|
|
41
|
+
return {};
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Expand `${NAME}` references in env values.
|
|
46
|
+
*
|
|
47
|
+
* Lookup order per reference:
|
|
48
|
+
* 1. Already-resolved values from this same expansion pass (supports
|
|
49
|
+
* forward references — keys defined earlier in insertion order).
|
|
50
|
+
* 2. `process.env[NAME]` (host environment variables).
|
|
51
|
+
* 3. Empty string fallback.
|
|
52
|
+
*
|
|
53
|
+
* Iteration is insertion order. This means:
|
|
54
|
+
* - Within a single file, a later key can reference earlier keys.
|
|
55
|
+
* - When called on a merged `{ ...vars, ...secrets }` object, secrets-only
|
|
56
|
+
* keys can reference any key from `vars` (because vars keys are inserted
|
|
57
|
+
* first). Vars keys referencing secrets-only keys **will not resolve**
|
|
58
|
+
* in a single pass — they'd need a multi-pass resolver.
|
|
59
|
+
*
|
|
60
|
+
* The multi-pass limitation is accepted — callers who need full
|
|
61
|
+
* topological expansion should use the SDK's `{{NAME}}` template at
|
|
62
|
+
* runtime via `resolveTemplate`, which resolves lazily in test execution
|
|
63
|
+
* context and can pull from vars / secrets / session dynamically.
|
|
64
|
+
*/
|
|
65
|
+
export function expandVars(vars) {
|
|
66
|
+
const result = {};
|
|
67
|
+
for (const [key, value] of Object.entries(vars)) {
|
|
68
|
+
result[key] = value.replace(/\$\{(\w+)\}/g, (_, name) => {
|
|
69
|
+
return result[name] ?? process.env[name] ?? "";
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
return result;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Load a project's `.env` + `.env.secrets` with full `${NAME}` expansion.
|
|
76
|
+
*
|
|
77
|
+
* This is the canonical entry point for loading project env in any Glubean
|
|
78
|
+
* tool. CLI, MCP, and future consumers should all go through this function.
|
|
79
|
+
*
|
|
80
|
+
* ### Behavior
|
|
81
|
+
*
|
|
82
|
+
* 1. Reads `<rootDir>/<envFileName>` (default `.env`) and
|
|
83
|
+
* `<rootDir>/<envFileName>.secrets` (`.env.secrets` by default). Missing
|
|
84
|
+
* files are silently treated as empty.
|
|
85
|
+
* 2. Merges them temporarily (secrets override vars on key collision) and
|
|
86
|
+
* runs {@link expandVars} over the merged set so `${NAME}` references
|
|
87
|
+
* can cross between vars and secrets in either direction (subject to
|
|
88
|
+
* insertion-order single-pass limitation — see `expandVars` docs).
|
|
89
|
+
* 3. Splits the expanded merged map back into `vars` and `secrets`,
|
|
90
|
+
* preserving the invariant: a key on collision appears only in
|
|
91
|
+
* `secrets`, never duplicated into `vars`.
|
|
92
|
+
*
|
|
93
|
+
* ### Naming convention
|
|
94
|
+
*
|
|
95
|
+
* `.env` → `.env.secrets`
|
|
96
|
+
* `.env.staging` → `.env.staging.secrets`
|
|
97
|
+
* `.env.ci` → `.env.ci.secrets`
|
|
98
|
+
*
|
|
99
|
+
* The secrets path is always `<envFileName>.secrets` in the same directory.
|
|
100
|
+
*
|
|
101
|
+
* @example
|
|
102
|
+
* ```ts
|
|
103
|
+
* import { loadProjectEnv } from "@glubean/runner";
|
|
104
|
+
*
|
|
105
|
+
* const { vars, secrets } = await loadProjectEnv(projectRoot);
|
|
106
|
+
* const { vars: stagingVars, secrets: stagingSecrets } =
|
|
107
|
+
* await loadProjectEnv(projectRoot, ".env.staging");
|
|
108
|
+
* ```
|
|
109
|
+
*/
|
|
110
|
+
export async function loadProjectEnv(rootDir, envFileName = ".env") {
|
|
111
|
+
const envPath = resolve(rootDir, envFileName);
|
|
112
|
+
const secretsPath = resolve(rootDir, `${envFileName}.secrets`);
|
|
113
|
+
const rawVars = await loadEnvFile(envPath);
|
|
114
|
+
const rawSecrets = await loadEnvFile(secretsPath);
|
|
115
|
+
// Merge for expansion so `${NAME}` can cross-reference both files.
|
|
116
|
+
// `{ ...rawVars, ...rawSecrets }` keeps insertion order — vars keys first,
|
|
117
|
+
// then secrets-only keys — so secrets can reference vars in a single pass.
|
|
118
|
+
const merged = { ...rawVars, ...rawSecrets };
|
|
119
|
+
const expanded = expandVars(merged);
|
|
120
|
+
// Split back. Keys that exist in both files → secret wins, appears only
|
|
121
|
+
// in `secrets` (not duplicated into `vars`).
|
|
122
|
+
const vars = {};
|
|
123
|
+
for (const key of Object.keys(rawVars)) {
|
|
124
|
+
if (!(key in rawSecrets)) {
|
|
125
|
+
vars[key] = expanded[key];
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
const secrets = {};
|
|
129
|
+
for (const key of Object.keys(rawSecrets)) {
|
|
130
|
+
secrets[key] = expanded[key];
|
|
131
|
+
}
|
|
132
|
+
return { vars, secrets };
|
|
133
|
+
}
|
|
134
|
+
//# sourceMappingURL=env.js.map
|
package/dist/env.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"env.js","sourceRoot":"","sources":["../src/env.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,KAAK,IAAI,WAAW,EAAE,MAAM,QAAQ,CAAC;AAE9C;;;;;;;;;;GAUG;AACH,MAAM,CAAC,KAAK,UAAU,WAAW,CAC/B,OAAe;IAEf,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,MAAM,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;QACjD,OAAO,WAAW,CAAC,OAAO,CAAC,CAAC;IAC9B,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAK,KAA+B,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;YACvD,OAAO,EAAE,CAAC;QACZ,CAAC;QACD,OAAO,CAAC,IAAI,CAAC,oCAAoC,OAAO,KAAM,KAAe,CAAC,OAAO,EAAE,CAAC,CAAC;QACzF,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,UAAU,UAAU,CACxB,IAA4B;IAE5B,MAAM,MAAM,GAA2B,EAAE,CAAC;IAC1C,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;QAChD,MAAM,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC,OAAO,CAAC,cAAc,EAAE,CAAC,CAAC,EAAE,IAAY,EAAE,EAAE;YAC9D,OAAO,MAAM,CAAC,IAAI,CAAC,IAAI,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;QACjD,CAAC,CAAC,CAAC;IACL,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAkBD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAClC,OAAe,EACf,WAAW,GAAG,MAAM;IAEpB,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,EAAE,WAAW,CAAC,CAAC;IAC9C,MAAM,WAAW,GAAG,OAAO,CAAC,OAAO,EAAE,GAAG,WAAW,UAAU,CAAC,CAAC;IAE/D,MAAM,OAAO,GAAG,MAAM,WAAW,CAAC,OAAO,CAAC,CAAC;IAC3C,MAAM,UAAU,GAAG,MAAM,WAAW,CAAC,WAAW,CAAC,CAAC;IAElD,mEAAmE;IACnE,2EAA2E;IAC3E,2EAA2E;IAC3E,MAAM,MAAM,GAAG,EAAE,GAAG,OAAO,EAAE,GAAG,UAAU,EAAE,CAAC;IAC7C,MAAM,QAAQ,GAAG,UAAU,CAAC,MAAM,CAAC,CAAC;IAEpC,wEAAwE;IACxE,6CAA6C;IAC7C,MAAM,IAAI,GAA2B,EAAE,CAAC;IACxC,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;QACvC,IAAI,CAAC,CAAC,GAAG,IAAI,UAAU,CAAC,EAAE,CAAC;YACzB,IAAI,CAAC,GAAG,CAAC,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC;QAC5B,CAAC;IACH,CAAC;IACD,MAAM,OAAO,GAA2B,EAAE,CAAC;IAC3C,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC,EAAE,CAAC;QAC1C,OAAO,CAAC,GAAG,CAAC,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC;IAC/B,CAAC;IAED,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;AAC3B,CAAC"}
|
package/dist/harness.js
CHANGED
|
@@ -8,7 +8,9 @@
|
|
|
8
8
|
import { parseArgs } from "node:util";
|
|
9
9
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
10
10
|
import { inferJsonSchema, truncateDeep } from "./schema_inference.js";
|
|
11
|
-
import {
|
|
11
|
+
import { bootstrap } from "./bootstrap.js";
|
|
12
|
+
import { loadProjectOverlays } from "@glubean/scanner";
|
|
13
|
+
import { setRuntime, setExplicitInput, setBootstrapInput, setForceStandalone, } from "@glubean/sdk/internal";
|
|
12
14
|
import ky from "ky";
|
|
13
15
|
import { Expectation } from "@glubean/sdk/expect";
|
|
14
16
|
// Global error handlers for async errors that escape try/catch
|
|
@@ -1013,6 +1015,84 @@ setRuntime({
|
|
|
1013
1015
|
log: ctx.log,
|
|
1014
1016
|
});
|
|
1015
1017
|
try {
|
|
1018
|
+
// Bootstrap project plugins before loading user code. Without this, any
|
|
1019
|
+
// test module that uses plugin-registered primitives (custom matchers,
|
|
1020
|
+
// `contract.graphql(...)`, `contract.grpc(...)`, ...) would either fail
|
|
1021
|
+
// on first access or silently fall through to the default behavior — see
|
|
1022
|
+
// plugin-manifest-proposal.md D2.
|
|
1023
|
+
await bootstrap(process.cwd());
|
|
1024
|
+
// Eagerly load `.bootstrap.{ts,js,mjs}` files (attachment-model §7.4).
|
|
1025
|
+
// Parent process also calls this for its own scanning purposes, but the
|
|
1026
|
+
// SDK bootstrap registry is process-local — every harness subprocess
|
|
1027
|
+
// has its own empty Map until it imports the overlay modules itself.
|
|
1028
|
+
// Without this, a filtered run like `glubean run project.contract.ts`
|
|
1029
|
+
// would reach the child's dispatcher with no overlays registered and
|
|
1030
|
+
// silently fall through to the raw execution path, defeating both the
|
|
1031
|
+
// overlay intent and `runnability.requireAttachment` guards.
|
|
1032
|
+
const overlayLoad = await loadProjectOverlays(process.cwd());
|
|
1033
|
+
for (const err of overlayLoad.errors) {
|
|
1034
|
+
console.log(JSON.stringify({
|
|
1035
|
+
type: "log",
|
|
1036
|
+
message: `Bootstrap overlay failed to load: ${err.file} — ${err.error}`,
|
|
1037
|
+
}));
|
|
1038
|
+
}
|
|
1039
|
+
// Spike 3 — runner input channels (attachment-model §8). The CLI / MCP
|
|
1040
|
+
// serializes runner-supplied case input + bootstrap params into env
|
|
1041
|
+
// vars before spawning this subprocess. Each map is JSON `{ testId:
|
|
1042
|
+
// value }`. Force-standalone is a JSON-encoded `string[]` of testIds.
|
|
1043
|
+
// We populate the SDK runner-input channel here so the dispatcher's
|
|
1044
|
+
// §5.1 algorithm sees them when test.fn runs.
|
|
1045
|
+
const explicitMapRaw = process.env["GLUBEAN_RUNNER_EXPLICIT_INPUT_MAP"];
|
|
1046
|
+
if (explicitMapRaw) {
|
|
1047
|
+
try {
|
|
1048
|
+
const parsed = JSON.parse(explicitMapRaw);
|
|
1049
|
+
for (const [testId, value] of Object.entries(parsed)) {
|
|
1050
|
+
setExplicitInput(testId, value);
|
|
1051
|
+
}
|
|
1052
|
+
}
|
|
1053
|
+
catch (err) {
|
|
1054
|
+
console.log(JSON.stringify({
|
|
1055
|
+
type: "log",
|
|
1056
|
+
message: `Invalid GLUBEAN_RUNNER_EXPLICIT_INPUT_MAP JSON: ` +
|
|
1057
|
+
(err instanceof Error ? err.message : String(err)),
|
|
1058
|
+
}));
|
|
1059
|
+
}
|
|
1060
|
+
}
|
|
1061
|
+
const bootstrapMapRaw = process.env["GLUBEAN_RUNNER_BOOTSTRAP_INPUT_MAP"];
|
|
1062
|
+
if (bootstrapMapRaw) {
|
|
1063
|
+
try {
|
|
1064
|
+
const parsed = JSON.parse(bootstrapMapRaw);
|
|
1065
|
+
for (const [testId, value] of Object.entries(parsed)) {
|
|
1066
|
+
setBootstrapInput(testId, value);
|
|
1067
|
+
}
|
|
1068
|
+
}
|
|
1069
|
+
catch (err) {
|
|
1070
|
+
console.log(JSON.stringify({
|
|
1071
|
+
type: "log",
|
|
1072
|
+
message: `Invalid GLUBEAN_RUNNER_BOOTSTRAP_INPUT_MAP JSON: ` +
|
|
1073
|
+
(err instanceof Error ? err.message : String(err)),
|
|
1074
|
+
}));
|
|
1075
|
+
}
|
|
1076
|
+
}
|
|
1077
|
+
const forceStandaloneRaw = process.env["GLUBEAN_RUNNER_FORCE_STANDALONE_IDS"];
|
|
1078
|
+
if (forceStandaloneRaw) {
|
|
1079
|
+
try {
|
|
1080
|
+
const parsed = JSON.parse(forceStandaloneRaw);
|
|
1081
|
+
if (Array.isArray(parsed)) {
|
|
1082
|
+
for (const testId of parsed) {
|
|
1083
|
+
if (typeof testId === "string")
|
|
1084
|
+
setForceStandalone(testId);
|
|
1085
|
+
}
|
|
1086
|
+
}
|
|
1087
|
+
}
|
|
1088
|
+
catch (err) {
|
|
1089
|
+
console.log(JSON.stringify({
|
|
1090
|
+
type: "log",
|
|
1091
|
+
message: `Invalid GLUBEAN_RUNNER_FORCE_STANDALONE_IDS JSON: ` +
|
|
1092
|
+
(err instanceof Error ? err.message : String(err)),
|
|
1093
|
+
}));
|
|
1094
|
+
}
|
|
1095
|
+
}
|
|
1016
1096
|
// Dynamic import - LOAD phase
|
|
1017
1097
|
console.log(JSON.stringify({
|
|
1018
1098
|
type: "log",
|