veriskit 0.4.1 → 0.5.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/CHANGELOG.md +7 -0
- package/README.md +58 -0
- package/dist/index.js +502 -38
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.5.0 — 2026-07-13
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
- Publish to GitHub. `veris verify --github` posts and updates one sticky PR comment with the verdict and report, and creates a Check Run (verified passes, failed fails, partial is neutral). Token read from `GITHUB_TOKEN`; publishing never changes the verdict or exit code. `veris badge` writes a shields.io endpoint JSON. GitHub API over built-in fetch, no new dependency.
|
|
7
|
+
- Browser tests. A real Playwright runner, opt-in with `veris verify --browser` (or a `browser` entry in `.veris/config.json`). Detected Playwright now shows as an available capability.
|
|
8
|
+
- `veris log` lists past runs from the stored evidence records, and `veris log --flaky` flags checks that both passed and failed across recent runs. Local per-machine history.
|
|
9
|
+
|
|
3
10
|
## 0.4.1 — 2026-07-10
|
|
4
11
|
|
|
5
12
|
### Added
|
package/README.md
CHANGED
|
@@ -237,6 +237,64 @@ VerisKit says what it cannot do as plainly as what it can:
|
|
|
237
237
|
- **Scanner fallback on plain-JS or TS 7.x-native projects.** The accurate resolver needs the classic TypeScript compiler API. Without it you get the labeled, relative-imports-only graph described above, and no dependency is added to paper over the gap.
|
|
238
238
|
- **No keyless or identity-bound signing.** Evidence can be signed with a local Ed25519 key (see Signing), but sigstore-style keyless signing that ties a signature to an identity is not built yet.
|
|
239
239
|
|
|
240
|
+
## Publish to a pull request
|
|
241
|
+
|
|
242
|
+
In CI, surface the verdict where reviewers look. Opt in with `--github`; VerisKit
|
|
243
|
+
reads `GITHUB_TOKEN` from the environment and never stores it.
|
|
244
|
+
|
|
245
|
+
```bash
|
|
246
|
+
veris verify --github # posts/updates a sticky PR comment + a Check Run
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
It edits one comment on re-runs (no spam) and creates a Check Run whose
|
|
250
|
+
conclusion follows the verdict (verified passes, failed fails, partial is
|
|
251
|
+
neutral). Publishing is a side channel: if there is no token or PR, VerisKit
|
|
252
|
+
prints a notice and the exit code still reflects the verdict, and a GitHub API
|
|
253
|
+
error is reported but never changes the result.
|
|
254
|
+
|
|
255
|
+
The workflow needs permission to write them:
|
|
256
|
+
|
|
257
|
+
```yaml
|
|
258
|
+
permissions:
|
|
259
|
+
contents: read
|
|
260
|
+
pull-requests: write
|
|
261
|
+
checks: write
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
For a README badge, write a shields.io endpoint file:
|
|
265
|
+
|
|
266
|
+
```bash
|
|
267
|
+
veris badge # writes .veris/badge.json
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
```markdown
|
|
271
|
+

