@sdods/cli 0.21.0 → 0.22.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/dist/.tsbuildinfo +1 -1
- package/dist/commands/report.js +34 -19
- package/dist/commands/steps.d.ts +5 -0
- package/dist/commands/steps.js +12 -0
- package/package.json +10 -10
package/dist/commands/report.js
CHANGED
|
@@ -3,7 +3,7 @@ import { tmpdir } from 'node:os';
|
|
|
3
3
|
import { basename, dirname, extname, join, resolve } from 'node:path';
|
|
4
4
|
import { execa } from 'execa';
|
|
5
5
|
import pc from 'picocolors';
|
|
6
|
-
import { legacyRunFiles, runFiles, } from '@sdods/contracts';
|
|
6
|
+
import { legacyRunFiles, parseVersioned, runFiles, } from '@sdods/contracts';
|
|
7
7
|
import { SdodsError, collectGateEvidence, evaluateGates, gateReportsFromRunnerJson, gateTotalsFromRunnerStats, hasGates, ingestGateJudge, } from '@sdods/core';
|
|
8
8
|
import { createContext } from '../context.js';
|
|
9
9
|
import { gateFailedError, printGates } from '../gates.js';
|
|
@@ -168,11 +168,12 @@ export function register(program) {
|
|
|
168
168
|
});
|
|
169
169
|
report
|
|
170
170
|
.command('ingest [files...]')
|
|
171
|
-
.description('Ingest cucumber messages NDJSON
|
|
171
|
+
.description('Ingest cucumber messages NDJSON, runner JSON and JUnit XML results into the database')
|
|
172
172
|
.requiredOption('--run-id <id>', 'run id (directory name under the artifacts root)')
|
|
173
173
|
.option('-p, --project <slug>', 'project slug when run.json is missing')
|
|
174
174
|
.option('--manifest <file>', 'explicit run.json path')
|
|
175
|
-
.option('--format <fmt>', 'auto|cucumber|runner-json (alias: pw-json)', 'auto')
|
|
175
|
+
.option('--format <fmt>', 'auto|cucumber|runner-json (alias: pw-json)|junit', 'auto')
|
|
176
|
+
.option('--approved-skips <file>', 'approved-skip registry (approved-skips/v1); a skip it does not approve makes the run INFRASTRUCTURE_ERROR')
|
|
176
177
|
.option('--artifacts-dir <dir>', 'artifacts root (default .sdods/runs)')
|
|
177
178
|
.option('--replace', 'delete previously ingested rows of this run first')
|
|
178
179
|
.option('-e, --env <name>', 'environment name when run.json is missing')
|
|
@@ -192,13 +193,18 @@ export function register(program) {
|
|
|
192
193
|
const { basename } = await import('node:path');
|
|
193
194
|
const form = new FormData();
|
|
194
195
|
let count = 0;
|
|
195
|
-
for (const f of files) {
|
|
196
|
+
for (const [i, f] of files.entries()) {
|
|
196
197
|
const abs = resolve(ctx.rootDir, f);
|
|
197
198
|
if (!existsSync(abs))
|
|
198
199
|
continue;
|
|
199
|
-
|
|
200
|
+
// The server accepts result files by name; a JUnit file goes up under the name it reads.
|
|
201
|
+
const junit = ingestFormat(opts.format, abs) === 'junit' &&
|
|
202
|
+
!/^junit(\.shard-\d+)?\.xml$/.test(basename(abs));
|
|
203
|
+
form.append('files', new Blob([readFileSync(abs)]), junit ? `junit.shard-${i}.xml` : basename(abs));
|
|
200
204
|
count++;
|
|
201
205
|
}
|
|
206
|
+
if (opts.approvedSkips)
|
|
207
|
+
form.append('files', new Blob([readFileSync(resolve(ctx.rootDir, opts.approvedSkips))]), 'approved-skips.json');
|
|
202
208
|
if (!count)
|
|
203
209
|
throw new SdodsError('CONFIG_INVALID', 'No existing files to upload.', { exitCode: 2 });
|
|
204
210
|
const url = `${serverUrl.replace(/\/$/, '')}/api/runs/${encodeURIComponent(opts.runId)}/ingest`;
|
|
@@ -223,19 +229,15 @@ export function register(program) {
|
|
|
223
229
|
const root = opts.artifactsDir
|
|
224
230
|
? resolve(ctx.rootDir, opts.artifactsDir)
|
|
225
231
|
: artifactsRoot(ctx.rootDir);
|
|
226
|
-
const
|
|
227
|
-
const runnerJson = [];
|
|
232
|
+
const paths = { cucumber: [], 'runner-json': [], junit: [] };
|
|
228
233
|
for (const f of files) {
|
|
229
234
|
const abs = resolve(ctx.rootDir, f);
|
|
230
|
-
const fmt = opts.format
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
:
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
? 'runner-json'
|
|
237
|
-
: opts.format;
|
|
238
|
-
(fmt === 'cucumber' ? ndjson : runnerJson).push(abs);
|
|
235
|
+
const fmt = ingestFormat(opts.format, abs);
|
|
236
|
+
if (!paths[fmt])
|
|
237
|
+
throw new SdodsError('CONFIG_INVALID', `Unknown --format "${opts.format}".`, {
|
|
238
|
+
exitCode: 2,
|
|
239
|
+
});
|
|
240
|
+
paths[fmt].push(abs);
|
|
239
241
|
}
|
|
240
242
|
const manifest = opts.manifest
|
|
241
243
|
? readJsonFile(resolve(ctx.rootDir, opts.manifest))
|
|
@@ -244,8 +246,12 @@ export function register(program) {
|
|
|
244
246
|
runId: opts.runId,
|
|
245
247
|
projectSlug: opts.project,
|
|
246
248
|
manifest,
|
|
247
|
-
ndjsonPaths:
|
|
248
|
-
runnerJsonPaths:
|
|
249
|
+
ndjsonPaths: paths.cucumber,
|
|
250
|
+
runnerJsonPaths: paths['runner-json'],
|
|
251
|
+
junitPaths: paths.junit,
|
|
252
|
+
approvedSkips: opts.approvedSkips
|
|
253
|
+
? parseVersioned('approved-skips', readJsonFile(resolve(ctx.rootDir, opts.approvedSkips))).skips
|
|
254
|
+
: undefined,
|
|
249
255
|
artifactsRoot: root,
|
|
250
256
|
replace: Boolean(opts.replace),
|
|
251
257
|
env: opts.env,
|
|
@@ -260,7 +266,9 @@ export function register(program) {
|
|
|
260
266
|
return json(res);
|
|
261
267
|
ok(`Ingested run ${res.runId} (${res.projectSlug}): ${res.scenarios} scenario(s), ${res.attempts} attempt(s), ${res.steps} step(s), ${res.artifacts} artifact(s), ${res.healEvents} heal event(s)`);
|
|
262
268
|
const t = res.totals;
|
|
263
|
-
out(`${pc.green(`${t.passed} passed`)} ${pc.red(`${t.failed} failed`)} ${pc.yellow(`${t.flaky} flaky`)} ${pc.dim(`${t.skipped} skipped`)} → ${res.status}`);
|
|
269
|
+
out(`${pc.green(`${t.passed} passed`)} ${pc.red(`${t.failed} failed`)} ${pc.yellow(`${t.flaky} flaky`)} ${pc.dim(`${t.skipped} skipped`)} → ${res.status} (${res.outcome})`);
|
|
270
|
+
for (const r of res.outcomeReasons)
|
|
271
|
+
out(pc.yellow(` ${r.code}${r.test ? ` · ${r.test}` : ''}${r.detail ? `: ${r.detail}` : ''}`));
|
|
264
272
|
if (res.gates)
|
|
265
273
|
out(`gates · process ${res.gates.process}: ${res.gates.passed
|
|
266
274
|
? pc.green('passed')
|
|
@@ -478,4 +486,11 @@ export function register(program) {
|
|
|
478
486
|
await execa(file, argv, { stdio: 'inherit', cwd: ctx.rootDir });
|
|
479
487
|
});
|
|
480
488
|
}
|
|
489
|
+
/** The format of one `report ingest` file: `--format`, or its extension under `auto`. */
|
|
490
|
+
function ingestFormat(format, file) {
|
|
491
|
+
if (format === 'auto')
|
|
492
|
+
return file.endsWith('.ndjson') ? 'cucumber' : file.endsWith('.xml') ? 'junit' : 'runner-json';
|
|
493
|
+
// `pw-json` is the pre-1.0 name, still accepted
|
|
494
|
+
return format === 'pw-json' ? 'runner-json' : format;
|
|
495
|
+
}
|
|
481
496
|
//# sourceMappingURL=report.js.map
|
package/dist/commands/steps.d.ts
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
import type { Command } from 'commander';
|
|
2
2
|
import type { StepDef } from '@sdods/contracts';
|
|
3
|
+
/**
|
|
4
|
+
* The line of a failed `bddgen export` that says why, so the error names the cause rather than
|
|
5
|
+
* only "could not list" (SDO-71): the first `Error:` line of a stack, else the last line printed.
|
|
6
|
+
*/
|
|
7
|
+
export declare function failureReason(output: string): string | undefined;
|
|
3
8
|
/** Parse `bddgen export` output (`* Given ...` lines) into StepDef[]. */
|
|
4
9
|
export declare function listSteps(rootDir: string, slug: string, opts?: {
|
|
5
10
|
unused?: boolean;
|
package/dist/commands/steps.js
CHANGED
|
@@ -4,6 +4,17 @@ import { SdodsError, coreStepsDir } from '@sdods/core';
|
|
|
4
4
|
import { createContext } from '../context.js';
|
|
5
5
|
import { json, table } from '../ui.js';
|
|
6
6
|
import { toolCommand } from '../workspace-bin.js';
|
|
7
|
+
/**
|
|
8
|
+
* The line of a failed `bddgen export` that says why, so the error names the cause rather than
|
|
9
|
+
* only "could not list" (SDO-71): the first `Error:` line of a stack, else the last line printed.
|
|
10
|
+
*/
|
|
11
|
+
export function failureReason(output) {
|
|
12
|
+
const lines = output
|
|
13
|
+
.split('\n')
|
|
14
|
+
.map((l) => l.trim())
|
|
15
|
+
.filter(Boolean);
|
|
16
|
+
return lines.find((l) => /^\w*Error\b/.test(l)) ?? lines.at(-1);
|
|
17
|
+
}
|
|
7
18
|
/** Parse `bddgen export` output (`* Given ...` lines) into StepDef[]. */
|
|
8
19
|
export async function listSteps(rootDir, slug, opts = {}) {
|
|
9
20
|
const env = {
|
|
@@ -19,6 +30,7 @@ export async function listSteps(rootDir, slug, opts = {}) {
|
|
|
19
30
|
const result = await execa(file, argv, { cwd: rootDir, env, reject: false, all: true });
|
|
20
31
|
if (result.exitCode !== 0) {
|
|
21
32
|
throw new SdodsError('RUN_FAILED', `Could not list the step definitions of ${slug}.`, {
|
|
33
|
+
hint: failureReason(result.all ?? ''),
|
|
22
34
|
details: { output: result.all?.slice(-2000) },
|
|
23
35
|
});
|
|
24
36
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sdods/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.22.0",
|
|
4
4
|
"description": "SDODS command line: run, record, analyze, agents, MCP server, scheduler and the web server.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"funding": "https://sdods.com/sponsor/",
|
|
@@ -26,22 +26,22 @@
|
|
|
26
26
|
"node": ">=22"
|
|
27
27
|
},
|
|
28
28
|
"dependencies": {
|
|
29
|
-
"@sdods/contracts": "0.
|
|
30
|
-
"@sdods/core": "0.
|
|
29
|
+
"@sdods/contracts": "0.22.0",
|
|
30
|
+
"@sdods/core": "0.22.0",
|
|
31
31
|
"commander": "^15.0.0",
|
|
32
32
|
"execa": "^10.2.1",
|
|
33
33
|
"picocolors": "^1.1.1",
|
|
34
34
|
"semver": "^7.8.5",
|
|
35
35
|
"yaml": "^2.9.1",
|
|
36
36
|
"zod": "^4.6.7",
|
|
37
|
-
"@playwright/test": "^1.64.
|
|
38
|
-
"playwright-core": "^1.64.
|
|
39
|
-
"@sdods/integrations": "0.
|
|
40
|
-
"@sdods/mcp": "0.
|
|
41
|
-
"@sdods/agents": "0.
|
|
42
|
-
"@sdods/db": "0.
|
|
37
|
+
"@playwright/test": "^1.64.1",
|
|
38
|
+
"playwright-core": "^1.64.1",
|
|
39
|
+
"@sdods/integrations": "0.22.0",
|
|
40
|
+
"@sdods/mcp": "0.22.0",
|
|
41
|
+
"@sdods/agents": "0.22.0",
|
|
42
|
+
"@sdods/db": "0.22.0",
|
|
43
43
|
"csv-parse": "^7.0.3",
|
|
44
|
-
"@sdods/server": "0.
|
|
44
|
+
"@sdods/server": "0.22.0"
|
|
45
45
|
},
|
|
46
46
|
"homepage": "https://sdods.com",
|
|
47
47
|
"bugs": {
|