sverka 0.2.15 → 0.2.16
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/index.d.mts.map +1 -1
- package/dist/index.mjs +103 -25
- package/dist/index.mjs.map +1 -1
- package/package.json +10 -10
package/dist/index.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/types.ts","../src/main.ts","../src/output.ts"],"mappings":";;iBAGiB;;EAEf;;EAEA;;EAEA;;EAEA;;EAEA;;;iBAIe;EACf,MAAM;EACN,UAAU;EACV,MAAM;EACN,UAAU;;EAEV,MAAM;;;qBAIK;;;;;;YAOD,mBAAmB,uBAAuB;;YAG1C;;qBAWC,iBAAiB;WACnB,MAAM;WACN,UAAU;WACD;EAEhB,YAAA,iBACA,MAAM,cACN,UAAU,UACV;;;;;UChCa;EACf,SAAS;;;;;;;
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/types.ts","../src/main.ts","../src/output.ts"],"mappings":";;iBAGiB;;EAEf;;EAEA;;EAEA;;EAEA;;EAEA;;;iBAIe;EACf,MAAM;EACN,UAAU;EACV,MAAM;EACN,UAAU;;EAEV,MAAM;;;qBAIK;;;;;;YAOD,mBAAmB,uBAAuB;;YAG1C;;qBAWC,iBAAiB;WACnB,MAAM;WACN,UAAU;WACD;EAEhB,YAAA,iBACA,MAAM,cACN,UAAU,UACV;;;;;UChCa;EACf,SAAS;;;;;;;wBAuZW,KAAK,gBAAgB,OAAO,WAAW;;;;KC7ajD,aAAa;;;;;;qBAOZ,+BAA+B;;EAM9B,YAAA,KAAK,WAAW,KAAK,WAAW,iBAAe;EAO3D,MAAM;EAKN,UAAU;EAKV,MAAM;EAIN,UAAU;;EAKV,MAAM;;;;;;wBAwDQ,mBACd,QAAQ,aACR,KAAK,WACL,KAAK,YACJ;;;;;wBAYa,iBACd,QAAQ,aACR,MAAM,eACL"}
|
package/dist/index.mjs
CHANGED
|
@@ -7,7 +7,7 @@ import { constants, existsSync, mkdirSync, readFileSync, readdirSync, realpathSy
|
|
|
7
7
|
import { dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
8
8
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
9
9
|
import { mkdir, open, readFile, realpath, writeFile } from "node:fs/promises";
|
|
10
|
-
import { collectConstructWarnings, synthesize, validateGraph } from "@sverka/workflow";
|
|
10
|
+
import { Entry, Pipeline, Project, ShellStep, collectConstructWarnings, manual, synthesize, validateGraph } from "@sverka/workflow";
|
|
11
11
|
import { bindRunPlan, createPlanner } from "@sverka/sdk";
|
|
12
12
|
import { DEFAULT_POLICY, createBuiltinResolver, evaluatePolicy, loadBaseline, normalizeSarif, serializeSarif, synthesizeCheckSteps } from "@sverka/verification";
|
|
13
13
|
import { createDockerDriver, createEngine, createHostDriver } from "@sverka/runtime";
|
|
@@ -720,6 +720,48 @@ async function graphCommand(global, output, start) {
|
|
|
720
720
|
return ExitCode.Success;
|
|
721
721
|
}
|
|
722
722
|
//#endregion
|
|
723
|
+
//#region src/internal/implicit.ts
|
|
724
|
+
/** Load the graph `sverka run` should execute: the real config when one
|
|
725
|
+
* exists on disk (or --config names one), else the implicit detected
|
|
726
|
+
* pipeline. */
|
|
727
|
+
async function loadRunGraph(global) {
|
|
728
|
+
if (global.config === null && await findConfig(global.root) === null) return implicitGraph(global.root);
|
|
729
|
+
const { graph, warnings } = await loadProjectGraph(global);
|
|
730
|
+
return {
|
|
731
|
+
graph,
|
|
732
|
+
warnings
|
|
733
|
+
};
|
|
734
|
+
}
|
|
735
|
+
async function implicitGraph(root) {
|
|
736
|
+
const checks = await detectProjectChecks(root);
|
|
737
|
+
if (checks.length === 0) throw new CliError("no config found and no checks detected (run `sverka init` to create one)", "MISSING_ARG", ExitCode.UsageError);
|
|
738
|
+
const project = new Project("default");
|
|
739
|
+
const pipeline = new Pipeline(project, "default");
|
|
740
|
+
const steps = checks.map((check) => new ShellStep(pipeline, check.checkId, shellStepProps(check)));
|
|
741
|
+
const entry = new Entry(pipeline, "run", {
|
|
742
|
+
trigger: manual(),
|
|
743
|
+
roots: steps.map((step) => step.node.id)
|
|
744
|
+
});
|
|
745
|
+
return {
|
|
746
|
+
graph: synthesize(project),
|
|
747
|
+
warnings: collectConstructWarnings(project),
|
|
748
|
+
detected: entry.roots
|
|
749
|
+
};
|
|
750
|
+
}
|
|
751
|
+
/** Detected check → ShellStep props. A SARIF-on-stdout check keeps the
|
|
752
|
+
* `fromStdout` artifact declaration `init --detect` generates, so the
|
|
753
|
+
* implicit run feeds findings collection the same way. */
|
|
754
|
+
function shellStepProps(check) {
|
|
755
|
+
return {
|
|
756
|
+
command: check.command,
|
|
757
|
+
runtime: { mode: "host" },
|
|
758
|
+
...check.sarifOutput !== void 0 ? { outputs: { [check.sarifOutput]: {
|
|
759
|
+
type: "artifact",
|
|
760
|
+
fromStdout: true
|
|
761
|
+
} } } : {}
|
|
762
|
+
};
|
|
763
|
+
}
|
|
764
|
+
//#endregion
|
|
723
765
|
//#region src/internal/runtime-check.ts
|
|
724
766
|
/**
|
|
725
767
|
* Check if a binary is available on PATH by spawning `<binary> --version`.
|
|
@@ -922,7 +964,8 @@ async function runCommand(args, global, output, start) {
|
|
|
922
964
|
const fmt = resolveFormats(args, global);
|
|
923
965
|
output.debug(`run: root=${global.root} executor=${executor} entry=${args.entryId ?? "(first)"} format=${global.format}`);
|
|
924
966
|
assertExecutorAvailable(executor);
|
|
925
|
-
const { graph, warnings } = await
|
|
967
|
+
const { graph, warnings, detected } = await loadRunGraph(global);
|
|
968
|
+
if (detected !== void 0) output.errorLine(`no sverka.config — running ${detected.length} detected checks (${detected.join(", ")})`);
|
|
926
969
|
for (const warning of warnings) output.errorLine(`warning: ${warning}`);
|
|
927
970
|
const entryId = resolveEntryId(graph, args.entryId);
|
|
928
971
|
const plan = bindRunPlan({
|
|
@@ -947,13 +990,11 @@ async function runCommand(args, global, output, start) {
|
|
|
947
990
|
durationMs,
|
|
948
991
|
evalResult: evaluation.summary,
|
|
949
992
|
artifactDir,
|
|
950
|
-
sinceMs: start
|
|
993
|
+
sinceMs: start,
|
|
994
|
+
...detected !== void 0 ? { detected } : {},
|
|
995
|
+
...args.report !== void 0 ? { report: args.report } : {}
|
|
951
996
|
}, events, output);
|
|
952
|
-
|
|
953
|
-
const reportPath = args.output ?? join(global.root, ".sverka", "report.html");
|
|
954
|
-
output.writeLine(`Wrote HTML report to ${reportPath}`);
|
|
955
|
-
}
|
|
956
|
-
if (renderer && "waitUntilExit" in renderer) await renderer.waitUntilExit();
|
|
997
|
+
await finalizeRenderer(fmt, renderer, args, global, output);
|
|
957
998
|
if (evaluation.collectionFailed) return evaluation.exitCode;
|
|
958
999
|
writeRunOutput({
|
|
959
1000
|
planId: plan.id,
|
|
@@ -963,12 +1004,36 @@ async function runCommand(args, global, output, start) {
|
|
|
963
1004
|
global,
|
|
964
1005
|
output,
|
|
965
1006
|
evalResult: evaluation.summary,
|
|
966
|
-
report
|
|
1007
|
+
report,
|
|
1008
|
+
...detected !== void 0 ? { detected } : {}
|
|
967
1009
|
});
|
|
968
|
-
|
|
1010
|
+
printReportTail(report, args.report, global, output);
|
|
969
1011
|
if (evaluation.exitCode !== 0) return evaluation.exitCode;
|
|
970
1012
|
return exitCodeForStatus(runStatus);
|
|
971
1013
|
}
|
|
1014
|
+
/** Post-run: announce the HTML report path (sarif/web print their own),
|
|
1015
|
+
* then keep interactive renderers mounted until the user quits. */
|
|
1016
|
+
async function finalizeRenderer(fmt, renderer, args, global, output) {
|
|
1017
|
+
if (fmt.isHtml && renderer) {
|
|
1018
|
+
const reportPath = args.output ?? join(global.root, ".sverka", "report.html");
|
|
1019
|
+
output.writeLine(`Wrote HTML report to ${reportPath}`);
|
|
1020
|
+
}
|
|
1021
|
+
if (renderer && "waitUntilExit" in renderer) await renderer.waitUntilExit();
|
|
1022
|
+
}
|
|
1023
|
+
/** Human-mode tail (Spec 53): findings summary, then the report location —
|
|
1024
|
+
* the report is discoverable, not hidden. The `sverka view` hint applies
|
|
1025
|
+
* only when report.html sits at the default path — view resolves
|
|
1026
|
+
* .sverka/runs/<latest>/report.html, not a --report relocation. */
|
|
1027
|
+
function printReportTail(report, reportFlag, global, output) {
|
|
1028
|
+
if (report === void 0 || global.format !== "text") return;
|
|
1029
|
+
output.writeLine(` findings: ${report.findings}`);
|
|
1030
|
+
if (report.html === null) {
|
|
1031
|
+
output.writeLine(` report: ${report.json}`);
|
|
1032
|
+
return;
|
|
1033
|
+
}
|
|
1034
|
+
const hint = reportFlag !== void 0 ? "(open in a browser)" : "(sverka view to open)";
|
|
1035
|
+
output.writeLine(` report: ${report.html} ${hint}`);
|
|
1036
|
+
}
|
|
972
1037
|
/** Derive output modes — `--format html`, or `--output` without
|
|
973
1038
|
* sarif/web, implies HTML; evaluation runs for any structured format. */
|
|
974
1039
|
function resolveFormats(args, global) {
|
|
@@ -1018,11 +1083,14 @@ async function writeReportSafe(ctx, events, output) {
|
|
|
1018
1083
|
durationMs: ctx.durationMs,
|
|
1019
1084
|
evalResult: ctx.evalResult,
|
|
1020
1085
|
artifactDir: ctx.artifactDir,
|
|
1021
|
-
sinceMs: ctx.sinceMs
|
|
1086
|
+
sinceMs: ctx.sinceMs,
|
|
1087
|
+
...ctx.detected !== void 0 ? { detected: ctx.detected } : {},
|
|
1088
|
+
...ctx.report !== void 0 ? { reportPath: ctx.report } : {}
|
|
1022
1089
|
});
|
|
1023
1090
|
return {
|
|
1024
1091
|
html: artifacts.htmlPath,
|
|
1025
|
-
json: join(artifacts.dir, "report.json")
|
|
1092
|
+
json: join(artifacts.dir, "report.json"),
|
|
1093
|
+
findings: artifacts.findingsCount
|
|
1026
1094
|
};
|
|
1027
1095
|
} catch (err) {
|
|
1028
1096
|
if (err instanceof CliError) throw err;
|
|
@@ -1049,9 +1117,10 @@ async function runWatch(args, global, output) {
|
|
|
1049
1117
|
const configPath = global.config === null ? null : resolve(global.root, global.config);
|
|
1050
1118
|
const extraPaths = configPath !== null && !configPath.startsWith(`${resolve(global.root)}/`) ? [configPath] : [];
|
|
1051
1119
|
const ignored = [];
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1120
|
+
for (const file of [args.output, args.report]) {
|
|
1121
|
+
if (file === void 0) continue;
|
|
1122
|
+
const abs = resolve(global.root, file);
|
|
1123
|
+
if (abs.startsWith(`${resolve(global.root)}/`)) ignored.push(abs);
|
|
1055
1124
|
}
|
|
1056
1125
|
return await watchLoop({
|
|
1057
1126
|
root: global.root,
|
|
@@ -1259,6 +1328,7 @@ function writeRunOutput(opts) {
|
|
|
1259
1328
|
planId,
|
|
1260
1329
|
status: runStatus,
|
|
1261
1330
|
steps,
|
|
1331
|
+
...opts.detected !== void 0 ? { detected: opts.detected } : {},
|
|
1262
1332
|
...evalResult ? {
|
|
1263
1333
|
findings: evalResult.findings.length,
|
|
1264
1334
|
verdict: evalResult.verdict,
|
|
@@ -1266,7 +1336,8 @@ function writeRunOutput(opts) {
|
|
|
1266
1336
|
} : {},
|
|
1267
1337
|
...report !== void 0 ? { report: {
|
|
1268
1338
|
json: report.json,
|
|
1269
|
-
...report.html !== null ? { html: report.html } : {}
|
|
1339
|
+
...report.html !== null ? { html: report.html } : {},
|
|
1340
|
+
findings: report.findings
|
|
1270
1341
|
} } : {}
|
|
1271
1342
|
},
|
|
1272
1343
|
durationMs
|
|
@@ -1341,26 +1412,27 @@ async function collectRunFindings(opts, warnings) {
|
|
|
1341
1412
|
}
|
|
1342
1413
|
/** Best-effort report.html — a render failure never fails the run; it is
|
|
1343
1414
|
* recorded in the report's `warnings` instead of being dropped silently. */
|
|
1344
|
-
async function writeReportHtml(
|
|
1415
|
+
async function writeReportHtml(htmlPath, findings, warnings) {
|
|
1345
1416
|
try {
|
|
1346
1417
|
const { generateSarifHtml } = await import("@sverka/sarif-viewer-web");
|
|
1347
|
-
|
|
1348
|
-
writeFileSync(
|
|
1349
|
-
return
|
|
1418
|
+
mkdirSync(dirname(htmlPath), { recursive: true });
|
|
1419
|
+
writeFileSync(htmlPath, generateSarifHtml(findings), "utf-8");
|
|
1420
|
+
return htmlPath;
|
|
1350
1421
|
} catch (e) {
|
|
1351
1422
|
warnings.push(`report.html generation failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
1352
1423
|
return null;
|
|
1353
1424
|
}
|
|
1354
1425
|
}
|
|
1355
|
-
function runReportPayload(opts, warnings) {
|
|
1426
|
+
function runReportPayload(opts, findingsCount, warnings) {
|
|
1356
1427
|
return {
|
|
1357
1428
|
schema: "sverka.run/v1",
|
|
1358
1429
|
data: {
|
|
1359
1430
|
planId: opts.planId,
|
|
1360
1431
|
status: opts.runStatus,
|
|
1361
1432
|
steps: boundStepOutput(summarizeSteps(opts.events)),
|
|
1433
|
+
...opts.detected !== void 0 ? { detected: opts.detected } : {},
|
|
1434
|
+
findings: findingsCount,
|
|
1362
1435
|
...opts.evalResult ? {
|
|
1363
|
-
findings: opts.evalResult.findings.length,
|
|
1364
1436
|
verdict: opts.evalResult.verdict,
|
|
1365
1437
|
summary: opts.evalResult.summary
|
|
1366
1438
|
} : {},
|
|
@@ -1375,11 +1447,13 @@ function runReportPayload(opts, warnings) {
|
|
|
1375
1447
|
async function writeRunArtifacts(opts) {
|
|
1376
1448
|
const dir = reportDir(opts.root, opts.runId);
|
|
1377
1449
|
const warnings = [];
|
|
1378
|
-
const
|
|
1379
|
-
|
|
1450
|
+
const findings = await collectRunFindings(opts, warnings);
|
|
1451
|
+
const htmlPath = await writeReportHtml(opts.reportPath !== void 0 ? resolveUnderRoot(opts.root, opts.reportPath) : join(dir, "report.html"), findings, warnings);
|
|
1452
|
+
writeFileSync(join(dir, "report.json"), JSON.stringify(runReportPayload(opts, findings.length, warnings), null, 2), "utf-8");
|
|
1380
1453
|
return {
|
|
1381
1454
|
dir,
|
|
1382
|
-
htmlPath
|
|
1455
|
+
htmlPath,
|
|
1456
|
+
findingsCount: findings.length
|
|
1383
1457
|
};
|
|
1384
1458
|
}
|
|
1385
1459
|
function exitCodeForStatus(runStatus) {
|
|
@@ -2228,6 +2302,9 @@ function addRunCommand(y) {
|
|
|
2228
2302
|
type: "string",
|
|
2229
2303
|
alias: "o",
|
|
2230
2304
|
describe: "Output file path for HTML report (implies --format html)"
|
|
2305
|
+
}).option("report", {
|
|
2306
|
+
type: "string",
|
|
2307
|
+
describe: "Relocate the per-run HTML report (report.json stays under .sverka/runs/)"
|
|
2231
2308
|
}).option("tui", {
|
|
2232
2309
|
type: "boolean",
|
|
2233
2310
|
describe: "Interactive terminal UI (off by default; opt-in only)"
|
|
@@ -2395,6 +2472,7 @@ function dispatchRun(parsed, global, output, start) {
|
|
|
2395
2472
|
}
|
|
2396
2473
|
if (typeof parsed.entry === "string") args.entryId = parsed.entry;
|
|
2397
2474
|
if (typeof parsed.output === "string") args.output = parsed.output;
|
|
2475
|
+
if (typeof parsed.report === "string") args.report = parsed.report;
|
|
2398
2476
|
if (parsed.tui === true) args.tui = true;
|
|
2399
2477
|
if (parsed.tui === false) args.tui = false;
|
|
2400
2478
|
if (parsed.watch === true) args.watch = true;
|