veriskit 0.4.1 → 0.5.1
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 +13 -0
- package/README.md +58 -0
- package/dist/index.js +508 -38
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.5.1 — 2026-07-13
|
|
4
|
+
|
|
5
|
+
### Changed
|
|
6
|
+
- `--github` publish errors now include GitHub's own message (for example "Resource not accessible by integration" when the workflow token lacks `pull-requests: write` or `checks: write`), so a failed publish is diagnosable. The token still never appears in any error.
|
|
7
|
+
- `homepage` now points to the VerisKit product page.
|
|
8
|
+
|
|
9
|
+
## 0.5.0 — 2026-07-13
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
- 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.
|
|
13
|
+
- 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.
|
|
14
|
+
- `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.
|
|
15
|
+
|
|
3
16
|
## 0.4.1 — 2026-07-10
|
|
4
17
|
|
|
5
18
|
### 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,211 @@ 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
|
+
let detail = "";
|
|
1154
|
+
try {
|
|
1155
|
+
const errBody = await res.json();
|
|
1156
|
+
if (errBody?.message) detail = `: ${errBody.message}`;
|
|
1157
|
+
} catch {
|
|
1158
|
+
}
|
|
1159
|
+
throw new GitHubApiError(
|
|
1160
|
+
res.status,
|
|
1161
|
+
`GitHub API ${method} ${path} -> ${res.status}${detail}`
|
|
1162
|
+
);
|
|
1163
|
+
}
|
|
1164
|
+
return res.status === 204 ? null : res.json();
|
|
1165
|
+
}
|
|
1166
|
+
function repoBase(ctx) {
|
|
1167
|
+
return `https://api.github.com/repos/${ctx.owner}/${ctx.repo}`;
|
|
1168
|
+
}
|
|
1169
|
+
async function upsertComment(ctx, body) {
|
|
1170
|
+
const base = repoBase(ctx);
|
|
1171
|
+
const comments = await gh(
|
|
1172
|
+
"GET",
|
|
1173
|
+
`${base}/issues/${ctx.prNumber}/comments?per_page=100`,
|
|
1174
|
+
ctx.token
|
|
1175
|
+
);
|
|
1176
|
+
const existing = comments.find((c) => (c.body ?? "").includes(MARKER));
|
|
1177
|
+
if (existing) {
|
|
1178
|
+
await gh("PATCH", `${base}/issues/comments/${existing.id}`, ctx.token, {
|
|
1179
|
+
body
|
|
1180
|
+
});
|
|
1181
|
+
} else {
|
|
1182
|
+
await gh("POST", `${base}/issues/${ctx.prNumber}/comments`, ctx.token, {
|
|
1183
|
+
body
|
|
1184
|
+
});
|
|
1185
|
+
}
|
|
1186
|
+
}
|
|
1187
|
+
async function createCheckRun(ctx, opts) {
|
|
1188
|
+
await gh("POST", `${repoBase(ctx)}/check-runs`, ctx.token, {
|
|
1189
|
+
name: opts.name,
|
|
1190
|
+
head_sha: ctx.headSha,
|
|
1191
|
+
status: "completed",
|
|
1192
|
+
conclusion: opts.conclusion,
|
|
1193
|
+
output: { title: opts.title, summary: opts.summary }
|
|
1194
|
+
});
|
|
1195
|
+
}
|
|
1196
|
+
var GitHubApiError;
|
|
1197
|
+
var init_github = __esm({
|
|
1198
|
+
"src/publish/github.ts"() {
|
|
1199
|
+
"use strict";
|
|
1200
|
+
init_comment();
|
|
1201
|
+
GitHubApiError = class extends Error {
|
|
1202
|
+
status;
|
|
1203
|
+
constructor(status, message) {
|
|
1204
|
+
super(message);
|
|
1205
|
+
this.name = "GitHubApiError";
|
|
1206
|
+
this.status = status;
|
|
1207
|
+
}
|
|
1208
|
+
};
|
|
1209
|
+
}
|
|
1210
|
+
});
|
|
1211
|
+
|
|
1212
|
+
// src/publish/publish.ts
|
|
1213
|
+
async function publishToGitHub(run, record) {
|
|
1214
|
+
const ctx = resolvePublishContext();
|
|
1215
|
+
if (!ctx) {
|
|
1216
|
+
process.stdout.write(
|
|
1217
|
+
"veris: --github set but no GitHub PR context found (need GITHUB_TOKEN + a pull_request event); skipping publish.\n"
|
|
1218
|
+
);
|
|
1219
|
+
return;
|
|
1220
|
+
}
|
|
1221
|
+
try {
|
|
1222
|
+
await upsertComment(ctx, renderComment(run, record));
|
|
1223
|
+
await createCheckRun(ctx, {
|
|
1224
|
+
name: "VerisKit",
|
|
1225
|
+
conclusion: CONCLUSION[run.verdict.state],
|
|
1226
|
+
title: `VerisKit: ${run.verdict.state}`,
|
|
1227
|
+
summary: renderMarkdown(run, record)
|
|
1228
|
+
});
|
|
1229
|
+
process.stdout.write(
|
|
1230
|
+
`veris: published the verdict to PR #${ctx.prNumber}
|
|
1231
|
+
`
|
|
1232
|
+
);
|
|
1233
|
+
} catch (err) {
|
|
1234
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1235
|
+
process.stderr.write(`veris: GitHub publish failed: ${msg}
|
|
1236
|
+
`);
|
|
1237
|
+
}
|
|
1238
|
+
}
|
|
1239
|
+
var CONCLUSION;
|
|
1240
|
+
var init_publish = __esm({
|
|
1241
|
+
"src/publish/publish.ts"() {
|
|
1242
|
+
"use strict";
|
|
1243
|
+
init_markdown();
|
|
1244
|
+
init_comment();
|
|
1245
|
+
init_context();
|
|
1246
|
+
init_github();
|
|
1247
|
+
CONCLUSION = {
|
|
1248
|
+
verified: "success",
|
|
1249
|
+
failed: "failure",
|
|
1250
|
+
partial: "neutral"
|
|
1251
|
+
};
|
|
1252
|
+
}
|
|
1253
|
+
});
|
|
1254
|
+
|
|
1000
1255
|
// src/cli/commands/verify.ts
|
|
1001
1256
|
var verify_exports = {};
|
|
1002
1257
|
__export(verify_exports, {
|
|
1258
|
+
resolveChecks: () => resolveChecks,
|
|
1003
1259
|
runVerify: () => runVerify
|
|
1004
1260
|
});
|
|
1261
|
+
function resolveChecks(configChecks, project, opts) {
|
|
1262
|
+
let checks = configChecks?.length ? configChecks : DEFAULT_CHECKS;
|
|
1263
|
+
if (opts.browser && !checks.includes("browser")) {
|
|
1264
|
+
const cap = project.capabilities.find((c) => c.id === "browser");
|
|
1265
|
+
if (cap?.available) checks = [...checks, "browser"];
|
|
1266
|
+
}
|
|
1267
|
+
return checks;
|
|
1268
|
+
}
|
|
1005
1269
|
async function runVerify(root, opts = {}) {
|
|
1006
1270
|
const project = await detectProject(root);
|
|
1007
1271
|
const config = await loadConfig(root);
|
|
1008
|
-
const checks = config?.checks
|
|
1272
|
+
const checks = resolveChecks(config?.checks, project, opts);
|
|
1009
1273
|
const run = await runChecks(project, checks, root);
|
|
1010
1274
|
const git = await gitAnchor(root);
|
|
1011
1275
|
const logDigests = await digestLogs(run);
|
|
@@ -1020,6 +1284,7 @@ async function runVerify(root, opts = {}) {
|
|
|
1020
1284
|
await writeEvidence(runDir, record);
|
|
1021
1285
|
process.stdout.write(`${renderRun(run, record)}
|
|
1022
1286
|
`);
|
|
1287
|
+
if (opts.github) await publishToGitHub(run, record);
|
|
1023
1288
|
return verdictExitCode(run.verdict, opts);
|
|
1024
1289
|
}
|
|
1025
1290
|
var DEFAULT_CHECKS;
|
|
@@ -1033,6 +1298,7 @@ var init_verify = __esm({
|
|
|
1033
1298
|
init_record();
|
|
1034
1299
|
init_store();
|
|
1035
1300
|
init_changes();
|
|
1301
|
+
init_publish();
|
|
1036
1302
|
init_markdown();
|
|
1037
1303
|
init_terminal();
|
|
1038
1304
|
init_version();
|
|
@@ -1045,7 +1311,7 @@ var report_exports = {};
|
|
|
1045
1311
|
__export(report_exports, {
|
|
1046
1312
|
runReport: () => runReport
|
|
1047
1313
|
});
|
|
1048
|
-
import { readdirSync as readdirSync2, readFileSync as
|
|
1314
|
+
import { readdirSync as readdirSync2, readFileSync as readFileSync3 } from "fs";
|
|
1049
1315
|
import { join as join6 } from "path";
|
|
1050
1316
|
async function runReport(root) {
|
|
1051
1317
|
const dir = join6(root, ".veris", "reports");
|
|
@@ -1061,7 +1327,7 @@ async function runReport(root) {
|
|
|
1061
1327
|
process.stdout.write("No reports yet. Run `veris verify` first.\n");
|
|
1062
1328
|
return 0;
|
|
1063
1329
|
}
|
|
1064
|
-
process.stdout.write(`${
|
|
1330
|
+
process.stdout.write(`${readFileSync3(join6(dir, latest), "utf8")}
|
|
1065
1331
|
`);
|
|
1066
1332
|
return 0;
|
|
1067
1333
|
}
|
|
@@ -1176,7 +1442,7 @@ var init_discover = __esm({
|
|
|
1176
1442
|
});
|
|
1177
1443
|
|
|
1178
1444
|
// src/project-graph/scanner-resolver.ts
|
|
1179
|
-
import { existsSync as existsSync3, readFileSync as
|
|
1445
|
+
import { existsSync as existsSync3, readFileSync as readFileSync4, statSync as statSync2 } from "fs";
|
|
1180
1446
|
import { dirname, join as join8, relative as relative3, resolve } from "path";
|
|
1181
1447
|
function extractSpecifiers(text) {
|
|
1182
1448
|
const specs = [];
|
|
@@ -1215,7 +1481,7 @@ function resolveRelative(fromDir, spec) {
|
|
|
1215
1481
|
function scannerImports(root, file) {
|
|
1216
1482
|
let text;
|
|
1217
1483
|
try {
|
|
1218
|
-
text =
|
|
1484
|
+
text = readFileSync4(join8(root, file), "utf8");
|
|
1219
1485
|
} catch {
|
|
1220
1486
|
return [];
|
|
1221
1487
|
}
|
|
@@ -1239,7 +1505,7 @@ var init_scanner_resolver = __esm({
|
|
|
1239
1505
|
});
|
|
1240
1506
|
|
|
1241
1507
|
// src/project-graph/ts-resolver.ts
|
|
1242
|
-
import { existsSync as existsSync4, readFileSync as
|
|
1508
|
+
import { existsSync as existsSync4, readFileSync as readFileSync5 } from "fs";
|
|
1243
1509
|
import { createRequire } from "module";
|
|
1244
1510
|
import { join as join9, relative as relative4, sep as sep2 } from "path";
|
|
1245
1511
|
function hasClassicApi(mod) {
|
|
@@ -1266,7 +1532,7 @@ function loadCompilerOptions(tsmod, root) {
|
|
|
1266
1532
|
function tsImports(tsmod, root, options, file) {
|
|
1267
1533
|
let text;
|
|
1268
1534
|
try {
|
|
1269
|
-
text =
|
|
1535
|
+
text = readFileSync5(join9(root, file), "utf8");
|
|
1270
1536
|
} catch {
|
|
1271
1537
|
return [];
|
|
1272
1538
|
}
|
|
@@ -1518,6 +1784,7 @@ async function runAffected(root, opts = {}) {
|
|
|
1518
1784
|
`);
|
|
1519
1785
|
if (narrowedNote) process.stdout.write(`${narrowedNote}
|
|
1520
1786
|
`);
|
|
1787
|
+
if (opts.github) await publishToGitHub(run, record);
|
|
1521
1788
|
return verdictExitCode(run.verdict, opts);
|
|
1522
1789
|
}
|
|
1523
1790
|
var init_affected = __esm({
|
|
@@ -1530,6 +1797,7 @@ var init_affected = __esm({
|
|
|
1530
1797
|
init_record();
|
|
1531
1798
|
init_store();
|
|
1532
1799
|
init_changes();
|
|
1800
|
+
init_publish();
|
|
1533
1801
|
init_markdown();
|
|
1534
1802
|
init_terminal();
|
|
1535
1803
|
init_version();
|
|
@@ -2143,7 +2411,7 @@ __export(evidence_exports, {
|
|
|
2143
2411
|
runEvidenceSign: () => runEvidenceSign,
|
|
2144
2412
|
runEvidenceVerify: () => runEvidenceVerify
|
|
2145
2413
|
});
|
|
2146
|
-
import { chmodSync, existsSync as existsSync6, readFileSync as
|
|
2414
|
+
import { chmodSync, existsSync as existsSync6, readFileSync as readFileSync6, writeFileSync } from "fs";
|
|
2147
2415
|
import { writeFile as writeFile4 } from "fs/promises";
|
|
2148
2416
|
import { basename as basename3, dirname as dirname2, join as join12 } from "path";
|
|
2149
2417
|
import pc5 from "picocolors";
|
|
@@ -2151,7 +2419,7 @@ async function runEvidenceVerify(path, opts = {}) {
|
|
|
2151
2419
|
let expectedPubKeyPem;
|
|
2152
2420
|
if (opts.pubkey) {
|
|
2153
2421
|
try {
|
|
2154
|
-
expectedPubKeyPem =
|
|
2422
|
+
expectedPubKeyPem = readFileSync6(opts.pubkey, "utf8");
|
|
2155
2423
|
} catch {
|
|
2156
2424
|
process.stderr.write(`veris: cannot read public key at ${opts.pubkey}
|
|
2157
2425
|
`);
|
|
@@ -2206,8 +2474,8 @@ async function runEvidenceKeygen(root, opts = {}) {
|
|
|
2206
2474
|
);
|
|
2207
2475
|
return 1;
|
|
2208
2476
|
}
|
|
2209
|
-
const { mkdir:
|
|
2210
|
-
await
|
|
2477
|
+
const { mkdir: mkdir3 } = await import("fs/promises");
|
|
2478
|
+
await mkdir3(dirname2(out), { recursive: true });
|
|
2211
2479
|
const kp = generateKeyPair();
|
|
2212
2480
|
writeFileSync(out, kp.privateKeyPem, { mode: 384 });
|
|
2213
2481
|
chmodSync(out, 384);
|
|
@@ -2228,7 +2496,7 @@ async function runEvidenceSign(evidencePath, opts = {}) {
|
|
|
2228
2496
|
privateKeyPem = process.env.VERISKIT_SIGNING_KEY;
|
|
2229
2497
|
} else if (opts.key) {
|
|
2230
2498
|
try {
|
|
2231
|
-
privateKeyPem =
|
|
2499
|
+
privateKeyPem = readFileSync6(opts.key, "utf8");
|
|
2232
2500
|
} catch {
|
|
2233
2501
|
process.stderr.write(`veris: cannot read signing key at ${opts.key}
|
|
2234
2502
|
`);
|
|
@@ -2242,7 +2510,7 @@ async function runEvidenceSign(evidencePath, opts = {}) {
|
|
|
2242
2510
|
}
|
|
2243
2511
|
let record;
|
|
2244
2512
|
try {
|
|
2245
|
-
record = JSON.parse(
|
|
2513
|
+
record = JSON.parse(readFileSync6(evidencePath, "utf8"));
|
|
2246
2514
|
} catch {
|
|
2247
2515
|
process.stderr.write(`veris: cannot read evidence at ${evidencePath}
|
|
2248
2516
|
`);
|
|
@@ -2285,7 +2553,7 @@ async function runEvidenceBundle(root, opts = {}) {
|
|
|
2285
2553
|
let record;
|
|
2286
2554
|
try {
|
|
2287
2555
|
record = JSON.parse(
|
|
2288
|
-
|
|
2556
|
+
readFileSync6(join12(runDir, "evidence.json"), "utf8")
|
|
2289
2557
|
);
|
|
2290
2558
|
} catch {
|
|
2291
2559
|
process.stderr.write(`veris: no evidence.json in ${runDir}
|
|
@@ -2294,7 +2562,7 @@ async function runEvidenceBundle(root, opts = {}) {
|
|
|
2294
2562
|
}
|
|
2295
2563
|
let report = "";
|
|
2296
2564
|
try {
|
|
2297
|
-
report =
|
|
2565
|
+
report = readFileSync6(
|
|
2298
2566
|
join12(root, ".veris", "reports", `verify-${record.id}.md`),
|
|
2299
2567
|
"utf8"
|
|
2300
2568
|
);
|
|
@@ -2306,7 +2574,7 @@ async function runEvidenceBundle(root, opts = {}) {
|
|
|
2306
2574
|
const sigPath = join12(runDir, "evidence.json.sig");
|
|
2307
2575
|
if (existsSync6(sigPath)) {
|
|
2308
2576
|
try {
|
|
2309
|
-
signature = JSON.parse(
|
|
2577
|
+
signature = JSON.parse(readFileSync6(sigPath, "utf8"));
|
|
2310
2578
|
} catch {
|
|
2311
2579
|
}
|
|
2312
2580
|
}
|
|
@@ -2330,7 +2598,7 @@ async function runEvidenceShow(root, path) {
|
|
|
2330
2598
|
}
|
|
2331
2599
|
let record;
|
|
2332
2600
|
try {
|
|
2333
|
-
record = JSON.parse(
|
|
2601
|
+
record = JSON.parse(readFileSync6(target, "utf8"));
|
|
2334
2602
|
} catch {
|
|
2335
2603
|
process.stderr.write(`veris: cannot read evidence at ${target}
|
|
2336
2604
|
`);
|
|
@@ -2365,6 +2633,181 @@ var init_evidence = __esm({
|
|
|
2365
2633
|
}
|
|
2366
2634
|
});
|
|
2367
2635
|
|
|
2636
|
+
// src/publish/badge.ts
|
|
2637
|
+
function buildBadge(verdict) {
|
|
2638
|
+
const s = STYLE[verdict.state];
|
|
2639
|
+
return {
|
|
2640
|
+
schemaVersion: 1,
|
|
2641
|
+
label: "veriskit",
|
|
2642
|
+
message: s.message,
|
|
2643
|
+
color: s.color
|
|
2644
|
+
};
|
|
2645
|
+
}
|
|
2646
|
+
var STYLE;
|
|
2647
|
+
var init_badge = __esm({
|
|
2648
|
+
"src/publish/badge.ts"() {
|
|
2649
|
+
"use strict";
|
|
2650
|
+
STYLE = {
|
|
2651
|
+
verified: { message: "verified", color: "14b8a6" },
|
|
2652
|
+
failed: { message: "failed", color: "e5484d" },
|
|
2653
|
+
partial: { message: "partial", color: "f5a623" }
|
|
2654
|
+
};
|
|
2655
|
+
}
|
|
2656
|
+
});
|
|
2657
|
+
|
|
2658
|
+
// src/cli/commands/badge.ts
|
|
2659
|
+
var badge_exports = {};
|
|
2660
|
+
__export(badge_exports, {
|
|
2661
|
+
runBadge: () => runBadge
|
|
2662
|
+
});
|
|
2663
|
+
import { readFileSync as readFileSync7 } from "fs";
|
|
2664
|
+
import { mkdir as mkdir2, writeFile as writeFile5 } from "fs/promises";
|
|
2665
|
+
import { dirname as dirname3, join as join13 } from "path";
|
|
2666
|
+
async function runBadge(root, opts = {}) {
|
|
2667
|
+
const dir = latestRunDir(root);
|
|
2668
|
+
if (!dir) {
|
|
2669
|
+
process.stdout.write("No runs yet. Run `veris verify` first.\n");
|
|
2670
|
+
return 1;
|
|
2671
|
+
}
|
|
2672
|
+
let record;
|
|
2673
|
+
try {
|
|
2674
|
+
record = JSON.parse(
|
|
2675
|
+
readFileSync7(join13(dir, "evidence.json"), "utf8")
|
|
2676
|
+
);
|
|
2677
|
+
} catch {
|
|
2678
|
+
process.stderr.write(`veris: no evidence.json in ${dir}
|
|
2679
|
+
`);
|
|
2680
|
+
return 1;
|
|
2681
|
+
}
|
|
2682
|
+
const out = opts.out ?? join13(root, ".veris", "badge.json");
|
|
2683
|
+
await mkdir2(dirname3(out), { recursive: true });
|
|
2684
|
+
await writeFile5(
|
|
2685
|
+
out,
|
|
2686
|
+
`${JSON.stringify(buildBadge(record.verdict), null, 2)}
|
|
2687
|
+
`,
|
|
2688
|
+
"utf8"
|
|
2689
|
+
);
|
|
2690
|
+
process.stdout.write(`Wrote badge ${out}
|
|
2691
|
+
`);
|
|
2692
|
+
return 0;
|
|
2693
|
+
}
|
|
2694
|
+
var init_badge2 = __esm({
|
|
2695
|
+
"src/cli/commands/badge.ts"() {
|
|
2696
|
+
"use strict";
|
|
2697
|
+
init_store();
|
|
2698
|
+
init_badge();
|
|
2699
|
+
}
|
|
2700
|
+
});
|
|
2701
|
+
|
|
2702
|
+
// src/history/flaky.ts
|
|
2703
|
+
function detectFlaky(records) {
|
|
2704
|
+
const byId = /* @__PURE__ */ new Map();
|
|
2705
|
+
for (const rec of records) {
|
|
2706
|
+
for (const c of rec.checks ?? []) {
|
|
2707
|
+
const arr = byId.get(c.id) ?? [];
|
|
2708
|
+
arr.push(c.status);
|
|
2709
|
+
byId.set(c.id, arr);
|
|
2710
|
+
}
|
|
2711
|
+
}
|
|
2712
|
+
const flaky = [];
|
|
2713
|
+
for (const [id, statuses] of byId) {
|
|
2714
|
+
if (statuses.includes("passed") && statuses.includes("failed")) {
|
|
2715
|
+
flaky.push({ id, statuses });
|
|
2716
|
+
}
|
|
2717
|
+
}
|
|
2718
|
+
return flaky;
|
|
2719
|
+
}
|
|
2720
|
+
var init_flaky = __esm({
|
|
2721
|
+
"src/history/flaky.ts"() {
|
|
2722
|
+
"use strict";
|
|
2723
|
+
}
|
|
2724
|
+
});
|
|
2725
|
+
|
|
2726
|
+
// src/history/read.ts
|
|
2727
|
+
import { readdirSync as readdirSync5, readFileSync as readFileSync8 } from "fs";
|
|
2728
|
+
import { join as join14 } from "path";
|
|
2729
|
+
function loadRuns(root, limit = 20) {
|
|
2730
|
+
const runs = join14(root, ".veris", "runs");
|
|
2731
|
+
let names;
|
|
2732
|
+
try {
|
|
2733
|
+
names = readdirSync5(runs, { withFileTypes: true }).filter((d) => d.isDirectory() && d.name !== "watch").map((d) => d.name);
|
|
2734
|
+
} catch {
|
|
2735
|
+
return [];
|
|
2736
|
+
}
|
|
2737
|
+
const records = [];
|
|
2738
|
+
for (const name of names) {
|
|
2739
|
+
try {
|
|
2740
|
+
const rec = JSON.parse(
|
|
2741
|
+
readFileSync8(join14(runs, name, "evidence.json"), "utf8")
|
|
2742
|
+
);
|
|
2743
|
+
if (rec?.schema === "veriskit/evidence@1") records.push(rec);
|
|
2744
|
+
} catch {
|
|
2745
|
+
}
|
|
2746
|
+
}
|
|
2747
|
+
records.sort(
|
|
2748
|
+
(a, b) => a.startedAt < b.startedAt ? 1 : a.startedAt > b.startedAt ? -1 : 0
|
|
2749
|
+
);
|
|
2750
|
+
return records.slice(0, limit);
|
|
2751
|
+
}
|
|
2752
|
+
var init_read = __esm({
|
|
2753
|
+
"src/history/read.ts"() {
|
|
2754
|
+
"use strict";
|
|
2755
|
+
}
|
|
2756
|
+
});
|
|
2757
|
+
|
|
2758
|
+
// src/cli/commands/log.ts
|
|
2759
|
+
var log_exports = {};
|
|
2760
|
+
__export(log_exports, {
|
|
2761
|
+
runLog: () => runLog
|
|
2762
|
+
});
|
|
2763
|
+
async function runLog(root, opts = {}) {
|
|
2764
|
+
const limit = Number.isFinite(opts.limit) && opts.limit > 0 ? opts.limit : 20;
|
|
2765
|
+
const records = loadRuns(root, limit);
|
|
2766
|
+
if (records.length === 0) {
|
|
2767
|
+
process.stdout.write("No runs yet. Run `veris verify` first.\n");
|
|
2768
|
+
return 0;
|
|
2769
|
+
}
|
|
2770
|
+
if (opts.flaky) {
|
|
2771
|
+
const flaky = detectFlaky(records);
|
|
2772
|
+
if (flaky.length === 0) {
|
|
2773
|
+
process.stdout.write(
|
|
2774
|
+
`No flaky checks in the last ${records.length} run(s).
|
|
2775
|
+
`
|
|
2776
|
+
);
|
|
2777
|
+
return 0;
|
|
2778
|
+
}
|
|
2779
|
+
process.stdout.write(
|
|
2780
|
+
`Flaky checks (both passed and failed in the last ${records.length} run(s), newest first):
|
|
2781
|
+
`
|
|
2782
|
+
);
|
|
2783
|
+
for (const f of flaky) {
|
|
2784
|
+
process.stdout.write(` ${f.id.padEnd(8)} ${f.statuses.join(" ")}
|
|
2785
|
+
`);
|
|
2786
|
+
}
|
|
2787
|
+
return 0;
|
|
2788
|
+
}
|
|
2789
|
+
for (const rec of records) {
|
|
2790
|
+
const date = rec.startedAt.slice(0, 19).replace("T", " ");
|
|
2791
|
+
const checks = (rec.checks ?? []).map((c) => `${c.id}:${c.status}`).join(" ");
|
|
2792
|
+
const commit = rec.git ? rec.git.commit.slice(0, 7) : "-";
|
|
2793
|
+
process.stdout.write(
|
|
2794
|
+
`${date} ${rec.verdict.state.padEnd(9)} ${checks} ${commit}
|
|
2795
|
+
`
|
|
2796
|
+
);
|
|
2797
|
+
}
|
|
2798
|
+
process.stdout.write(
|
|
2799
|
+
"\nHistory is local to this machine (.veris/runs is gitignored).\n"
|
|
2800
|
+
);
|
|
2801
|
+
return 0;
|
|
2802
|
+
}
|
|
2803
|
+
var init_log = __esm({
|
|
2804
|
+
"src/cli/commands/log.ts"() {
|
|
2805
|
+
"use strict";
|
|
2806
|
+
init_flaky();
|
|
2807
|
+
init_read();
|
|
2808
|
+
}
|
|
2809
|
+
});
|
|
2810
|
+
|
|
2368
2811
|
// src/cli/index.ts
|
|
2369
2812
|
init_version();
|
|
2370
2813
|
import { realpathSync } from "fs";
|
|
@@ -2387,23 +2830,36 @@ function buildCli() {
|
|
|
2387
2830
|
const { runInit: runInit2 } = await Promise.resolve().then(() => (init_init(), init_exports));
|
|
2388
2831
|
process.exitCode = await runInit2(process.cwd());
|
|
2389
2832
|
});
|
|
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
|
-
|
|
2833
|
+
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(
|
|
2834
|
+
"--github",
|
|
2835
|
+
"Publish the verdict to the GitHub PR (comment + check run)"
|
|
2836
|
+
).option("--browser", "Also run browser tests (Playwright), when available").action(
|
|
2837
|
+
async (opts) => {
|
|
2838
|
+
const { runVerify: runVerify2 } = await Promise.resolve().then(() => (init_verify(), verify_exports));
|
|
2839
|
+
process.exitCode = await runVerify2(process.cwd(), {
|
|
2840
|
+
partialOk: opts.partialOk,
|
|
2841
|
+
github: opts.github,
|
|
2842
|
+
browser: opts.browser
|
|
2843
|
+
});
|
|
2844
|
+
}
|
|
2845
|
+
);
|
|
2396
2846
|
cli.command("report", "Print the latest verification report").action(async () => {
|
|
2397
2847
|
const { runReport: runReport2 } = await Promise.resolve().then(() => (init_report(), report_exports));
|
|
2398
2848
|
process.exitCode = await runReport2(process.cwd());
|
|
2399
2849
|
});
|
|
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
|
-
|
|
2850
|
+
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(
|
|
2851
|
+
"--github",
|
|
2852
|
+
"Publish the verdict to the GitHub PR (comment + check run)"
|
|
2853
|
+
).action(
|
|
2854
|
+
async (opts) => {
|
|
2855
|
+
const { runAffected: runAffected2 } = await Promise.resolve().then(() => (init_affected(), affected_exports));
|
|
2856
|
+
process.exitCode = await runAffected2(process.cwd(), {
|
|
2857
|
+
base: opts.base,
|
|
2858
|
+
partialOk: opts.partialOk,
|
|
2859
|
+
github: opts.github
|
|
2860
|
+
});
|
|
2861
|
+
}
|
|
2862
|
+
);
|
|
2407
2863
|
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
2864
|
const { runWatch: runWatch2 } = await Promise.resolve().then(() => (init_watch(), watch_exports));
|
|
2409
2865
|
process.exitCode = await runWatch2(process.cwd(), { poll: opts.poll });
|
|
@@ -2465,6 +2921,20 @@ function buildCli() {
|
|
|
2465
2921
|
}
|
|
2466
2922
|
}
|
|
2467
2923
|
);
|
|
2924
|
+
cli.command(
|
|
2925
|
+
"badge",
|
|
2926
|
+
"Write a shields.io endpoint JSON from the latest verdict"
|
|
2927
|
+
).option("--out <file>", "Output path (default .veris/badge.json)").action(async (opts) => {
|
|
2928
|
+
const { runBadge: runBadge2 } = await Promise.resolve().then(() => (init_badge2(), badge_exports));
|
|
2929
|
+
process.exitCode = await runBadge2(process.cwd(), { out: opts.out });
|
|
2930
|
+
});
|
|
2931
|
+
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) => {
|
|
2932
|
+
const { runLog: runLog2 } = await Promise.resolve().then(() => (init_log(), log_exports));
|
|
2933
|
+
process.exitCode = await runLog2(process.cwd(), {
|
|
2934
|
+
limit: opts.limit ? Number(opts.limit) : void 0,
|
|
2935
|
+
flaky: opts.flaky
|
|
2936
|
+
});
|
|
2937
|
+
});
|
|
2468
2938
|
return { raw: cli, version: VERSION };
|
|
2469
2939
|
}
|
|
2470
2940
|
async function main(argv2) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "veriskit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.1",
|
|
4
4
|
"description": "The fastest way to prove your software works — a zero-config verification CLI.",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"test": "vitest run",
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
"bugs": {
|
|
20
20
|
"url": "https://github.com/abhiyoheswaran1/veris/issues"
|
|
21
21
|
},
|
|
22
|
-
"homepage": "https://
|
|
22
|
+
"homepage": "https://www.baseframelabs.com/apps/veriskit",
|
|
23
23
|
"bin": {
|
|
24
24
|
"veris": "bin/veris"
|
|
25
25
|
},
|