@glubean/cli 0.8.3 → 0.8.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/dry-run.d.ts +100 -0
- package/dist/commands/dry-run.d.ts.map +1 -0
- package/dist/commands/dry-run.js +237 -0
- package/dist/commands/dry-run.js.map +1 -0
- package/dist/commands/run.d.ts +18 -0
- package/dist/commands/run.d.ts.map +1 -1
- package/dist/commands/run.js +135 -19
- 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 +297 -0
- package/dist/commands/sync.js.map +1 -0
- 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 +69 -0
- package/dist/main.js.map +1 -1
- package/package.json +6 -6
- 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,100 @@
|
|
|
1
|
+
export interface DryRunCommandOptions {
|
|
2
|
+
dir?: string;
|
|
3
|
+
json?: boolean;
|
|
4
|
+
out?: string;
|
|
5
|
+
}
|
|
6
|
+
/** One projected test, merged from static scan metadata + dynamic dry-run shape. */
|
|
7
|
+
export interface ProjectedTest {
|
|
8
|
+
testId: string;
|
|
9
|
+
exportName: string;
|
|
10
|
+
file: string;
|
|
11
|
+
description?: string;
|
|
12
|
+
deprecated?: string;
|
|
13
|
+
requires?: string;
|
|
14
|
+
defaultRun?: string;
|
|
15
|
+
tags?: string[];
|
|
16
|
+
assertions: Array<{
|
|
17
|
+
kind: string;
|
|
18
|
+
message?: string;
|
|
19
|
+
branch?: string;
|
|
20
|
+
}>;
|
|
21
|
+
endpoints: Array<{
|
|
22
|
+
method: string;
|
|
23
|
+
url: string;
|
|
24
|
+
branch?: string;
|
|
25
|
+
}>;
|
|
26
|
+
assertionCount: number;
|
|
27
|
+
projectionComplete: boolean;
|
|
28
|
+
incompleteReason?: string;
|
|
29
|
+
skipped?: boolean;
|
|
30
|
+
}
|
|
31
|
+
/** One projected contract (C1) — the scanner's static normalized contract, plus
|
|
32
|
+
* derived head fields, for upload to /projections/contract. */
|
|
33
|
+
export interface ProjectedContract {
|
|
34
|
+
contractId: string;
|
|
35
|
+
protocol: string;
|
|
36
|
+
target?: string;
|
|
37
|
+
description?: string;
|
|
38
|
+
deprecated?: string;
|
|
39
|
+
tags?: string[];
|
|
40
|
+
caseCount: number;
|
|
41
|
+
/** Full normalized contract (cases + schemas). */
|
|
42
|
+
projection: unknown;
|
|
43
|
+
projectionComplete: boolean;
|
|
44
|
+
incompleteReason?: string;
|
|
45
|
+
}
|
|
46
|
+
/** One projected workflow (C1) — the scanner's static normalized workflow. */
|
|
47
|
+
export interface ProjectedWorkflow {
|
|
48
|
+
workflowId: string;
|
|
49
|
+
name?: string;
|
|
50
|
+
description?: string;
|
|
51
|
+
tags?: string[];
|
|
52
|
+
nodeCount: number;
|
|
53
|
+
/** Full normalized workflow (node tree + phases). */
|
|
54
|
+
projection: unknown;
|
|
55
|
+
projectionComplete: boolean;
|
|
56
|
+
incompleteReason?: string;
|
|
57
|
+
}
|
|
58
|
+
export interface ProjectionResult {
|
|
59
|
+
projected: ProjectedTest[];
|
|
60
|
+
files: string[];
|
|
61
|
+
errors: Array<{
|
|
62
|
+
file: string;
|
|
63
|
+
message: string;
|
|
64
|
+
}>;
|
|
65
|
+
/** Scanner warnings (e.g. "Failed to extract metadata from <file>: …") — a
|
|
66
|
+
* file whose metadata couldn't be extracted is omitted from `files`/`projected`
|
|
67
|
+
* entirely, so a full-snapshot sync must treat these as fatal. */
|
|
68
|
+
warnings: string[];
|
|
69
|
+
/** Test-named files that yielded ZERO exports (parser recovered from a syntax
|
|
70
|
+
* error, or a misnamed module) — silently dropped, so also fatal for sync. */
|
|
71
|
+
emptyTestFiles: string[];
|
|
72
|
+
/** Statically-projected contracts (declarative — no dry-run). */
|
|
73
|
+
contracts: ProjectedContract[];
|
|
74
|
+
/** Statically-projected workflows (declarative — no dry-run). */
|
|
75
|
+
workflows: ProjectedWorkflow[];
|
|
76
|
+
/** OpenAPI 3.1 doc rendered from the project's HTTP contracts, for the Cloud
|
|
77
|
+
* api-reference view (endpoints/schemas with constraints/formats/enums). `null`
|
|
78
|
+
* when the project has no HTTP endpoints (a full sync then clears any stale doc).
|
|
79
|
+
* See `openapiFailed` for the render-error case. */
|
|
80
|
+
openapi?: Record<string, unknown> | null;
|
|
81
|
+
/** True when OpenAPI rendering THREW (bootstrap/extract/render). Distinct from a
|
|
82
|
+
* `null` doc: sync SKIPS the openapi upload on failure, so a transient error never
|
|
83
|
+
* wipes the project's previously-synced API reference. */
|
|
84
|
+
openapiFailed?: boolean;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Scan + dry-run a directory and merge static scan metadata
|
|
88
|
+
* (description/deprecated/requires/defaultRun/bareBranchCount) with each test's
|
|
89
|
+
* dynamic dry-run shape. Shared by `glubean dry-run` (print) and `glubean sync`
|
|
90
|
+
* (upload).
|
|
91
|
+
*/
|
|
92
|
+
export declare function buildProjections(dir: string): Promise<ProjectionResult>;
|
|
93
|
+
/**
|
|
94
|
+
* `glubean dry-run` — project the SHAPE of every simple test (assertions made,
|
|
95
|
+
* endpoints hit) without running them, for cloud team review. Combines static
|
|
96
|
+
* scan metadata (description/deprecated/bareBranchCount) with the dynamic
|
|
97
|
+
* dry-run projection.
|
|
98
|
+
*/
|
|
99
|
+
export declare function dryRunCommand(options?: DryRunCommandOptions): Promise<void>;
|
|
100
|
+
//# sourceMappingURL=dry-run.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dry-run.d.ts","sourceRoot":"","sources":["../../src/commands/dry-run.ts"],"names":[],"mappings":"AAoBA,MAAM,WAAW,oBAAoB;IACnC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED,oFAAoF;AACpF,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,UAAU,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACvE,SAAS,EAAE,KAAK,CAAC;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACnE,cAAc,EAAE,MAAM,CAAC;IACvB,kBAAkB,EAAE,OAAO,CAAC;IAC5B,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED;gEACgE;AAChE,MAAM,WAAW,iBAAiB;IAChC,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,kDAAkD;IAClD,UAAU,EAAE,OAAO,CAAC;IACpB,kBAAkB,EAAE,OAAO,CAAC;IAC5B,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,8EAA8E;AAC9E,MAAM,WAAW,iBAAiB;IAChC,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,qDAAqD;IACrD,UAAU,EAAE,OAAO,CAAC;IACpB,kBAAkB,EAAE,OAAO,CAAC;IAC5B,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,MAAM,WAAW,gBAAgB;IAC/B,SAAS,EAAE,aAAa,EAAE,CAAC;IAC3B,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,MAAM,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACjD;;uEAEmE;IACnE,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB;mFAC+E;IAC/E,cAAc,EAAE,MAAM,EAAE,CAAC;IACzB,iEAAiE;IACjE,SAAS,EAAE,iBAAiB,EAAE,CAAC;IAC/B,iEAAiE;IACjE,SAAS,EAAE,iBAAiB,EAAE,CAAC;IAC/B;;;yDAGqD;IACrD,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;IACzC;;+DAE2D;IAC3D,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AAED;;;;;GAKG;AACH,wBAAsB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAwK7E;AAED;;;;;GAKG;AACH,wBAAsB,aAAa,CAAC,OAAO,GAAE,oBAAyB,GAAG,OAAO,CAAC,IAAI,CAAC,CA8DrF"}
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
import { resolve, basename } from "node:path";
|
|
2
|
+
import { writeFile } from "node:fs/promises";
|
|
3
|
+
import { scan, extractContractsFromProject } from "@glubean/scanner";
|
|
4
|
+
import { dryRunFiles, bootstrap } from "@glubean/runner";
|
|
5
|
+
import { renderArtifact, openapiArtifact, } from "@glubean/sdk";
|
|
6
|
+
const colors = {
|
|
7
|
+
reset: "\x1b[0m",
|
|
8
|
+
bold: "\x1b[1m",
|
|
9
|
+
dim: "\x1b[2m",
|
|
10
|
+
green: "\x1b[32m",
|
|
11
|
+
yellow: "\x1b[33m",
|
|
12
|
+
blue: "\x1b[34m",
|
|
13
|
+
red: "\x1b[31m",
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* Scan + dry-run a directory and merge static scan metadata
|
|
17
|
+
* (description/deprecated/requires/defaultRun/bareBranchCount) with each test's
|
|
18
|
+
* dynamic dry-run shape. Shared by `glubean dry-run` (print) and `glubean sync`
|
|
19
|
+
* (upload).
|
|
20
|
+
*/
|
|
21
|
+
export async function buildProjections(dir) {
|
|
22
|
+
// Bootstrap the project's SDK runtime/plugins BEFORE any contract/flow module is
|
|
23
|
+
// imported — both `scan` (below) and `extractContractsFromProject` (for the OpenAPI
|
|
24
|
+
// render) import them. A project that relies on `glubean.setup.*` to make its
|
|
25
|
+
// contracts importable would otherwise cache a FAILED ESM evaluation from scan's
|
|
26
|
+
// first import, breaking every later extraction (the OpenAPI render would silently
|
|
27
|
+
// go null). Best-effort: a bootstrap failure must NOT break scan / dry-run for
|
|
28
|
+
// projects that don't need setup.
|
|
29
|
+
try {
|
|
30
|
+
await bootstrap(dir);
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
// ignore — scan/dry-run may still work; the OpenAPI extract fail-softs below
|
|
34
|
+
}
|
|
35
|
+
const scanResult = await scan(dir);
|
|
36
|
+
// Build a lookup of static metadata keyed by absolute file + export name.
|
|
37
|
+
const metaByKey = new Map();
|
|
38
|
+
const fileSet = new Set();
|
|
39
|
+
for (const [relPath, fileMeta] of Object.entries(scanResult.files)) {
|
|
40
|
+
const absPath = resolve(dir, relPath);
|
|
41
|
+
for (const exp of fileMeta.exports) {
|
|
42
|
+
// Only simple tests have a dry-runnable body; workflows are statically shaped.
|
|
43
|
+
if (exp.workflow)
|
|
44
|
+
continue;
|
|
45
|
+
fileSet.add(absPath);
|
|
46
|
+
metaByKey.set(`${absPath}::${exp.exportName}`, {
|
|
47
|
+
description: exp.description,
|
|
48
|
+
deprecated: exp.deprecated,
|
|
49
|
+
requires: exp.requires,
|
|
50
|
+
defaultRun: exp.defaultRun,
|
|
51
|
+
tags: exp.tags,
|
|
52
|
+
bareBranchCount: exp.bareBranchCount,
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
const files = [...fileSet];
|
|
57
|
+
const { shapes, errors } = await dryRunFiles(files, { cwd: dir });
|
|
58
|
+
const projected = shapes.map((s) => {
|
|
59
|
+
const meta = metaByKey.get(`${s.file}::${s.exportName}`) ?? {};
|
|
60
|
+
let projectionComplete = s.projectionComplete;
|
|
61
|
+
let incompleteReason = s.incompleteReason;
|
|
62
|
+
// Fold the static bare-branch signal: a native if/switch means only one arm
|
|
63
|
+
// was followed, so the shape is partial even if execution succeeded.
|
|
64
|
+
if (projectionComplete && meta.bareBranchCount && meta.bareBranchCount > 0) {
|
|
65
|
+
projectionComplete = false;
|
|
66
|
+
incompleteReason = `${meta.bareBranchCount} bare branch/loop(s) — use ctx.when()/ctx.switch()/ctx.while() for full projection`;
|
|
67
|
+
}
|
|
68
|
+
return {
|
|
69
|
+
testId: s.testId,
|
|
70
|
+
exportName: s.exportName,
|
|
71
|
+
file: s.file,
|
|
72
|
+
description: meta.description,
|
|
73
|
+
deprecated: meta.deprecated,
|
|
74
|
+
requires: meta.requires,
|
|
75
|
+
defaultRun: meta.defaultRun,
|
|
76
|
+
// Prefer the RUNTIME shape's tags (test.meta.tags) so data-driven `tagFields`
|
|
77
|
+
// row tags survive; fall back to the static scan tags when the project's
|
|
78
|
+
// runner predates shape tags (version skew), so statically-tagged tests
|
|
79
|
+
// aren't published with empty tags.
|
|
80
|
+
...((s.tags ?? meta.tags)?.length ? { tags: s.tags ?? meta.tags } : {}),
|
|
81
|
+
assertions: s.assertions,
|
|
82
|
+
endpoints: s.endpoints,
|
|
83
|
+
assertionCount: s.assertionCount,
|
|
84
|
+
projectionComplete,
|
|
85
|
+
...(incompleteReason ? { incompleteReason } : {}),
|
|
86
|
+
...(s.skipped ? { skipped: true } : {}),
|
|
87
|
+
};
|
|
88
|
+
});
|
|
89
|
+
projected.sort((a, b) => a.testId.localeCompare(b.testId));
|
|
90
|
+
// Contracts + workflows are DECLARATIVE — the scanner already produced their full
|
|
91
|
+
// normalized projection statically (no dry-run). Map to the upload shape; the
|
|
92
|
+
// normalized blob is the reviewable body, the head fields derive from it.
|
|
93
|
+
const contracts = (scanResult.contractsProjection ?? []).map((c) => ({
|
|
94
|
+
contractId: c.id,
|
|
95
|
+
protocol: c.protocol,
|
|
96
|
+
target: c.target,
|
|
97
|
+
description: c.description,
|
|
98
|
+
deprecated: c.deprecated,
|
|
99
|
+
tags: c.tags,
|
|
100
|
+
caseCount: c.cases?.length ?? 0,
|
|
101
|
+
projection: c,
|
|
102
|
+
projectionComplete: !(c.unprojectableSchemas && c.unprojectableSchemas.length > 0),
|
|
103
|
+
...(c.unprojectableSchemas && c.unprojectableSchemas.length > 0
|
|
104
|
+
? { incompleteReason: `${c.unprojectableSchemas.length} declared schema(s) couldn't be projected` }
|
|
105
|
+
: {}),
|
|
106
|
+
}));
|
|
107
|
+
const workflows = (scanResult.workflows ?? []).map((w) => {
|
|
108
|
+
const g = w.gradeSummary ?? { full: 0, partial: 0, opaque: 0 };
|
|
109
|
+
const nodeCount = (g.full ?? 0) + (g.partial ?? 0) + (g.opaque ?? 0);
|
|
110
|
+
return {
|
|
111
|
+
workflowId: w.id,
|
|
112
|
+
name: w.name,
|
|
113
|
+
description: w.description,
|
|
114
|
+
tags: w.tags,
|
|
115
|
+
nodeCount,
|
|
116
|
+
projection: w,
|
|
117
|
+
projectionComplete: (g.opaque ?? 0) === 0,
|
|
118
|
+
...((g.opaque ?? 0) > 0
|
|
119
|
+
? { incompleteReason: `${g.opaque} node(s) projected opaque — not fully statically shaped` }
|
|
120
|
+
: {}),
|
|
121
|
+
};
|
|
122
|
+
});
|
|
123
|
+
contracts.sort((a, b) => a.contractId.localeCompare(b.contractId));
|
|
124
|
+
workflows.sort((a, b) => a.workflowId.localeCompare(b.workflowId));
|
|
125
|
+
// Render an OpenAPI 3.1 doc from the SAME contracts, so Cloud can show
|
|
126
|
+
// endpoints/schemas with the published api-reference view. This needs the
|
|
127
|
+
// RUNTIME extraction (real schema objects → JSON Schema); the static scan's
|
|
128
|
+
// `contractsProjection` can't produce it. Same-process ESM import is cached,
|
|
129
|
+
// so this doesn't re-run contract module side effects. Best-effort — a failure
|
|
130
|
+
// here never blocks the test/contract/workflow projection.
|
|
131
|
+
// Three DISTINCT states, because sync treats them differently:
|
|
132
|
+
// • a real doc (has HTTP paths) → upload it
|
|
133
|
+
// • no HTTP endpoints (null) → upload null, clearing any stale doc
|
|
134
|
+
// • render FAILED (openapiFailed) → sync SKIPS the upload, preserving the prior doc
|
|
135
|
+
// (so a transient bootstrap/extract/render error never wipes the API reference).
|
|
136
|
+
let openapi = null;
|
|
137
|
+
let openapiFailed = false;
|
|
138
|
+
try {
|
|
139
|
+
// bootstrap already ran at the top of buildProjections (before scan imported the
|
|
140
|
+
// contract modules), so the runtime extraction below resolves cleanly.
|
|
141
|
+
const { contracts: rawContracts, errors: extractErrors } = await extractContractsFromProject(dir);
|
|
142
|
+
if (extractErrors && extractErrors.length > 0) {
|
|
143
|
+
// Per-file import/synthesis failures are REPORTED here (not thrown). A partial
|
|
144
|
+
// extraction must not publish a half-doc or clear the prior one — skip + preserve.
|
|
145
|
+
openapiFailed = true;
|
|
146
|
+
}
|
|
147
|
+
else if (rawContracts.length > 0) {
|
|
148
|
+
const doc = renderArtifact(openapiArtifact, rawContracts, { title: basename(dir) || "API Specification" });
|
|
149
|
+
// An empty fallback (non-HTTP / inbound-only contracts) carries no paths — that's
|
|
150
|
+
// "no API reference" (null → clear), NOT a real doc.
|
|
151
|
+
const paths = doc?.paths;
|
|
152
|
+
openapi = paths && Object.keys(paths).length > 0 ? doc : null;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
catch {
|
|
156
|
+
// Render threw — keep `openapi` null but flag the failure so sync skips the upload
|
|
157
|
+
// instead of clearing a previously-synced doc.
|
|
158
|
+
openapiFailed = true;
|
|
159
|
+
}
|
|
160
|
+
return {
|
|
161
|
+
projected,
|
|
162
|
+
files,
|
|
163
|
+
errors,
|
|
164
|
+
warnings: scanResult.warnings,
|
|
165
|
+
emptyTestFiles: scanResult.emptyTestFiles,
|
|
166
|
+
contracts,
|
|
167
|
+
workflows,
|
|
168
|
+
openapi,
|
|
169
|
+
openapiFailed,
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* `glubean dry-run` — project the SHAPE of every simple test (assertions made,
|
|
174
|
+
* endpoints hit) without running them, for cloud team review. Combines static
|
|
175
|
+
* scan metadata (description/deprecated/bareBranchCount) with the dynamic
|
|
176
|
+
* dry-run projection.
|
|
177
|
+
*/
|
|
178
|
+
export async function dryRunCommand(options = {}) {
|
|
179
|
+
const dir = options.dir ? resolve(options.dir) : process.cwd();
|
|
180
|
+
const { projected, files, errors, warnings, emptyTestFiles, openapi } = await buildProjections(dir);
|
|
181
|
+
// Files that contribute NO projection though they look like tests — a sync
|
|
182
|
+
// would silently drop them (see syncCommand).
|
|
183
|
+
const dropped = [
|
|
184
|
+
...warnings.filter((w) => w.startsWith("Failed to extract metadata from")),
|
|
185
|
+
...emptyTestFiles.map((f) => `${f} — a Glubean test file with no extractable tests (syntax error, or tests removed/unrecognized?)`),
|
|
186
|
+
];
|
|
187
|
+
if (options.out) {
|
|
188
|
+
await writeFile(resolve(options.out), JSON.stringify({ tests: projected, errors, warnings, emptyTestFiles, openapi }, null, 2));
|
|
189
|
+
}
|
|
190
|
+
if (options.json) {
|
|
191
|
+
console.log(JSON.stringify({ tests: projected, errors, warnings, emptyTestFiles, openapi }, null, 2));
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
// ── Human-readable ──
|
|
195
|
+
console.log(`\n${colors.bold}${colors.blue}🔬 Glubean Dry-Run (shape projection)${colors.reset}\n`);
|
|
196
|
+
console.log(`${colors.dim}Directory: ${dir}${colors.reset}`);
|
|
197
|
+
console.log(`${colors.dim}Projected: ${projected.length} test(s) from ${files.length} file(s)${colors.reset}\n`);
|
|
198
|
+
for (const t of projected) {
|
|
199
|
+
const flag = t.skipped
|
|
200
|
+
? `${colors.yellow}(skipped)${colors.reset}`
|
|
201
|
+
: t.projectionComplete
|
|
202
|
+
? `${colors.green}● complete${colors.reset}`
|
|
203
|
+
: `${colors.yellow}◐ partial${colors.reset}`;
|
|
204
|
+
console.log(`${colors.bold}${t.testId}${colors.reset} ${flag}`);
|
|
205
|
+
if (t.description)
|
|
206
|
+
console.log(` ${colors.dim}${t.description}${colors.reset}`);
|
|
207
|
+
if (t.deprecated)
|
|
208
|
+
console.log(` ${colors.yellow}⚠ deprecated: ${t.deprecated}${colors.reset}`);
|
|
209
|
+
if (t.endpoints.length) {
|
|
210
|
+
const eps = t.endpoints.map((e) => `${e.method} ${e.url}`).join(", ");
|
|
211
|
+
console.log(` ${colors.dim}endpoints:${colors.reset} ${eps}`);
|
|
212
|
+
}
|
|
213
|
+
console.log(` ${colors.dim}assertions (${t.assertionCount}):${colors.reset}`);
|
|
214
|
+
for (const a of t.assertions) {
|
|
215
|
+
const label = a.message ? `${a.kind} "${a.message}"` : a.kind;
|
|
216
|
+
const branch = a.branch ? ` ${colors.dim}[${a.branch}]${colors.reset}` : "";
|
|
217
|
+
console.log(` - ${label}${branch}`);
|
|
218
|
+
}
|
|
219
|
+
if (!t.projectionComplete && t.incompleteReason) {
|
|
220
|
+
console.log(` ${colors.yellow}⚠ partial: ${t.incompleteReason}${colors.reset}`);
|
|
221
|
+
}
|
|
222
|
+
console.log();
|
|
223
|
+
}
|
|
224
|
+
if (errors.length) {
|
|
225
|
+
console.log(`${colors.red}Import errors:${colors.reset}`);
|
|
226
|
+
for (const e of errors)
|
|
227
|
+
console.log(` ${colors.red}✗ ${e.file}: ${e.message}${colors.reset}`);
|
|
228
|
+
console.log();
|
|
229
|
+
}
|
|
230
|
+
if (dropped.length) {
|
|
231
|
+
console.log(`${colors.red}Dropped files (their tests are omitted — sync would delete them):${colors.reset}`);
|
|
232
|
+
for (const w of dropped)
|
|
233
|
+
console.log(` ${colors.red}✗ ${w}${colors.reset}`);
|
|
234
|
+
console.log();
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
//# sourceMappingURL=dry-run.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dry-run.js","sourceRoot":"","sources":["../../src/commands/dry-run.ts"],"names":[],"mappings":"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"}
|
package/dist/commands/run.d.ts
CHANGED
|
@@ -44,6 +44,10 @@ interface RunOptions {
|
|
|
44
44
|
includeOptIn?: boolean;
|
|
45
45
|
upload?: boolean;
|
|
46
46
|
uploadReceiptJson?: string;
|
|
47
|
+
/** ART1-B escape hatch — keep this run's local screenshot files after a
|
|
48
|
+
* successful `--upload` instead of the default post-upload cleanup
|
|
49
|
+
* (which unlinks only server-confirmed, whitelist-contained files). */
|
|
50
|
+
keepLocal?: boolean;
|
|
47
51
|
project?: string;
|
|
48
52
|
/**
|
|
49
53
|
* Upload TARGET (the API/system under test the runs belong to). Resolved from
|
|
@@ -115,6 +119,20 @@ interface RunOptions {
|
|
|
115
119
|
* per-profile gates that the legacy flat-shape path can't express.
|
|
116
120
|
*/
|
|
117
121
|
thresholds?: import("@glubean/sdk").ThresholdConfig;
|
|
122
|
+
/**
|
|
123
|
+
* B2 M3 — `{id, rowIndex}` selector protocol.
|
|
124
|
+
*
|
|
125
|
+
* `onlyId` — run only the test(s) with a matching id (repeatable). For `.each`
|
|
126
|
+
* rows, pass the concrete expanded id (e.g. `user-0`).
|
|
127
|
+
*
|
|
128
|
+
* `row` — with a single `onlyId`, isolate the 0-based `.each` row index.
|
|
129
|
+
*
|
|
130
|
+
* `rerunFailed` — re-run only the tests that failed in the last `glubean run`
|
|
131
|
+
* (reads `.glubean/last-run.result.json`). Mutually exclusive with onlyId/row.
|
|
132
|
+
*/
|
|
133
|
+
onlyId?: string[];
|
|
134
|
+
row?: number;
|
|
135
|
+
rerunFailed?: boolean;
|
|
118
136
|
}
|
|
119
137
|
export declare function findProjectConfig(startDir: string): Promise<{
|
|
120
138
|
rootDir: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"run.d.ts","sourceRoot":"","sources":["../../src/commands/run.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"run.d.ts","sourceRoot":"","sources":["../../src/commands/run.ts"],"names":[],"mappings":"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"}
|
package/dist/commands/run.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { bootstrap, evaluateThresholds, MetricCollector, ProjectRunner, buildRunContext, } from "@glubean/runner";
|
|
2
|
+
import { buildOnlySelectorsFromFlags, deriveRerunSelectors } from "../lib/only-selectors.js";
|
|
2
3
|
import { basename, dirname, isAbsolute, relative, resolve } from "node:path";
|
|
3
4
|
import { randomUUID } from "node:crypto";
|
|
4
5
|
import { stat, readdir, readFile, writeFile, mkdir, rm } from "node:fs/promises";
|
|
@@ -519,8 +520,8 @@ export async function runCommand(target, options = {}) {
|
|
|
519
520
|
const interactive = capabilityProfile.browser;
|
|
520
521
|
const traceCollector = [];
|
|
521
522
|
console.log(`\n${colors.bold}${colors.blue}🧪 Glubean Test Runner${colors.reset}\n`);
|
|
522
|
-
|
|
523
|
-
|
|
523
|
+
let testFiles = await resolveTestFiles(target);
|
|
524
|
+
let isMultiFile = testFiles.length > 1;
|
|
524
525
|
// Single string view of target for serialization / display paths
|
|
525
526
|
// (result.json, junit, traces). Multi-suite passes an array; join with
|
|
526
527
|
// ", " so downstream consumers still see a printable target field.
|
|
@@ -544,6 +545,59 @@ export async function runCommand(target, options = {}) {
|
|
|
544
545
|
}
|
|
545
546
|
const startDir = testFiles[0].substring(0, testFiles[0].lastIndexOf("/"));
|
|
546
547
|
const { rootDir } = await findProjectConfig(startDir);
|
|
548
|
+
// ── B2 M3 — `{id, rowIndex}` "only" selectors ────────────────────────────
|
|
549
|
+
// Validate the --only-id / --row / --rerun-failed combo up front (single
|
|
550
|
+
// gate), then resolve the active selector set. `--rerun-failed` reads the
|
|
551
|
+
// previous run and narrows discovery to the files that failed; `--only-id` /
|
|
552
|
+
// `--row` narrow `testsToRun` below by template-matching concrete ids against
|
|
553
|
+
// static `.each` template ids. Either way the precise per-row filter runs in
|
|
554
|
+
// the harness subprocess via the GLUBEAN_RUNNER_ONLY_SELECTORS env channel.
|
|
555
|
+
let onlySelectors = [];
|
|
556
|
+
const selectorFlags = buildOnlySelectorsFromFlags({
|
|
557
|
+
onlyId: options.onlyId,
|
|
558
|
+
row: options.row,
|
|
559
|
+
rerunFailed: options.rerunFailed,
|
|
560
|
+
});
|
|
561
|
+
if (!selectorFlags.ok) {
|
|
562
|
+
console.error(`\n${colors.red}❌ ${selectorFlags.error}${colors.reset}\n`);
|
|
563
|
+
process.exit(1);
|
|
564
|
+
}
|
|
565
|
+
if (options.rerunFailed) {
|
|
566
|
+
const lastRunPath = resolve(rootDir, ".glubean", "last-run.result.json");
|
|
567
|
+
let lastRun;
|
|
568
|
+
try {
|
|
569
|
+
lastRun = JSON.parse(await readFile(lastRunPath, "utf-8"));
|
|
570
|
+
}
|
|
571
|
+
catch {
|
|
572
|
+
console.error(`\n${colors.red}❌ No previous run found. Run \`glubean run\` first.${colors.reset}\n` +
|
|
573
|
+
`${colors.dim}--rerun-failed reads ${lastRunPath}.${colors.reset}\n`);
|
|
574
|
+
process.exit(1);
|
|
575
|
+
}
|
|
576
|
+
const { selectors, files } = deriveRerunSelectors({ tests: lastRun.tests ?? [] });
|
|
577
|
+
if (selectors.length === 0) {
|
|
578
|
+
console.log(`\n${colors.green}✓ Last run had no failures — nothing to rerun.${colors.reset}\n`);
|
|
579
|
+
process.exit(0);
|
|
580
|
+
}
|
|
581
|
+
// Narrow discovery to the files that contained a failure. `files` were
|
|
582
|
+
// written relative to rootDir (resultPayload.tests.filePath) — rootDir is the
|
|
583
|
+
// stable basis (the dir holding .glubean/), so rerun resolves correctly even
|
|
584
|
+
// when invoked from a different cwd than the original run. Resolve both sides
|
|
585
|
+
// to absolute for an exact match against the discovered testFiles.
|
|
586
|
+
const failedFilesAbs = new Set(files.map((f) => resolve(rootDir, f)));
|
|
587
|
+
testFiles = testFiles.filter((f) => failedFilesAbs.has(resolve(f)));
|
|
588
|
+
isMultiFile = testFiles.length > 1;
|
|
589
|
+
if (testFiles.length === 0) {
|
|
590
|
+
console.error(`\n${colors.red}❌ --rerun-failed: none of the ${failedFilesAbs.size} failed file(s) ` +
|
|
591
|
+
`from the last run are in the current target.${colors.reset}\n`);
|
|
592
|
+
process.exit(1);
|
|
593
|
+
}
|
|
594
|
+
onlySelectors = selectors;
|
|
595
|
+
console.log(`${colors.dim}--rerun-failed: ${selectors.length} failed test(s) across ` +
|
|
596
|
+
`${testFiles.length} file(s)${colors.reset}\n`);
|
|
597
|
+
}
|
|
598
|
+
else {
|
|
599
|
+
onlySelectors = selectorFlags.selectors;
|
|
600
|
+
}
|
|
547
601
|
// Config consolidation (docs/06 P2): the legacy package.json `glubean`
|
|
548
602
|
// flat-shape is no longer read. Profile runs get run/redaction/thresholds
|
|
549
603
|
// from the resolved plan (threaded via `options`); non-profile target runs
|
|
@@ -797,7 +851,7 @@ export async function runCommand(target, options = {}) {
|
|
|
797
851
|
}
|
|
798
852
|
const hasTags = options.tags && options.tags.length > 0;
|
|
799
853
|
const hasExcludeTags = options.excludeTags && options.excludeTags.length > 0;
|
|
800
|
-
|
|
854
|
+
let testsToRun = allFileTests.filter((ft) => {
|
|
801
855
|
const tc = ft.test;
|
|
802
856
|
if (tc.meta.skip)
|
|
803
857
|
return false;
|
|
@@ -811,6 +865,26 @@ export async function runCommand(target, options = {}) {
|
|
|
811
865
|
return false;
|
|
812
866
|
return true;
|
|
813
867
|
});
|
|
868
|
+
// B2 M3 — narrow testsToRun for --only-id / --row. Selector ids are CONCRETE
|
|
869
|
+
// (e.g. `user-0`); a `.each` export appears in discovery under its TEMPLATE id
|
|
870
|
+
// (e.g. `user-$index`). Template-match each selector id against the discovered
|
|
871
|
+
// template ids so concrete selectors reach the right export — the harness then
|
|
872
|
+
// applies the precise per-row filter at runtime. (Rerun narrows by file above.)
|
|
873
|
+
if (onlySelectors.length > 0 && !options.rerunFailed) {
|
|
874
|
+
const selectorIds = Array.from(new Set(onlySelectors.map((s) => (typeof s === "string" ? s : s.id))));
|
|
875
|
+
const indexed = testsToRun.map((ft) => ({ id: ft.test.meta.id, ft }));
|
|
876
|
+
const kept = new Set();
|
|
877
|
+
for (const selId of selectorIds) {
|
|
878
|
+
const match = findTemplateMatch(indexed, selId);
|
|
879
|
+
if (match)
|
|
880
|
+
kept.add(match.ft);
|
|
881
|
+
}
|
|
882
|
+
testsToRun = testsToRun.filter((ft) => kept.has(ft));
|
|
883
|
+
if (testsToRun.length === 0) {
|
|
884
|
+
console.error(`\n${colors.red}❌ No tests match --only-id ${selectorIds.join(", ")}${colors.reset}\n`);
|
|
885
|
+
process.exit(1);
|
|
886
|
+
}
|
|
887
|
+
}
|
|
814
888
|
if (testsToRun.length === 0) {
|
|
815
889
|
if (options.filter || hasTags) {
|
|
816
890
|
const parts = [];
|
|
@@ -952,6 +1026,15 @@ export async function runCommand(target, options = {}) {
|
|
|
952
1026
|
delete process.env["GLUBEAN_RUNNER_BOOTSTRAP_INPUT_MAP"];
|
|
953
1027
|
delete process.env["GLUBEAN_RUNNER_FORCE_STANDALONE_IDS"];
|
|
954
1028
|
}
|
|
1029
|
+
// B2 M3 — hand the resolved selector set to the harness subprocess (it applies
|
|
1030
|
+
// the precise per-row filter at runtime). Clear it otherwise so a stale value
|
|
1031
|
+
// never leaks across in-process invocations (parity with the input maps above).
|
|
1032
|
+
if (onlySelectors.length > 0) {
|
|
1033
|
+
process.env["GLUBEAN_RUNNER_ONLY_SELECTORS"] = JSON.stringify(onlySelectors);
|
|
1034
|
+
}
|
|
1035
|
+
else {
|
|
1036
|
+
delete process.env["GLUBEAN_RUNNER_ONLY_SELECTORS"];
|
|
1037
|
+
}
|
|
955
1038
|
if (options.pick) {
|
|
956
1039
|
process.env.GLUBEAN_PICK = options.pick;
|
|
957
1040
|
console.log(`${colors.dim} pick: ${options.pick}${colors.reset}`);
|
|
@@ -1024,6 +1107,7 @@ export async function runCommand(target, options = {}) {
|
|
|
1024
1107
|
let currentTestItems;
|
|
1025
1108
|
let testId = "";
|
|
1026
1109
|
let testName = "";
|
|
1110
|
+
let testRowIndex = undefined;
|
|
1027
1111
|
let testItem = null;
|
|
1028
1112
|
let startTime = Date.now();
|
|
1029
1113
|
let testEvents = [];
|
|
@@ -1089,6 +1173,7 @@ export async function runCommand(target, options = {}) {
|
|
|
1089
1173
|
success: skippedClean ? true : finalSuccess,
|
|
1090
1174
|
durationMs: duration,
|
|
1091
1175
|
groupId: testItem?.meta.groupId,
|
|
1176
|
+
rowIndex: testRowIndex,
|
|
1092
1177
|
});
|
|
1093
1178
|
addLogEntry("result", skippedClean ? "SKIPPED" : finalSuccess ? "PASSED" : "FAILED", {
|
|
1094
1179
|
duration,
|
|
@@ -1406,6 +1491,10 @@ export async function runCommand(target, options = {}) {
|
|
|
1406
1491
|
(currentTestItems ? findFileTestByRuntimeId(currentTestItems, event.id) : undefined);
|
|
1407
1492
|
testId = event.id;
|
|
1408
1493
|
testName = entry?.test.meta.name || event.name || event.id;
|
|
1494
|
+
// rowIndex (B2 M3): the runtime start event is authoritative for the
|
|
1495
|
+
// per-row `.each` index (static discovery only sees the template id,
|
|
1496
|
+
// so it carries no rowIndex). undefined for non-each tests.
|
|
1497
|
+
testRowIndex = event.rowIndex;
|
|
1409
1498
|
testItem = entry?.test || null;
|
|
1410
1499
|
startTime = Date.now();
|
|
1411
1500
|
testEvents = [];
|
|
@@ -1871,6 +1960,12 @@ export async function runCommand(target, options = {}) {
|
|
|
1871
1960
|
success: r.success,
|
|
1872
1961
|
durationMs: r.durationMs,
|
|
1873
1962
|
events: r.events,
|
|
1963
|
+
// B2 M3 — persist rowIndex + filePath so `--rerun-failed` can reconstruct
|
|
1964
|
+
// the failed `{id, rowIndex}` selector set and narrow to the failed files.
|
|
1965
|
+
// filePath is relative to rootDir (the stable .glubean/ basis), NOT cwd, so
|
|
1966
|
+
// rerun resolves correctly when run from a different working directory.
|
|
1967
|
+
...(r.rowIndex !== undefined && { rowIndex: r.rowIndex }),
|
|
1968
|
+
filePath: relative(rootDir, r.filePath),
|
|
1874
1969
|
})),
|
|
1875
1970
|
...(thresholdSummary && { thresholds: thresholdSummary }),
|
|
1876
1971
|
...(options.meta && Object.keys(options.meta).length > 0 && { customMetadata: options.meta }),
|
|
@@ -1924,29 +2019,30 @@ export async function runCommand(target, options = {}) {
|
|
|
1924
2019
|
}
|
|
1925
2020
|
}
|
|
1926
2021
|
// ── Screenshot paths ──────────────────────────────────────────────────
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
2022
|
+
// This run's exact screenshot files, pulled from the `browser:screenshot`
|
|
2023
|
+
// event stream. Doubles as the upload whitelist below so `--upload` attaches
|
|
2024
|
+
// only THIS run's screenshots, not every file in the shared dir (ART1).
|
|
2025
|
+
const screenshotPaths = [];
|
|
2026
|
+
for (const run of collectedRuns) {
|
|
2027
|
+
for (const event of run.events) {
|
|
2028
|
+
if (event.type !== "event")
|
|
2029
|
+
continue;
|
|
2030
|
+
const ev = event.data;
|
|
2031
|
+
if (ev.type === "browser:screenshot" && typeof ev.data?.path === "string") {
|
|
2032
|
+
screenshotPaths.push(resolve(rootDir, ev.data.path));
|
|
1937
2033
|
}
|
|
1938
2034
|
}
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
}
|
|
1943
|
-
console.log();
|
|
2035
|
+
}
|
|
2036
|
+
if (screenshotPaths.length > 0) {
|
|
2037
|
+
for (const p of screenshotPaths) {
|
|
2038
|
+
console.log(`${colors.dim}Screenshot: ${colors.reset}${p}`);
|
|
1944
2039
|
}
|
|
2040
|
+
console.log();
|
|
1945
2041
|
}
|
|
1946
2042
|
// ── Cloud upload ────────────────────────────────────────────────────────
|
|
1947
2043
|
if (options.upload) {
|
|
1948
2044
|
const { resolveToken, resolveProjectId, resolveApiUrl } = await import("../lib/auth.js");
|
|
1949
|
-
const { uploadToCloud } = await import("../lib/upload.js");
|
|
2045
|
+
const { uploadToCloud, removeUploadedScreenshots } = await import("../lib/upload.js");
|
|
1950
2046
|
const authOpts = {
|
|
1951
2047
|
token: options.token,
|
|
1952
2048
|
project: options.project,
|
|
@@ -2096,7 +2192,27 @@ export async function runCommand(target, options = {}) {
|
|
|
2096
2192
|
targetId,
|
|
2097
2193
|
envFile: effectiveRun.envFile,
|
|
2098
2194
|
rootDir,
|
|
2195
|
+
// Upload only THIS run's screenshots (whitelist), not the whole
|
|
2196
|
+
// shared `.glubean/screenshots` dir which accumulates prior runs.
|
|
2197
|
+
screenshotPaths,
|
|
2099
2198
|
});
|
|
2199
|
+
// ART1-B — the shared screenshots dir only ever grows, so once the
|
|
2200
|
+
// Cloud confirmed it received this run's screenshots, delete the local
|
|
2201
|
+
// copies. Deletes ONLY uploadedFiles ∩ screenshotPaths (both server-
|
|
2202
|
+
// confirmed and provably this run's), realpath-contained to
|
|
2203
|
+
// `.glubean/screenshots`, and only while the on-disk file still has
|
|
2204
|
+
// its upload-time stat identity (re-checked just before each unlink)
|
|
2205
|
+
// — a failed/partial upload keeps its files, and a concurrently
|
|
2206
|
+
// recreated path is kept.
|
|
2207
|
+
if (!options.keepLocal &&
|
|
2208
|
+
uploadReceipt.artifactUpload.status === "uploaded" &&
|
|
2209
|
+
uploadReceipt.artifactUpload.uploadedFiles?.length &&
|
|
2210
|
+
screenshotPaths.length > 0) {
|
|
2211
|
+
const { removed } = await removeUploadedScreenshots(rootDir, screenshotPaths, uploadReceipt.artifactUpload.uploadedFiles);
|
|
2212
|
+
if (removed > 0) {
|
|
2213
|
+
console.log(`${colors.dim}Cleaned up ${removed} uploaded screenshot(s) from .glubean/screenshots (use --keep-local to keep them)${colors.reset}`);
|
|
2214
|
+
}
|
|
2215
|
+
}
|
|
2100
2216
|
if (options.uploadReceiptJson) {
|
|
2101
2217
|
const receiptPath = resolveOutputPath(options.uploadReceiptJson, process.cwd());
|
|
2102
2218
|
await mkdir(dirname(receiptPath), { recursive: true });
|