|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
## Browser tests
|
|
275
|
+
|
|
276
|
+
VerisKit can run your Playwright suite as part of the verdict. It is opt-in, so
|
|
277
|
+
a normal `veris verify` stays fast:
|
|
278
|
+
|
|
279
|
+
```bash
|
|
280
|
+
veris verify --browser # also runs `playwright test`, folded into the verdict
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
When Playwright is detected, `veris doctor` lists `browser` as available. You can
|
|
284
|
+
also add `browser` to the `checks` array in `.veris/config.json` to run it every
|
|
285
|
+
time.
|
|
286
|
+
|
|
287
|
+
## History
|
|
288
|
+
|
|
289
|
+
Every run leaves an evidence record, so VerisKit can show you a trend:
|
|
290
|
+
|
|
291
|
+
```bash
|
|
292
|
+
veris log # past runs, newest first
|
|
293
|
+
veris log --flaky # checks that both passed and failed across recent runs
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
History is local to your machine (the `.veris/runs` directory is gitignored).
|
|
297
|
+
|
|
240
298
|
## Part of Baseframe Labs
|
|
241
299
|
|
|
242
300
|
VerisKit is one of four developer tools from [Baseframe Labs](https://www.baseframelabs.com), each answering a different question about your work:
|
package/dist/index.js
CHANGED
|
@@ -105,13 +105,9 @@ function detectLint(root, has) {
|
|
|
105
105
|
return { id: "lint", available: false, reason: "no linter configured" };
|
|
106
106
|
}
|
|
107
107
|
function detectBrowser(root, has) {
|
|
108
|
-
if (has("@playwright/test") || existsSync2(join(root, "playwright.config.ts")))
|
|
109
|
-
return {
|
|
110
|
-
|
|
111
|
-
available: false,
|
|
112
|
-
runner: "playwright",
|
|
113
|
-
reason: "detected; browser execution deferred to v0.5"
|
|
114
|
-
};
|
|
108
|
+
if (has("@playwright/test") || existsSync2(join(root, "playwright.config.ts")) || existsSync2(join(root, "playwright.config.js"))) {
|
|
109
|
+
return { id: "browser", available: true, runner: "playwright" };
|
|
110
|
+
}
|
|
115
111
|
return {
|
|
116
112
|
id: "browser",
|
|
117
113
|
available: false,
|
|
@@ -548,6 +544,76 @@ var init_node_test = __esm({
|
|
|
548
544
|
}
|
|
549
545
|
});
|
|
550
546
|
|
|
547
|
+
// src/runners/playwright.ts
|
|
548
|
+
function parsePlaywrightStats(stdout) {
|
|
549
|
+
try {
|
|
550
|
+
const json = JSON.parse(stdout);
|
|
551
|
+
return json.stats ?? null;
|
|
552
|
+
} catch {
|
|
553
|
+
return null;
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
var TAIL_LINES2, playwrightRunner;
|
|
557
|
+
var init_playwright = __esm({
|
|
558
|
+
"src/runners/playwright.ts"() {
|
|
559
|
+
"use strict";
|
|
560
|
+
init_store();
|
|
561
|
+
init_exec();
|
|
562
|
+
init_base();
|
|
563
|
+
TAIL_LINES2 = 20;
|
|
564
|
+
playwrightRunner = {
|
|
565
|
+
id: "playwright",
|
|
566
|
+
toCheck(project, _cap) {
|
|
567
|
+
return {
|
|
568
|
+
id: "browser",
|
|
569
|
+
title: "Browser tests",
|
|
570
|
+
runner: "playwright",
|
|
571
|
+
cmd: localBin(project.root, "playwright"),
|
|
572
|
+
args: ["test", "--reporter=json"]
|
|
573
|
+
};
|
|
574
|
+
},
|
|
575
|
+
async run(check, ctx) {
|
|
576
|
+
const r = await exec(check.cmd, check.args, {
|
|
577
|
+
cwd: ctx.root,
|
|
578
|
+
timeoutMs: 15 * 6e4
|
|
579
|
+
});
|
|
580
|
+
const output = [r.stdout, r.stderr].filter(Boolean).join("\n").trim();
|
|
581
|
+
const logRef = await writeLog(ctx.runDir, check.id, `${output}
|
|
582
|
+
`);
|
|
583
|
+
const stats = parsePlaywrightStats(r.stdout);
|
|
584
|
+
let status;
|
|
585
|
+
if (r.timedOut) {
|
|
586
|
+
status = "unknown";
|
|
587
|
+
} else if (stats) {
|
|
588
|
+
status = r.code === 0 && (stats.unexpected ?? 0) === 0 ? "passed" : "failed";
|
|
589
|
+
} else {
|
|
590
|
+
status = r.code === 0 ? "unknown" : "failed";
|
|
591
|
+
}
|
|
592
|
+
const result = {
|
|
593
|
+
checkId: "browser",
|
|
594
|
+
status,
|
|
595
|
+
durationMs: r.durationMs,
|
|
596
|
+
summary: status === "passed" ? "browser tests passed" : r.timedOut ? "timed out" : status === "unknown" ? "browser tests ran but the results could not be parsed" : "browser tests failed",
|
|
597
|
+
logRef
|
|
598
|
+
};
|
|
599
|
+
if (stats) {
|
|
600
|
+
const passed = stats.expected ?? 0;
|
|
601
|
+
const failed = stats.unexpected ?? 0;
|
|
602
|
+
result.counts = {
|
|
603
|
+
passed,
|
|
604
|
+
failed,
|
|
605
|
+
total: passed + failed + (stats.flaky ?? 0) + (stats.skipped ?? 0)
|
|
606
|
+
};
|
|
607
|
+
}
|
|
608
|
+
if (status !== "passed" && output) {
|
|
609
|
+
result.outputTail = output.split("\n").slice(-TAIL_LINES2).join("\n");
|
|
610
|
+
}
|
|
611
|
+
return result;
|
|
612
|
+
}
|
|
613
|
+
};
|
|
614
|
+
}
|
|
615
|
+
});
|
|
616
|
+
|
|
551
617
|
// src/runners/tsc.ts
|
|
552
618
|
var tscRunner;
|
|
553
619
|
var init_tsc = __esm({
|
|
@@ -614,6 +680,7 @@ var init_runners = __esm({
|
|
|
614
680
|
init_eslint();
|
|
615
681
|
init_jest();
|
|
616
682
|
init_node_test();
|
|
683
|
+
init_playwright();
|
|
617
684
|
init_tsc();
|
|
618
685
|
init_vitest();
|
|
619
686
|
init_base();
|
|
@@ -623,7 +690,8 @@ var init_runners = __esm({
|
|
|
623
690
|
jest: jestRunner,
|
|
624
691
|
"node-test": nodeTestRunner,
|
|
625
692
|
eslint: eslintRunner,
|
|
626
|
-
biome: biomeRunner
|
|
693
|
+
biome: biomeRunner,
|
|
694
|
+
playwright: playwrightRunner
|
|
627
695
|
};
|
|
628
696
|
}
|
|
629
697
|
});
|
|
@@ -997,15 +1065,205 @@ var init_markdown = __esm({
|
|
|
997
1065
|
}
|
|
998
1066
|
});
|
|
999
1067
|
|
|
1068
|
+
// src/publish/comment.ts
|
|
1069
|
+
function renderComment(run, record) {
|
|
1070
|
+
const summary = run.results.filter((r) => r.status !== "skipped").map((r) => `${r.checkId} ${r.status}`).join(" \xB7 ");
|
|
1071
|
+
return [
|
|
1072
|
+
MARKER,
|
|
1073
|
+
`**VerisKit: ${STATE_LABEL2[run.verdict.state]}**`,
|
|
1074
|
+
"",
|
|
1075
|
+
summary,
|
|
1076
|
+
"",
|
|
1077
|
+
"<details><summary>Report</summary>",
|
|
1078
|
+
"",
|
|
1079
|
+
renderMarkdown(run, record),
|
|
1080
|
+
"",
|
|
1081
|
+
"</details>",
|
|
1082
|
+
""
|
|
1083
|
+
].join("\n");
|
|
1084
|
+
}
|
|
1085
|
+
var MARKER, STATE_LABEL2;
|
|
1086
|
+
var init_comment = __esm({
|
|
1087
|
+
"src/publish/comment.ts"() {
|
|
1088
|
+
"use strict";
|
|
1089
|
+
init_markdown();
|
|
1090
|
+
MARKER = "<!-- veriskit-report -->";
|
|
1091
|
+
STATE_LABEL2 = {
|
|
1092
|
+
verified: "verified",
|
|
1093
|
+
failed: "failed",
|
|
1094
|
+
partial: "partial"
|
|
1095
|
+
};
|
|
1096
|
+
}
|
|
1097
|
+
});
|
|
1098
|
+
|
|
1099
|
+
// src/publish/context.ts
|
|
1100
|
+
import { readFileSync as readFileSync2 } from "fs";
|
|
1101
|
+
function defaultReadEvent(path) {
|
|
1102
|
+
try {
|
|
1103
|
+
return JSON.parse(readFileSync2(path, "utf8"));
|
|
1104
|
+
} catch {
|
|
1105
|
+
return null;
|
|
1106
|
+
}
|
|
1107
|
+
}
|
|
1108
|
+
function resolvePublishContext(env = process.env, readEvent = defaultReadEvent) {
|
|
1109
|
+
const repository = env.GITHUB_REPOSITORY;
|
|
1110
|
+
const token = env.GITHUB_TOKEN;
|
|
1111
|
+
if (!repository || !token) return null;
|
|
1112
|
+
const [owner, repo] = repository.split("/");
|
|
1113
|
+
if (!owner || !repo) return null;
|
|
1114
|
+
let prNumber = null;
|
|
1115
|
+
let headSha = env.GITHUB_SHA ?? "";
|
|
1116
|
+
const refMatch = (env.GITHUB_REF ?? "").match(/^refs\/pull\/(\d+)\//);
|
|
1117
|
+
if (refMatch) prNumber = Number(refMatch[1]);
|
|
1118
|
+
const event = readEvent(env.GITHUB_EVENT_PATH ?? "");
|
|
1119
|
+
const pr = event?.pull_request;
|
|
1120
|
+
if (pr) {
|
|
1121
|
+
if (prNumber === null && typeof pr.number === "number") {
|
|
1122
|
+
prNumber = pr.number;
|
|
1123
|
+
}
|
|
1124
|
+
if (pr.head?.sha) headSha = pr.head.sha;
|
|
1125
|
+
}
|
|
1126
|
+
if (prNumber === null || !headSha) return null;
|
|
1127
|
+
return { owner, repo, token, prNumber, headSha };
|
|
1128
|
+
}
|
|
1129
|
+
var init_context = __esm({
|
|
1130
|
+
"src/publish/context.ts"() {
|
|
1131
|
+
"use strict";
|
|
1132
|
+
}
|
|
1133
|
+
});
|
|
1134
|
+
|
|
1135
|
+
// src/publish/github.ts
|
|
1136
|
+
function headers(token) {
|
|
1137
|
+
return {
|
|
1138
|
+
Authorization: `Bearer ${token}`,
|
|
1139
|
+
Accept: "application/vnd.github+json",
|
|
1140
|
+
"X-GitHub-Api-Version": "2022-11-28",
|
|
1141
|
+
"User-Agent": "veriskit",
|
|
1142
|
+
"Content-Type": "application/json"
|
|
1143
|
+
};
|
|
1144
|
+
}
|
|
1145
|
+
async function gh(method, url, token, body) {
|
|
1146
|
+
const res = await fetch(url, {
|
|
1147
|
+
method,
|
|
1148
|
+
headers: headers(token),
|
|
1149
|
+
body: body === void 0 ? void 0 : JSON.stringify(body)
|
|
1150
|
+
});
|
|
1151
|
+
if (!res.ok) {
|
|
1152
|
+
const path = url.replace("https://api.github.com", "");
|
|
1153
|
+
throw new GitHubApiError(
|
|
1154
|
+
res.status,
|
|
1155
|
+
`GitHub API ${method} ${path} -> ${res.status}`
|
|
1156
|
+
);
|
|
1157
|
+
}
|
|
1158
|
+
return res.json();
|
|
1159
|
+
}
|
|
1160
|
+
function repoBase(ctx) {
|
|
1161
|
+
return `https://api.github.com/repos/${ctx.owner}/${ctx.repo}`;
|
|
1162
|
+
}
|
|
1163
|
+
async function upsertComment(ctx, body) {
|
|
1164
|
+
const base = repoBase(ctx);
|
|
1165
|
+
const comments = await gh(
|
|
1166
|
+
"GET",
|
|
1167
|
+
`${base}/issues/${ctx.prNumber}/comments?per_page=100`,
|
|
1168
|
+
ctx.token
|
|
1169
|
+
);
|
|
1170
|
+
const existing = comments.find((c) => (c.body ?? "").includes(MARKER));
|
|
1171
|
+
if (existing) {
|
|
1172
|
+
await gh("PATCH", `${base}/issues/comments/${existing.id}`, ctx.token, {
|
|
1173
|
+
body
|
|
1174
|
+
});
|
|
1175
|
+
} else {
|
|
1176
|
+
await gh("POST", `${base}/issues/${ctx.prNumber}/comments`, ctx.token, {
|
|
1177
|
+
body
|
|
1178
|
+
});
|
|
1179
|
+
}
|
|
1180
|
+
}
|
|
1181
|
+
async function createCheckRun(ctx, opts) {
|
|
1182
|
+
await gh("POST", `${repoBase(ctx)}/check-runs`, ctx.token, {
|
|
1183
|
+
name: opts.name,
|
|
1184
|
+
head_sha: ctx.headSha,
|
|
1185
|
+
status: "completed",
|
|
1186
|
+
conclusion: opts.conclusion,
|
|
1187
|
+
output: { title: opts.title, summary: opts.summary }
|
|
1188
|
+
});
|
|
1189
|
+
}
|
|
1190
|
+
var GitHubApiError;
|
|
1191
|
+
var init_github = __esm({
|
|
1192
|
+
"src/publish/github.ts"() {
|
|
1193
|
+
"use strict";
|
|
1194
|
+
init_comment();
|
|
1195
|
+
GitHubApiError = class extends Error {
|
|
1196
|
+
status;
|
|
1197
|
+
constructor(status, message) {
|
|
1198
|
+
super(message);
|
|
1199
|
+
this.name = "GitHubApiError";
|
|
1200
|
+
this.status = status;
|
|
1201
|
+
}
|
|
1202
|
+
};
|
|
1203
|
+
}
|
|
1204
|
+
});
|
|
1205
|
+
|
|
1206
|
+
// src/publish/publish.ts
|
|
1207
|
+
async function publishToGitHub(run, record) {
|
|
1208
|
+
const ctx = resolvePublishContext();
|
|
1209
|
+
if (!ctx) {
|
|
1210
|
+
process.stdout.write(
|
|
1211
|
+
"veris: --github set but no GitHub PR context found (need GITHUB_TOKEN + a pull_request event); skipping publish.\n"
|
|
1212
|
+
);
|
|
1213
|
+
return;
|
|
1214
|
+
}
|
|
1215
|
+
try {
|
|
1216
|
+
await upsertComment(ctx, renderComment(run, record));
|
|
1217
|
+
await createCheckRun(ctx, {
|
|
1218
|
+
name: "VerisKit",
|
|
1219
|
+
conclusion: CONCLUSION[run.verdict.state],
|
|
1220
|
+
title: `VerisKit: ${run.verdict.state}`,
|
|
1221
|
+
summary: renderMarkdown(run, record)
|
|
1222
|
+
});
|
|
1223
|
+
process.stdout.write(
|
|
1224
|
+
`veris: published the verdict to PR #${ctx.prNumber}
|
|
1225
|
+
`
|
|
1226
|
+
);
|
|
1227
|
+
} catch (err) {
|
|
1228
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1229
|
+
process.stderr.write(`veris: GitHub publish failed: ${msg}
|
|
1230
|
+
`);
|
|
1231
|
+
}
|
|
1232
|
+
}
|
|
1233
|
+
var CONCLUSION;
|
|
1234
|
+
var init_publish = __esm({
|
|
1235
|
+
"src/publish/publish.ts"() {
|
|
1236
|
+
"use strict";
|
|
1237
|
+
init_markdown();
|
|
1238
|
+
init_comment();
|
|
1239
|
+
init_context();
|
|
1240
|
+
init_github();
|
|
1241
|
+
CONCLUSION = {
|
|
1242
|
+
verified: "success",
|
|
1243
|
+
failed: "failure",
|
|
1244
|
+
partial: "neutral"
|
|
1245
|
+
};
|
|
1246
|
+
}
|
|
1247
|
+
});
|
|
1248
|
+
|
|
1000
1249
|
// src/cli/commands/verify.ts
|
|
1001
1250
|
var verify_exports = {};
|
|
1002
1251
|
__export(verify_exports, {
|
|
1252
|
+
resolveChecks: () => resolveChecks,
|
|
1003
1253
|
runVerify: () => runVerify
|
|
1004
1254
|
});
|
|
1255
|
+
function resolveChecks(configChecks, project, opts) {
|
|
1256
|
+
let checks = configChecks?.length ? configChecks : DEFAULT_CHECKS;
|
|
1257
|
+
if (opts.browser && !checks.includes("browser")) {
|
|
1258
|
+
const cap = project.capabilities.find((c) => c.id === "browser");
|
|
1259
|
+
if (cap?.available) checks = [...checks, "browser"];
|
|
1260
|
+
}
|
|
1261
|
+
return checks;
|
|
1262
|
+
}
|
|
1005
1263
|
async function runVerify(root, opts = {}) {
|
|
1006
1264
|
const project = await detectProject(root);
|
|
1007
1265
|
const config = await loadConfig(root);
|
|
1008
|
-
const checks = config?.checks
|
|
1266
|
+
const checks = resolveChecks(config?.checks, project, opts);
|
|
1009
1267
|
const run = await runChecks(project, checks, root);
|
|
1010
1268
|
const git = await gitAnchor(root);
|
|
1011
1269
|
const logDigests = await digestLogs(run);
|
|
@@ -1020,6 +1278,7 @@ async function runVerify(root, opts = {}) {
|
|
|
1020
1278
|
await writeEvidence(runDir, record);
|
|
1021
1279
|
process.stdout.write(`${renderRun(run, record)}
|
|
1022
1280
|
`);
|
|
1281
|
+
if (opts.github) await publishToGitHub(run, record);
|
|
1023
1282
|
return verdictExitCode(run.verdict, opts);
|
|
1024
1283
|
}
|
|
1025
1284
|
var DEFAULT_CHECKS;
|
|
@@ -1033,6 +1292,7 @@ var init_verify = __esm({
|
|
|
1033
1292
|
init_record();
|
|
1034
1293
|
init_store();
|
|
1035
1294
|
init_changes();
|
|
1295
|
+
init_publish();
|
|
1036
1296
|
init_markdown();
|
|
1037
1297
|
init_terminal();
|
|
1038
1298
|
init_version();
|
|
@@ -1045,7 +1305,7 @@ var report_exports = {};
|
|
|
1045
1305
|
__export(report_exports, {
|
|
1046
1306
|
runReport: () => runReport
|
|
1047
1307
|
});
|
|
1048
|
-
import { readdirSync as readdirSync2, readFileSync as
|
|
1308
|
+
import { readdirSync as readdirSync2, readFileSync as readFileSync3 } from "fs";
|
|
1049
1309
|
import { join as join6 } from "path";
|
|
1050
1310
|
async function runReport(root) {
|
|
1051
1311
|
const dir = join6(root, ".veris", "reports");
|
|
@@ -1061,7 +1321,7 @@ async function runReport(root) {
|
|
|
1061
1321
|
process.stdout.write("No reports yet. Run `veris verify` first.\n");
|
|
1062
1322
|
return 0;
|
|
1063
1323
|
}
|
|
1064
|
-
process.stdout.write(`${
|
|
1324
|
+
process.stdout.write(`${readFileSync3(join6(dir, latest), "utf8")}
|
|
1065
1325
|
`);
|
|
1066
1326
|
return 0;
|
|
1067
1327
|
}
|
|
@@ -1176,7 +1436,7 @@ var init_discover = __esm({
|
|
|
1176
1436
|
});
|
|
1177
1437
|
|
|
1178
1438
|
// src/project-graph/scanner-resolver.ts
|
|
1179
|
-
import { existsSync as existsSync3, readFileSync as
|
|
1439
|
+
import { existsSync as existsSync3, readFileSync as readFileSync4, statSync as statSync2 } from "fs";
|
|
1180
1440
|
import { dirname, join as join8, relative as relative3, resolve } from "path";
|
|
1181
1441
|
function extractSpecifiers(text) {
|
|
1182
1442
|
const specs = [];
|
|
@@ -1215,7 +1475,7 @@ function resolveRelative(fromDir, spec) {
|
|
|
1215
1475
|
function scannerImports(root, file) {
|
|
1216
1476
|
let text;
|
|
1217
1477
|
try {
|
|
1218
|
-
text =
|
|
1478
|
+
text = readFileSync4(join8(root, file), "utf8");
|
|
1219
1479
|
} catch {
|
|
1220
1480
|
return [];
|
|
1221
1481
|
}
|
|
@@ -1239,7 +1499,7 @@ var init_scanner_resolver = __esm({
|
|
|
1239
1499
|
});
|
|
1240
1500
|
|
|
1241
1501
|
// src/project-graph/ts-resolver.ts
|
|
1242
|
-
import { existsSync as existsSync4, readFileSync as
|
|
1502
|
+
import { existsSync as existsSync4, readFileSync as readFileSync5 } from "fs";
|
|
1243
1503
|
import { createRequire } from "module";
|
|
1244
1504
|
import { join as join9, relative as relative4, sep as sep2 } from "path";
|
|
1245
1505
|
function hasClassicApi(mod) {
|
|
@@ -1266,7 +1526,7 @@ function loadCompilerOptions(tsmod, root) {
|
|
|
1266
1526
|
function tsImports(tsmod, root, options, file) {
|
|
1267
1527
|
let text;
|
|
1268
1528
|
try {
|
|
1269
|
-
text =
|
|
1529
|
+
text = readFileSync5(join9(root, file), "utf8");
|
|
1270
1530
|
} catch {
|
|
1271
1531
|
return [];
|
|
1272
1532
|
}
|
|
@@ -1518,6 +1778,7 @@ async function runAffected(root, opts = {}) {
|
|
|
1518
1778
|
`);
|
|
1519
1779
|
if (narrowedNote) process.stdout.write(`${narrowedNote}
|
|
1520
1780
|
`);
|
|
1781
|
+
if (opts.github) await publishToGitHub(run, record);
|
|
1521
1782
|
return verdictExitCode(run.verdict, opts);
|
|
1522
1783
|
}
|
|
1523
1784
|
var init_affected = __esm({
|
|
@@ -1530,6 +1791,7 @@ var init_affected = __esm({
|
|
|
1530
1791
|
init_record();
|
|
1531
1792
|
init_store();
|
|
1532
1793
|
init_changes();
|
|
1794
|
+
init_publish();
|
|
1533
1795
|
init_markdown();
|
|
1534
1796
|
init_terminal();
|
|
1535
1797
|
init_version();
|
|
@@ -2143,7 +2405,7 @@ __export(evidence_exports, {
|
|
|
2143
2405
|
runEvidenceSign: () => runEvidenceSign,
|
|
2144
2406
|
runEvidenceVerify: () => runEvidenceVerify
|
|
2145
2407
|
});
|
|
2146
|
-
import { chmodSync, existsSync as existsSync6, readFileSync as
|
|
2408
|
+
import { chmodSync, existsSync as existsSync6, readFileSync as readFileSync6, writeFileSync } from "fs";
|
|
2147
2409
|
import { writeFile as writeFile4 } from "fs/promises";
|
|
2148
2410
|
import { basename as basename3, dirname as dirname2, join as join12 } from "path";
|
|
2149
2411
|
import pc5 from "picocolors";
|
|
@@ -2151,7 +2413,7 @@ async function runEvidenceVerify(path, opts = {}) {
|
|
|
2151
2413
|
let expectedPubKeyPem;
|
|
2152
2414
|
if (opts.pubkey) {
|
|
2153
2415
|
try {
|
|
2154
|
-
expectedPubKeyPem =
|
|
2416
|
+
expectedPubKeyPem = readFileSync6(opts.pubkey, "utf8");
|
|
2155
2417
|
} catch {
|
|
2156
2418
|
process.stderr.write(`veris: cannot read public key at ${opts.pubkey}
|
|
2157
2419
|
`);
|
|
@@ -2206,8 +2468,8 @@ async function runEvidenceKeygen(root, opts = {}) {
|
|
|
2206
2468
|
);
|
|
2207
2469
|
return 1;
|
|
2208
2470
|
}
|
|
2209
|
-
const { mkdir:
|
|
2210
|
-
await
|
|
2471
|
+
const { mkdir: mkdir3 } = await import("fs/promises");
|
|
2472
|
+
await mkdir3(dirname2(out), { recursive: true });
|
|
2211
2473
|
const kp = generateKeyPair();
|
|
2212
2474
|
writeFileSync(out, kp.privateKeyPem, { mode: 384 });
|
|
2213
2475
|
chmodSync(out, 384);
|
|
@@ -2228,7 +2490,7 @@ async function runEvidenceSign(evidencePath, opts = {}) {
|
|
|
2228
2490
|
privateKeyPem = process.env.VERISKIT_SIGNING_KEY;
|
|
2229
2491
|
} else if (opts.key) {
|
|
2230
2492
|
try {
|
|
2231
|
-
privateKeyPem =
|
|
2493
|
+
privateKeyPem = readFileSync6(opts.key, "utf8");
|
|
2232
2494
|
} catch {
|
|
2233
2495
|
process.stderr.write(`veris: cannot read signing key at ${opts.key}
|
|
2234
2496
|
`);
|
|
@@ -2242,7 +2504,7 @@ async function runEvidenceSign(evidencePath, opts = {}) {
|
|
|
2242
2504
|
}
|
|
2243
2505
|
let record;
|
|
2244
2506
|
try {
|
|
2245
|
-
record = JSON.parse(
|
|
2507
|
+
record = JSON.parse(readFileSync6(evidencePath, "utf8"));
|
|
2246
2508
|
} catch {
|
|
2247
2509
|
process.stderr.write(`veris: cannot read evidence at ${evidencePath}
|
|
2248
2510
|
`);
|
|
@@ -2285,7 +2547,7 @@ async function runEvidenceBundle(root, opts = {}) {
|
|
|
2285
2547
|
let record;
|
|
2286
2548
|
try {
|
|
2287
2549
|
record = JSON.parse(
|
|
2288
|
-
|
|
2550
|
+
readFileSync6(join12(runDir, "evidence.json"), "utf8")
|
|
2289
2551
|
);
|
|
2290
2552
|
} catch {
|
|
2291
2553
|
process.stderr.write(`veris: no evidence.json in ${runDir}
|
|
@@ -2294,7 +2556,7 @@ async function runEvidenceBundle(root, opts = {}) {
|
|
|
2294
2556
|
}
|
|
2295
2557
|
let report = "";
|
|
2296
2558
|
try {
|
|
2297
|
-
report =
|
|
2559
|
+
report = readFileSync6(
|
|
2298
2560
|
join12(root, ".veris", "reports", `verify-${record.id}.md`),
|
|
2299
2561
|
"utf8"
|
|
2300
2562
|
);
|
|
@@ -2306,7 +2568,7 @@ async function runEvidenceBundle(root, opts = {}) {
|
|
|
2306
2568
|
const sigPath = join12(runDir, "evidence.json.sig");
|
|
2307
2569
|
if (existsSync6(sigPath)) {
|
|
2308
2570
|
try {
|
|
2309
|
-
signature = JSON.parse(
|
|
2571
|
+
signature = JSON.parse(readFileSync6(sigPath, "utf8"));
|
|
2310
2572
|
} catch {
|
|
2311
2573
|
}
|
|
2312
2574
|
}
|
|
@@ -2330,7 +2592,7 @@ async function runEvidenceShow(root, path) {
|
|
|
2330
2592
|
}
|
|
2331
2593
|
let record;
|
|
2332
2594
|
try {
|
|
2333
|
-
record = JSON.parse(
|
|
2595
|
+
record = JSON.parse(readFileSync6(target, "utf8"));
|
|
2334
2596
|
} catch {
|
|
2335
2597
|
process.stderr.write(`veris: cannot read evidence at ${target}
|
|
2336
2598
|
`);
|
|
@@ -2365,6 +2627,181 @@ var init_evidence = __esm({
|
|
|
2365
2627
|
}
|
|
2366
2628
|
});
|
|
2367
2629
|
|
|
2630
|
+
// src/publish/badge.ts
|
|
2631
|
+
function buildBadge(verdict) {
|
|
2632
|
+
const s = STYLE[verdict.state];
|
|
2633
|
+
return {
|
|
2634
|
+
schemaVersion: 1,
|
|
2635
|
+
label: "veriskit",
|
|
2636
|
+
message: s.message,
|
|
2637
|
+
color: s.color
|
|
2638
|
+
};
|
|
2639
|
+
}
|
|
2640
|
+
var STYLE;
|
|
2641
|
+
var init_badge = __esm({
|
|
2642
|
+
"src/publish/badge.ts"() {
|
|
2643
|
+
"use strict";
|
|
2644
|
+
STYLE = {
|
|
2645
|
+
verified: { message: "verified", color: "14b8a6" },
|
|
2646
|
+
failed: { message: "failed", color: "e5484d" },
|
|
2647
|
+
partial: { message: "partial", color: "f5a623" }
|
|
2648
|
+
};
|
|
2649
|
+
}
|
|
2650
|
+
});
|
|
2651
|
+
|
|
2652
|
+
// src/cli/commands/badge.ts
|
|
2653
|
+
var badge_exports = {};
|
|
2654
|
+
__export(badge_exports, {
|
|
2655
|
+
runBadge: () => runBadge
|
|
2656
|
+
});
|
|
2657
|
+
import { readFileSync as readFileSync7 } from "fs";
|
|
2658
|
+
import { mkdir as mkdir2, writeFile as writeFile5 } from "fs/promises";
|
|
2659
|
+
import { dirname as dirname3, join as join13 } from "path";
|
|
2660
|
+
async function runBadge(root, opts = {}) {
|
|
2661
|
+
const dir = latestRunDir(root);
|
|
2662
|
+
if (!dir) {
|
|
2663
|
+
process.stdout.write("No runs yet. Run `veris verify` first.\n");
|
|
2664
|
+
return 1;
|
|
2665
|
+
}
|
|
2666
|
+
let record;
|
|
2667
|
+
try {
|
|
2668
|
+
record = JSON.parse(
|
|
2669
|
+
readFileSync7(join13(dir, "evidence.json"), "utf8")
|
|
2670
|
+
);
|
|
2671
|
+
} catch {
|
|
2672
|
+
process.stderr.write(`veris: no evidence.json in ${dir}
|
|
2673
|
+
`);
|
|
2674
|
+
return 1;
|
|
2675
|
+
}
|
|
2676
|
+
const out = opts.out ?? join13(root, ".veris", "badge.json");
|
|
2677
|
+
await mkdir2(dirname3(out), { recursive: true });
|
|
2678
|
+
await writeFile5(
|
|
2679
|
+
out,
|
|
2680
|
+
`${JSON.stringify(buildBadge(record.verdict), null, 2)}
|
|
2681
|
+
`,
|
|
2682
|
+
"utf8"
|
|
2683
|
+
);
|
|
2684
|
+
process.stdout.write(`Wrote badge ${out}
|
|
2685
|
+
`);
|
|
2686
|
+
return 0;
|
|
2687
|
+
}
|
|
2688
|
+
var init_badge2 = __esm({
|
|
2689
|
+
"src/cli/commands/badge.ts"() {
|
|
2690
|
+
"use strict";
|
|
2691
|
+
init_store();
|
|
2692
|
+
init_badge();
|
|
2693
|
+
}
|
|
2694
|
+
});
|
|
2695
|
+
|
|
2696
|
+
// src/history/flaky.ts
|
|
2697
|
+
function detectFlaky(records) {
|
|
2698
|
+
const byId = /* @__PURE__ */ new Map();
|
|
2699
|
+
for (const rec of records) {
|
|
2700
|
+
for (const c of rec.checks ?? []) {
|
|
2701
|
+
const arr = byId.get(c.id) ?? [];
|
|
2702
|
+
arr.push(c.status);
|
|
2703
|
+
byId.set(c.id, arr);
|
|
2704
|
+
}
|
|
2705
|
+
}
|
|
2706
|
+
const flaky = [];
|
|
2707
|
+
for (const [id, statuses] of byId) {
|
|
2708
|
+
if (statuses.includes("passed") && statuses.includes("failed")) {
|
|
2709
|
+
flaky.push({ id, statuses });
|
|
2710
|
+
}
|
|
2711
|
+
}
|
|
2712
|
+
return flaky;
|
|
2713
|
+
}
|
|
2714
|
+
var init_flaky = __esm({
|
|
2715
|
+
"src/history/flaky.ts"() {
|
|
2716
|
+
"use strict";
|
|
2717
|
+
}
|
|
2718
|
+
});
|
|
2719
|
+
|
|
2720
|
+
// src/history/read.ts
|
|
2721
|
+
import { readdirSync as readdirSync5, readFileSync as readFileSync8 } from "fs";
|
|
2722
|
+
import { join as join14 } from "path";
|
|
2723
|
+
function loadRuns(root, limit = 20) {
|
|
2724
|
+
const runs = join14(root, ".veris", "runs");
|
|
2725
|
+
let names;
|
|
2726
|
+
try {
|
|
2727
|
+
names = readdirSync5(runs, { withFileTypes: true }).filter((d) => d.isDirectory() && d.name !== "watch").map((d) => d.name);
|
|
2728
|
+
} catch {
|
|
2729
|
+
return [];
|
|
2730
|
+
}
|
|
2731
|
+
const records = [];
|
|
2732
|
+
for (const name of names) {
|
|
2733
|
+
try {
|
|
2734
|
+
const rec = JSON.parse(
|
|
2735
|
+
readFileSync8(join14(runs, name, "evidence.json"), "utf8")
|
|
2736
|
+
);
|
|
2737
|
+
if (rec?.schema === "veriskit/evidence@1") records.push(rec);
|
|
2738
|
+
} catch {
|
|
2739
|
+
}
|
|
2740
|
+
}
|
|
2741
|
+
records.sort(
|
|
2742
|
+
(a, b) => a.startedAt < b.startedAt ? 1 : a.startedAt > b.startedAt ? -1 : 0
|
|
2743
|
+
);
|
|
2744
|
+
return records.slice(0, limit);
|
|
2745
|
+
}
|
|
2746
|
+
var init_read = __esm({
|
|
2747
|
+
"src/history/read.ts"() {
|
|
2748
|
+
"use strict";
|
|
2749
|
+
}
|
|
2750
|
+
});
|
|
2751
|
+
|
|
2752
|
+
// src/cli/commands/log.ts
|
|
2753
|
+
var log_exports = {};
|
|
2754
|
+
__export(log_exports, {
|
|
2755
|
+
runLog: () => runLog
|
|
2756
|
+
});
|
|
2757
|
+
async function runLog(root, opts = {}) {
|
|
2758
|
+
const limit = Number.isFinite(opts.limit) && opts.limit > 0 ? opts.limit : 20;
|
|
2759
|
+
const records = loadRuns(root, limit);
|
|
2760
|
+
if (records.length === 0) {
|
|
2761
|
+
process.stdout.write("No runs yet. Run `veris verify` first.\n");
|
|
2762
|
+
return 0;
|
|
2763
|
+
}
|
|
2764
|
+
if (opts.flaky) {
|
|
2765
|
+
const flaky = detectFlaky(records);
|
|
2766
|
+
if (flaky.length === 0) {
|
|
2767
|
+
process.stdout.write(
|
|
2768
|
+
`No flaky checks in the last ${records.length} run(s).
|
|
2769
|
+
`
|
|
2770
|
+
);
|
|
2771
|
+
return 0;
|
|
2772
|
+
}
|
|
2773
|
+
process.stdout.write(
|
|
2774
|
+
`Flaky checks (both passed and failed in the last ${records.length} run(s), newest first):
|
|
2775
|
+
`
|
|
2776
|
+
);
|
|
2777
|
+
for (const f of flaky) {
|
|
2778
|
+
process.stdout.write(` ${f.id.padEnd(8)} ${f.statuses.join(" ")}
|
|
2779
|
+
`);
|
|
2780
|
+
}
|
|
2781
|
+
return 0;
|
|
2782
|
+
}
|
|
2783
|
+
for (const rec of records) {
|
|
2784
|
+
const date = rec.startedAt.slice(0, 19).replace("T", " ");
|
|
2785
|
+
const checks = (rec.checks ?? []).map((c) => `${c.id}:${c.status}`).join(" ");
|
|
2786
|
+
const commit = rec.git ? rec.git.commit.slice(0, 7) : "-";
|
|
2787
|
+
process.stdout.write(
|
|
2788
|
+
`${date} ${rec.verdict.state.padEnd(9)} ${checks} ${commit}
|
|
2789
|
+
`
|
|
2790
|
+
);
|
|
2791
|
+
}
|
|
2792
|
+
process.stdout.write(
|
|
2793
|
+
"\nHistory is local to this machine (.veris/runs is gitignored).\n"
|
|
2794
|
+
);
|
|
2795
|
+
return 0;
|
|
2796
|
+
}
|
|
2797
|
+
var init_log = __esm({
|
|
2798
|
+
"src/cli/commands/log.ts"() {
|
|
2799
|
+
"use strict";
|
|
2800
|
+
init_flaky();
|
|
2801
|
+
init_read();
|
|
2802
|
+
}
|
|
2803
|
+
});
|
|
2804
|
+
|
|
2368
2805
|
// src/cli/index.ts
|
|
2369
2806
|
init_version();
|
|
2370
2807
|
import { realpathSync } from "fs";
|
|
@@ -2387,23 +2824,36 @@ function buildCli() {
|
|
|
2387
2824
|
const { runInit: runInit2 } = await Promise.resolve().then(() => (init_init(), init_exports));
|
|
2388
2825
|
process.exitCode = await runInit2(process.cwd());
|
|
2389
2826
|
});
|
|
2390
|
-
cli.command("verify", "Run the full check set and produce a verdict + report").option("--partial-ok", "Exit 0 even when the verdict is partial").
|
|
2391
|
-
|
|
2392
|
-
|
|
2393
|
-
|
|
2394
|
-
|
|
2395
|
-
|
|
2827
|
+
cli.command("verify", "Run the full check set and produce a verdict + report").option("--partial-ok", "Exit 0 even when the verdict is partial").option(
|
|
2828
|
+
"--github",
|
|
2829
|
+
"Publish the verdict to the GitHub PR (comment + check run)"
|
|
2830
|
+
).option("--browser", "Also run browser tests (Playwright), when available").action(
|
|
2831
|
+
async (opts) => {
|
|
2832
|
+
const { runVerify: runVerify2 } = await Promise.resolve().then(() => (init_verify(), verify_exports));
|
|
2833
|
+
process.exitCode = await runVerify2(process.cwd(), {
|
|
2834
|
+
partialOk: opts.partialOk,
|
|
2835
|
+
github: opts.github,
|
|
2836
|
+
browser: opts.browser
|
|
2837
|
+
});
|
|
2838
|
+
}
|
|
2839
|
+
);
|
|
2396
2840
|
cli.command("report", "Print the latest verification report").action(async () => {
|
|
2397
2841
|
const { runReport: runReport2 } = await Promise.resolve().then(() => (init_report(), report_exports));
|
|
2398
2842
|
process.exitCode = await runReport2(process.cwd());
|
|
2399
2843
|
});
|
|
2400
|
-
cli.command("affected", "Run only the checks affected by changed files").option("--base <ref>", "Compare against a git ref instead of HEAD").option("--partial-ok", "Exit 0 even when the verdict is partial").
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
|
|
2844
|
+
cli.command("affected", "Run only the checks affected by changed files").option("--base <ref>", "Compare against a git ref instead of HEAD").option("--partial-ok", "Exit 0 even when the verdict is partial").option(
|
|
2845
|
+
"--github",
|
|
2846
|
+
"Publish the verdict to the GitHub PR (comment + check run)"
|
|
2847
|
+
).action(
|
|
2848
|
+
async (opts) => {
|
|
2849
|
+
const { runAffected: runAffected2 } = await Promise.resolve().then(() => (init_affected(), affected_exports));
|
|
2850
|
+
process.exitCode = await runAffected2(process.cwd(), {
|
|
2851
|
+
base: opts.base,
|
|
2852
|
+
partialOk: opts.partialOk,
|
|
2853
|
+
github: opts.github
|
|
2854
|
+
});
|
|
2855
|
+
}
|
|
2856
|
+
);
|
|
2407
2857
|
cli.command("watch", "Re-run affected checks as files change (Ctrl-C to stop)").option("--poll", "Use mtime polling instead of native fs.watch").action(async (opts) => {
|
|
2408
2858
|
const { runWatch: runWatch2 } = await Promise.resolve().then(() => (init_watch(), watch_exports));
|
|
2409
2859
|
process.exitCode = await runWatch2(process.cwd(), { poll: opts.poll });
|
|
@@ -2465,6 +2915,20 @@ function buildCli() {
|
|
|
2465
2915
|
}
|
|
2466
2916
|
}
|
|
2467
2917
|
);
|
|
2918
|
+
cli.command(
|
|
2919
|
+
"badge",
|
|
2920
|
+
"Write a shields.io endpoint JSON from the latest verdict"
|
|
2921
|
+
).option("--out <file>", "Output path (default .veris/badge.json)").action(async (opts) => {
|
|
2922
|
+
const { runBadge: runBadge2 } = await Promise.resolve().then(() => (init_badge2(), badge_exports));
|
|
2923
|
+
process.exitCode = await runBadge2(process.cwd(), { out: opts.out });
|
|
2924
|
+
});
|
|
2925
|
+
cli.command("log", "List past verification runs (local history)").option("--limit <n>", "How many runs to show (default 20)").option("--flaky", "Show only checks that flip-flop across runs").action(async (opts) => {
|
|
2926
|
+
const { runLog: runLog2 } = await Promise.resolve().then(() => (init_log(), log_exports));
|
|
2927
|
+
process.exitCode = await runLog2(process.cwd(), {
|
|
2928
|
+
limit: opts.limit ? Number(opts.limit) : void 0,
|
|
2929
|
+
flaky: opts.flaky
|
|
2930
|
+
});
|
|
2931
|
+
});
|
|
2468
2932
|
return { raw: cli, version: VERSION };
|
|
2469
2933
|
}
|
|
2470
2934
|
async function main(argv2) {
|