@glubean/cli 0.8.3 → 0.9.0
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/README.md +4 -2
- package/dist/commands/dry-run.d.ts +105 -0
- package/dist/commands/dry-run.d.ts.map +1 -0
- package/dist/commands/dry-run.js +238 -0
- package/dist/commands/dry-run.js.map +1 -0
- package/dist/commands/load.d.ts.map +1 -1
- package/dist/commands/load.js +20 -2
- package/dist/commands/load.js.map +1 -1
- package/dist/commands/run.d.ts +18 -0
- package/dist/commands/run.d.ts.map +1 -1
- package/dist/commands/run.js +188 -24
- package/dist/commands/run.js.map +1 -1
- package/dist/commands/sync.d.ts +21 -0
- package/dist/commands/sync.d.ts.map +1 -0
- package/dist/commands/sync.js +322 -0
- package/dist/commands/sync.js.map +1 -0
- package/dist/lib/active_env.d.ts +16 -1
- package/dist/lib/active_env.d.ts.map +1 -1
- package/dist/lib/active_env.js +46 -1
- package/dist/lib/active_env.js.map +1 -1
- package/dist/lib/only-selectors.d.ts +61 -0
- package/dist/lib/only-selectors.d.ts.map +1 -0
- package/dist/lib/only-selectors.js +79 -0
- package/dist/lib/only-selectors.js.map +1 -0
- package/dist/lib/upload.d.ts +53 -0
- package/dist/lib/upload.d.ts.map +1 -1
- package/dist/lib/upload.js +196 -9
- package/dist/lib/upload.js.map +1 -1
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +72 -3
- package/dist/main.js.map +1 -1
- package/package.json +7 -7
- package/dist/lib/env.d.ts +0 -29
- package/dist/lib/env.d.ts.map +0 -1
- package/dist/lib/env.js +0 -59
- package/dist/lib/env.js.map +0 -1
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
import { resolve } from "node:path";
|
|
2
|
+
import { stat } from "node:fs/promises";
|
|
3
|
+
import { loadProjectEnv } from "@glubean/runner";
|
|
4
|
+
import { buildProjections } from "./dry-run.js";
|
|
5
|
+
import { findProjectConfig } from "./run.js";
|
|
6
|
+
import { resolveToken, resolveProjectId, resolveApiUrl } from "../lib/auth.js";
|
|
7
|
+
import { resolveEnvFileName, SensitiveActiveEnvError } from "../lib/active_env.js";
|
|
8
|
+
const colors = {
|
|
9
|
+
reset: "\x1b[0m",
|
|
10
|
+
bold: "\x1b[1m",
|
|
11
|
+
dim: "\x1b[2m",
|
|
12
|
+
green: "\x1b[32m",
|
|
13
|
+
yellow: "\x1b[33m",
|
|
14
|
+
blue: "\x1b[34m",
|
|
15
|
+
red: "\x1b[31m",
|
|
16
|
+
};
|
|
17
|
+
/** Strip credential-bearing URL parts (query / fragment / userinfo) before a URL
|
|
18
|
+
* leaves the machine — mirrors the cloud's server-side sanitizer (defense in
|
|
19
|
+
* depth: the dry-run projector already placeholders ctx.secrets to `<KEY>`). */
|
|
20
|
+
function sanitizeUrl(url) {
|
|
21
|
+
try {
|
|
22
|
+
const u = new URL(url);
|
|
23
|
+
u.username = "";
|
|
24
|
+
u.password = "";
|
|
25
|
+
u.search = "";
|
|
26
|
+
u.hash = "";
|
|
27
|
+
return u.toString();
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
return url.split("#")[0].split("?")[0].replace(/(\/\/)[^/@]*@/, "$1");
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* `glubean sync` — sync the repo's test-definition projections (declared
|
|
35
|
+
* metadata + dry-run shape) to Glubean Cloud for team review. PROJECT-scoped:
|
|
36
|
+
* the projection is generated from SOURCE CODE, so it's one set per codebase
|
|
37
|
+
* regardless of how many targets it runs against. The upload is the COMPLETE
|
|
38
|
+
* source snapshot — the server replaces the project's projections with it
|
|
39
|
+
* (removed tests are deleted). Distinct from `glubean run --upload` (run
|
|
40
|
+
* evidence).
|
|
41
|
+
*/
|
|
42
|
+
export async function syncCommand(options = {}) {
|
|
43
|
+
const dir = options.dir ? resolve(options.dir) : process.cwd();
|
|
44
|
+
// Resolve auth/env from the PROJECT ROOT (so root .env.* / .glubean/active-env
|
|
45
|
+
// are honored even when --dir points at a nested scan dir) — parity with run.
|
|
46
|
+
const { rootDir } = await findProjectConfig(dir);
|
|
47
|
+
console.log(`\n${colors.bold}${colors.blue}🔄 Glubean Sync (test-definition projection)${colors.reset}\n`);
|
|
48
|
+
// Validate an EXPLICIT --env-file FIRST — before the (expensive, user-code-
|
|
49
|
+
// running) projection — so a typo fails fast. A missing explicit env file
|
|
50
|
+
// would otherwise load empty and let global/process credentials upload to the
|
|
51
|
+
// WRONG project (parity with run/load). Default: the active env (or .env).
|
|
52
|
+
// GLU-88: resolveEnvFileName throws SensitiveActiveEnvError instead of
|
|
53
|
+
// silently returning a prod-like active-env file — surface it as a clear,
|
|
54
|
+
// actionable error (mirrors run/load) rather than silently syncing
|
|
55
|
+
// projections against prod.
|
|
56
|
+
const userSpecifiedEnvFile = !!options.envFile;
|
|
57
|
+
let envFileName;
|
|
58
|
+
if (userSpecifiedEnvFile) {
|
|
59
|
+
envFileName = options.envFile;
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
try {
|
|
63
|
+
envFileName = await resolveEnvFileName(rootDir);
|
|
64
|
+
}
|
|
65
|
+
catch (err) {
|
|
66
|
+
if (err instanceof SensitiveActiveEnvError) {
|
|
67
|
+
console.error(`${colors.red}Sync failed: ${err.message}${colors.reset}`);
|
|
68
|
+
process.exit(1);
|
|
69
|
+
}
|
|
70
|
+
throw err;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
if (userSpecifiedEnvFile) {
|
|
74
|
+
try {
|
|
75
|
+
await stat(resolve(rootDir, envFileName));
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
console.error(`${colors.red}Sync failed: env file '${envFileName}' not found in ${rootDir}${colors.reset}`);
|
|
79
|
+
process.exit(1);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
// ALWAYS project the WHOLE project (rootDir), never just --dir: the upload is a
|
|
83
|
+
// complete snapshot the server replaces, so scanning a subdirectory would make
|
|
84
|
+
// the server delete every test outside it. --dir only locates the project root.
|
|
85
|
+
const { projected, errors, warnings, emptyTestFiles, contracts, workflows, openapi, openapiFailed } = await buildProjections(rootDir);
|
|
86
|
+
// A file that failed to import / timed out has NO projection. Since sync is a
|
|
87
|
+
// full-snapshot replace, publishing now would DELETE the broken file's tests'
|
|
88
|
+
// projections (treating them as removed) — so abort and let the user fix +
|
|
89
|
+
// re-sync the complete set.
|
|
90
|
+
if (errors.length) {
|
|
91
|
+
console.error(`${colors.red}Sync aborted: ${errors.length} file(s) failed to project.${colors.reset}`);
|
|
92
|
+
for (const e of errors)
|
|
93
|
+
console.error(` ${colors.red}✗ ${e.file}: ${e.message}${colors.reset}`);
|
|
94
|
+
console.error(`${colors.dim}Fix these files and re-run — syncing now would drop their tests' projections.${colors.reset}`);
|
|
95
|
+
process.exit(1);
|
|
96
|
+
}
|
|
97
|
+
// A file the SCANNER couldn't turn into a projection is dropped BEFORE upload
|
|
98
|
+
// (it never shows in `errors`), yet its tests/contracts/workflows would vanish
|
|
99
|
+
// from this full snapshot and be DELETED on replace. All fatal: test extraction
|
|
100
|
+
// THREW ("Failed to extract metadata from"), a test file yielded ZERO exports
|
|
101
|
+
// (emptyTestFiles), or a contract/workflow file failed to import ("Contract
|
|
102
|
+
// import failed" / "Flow import failed" — leaves contractsProjection/workflows
|
|
103
|
+
// missing that file, so a full-replace would wipe its prior Cloud projection).
|
|
104
|
+
const dropped = [
|
|
105
|
+
...warnings.filter((w) => w.startsWith("Failed to extract metadata from") ||
|
|
106
|
+
w.startsWith("Contract import failed") ||
|
|
107
|
+
w.startsWith("Flow import failed")),
|
|
108
|
+
...emptyTestFiles.map((f) => `${f} — a Glubean test file with no extractable tests (syntax error, or tests removed/unrecognized?)`),
|
|
109
|
+
];
|
|
110
|
+
if (dropped.length) {
|
|
111
|
+
console.error(`${colors.red}Sync aborted: ${dropped.length} file(s) would be dropped from the snapshot.${colors.reset}`);
|
|
112
|
+
for (const w of dropped)
|
|
113
|
+
console.error(` ${colors.red}✗ ${w}${colors.reset}`);
|
|
114
|
+
console.error(`${colors.dim}Fix these files and re-run — syncing now would delete their projections from Cloud.${colors.reset}`);
|
|
115
|
+
process.exit(1);
|
|
116
|
+
}
|
|
117
|
+
// Empty snapshot would CLEAR the project's projections — guard against an
|
|
118
|
+
// accidental run in the wrong/empty dir; require --allow-empty to actually wipe.
|
|
119
|
+
// "Empty" means NO specs of ANY kind (test + contract + workflow).
|
|
120
|
+
if (projected.length === 0 && contracts.length === 0 && workflows.length === 0 && !options.allowEmpty) {
|
|
121
|
+
console.log(`${colors.yellow}No tests, contracts, or workflows found.${colors.reset} ${colors.dim}Pass --allow-empty to clear the project's projections, or check the directory.${colors.reset}\n`);
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
// Resolve cloud auth — PROJECT-scoped (no target: the projection is repo-level).
|
|
125
|
+
const { vars, secrets } = await loadProjectEnv(rootDir, envFileName);
|
|
126
|
+
const authOpts = { token: options.token, project: options.project, apiUrl: options.apiUrl };
|
|
127
|
+
const sources = { envFileVars: { ...vars, ...secrets } };
|
|
128
|
+
const token = await resolveToken(authOpts, sources, options.tokenEnv);
|
|
129
|
+
const projectId = await resolveProjectId(authOpts, sources);
|
|
130
|
+
const apiUrl = await resolveApiUrl(authOpts, sources);
|
|
131
|
+
if (!token) {
|
|
132
|
+
console.error(`${colors.red}Sync failed: no auth token.${colors.reset}\n` +
|
|
133
|
+
`${colors.dim}Create a project token (glb_…) in the dashboard (Project → Tokens), then run 'glubean login', set GLUBEAN_TOKEN / --token, or add it to .env.secrets.${colors.reset}`);
|
|
134
|
+
process.exit(1);
|
|
135
|
+
}
|
|
136
|
+
if (!projectId) {
|
|
137
|
+
console.error(`${colors.red}Sync failed: no project ID.${colors.reset}\n` +
|
|
138
|
+
`${colors.dim}Set --project / GLUBEAN_PROJECT_ID, or run 'glubean login'.${colors.reset}`);
|
|
139
|
+
process.exit(1);
|
|
140
|
+
}
|
|
141
|
+
if (!apiUrl) {
|
|
142
|
+
console.error(`${colors.red}Sync failed: no API URL (set --api-url / GLUBEAN_API_URL).${colors.reset}`);
|
|
143
|
+
process.exit(1);
|
|
144
|
+
}
|
|
145
|
+
const tests = projected.map((p) => ({
|
|
146
|
+
testId: p.testId,
|
|
147
|
+
description: p.description ?? null,
|
|
148
|
+
deprecated: p.deprecated ?? null,
|
|
149
|
+
requires: p.requires ?? null,
|
|
150
|
+
defaultRun: p.defaultRun ?? null,
|
|
151
|
+
tags: p.tags ?? [],
|
|
152
|
+
assertions: p.assertions,
|
|
153
|
+
endpoints: p.endpoints.map((e) => ({ ...e, url: sanitizeUrl(e.url) })),
|
|
154
|
+
assertionCount: p.assertionCount,
|
|
155
|
+
projectionComplete: p.projectionComplete,
|
|
156
|
+
incompleteReason: p.incompleteReason ?? null,
|
|
157
|
+
skipped: p.skipped ?? false,
|
|
158
|
+
// B3 T1.5 row provenance — a server that predates the field strips it
|
|
159
|
+
// (ingest zod is non-strict), so this is forward-compatible. NOT redacted
|
|
160
|
+
// below (like testId): idTemplate/rowKey are identity keys built from the
|
|
161
|
+
// same row values as the uploaded testId itself — masking them would break
|
|
162
|
+
// the rowKey === id join Cloud derive performs.
|
|
163
|
+
...(p.each ? { each: p.each } : {}),
|
|
164
|
+
}));
|
|
165
|
+
// Redact outbound data before it leaves the machine (parity with run/load):
|
|
166
|
+
// a hardcoded credential in an assertion message / endpoint is masked (URL
|
|
167
|
+
// query/userinfo/fragment is already stripped above). Honor the PROJECT's
|
|
168
|
+
// redaction rules (glubean.yaml `defaults.redaction` — custom sensitiveKeys /
|
|
169
|
+
// customPatterns), not just the built-in defaults; FAIL CLOSED on invalid config.
|
|
170
|
+
const { redactValue, BUILTIN_SCOPES } = await import("@glubean/redaction");
|
|
171
|
+
const { loadProjectConfigV1, resolveRedactionConfig } = await import("../lib/config.js");
|
|
172
|
+
let redaction = resolveRedactionConfig(undefined); // built-in defaults
|
|
173
|
+
let hasConfig = false;
|
|
174
|
+
try {
|
|
175
|
+
await stat(resolve(rootDir, "glubean.yaml"));
|
|
176
|
+
hasConfig = true;
|
|
177
|
+
}
|
|
178
|
+
catch {
|
|
179
|
+
/* no glubean.yaml → built-in default redaction */
|
|
180
|
+
}
|
|
181
|
+
if (hasConfig) {
|
|
182
|
+
try {
|
|
183
|
+
const { config } = await loadProjectConfigV1(rootDir);
|
|
184
|
+
redaction = resolveRedactionConfig(config.defaults?.redaction);
|
|
185
|
+
}
|
|
186
|
+
catch (err) {
|
|
187
|
+
console.error(`${colors.red}Sync failed: invalid glubean.yaml redaction config — ${err?.message ?? String(err)}${colors.reset}`);
|
|
188
|
+
process.exit(1);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
// The projection is a static blob with NO request/response scope to bind to, so
|
|
192
|
+
// fold the builtin SCOPE sensitive keys (cookie / set-cookie / authorization /
|
|
193
|
+
// token / …) into the global keys — otherwise header/query examples carried in a
|
|
194
|
+
// contract/workflow projection would upload in cleartext (run/load apply these
|
|
195
|
+
// per-scope; here there's no scope, so apply them everywhere).
|
|
196
|
+
const scopeKeys = [...new Set(BUILTIN_SCOPES.flatMap((s) => s.rules?.sensitiveKeys ?? []))];
|
|
197
|
+
const globalRules = {
|
|
198
|
+
...redaction.globalRules,
|
|
199
|
+
sensitiveKeys: [...new Set([...(redaction.globalRules.sensitiveKeys ?? []), ...scopeKeys])],
|
|
200
|
+
};
|
|
201
|
+
const redactField = (v) => redactValue(v, {
|
|
202
|
+
globalRules,
|
|
203
|
+
replacementFormat: redaction.replacementFormat,
|
|
204
|
+
maxDepth: 64,
|
|
205
|
+
});
|
|
206
|
+
// The normalized contract/workflow `projection` is a TYPE/STRUCTURE blob (JSON
|
|
207
|
+
// schemas, node trees) — its object KEYS are field names (e.g. a schema property
|
|
208
|
+
// literally named `password`/`token`), NOT secrets. Key-based redaction would
|
|
209
|
+
// MASK those field names and destroy the schema projection — the most important
|
|
210
|
+
// part of a contract. So redact the projection with PATTERN rules ONLY (still
|
|
211
|
+
// catches secret-LOOKING literal values, e.g. a hardcoded `sk-…` default), never
|
|
212
|
+
// sensitiveKeys.
|
|
213
|
+
const structureRules = { ...redaction.globalRules, sensitiveKeys: [] };
|
|
214
|
+
const redactStructure = (v) => redactValue(v, {
|
|
215
|
+
globalRules: structureRules,
|
|
216
|
+
replacementFormat: redaction.replacementFormat,
|
|
217
|
+
maxDepth: 64,
|
|
218
|
+
});
|
|
219
|
+
// Redact ONLY the secret-bearing/free-text fields — NEVER `testId` (the stable
|
|
220
|
+
// join key with run evidence; redacting an id that matches a built-in pattern
|
|
221
|
+
// would break correlation and collapse distinct ids) or structural fields
|
|
222
|
+
// (requires/defaultRun/counts/flags).
|
|
223
|
+
const safeTests = tests.map((t) => ({
|
|
224
|
+
...t,
|
|
225
|
+
description: t.description == null ? t.description : redactField(t.description),
|
|
226
|
+
deprecated: t.deprecated == null ? t.deprecated : redactField(t.deprecated),
|
|
227
|
+
incompleteReason: t.incompleteReason == null ? t.incompleteReason : redactField(t.incompleteReason),
|
|
228
|
+
assertions: redactField(t.assertions),
|
|
229
|
+
endpoints: redactField(t.endpoints),
|
|
230
|
+
}));
|
|
231
|
+
// Contracts/workflows: redact the free-text + the normalized `projection` body
|
|
232
|
+
// (schemas/descriptions/notes), preserve identity/structural fields.
|
|
233
|
+
const safeContracts = contracts.map((c) => ({
|
|
234
|
+
contractId: c.contractId,
|
|
235
|
+
protocol: c.protocol,
|
|
236
|
+
target: c.target ?? null,
|
|
237
|
+
description: c.description == null ? null : redactField(c.description),
|
|
238
|
+
deprecated: c.deprecated == null ? null : redactField(c.deprecated),
|
|
239
|
+
tags: c.tags ?? [],
|
|
240
|
+
caseCount: c.caseCount,
|
|
241
|
+
projection: redactStructure(c.projection),
|
|
242
|
+
projectionComplete: c.projectionComplete,
|
|
243
|
+
incompleteReason: c.incompleteReason ?? null,
|
|
244
|
+
}));
|
|
245
|
+
const safeWorkflows = workflows.map((w) => ({
|
|
246
|
+
workflowId: w.workflowId,
|
|
247
|
+
name: w.name ?? null,
|
|
248
|
+
description: w.description == null ? null : redactField(w.description),
|
|
249
|
+
tags: w.tags ?? [],
|
|
250
|
+
nodeCount: w.nodeCount,
|
|
251
|
+
projection: redactStructure(w.projection),
|
|
252
|
+
projectionComplete: w.projectionComplete,
|
|
253
|
+
incompleteReason: w.incompleteReason ?? null,
|
|
254
|
+
}));
|
|
255
|
+
// The OpenAPI doc is purely structural (paths + schemas). Redact it pattern-ONLY
|
|
256
|
+
// (same as the normalized projection): mask secret-LOOKING example/default values
|
|
257
|
+
// but PRESERVE schema field names — masking a `password`/`token` field name (a
|
|
258
|
+
// type, not a secret) would corrupt the schema. `null` when there are no HTTP
|
|
259
|
+
// contracts, so a full-replace clears any stale doc.
|
|
260
|
+
const safeOpenapi = openapi ? redactStructure(openapi) : null;
|
|
261
|
+
const base = `${apiUrl.replace(/\/+$/, "")}/v1/projects/${projectId}/projections`;
|
|
262
|
+
// Each kind is its OWN full-snapshot replace (an empty kind clears that kind's
|
|
263
|
+
// stale projections). POST all three; a failure on any aborts.
|
|
264
|
+
const post = async (kind, body, opts) => {
|
|
265
|
+
let res;
|
|
266
|
+
try {
|
|
267
|
+
res = await fetch(`${base}/${kind}`, {
|
|
268
|
+
method: "POST",
|
|
269
|
+
headers: { "content-type": "application/json", authorization: `Bearer ${token}` },
|
|
270
|
+
body: JSON.stringify(body),
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
catch (err) {
|
|
274
|
+
console.error(`${colors.red}Sync failed (${kind}): ${err?.message ?? String(err)}${colors.reset}`);
|
|
275
|
+
process.exit(1);
|
|
276
|
+
}
|
|
277
|
+
if (!res.ok) {
|
|
278
|
+
// A server that predates this projection kind answers 404 — tolerate it (skip
|
|
279
|
+
// this kind) instead of failing a sync that already replaced the OTHER kinds, so
|
|
280
|
+
// a newer CLI keeps working against a not-yet-upgraded / self-hosted server.
|
|
281
|
+
if (opts?.tolerateMissingRoute && res.status === 404)
|
|
282
|
+
return { skipped: true };
|
|
283
|
+
const text = await res.text().catch(() => "");
|
|
284
|
+
console.error(`${colors.red}Sync failed (${kind}): ${res.status} ${text}${colors.reset}`);
|
|
285
|
+
if (res.status === 401 || res.status === 403) {
|
|
286
|
+
console.error(`${colors.dim}The token is invalid/expired or lacks runs:write. Create a project token in the dashboard and 'glubean login' (or set GLUBEAN_TOKEN).${colors.reset}`);
|
|
287
|
+
}
|
|
288
|
+
process.exit(1);
|
|
289
|
+
}
|
|
290
|
+
return (await res.json().catch(() => ({})));
|
|
291
|
+
};
|
|
292
|
+
const testRes = await post("test", { tests: safeTests });
|
|
293
|
+
const contractRes = await post("contract", { contracts: safeContracts });
|
|
294
|
+
const workflowRes = await post("workflow", { workflows: safeWorkflows });
|
|
295
|
+
// The OpenAPI doc is a project-level single snapshot (not a per-id replace) — one
|
|
296
|
+
// doc rendered from all HTTP contracts. POST it last; `null` clears a stale doc. Two
|
|
297
|
+
// ways it skips (best-effort, never aborts the whole sync): render FAILED (don't wipe
|
|
298
|
+
// a prior doc on a transient error), or the server predates the route (404 tolerated,
|
|
299
|
+
// so a newer CLI doesn't break sync against a not-yet-upgraded server).
|
|
300
|
+
const openapiRes = openapiFailed
|
|
301
|
+
? { skipped: true }
|
|
302
|
+
: await post("openapi", { openapi: safeOpenapi }, { tolerateMissingRoute: true });
|
|
303
|
+
const line = (label, r, n) => {
|
|
304
|
+
const removed = r.deleted ? `${colors.dim} (${r.deleted} removed)${colors.reset}` : "";
|
|
305
|
+
return `${colors.green}✓ ${r.upserted ?? n} ${label}${colors.reset}${removed}`;
|
|
306
|
+
};
|
|
307
|
+
const pathCount = safeOpenapi ? Object.keys(safeOpenapi.paths ?? {}).length : 0;
|
|
308
|
+
const openapiLine = openapiFailed
|
|
309
|
+
? `${colors.yellow}⚠ openapi skipped (render failed; kept previous)${colors.reset}`
|
|
310
|
+
: openapiRes?.skipped
|
|
311
|
+
? `${colors.dim}· openapi not supported by this server (skipped)${colors.reset}`
|
|
312
|
+
: `${colors.green}✓ openapi${colors.reset}${colors.dim} (${pathCount} path${pathCount === 1 ? "" : "s"})${colors.reset}`;
|
|
313
|
+
console.log(`${line("test", testRes, safeTests.length)} ${line("contract", contractRes, safeContracts.length)} ${line("workflow", workflowRes, safeWorkflows.length)} ${openapiLine} ${colors.dim}→ project ${projectId}${colors.reset}`);
|
|
314
|
+
const partial = projected.filter((p) => !p.projectionComplete).length +
|
|
315
|
+
contracts.filter((c) => !c.projectionComplete).length +
|
|
316
|
+
workflows.filter((w) => !w.projectionComplete).length;
|
|
317
|
+
if (partial > 0) {
|
|
318
|
+
console.log(`${colors.yellow} ◐ ${partial} partial — use ctx.when()/switch()/while() (tests) or resolve opaque nodes/unprojectable schemas (workflows/contracts) for full projection${colors.reset}`);
|
|
319
|
+
}
|
|
320
|
+
console.log();
|
|
321
|
+
}
|
|
322
|
+
//# sourceMappingURL=sync.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sync.js","sourceRoot":"","sources":["../../src/commands/sync.ts"],"names":[],"mappings":"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"}
|
package/dist/lib/active_env.d.ts
CHANGED
|
@@ -5,6 +5,18 @@
|
|
|
5
5
|
* Used by both the CLI `run` command and the MCP server to resolve
|
|
6
6
|
* which `.env.<name>` file to load when no explicit `--env-file` is given.
|
|
7
7
|
*/
|
|
8
|
+
/**
|
|
9
|
+
* Thrown by `resolveEnvFileName` when the active env resolves to a
|
|
10
|
+
* sensitive (prod-like) name. Callers MUST catch this and surface an
|
|
11
|
+
* actionable error — never fall through to silently loading the file
|
|
12
|
+
* anyway. Explicit `--env-file .env.prod` bypasses `resolveEnvFileName`
|
|
13
|
+
* entirely (call sites branch on `userSpecifiedEnvFile` first), so this
|
|
14
|
+
* only blocks the *implicit* path.
|
|
15
|
+
*/
|
|
16
|
+
export declare class SensitiveActiveEnvError extends Error {
|
|
17
|
+
readonly envName: string;
|
|
18
|
+
constructor(envName: string);
|
|
19
|
+
}
|
|
8
20
|
/**
|
|
9
21
|
* Read the active environment name from `.glubean/active-env`.
|
|
10
22
|
* Returns `undefined` if the file doesn't exist or is empty.
|
|
@@ -23,7 +35,10 @@ export declare function clearActiveEnv(projectRoot: string): Promise<void>;
|
|
|
23
35
|
*
|
|
24
36
|
* Priority:
|
|
25
37
|
* 1. Explicit `--env-file` flag (pass-through, not handled here)
|
|
26
|
-
* 2. `.glubean/active-env` → `.env.<name>`
|
|
38
|
+
* 2. `.glubean/active-env` → `.env.<name>` — UNLESS `<name>` is a sensitive
|
|
39
|
+
* (prod-like) name, in which case this throws `SensitiveActiveEnvError`
|
|
40
|
+
* instead of silently returning it (GLU-88). Every caller must handle
|
|
41
|
+
* that error explicitly.
|
|
27
42
|
* 3. Default `.env`
|
|
28
43
|
*/
|
|
29
44
|
export declare function resolveEnvFileName(projectRoot: string): Promise<string>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"active_env.d.ts","sourceRoot":"","sources":["../../src/lib/active_env.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;
|
|
1
|
+
{"version":3,"file":"active_env.d.ts","sourceRoot":"","sources":["../../src/lib/active_env.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AA4BH;;;;;;;GAOG;AACH,qBAAa,uBAAwB,SAAQ,KAAK;aACpB,OAAO,EAAE,MAAM;gBAAf,OAAO,EAAE,MAAM;CAW5C;AAMD;;;GAGG;AACH,wBAAsB,aAAa,CACjC,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAQ7B;AAED;;GAEG;AACH,wBAAsB,cAAc,CAClC,WAAW,EAAE,MAAM,EACnB,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,IAAI,CAAC,CAIf;AAED;;GAEG;AACH,wBAAsB,cAAc,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAMvE;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,kBAAkB,CACtC,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,MAAM,CAAC,CASjB"}
|
package/dist/lib/active_env.js
CHANGED
|
@@ -9,6 +9,45 @@ import { resolve } from "node:path";
|
|
|
9
9
|
import { readFile, writeFile, mkdir, unlink } from "node:fs/promises";
|
|
10
10
|
const ACTIVE_ENV_DIR = ".glubean";
|
|
11
11
|
const ACTIVE_ENV_FILE = "active-env";
|
|
12
|
+
/**
|
|
13
|
+
* GLU-88: env names that must NEVER be picked up silently through the
|
|
14
|
+
* `.glubean/active-env` fallback. `.glubean/active-env` is a persistent,
|
|
15
|
+
* un-TTL'd, un-warned sticky file — a `glubean env use prod` run once (e.g.
|
|
16
|
+
* to debug something) stays in effect for every future `glubean run` /
|
|
17
|
+
* `sync` / `load` in that directory until someone remembers to `glubean env
|
|
18
|
+
* reset`. For an ordinary named env (staging, dev, ci) that stickiness is
|
|
19
|
+
* the whole point of the feature. For a *production* env it's a footgun:
|
|
20
|
+
* a later, unrelated `--upload` silently ships data to prod with no
|
|
21
|
+
* confirmation and (today) no way to delete it server-side. Matched
|
|
22
|
+
* case-insensitively against the trimmed active-env name; deliberately an
|
|
23
|
+
* exact-match denylist (not a substring/regex sweep) to avoid false
|
|
24
|
+
* positives on names like "preprod-mirror".
|
|
25
|
+
*/
|
|
26
|
+
const SENSITIVE_ENV_NAMES = new Set(["prod", "production"]);
|
|
27
|
+
function isSensitiveEnvName(name) {
|
|
28
|
+
return SENSITIVE_ENV_NAMES.has(name.trim().toLowerCase());
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Thrown by `resolveEnvFileName` when the active env resolves to a
|
|
32
|
+
* sensitive (prod-like) name. Callers MUST catch this and surface an
|
|
33
|
+
* actionable error — never fall through to silently loading the file
|
|
34
|
+
* anyway. Explicit `--env-file .env.prod` bypasses `resolveEnvFileName`
|
|
35
|
+
* entirely (call sites branch on `userSpecifiedEnvFile` first), so this
|
|
36
|
+
* only blocks the *implicit* path.
|
|
37
|
+
*/
|
|
38
|
+
export class SensitiveActiveEnvError extends Error {
|
|
39
|
+
envName;
|
|
40
|
+
constructor(envName) {
|
|
41
|
+
super(`Active environment is "${envName}" (set via \`glubean env use ${envName}\`, ` +
|
|
42
|
+
`recorded in .glubean/active-env), which looks like a production ` +
|
|
43
|
+
`environment. Refusing to load it implicitly — GLU-88 hardens exactly ` +
|
|
44
|
+
`this case (a stale active-env silently routing an unrelated run to prod). ` +
|
|
45
|
+
`Pass \`--env-file .env.${envName}\` to use it explicitly, or run ` +
|
|
46
|
+
`\`glubean env reset\` to clear the active environment and fall back to .env.`);
|
|
47
|
+
this.envName = envName;
|
|
48
|
+
this.name = "SensitiveActiveEnvError";
|
|
49
|
+
}
|
|
50
|
+
}
|
|
12
51
|
function activeEnvPath(projectRoot) {
|
|
13
52
|
return resolve(projectRoot, ACTIVE_ENV_DIR, ACTIVE_ENV_FILE);
|
|
14
53
|
}
|
|
@@ -50,12 +89,18 @@ export async function clearActiveEnv(projectRoot) {
|
|
|
50
89
|
*
|
|
51
90
|
* Priority:
|
|
52
91
|
* 1. Explicit `--env-file` flag (pass-through, not handled here)
|
|
53
|
-
* 2. `.glubean/active-env` → `.env.<name>`
|
|
92
|
+
* 2. `.glubean/active-env` → `.env.<name>` — UNLESS `<name>` is a sensitive
|
|
93
|
+
* (prod-like) name, in which case this throws `SensitiveActiveEnvError`
|
|
94
|
+
* instead of silently returning it (GLU-88). Every caller must handle
|
|
95
|
+
* that error explicitly.
|
|
54
96
|
* 3. Default `.env`
|
|
55
97
|
*/
|
|
56
98
|
export async function resolveEnvFileName(projectRoot) {
|
|
57
99
|
const activeEnv = await readActiveEnv(projectRoot);
|
|
58
100
|
if (activeEnv) {
|
|
101
|
+
if (isSensitiveEnvName(activeEnv)) {
|
|
102
|
+
throw new SensitiveActiveEnvError(activeEnv);
|
|
103
|
+
}
|
|
59
104
|
return `.env.${activeEnv}`;
|
|
60
105
|
}
|
|
61
106
|
return ".env";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"active_env.js","sourceRoot":"","sources":["../../src/lib/active_env.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAEtE,MAAM,cAAc,GAAG,UAAU,CAAC;AAClC,MAAM,eAAe,GAAG,YAAY,CAAC;AAErC,SAAS,aAAa,CAAC,WAAmB;IACxC,OAAO,OAAO,CAAC,WAAW,EAAE,cAAc,EAAE,eAAe,CAAC,CAAC;AAC/D,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CACjC,WAAmB;IAEnB,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,MAAM,QAAQ,CAAC,aAAa,CAAC,WAAW,CAAC,EAAE,OAAO,CAAC,CAAC;QACpE,MAAM,GAAG,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC;QAC3B,OAAO,GAAG,IAAI,SAAS,CAAC;IAC1B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAC;IACnB,CAAC;AACH,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAClC,WAAmB,EACnB,OAAe;IAEf,MAAM,GAAG,GAAG,OAAO,CAAC,WAAW,EAAE,cAAc,CAAC,CAAC;IACjD,MAAM,KAAK,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACtC,MAAM,SAAS,CAAC,aAAa,CAAC,WAAW,CAAC,EAAE,OAAO,GAAG,IAAI,EAAE,OAAO,CAAC,CAAC;AACvE,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,WAAmB;IACtD,IAAI,CAAC;QACH,MAAM,MAAM,CAAC,aAAa,CAAC,WAAW,CAAC,CAAC,CAAC;IAC3C,CAAC;IAAC,MAAM,CAAC;QACP,6BAA6B;IAC/B,CAAC;AACH,CAAC;AAED
|
|
1
|
+
{"version":3,"file":"active_env.js","sourceRoot":"","sources":["../../src/lib/active_env.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAEtE,MAAM,cAAc,GAAG,UAAU,CAAC;AAClC,MAAM,eAAe,GAAG,YAAY,CAAC;AAErC;;;;;;;;;;;;;GAaG;AACH,MAAM,mBAAmB,GAAG,IAAI,GAAG,CAAC,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC,CAAC;AAE5D,SAAS,kBAAkB,CAAC,IAAY;IACtC,OAAO,mBAAmB,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,CAAC;AAC5D,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,OAAO,uBAAwB,SAAQ,KAAK;IACpB;IAA5B,YAA4B,OAAe;QACzC,KAAK,CACH,0BAA0B,OAAO,gCAAgC,OAAO,MAAM;YAC5E,kEAAkE;YAClE,uEAAuE;YACvE,4EAA4E;YAC5E,0BAA0B,OAAO,kCAAkC;YACnE,8EAA8E,CACjF,CAAC;QARwB,YAAO,GAAP,OAAO,CAAQ;QASzC,IAAI,CAAC,IAAI,GAAG,yBAAyB,CAAC;IACxC,CAAC;CACF;AAED,SAAS,aAAa,CAAC,WAAmB;IACxC,OAAO,OAAO,CAAC,WAAW,EAAE,cAAc,EAAE,eAAe,CAAC,CAAC;AAC/D,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CACjC,WAAmB;IAEnB,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,MAAM,QAAQ,CAAC,aAAa,CAAC,WAAW,CAAC,EAAE,OAAO,CAAC,CAAC;QACpE,MAAM,GAAG,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC;QAC3B,OAAO,GAAG,IAAI,SAAS,CAAC;IAC1B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAC;IACnB,CAAC;AACH,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAClC,WAAmB,EACnB,OAAe;IAEf,MAAM,GAAG,GAAG,OAAO,CAAC,WAAW,EAAE,cAAc,CAAC,CAAC;IACjD,MAAM,KAAK,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACtC,MAAM,SAAS,CAAC,aAAa,CAAC,WAAW,CAAC,EAAE,OAAO,GAAG,IAAI,EAAE,OAAO,CAAC,CAAC;AACvE,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,WAAmB;IACtD,IAAI,CAAC;QACH,MAAM,MAAM,CAAC,aAAa,CAAC,WAAW,CAAC,CAAC,CAAC;IAC3C,CAAC;IAAC,MAAM,CAAC;QACP,6BAA6B;IAC/B,CAAC;AACH,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,CAAC,KAAK,UAAU,kBAAkB,CACtC,WAAmB;IAEnB,MAAM,SAAS,GAAG,MAAM,aAAa,CAAC,WAAW,CAAC,CAAC;IACnD,IAAI,SAAS,EAAE,CAAC;QACd,IAAI,kBAAkB,CAAC,SAAS,CAAC,EAAE,CAAC;YAClC,MAAM,IAAI,uBAAuB,CAAC,SAAS,CAAC,CAAC;QAC/C,CAAC;QACD,OAAO,QAAQ,SAAS,EAAE,CAAC;IAC7B,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* only-selectors.ts — pure, unit-testable builders for the B2 M3 `{id, rowIndex}`
|
|
3
|
+
* "only" selector protocol at the CLI surface.
|
|
4
|
+
*
|
|
5
|
+
* Two entry points:
|
|
6
|
+
* - `buildOnlySelectorsFromFlags` — the single validation gate for the
|
|
7
|
+
* `--only-id` / `--row` / `--rerun-failed` flag combination. Returns the
|
|
8
|
+
* selector set for the explicit (`--only-id`/`--row`) path, or a clear error.
|
|
9
|
+
* - `deriveRerunSelectors` — turns a prior `last-run.result.json` into the
|
|
10
|
+
* failed-subset selectors + the distinct files those failures live in.
|
|
11
|
+
*
|
|
12
|
+
* The protocol shape (`OnlySelector`) and matching/collection semantics
|
|
13
|
+
* (`collectFailedSelectors`) are reused from `@glubean/runner` so the CLI, the
|
|
14
|
+
* SDK runner, and the cloud harness all speak ONE protocol.
|
|
15
|
+
*/
|
|
16
|
+
import { type OnlySelector } from "@glubean/runner";
|
|
17
|
+
export interface OnlyFlags {
|
|
18
|
+
onlyId?: string[];
|
|
19
|
+
row?: number;
|
|
20
|
+
rerunFailed?: boolean;
|
|
21
|
+
}
|
|
22
|
+
export type BuildSelectorsResult = {
|
|
23
|
+
ok: true;
|
|
24
|
+
selectors: OnlySelector[];
|
|
25
|
+
} | {
|
|
26
|
+
ok: false;
|
|
27
|
+
error: string;
|
|
28
|
+
};
|
|
29
|
+
/**
|
|
30
|
+
* Validate the flag combination and build selectors for the explicit
|
|
31
|
+
* `--only-id` / `--row` path.
|
|
32
|
+
*
|
|
33
|
+
* Validation:
|
|
34
|
+
* - `--rerun-failed` is mutually exclusive with `--only-id` / `--row`.
|
|
35
|
+
* - `--row` requires EXACTLY one `--only-id` (it pins a single `.each` row).
|
|
36
|
+
*
|
|
37
|
+
* Returns `{ selectors: [] }` for the conflict-free `--rerun-failed` case — the
|
|
38
|
+
* caller sources the actual selectors from the last run via `deriveRerunSelectors`.
|
|
39
|
+
*/
|
|
40
|
+
export declare function buildOnlySelectorsFromFlags(opts: OnlyFlags): BuildSelectorsResult;
|
|
41
|
+
/**
|
|
42
|
+
* Derive the `--rerun-failed` selector set + the distinct files those failures
|
|
43
|
+
* came from, off a prior run's `last-run.result.json` `tests` array.
|
|
44
|
+
*
|
|
45
|
+
* `selectors` reuses `collectFailedSelectors` (failed records with an id → bare
|
|
46
|
+
* `{id}` or row-pinned `{id, rowIndex}`). `files` is the de-duplicated list of
|
|
47
|
+
* `filePath`s of failed records (declaration order preserved) so the caller can
|
|
48
|
+
* narrow discovery to only the files that actually contained a failure.
|
|
49
|
+
*/
|
|
50
|
+
export declare function deriveRerunSelectors(lastRun: {
|
|
51
|
+
tests: Array<{
|
|
52
|
+
testId?: string;
|
|
53
|
+
rowIndex?: number;
|
|
54
|
+
filePath?: string;
|
|
55
|
+
success: boolean;
|
|
56
|
+
}>;
|
|
57
|
+
}): {
|
|
58
|
+
selectors: OnlySelector[];
|
|
59
|
+
files: string[];
|
|
60
|
+
};
|
|
61
|
+
//# sourceMappingURL=only-selectors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"only-selectors.d.ts","sourceRoot":"","sources":["../../src/lib/only-selectors.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,OAAO,EAA0B,KAAK,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAE5E,MAAM,WAAW,SAAS;IACxB,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED,MAAM,MAAM,oBAAoB,GAC5B;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,SAAS,EAAE,YAAY,EAAE,CAAA;CAAE,GACvC;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAEjC;;;;;;;;;;GAUG;AACH,wBAAgB,2BAA2B,CAAC,IAAI,EAAE,SAAS,GAAG,oBAAoB,CAiCjF;AAED;;;;;;;;GAQG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE;IAC5C,KAAK,EAAE,KAAK,CAAC;QAAE,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC;CAC3F,GAAG;IAAE,SAAS,EAAE,YAAY,EAAE,CAAC;IAAC,KAAK,EAAE,MAAM,EAAE,CAAA;CAAE,CAcjD"}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* only-selectors.ts — pure, unit-testable builders for the B2 M3 `{id, rowIndex}`
|
|
3
|
+
* "only" selector protocol at the CLI surface.
|
|
4
|
+
*
|
|
5
|
+
* Two entry points:
|
|
6
|
+
* - `buildOnlySelectorsFromFlags` — the single validation gate for the
|
|
7
|
+
* `--only-id` / `--row` / `--rerun-failed` flag combination. Returns the
|
|
8
|
+
* selector set for the explicit (`--only-id`/`--row`) path, or a clear error.
|
|
9
|
+
* - `deriveRerunSelectors` — turns a prior `last-run.result.json` into the
|
|
10
|
+
* failed-subset selectors + the distinct files those failures live in.
|
|
11
|
+
*
|
|
12
|
+
* The protocol shape (`OnlySelector`) and matching/collection semantics
|
|
13
|
+
* (`collectFailedSelectors`) are reused from `@glubean/runner` so the CLI, the
|
|
14
|
+
* SDK runner, and the cloud harness all speak ONE protocol.
|
|
15
|
+
*/
|
|
16
|
+
import { collectFailedSelectors } from "@glubean/runner";
|
|
17
|
+
/**
|
|
18
|
+
* Validate the flag combination and build selectors for the explicit
|
|
19
|
+
* `--only-id` / `--row` path.
|
|
20
|
+
*
|
|
21
|
+
* Validation:
|
|
22
|
+
* - `--rerun-failed` is mutually exclusive with `--only-id` / `--row`.
|
|
23
|
+
* - `--row` requires EXACTLY one `--only-id` (it pins a single `.each` row).
|
|
24
|
+
*
|
|
25
|
+
* Returns `{ selectors: [] }` for the conflict-free `--rerun-failed` case — the
|
|
26
|
+
* caller sources the actual selectors from the last run via `deriveRerunSelectors`.
|
|
27
|
+
*/
|
|
28
|
+
export function buildOnlySelectorsFromFlags(opts) {
|
|
29
|
+
const onlyId = opts.onlyId ?? [];
|
|
30
|
+
const hasRow = opts.row !== undefined;
|
|
31
|
+
const rerun = !!opts.rerunFailed;
|
|
32
|
+
// --rerun-failed reuses the last run's failed set; it can't be combined with
|
|
33
|
+
// an explicit --only-id / --row target.
|
|
34
|
+
if (rerun && (onlyId.length > 0 || hasRow)) {
|
|
35
|
+
return {
|
|
36
|
+
ok: false,
|
|
37
|
+
error: "--rerun-failed cannot be combined with --only-id or --row (it reuses the last run's failed set).",
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
// --row pins a single .each row, so it needs exactly one --only-id to attach to.
|
|
41
|
+
if (hasRow && onlyId.length !== 1) {
|
|
42
|
+
return {
|
|
43
|
+
ok: false,
|
|
44
|
+
error: onlyId.length === 0
|
|
45
|
+
? "--row requires --only-id (the id whose .each row you want to isolate)."
|
|
46
|
+
: `--row requires exactly one --only-id (got ${onlyId.length}). Drop --row to run multiple ids.`,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
// Conflict-free rerun mode: no flag-derived selectors — they come from the file.
|
|
50
|
+
if (rerun)
|
|
51
|
+
return { ok: true, selectors: [] };
|
|
52
|
+
if (hasRow) {
|
|
53
|
+
return { ok: true, selectors: [{ id: onlyId[0], rowIndex: opts.row }] };
|
|
54
|
+
}
|
|
55
|
+
return { ok: true, selectors: onlyId.map((id) => ({ id })) };
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Derive the `--rerun-failed` selector set + the distinct files those failures
|
|
59
|
+
* came from, off a prior run's `last-run.result.json` `tests` array.
|
|
60
|
+
*
|
|
61
|
+
* `selectors` reuses `collectFailedSelectors` (failed records with an id → bare
|
|
62
|
+
* `{id}` or row-pinned `{id, rowIndex}`). `files` is the de-duplicated list of
|
|
63
|
+
* `filePath`s of failed records (declaration order preserved) so the caller can
|
|
64
|
+
* narrow discovery to only the files that actually contained a failure.
|
|
65
|
+
*/
|
|
66
|
+
export function deriveRerunSelectors(lastRun) {
|
|
67
|
+
const tests = lastRun.tests ?? [];
|
|
68
|
+
const selectors = collectFailedSelectors(tests.map((t) => ({ id: t.testId, rowIndex: t.rowIndex, success: t.success })));
|
|
69
|
+
const files = [];
|
|
70
|
+
const seen = new Set();
|
|
71
|
+
for (const t of tests) {
|
|
72
|
+
if (t.success === false && t.filePath && !seen.has(t.filePath)) {
|
|
73
|
+
seen.add(t.filePath);
|
|
74
|
+
files.push(t.filePath);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return { selectors, files };
|
|
78
|
+
}
|
|
79
|
+
//# sourceMappingURL=only-selectors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"only-selectors.js","sourceRoot":"","sources":["../../src/lib/only-selectors.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,OAAO,EAAE,sBAAsB,EAAqB,MAAM,iBAAiB,CAAC;AAY5E;;;;;;;;;;GAUG;AACH,MAAM,UAAU,2BAA2B,CAAC,IAAe;IACzD,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,IAAI,EAAE,CAAC;IACjC,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,KAAK,SAAS,CAAC;IACtC,MAAM,KAAK,GAAG,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC;IAEjC,6EAA6E;IAC7E,wCAAwC;IACxC,IAAI,KAAK,IAAI,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,IAAI,MAAM,CAAC,EAAE,CAAC;QAC3C,OAAO;YACL,EAAE,EAAE,KAAK;YACT,KAAK,EACH,kGAAkG;SACrG,CAAC;IACJ,CAAC;IAED,iFAAiF;IACjF,IAAI,MAAM,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAClC,OAAO;YACL,EAAE,EAAE,KAAK;YACT,KAAK,EACH,MAAM,CAAC,MAAM,KAAK,CAAC;gBACjB,CAAC,CAAC,wEAAwE;gBAC1E,CAAC,CAAC,6CAA6C,MAAM,CAAC,MAAM,oCAAoC;SACrG,CAAC;IACJ,CAAC;IAED,iFAAiF;IACjF,IAAI,KAAK;QAAE,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,EAAE,EAAE,CAAC;IAE9C,IAAI,MAAM,EAAE,CAAC;QACX,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC,EAAE,EAAE,EAAE,MAAM,CAAC,CAAC,CAAE,EAAE,QAAQ,EAAE,IAAI,CAAC,GAAI,EAAE,CAAC,EAAE,CAAC;IAC5E,CAAC;IACD,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC;AAC/D,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,oBAAoB,CAAC,OAEpC;IACC,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,IAAI,EAAE,CAAC;IAClC,MAAM,SAAS,GAAG,sBAAsB,CACtC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,MAAM,EAAE,QAAQ,EAAE,CAAC,CAAC,QAAQ,EAAE,OAAO,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,CAC/E,CAAC;IACF,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;IAC/B,KAAK,MAAM,CAAC,IAAI,KAAK,EAAE,CAAC;QACtB,IAAI,CAAC,CAAC,OAAO,KAAK,KAAK,IAAI,CAAC,CAAC,QAAQ,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC;YAC/D,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC;YACrB,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC;QACzB,CAAC;IACH,CAAC;IACD,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC;AAC9B,CAAC"}
|