veriskit 0.5.1 → 0.6.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 +6 -0
- package/README.md +21 -0
- package/bin/veris +1 -1
- package/dist/cli.d.ts +9 -0
- package/dist/cli.js +2982 -0
- package/dist/index.d.ts +190 -0
- package/dist/index.js +1100 -2620
- package/package.json +14 -1
package/dist/index.js
CHANGED
|
@@ -8,198 +8,6 @@ var __export = (target, all) => {
|
|
|
8
8
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
9
|
};
|
|
10
10
|
|
|
11
|
-
// src/version.ts
|
|
12
|
-
import { readFileSync } from "fs";
|
|
13
|
-
import { fileURLToPath } from "url";
|
|
14
|
-
var pkgUrl, VERSION;
|
|
15
|
-
var init_version = __esm({
|
|
16
|
-
"src/version.ts"() {
|
|
17
|
-
"use strict";
|
|
18
|
-
pkgUrl = new URL("../package.json", import.meta.url);
|
|
19
|
-
VERSION = JSON.parse(
|
|
20
|
-
readFileSync(fileURLToPath(pkgUrl), "utf8")
|
|
21
|
-
).version;
|
|
22
|
-
}
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
// src/util/fs-safe.ts
|
|
26
|
-
import { existsSync } from "fs";
|
|
27
|
-
import { mkdir, readFile, writeFile } from "fs/promises";
|
|
28
|
-
async function ensureDir(path) {
|
|
29
|
-
await mkdir(path, { recursive: true });
|
|
30
|
-
}
|
|
31
|
-
async function writeIfAbsent(path, content) {
|
|
32
|
-
if (existsSync(path)) return false;
|
|
33
|
-
await writeFile(path, content, "utf8");
|
|
34
|
-
return true;
|
|
35
|
-
}
|
|
36
|
-
async function readJsonIfExists(path) {
|
|
37
|
-
if (!existsSync(path)) return null;
|
|
38
|
-
try {
|
|
39
|
-
return JSON.parse(await readFile(path, "utf8"));
|
|
40
|
-
} catch {
|
|
41
|
-
return null;
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
var init_fs_safe = __esm({
|
|
45
|
-
"src/util/fs-safe.ts"() {
|
|
46
|
-
"use strict";
|
|
47
|
-
}
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
// src/config/detect.ts
|
|
51
|
-
import { existsSync as existsSync2 } from "fs";
|
|
52
|
-
import { join } from "path";
|
|
53
|
-
function detectPackageManager(root) {
|
|
54
|
-
if (existsSync2(join(root, "pnpm-lock.yaml"))) return "pnpm";
|
|
55
|
-
if (existsSync2(join(root, "yarn.lock"))) return "yarn";
|
|
56
|
-
if (existsSync2(join(root, "bun.lockb"))) return "bun";
|
|
57
|
-
return "npm";
|
|
58
|
-
}
|
|
59
|
-
async function detectProject(root) {
|
|
60
|
-
const pkg = await readJsonIfExists(join(root, "package.json")) ?? {};
|
|
61
|
-
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
62
|
-
const has = (name) => name in deps;
|
|
63
|
-
const scripts = pkg.scripts ?? {};
|
|
64
|
-
const languages = existsSync2(join(root, "tsconfig.json")) ? ["typescript", "javascript"] : ["javascript"];
|
|
65
|
-
const frameworks = ["next", "vite", "react"].filter(has);
|
|
66
|
-
const capabilities = [
|
|
67
|
-
detectTypes(root, has),
|
|
68
|
-
detectUnit(has, scripts),
|
|
69
|
-
detectLint(root, has),
|
|
70
|
-
detectBrowser(root, has)
|
|
71
|
-
];
|
|
72
|
-
return {
|
|
73
|
-
root,
|
|
74
|
-
name: pkg.name ?? void 0,
|
|
75
|
-
packageManager: detectPackageManager(root),
|
|
76
|
-
frameworks,
|
|
77
|
-
languages,
|
|
78
|
-
scripts,
|
|
79
|
-
capabilities
|
|
80
|
-
};
|
|
81
|
-
}
|
|
82
|
-
function detectTypes(root, has) {
|
|
83
|
-
if (existsSync2(join(root, "tsconfig.json")) && has("typescript"))
|
|
84
|
-
return { id: "types", available: true, runner: "tsc" };
|
|
85
|
-
return {
|
|
86
|
-
id: "types",
|
|
87
|
-
available: false,
|
|
88
|
-
reason: "no tsconfig.json + typescript dependency"
|
|
89
|
-
};
|
|
90
|
-
}
|
|
91
|
-
function detectUnit(has, scripts) {
|
|
92
|
-
if (has("vitest")) return { id: "unit", available: true, runner: "vitest" };
|
|
93
|
-
if (has("jest")) return { id: "unit", available: true, runner: "jest" };
|
|
94
|
-
if (Object.values(scripts).some((s) => s.includes("node --test")))
|
|
95
|
-
return { id: "unit", available: true, runner: "node-test" };
|
|
96
|
-
return { id: "unit", available: false, reason: "no test runner detected" };
|
|
97
|
-
}
|
|
98
|
-
function detectLint(root, has) {
|
|
99
|
-
if (existsSync2(join(root, "biome.json")) || has("@biomejs/biome"))
|
|
100
|
-
return { id: "lint", available: true, runner: "biome" };
|
|
101
|
-
if (has("eslint") || [".eslintrc", ".eslintrc.json", ".eslintrc.cjs", "eslint.config.js"].some(
|
|
102
|
-
(f) => existsSync2(join(root, f))
|
|
103
|
-
))
|
|
104
|
-
return { id: "lint", available: true, runner: "eslint" };
|
|
105
|
-
return { id: "lint", available: false, reason: "no linter configured" };
|
|
106
|
-
}
|
|
107
|
-
function detectBrowser(root, has) {
|
|
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
|
-
}
|
|
111
|
-
return {
|
|
112
|
-
id: "browser",
|
|
113
|
-
available: false,
|
|
114
|
-
reason: "no browser runner configured"
|
|
115
|
-
};
|
|
116
|
-
}
|
|
117
|
-
var init_detect = __esm({
|
|
118
|
-
"src/config/detect.ts"() {
|
|
119
|
-
"use strict";
|
|
120
|
-
init_fs_safe();
|
|
121
|
-
}
|
|
122
|
-
});
|
|
123
|
-
|
|
124
|
-
// src/util/env.ts
|
|
125
|
-
import { platform } from "os";
|
|
126
|
-
function detectCI() {
|
|
127
|
-
return process.env.CI === "true" || process.env.CI === "1";
|
|
128
|
-
}
|
|
129
|
-
function getEnvironmentInfo(pm) {
|
|
130
|
-
return {
|
|
131
|
-
os: platform(),
|
|
132
|
-
node: process.version,
|
|
133
|
-
pm,
|
|
134
|
-
ci: detectCI(),
|
|
135
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
136
|
-
};
|
|
137
|
-
}
|
|
138
|
-
var init_env = __esm({
|
|
139
|
-
"src/util/env.ts"() {
|
|
140
|
-
"use strict";
|
|
141
|
-
}
|
|
142
|
-
});
|
|
143
|
-
|
|
144
|
-
// src/cli/tty.ts
|
|
145
|
-
function isPlain() {
|
|
146
|
-
return detectCI() || !process.stdout.isTTY;
|
|
147
|
-
}
|
|
148
|
-
var init_tty = __esm({
|
|
149
|
-
"src/cli/tty.ts"() {
|
|
150
|
-
"use strict";
|
|
151
|
-
init_env();
|
|
152
|
-
}
|
|
153
|
-
});
|
|
154
|
-
|
|
155
|
-
// src/cli/commands/doctor.ts
|
|
156
|
-
var doctor_exports = {};
|
|
157
|
-
__export(doctor_exports, {
|
|
158
|
-
renderDoctor: () => renderDoctor,
|
|
159
|
-
runDoctor: () => runDoctor
|
|
160
|
-
});
|
|
161
|
-
import pc from "picocolors";
|
|
162
|
-
function renderDoctor(project, env) {
|
|
163
|
-
const plain = isPlain();
|
|
164
|
-
const ok = (s) => plain ? s : pc.green(s);
|
|
165
|
-
const dim = (s) => plain ? s : pc.dim(s);
|
|
166
|
-
const lines = [];
|
|
167
|
-
lines.push("VerisKit doctor");
|
|
168
|
-
lines.push("");
|
|
169
|
-
lines.push(`Package manager ${project.packageManager}`);
|
|
170
|
-
lines.push(`Node ${env.node}`);
|
|
171
|
-
lines.push(`Languages ${project.languages.join(", ")}`);
|
|
172
|
-
if (project.frameworks.length)
|
|
173
|
-
lines.push(`Frameworks ${project.frameworks.join(", ")}`);
|
|
174
|
-
lines.push("");
|
|
175
|
-
lines.push("Capabilities");
|
|
176
|
-
for (const c of project.capabilities) {
|
|
177
|
-
if (c.available) {
|
|
178
|
-
lines.push(` ${ok("\u2713")} ${c.id.padEnd(8)} ${dim(`via ${c.runner}`)}`);
|
|
179
|
-
} else {
|
|
180
|
-
lines.push(
|
|
181
|
-
` ${dim("\u2298")} ${c.id.padEnd(8)} ${dim(`skipped \u2014 ${c.reason}`)}`
|
|
182
|
-
);
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
return lines.join("\n");
|
|
186
|
-
}
|
|
187
|
-
async function runDoctor(root) {
|
|
188
|
-
const project = await detectProject(root);
|
|
189
|
-
const env = getEnvironmentInfo(project.packageManager);
|
|
190
|
-
process.stdout.write(`${renderDoctor(project, env)}
|
|
191
|
-
`);
|
|
192
|
-
return 0;
|
|
193
|
-
}
|
|
194
|
-
var init_doctor = __esm({
|
|
195
|
-
"src/cli/commands/doctor.ts"() {
|
|
196
|
-
"use strict";
|
|
197
|
-
init_detect();
|
|
198
|
-
init_env();
|
|
199
|
-
init_tty();
|
|
200
|
-
}
|
|
201
|
-
});
|
|
202
|
-
|
|
203
11
|
// src/evidence/record.ts
|
|
204
12
|
import { createHash } from "crypto";
|
|
205
13
|
import { basename } from "path";
|
|
@@ -271,1073 +79,637 @@ var init_record = __esm({
|
|
|
271
79
|
}
|
|
272
80
|
});
|
|
273
81
|
|
|
274
|
-
// src/
|
|
275
|
-
import { readdirSync } from "fs";
|
|
276
|
-
import {
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
counter += 1;
|
|
280
|
-
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
281
|
-
return `${stamp}-${counter}`;
|
|
282
|
-
}
|
|
283
|
-
async function createRunDir(root, id) {
|
|
284
|
-
const dir = join2(root, ".veris", "runs", id);
|
|
285
|
-
await ensureDir(dir);
|
|
286
|
-
return dir;
|
|
287
|
-
}
|
|
288
|
-
async function writeLog(runDir, checkId, content) {
|
|
289
|
-
const ref = join2(runDir, `${checkId}.log`);
|
|
290
|
-
await writeFile2(ref, content, "utf8");
|
|
291
|
-
return ref;
|
|
292
|
-
}
|
|
293
|
-
async function writeReport(root, id, markdown) {
|
|
294
|
-
const dir = join2(root, ".veris", "reports");
|
|
295
|
-
await ensureDir(dir);
|
|
296
|
-
const ref = join2(dir, `verify-${id}.md`);
|
|
297
|
-
await writeFile2(ref, markdown, "utf8");
|
|
298
|
-
return ref;
|
|
82
|
+
// src/project-graph/discover.ts
|
|
83
|
+
import { readdirSync as readdirSync2, statSync } from "fs";
|
|
84
|
+
import { join as join5, relative as relative2, sep } from "path";
|
|
85
|
+
function toPosix(p) {
|
|
86
|
+
return sep === "/" ? p : p.split(sep).join("/");
|
|
299
87
|
}
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
return ref;
|
|
88
|
+
function classify(rel) {
|
|
89
|
+
if (TEST_RE2.test(rel)) return "test";
|
|
90
|
+
if (CONFIG_RE2.test(rel)) return "config";
|
|
91
|
+
return "source";
|
|
305
92
|
}
|
|
306
|
-
|
|
307
|
-
const out =
|
|
308
|
-
|
|
309
|
-
|
|
93
|
+
function discoverFiles(root) {
|
|
94
|
+
const out = [];
|
|
95
|
+
const walk = (dir) => {
|
|
96
|
+
let entries;
|
|
310
97
|
try {
|
|
311
|
-
|
|
98
|
+
entries = readdirSync2(dir);
|
|
312
99
|
} catch {
|
|
100
|
+
return;
|
|
313
101
|
}
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
102
|
+
for (const name of entries) {
|
|
103
|
+
const abs = join5(dir, name);
|
|
104
|
+
const rel = toPosix(relative2(root, abs));
|
|
105
|
+
if (IGNORE.test(rel)) continue;
|
|
106
|
+
let st;
|
|
107
|
+
try {
|
|
108
|
+
st = statSync(abs);
|
|
109
|
+
} catch {
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
if (st.isDirectory()) {
|
|
113
|
+
walk(abs);
|
|
114
|
+
} else if (CODE_RE.test(rel)) {
|
|
115
|
+
out.push({ file: rel, kind: classify(rel) });
|
|
116
|
+
}
|
|
323
117
|
}
|
|
118
|
+
};
|
|
119
|
+
walk(root);
|
|
120
|
+
return out.sort((a, b) => a.file.localeCompare(b.file));
|
|
121
|
+
}
|
|
122
|
+
var IGNORE, CODE_RE, TEST_RE2, CONFIG_RE2;
|
|
123
|
+
var init_discover = __esm({
|
|
124
|
+
"src/project-graph/discover.ts"() {
|
|
125
|
+
"use strict";
|
|
126
|
+
IGNORE = /(^|\/)(\.git|\.claude|\.veris|\.agentloop|\.agentflight|node_modules|dist|coverage|build|fixtures|__fixtures__)(\/|$)/;
|
|
127
|
+
CODE_RE = /\.[cm]?[jt]sx?$/;
|
|
128
|
+
TEST_RE2 = /(\.(test|spec)\.[cm]?[jt]sx?$)|(^|\/)(test|tests|__tests__)\//;
|
|
129
|
+
CONFIG_RE2 = /(^|\/)([^/]+\.config\.[cm]?[jt]sx?)$/;
|
|
324
130
|
}
|
|
325
|
-
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
// src/project-graph/scanner-resolver.ts
|
|
134
|
+
import { existsSync as existsSync3, readFileSync as readFileSync2, statSync as statSync2 } from "fs";
|
|
135
|
+
import { dirname, join as join6, relative as relative3, resolve } from "path";
|
|
136
|
+
function extractSpecifiers(text) {
|
|
137
|
+
const specs = [];
|
|
138
|
+
SPEC_RE.lastIndex = 0;
|
|
139
|
+
let m;
|
|
140
|
+
m = SPEC_RE.exec(text);
|
|
141
|
+
while (m !== null) {
|
|
142
|
+
const s = m[1] ?? m[2] ?? m[3] ?? m[4];
|
|
143
|
+
if (s) specs.push(s);
|
|
144
|
+
m = SPEC_RE.exec(text);
|
|
145
|
+
}
|
|
146
|
+
return specs;
|
|
326
147
|
}
|
|
327
|
-
function
|
|
328
|
-
const runs = join2(root, ".veris", "runs");
|
|
148
|
+
function isFile(p) {
|
|
329
149
|
try {
|
|
330
|
-
|
|
331
|
-
const latest = dirs.at(-1);
|
|
332
|
-
return latest ? join2(runs, latest) : null;
|
|
150
|
+
return statSync2(p).isFile();
|
|
333
151
|
} catch {
|
|
334
|
-
return
|
|
152
|
+
return false;
|
|
335
153
|
}
|
|
336
154
|
}
|
|
337
|
-
|
|
338
|
-
const
|
|
339
|
-
|
|
340
|
-
|
|
155
|
+
function resolveRelative(fromDir, spec) {
|
|
156
|
+
const base = resolve(fromDir, spec);
|
|
157
|
+
const candidates = [base];
|
|
158
|
+
for (const ext of EXTS) candidates.push(base + ext);
|
|
159
|
+
const extMatch = base.match(/\.[cm]?[jt]sx?$/);
|
|
160
|
+
if (extMatch) {
|
|
161
|
+
const noExt = base.slice(0, -extMatch[0].length);
|
|
162
|
+
for (const ext of EXTS) candidates.push(noExt + ext);
|
|
163
|
+
}
|
|
164
|
+
for (const ext of EXTS) candidates.push(join6(base, `index${ext}`));
|
|
165
|
+
for (const c of candidates) {
|
|
166
|
+
if (existsSync3(c) && isFile(c)) return c;
|
|
167
|
+
}
|
|
168
|
+
return null;
|
|
341
169
|
}
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
counter = 0;
|
|
170
|
+
function scannerImports(root, file) {
|
|
171
|
+
let text;
|
|
172
|
+
try {
|
|
173
|
+
text = readFileSync2(join6(root, file), "utf8");
|
|
174
|
+
} catch {
|
|
175
|
+
return [];
|
|
349
176
|
}
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
cwd: opts.cwd,
|
|
359
|
-
env: opts.env ?? process.env,
|
|
360
|
-
shell: false
|
|
361
|
-
});
|
|
362
|
-
let stdout = "";
|
|
363
|
-
let stderr = "";
|
|
364
|
-
let timedOut = false;
|
|
365
|
-
const timer = opts.timeoutMs ? setTimeout(() => {
|
|
366
|
-
timedOut = true;
|
|
367
|
-
child.kill("SIGKILL");
|
|
368
|
-
}, opts.timeoutMs) : null;
|
|
369
|
-
child.stdout.on("data", (d) => stdout += d.toString());
|
|
370
|
-
child.stderr.on("data", (d) => stderr += d.toString());
|
|
371
|
-
child.on("close", (code) => {
|
|
372
|
-
if (timer) clearTimeout(timer);
|
|
373
|
-
resolve2({
|
|
374
|
-
code: code ?? 1,
|
|
375
|
-
stdout,
|
|
376
|
-
stderr,
|
|
377
|
-
durationMs: Math.round(performance.now() - start),
|
|
378
|
-
timedOut
|
|
379
|
-
});
|
|
380
|
-
});
|
|
381
|
-
child.on("error", () => {
|
|
382
|
-
if (timer) clearTimeout(timer);
|
|
383
|
-
resolve2({
|
|
384
|
-
code: 127,
|
|
385
|
-
stdout,
|
|
386
|
-
stderr: stderr || `failed to spawn ${cmd}`,
|
|
387
|
-
durationMs: Math.round(performance.now() - start),
|
|
388
|
-
timedOut
|
|
389
|
-
});
|
|
390
|
-
});
|
|
391
|
-
});
|
|
177
|
+
const dir = dirname(join6(root, file));
|
|
178
|
+
const out = /* @__PURE__ */ new Set();
|
|
179
|
+
for (const spec of extractSpecifiers(text)) {
|
|
180
|
+
if (!spec.startsWith(".")) continue;
|
|
181
|
+
const resolved = resolveRelative(dir, spec);
|
|
182
|
+
if (resolved) out.add(toPosix(relative3(root, resolved)));
|
|
183
|
+
}
|
|
184
|
+
return [...out];
|
|
392
185
|
}
|
|
393
|
-
var
|
|
394
|
-
|
|
186
|
+
var EXTS, SPEC_RE;
|
|
187
|
+
var init_scanner_resolver = __esm({
|
|
188
|
+
"src/project-graph/scanner-resolver.ts"() {
|
|
395
189
|
"use strict";
|
|
190
|
+
init_discover();
|
|
191
|
+
EXTS = [".ts", ".tsx", ".mts", ".cts", ".js", ".jsx", ".mjs", ".cjs"];
|
|
192
|
+
SPEC_RE = /(?:import|export)\b[^'"]*?\bfrom\s*['"]([^'"]+)['"]|(?:^|[^.\w])import\s*['"]([^'"]+)['"]|\bimport\(\s*['"]([^'"]+)['"]\s*\)|\brequire\(\s*['"]([^'"]+)['"]\s*\)/g;
|
|
396
193
|
}
|
|
397
194
|
});
|
|
398
195
|
|
|
399
|
-
// src/
|
|
400
|
-
import {
|
|
401
|
-
|
|
402
|
-
|
|
196
|
+
// src/project-graph/ts-resolver.ts
|
|
197
|
+
import { existsSync as existsSync4, readFileSync as readFileSync3 } from "fs";
|
|
198
|
+
import { createRequire } from "module";
|
|
199
|
+
import { join as join7, relative as relative4, sep as sep2 } from "path";
|
|
200
|
+
function hasClassicApi(mod) {
|
|
201
|
+
const m = mod;
|
|
202
|
+
return !!m && typeof m.preProcessFile === "function" && typeof m.resolveModuleName === "function" && typeof m.readConfigFile === "function" && typeof m.parseJsonConfigFileContent === "function" && typeof m.sys === "object" && m.sys !== null;
|
|
403
203
|
}
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
`);
|
|
413
|
-
const result = {
|
|
414
|
-
checkId: check.id,
|
|
415
|
-
status,
|
|
416
|
-
durationMs: r.durationMs,
|
|
417
|
-
summary: status === "passed" ? opts.pass : r.timedOut ? "timed out" : opts.fail,
|
|
418
|
-
logRef
|
|
419
|
-
};
|
|
420
|
-
if (status !== "passed" && output) {
|
|
421
|
-
result.outputTail = output.split("\n").slice(-TAIL_LINES).join("\n");
|
|
204
|
+
function loadTypeScript(root) {
|
|
205
|
+
try {
|
|
206
|
+
const require2 = createRequire(join7(root, "__veris__.js"));
|
|
207
|
+
const tsPath = require2.resolve("typescript");
|
|
208
|
+
const mod = require2(tsPath);
|
|
209
|
+
return hasClassicApi(mod) ? mod : null;
|
|
210
|
+
} catch {
|
|
211
|
+
return null;
|
|
422
212
|
}
|
|
423
|
-
return result;
|
|
424
213
|
}
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
214
|
+
function loadCompilerOptions(tsmod, root) {
|
|
215
|
+
const configPath = join7(root, "tsconfig.json");
|
|
216
|
+
const read = tsmod.readConfigFile(configPath, tsmod.sys.readFile);
|
|
217
|
+
if (read.error || !read.config) return {};
|
|
218
|
+
const parsed = tsmod.parseJsonConfigFileContent(read.config, tsmod.sys, root);
|
|
219
|
+
return parsed.options;
|
|
220
|
+
}
|
|
221
|
+
function tsImports(tsmod, root, options, file) {
|
|
222
|
+
let text;
|
|
223
|
+
try {
|
|
224
|
+
text = readFileSync3(join7(root, file), "utf8");
|
|
225
|
+
} catch {
|
|
226
|
+
return [];
|
|
432
227
|
}
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
runner: "biome",
|
|
448
|
-
cmd: localBin(project.root, "biome"),
|
|
449
|
-
args: ["check", "."]
|
|
450
|
-
};
|
|
451
|
-
},
|
|
452
|
-
run(check, ctx) {
|
|
453
|
-
return runViaExec(check, ctx, {
|
|
454
|
-
pass: "no lint errors",
|
|
455
|
-
fail: "lint errors found",
|
|
456
|
-
timeoutMs: 3 * 6e4
|
|
457
|
-
});
|
|
228
|
+
const abs = join7(root, file);
|
|
229
|
+
const out = /* @__PURE__ */ new Set();
|
|
230
|
+
try {
|
|
231
|
+
const pre = tsmod.preProcessFile(text, true, true);
|
|
232
|
+
for (const imp of pre.importedFiles) {
|
|
233
|
+
const res = tsmod.resolveModuleName(
|
|
234
|
+
imp.fileName,
|
|
235
|
+
abs,
|
|
236
|
+
options,
|
|
237
|
+
tsmod.sys
|
|
238
|
+
);
|
|
239
|
+
const resolved = res.resolvedModule?.resolvedFileName;
|
|
240
|
+
if (resolved && !resolved.includes("node_modules") && resolved.startsWith(root + sep2)) {
|
|
241
|
+
out.add(toPosix(relative4(root, resolved)));
|
|
458
242
|
}
|
|
459
|
-
}
|
|
243
|
+
}
|
|
244
|
+
} catch {
|
|
460
245
|
}
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
title: "Lint",
|
|
475
|
-
runner: "eslint",
|
|
476
|
-
cmd: localBin(project.root, "eslint"),
|
|
477
|
-
args: ["."]
|
|
478
|
-
};
|
|
479
|
-
},
|
|
480
|
-
run(check, ctx) {
|
|
481
|
-
return runViaExec(check, ctx, {
|
|
482
|
-
pass: "no lint errors",
|
|
483
|
-
fail: "lint errors found",
|
|
484
|
-
timeoutMs: 3 * 6e4
|
|
485
|
-
});
|
|
486
|
-
}
|
|
487
|
-
};
|
|
246
|
+
return [...out];
|
|
247
|
+
}
|
|
248
|
+
function selectResolver(root) {
|
|
249
|
+
const tsmod = existsSync4(join7(root, "tsconfig.json")) ? loadTypeScript(root) : null;
|
|
250
|
+
if (tsmod) {
|
|
251
|
+
try {
|
|
252
|
+
const options = loadCompilerOptions(tsmod, root);
|
|
253
|
+
return {
|
|
254
|
+
resolver: "typescript",
|
|
255
|
+
importsOf: (file) => tsImports(tsmod, root, options, file)
|
|
256
|
+
};
|
|
257
|
+
} catch {
|
|
258
|
+
}
|
|
488
259
|
}
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
260
|
+
return {
|
|
261
|
+
resolver: "scanner",
|
|
262
|
+
importsOf: (file) => scannerImports(root, file)
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
var init_ts_resolver = __esm({
|
|
266
|
+
"src/project-graph/ts-resolver.ts"() {
|
|
495
267
|
"use strict";
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
id: "jest",
|
|
499
|
-
toCheck(project, _cap, opts) {
|
|
500
|
-
return {
|
|
501
|
-
id: "unit",
|
|
502
|
-
title: "Unit tests",
|
|
503
|
-
runner: "jest",
|
|
504
|
-
cmd: localBin(project.root, "jest"),
|
|
505
|
-
args: ["--ci", ...opts?.targetFiles ?? []]
|
|
506
|
-
};
|
|
507
|
-
},
|
|
508
|
-
run(check, ctx) {
|
|
509
|
-
return runViaExec(check, ctx, {
|
|
510
|
-
pass: "unit tests passed",
|
|
511
|
-
fail: "unit tests failed",
|
|
512
|
-
timeoutMs: 10 * 6e4
|
|
513
|
-
});
|
|
514
|
-
}
|
|
515
|
-
};
|
|
268
|
+
init_discover();
|
|
269
|
+
init_scanner_resolver();
|
|
516
270
|
}
|
|
517
271
|
});
|
|
518
272
|
|
|
519
|
-
// src/
|
|
520
|
-
var
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
"use strict";
|
|
524
|
-
init_base();
|
|
525
|
-
nodeTestRunner = {
|
|
526
|
-
id: "node-test",
|
|
527
|
-
toCheck(_project, _cap) {
|
|
528
|
-
return {
|
|
529
|
-
id: "unit",
|
|
530
|
-
title: "Unit tests",
|
|
531
|
-
runner: "node-test",
|
|
532
|
-
cmd: process.execPath,
|
|
533
|
-
args: ["--test"]
|
|
534
|
-
};
|
|
535
|
-
},
|
|
536
|
-
run(check, ctx) {
|
|
537
|
-
return runViaExec(check, ctx, {
|
|
538
|
-
pass: "unit tests passed",
|
|
539
|
-
fail: "unit tests failed",
|
|
540
|
-
timeoutMs: 10 * 6e4
|
|
541
|
-
});
|
|
542
|
-
}
|
|
543
|
-
};
|
|
544
|
-
}
|
|
273
|
+
// src/project-graph/graph.ts
|
|
274
|
+
var graph_exports = {};
|
|
275
|
+
__export(graph_exports, {
|
|
276
|
+
buildGraph: () => buildGraph
|
|
545
277
|
});
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
278
|
+
async function buildGraph(project) {
|
|
279
|
+
const root = project.root;
|
|
280
|
+
const files = discoverFiles(root);
|
|
281
|
+
const known = new Set(files.map((f) => f.file));
|
|
282
|
+
const { resolver, importsOf } = selectResolver(root);
|
|
283
|
+
const nodes = {};
|
|
284
|
+
for (const f of files) {
|
|
285
|
+
nodes[f.file] = { file: f.file, kind: f.kind, imports: [], importedBy: [] };
|
|
286
|
+
}
|
|
287
|
+
for (const f of files) {
|
|
288
|
+
const imps = importsOf(f.file).filter((i) => known.has(i) && i !== f.file);
|
|
289
|
+
nodes[f.file].imports = imps;
|
|
290
|
+
for (const i of imps) nodes[i]?.importedBy.push(f.file);
|
|
554
291
|
}
|
|
292
|
+
return {
|
|
293
|
+
root,
|
|
294
|
+
resolver,
|
|
295
|
+
nodes,
|
|
296
|
+
sourceFiles: files.filter((f) => f.kind === "source").map((f) => f.file),
|
|
297
|
+
testFiles: files.filter((f) => f.kind === "test").map((f) => f.file)
|
|
298
|
+
};
|
|
555
299
|
}
|
|
556
|
-
var
|
|
557
|
-
|
|
558
|
-
"src/runners/playwright.ts"() {
|
|
300
|
+
var init_graph = __esm({
|
|
301
|
+
"src/project-graph/graph.ts"() {
|
|
559
302
|
"use strict";
|
|
560
|
-
|
|
561
|
-
|
|
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
|
-
};
|
|
303
|
+
init_discover();
|
|
304
|
+
init_ts_resolver();
|
|
614
305
|
}
|
|
615
306
|
});
|
|
616
307
|
|
|
617
|
-
// src/
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
return {
|
|
627
|
-
id: "types",
|
|
628
|
-
title: "Types",
|
|
629
|
-
runner: "tsc",
|
|
630
|
-
cmd: localBin(project.root, "tsc"),
|
|
631
|
-
args: ["--noEmit", "--pretty", "false"]
|
|
632
|
-
};
|
|
633
|
-
},
|
|
634
|
-
run(check, ctx) {
|
|
635
|
-
return runViaExec(check, ctx, {
|
|
636
|
-
pass: "no type errors",
|
|
637
|
-
fail: "type errors found",
|
|
638
|
-
timeoutMs: 5 * 6e4
|
|
639
|
-
});
|
|
640
|
-
}
|
|
641
|
-
};
|
|
308
|
+
// src/project-graph/analyze.ts
|
|
309
|
+
function transitiveDependents(graph, file) {
|
|
310
|
+
const seen = /* @__PURE__ */ new Set();
|
|
311
|
+
const stack = [...graph.nodes[file]?.importedBy ?? []];
|
|
312
|
+
while (stack.length) {
|
|
313
|
+
const f = stack.pop();
|
|
314
|
+
if (!f || seen.has(f)) continue;
|
|
315
|
+
seen.add(f);
|
|
316
|
+
for (const d of graph.nodes[f]?.importedBy ?? []) stack.push(d);
|
|
642
317
|
}
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
318
|
+
return seen;
|
|
319
|
+
}
|
|
320
|
+
function reachableFromTests(graph) {
|
|
321
|
+
const seen = /* @__PURE__ */ new Set();
|
|
322
|
+
const stack = [...graph.testFiles];
|
|
323
|
+
while (stack.length) {
|
|
324
|
+
const f = stack.pop();
|
|
325
|
+
if (!f || seen.has(f)) continue;
|
|
326
|
+
seen.add(f);
|
|
327
|
+
for (const i of graph.nodes[f]?.imports ?? []) stack.push(i);
|
|
328
|
+
}
|
|
329
|
+
return seen;
|
|
330
|
+
}
|
|
331
|
+
function analyze(graph, changed = []) {
|
|
332
|
+
const blastRadius = {};
|
|
333
|
+
for (const file of Object.keys(graph.nodes)) {
|
|
334
|
+
blastRadius[file] = transitiveDependents(graph, file).size;
|
|
335
|
+
}
|
|
336
|
+
const reached = reachableFromTests(graph);
|
|
337
|
+
const byBlast = (a, b) => (blastRadius[b] ?? 0) - (blastRadius[a] ?? 0);
|
|
338
|
+
const untested = graph.sourceFiles.filter((f) => !reached.has(f)).sort(byBlast);
|
|
339
|
+
const changedSet = new Set(changed);
|
|
340
|
+
const untestedSet = new Set(untested);
|
|
341
|
+
const risky = graph.sourceFiles.filter(
|
|
342
|
+
(f) => (blastRadius[f] ?? 0) > 0 && (untestedSet.has(f) || changedSet.has(f))
|
|
343
|
+
).sort(byBlast);
|
|
344
|
+
return { untested, blastRadius, risky };
|
|
345
|
+
}
|
|
346
|
+
var init_analyze = __esm({
|
|
347
|
+
"src/project-graph/analyze.ts"() {
|
|
649
348
|
"use strict";
|
|
650
|
-
init_base();
|
|
651
|
-
vitestRunner = {
|
|
652
|
-
id: "vitest",
|
|
653
|
-
toCheck(project, _cap, opts) {
|
|
654
|
-
const files = opts?.targetFiles ?? [];
|
|
655
|
-
return {
|
|
656
|
-
id: "unit",
|
|
657
|
-
title: "Unit tests",
|
|
658
|
-
runner: "vitest",
|
|
659
|
-
cmd: localBin(project.root, "vitest"),
|
|
660
|
-
args: ["run", "--reporter=json", ...files]
|
|
661
|
-
};
|
|
662
|
-
},
|
|
663
|
-
run(check, ctx) {
|
|
664
|
-
return runViaExec(check, ctx, {
|
|
665
|
-
pass: "unit tests passed",
|
|
666
|
-
fail: "unit tests failed",
|
|
667
|
-
timeoutMs: 10 * 6e4
|
|
668
|
-
});
|
|
669
|
-
}
|
|
670
|
-
};
|
|
671
349
|
}
|
|
672
350
|
});
|
|
673
351
|
|
|
674
|
-
// src/
|
|
675
|
-
var
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
init_vitest();
|
|
686
|
-
init_base();
|
|
687
|
-
runners = {
|
|
688
|
-
tsc: tscRunner,
|
|
689
|
-
vitest: vitestRunner,
|
|
690
|
-
jest: jestRunner,
|
|
691
|
-
"node-test": nodeTestRunner,
|
|
692
|
-
eslint: eslintRunner,
|
|
693
|
-
biome: biomeRunner,
|
|
694
|
-
playwright: playwrightRunner
|
|
352
|
+
// src/affected/select.ts
|
|
353
|
+
var select_exports = {};
|
|
354
|
+
__export(select_exports, {
|
|
355
|
+
selectAffectedTests: () => selectAffectedTests
|
|
356
|
+
});
|
|
357
|
+
function selectAffectedTests(graph, changed) {
|
|
358
|
+
if (graph.resolver !== "typescript") {
|
|
359
|
+
return {
|
|
360
|
+
mode: "full",
|
|
361
|
+
testFiles: [],
|
|
362
|
+
reason: "scanner graph can miss aliased/subpath imports \u2014 running the full suite"
|
|
695
363
|
};
|
|
696
364
|
}
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
const anyFailed = results.some((r) => r.status === "failed");
|
|
705
|
-
for (const cap of capabilities) {
|
|
706
|
-
const result = results.find((r) => r.checkId === cap.id);
|
|
707
|
-
if (!cap.available) {
|
|
708
|
-
skipped.push(cap.id);
|
|
709
|
-
reasons.push(`${cap.id} skipped \u2014 ${cap.reason ?? "not configured"}`);
|
|
710
|
-
continue;
|
|
711
|
-
}
|
|
712
|
-
if (result?.status === "passed") {
|
|
713
|
-
verifiedCapabilities.push(cap.id);
|
|
714
|
-
continue;
|
|
715
|
-
}
|
|
716
|
-
if (result?.status === "failed") {
|
|
717
|
-
reasons.push(`${cap.id} failed \u2014 ${result.summary}`);
|
|
718
|
-
continue;
|
|
365
|
+
for (const f of changed) {
|
|
366
|
+
if (GLOBAL_RE.test(f)) {
|
|
367
|
+
return {
|
|
368
|
+
mode: "full",
|
|
369
|
+
testFiles: [],
|
|
370
|
+
reason: `global/config change (${f})`
|
|
371
|
+
};
|
|
719
372
|
}
|
|
720
|
-
|
|
721
|
-
reasons.push(`${cap.id} skipped \u2014 ${result?.summary ?? "did not run"}`);
|
|
722
|
-
}
|
|
723
|
-
let state;
|
|
724
|
-
if (anyFailed) state = "failed";
|
|
725
|
-
else if (skipped.length > 0) state = "partial";
|
|
726
|
-
else state = "verified";
|
|
727
|
-
return { state, verifiedCapabilities, skipped, reasons };
|
|
728
|
-
}
|
|
729
|
-
function verdictExitCode(verdict, opts = {}) {
|
|
730
|
-
if (verdict.state === "failed") return 1;
|
|
731
|
-
if (verdict.state === "partial") return opts.partialOk ? 0 : 2;
|
|
732
|
-
return 0;
|
|
733
|
-
}
|
|
734
|
-
var init_verdict = __esm({
|
|
735
|
-
"src/core/verdict.ts"() {
|
|
736
|
-
"use strict";
|
|
737
|
-
}
|
|
738
|
-
});
|
|
739
|
-
|
|
740
|
-
// src/core/orchestrator.ts
|
|
741
|
-
async function runChecks(project, ids, root, opts = {}) {
|
|
742
|
-
const known = new Set(project.capabilities.map((c) => c.id));
|
|
743
|
-
const unknown = ids.filter((id2) => !known.has(id2));
|
|
744
|
-
if (unknown.length > 0) {
|
|
745
|
-
throw new Error(`Unknown check id(s): ${unknown.join(", ")}`);
|
|
746
|
-
}
|
|
747
|
-
const id = newRunId();
|
|
748
|
-
const runDir = await createRunDir(root, id);
|
|
749
|
-
const ctx = { root, runDir };
|
|
750
|
-
const tasks = ids.map(async (capId) => {
|
|
751
|
-
const cap = project.capabilities.find((c) => c.id === capId);
|
|
752
|
-
const runner = cap?.runner ? runners[cap.runner] : void 0;
|
|
753
|
-
if (!cap?.available || !runner) {
|
|
754
|
-
const summary = !cap?.available ? cap?.reason ?? "not configured" : `no runner registered for ${cap.runner}`;
|
|
373
|
+
if (!(f in graph.nodes)) {
|
|
755
374
|
return {
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
summary
|
|
375
|
+
mode: "full",
|
|
376
|
+
testFiles: [],
|
|
377
|
+
reason: `unresolved changed file (${f})`
|
|
760
378
|
};
|
|
761
379
|
}
|
|
762
|
-
const check = runner.toCheck(project, cap, {
|
|
763
|
-
targetFiles: opts.targetFiles?.[capId]
|
|
764
|
-
});
|
|
765
|
-
return runner.run(check, ctx);
|
|
766
|
-
});
|
|
767
|
-
const results = await Promise.all(tasks);
|
|
768
|
-
const requested = project.capabilities.filter((c) => ids.includes(c.id));
|
|
769
|
-
const verdict = computeVerdict(results, requested);
|
|
770
|
-
return {
|
|
771
|
-
id,
|
|
772
|
-
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
773
|
-
project,
|
|
774
|
-
results,
|
|
775
|
-
verdict,
|
|
776
|
-
env: getEnvironmentInfo(project.packageManager)
|
|
777
|
-
};
|
|
778
|
-
}
|
|
779
|
-
var init_orchestrator = __esm({
|
|
780
|
-
"src/core/orchestrator.ts"() {
|
|
781
|
-
"use strict";
|
|
782
|
-
init_store();
|
|
783
|
-
init_runners();
|
|
784
|
-
init_env();
|
|
785
|
-
init_verdict();
|
|
786
|
-
}
|
|
787
|
-
});
|
|
788
|
-
|
|
789
|
-
// src/reporters/terminal.ts
|
|
790
|
-
import pc2 from "picocolors";
|
|
791
|
-
function glyph(status, plain) {
|
|
792
|
-
const map = {
|
|
793
|
-
passed: "\u2713",
|
|
794
|
-
failed: "\u2717",
|
|
795
|
-
skipped: "\u2298",
|
|
796
|
-
unknown: "?"
|
|
797
|
-
};
|
|
798
|
-
const g = map[status];
|
|
799
|
-
if (plain) return g;
|
|
800
|
-
if (status === "passed") return pc2.green(g);
|
|
801
|
-
if (status === "failed") return pc2.red(g);
|
|
802
|
-
return pc2.dim(g);
|
|
803
|
-
}
|
|
804
|
-
function secs(ms) {
|
|
805
|
-
return ms === 0 ? "" : `${(ms / 1e3).toFixed(1)}s`;
|
|
806
|
-
}
|
|
807
|
-
function renderRun(run, record) {
|
|
808
|
-
const plain = isPlain();
|
|
809
|
-
const bold = (s) => plain ? s : pc2.bold(s);
|
|
810
|
-
const dim = (s) => plain ? s : pc2.dim(s);
|
|
811
|
-
const lines = [];
|
|
812
|
-
const scoped = run.scope?.kind;
|
|
813
|
-
if (scoped) {
|
|
814
|
-
lines.push(bold(`VerisKit \u2014 ${scoped}`));
|
|
815
|
-
lines.push("");
|
|
816
|
-
lines.push(`Scope ${run.scope?.changedCount ?? 0} changed file(s)`);
|
|
817
|
-
} else {
|
|
818
|
-
lines.push(bold("VerisKit"));
|
|
819
|
-
lines.push("");
|
|
820
|
-
lines.push(
|
|
821
|
-
`Project ${run.project.root.split("/").pop() ?? run.project.root}`
|
|
822
|
-
);
|
|
823
|
-
lines.push(`Risk ${dim("\u2014")}`);
|
|
824
380
|
}
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
const
|
|
829
|
-
|
|
830
|
-
lines.push(` ${g} ${r.checkId.padEnd(14)} ${detail}`);
|
|
831
|
-
if (r.outputTail) {
|
|
832
|
-
for (const tail of r.outputTail.split("\n")) {
|
|
833
|
-
lines.push(dim(` ${tail}`));
|
|
834
|
-
}
|
|
381
|
+
const tests = /* @__PURE__ */ new Set();
|
|
382
|
+
for (const f of changed) {
|
|
383
|
+
if (graph.nodes[f]?.kind === "test") tests.add(f);
|
|
384
|
+
for (const dep of transitiveDependents(graph, f)) {
|
|
385
|
+
if (graph.nodes[dep]?.kind === "test") tests.add(dep);
|
|
835
386
|
}
|
|
836
387
|
}
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
const g = record.git;
|
|
844
|
-
lines.push("");
|
|
845
|
-
lines.push(
|
|
846
|
-
`Commit ${g.commit.slice(0, 7)} ${g.dirty ? "\xB7 tree dirty" : "\xB7 tree clean"}`
|
|
847
|
-
);
|
|
388
|
+
if (tests.size === 0) {
|
|
389
|
+
return {
|
|
390
|
+
mode: "full",
|
|
391
|
+
testFiles: [],
|
|
392
|
+
reason: "no tests reach the changed files"
|
|
393
|
+
};
|
|
848
394
|
}
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
395
|
+
const selected = [...tests].sort();
|
|
396
|
+
if (selected.some((t) => t.startsWith("-"))) {
|
|
397
|
+
return {
|
|
398
|
+
mode: "full",
|
|
399
|
+
testFiles: [],
|
|
400
|
+
reason: "a reaching test path starts with '-' (unsafe as a CLI argument)"
|
|
401
|
+
};
|
|
853
402
|
}
|
|
854
|
-
return
|
|
403
|
+
return { mode: "graph", testFiles: selected, reason: "" };
|
|
855
404
|
}
|
|
856
|
-
var
|
|
857
|
-
|
|
405
|
+
var GLOBAL_RE;
|
|
406
|
+
var init_select = __esm({
|
|
407
|
+
"src/affected/select.ts"() {
|
|
858
408
|
"use strict";
|
|
859
|
-
|
|
409
|
+
init_analyze();
|
|
410
|
+
GLOBAL_RE = /(^|\/)(tsconfig[^/]*\.json|package\.json|biome\.jsonc?|\.eslintrc[^/]*|eslint\.config\.[^/]+|veris\.config\.[^/]+|[^/]+\.config\.[cm]?[jt]sx?|[^/]+\.setup\.[cm]?[jt]sx?)$/;
|
|
860
411
|
}
|
|
861
412
|
});
|
|
862
413
|
|
|
863
|
-
// src/
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
return
|
|
414
|
+
// src/evidence/signing.ts
|
|
415
|
+
import {
|
|
416
|
+
createHash as createHash2,
|
|
417
|
+
createPrivateKey,
|
|
418
|
+
createPublicKey,
|
|
419
|
+
sign as cryptoSign,
|
|
420
|
+
verify as cryptoVerify,
|
|
421
|
+
generateKeyPairSync
|
|
422
|
+
} from "crypto";
|
|
423
|
+
function derOf(publicKey) {
|
|
424
|
+
return publicKey.export({ type: "spki", format: "der" });
|
|
425
|
+
}
|
|
426
|
+
function keyId(publicKey) {
|
|
427
|
+
const key = typeof publicKey === "string" ? createPublicKey(publicKey) : publicKey;
|
|
428
|
+
return createHash2("sha256").update(derOf(key)).digest("hex").slice(0, 8);
|
|
429
|
+
}
|
|
430
|
+
function signatureKeyId(sig) {
|
|
431
|
+
const key = createPublicKey({
|
|
432
|
+
key: Buffer.from(sig.publicKey, "base64"),
|
|
433
|
+
format: "der",
|
|
434
|
+
type: "spki"
|
|
435
|
+
});
|
|
436
|
+
return keyId(key);
|
|
437
|
+
}
|
|
438
|
+
function verifySignature(sig) {
|
|
439
|
+
try {
|
|
440
|
+
const publicKey = createPublicKey({
|
|
441
|
+
key: Buffer.from(sig.publicKey, "base64"),
|
|
442
|
+
format: "der",
|
|
443
|
+
type: "spki"
|
|
444
|
+
});
|
|
445
|
+
return cryptoVerify(
|
|
446
|
+
null,
|
|
447
|
+
Buffer.from(sig.digest, "utf8"),
|
|
448
|
+
publicKey,
|
|
449
|
+
Buffer.from(sig.signature, "base64")
|
|
450
|
+
);
|
|
451
|
+
} catch {
|
|
452
|
+
return false;
|
|
453
|
+
}
|
|
874
454
|
}
|
|
875
|
-
var
|
|
876
|
-
"src/
|
|
455
|
+
var init_signing = __esm({
|
|
456
|
+
"src/evidence/signing.ts"() {
|
|
877
457
|
"use strict";
|
|
878
|
-
init_detect();
|
|
879
|
-
init_orchestrator();
|
|
880
|
-
init_verdict();
|
|
881
|
-
init_terminal();
|
|
882
458
|
}
|
|
883
459
|
});
|
|
884
460
|
|
|
885
|
-
// src/
|
|
886
|
-
var
|
|
887
|
-
__export(
|
|
888
|
-
|
|
461
|
+
// src/evidence/bundle.ts
|
|
462
|
+
var bundle_exports = {};
|
|
463
|
+
__export(bundle_exports, {
|
|
464
|
+
BUNDLE_SCHEMA: () => BUNDLE_SCHEMA,
|
|
465
|
+
buildBundle: () => buildBundle,
|
|
466
|
+
verifyBundle: () => verifyBundle
|
|
889
467
|
});
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
);
|
|
901
|
-
await writeIfAbsent(join4(dir, ".gitignore"), GITIGNORE);
|
|
902
|
-
process.stdout.write(
|
|
903
|
-
wroteConfig ? `Veris initialized. Detected checks: ${defaultChecks.join(", ") || "none"}.
|
|
904
|
-
` : "VerisKit already initialized (.veris/config.json exists). Nothing overwritten.\n"
|
|
905
|
-
);
|
|
906
|
-
return 0;
|
|
468
|
+
function buildBundle(record, report, logs, signature) {
|
|
469
|
+
const manifest = {
|
|
470
|
+
record: record.digest,
|
|
471
|
+
report: sha256(report),
|
|
472
|
+
logs: Object.fromEntries(
|
|
473
|
+
Object.entries(logs).map(([k, v]) => [k, sha256(v)])
|
|
474
|
+
)
|
|
475
|
+
};
|
|
476
|
+
const base = signature ? { schema: BUNDLE_SCHEMA, record, report, logs, manifest, signature } : { schema: BUNDLE_SCHEMA, record, report, logs, manifest };
|
|
477
|
+
return { ...base, bundleDigest: sha256(canonicalize(base)) };
|
|
907
478
|
}
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
479
|
+
function verifyBundle(bundle, opts = {}) {
|
|
480
|
+
const checks = [];
|
|
481
|
+
const recordResult = verifyRecord(bundle.record);
|
|
482
|
+
checks.push(...recordResult.checks);
|
|
483
|
+
const reportDigest = sha256(bundle.report);
|
|
484
|
+
checks.push({
|
|
485
|
+
name: "report",
|
|
486
|
+
ok: reportDigest === bundle.manifest.report,
|
|
487
|
+
detail: reportDigest === bundle.manifest.report ? "matches manifest" : "mismatch"
|
|
488
|
+
});
|
|
489
|
+
for (const [id, body] of Object.entries(bundle.logs)) {
|
|
490
|
+
const d = sha256(body);
|
|
491
|
+
checks.push({
|
|
492
|
+
name: `log:${id}`,
|
|
493
|
+
ok: d === bundle.manifest.logs[id],
|
|
494
|
+
detail: d === bundle.manifest.logs[id] ? "matches manifest" : "mismatch"
|
|
495
|
+
});
|
|
923
496
|
}
|
|
924
|
-
}
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
497
|
+
const { bundleDigest: _omit, ...rest } = bundle;
|
|
498
|
+
const recomputed = sha256(canonicalize(rest));
|
|
499
|
+
checks.push({
|
|
500
|
+
name: "bundle digest",
|
|
501
|
+
ok: recomputed === bundle.bundleDigest,
|
|
502
|
+
detail: recomputed === bundle.bundleDigest ? "matches" : "mismatch"
|
|
503
|
+
});
|
|
504
|
+
let signed = false;
|
|
505
|
+
if (bundle.signature) {
|
|
506
|
+
signed = true;
|
|
507
|
+
checks.push(
|
|
508
|
+
...signatureChecks(bundle.record.digest, bundle.signature, {
|
|
509
|
+
expectedKeyId: opts.expectedKeyId,
|
|
510
|
+
expectedPubKeyPem: opts.expectedPubKeyPem
|
|
511
|
+
})
|
|
512
|
+
);
|
|
513
|
+
} else if (opts.expectedKeyId || opts.expectedPubKeyPem) {
|
|
514
|
+
checks.push({
|
|
515
|
+
name: "signature",
|
|
516
|
+
ok: false,
|
|
517
|
+
detail: "a signature was required (--pubkey/--key-id) but the bundle is unsigned"
|
|
518
|
+
});
|
|
519
|
+
}
|
|
520
|
+
return {
|
|
521
|
+
ok: checks.every((c) => c.ok),
|
|
522
|
+
kind: "bundle",
|
|
523
|
+
record: bundle.record,
|
|
524
|
+
checks,
|
|
525
|
+
signed
|
|
526
|
+
};
|
|
930
527
|
}
|
|
931
|
-
var
|
|
932
|
-
|
|
528
|
+
var BUNDLE_SCHEMA;
|
|
529
|
+
var init_bundle = __esm({
|
|
530
|
+
"src/evidence/bundle.ts"() {
|
|
933
531
|
"use strict";
|
|
934
|
-
|
|
532
|
+
init_record();
|
|
533
|
+
init_verify_evidence();
|
|
534
|
+
BUNDLE_SCHEMA = "veriskit/bundle@1";
|
|
935
535
|
}
|
|
936
536
|
});
|
|
937
537
|
|
|
938
|
-
// src/
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
}
|
|
945
|
-
async function changedFiles(root, opts = {}) {
|
|
946
|
-
const base = opts.base ?? null;
|
|
947
|
-
const files = /* @__PURE__ */ new Set();
|
|
948
|
-
const diffArgs = base ? ["diff", "--name-only", base] : ["diff", "--name-only", "HEAD"];
|
|
949
|
-
const diff = await exec("git", diffArgs, { cwd: root });
|
|
950
|
-
if (diff.code === 0) collect(files, diff.stdout);
|
|
951
|
-
const untracked = await exec(
|
|
952
|
-
"git",
|
|
953
|
-
["ls-files", "--others", "--exclude-standard"],
|
|
954
|
-
{ cwd: root }
|
|
538
|
+
// src/evidence/verify-evidence.ts
|
|
539
|
+
import { existsSync as existsSync5 } from "fs";
|
|
540
|
+
import { readFile as readFile3 } from "fs/promises";
|
|
541
|
+
function verifyRecord(record) {
|
|
542
|
+
const recomputed = computeDigest(
|
|
543
|
+
record
|
|
955
544
|
);
|
|
956
|
-
|
|
957
|
-
|
|
545
|
+
const ok = recomputed === record.digest;
|
|
546
|
+
const checks = [
|
|
547
|
+
{
|
|
548
|
+
name: "record digest",
|
|
549
|
+
ok,
|
|
550
|
+
detail: ok ? "matches the canonical record" : `recomputed ${recomputed}, record claims ${record.digest}`
|
|
551
|
+
}
|
|
552
|
+
];
|
|
553
|
+
return { ok, kind: "record", record, checks, signed: false };
|
|
958
554
|
}
|
|
959
|
-
|
|
960
|
-
const
|
|
961
|
-
|
|
962
|
-
const
|
|
963
|
-
|
|
964
|
-
|
|
555
|
+
function signatureChecks(recordDigest, sig, opts = {}) {
|
|
556
|
+
const checks = [];
|
|
557
|
+
const kid = signatureKeyId(sig);
|
|
558
|
+
const validSig = verifySignature(sig) && sig.digest === recordDigest;
|
|
559
|
+
checks.push({
|
|
560
|
+
name: "signature",
|
|
561
|
+
ok: validSig,
|
|
562
|
+
detail: validSig ? `valid ed25519 signature by key ${kid}` : "signature does not match this record"
|
|
965
563
|
});
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
564
|
+
if (opts.expectedKeyId || opts.expectedPubKeyPem) {
|
|
565
|
+
let matches = false;
|
|
566
|
+
if (opts.expectedPubKeyPem) {
|
|
567
|
+
try {
|
|
568
|
+
matches = keyId(opts.expectedPubKeyPem) === kid;
|
|
569
|
+
} catch {
|
|
570
|
+
matches = false;
|
|
571
|
+
}
|
|
572
|
+
} else if (opts.expectedKeyId) {
|
|
573
|
+
matches = opts.expectedKeyId.toLowerCase() === kid.toLowerCase();
|
|
574
|
+
}
|
|
575
|
+
checks.push({
|
|
576
|
+
name: "signer",
|
|
577
|
+
ok: matches,
|
|
578
|
+
detail: matches ? `signer matches the expected key ${kid}` : `signer ${kid} does not match the expected key`
|
|
579
|
+
});
|
|
980
580
|
}
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
// src/reporters/markdown.ts
|
|
984
|
-
import { relative } from "path";
|
|
985
|
-
function cell(value) {
|
|
986
|
-
return value.replace(/\|/g, "\\|");
|
|
581
|
+
return checks;
|
|
987
582
|
}
|
|
988
|
-
function
|
|
989
|
-
const
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
lines.push("");
|
|
994
|
-
lines.push(
|
|
995
|
-
`> **Scope:** ${run.scope.kind} \u2014 ${run.scope.changedCount} changed file(s). Only affected checks ran; this is not a full verification.`
|
|
996
|
-
);
|
|
583
|
+
async function verifyEvidenceFile(path, opts = {}) {
|
|
584
|
+
const parsed = JSON.parse(await readFile3(path, "utf8"));
|
|
585
|
+
if (parsed?.schema === "veriskit/bundle@1") {
|
|
586
|
+
const { verifyBundle: verifyBundle2 } = await Promise.resolve().then(() => (init_bundle(), bundle_exports));
|
|
587
|
+
return verifyBundle2(parsed, opts);
|
|
997
588
|
}
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
lines.push(
|
|
1002
|
-
`**Node:** ${run.env.node} \xB7 **OS:** ${run.env.os} \xB7 **PM:** ${run.env.pm} \xB7 **CI:** ${run.env.ci}`
|
|
589
|
+
const result = verifyRecord(parsed);
|
|
590
|
+
const assertedSigner = Boolean(
|
|
591
|
+
opts.expectedKeyId || opts.expectedPubKeyPem || opts.sigPath
|
|
1003
592
|
);
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
const
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
lines.push("## Checks");
|
|
1013
|
-
lines.push("");
|
|
1014
|
-
lines.push("| Check | Status | Duration | Summary | Log |");
|
|
1015
|
-
lines.push("| --- | --- | --- | --- | --- |");
|
|
1016
|
-
for (const r of run.results) {
|
|
1017
|
-
const dur = r.durationMs ? `${(r.durationMs / 1e3).toFixed(1)}s` : "\u2014";
|
|
1018
|
-
const log = r.logRef ? cell(relative(root, r.logRef)) : "\u2014";
|
|
1019
|
-
lines.push(
|
|
1020
|
-
`| ${r.checkId} | ${r.status} | ${dur} | ${cell(r.summary)} | ${log} |`
|
|
1021
|
-
);
|
|
1022
|
-
}
|
|
1023
|
-
if (run.verdict.skipped.length) {
|
|
1024
|
-
lines.push("");
|
|
1025
|
-
lines.push("## Skipped");
|
|
1026
|
-
lines.push("");
|
|
1027
|
-
for (const reason of run.verdict.reasons) lines.push(`- ${reason}`);
|
|
1028
|
-
}
|
|
1029
|
-
const failures = run.results.filter((r) => r.outputTail);
|
|
1030
|
-
if (failures.length) {
|
|
1031
|
-
lines.push("");
|
|
1032
|
-
lines.push("## Failure output");
|
|
1033
|
-
for (const r of failures) {
|
|
1034
|
-
lines.push("");
|
|
1035
|
-
lines.push(`### ${r.checkId}`);
|
|
1036
|
-
lines.push("");
|
|
1037
|
-
lines.push("```");
|
|
1038
|
-
lines.push(r.outputTail ?? "");
|
|
1039
|
-
lines.push("```");
|
|
1040
|
-
}
|
|
1041
|
-
}
|
|
1042
|
-
if (record) {
|
|
1043
|
-
lines.push("");
|
|
1044
|
-
lines.push(`**Evidence digest:** \`${record.digest}\``);
|
|
1045
|
-
lines.push(
|
|
1046
|
-
"_Integrity digest over the canonical record. Detects edits and corruption; it is not forgery-proof on its own. Publish the digest separately or sign it to prove authorship._"
|
|
593
|
+
const sigPath = opts.sigPath ?? `${path}.sig`;
|
|
594
|
+
if (existsSync5(sigPath)) {
|
|
595
|
+
const sig = JSON.parse(await readFile3(sigPath, "utf8"));
|
|
596
|
+
result.checks.push(
|
|
597
|
+
...signatureChecks(result.record.digest, sig, {
|
|
598
|
+
expectedKeyId: opts.expectedKeyId,
|
|
599
|
+
expectedPubKeyPem: opts.expectedPubKeyPem
|
|
600
|
+
})
|
|
1047
601
|
);
|
|
602
|
+
result.signed = true;
|
|
603
|
+
} else if (assertedSigner) {
|
|
604
|
+
result.checks.push({
|
|
605
|
+
name: "signature",
|
|
606
|
+
ok: false,
|
|
607
|
+
detail: "a signature was required (--pubkey/--key-id/--sig) but none was found"
|
|
608
|
+
});
|
|
1048
609
|
}
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
"_Generated by VerisKit. A `Partial` verdict means one or more capabilities did not run \u2014 it is not a pass._"
|
|
1052
|
-
);
|
|
1053
|
-
lines.push("");
|
|
1054
|
-
return lines.join("\n");
|
|
610
|
+
result.ok = result.checks.every((c) => c.ok);
|
|
611
|
+
return result;
|
|
1055
612
|
}
|
|
1056
|
-
var
|
|
1057
|
-
|
|
1058
|
-
"src/reporters/markdown.ts"() {
|
|
613
|
+
var init_verify_evidence = __esm({
|
|
614
|
+
"src/evidence/verify-evidence.ts"() {
|
|
1059
615
|
"use strict";
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
failed: "Failed",
|
|
1063
|
-
partial: "Partial"
|
|
1064
|
-
};
|
|
616
|
+
init_record();
|
|
617
|
+
init_signing();
|
|
1065
618
|
}
|
|
1066
619
|
});
|
|
1067
620
|
|
|
1068
|
-
// src/
|
|
1069
|
-
|
|
1070
|
-
|
|
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
|
-
});
|
|
621
|
+
// src/config/detect.ts
|
|
622
|
+
import { existsSync as existsSync2 } from "fs";
|
|
623
|
+
import { join } from "path";
|
|
1098
624
|
|
|
1099
|
-
// src/
|
|
1100
|
-
import {
|
|
1101
|
-
|
|
625
|
+
// src/util/fs-safe.ts
|
|
626
|
+
import { existsSync } from "fs";
|
|
627
|
+
import { mkdir, readFile, writeFile } from "fs/promises";
|
|
628
|
+
async function ensureDir(path) {
|
|
629
|
+
await mkdir(path, { recursive: true });
|
|
630
|
+
}
|
|
631
|
+
async function readJsonIfExists(path) {
|
|
632
|
+
if (!existsSync(path)) return null;
|
|
1102
633
|
try {
|
|
1103
|
-
return JSON.parse(
|
|
634
|
+
return JSON.parse(await readFile(path, "utf8"));
|
|
1104
635
|
} catch {
|
|
1105
636
|
return null;
|
|
1106
637
|
}
|
|
1107
638
|
}
|
|
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
639
|
|
|
1135
|
-
// src/
|
|
1136
|
-
function
|
|
640
|
+
// src/config/detect.ts
|
|
641
|
+
function detectPackageManager(root) {
|
|
642
|
+
if (existsSync2(join(root, "pnpm-lock.yaml"))) return "pnpm";
|
|
643
|
+
if (existsSync2(join(root, "yarn.lock"))) return "yarn";
|
|
644
|
+
if (existsSync2(join(root, "bun.lockb"))) return "bun";
|
|
645
|
+
return "npm";
|
|
646
|
+
}
|
|
647
|
+
async function detectProject(root) {
|
|
648
|
+
const pkg = await readJsonIfExists(join(root, "package.json")) ?? {};
|
|
649
|
+
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
650
|
+
const has = (name) => name in deps;
|
|
651
|
+
const scripts = pkg.scripts ?? {};
|
|
652
|
+
const languages = existsSync2(join(root, "tsconfig.json")) ? ["typescript", "javascript"] : ["javascript"];
|
|
653
|
+
const frameworks = ["next", "vite", "react"].filter(has);
|
|
654
|
+
const capabilities = [
|
|
655
|
+
detectTypes(root, has),
|
|
656
|
+
detectUnit(has, scripts),
|
|
657
|
+
detectLint(root, has),
|
|
658
|
+
detectBrowser(root, has)
|
|
659
|
+
];
|
|
1137
660
|
return {
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
661
|
+
root,
|
|
662
|
+
name: pkg.name ?? void 0,
|
|
663
|
+
packageManager: detectPackageManager(root),
|
|
664
|
+
frameworks,
|
|
665
|
+
languages,
|
|
666
|
+
scripts,
|
|
667
|
+
capabilities
|
|
1143
668
|
};
|
|
1144
669
|
}
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
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}`;
|
|
670
|
+
function detectTypes(root, has) {
|
|
671
|
+
if (existsSync2(join(root, "tsconfig.json")) && has("typescript"))
|
|
672
|
+
return { id: "types", available: true, runner: "tsc" };
|
|
673
|
+
return {
|
|
674
|
+
id: "types",
|
|
675
|
+
available: false,
|
|
676
|
+
reason: "no tsconfig.json + typescript dependency"
|
|
677
|
+
};
|
|
1168
678
|
}
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
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
|
-
}
|
|
679
|
+
function detectUnit(has, scripts) {
|
|
680
|
+
if (has("vitest")) return { id: "unit", available: true, runner: "vitest" };
|
|
681
|
+
if (has("jest")) return { id: "unit", available: true, runner: "jest" };
|
|
682
|
+
if (Object.values(scripts).some((s) => s.includes("node --test")))
|
|
683
|
+
return { id: "unit", available: true, runner: "node-test" };
|
|
684
|
+
return { id: "unit", available: false, reason: "no test runner detected" };
|
|
1186
685
|
}
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
}
|
|
686
|
+
function detectLint(root, has) {
|
|
687
|
+
if (existsSync2(join(root, "biome.json")) || has("@biomejs/biome"))
|
|
688
|
+
return { id: "lint", available: true, runner: "biome" };
|
|
689
|
+
if (has("eslint") || [".eslintrc", ".eslintrc.json", ".eslintrc.cjs", "eslint.config.js"].some(
|
|
690
|
+
(f) => existsSync2(join(root, f))
|
|
691
|
+
))
|
|
692
|
+
return { id: "lint", available: true, runner: "eslint" };
|
|
693
|
+
return { id: "lint", available: false, reason: "no linter configured" };
|
|
1195
694
|
}
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
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
|
-
|
|
1255
|
-
// src/cli/commands/verify.ts
|
|
1256
|
-
var verify_exports = {};
|
|
1257
|
-
__export(verify_exports, {
|
|
1258
|
-
resolveChecks: () => resolveChecks,
|
|
1259
|
-
runVerify: () => runVerify
|
|
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
|
-
}
|
|
1269
|
-
async function runVerify(root, opts = {}) {
|
|
1270
|
-
const project = await detectProject(root);
|
|
1271
|
-
const config = await loadConfig(root);
|
|
1272
|
-
const checks = resolveChecks(config?.checks, project, opts);
|
|
1273
|
-
const run = await runChecks(project, checks, root);
|
|
1274
|
-
const git = await gitAnchor(root);
|
|
1275
|
-
const logDigests = await digestLogs(run);
|
|
1276
|
-
const record = buildRecord(run, git, logDigests, VERSION);
|
|
1277
|
-
const reportRef = await writeReport(
|
|
1278
|
-
root,
|
|
1279
|
-
run.id,
|
|
1280
|
-
renderMarkdown(run, record)
|
|
1281
|
-
);
|
|
1282
|
-
run.reportRef = reportRef;
|
|
1283
|
-
const runDir = await createRunDir(root, run.id);
|
|
1284
|
-
await writeEvidence(runDir, record);
|
|
1285
|
-
process.stdout.write(`${renderRun(run, record)}
|
|
1286
|
-
`);
|
|
1287
|
-
if (opts.github) await publishToGitHub(run, record);
|
|
1288
|
-
return verdictExitCode(run.verdict, opts);
|
|
1289
|
-
}
|
|
1290
|
-
var DEFAULT_CHECKS;
|
|
1291
|
-
var init_verify = __esm({
|
|
1292
|
-
"src/cli/commands/verify.ts"() {
|
|
1293
|
-
"use strict";
|
|
1294
|
-
init_detect();
|
|
1295
|
-
init_load();
|
|
1296
|
-
init_orchestrator();
|
|
1297
|
-
init_verdict();
|
|
1298
|
-
init_record();
|
|
1299
|
-
init_store();
|
|
1300
|
-
init_changes();
|
|
1301
|
-
init_publish();
|
|
1302
|
-
init_markdown();
|
|
1303
|
-
init_terminal();
|
|
1304
|
-
init_version();
|
|
1305
|
-
DEFAULT_CHECKS = ["types", "lint", "unit"];
|
|
1306
|
-
}
|
|
1307
|
-
});
|
|
1308
|
-
|
|
1309
|
-
// src/cli/commands/report.ts
|
|
1310
|
-
var report_exports = {};
|
|
1311
|
-
__export(report_exports, {
|
|
1312
|
-
runReport: () => runReport
|
|
1313
|
-
});
|
|
1314
|
-
import { readdirSync as readdirSync2, readFileSync as readFileSync3 } from "fs";
|
|
1315
|
-
import { join as join6 } from "path";
|
|
1316
|
-
async function runReport(root) {
|
|
1317
|
-
const dir = join6(root, ".veris", "reports");
|
|
1318
|
-
let files;
|
|
1319
|
-
try {
|
|
1320
|
-
files = readdirSync2(dir).filter((f) => f.endsWith(".md")).sort();
|
|
1321
|
-
} catch {
|
|
1322
|
-
process.stdout.write("No reports yet. Run `veris verify` first.\n");
|
|
1323
|
-
return 0;
|
|
1324
|
-
}
|
|
1325
|
-
const latest = files.at(-1);
|
|
1326
|
-
if (!latest) {
|
|
1327
|
-
process.stdout.write("No reports yet. Run `veris verify` first.\n");
|
|
1328
|
-
return 0;
|
|
695
|
+
function detectBrowser(root, has) {
|
|
696
|
+
if (has("@playwright/test") || existsSync2(join(root, "playwright.config.ts")) || existsSync2(join(root, "playwright.config.js"))) {
|
|
697
|
+
return { id: "browser", available: true, runner: "playwright" };
|
|
1329
698
|
}
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
699
|
+
return {
|
|
700
|
+
id: "browser",
|
|
701
|
+
available: false,
|
|
702
|
+
reason: "no browser runner configured"
|
|
703
|
+
};
|
|
1333
704
|
}
|
|
1334
|
-
var init_report = __esm({
|
|
1335
|
-
"src/cli/commands/report.ts"() {
|
|
1336
|
-
"use strict";
|
|
1337
|
-
}
|
|
1338
|
-
});
|
|
1339
705
|
|
|
1340
706
|
// src/affected/gate.ts
|
|
707
|
+
var ORDER = ["types", "lint", "unit", "browser"];
|
|
708
|
+
var CONFIG_RE = /(^|\/)(tsconfig[^/]*\.json|biome\.jsonc?|\.eslintrc[^/]*|eslint\.config\.[^/]+|package\.json|veris\.config\.[^/]+)$/;
|
|
709
|
+
var TEST_RE = /(\.(test|spec)\.[cm]?[jt]sx?$)|(^|\/)(test|__tests__)\//;
|
|
710
|
+
var TS_RE = /\.[cm]?tsx?$/;
|
|
711
|
+
var JS_RE = /\.[cm]?jsx?$/;
|
|
712
|
+
var DOC_RE = /(\.(md|mdx|markdown|txt|png|jpe?g|gif|svg|webp|ico)$)|((^|\/)LICENSE$)/i;
|
|
1341
713
|
function affectedChecks(files, project) {
|
|
1342
714
|
const available = new Set(
|
|
1343
715
|
project.capabilities.filter((c) => c.available).map((c) => c.id)
|
|
@@ -1377,1327 +749,635 @@ function affectedChecks(files, project) {
|
|
|
1377
749
|
changedCount: files.length
|
|
1378
750
|
};
|
|
1379
751
|
}
|
|
1380
|
-
var ORDER, CONFIG_RE, TEST_RE, TS_RE, JS_RE, DOC_RE;
|
|
1381
|
-
var init_gate = __esm({
|
|
1382
|
-
"src/affected/gate.ts"() {
|
|
1383
|
-
"use strict";
|
|
1384
|
-
ORDER = ["types", "lint", "unit", "browser"];
|
|
1385
|
-
CONFIG_RE = /(^|\/)(tsconfig[^/]*\.json|biome\.jsonc?|\.eslintrc[^/]*|eslint\.config\.[^/]+|package\.json|veris\.config\.[^/]+)$/;
|
|
1386
|
-
TEST_RE = /(\.(test|spec)\.[cm]?[jt]sx?$)|(^|\/)(test|__tests__)\//;
|
|
1387
|
-
TS_RE = /\.[cm]?tsx?$/;
|
|
1388
|
-
JS_RE = /\.[cm]?jsx?$/;
|
|
1389
|
-
DOC_RE = /(\.(md|mdx|markdown|txt|png|jpe?g|gif|svg|webp|ico)$)|((^|\/)LICENSE$)/i;
|
|
1390
|
-
}
|
|
1391
|
-
});
|
|
1392
752
|
|
|
1393
|
-
// src/
|
|
1394
|
-
import {
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
return sep === "/" ? p : p.split(sep).join("/");
|
|
1398
|
-
}
|
|
1399
|
-
function classify(rel) {
|
|
1400
|
-
if (TEST_RE2.test(rel)) return "test";
|
|
1401
|
-
if (CONFIG_RE2.test(rel)) return "config";
|
|
1402
|
-
return "source";
|
|
1403
|
-
}
|
|
1404
|
-
function discoverFiles(root) {
|
|
1405
|
-
const out = [];
|
|
1406
|
-
const walk = (dir) => {
|
|
1407
|
-
let entries;
|
|
1408
|
-
try {
|
|
1409
|
-
entries = readdirSync3(dir);
|
|
1410
|
-
} catch {
|
|
1411
|
-
return;
|
|
1412
|
-
}
|
|
1413
|
-
for (const name of entries) {
|
|
1414
|
-
const abs = join7(dir, name);
|
|
1415
|
-
const rel = toPosix(relative2(root, abs));
|
|
1416
|
-
if (IGNORE.test(rel)) continue;
|
|
1417
|
-
let st;
|
|
1418
|
-
try {
|
|
1419
|
-
st = statSync(abs);
|
|
1420
|
-
} catch {
|
|
1421
|
-
continue;
|
|
1422
|
-
}
|
|
1423
|
-
if (st.isDirectory()) {
|
|
1424
|
-
walk(abs);
|
|
1425
|
-
} else if (CODE_RE.test(rel)) {
|
|
1426
|
-
out.push({ file: rel, kind: classify(rel) });
|
|
1427
|
-
}
|
|
1428
|
-
}
|
|
1429
|
-
};
|
|
1430
|
-
walk(root);
|
|
1431
|
-
return out.sort((a, b) => a.file.localeCompare(b.file));
|
|
753
|
+
// src/config/load.ts
|
|
754
|
+
import { join as join2 } from "path";
|
|
755
|
+
async function loadConfig(root) {
|
|
756
|
+
return readJsonIfExists(join2(root, ".veris", "config.json"));
|
|
1432
757
|
}
|
|
1433
|
-
var IGNORE, CODE_RE, TEST_RE2, CONFIG_RE2;
|
|
1434
|
-
var init_discover = __esm({
|
|
1435
|
-
"src/project-graph/discover.ts"() {
|
|
1436
|
-
"use strict";
|
|
1437
|
-
IGNORE = /(^|\/)(\.git|\.claude|\.veris|\.agentloop|\.agentflight|node_modules|dist|coverage|build|fixtures|__fixtures__)(\/|$)/;
|
|
1438
|
-
CODE_RE = /\.[cm]?[jt]sx?$/;
|
|
1439
|
-
TEST_RE2 = /(\.(test|spec)\.[cm]?[jt]sx?$)|(^|\/)(test|tests|__tests__)\//;
|
|
1440
|
-
CONFIG_RE2 = /(^|\/)([^/]+\.config\.[cm]?[jt]sx?)$/;
|
|
1441
|
-
}
|
|
1442
|
-
});
|
|
1443
758
|
|
|
1444
|
-
// src/
|
|
1445
|
-
|
|
1446
|
-
import { dirname, join as join8, relative as relative3, resolve } from "path";
|
|
1447
|
-
function extractSpecifiers(text) {
|
|
1448
|
-
const specs = [];
|
|
1449
|
-
SPEC_RE.lastIndex = 0;
|
|
1450
|
-
let m;
|
|
1451
|
-
m = SPEC_RE.exec(text);
|
|
1452
|
-
while (m !== null) {
|
|
1453
|
-
const s = m[1] ?? m[2] ?? m[3] ?? m[4];
|
|
1454
|
-
if (s) specs.push(s);
|
|
1455
|
-
m = SPEC_RE.exec(text);
|
|
1456
|
-
}
|
|
1457
|
-
return specs;
|
|
1458
|
-
}
|
|
1459
|
-
function isFile(p) {
|
|
1460
|
-
try {
|
|
1461
|
-
return statSync2(p).isFile();
|
|
1462
|
-
} catch {
|
|
1463
|
-
return false;
|
|
1464
|
-
}
|
|
1465
|
-
}
|
|
1466
|
-
function resolveRelative(fromDir, spec) {
|
|
1467
|
-
const base = resolve(fromDir, spec);
|
|
1468
|
-
const candidates = [base];
|
|
1469
|
-
for (const ext of EXTS) candidates.push(base + ext);
|
|
1470
|
-
const extMatch = base.match(/\.[cm]?[jt]sx?$/);
|
|
1471
|
-
if (extMatch) {
|
|
1472
|
-
const noExt = base.slice(0, -extMatch[0].length);
|
|
1473
|
-
for (const ext of EXTS) candidates.push(noExt + ext);
|
|
1474
|
-
}
|
|
1475
|
-
for (const ext of EXTS) candidates.push(join8(base, `index${ext}`));
|
|
1476
|
-
for (const c of candidates) {
|
|
1477
|
-
if (existsSync3(c) && isFile(c)) return c;
|
|
1478
|
-
}
|
|
1479
|
-
return null;
|
|
1480
|
-
}
|
|
1481
|
-
function scannerImports(root, file) {
|
|
1482
|
-
let text;
|
|
1483
|
-
try {
|
|
1484
|
-
text = readFileSync4(join8(root, file), "utf8");
|
|
1485
|
-
} catch {
|
|
1486
|
-
return [];
|
|
1487
|
-
}
|
|
1488
|
-
const dir = dirname(join8(root, file));
|
|
1489
|
-
const out = /* @__PURE__ */ new Set();
|
|
1490
|
-
for (const spec of extractSpecifiers(text)) {
|
|
1491
|
-
if (!spec.startsWith(".")) continue;
|
|
1492
|
-
const resolved = resolveRelative(dir, spec);
|
|
1493
|
-
if (resolved) out.add(toPosix(relative3(root, resolved)));
|
|
1494
|
-
}
|
|
1495
|
-
return [...out];
|
|
1496
|
-
}
|
|
1497
|
-
var EXTS, SPEC_RE;
|
|
1498
|
-
var init_scanner_resolver = __esm({
|
|
1499
|
-
"src/project-graph/scanner-resolver.ts"() {
|
|
1500
|
-
"use strict";
|
|
1501
|
-
init_discover();
|
|
1502
|
-
EXTS = [".ts", ".tsx", ".mts", ".cts", ".js", ".jsx", ".mjs", ".cjs"];
|
|
1503
|
-
SPEC_RE = /(?:import|export)\b[^'"]*?\bfrom\s*['"]([^'"]+)['"]|(?:^|[^.\w])import\s*['"]([^'"]+)['"]|\bimport\(\s*['"]([^'"]+)['"]\s*\)|\brequire\(\s*['"]([^'"]+)['"]\s*\)/g;
|
|
1504
|
-
}
|
|
1505
|
-
});
|
|
759
|
+
// src/core/orchestrate.ts
|
|
760
|
+
init_record();
|
|
1506
761
|
|
|
1507
|
-
// src/
|
|
1508
|
-
import {
|
|
1509
|
-
import {
|
|
1510
|
-
import { join as
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
762
|
+
// src/evidence/store.ts
|
|
763
|
+
import { readdirSync } from "fs";
|
|
764
|
+
import { readFile as readFile2, writeFile as writeFile2 } from "fs/promises";
|
|
765
|
+
import { join as join3 } from "path";
|
|
766
|
+
init_record();
|
|
767
|
+
var counter = 0;
|
|
768
|
+
function newRunId() {
|
|
769
|
+
counter += 1;
|
|
770
|
+
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
771
|
+
return `${stamp}-${counter}`;
|
|
1514
772
|
}
|
|
1515
|
-
function
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
const mod = require2(tsPath);
|
|
1520
|
-
return hasClassicApi(mod) ? mod : null;
|
|
1521
|
-
} catch {
|
|
1522
|
-
return null;
|
|
1523
|
-
}
|
|
773
|
+
async function createRunDir(root, id) {
|
|
774
|
+
const dir = join3(root, ".veris", "runs", id);
|
|
775
|
+
await ensureDir(dir);
|
|
776
|
+
return dir;
|
|
1524
777
|
}
|
|
1525
|
-
function
|
|
1526
|
-
const
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
const parsed = tsmod.parseJsonConfigFileContent(read.config, tsmod.sys, root);
|
|
1530
|
-
return parsed.options;
|
|
778
|
+
async function writeLog(runDir, checkId, content) {
|
|
779
|
+
const ref = join3(runDir, `${checkId}.log`);
|
|
780
|
+
await writeFile2(ref, content, "utf8");
|
|
781
|
+
return ref;
|
|
1531
782
|
}
|
|
1532
|
-
function
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
}
|
|
1539
|
-
const abs = join9(root, file);
|
|
1540
|
-
const out = /* @__PURE__ */ new Set();
|
|
1541
|
-
try {
|
|
1542
|
-
const pre = tsmod.preProcessFile(text, true, true);
|
|
1543
|
-
for (const imp of pre.importedFiles) {
|
|
1544
|
-
const res = tsmod.resolveModuleName(
|
|
1545
|
-
imp.fileName,
|
|
1546
|
-
abs,
|
|
1547
|
-
options,
|
|
1548
|
-
tsmod.sys
|
|
1549
|
-
);
|
|
1550
|
-
const resolved = res.resolvedModule?.resolvedFileName;
|
|
1551
|
-
if (resolved && !resolved.includes("node_modules") && resolved.startsWith(root + sep2)) {
|
|
1552
|
-
out.add(toPosix(relative4(root, resolved)));
|
|
1553
|
-
}
|
|
1554
|
-
}
|
|
1555
|
-
} catch {
|
|
1556
|
-
}
|
|
1557
|
-
return [...out];
|
|
783
|
+
async function writeReport(root, id, markdown) {
|
|
784
|
+
const dir = join3(root, ".veris", "reports");
|
|
785
|
+
await ensureDir(dir);
|
|
786
|
+
const ref = join3(dir, `verify-${id}.md`);
|
|
787
|
+
await writeFile2(ref, markdown, "utf8");
|
|
788
|
+
return ref;
|
|
1558
789
|
}
|
|
1559
|
-
function
|
|
1560
|
-
const
|
|
1561
|
-
|
|
790
|
+
async function writeEvidence(runDir, record) {
|
|
791
|
+
const ref = join3(runDir, "evidence.json");
|
|
792
|
+
await writeFile2(ref, `${JSON.stringify(record, null, 2)}
|
|
793
|
+
`, "utf8");
|
|
794
|
+
return ref;
|
|
795
|
+
}
|
|
796
|
+
async function digestLogs(run) {
|
|
797
|
+
const out = {};
|
|
798
|
+
for (const r of run.results) {
|
|
799
|
+
if (!r.logRef) continue;
|
|
1562
800
|
try {
|
|
1563
|
-
|
|
1564
|
-
return {
|
|
1565
|
-
resolver: "typescript",
|
|
1566
|
-
importsOf: (file) => tsImports(tsmod, root, options, file)
|
|
1567
|
-
};
|
|
801
|
+
out[r.checkId] = sha256(await readFile2(r.logRef, "utf8"));
|
|
1568
802
|
} catch {
|
|
1569
803
|
}
|
|
1570
804
|
}
|
|
1571
|
-
return
|
|
1572
|
-
resolver: "scanner",
|
|
1573
|
-
importsOf: (file) => scannerImports(root, file)
|
|
1574
|
-
};
|
|
805
|
+
return out;
|
|
1575
806
|
}
|
|
1576
|
-
var init_ts_resolver = __esm({
|
|
1577
|
-
"src/project-graph/ts-resolver.ts"() {
|
|
1578
|
-
"use strict";
|
|
1579
|
-
init_discover();
|
|
1580
|
-
init_scanner_resolver();
|
|
1581
|
-
}
|
|
1582
|
-
});
|
|
1583
|
-
|
|
1584
|
-
// src/project-graph/graph.ts
|
|
1585
|
-
var graph_exports = {};
|
|
1586
|
-
__export(graph_exports, {
|
|
1587
|
-
buildGraph: () => buildGraph
|
|
1588
|
-
});
|
|
1589
|
-
async function buildGraph(project) {
|
|
1590
|
-
const root = project.root;
|
|
1591
|
-
const files = discoverFiles(root);
|
|
1592
|
-
const known = new Set(files.map((f) => f.file));
|
|
1593
|
-
const { resolver, importsOf } = selectResolver(root);
|
|
1594
|
-
const nodes = {};
|
|
1595
|
-
for (const f of files) {
|
|
1596
|
-
nodes[f.file] = { file: f.file, kind: f.kind, imports: [], importedBy: [] };
|
|
1597
|
-
}
|
|
1598
|
-
for (const f of files) {
|
|
1599
|
-
const imps = importsOf(f.file).filter((i) => known.has(i) && i !== f.file);
|
|
1600
|
-
nodes[f.file].imports = imps;
|
|
1601
|
-
for (const i of imps) nodes[i]?.importedBy.push(f.file);
|
|
1602
|
-
}
|
|
1603
|
-
return {
|
|
1604
|
-
root,
|
|
1605
|
-
resolver,
|
|
1606
|
-
nodes,
|
|
1607
|
-
sourceFiles: files.filter((f) => f.kind === "source").map((f) => f.file),
|
|
1608
|
-
testFiles: files.filter((f) => f.kind === "test").map((f) => f.file)
|
|
1609
|
-
};
|
|
1610
|
-
}
|
|
1611
|
-
var init_graph = __esm({
|
|
1612
|
-
"src/project-graph/graph.ts"() {
|
|
1613
|
-
"use strict";
|
|
1614
|
-
init_discover();
|
|
1615
|
-
init_ts_resolver();
|
|
1616
|
-
}
|
|
1617
|
-
});
|
|
1618
|
-
|
|
1619
|
-
// src/project-graph/analyze.ts
|
|
1620
|
-
function transitiveDependents(graph, file) {
|
|
1621
|
-
const seen = /* @__PURE__ */ new Set();
|
|
1622
|
-
const stack = [...graph.nodes[file]?.importedBy ?? []];
|
|
1623
|
-
while (stack.length) {
|
|
1624
|
-
const f = stack.pop();
|
|
1625
|
-
if (!f || seen.has(f)) continue;
|
|
1626
|
-
seen.add(f);
|
|
1627
|
-
for (const d of graph.nodes[f]?.importedBy ?? []) stack.push(d);
|
|
1628
|
-
}
|
|
1629
|
-
return seen;
|
|
1630
|
-
}
|
|
1631
|
-
function reachableFromTests(graph) {
|
|
1632
|
-
const seen = /* @__PURE__ */ new Set();
|
|
1633
|
-
const stack = [...graph.testFiles];
|
|
1634
|
-
while (stack.length) {
|
|
1635
|
-
const f = stack.pop();
|
|
1636
|
-
if (!f || seen.has(f)) continue;
|
|
1637
|
-
seen.add(f);
|
|
1638
|
-
for (const i of graph.nodes[f]?.imports ?? []) stack.push(i);
|
|
1639
|
-
}
|
|
1640
|
-
return seen;
|
|
1641
|
-
}
|
|
1642
|
-
function analyze(graph, changed = []) {
|
|
1643
|
-
const blastRadius = {};
|
|
1644
|
-
for (const file of Object.keys(graph.nodes)) {
|
|
1645
|
-
blastRadius[file] = transitiveDependents(graph, file).size;
|
|
1646
|
-
}
|
|
1647
|
-
const reached = reachableFromTests(graph);
|
|
1648
|
-
const byBlast = (a, b) => (blastRadius[b] ?? 0) - (blastRadius[a] ?? 0);
|
|
1649
|
-
const untested = graph.sourceFiles.filter((f) => !reached.has(f)).sort(byBlast);
|
|
1650
|
-
const changedSet = new Set(changed);
|
|
1651
|
-
const untestedSet = new Set(untested);
|
|
1652
|
-
const risky = graph.sourceFiles.filter(
|
|
1653
|
-
(f) => (blastRadius[f] ?? 0) > 0 && (untestedSet.has(f) || changedSet.has(f))
|
|
1654
|
-
).sort(byBlast);
|
|
1655
|
-
return { untested, blastRadius, risky };
|
|
1656
|
-
}
|
|
1657
|
-
var init_analyze = __esm({
|
|
1658
|
-
"src/project-graph/analyze.ts"() {
|
|
1659
|
-
"use strict";
|
|
1660
|
-
}
|
|
1661
|
-
});
|
|
1662
807
|
|
|
1663
|
-
// src/
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
}
|
|
1691
|
-
}
|
|
1692
|
-
const tests = /* @__PURE__ */ new Set();
|
|
1693
|
-
for (const f of changed) {
|
|
1694
|
-
if (graph.nodes[f]?.kind === "test") tests.add(f);
|
|
1695
|
-
for (const dep of transitiveDependents(graph, f)) {
|
|
1696
|
-
if (graph.nodes[dep]?.kind === "test") tests.add(dep);
|
|
1697
|
-
}
|
|
1698
|
-
}
|
|
1699
|
-
if (tests.size === 0) {
|
|
1700
|
-
return {
|
|
1701
|
-
mode: "full",
|
|
1702
|
-
testFiles: [],
|
|
1703
|
-
reason: "no tests reach the changed files"
|
|
1704
|
-
};
|
|
1705
|
-
}
|
|
1706
|
-
const selected = [...tests].sort();
|
|
1707
|
-
if (selected.some((t) => t.startsWith("-"))) {
|
|
1708
|
-
return {
|
|
1709
|
-
mode: "full",
|
|
1710
|
-
testFiles: [],
|
|
1711
|
-
reason: "a reaching test path starts with '-' (unsafe as a CLI argument)"
|
|
1712
|
-
};
|
|
1713
|
-
}
|
|
1714
|
-
return { mode: "graph", testFiles: selected, reason: "" };
|
|
1715
|
-
}
|
|
1716
|
-
var GLOBAL_RE;
|
|
1717
|
-
var init_select = __esm({
|
|
1718
|
-
"src/affected/select.ts"() {
|
|
1719
|
-
"use strict";
|
|
1720
|
-
init_analyze();
|
|
1721
|
-
GLOBAL_RE = /(^|\/)(tsconfig[^/]*\.json|package\.json|biome\.jsonc?|\.eslintrc[^/]*|eslint\.config\.[^/]+|veris\.config\.[^/]+|[^/]+\.config\.[cm]?[jt]sx?|[^/]+\.setup\.[cm]?[jt]sx?)$/;
|
|
1722
|
-
}
|
|
1723
|
-
});
|
|
1724
|
-
|
|
1725
|
-
// src/cli/commands/affected.ts
|
|
1726
|
-
var affected_exports = {};
|
|
1727
|
-
__export(affected_exports, {
|
|
1728
|
-
runAffected: () => runAffected
|
|
1729
|
-
});
|
|
1730
|
-
async function runAffected(root, opts = {}) {
|
|
1731
|
-
const project = await detectProject(root);
|
|
1732
|
-
const { files } = await changedFiles(root, { base: opts.base });
|
|
1733
|
-
const plan = affectedChecks(files, project);
|
|
1734
|
-
if (plan.checks.length === 0) {
|
|
1735
|
-
process.stdout.write(
|
|
1736
|
-
`Nothing affected \u2014 ${files.length} changed file(s), no checks to run.
|
|
1737
|
-
`
|
|
1738
|
-
);
|
|
1739
|
-
return 0;
|
|
1740
|
-
}
|
|
1741
|
-
let targetFiles;
|
|
1742
|
-
let narrowedNote = "";
|
|
1743
|
-
const unitRunner = project.capabilities.find((c) => c.id === "unit")?.runner;
|
|
1744
|
-
const canNarrow = unitRunner === "vitest" || unitRunner === "jest";
|
|
1745
|
-
if (plan.checks.includes("unit") && canNarrow) {
|
|
1746
|
-
const { buildGraph: buildGraph2 } = await Promise.resolve().then(() => (init_graph(), graph_exports));
|
|
1747
|
-
const { selectAffectedTests: selectAffectedTests2 } = await Promise.resolve().then(() => (init_select(), select_exports));
|
|
1748
|
-
const graph = await buildGraph2(project);
|
|
1749
|
-
const sel = selectAffectedTests2(graph, files);
|
|
1750
|
-
if (sel.mode === "graph") {
|
|
1751
|
-
targetFiles = { unit: sel.testFiles };
|
|
1752
|
-
narrowedNote = `unit narrowed to ${sel.testFiles.length} of ${graph.testFiles.length} test file(s) via ${graph.resolver} graph`;
|
|
1753
|
-
} else {
|
|
1754
|
-
narrowedNote = `unit ran in full \u2014 ${sel.reason}`;
|
|
1755
|
-
}
|
|
1756
|
-
} else if (plan.checks.includes("unit") && unitRunner) {
|
|
1757
|
-
narrowedNote = `unit ran in full \u2014 the ${unitRunner} runner does not support file filtering`;
|
|
1758
|
-
}
|
|
1759
|
-
const run = await runChecks(project, plan.checks, root, { targetFiles });
|
|
1760
|
-
run.scope = { kind: "affected", changedCount: files.length };
|
|
1761
|
-
const affected = new Set(plan.checks);
|
|
1762
|
-
for (const cap of project.capabilities) {
|
|
1763
|
-
if (cap.available && cap.id !== "browser" && !affected.has(cap.id)) {
|
|
1764
|
-
run.results.push({
|
|
1765
|
-
checkId: cap.id,
|
|
1766
|
-
status: "skipped",
|
|
1767
|
-
durationMs: 0,
|
|
1768
|
-
summary: "not affected by changes"
|
|
808
|
+
// src/util/exec.ts
|
|
809
|
+
import { spawn } from "child_process";
|
|
810
|
+
function exec(cmd, args, opts = {}) {
|
|
811
|
+
return new Promise((resolve2) => {
|
|
812
|
+
const start = performance.now();
|
|
813
|
+
const child = spawn(cmd, args, {
|
|
814
|
+
cwd: opts.cwd,
|
|
815
|
+
env: opts.env ?? process.env,
|
|
816
|
+
shell: false
|
|
817
|
+
});
|
|
818
|
+
let stdout = "";
|
|
819
|
+
let stderr = "";
|
|
820
|
+
let timedOut = false;
|
|
821
|
+
const timer = opts.timeoutMs ? setTimeout(() => {
|
|
822
|
+
timedOut = true;
|
|
823
|
+
child.kill("SIGKILL");
|
|
824
|
+
}, opts.timeoutMs) : null;
|
|
825
|
+
child.stdout.on("data", (d) => stdout += d.toString());
|
|
826
|
+
child.stderr.on("data", (d) => stderr += d.toString());
|
|
827
|
+
child.on("close", (code) => {
|
|
828
|
+
if (timer) clearTimeout(timer);
|
|
829
|
+
resolve2({
|
|
830
|
+
code: code ?? 1,
|
|
831
|
+
stdout,
|
|
832
|
+
stderr,
|
|
833
|
+
durationMs: Math.round(performance.now() - start),
|
|
834
|
+
timedOut
|
|
1769
835
|
});
|
|
1770
|
-
}
|
|
1771
|
-
|
|
1772
|
-
const git = await gitAnchor(root);
|
|
1773
|
-
const logDigests = await digestLogs(run);
|
|
1774
|
-
const record = buildRecord(run, git, logDigests, VERSION);
|
|
1775
|
-
const reportRef = await writeReport(
|
|
1776
|
-
root,
|
|
1777
|
-
run.id,
|
|
1778
|
-
renderMarkdown(run, record)
|
|
1779
|
-
);
|
|
1780
|
-
run.reportRef = reportRef;
|
|
1781
|
-
const runDir = await createRunDir(root, run.id);
|
|
1782
|
-
await writeEvidence(runDir, record);
|
|
1783
|
-
process.stdout.write(`${renderRun(run, record)}
|
|
1784
|
-
`);
|
|
1785
|
-
if (narrowedNote) process.stdout.write(`${narrowedNote}
|
|
1786
|
-
`);
|
|
1787
|
-
if (opts.github) await publishToGitHub(run, record);
|
|
1788
|
-
return verdictExitCode(run.verdict, opts);
|
|
1789
|
-
}
|
|
1790
|
-
var init_affected = __esm({
|
|
1791
|
-
"src/cli/commands/affected.ts"() {
|
|
1792
|
-
"use strict";
|
|
1793
|
-
init_gate();
|
|
1794
|
-
init_detect();
|
|
1795
|
-
init_orchestrator();
|
|
1796
|
-
init_verdict();
|
|
1797
|
-
init_record();
|
|
1798
|
-
init_store();
|
|
1799
|
-
init_changes();
|
|
1800
|
-
init_publish();
|
|
1801
|
-
init_markdown();
|
|
1802
|
-
init_terminal();
|
|
1803
|
-
init_version();
|
|
1804
|
-
}
|
|
1805
|
-
});
|
|
1806
|
-
|
|
1807
|
-
// src/watch/watcher.ts
|
|
1808
|
-
import {
|
|
1809
|
-
watch as fsWatch,
|
|
1810
|
-
readdirSync as readdirSync4,
|
|
1811
|
-
statSync as statSync3
|
|
1812
|
-
} from "fs";
|
|
1813
|
-
import { basename as basename2, join as join10, relative as relative5 } from "path";
|
|
1814
|
-
function watch(root, opts, onBatch) {
|
|
1815
|
-
const debounceMs = opts.debounceMs ?? 150;
|
|
1816
|
-
const rootBase = basename2(root);
|
|
1817
|
-
let pending = /* @__PURE__ */ new Set();
|
|
1818
|
-
let timer = null;
|
|
1819
|
-
const flush = () => {
|
|
1820
|
-
const batch = [...pending];
|
|
1821
|
-
pending = /* @__PURE__ */ new Set();
|
|
1822
|
-
timer = null;
|
|
1823
|
-
if (batch.length) onBatch(batch);
|
|
1824
|
-
};
|
|
1825
|
-
const schedule = (rel) => {
|
|
1826
|
-
if (!rel || rel === rootBase || IGNORE2.test(rel)) return;
|
|
1827
|
-
pending.add(rel);
|
|
1828
|
-
if (timer) clearTimeout(timer);
|
|
1829
|
-
timer = setTimeout(flush, debounceMs);
|
|
1830
|
-
};
|
|
1831
|
-
if (opts.poll) {
|
|
1832
|
-
const interval = opts.pollIntervalMs ?? 400;
|
|
1833
|
-
const mtimes = scanMtimes(root);
|
|
1834
|
-
const tick = setInterval(() => {
|
|
1835
|
-
const next = scanMtimes(root);
|
|
1836
|
-
for (const [p, m] of next) {
|
|
1837
|
-
if (mtimes.get(p) !== m) schedule(p);
|
|
1838
|
-
}
|
|
1839
|
-
mtimes.clear();
|
|
1840
|
-
for (const [p, m] of next) mtimes.set(p, m);
|
|
1841
|
-
}, interval);
|
|
1842
|
-
return () => {
|
|
1843
|
-
clearInterval(tick);
|
|
836
|
+
});
|
|
837
|
+
child.on("error", () => {
|
|
1844
838
|
if (timer) clearTimeout(timer);
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
839
|
+
resolve2({
|
|
840
|
+
code: 127,
|
|
841
|
+
stdout,
|
|
842
|
+
stderr: stderr || `failed to spawn ${cmd}`,
|
|
843
|
+
durationMs: Math.round(performance.now() - start),
|
|
844
|
+
timedOut
|
|
845
|
+
});
|
|
1851
846
|
});
|
|
1852
|
-
}
|
|
1853
|
-
throw new Error(
|
|
1854
|
-
"recursive fs.watch is unavailable on this platform; rerun with --poll"
|
|
1855
|
-
);
|
|
1856
|
-
}
|
|
1857
|
-
return () => {
|
|
1858
|
-
if (timer) clearTimeout(timer);
|
|
1859
|
-
watcher.close();
|
|
1860
|
-
};
|
|
1861
|
-
}
|
|
1862
|
-
function scanMtimes(root) {
|
|
1863
|
-
const out = /* @__PURE__ */ new Map();
|
|
1864
|
-
const walk = (dir) => {
|
|
1865
|
-
let entries;
|
|
1866
|
-
try {
|
|
1867
|
-
entries = readdirSync4(dir);
|
|
1868
|
-
} catch {
|
|
1869
|
-
return;
|
|
1870
|
-
}
|
|
1871
|
-
for (const name of entries) {
|
|
1872
|
-
const abs = join10(dir, name);
|
|
1873
|
-
const rel = relative5(root, abs);
|
|
1874
|
-
if (IGNORE2.test(rel)) continue;
|
|
1875
|
-
let st;
|
|
1876
|
-
try {
|
|
1877
|
-
st = statSync3(abs);
|
|
1878
|
-
} catch {
|
|
1879
|
-
continue;
|
|
1880
|
-
}
|
|
1881
|
-
if (st.isDirectory()) walk(abs);
|
|
1882
|
-
else out.set(rel, st.mtimeMs);
|
|
1883
|
-
}
|
|
1884
|
-
};
|
|
1885
|
-
walk(root);
|
|
1886
|
-
return out;
|
|
847
|
+
});
|
|
1887
848
|
}
|
|
1888
|
-
var IGNORE2;
|
|
1889
|
-
var init_watcher = __esm({
|
|
1890
|
-
"src/watch/watcher.ts"() {
|
|
1891
|
-
"use strict";
|
|
1892
|
-
IGNORE2 = /(^|\/)(\.git|\.veris|\.agentloop|\.agentflight|node_modules|dist)(\/|$)/;
|
|
1893
|
-
}
|
|
1894
|
-
});
|
|
1895
849
|
|
|
1896
|
-
// src/
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
});
|
|
1902
|
-
function buildWatchResults(project, affected, fresh, cache) {
|
|
1903
|
-
const affectedSet = new Set(affected);
|
|
1904
|
-
const freshById = new Map(fresh.map((r) => [r.checkId, r]));
|
|
1905
|
-
const out = [];
|
|
1906
|
-
for (const cap of project.capabilities) {
|
|
1907
|
-
if (!cap.available || cap.id === "browser") continue;
|
|
1908
|
-
const f = freshById.get(cap.id);
|
|
1909
|
-
if (f) {
|
|
1910
|
-
out.push({ ...f, cached: false });
|
|
1911
|
-
continue;
|
|
1912
|
-
}
|
|
1913
|
-
const c = cache.get(cap.id);
|
|
1914
|
-
if (c && !affectedSet.has(cap.id)) {
|
|
1915
|
-
out.push({ ...c, cached: true });
|
|
1916
|
-
continue;
|
|
1917
|
-
}
|
|
1918
|
-
out.push({
|
|
1919
|
-
checkId: cap.id,
|
|
1920
|
-
status: "skipped",
|
|
1921
|
-
durationMs: 0,
|
|
1922
|
-
summary: "not affected by changes"
|
|
1923
|
-
});
|
|
850
|
+
// src/git/changes.ts
|
|
851
|
+
function collect(set, out) {
|
|
852
|
+
for (const line of out.split("\n")) {
|
|
853
|
+
const f = line.trim();
|
|
854
|
+
if (f) set.add(f);
|
|
1924
855
|
}
|
|
1925
|
-
return out;
|
|
1926
856
|
}
|
|
1927
|
-
async function
|
|
1928
|
-
const
|
|
1929
|
-
|
|
1930
|
-
const
|
|
1931
|
-
const
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
const graph = await buildGraph2(project);
|
|
1948
|
-
const sel = selectAffectedTests2(graph, files);
|
|
1949
|
-
if (sel.mode === "graph") {
|
|
1950
|
-
targetFiles = { unit: sel.testFiles };
|
|
1951
|
-
narrowedNote = `unit narrowed to ${sel.testFiles.length} of ${graph.testFiles.length} test file(s) via ${graph.resolver} graph`;
|
|
1952
|
-
} else {
|
|
1953
|
-
narrowedNote = `unit ran in full \u2014 ${sel.reason}`;
|
|
1954
|
-
}
|
|
1955
|
-
} else if (!initial && checks.includes("unit") && unitRunner) {
|
|
1956
|
-
narrowedNote = `unit ran in full \u2014 the ${unitRunner} runner does not support file filtering`;
|
|
1957
|
-
}
|
|
1958
|
-
let fresh = [];
|
|
1959
|
-
if (checks.length) {
|
|
1960
|
-
const r = await runChecks(project, checks, root, { targetFiles });
|
|
1961
|
-
fresh = r.results;
|
|
1962
|
-
for (const result of fresh) cache.set(result.checkId, { ...result });
|
|
1963
|
-
}
|
|
1964
|
-
const results = buildWatchResults(project, checks, fresh, cache);
|
|
1965
|
-
const availableCaps = project.capabilities.filter(
|
|
1966
|
-
(c) => c.available && c.id !== "browser"
|
|
1967
|
-
);
|
|
1968
|
-
const verdict = computeVerdict(results, availableCaps);
|
|
1969
|
-
const run = {
|
|
1970
|
-
id: "watch",
|
|
1971
|
-
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1972
|
-
project,
|
|
1973
|
-
results,
|
|
1974
|
-
verdict,
|
|
1975
|
-
env: {
|
|
1976
|
-
os: process.platform,
|
|
1977
|
-
node: process.version,
|
|
1978
|
-
pm: project.packageManager,
|
|
1979
|
-
ci: false,
|
|
1980
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1981
|
-
},
|
|
1982
|
-
scope: { kind: "watch", changedCount: files.length }
|
|
1983
|
-
};
|
|
1984
|
-
process.stdout.write(`${renderRun(run)}
|
|
1985
|
-
`);
|
|
1986
|
-
if (narrowedNote) process.stdout.write(`${narrowedNote}
|
|
1987
|
-
`);
|
|
1988
|
-
} finally {
|
|
1989
|
-
running = false;
|
|
1990
|
-
}
|
|
1991
|
-
};
|
|
1992
|
-
process.stdout.write("veris watch \u2014 press Ctrl-C to stop\n");
|
|
1993
|
-
await tick(true);
|
|
1994
|
-
return await new Promise((resolve2) => {
|
|
1995
|
-
let stop;
|
|
1996
|
-
try {
|
|
1997
|
-
stop = watch(root, { poll: opts.poll }, () => {
|
|
1998
|
-
void tick(false);
|
|
1999
|
-
});
|
|
2000
|
-
} catch (err) {
|
|
2001
|
-
process.stderr.write(
|
|
2002
|
-
`veris: ${err instanceof Error ? err.message : String(err)}
|
|
2003
|
-
`
|
|
2004
|
-
);
|
|
2005
|
-
resolve2(1);
|
|
2006
|
-
return;
|
|
2007
|
-
}
|
|
2008
|
-
process.on("SIGINT", () => {
|
|
2009
|
-
stop();
|
|
2010
|
-
process.stdout.write("\nStopped.\n");
|
|
2011
|
-
resolve2(0);
|
|
2012
|
-
});
|
|
857
|
+
async function changedFiles(root, opts = {}) {
|
|
858
|
+
const base = opts.base ?? null;
|
|
859
|
+
const files = /* @__PURE__ */ new Set();
|
|
860
|
+
const diffArgs = base ? ["diff", "--name-only", base] : ["diff", "--name-only", "HEAD"];
|
|
861
|
+
const diff = await exec("git", diffArgs, { cwd: root });
|
|
862
|
+
if (diff.code === 0) collect(files, diff.stdout);
|
|
863
|
+
const untracked = await exec(
|
|
864
|
+
"git",
|
|
865
|
+
["ls-files", "--others", "--exclude-standard"],
|
|
866
|
+
{ cwd: root }
|
|
867
|
+
);
|
|
868
|
+
if (untracked.code === 0) collect(files, untracked.stdout);
|
|
869
|
+
return { files: [...files].sort(), base };
|
|
870
|
+
}
|
|
871
|
+
async function gitAnchor(root) {
|
|
872
|
+
const head = await exec("git", ["rev-parse", "HEAD"], { cwd: root });
|
|
873
|
+
if (head.code !== 0) return null;
|
|
874
|
+
const commit = head.stdout.trim();
|
|
875
|
+
const branchRes = await exec("git", ["rev-parse", "--abbrev-ref", "HEAD"], {
|
|
876
|
+
cwd: root
|
|
2013
877
|
});
|
|
878
|
+
const branch = branchRes.code === 0 ? branchRes.stdout.trim() : "HEAD";
|
|
879
|
+
const status = await exec("git", ["status", "--porcelain"], { cwd: root });
|
|
880
|
+
const lines = status.code === 0 ? status.stdout.split("\n").map((l) => l.trim()).filter(Boolean) : [];
|
|
881
|
+
return {
|
|
882
|
+
commit,
|
|
883
|
+
branch,
|
|
884
|
+
dirty: lines.length > 0,
|
|
885
|
+
changedFiles: lines.length
|
|
886
|
+
};
|
|
2014
887
|
}
|
|
2015
|
-
var init_watch = __esm({
|
|
2016
|
-
"src/cli/commands/watch.ts"() {
|
|
2017
|
-
"use strict";
|
|
2018
|
-
init_gate();
|
|
2019
|
-
init_detect();
|
|
2020
|
-
init_orchestrator();
|
|
2021
|
-
init_verdict();
|
|
2022
|
-
init_changes();
|
|
2023
|
-
init_terminal();
|
|
2024
|
-
init_watcher();
|
|
2025
|
-
}
|
|
2026
|
-
});
|
|
2027
888
|
|
|
2028
|
-
// src/
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
const
|
|
2040
|
-
const dim = (s) => plain ? s : pc3.dim(s);
|
|
2041
|
-
const warn = (s) => plain ? s : pc3.yellow(s);
|
|
889
|
+
// src/reporters/markdown.ts
|
|
890
|
+
import { relative } from "path";
|
|
891
|
+
var STATE_LABEL = {
|
|
892
|
+
verified: "Verified",
|
|
893
|
+
failed: "Failed",
|
|
894
|
+
partial: "Partial"
|
|
895
|
+
};
|
|
896
|
+
function cell(value) {
|
|
897
|
+
return value.replace(/\|/g, "\\|");
|
|
898
|
+
}
|
|
899
|
+
function renderMarkdown(run, record) {
|
|
900
|
+
const root = run.project.root;
|
|
2042
901
|
const lines = [];
|
|
2043
|
-
lines.push(
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
`Resolver ${graph.resolver}${graph.resolver === "scanner" ? dim(" (no TypeScript found \u2014 relative imports only)") : ""}`
|
|
2047
|
-
);
|
|
2048
|
-
lines.push(
|
|
2049
|
-
`Modules ${Object.keys(graph.nodes).length} \xB7 Source ${graph.sourceFiles.length} \xB7 Tests ${graph.testFiles.length}`
|
|
2050
|
-
);
|
|
2051
|
-
lines.push("");
|
|
2052
|
-
lines.push("Untested (top by impact)");
|
|
2053
|
-
const top = analysis.untested.slice(0, 10);
|
|
2054
|
-
if (top.length === 0)
|
|
2055
|
-
lines.push(dim(" none \u2014 every source file is reached by a test"));
|
|
2056
|
-
for (const f of top) {
|
|
902
|
+
lines.push("# VerisKit Verification Report");
|
|
903
|
+
if (run.scope?.kind) {
|
|
904
|
+
lines.push("");
|
|
2057
905
|
lines.push(
|
|
2058
|
-
|
|
906
|
+
`> **Scope:** ${run.scope.kind} \u2014 ${run.scope.changedCount} changed file(s). Only affected checks ran; this is not a full verification.`
|
|
2059
907
|
);
|
|
2060
908
|
}
|
|
2061
|
-
|
|
2062
|
-
}
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
const analysis = analyze(graph);
|
|
2067
|
-
const dir = join11(root, ".veris");
|
|
2068
|
-
await ensureDir(dir);
|
|
2069
|
-
await writeFile3(
|
|
2070
|
-
join11(dir, "graph.json"),
|
|
2071
|
-
JSON.stringify({ resolver: graph.resolver, nodes: graph.nodes }, null, 2),
|
|
2072
|
-
"utf8"
|
|
909
|
+
lines.push("");
|
|
910
|
+
lines.push(`**Verdict:** ${STATE_LABEL[run.verdict.state]}`);
|
|
911
|
+
lines.push(`**When:** ${run.startedAt}`);
|
|
912
|
+
lines.push(
|
|
913
|
+
`**Node:** ${run.env.node} \xB7 **OS:** ${run.env.os} \xB7 **PM:** ${run.env.pm} \xB7 **CI:** ${run.env.ci}`
|
|
2073
914
|
);
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
}
|
|
2081
|
-
var init_scan = __esm({
|
|
2082
|
-
"src/cli/commands/scan.ts"() {
|
|
2083
|
-
"use strict";
|
|
2084
|
-
init_detect();
|
|
2085
|
-
init_analyze();
|
|
2086
|
-
init_graph();
|
|
2087
|
-
init_fs_safe();
|
|
2088
|
-
init_tty();
|
|
915
|
+
if (record?.git) {
|
|
916
|
+
const g = record.git;
|
|
917
|
+
const tree = g.dirty ? `tree dirty, ${g.changedFiles} uncommitted file(s)` : "tree clean";
|
|
918
|
+
lines.push(`**Commit:** ${g.commit.slice(0, 7)} (${g.branch}) \xB7 ${tree}`);
|
|
919
|
+
} else if (record) {
|
|
920
|
+
lines.push("**Commit:** no git anchor available");
|
|
2089
921
|
}
|
|
2090
|
-
});
|
|
2091
|
-
|
|
2092
|
-
// src/cli/commands/plan.ts
|
|
2093
|
-
var plan_exports = {};
|
|
2094
|
-
__export(plan_exports, {
|
|
2095
|
-
renderPlan: () => renderPlan,
|
|
2096
|
-
runPlan: () => runPlan
|
|
2097
|
-
});
|
|
2098
|
-
import pc4 from "picocolors";
|
|
2099
|
-
function renderPlan(project, graph, analysis, changed) {
|
|
2100
|
-
const plain = isPlain();
|
|
2101
|
-
const bold = (s) => plain ? s : pc4.bold(s);
|
|
2102
|
-
const dim = (s) => plain ? s : pc4.dim(s);
|
|
2103
|
-
const warn = (s) => plain ? s : pc4.yellow(s);
|
|
2104
|
-
const lines = [];
|
|
2105
|
-
lines.push(bold("VerisKit \u2014 plan"));
|
|
2106
|
-
lines.push(dim(`(graph via ${graph.resolver})`));
|
|
2107
922
|
lines.push("");
|
|
2108
|
-
lines.push("
|
|
2109
|
-
const top = analysis.untested.slice(0, 5);
|
|
2110
|
-
if (top.length === 0)
|
|
2111
|
-
lines.push(
|
|
2112
|
-
dim(
|
|
2113
|
-
" none \u2014 untested files have no dependents or all files are covered"
|
|
2114
|
-
)
|
|
2115
|
-
);
|
|
2116
|
-
top.forEach((f, i) => {
|
|
2117
|
-
lines.push(
|
|
2118
|
-
` ${i + 1}. ${f} ${dim(`(${analysis.blastRadius[f] ?? 0} dependents, no test reaches it)`)}`
|
|
2119
|
-
);
|
|
2120
|
-
});
|
|
923
|
+
lines.push("## Checks");
|
|
2121
924
|
lines.push("");
|
|
2122
|
-
lines.push("
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
lines.push(dim(" \u2713 types, lint, and unit are all configured"));
|
|
2128
|
-
for (const c of weak)
|
|
925
|
+
lines.push("| Check | Status | Duration | Summary | Log |");
|
|
926
|
+
lines.push("| --- | --- | --- | --- | --- |");
|
|
927
|
+
for (const r of run.results) {
|
|
928
|
+
const dur = r.durationMs ? `${(r.durationMs / 1e3).toFixed(1)}s` : "\u2014";
|
|
929
|
+
const log = r.logRef ? cell(relative(root, r.logRef)) : "\u2014";
|
|
2129
930
|
lines.push(
|
|
2130
|
-
|
|
931
|
+
`| ${r.checkId} | ${r.status} | ${dur} | ${cell(r.summary)} | ${log} |`
|
|
2131
932
|
);
|
|
2132
|
-
if (changed.length) {
|
|
2133
|
-
lines.push("");
|
|
2134
|
-
lines.push(`Risky changes (${changed.length} changed file(s))`);
|
|
2135
|
-
if (analysis.risky.length === 0) lines.push(dim(" none flagged"));
|
|
2136
|
-
for (const f of analysis.risky.slice(0, 8)) {
|
|
2137
|
-
const untested = analysis.untested.includes(f) ? ", untested" : "";
|
|
2138
|
-
lines.push(
|
|
2139
|
-
` ${warn("!")} ${f} ${dim(`${analysis.blastRadius[f] ?? 0} dependents${untested}`)}`
|
|
2140
|
-
);
|
|
2141
|
-
}
|
|
2142
933
|
}
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
}
|
|
2149
|
-
async function runPlan(root, opts = {}) {
|
|
2150
|
-
const project = await detectProject(root);
|
|
2151
|
-
const graph = await buildGraph(project);
|
|
2152
|
-
const { files } = await changedFiles(root, { base: opts.base });
|
|
2153
|
-
const analysis = analyze(graph, files);
|
|
2154
|
-
process.stdout.write(`${renderPlan(project, graph, analysis, files)}
|
|
2155
|
-
`);
|
|
2156
|
-
return 0;
|
|
2157
|
-
}
|
|
2158
|
-
var init_plan = __esm({
|
|
2159
|
-
"src/cli/commands/plan.ts"() {
|
|
2160
|
-
"use strict";
|
|
2161
|
-
init_detect();
|
|
2162
|
-
init_changes();
|
|
2163
|
-
init_analyze();
|
|
2164
|
-
init_graph();
|
|
2165
|
-
init_tty();
|
|
934
|
+
if (run.verdict.skipped.length) {
|
|
935
|
+
lines.push("");
|
|
936
|
+
lines.push("## Skipped");
|
|
937
|
+
lines.push("");
|
|
938
|
+
for (const reason of run.verdict.reasons) lines.push(`- ${reason}`);
|
|
2166
939
|
}
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
function signatureKeyId(sig) {
|
|
2186
|
-
const key = createPublicKey({
|
|
2187
|
-
key: Buffer.from(sig.publicKey, "base64"),
|
|
2188
|
-
format: "der",
|
|
2189
|
-
type: "spki"
|
|
2190
|
-
});
|
|
2191
|
-
return keyId(key);
|
|
2192
|
-
}
|
|
2193
|
-
function generateKeyPair() {
|
|
2194
|
-
const { publicKey, privateKey } = generateKeyPairSync("ed25519");
|
|
2195
|
-
return {
|
|
2196
|
-
publicKeyPem: publicKey.export({ type: "spki", format: "pem" }),
|
|
2197
|
-
privateKeyPem: privateKey.export({
|
|
2198
|
-
type: "pkcs8",
|
|
2199
|
-
format: "pem"
|
|
2200
|
-
}),
|
|
2201
|
-
keyId: keyId(publicKey)
|
|
2202
|
-
};
|
|
2203
|
-
}
|
|
2204
|
-
function signDigest(digest, privateKeyPem) {
|
|
2205
|
-
const privateKey = createPrivateKey(privateKeyPem);
|
|
2206
|
-
const publicKey = createPublicKey(
|
|
2207
|
-
privateKey
|
|
2208
|
-
);
|
|
2209
|
-
const sig = cryptoSign(null, Buffer.from(digest, "utf8"), privateKey);
|
|
2210
|
-
return {
|
|
2211
|
-
schema: SIGNATURE_SCHEMA,
|
|
2212
|
-
alg: "ed25519",
|
|
2213
|
-
keyId: keyId(publicKey),
|
|
2214
|
-
publicKey: derOf(publicKey).toString("base64"),
|
|
2215
|
-
digest,
|
|
2216
|
-
signature: sig.toString("base64"),
|
|
2217
|
-
signedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2218
|
-
};
|
|
2219
|
-
}
|
|
2220
|
-
function verifySignature(sig) {
|
|
2221
|
-
try {
|
|
2222
|
-
const publicKey = createPublicKey({
|
|
2223
|
-
key: Buffer.from(sig.publicKey, "base64"),
|
|
2224
|
-
format: "der",
|
|
2225
|
-
type: "spki"
|
|
2226
|
-
});
|
|
2227
|
-
return cryptoVerify(
|
|
2228
|
-
null,
|
|
2229
|
-
Buffer.from(sig.digest, "utf8"),
|
|
2230
|
-
publicKey,
|
|
2231
|
-
Buffer.from(sig.signature, "base64")
|
|
940
|
+
const failures = run.results.filter((r) => r.outputTail);
|
|
941
|
+
if (failures.length) {
|
|
942
|
+
lines.push("");
|
|
943
|
+
lines.push("## Failure output");
|
|
944
|
+
for (const r of failures) {
|
|
945
|
+
lines.push("");
|
|
946
|
+
lines.push(`### ${r.checkId}`);
|
|
947
|
+
lines.push("");
|
|
948
|
+
lines.push("```");
|
|
949
|
+
lines.push(r.outputTail ?? "");
|
|
950
|
+
lines.push("```");
|
|
951
|
+
}
|
|
952
|
+
}
|
|
953
|
+
if (record) {
|
|
954
|
+
lines.push("");
|
|
955
|
+
lines.push(`**Evidence digest:** \`${record.digest}\``);
|
|
956
|
+
lines.push(
|
|
957
|
+
"_Integrity digest over the canonical record. Detects edits and corruption; it is not forgery-proof on its own. Publish the digest separately or sign it to prove authorship._"
|
|
2232
958
|
);
|
|
2233
|
-
} catch {
|
|
2234
|
-
return false;
|
|
2235
959
|
}
|
|
960
|
+
lines.push("");
|
|
961
|
+
lines.push(
|
|
962
|
+
"_Generated by VerisKit. A `Partial` verdict means one or more capabilities did not run \u2014 it is not a pass._"
|
|
963
|
+
);
|
|
964
|
+
lines.push("");
|
|
965
|
+
return lines.join("\n");
|
|
2236
966
|
}
|
|
2237
|
-
var SIGNATURE_SCHEMA;
|
|
2238
|
-
var init_signing = __esm({
|
|
2239
|
-
"src/evidence/signing.ts"() {
|
|
2240
|
-
"use strict";
|
|
2241
|
-
SIGNATURE_SCHEMA = "veriskit/signature@1";
|
|
2242
|
-
}
|
|
2243
|
-
});
|
|
2244
967
|
|
|
2245
|
-
// src/
|
|
2246
|
-
import {
|
|
2247
|
-
import {
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
detail: ok ? "matches the canonical record" : `recomputed ${recomputed}, record claims ${record.digest}`
|
|
2258
|
-
}
|
|
2259
|
-
];
|
|
2260
|
-
return { ok, kind: "record", record, checks, signed: false };
|
|
968
|
+
// src/version.ts
|
|
969
|
+
import { readFileSync } from "fs";
|
|
970
|
+
import { fileURLToPath } from "url";
|
|
971
|
+
var pkgUrl = new URL("../package.json", import.meta.url);
|
|
972
|
+
var VERSION = JSON.parse(
|
|
973
|
+
readFileSync(fileURLToPath(pkgUrl), "utf8")
|
|
974
|
+
).version;
|
|
975
|
+
|
|
976
|
+
// src/runners/base.ts
|
|
977
|
+
import { join as join4 } from "path";
|
|
978
|
+
function localBin(root, name) {
|
|
979
|
+
return join4(root, "node_modules", ".bin", name);
|
|
2261
980
|
}
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
const
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
name: "signature",
|
|
2268
|
-
ok: validSig,
|
|
2269
|
-
detail: validSig ? `valid ed25519 signature by key ${kid}` : "signature does not match this record"
|
|
981
|
+
var TAIL_LINES = 20;
|
|
982
|
+
async function runViaExec(check, ctx, opts) {
|
|
983
|
+
const r = await exec(check.cmd, check.args, {
|
|
984
|
+
cwd: ctx.root,
|
|
985
|
+
timeoutMs: opts.timeoutMs
|
|
2270
986
|
});
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
ok: matches,
|
|
2285
|
-
detail: matches ? `signer matches the expected key ${kid}` : `signer ${kid} does not match the expected key`
|
|
2286
|
-
});
|
|
2287
|
-
}
|
|
2288
|
-
return checks;
|
|
2289
|
-
}
|
|
2290
|
-
async function verifyEvidenceFile(path, opts = {}) {
|
|
2291
|
-
const parsed = JSON.parse(await readFile3(path, "utf8"));
|
|
2292
|
-
if (parsed?.schema === "veriskit/bundle@1") {
|
|
2293
|
-
const { verifyBundle: verifyBundle2 } = await Promise.resolve().then(() => (init_bundle(), bundle_exports));
|
|
2294
|
-
return verifyBundle2(parsed, opts);
|
|
2295
|
-
}
|
|
2296
|
-
const result = verifyRecord(parsed);
|
|
2297
|
-
const assertedSigner = Boolean(
|
|
2298
|
-
opts.expectedKeyId || opts.expectedPubKeyPem || opts.sigPath
|
|
2299
|
-
);
|
|
2300
|
-
const sigPath = opts.sigPath ?? `${path}.sig`;
|
|
2301
|
-
if (existsSync5(sigPath)) {
|
|
2302
|
-
const sig = JSON.parse(await readFile3(sigPath, "utf8"));
|
|
2303
|
-
result.checks.push(
|
|
2304
|
-
...signatureChecks(result.record.digest, sig, {
|
|
2305
|
-
expectedKeyId: opts.expectedKeyId,
|
|
2306
|
-
expectedPubKeyPem: opts.expectedPubKeyPem
|
|
2307
|
-
})
|
|
2308
|
-
);
|
|
2309
|
-
result.signed = true;
|
|
2310
|
-
} else if (assertedSigner) {
|
|
2311
|
-
result.checks.push({
|
|
2312
|
-
name: "signature",
|
|
2313
|
-
ok: false,
|
|
2314
|
-
detail: "a signature was required (--pubkey/--key-id/--sig) but none was found"
|
|
2315
|
-
});
|
|
987
|
+
const status = r.code === 0 ? "passed" : r.timedOut ? "unknown" : "failed";
|
|
988
|
+
const output = [r.stdout, r.stderr].filter(Boolean).join("\n").trim();
|
|
989
|
+
const logRef = await writeLog(ctx.runDir, check.id, `${output}
|
|
990
|
+
`);
|
|
991
|
+
const result = {
|
|
992
|
+
checkId: check.id,
|
|
993
|
+
status,
|
|
994
|
+
durationMs: r.durationMs,
|
|
995
|
+
summary: status === "passed" ? opts.pass : r.timedOut ? "timed out" : opts.fail,
|
|
996
|
+
logRef
|
|
997
|
+
};
|
|
998
|
+
if (status !== "passed" && output) {
|
|
999
|
+
result.outputTail = output.split("\n").slice(-TAIL_LINES).join("\n");
|
|
2316
1000
|
}
|
|
2317
|
-
result.ok = result.checks.every((c) => c.ok);
|
|
2318
1001
|
return result;
|
|
2319
1002
|
}
|
|
2320
|
-
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
1003
|
+
|
|
1004
|
+
// src/runners/biome.ts
|
|
1005
|
+
var biomeRunner = {
|
|
1006
|
+
id: "biome",
|
|
1007
|
+
toCheck(project, _cap) {
|
|
1008
|
+
return {
|
|
1009
|
+
id: "lint",
|
|
1010
|
+
title: "Lint",
|
|
1011
|
+
runner: "biome",
|
|
1012
|
+
cmd: localBin(project.root, "biome"),
|
|
1013
|
+
args: ["check", "."]
|
|
1014
|
+
};
|
|
1015
|
+
},
|
|
1016
|
+
run(check, ctx) {
|
|
1017
|
+
return runViaExec(check, ctx, {
|
|
1018
|
+
pass: "no lint errors",
|
|
1019
|
+
fail: "lint errors found",
|
|
1020
|
+
timeoutMs: 3 * 6e4
|
|
1021
|
+
});
|
|
2325
1022
|
}
|
|
2326
|
-
}
|
|
1023
|
+
};
|
|
2327
1024
|
|
|
2328
|
-
// src/
|
|
2329
|
-
var
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
}
|
|
2346
|
-
function verifyBundle(bundle, opts = {}) {
|
|
2347
|
-
const checks = [];
|
|
2348
|
-
const recordResult = verifyRecord(bundle.record);
|
|
2349
|
-
checks.push(...recordResult.checks);
|
|
2350
|
-
const reportDigest = sha256(bundle.report);
|
|
2351
|
-
checks.push({
|
|
2352
|
-
name: "report",
|
|
2353
|
-
ok: reportDigest === bundle.manifest.report,
|
|
2354
|
-
detail: reportDigest === bundle.manifest.report ? "matches manifest" : "mismatch"
|
|
2355
|
-
});
|
|
2356
|
-
for (const [id, body] of Object.entries(bundle.logs)) {
|
|
2357
|
-
const d = sha256(body);
|
|
2358
|
-
checks.push({
|
|
2359
|
-
name: `log:${id}`,
|
|
2360
|
-
ok: d === bundle.manifest.logs[id],
|
|
2361
|
-
detail: d === bundle.manifest.logs[id] ? "matches manifest" : "mismatch"
|
|
1025
|
+
// src/runners/eslint.ts
|
|
1026
|
+
var eslintRunner = {
|
|
1027
|
+
id: "eslint",
|
|
1028
|
+
toCheck(project, _cap) {
|
|
1029
|
+
return {
|
|
1030
|
+
id: "lint",
|
|
1031
|
+
title: "Lint",
|
|
1032
|
+
runner: "eslint",
|
|
1033
|
+
cmd: localBin(project.root, "eslint"),
|
|
1034
|
+
args: ["."]
|
|
1035
|
+
};
|
|
1036
|
+
},
|
|
1037
|
+
run(check, ctx) {
|
|
1038
|
+
return runViaExec(check, ctx, {
|
|
1039
|
+
pass: "no lint errors",
|
|
1040
|
+
fail: "lint errors found",
|
|
1041
|
+
timeoutMs: 3 * 6e4
|
|
2362
1042
|
});
|
|
2363
1043
|
}
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
2374
|
-
|
|
2375
|
-
|
|
2376
|
-
|
|
2377
|
-
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
ok: false,
|
|
2384
|
-
detail: "a signature was required (--pubkey/--key-id) but the bundle is unsigned"
|
|
1044
|
+
};
|
|
1045
|
+
|
|
1046
|
+
// src/runners/jest.ts
|
|
1047
|
+
var jestRunner = {
|
|
1048
|
+
id: "jest",
|
|
1049
|
+
toCheck(project, _cap, opts) {
|
|
1050
|
+
return {
|
|
1051
|
+
id: "unit",
|
|
1052
|
+
title: "Unit tests",
|
|
1053
|
+
runner: "jest",
|
|
1054
|
+
cmd: localBin(project.root, "jest"),
|
|
1055
|
+
args: ["--ci", ...opts?.targetFiles ?? []]
|
|
1056
|
+
};
|
|
1057
|
+
},
|
|
1058
|
+
run(check, ctx) {
|
|
1059
|
+
return runViaExec(check, ctx, {
|
|
1060
|
+
pass: "unit tests passed",
|
|
1061
|
+
fail: "unit tests failed",
|
|
1062
|
+
timeoutMs: 10 * 6e4
|
|
2385
1063
|
});
|
|
2386
1064
|
}
|
|
2387
|
-
|
|
2388
|
-
|
|
2389
|
-
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
|
|
2393
|
-
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
|
|
2398
|
-
|
|
2399
|
-
|
|
2400
|
-
|
|
2401
|
-
|
|
1065
|
+
};
|
|
1066
|
+
|
|
1067
|
+
// src/runners/node-test.ts
|
|
1068
|
+
var nodeTestRunner = {
|
|
1069
|
+
id: "node-test",
|
|
1070
|
+
toCheck(_project, _cap) {
|
|
1071
|
+
return {
|
|
1072
|
+
id: "unit",
|
|
1073
|
+
title: "Unit tests",
|
|
1074
|
+
runner: "node-test",
|
|
1075
|
+
cmd: process.execPath,
|
|
1076
|
+
args: ["--test"]
|
|
1077
|
+
};
|
|
1078
|
+
},
|
|
1079
|
+
run(check, ctx) {
|
|
1080
|
+
return runViaExec(check, ctx, {
|
|
1081
|
+
pass: "unit tests passed",
|
|
1082
|
+
fail: "unit tests failed",
|
|
1083
|
+
timeoutMs: 10 * 6e4
|
|
1084
|
+
});
|
|
2402
1085
|
}
|
|
2403
|
-
}
|
|
1086
|
+
};
|
|
2404
1087
|
|
|
2405
|
-
// src/
|
|
2406
|
-
|
|
2407
|
-
|
|
2408
|
-
|
|
2409
|
-
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
|
|
2413
|
-
}
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
|
|
1088
|
+
// src/runners/playwright.ts
|
|
1089
|
+
function parsePlaywrightStats(stdout) {
|
|
1090
|
+
try {
|
|
1091
|
+
const json = JSON.parse(stdout);
|
|
1092
|
+
return json.stats ?? null;
|
|
1093
|
+
} catch {
|
|
1094
|
+
return null;
|
|
1095
|
+
}
|
|
1096
|
+
}
|
|
1097
|
+
var TAIL_LINES2 = 20;
|
|
1098
|
+
var playwrightRunner = {
|
|
1099
|
+
id: "playwright",
|
|
1100
|
+
toCheck(project, _cap) {
|
|
1101
|
+
return {
|
|
1102
|
+
id: "browser",
|
|
1103
|
+
title: "Browser tests",
|
|
1104
|
+
runner: "playwright",
|
|
1105
|
+
cmd: localBin(project.root, "playwright"),
|
|
1106
|
+
args: ["test", "--reporter=json"]
|
|
1107
|
+
};
|
|
1108
|
+
},
|
|
1109
|
+
async run(check, ctx) {
|
|
1110
|
+
const r = await exec(check.cmd, check.args, {
|
|
1111
|
+
cwd: ctx.root,
|
|
1112
|
+
timeoutMs: 15 * 6e4
|
|
1113
|
+
});
|
|
1114
|
+
const output = [r.stdout, r.stderr].filter(Boolean).join("\n").trim();
|
|
1115
|
+
const logRef = await writeLog(ctx.runDir, check.id, `${output}
|
|
2425
1116
|
`);
|
|
2426
|
-
|
|
1117
|
+
const stats = parsePlaywrightStats(r.stdout);
|
|
1118
|
+
let status;
|
|
1119
|
+
if (r.timedOut) {
|
|
1120
|
+
status = "unknown";
|
|
1121
|
+
} else if (stats) {
|
|
1122
|
+
status = r.code === 0 && (stats.unexpected ?? 0) === 0 ? "passed" : "failed";
|
|
1123
|
+
} else {
|
|
1124
|
+
status = r.code === 0 ? "unknown" : "failed";
|
|
1125
|
+
}
|
|
1126
|
+
const result = {
|
|
1127
|
+
checkId: "browser",
|
|
1128
|
+
status,
|
|
1129
|
+
durationMs: r.durationMs,
|
|
1130
|
+
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",
|
|
1131
|
+
logRef
|
|
1132
|
+
};
|
|
1133
|
+
if (stats) {
|
|
1134
|
+
const passed = stats.expected ?? 0;
|
|
1135
|
+
const failed = stats.unexpected ?? 0;
|
|
1136
|
+
result.counts = {
|
|
1137
|
+
passed,
|
|
1138
|
+
failed,
|
|
1139
|
+
total: passed + failed + (stats.flaky ?? 0) + (stats.skipped ?? 0)
|
|
1140
|
+
};
|
|
2427
1141
|
}
|
|
1142
|
+
if (status !== "passed" && output) {
|
|
1143
|
+
result.outputTail = output.split("\n").slice(-TAIL_LINES2).join("\n");
|
|
1144
|
+
}
|
|
1145
|
+
return result;
|
|
2428
1146
|
}
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
|
|
1147
|
+
};
|
|
1148
|
+
|
|
1149
|
+
// src/runners/tsc.ts
|
|
1150
|
+
var tscRunner = {
|
|
1151
|
+
id: "tsc",
|
|
1152
|
+
toCheck(project, _cap) {
|
|
1153
|
+
return {
|
|
1154
|
+
id: "types",
|
|
1155
|
+
title: "Types",
|
|
1156
|
+
runner: "tsc",
|
|
1157
|
+
cmd: localBin(project.root, "tsc"),
|
|
1158
|
+
args: ["--noEmit", "--pretty", "false"]
|
|
1159
|
+
};
|
|
1160
|
+
},
|
|
1161
|
+
run(check, ctx) {
|
|
1162
|
+
return runViaExec(check, ctx, {
|
|
1163
|
+
pass: "no type errors",
|
|
1164
|
+
fail: "type errors found",
|
|
1165
|
+
timeoutMs: 5 * 6e4
|
|
1166
|
+
});
|
|
1167
|
+
}
|
|
1168
|
+
};
|
|
1169
|
+
|
|
1170
|
+
// src/runners/vitest.ts
|
|
1171
|
+
var vitestRunner = {
|
|
1172
|
+
id: "vitest",
|
|
1173
|
+
toCheck(project, _cap, opts) {
|
|
1174
|
+
const files = opts?.targetFiles ?? [];
|
|
1175
|
+
return {
|
|
1176
|
+
id: "unit",
|
|
1177
|
+
title: "Unit tests",
|
|
1178
|
+
runner: "vitest",
|
|
1179
|
+
cmd: localBin(project.root, "vitest"),
|
|
1180
|
+
args: ["run", "--reporter=json", ...files]
|
|
1181
|
+
};
|
|
1182
|
+
},
|
|
1183
|
+
run(check, ctx) {
|
|
1184
|
+
return runViaExec(check, ctx, {
|
|
1185
|
+
pass: "unit tests passed",
|
|
1186
|
+
fail: "unit tests failed",
|
|
1187
|
+
timeoutMs: 10 * 6e4
|
|
2435
1188
|
});
|
|
2436
|
-
} catch (err) {
|
|
2437
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
2438
|
-
process.stderr.write(`veris: cannot read evidence at ${path}: ${msg}
|
|
2439
|
-
`);
|
|
2440
|
-
return 1;
|
|
2441
|
-
}
|
|
2442
|
-
const plain = isPlain();
|
|
2443
|
-
const mark = (ok) => plain ? ok ? "ok" : "FAIL" : ok ? pc5.green("\u2713") : pc5.red("\u2717");
|
|
2444
|
-
for (const check of result.checks) {
|
|
2445
|
-
process.stdout.write(
|
|
2446
|
-
` ${mark(check.ok)} ${check.name}: ${check.detail}
|
|
2447
|
-
`
|
|
2448
|
-
);
|
|
2449
|
-
}
|
|
2450
|
-
const g = result.record.git;
|
|
2451
|
-
const anchor = g ? `commit ${g.commit.slice(0, 7)} \xB7 ${g.dirty ? "tree dirty" : "tree clean"}` : "no git anchor";
|
|
2452
|
-
process.stdout.write(
|
|
2453
|
-
`
|
|
2454
|
-
${result.ok ? "digest OK" : "TAMPERED"} \xB7 verdict ${result.record.verdict.state} \xB7 ${anchor}
|
|
2455
|
-
`
|
|
2456
|
-
);
|
|
2457
|
-
process.stdout.write(`
|
|
2458
|
-
${HONESTY}
|
|
2459
|
-
`);
|
|
2460
|
-
if (result.signed && !opts.pubkey && !opts.keyId) {
|
|
2461
|
-
process.stdout.write(
|
|
2462
|
-
"\nThis record is signed. Confirm you trust the signing key by comparing its\nkey id above to one you already trust, or re-run with --pubkey / --key-id.\n"
|
|
2463
|
-
);
|
|
2464
1189
|
}
|
|
2465
|
-
|
|
1190
|
+
};
|
|
1191
|
+
|
|
1192
|
+
// src/runners/index.ts
|
|
1193
|
+
var runners = {
|
|
1194
|
+
tsc: tscRunner,
|
|
1195
|
+
vitest: vitestRunner,
|
|
1196
|
+
jest: jestRunner,
|
|
1197
|
+
"node-test": nodeTestRunner,
|
|
1198
|
+
eslint: eslintRunner,
|
|
1199
|
+
biome: biomeRunner,
|
|
1200
|
+
playwright: playwrightRunner
|
|
1201
|
+
};
|
|
1202
|
+
|
|
1203
|
+
// src/util/env.ts
|
|
1204
|
+
import { platform } from "os";
|
|
1205
|
+
function detectCI() {
|
|
1206
|
+
return process.env.CI === "true" || process.env.CI === "1";
|
|
2466
1207
|
}
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
return 1;
|
|
2476
|
-
}
|
|
2477
|
-
const { mkdir: mkdir3 } = await import("fs/promises");
|
|
2478
|
-
await mkdir3(dirname2(out), { recursive: true });
|
|
2479
|
-
const kp = generateKeyPair();
|
|
2480
|
-
writeFileSync(out, kp.privateKeyPem, { mode: 384 });
|
|
2481
|
-
chmodSync(out, 384);
|
|
2482
|
-
writeFileSync(pub, kp.publicKeyPem, "utf8");
|
|
2483
|
-
process.stdout.write(
|
|
2484
|
-
[
|
|
2485
|
-
`Wrote signing key ${out} (keep this secret; do not commit it)`,
|
|
2486
|
-
`Wrote public key ${pub}`,
|
|
2487
|
-
`Key id ${kp.keyId}`,
|
|
2488
|
-
""
|
|
2489
|
-
].join("\n")
|
|
2490
|
-
);
|
|
2491
|
-
return 0;
|
|
1208
|
+
function getEnvironmentInfo(pm) {
|
|
1209
|
+
return {
|
|
1210
|
+
os: platform(),
|
|
1211
|
+
node: process.version,
|
|
1212
|
+
pm,
|
|
1213
|
+
ci: detectCI(),
|
|
1214
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1215
|
+
};
|
|
2492
1216
|
}
|
|
2493
|
-
|
|
2494
|
-
|
|
2495
|
-
|
|
2496
|
-
|
|
2497
|
-
|
|
2498
|
-
|
|
2499
|
-
|
|
2500
|
-
|
|
2501
|
-
|
|
2502
|
-
|
|
2503
|
-
|
|
1217
|
+
|
|
1218
|
+
// src/core/verdict.ts
|
|
1219
|
+
function computeVerdict(results, capabilities) {
|
|
1220
|
+
const reasons = [];
|
|
1221
|
+
const skipped = [];
|
|
1222
|
+
const verifiedCapabilities = [];
|
|
1223
|
+
const anyFailed = results.some((r) => r.status === "failed");
|
|
1224
|
+
for (const cap of capabilities) {
|
|
1225
|
+
const result = results.find((r) => r.checkId === cap.id);
|
|
1226
|
+
if (!cap.available) {
|
|
1227
|
+
skipped.push(cap.id);
|
|
1228
|
+
reasons.push(`${cap.id} skipped \u2014 ${cap.reason ?? "not configured"}`);
|
|
1229
|
+
continue;
|
|
2504
1230
|
}
|
|
2505
|
-
|
|
2506
|
-
|
|
2507
|
-
|
|
2508
|
-
|
|
2509
|
-
|
|
2510
|
-
|
|
2511
|
-
|
|
2512
|
-
|
|
2513
|
-
|
|
2514
|
-
|
|
2515
|
-
process.stderr.write(`veris: cannot read evidence at ${evidencePath}
|
|
2516
|
-
`);
|
|
2517
|
-
return 1;
|
|
1231
|
+
if (result?.status === "passed") {
|
|
1232
|
+
verifiedCapabilities.push(cap.id);
|
|
1233
|
+
continue;
|
|
1234
|
+
}
|
|
1235
|
+
if (result?.status === "failed") {
|
|
1236
|
+
reasons.push(`${cap.id} failed \u2014 ${result.summary}`);
|
|
1237
|
+
continue;
|
|
1238
|
+
}
|
|
1239
|
+
skipped.push(cap.id);
|
|
1240
|
+
reasons.push(`${cap.id} skipped \u2014 ${result?.summary ?? "did not run"}`);
|
|
2518
1241
|
}
|
|
2519
|
-
|
|
2520
|
-
|
|
2521
|
-
);
|
|
2522
|
-
|
|
2523
|
-
|
|
2524
|
-
|
|
2525
|
-
|
|
2526
|
-
|
|
1242
|
+
let state;
|
|
1243
|
+
if (anyFailed) state = "failed";
|
|
1244
|
+
else if (skipped.length > 0) state = "partial";
|
|
1245
|
+
else state = "verified";
|
|
1246
|
+
return { state, verifiedCapabilities, skipped, reasons };
|
|
1247
|
+
}
|
|
1248
|
+
|
|
1249
|
+
// src/core/orchestrator.ts
|
|
1250
|
+
async function runChecks(project, ids, root, opts = {}) {
|
|
1251
|
+
const known = new Set(project.capabilities.map((c) => c.id));
|
|
1252
|
+
const unknown = ids.filter((id2) => !known.has(id2));
|
|
1253
|
+
if (unknown.length > 0) {
|
|
1254
|
+
throw new Error(`Unknown check id(s): ${unknown.join(", ")}`);
|
|
2527
1255
|
}
|
|
2528
|
-
|
|
2529
|
-
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
const
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
|
|
1256
|
+
const id = newRunId();
|
|
1257
|
+
const runDir = await createRunDir(root, id);
|
|
1258
|
+
const ctx = { root, runDir };
|
|
1259
|
+
const tasks = ids.map(async (capId) => {
|
|
1260
|
+
const cap = project.capabilities.find((c) => c.id === capId);
|
|
1261
|
+
const runner = cap?.runner ? runners[cap.runner] : void 0;
|
|
1262
|
+
if (!cap?.available || !runner) {
|
|
1263
|
+
const summary = !cap?.available ? cap?.reason ?? "not configured" : `no runner registered for ${cap.runner}`;
|
|
1264
|
+
return {
|
|
1265
|
+
checkId: capId,
|
|
1266
|
+
status: "skipped",
|
|
1267
|
+
durationMs: 0,
|
|
1268
|
+
summary
|
|
1269
|
+
};
|
|
1270
|
+
}
|
|
1271
|
+
const check = runner.toCheck(project, cap, {
|
|
1272
|
+
targetFiles: opts.targetFiles?.[capId]
|
|
1273
|
+
});
|
|
1274
|
+
return runner.run(check, ctx);
|
|
1275
|
+
});
|
|
1276
|
+
const results = await Promise.all(tasks);
|
|
1277
|
+
const requested = project.capabilities.filter((c) => ids.includes(c.id));
|
|
1278
|
+
const verdict = computeVerdict(results, requested);
|
|
1279
|
+
return {
|
|
1280
|
+
id,
|
|
1281
|
+
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1282
|
+
project,
|
|
1283
|
+
results,
|
|
1284
|
+
verdict,
|
|
1285
|
+
env: getEnvironmentInfo(project.packageManager)
|
|
1286
|
+
};
|
|
1287
|
+
}
|
|
1288
|
+
|
|
1289
|
+
// src/core/orchestrate.ts
|
|
1290
|
+
var DEFAULT_CHECKS = ["types", "lint", "unit"];
|
|
1291
|
+
function resolveChecks(configChecks, project, opts) {
|
|
1292
|
+
let checks = configChecks?.length ? configChecks : DEFAULT_CHECKS;
|
|
1293
|
+
if (opts.browser && !checks.includes("browser")) {
|
|
1294
|
+
const cap = project.capabilities.find((c) => c.id === "browser");
|
|
1295
|
+
if (cap?.available) checks = [...checks, "browser"];
|
|
2536
1296
|
}
|
|
2537
|
-
|
|
2538
|
-
|
|
2539
|
-
|
|
2540
|
-
|
|
2541
|
-
|
|
2542
|
-
|
|
2543
|
-
|
|
1297
|
+
return checks;
|
|
1298
|
+
}
|
|
1299
|
+
async function verifyProject(root, opts = {}) {
|
|
1300
|
+
const project = await detectProject(root);
|
|
1301
|
+
const config = await loadConfig(root);
|
|
1302
|
+
const checks = resolveChecks(config?.checks, project, opts);
|
|
1303
|
+
const run = await runChecks(project, checks, root);
|
|
1304
|
+
const git = await gitAnchor(root);
|
|
1305
|
+
const logDigests = await digestLogs(run);
|
|
1306
|
+
const record = buildRecord(run, git, logDigests, VERSION);
|
|
1307
|
+
const reportRef = await writeReport(
|
|
1308
|
+
root,
|
|
1309
|
+
run.id,
|
|
1310
|
+
renderMarkdown(run, record)
|
|
2544
1311
|
);
|
|
2545
|
-
|
|
1312
|
+
run.reportRef = reportRef;
|
|
1313
|
+
const runDir = await createRunDir(root, run.id);
|
|
1314
|
+
await writeEvidence(runDir, record);
|
|
1315
|
+
return { run, record };
|
|
2546
1316
|
}
|
|
2547
|
-
async function
|
|
2548
|
-
const
|
|
2549
|
-
|
|
2550
|
-
|
|
2551
|
-
|
|
2552
|
-
|
|
2553
|
-
|
|
2554
|
-
|
|
2555
|
-
|
|
2556
|
-
|
|
2557
|
-
);
|
|
2558
|
-
} catch {
|
|
2559
|
-
process.stderr.write(`veris: no evidence.json in ${runDir}
|
|
2560
|
-
`);
|
|
2561
|
-
return 1;
|
|
1317
|
+
async function affectedProject(root, opts = {}) {
|
|
1318
|
+
const project = await detectProject(root);
|
|
1319
|
+
const { files } = await changedFiles(root, { base: opts.base });
|
|
1320
|
+
const plan = affectedChecks(files, project);
|
|
1321
|
+
if (plan.checks.length === 0) {
|
|
1322
|
+
return {
|
|
1323
|
+
note: `Nothing affected \u2014 ${files.length} changed file(s), no checks to run.`,
|
|
1324
|
+
changedCount: files.length,
|
|
1325
|
+
nothingAffected: true
|
|
1326
|
+
};
|
|
2562
1327
|
}
|
|
2563
|
-
let
|
|
2564
|
-
|
|
2565
|
-
|
|
2566
|
-
|
|
2567
|
-
|
|
2568
|
-
);
|
|
2569
|
-
|
|
1328
|
+
let targetFiles;
|
|
1329
|
+
let note = "";
|
|
1330
|
+
const unitRunner = project.capabilities.find((c) => c.id === "unit")?.runner;
|
|
1331
|
+
const canNarrow = unitRunner === "vitest" || unitRunner === "jest";
|
|
1332
|
+
if (plan.checks.includes("unit") && canNarrow) {
|
|
1333
|
+
const { buildGraph: buildGraph2 } = await Promise.resolve().then(() => (init_graph(), graph_exports));
|
|
1334
|
+
const { selectAffectedTests: selectAffectedTests2 } = await Promise.resolve().then(() => (init_select(), select_exports));
|
|
1335
|
+
const graph = await buildGraph2(project);
|
|
1336
|
+
const sel = selectAffectedTests2(graph, files);
|
|
1337
|
+
if (sel.mode === "graph") {
|
|
1338
|
+
targetFiles = { unit: sel.testFiles };
|
|
1339
|
+
note = `unit narrowed to ${sel.testFiles.length} of ${graph.testFiles.length} test file(s) via ${graph.resolver} graph`;
|
|
1340
|
+
} else {
|
|
1341
|
+
note = `unit ran in full \u2014 ${sel.reason}`;
|
|
1342
|
+
}
|
|
1343
|
+
} else if (plan.checks.includes("unit") && unitRunner) {
|
|
1344
|
+
note = `unit ran in full \u2014 the ${unitRunner} runner does not support file filtering`;
|
|
2570
1345
|
}
|
|
2571
|
-
const
|
|
2572
|
-
|
|
2573
|
-
|
|
2574
|
-
const
|
|
2575
|
-
|
|
2576
|
-
|
|
2577
|
-
|
|
2578
|
-
|
|
1346
|
+
const run = await runChecks(project, plan.checks, root, { targetFiles });
|
|
1347
|
+
run.scope = { kind: "affected", changedCount: files.length };
|
|
1348
|
+
const affected = new Set(plan.checks);
|
|
1349
|
+
for (const cap of project.capabilities) {
|
|
1350
|
+
if (cap.available && cap.id !== "browser" && !affected.has(cap.id)) {
|
|
1351
|
+
run.results.push({
|
|
1352
|
+
checkId: cap.id,
|
|
1353
|
+
status: "skipped",
|
|
1354
|
+
durationMs: 0,
|
|
1355
|
+
summary: "not affected by changes"
|
|
1356
|
+
});
|
|
2579
1357
|
}
|
|
2580
1358
|
}
|
|
2581
|
-
const
|
|
2582
|
-
const
|
|
2583
|
-
const
|
|
2584
|
-
|
|
2585
|
-
|
|
2586
|
-
|
|
2587
|
-
|
|
2588
|
-
return 0;
|
|
2589
|
-
}
|
|
2590
|
-
async function runEvidenceShow(root, path) {
|
|
2591
|
-
const target = path ?? (() => {
|
|
2592
|
-
const dir = latestRunDir(root);
|
|
2593
|
-
return dir ? join12(dir, "evidence.json") : null;
|
|
2594
|
-
})();
|
|
2595
|
-
if (!target) {
|
|
2596
|
-
process.stdout.write("No evidence yet. Run `veris verify` first.\n");
|
|
2597
|
-
return 0;
|
|
2598
|
-
}
|
|
2599
|
-
let record;
|
|
2600
|
-
try {
|
|
2601
|
-
record = JSON.parse(readFileSync6(target, "utf8"));
|
|
2602
|
-
} catch {
|
|
2603
|
-
process.stderr.write(`veris: cannot read evidence at ${target}
|
|
2604
|
-
`);
|
|
2605
|
-
return 1;
|
|
2606
|
-
}
|
|
2607
|
-
const g = record.git;
|
|
2608
|
-
process.stdout.write(
|
|
2609
|
-
[
|
|
2610
|
-
`Evidence ${basename3(target)}`,
|
|
2611
|
-
`Verdict ${record.verdict.state}`,
|
|
2612
|
-
`Project ${record.project.name}`,
|
|
2613
|
-
`Scope ${record.scope.kind} (${record.scope.changedCount} changed)`,
|
|
2614
|
-
`Commit ${g ? `${g.commit.slice(0, 7)} (${g.branch}) ${g.dirty ? "\xB7 tree dirty" : "\xB7 tree clean"}` : "no git anchor"}`,
|
|
2615
|
-
`Checks ${record.checks.map((c) => `${c.id}:${c.status}`).join(", ")}`,
|
|
2616
|
-
`Digest ${record.digest}`,
|
|
2617
|
-
""
|
|
2618
|
-
].join("\n")
|
|
1359
|
+
const git = await gitAnchor(root);
|
|
1360
|
+
const logDigests = await digestLogs(run);
|
|
1361
|
+
const record = buildRecord(run, git, logDigests, VERSION);
|
|
1362
|
+
const reportRef = await writeReport(
|
|
1363
|
+
root,
|
|
1364
|
+
run.id,
|
|
1365
|
+
renderMarkdown(run, record)
|
|
2619
1366
|
);
|
|
2620
|
-
|
|
2621
|
-
|
|
2622
|
-
|
|
2623
|
-
var init_evidence = __esm({
|
|
2624
|
-
"src/cli/commands/evidence.ts"() {
|
|
2625
|
-
"use strict";
|
|
2626
|
-
init_bundle();
|
|
2627
|
-
init_record();
|
|
2628
|
-
init_signing();
|
|
2629
|
-
init_store();
|
|
2630
|
-
init_verify_evidence();
|
|
2631
|
-
init_tty();
|
|
2632
|
-
HONESTY = "An integrity digest confirms the record was not edited or corrupted since it was written.\nIt is not forgery-proof on its own: publish the digest separately (CI log, PR) or sign it (veris evidence sign) to prove authorship.";
|
|
2633
|
-
}
|
|
2634
|
-
});
|
|
2635
|
-
|
|
2636
|
-
// src/publish/badge.ts
|
|
2637
|
-
function buildBadge(verdict) {
|
|
2638
|
-
const s = STYLE[verdict.state];
|
|
1367
|
+
run.reportRef = reportRef;
|
|
1368
|
+
const runDir = await createRunDir(root, run.id);
|
|
1369
|
+
await writeEvidence(runDir, record);
|
|
2639
1370
|
return {
|
|
2640
|
-
|
|
2641
|
-
|
|
2642
|
-
|
|
2643
|
-
|
|
1371
|
+
run,
|
|
1372
|
+
record,
|
|
1373
|
+
note,
|
|
1374
|
+
changedCount: files.length,
|
|
1375
|
+
nothingAffected: false
|
|
2644
1376
|
};
|
|
2645
1377
|
}
|
|
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
1378
|
|
|
2658
|
-
// src/
|
|
2659
|
-
|
|
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
|
-
});
|
|
1379
|
+
// src/index.ts
|
|
1380
|
+
init_verify_evidence();
|
|
2701
1381
|
|
|
2702
1382
|
// src/history/flaky.ts
|
|
2703
1383
|
function detectFlaky(records) {
|
|
@@ -2717,20 +1397,15 @@ function detectFlaky(records) {
|
|
|
2717
1397
|
}
|
|
2718
1398
|
return flaky;
|
|
2719
1399
|
}
|
|
2720
|
-
var init_flaky = __esm({
|
|
2721
|
-
"src/history/flaky.ts"() {
|
|
2722
|
-
"use strict";
|
|
2723
|
-
}
|
|
2724
|
-
});
|
|
2725
1400
|
|
|
2726
1401
|
// src/history/read.ts
|
|
2727
|
-
import { readdirSync as
|
|
2728
|
-
import { join as
|
|
1402
|
+
import { readdirSync as readdirSync3, readFileSync as readFileSync4 } from "fs";
|
|
1403
|
+
import { join as join8 } from "path";
|
|
2729
1404
|
function loadRuns(root, limit = 20) {
|
|
2730
|
-
const runs =
|
|
1405
|
+
const runs = join8(root, ".veris", "runs");
|
|
2731
1406
|
let names;
|
|
2732
1407
|
try {
|
|
2733
|
-
names =
|
|
1408
|
+
names = readdirSync3(runs, { withFileTypes: true }).filter((d) => d.isDirectory() && d.name !== "watch").map((d) => d.name);
|
|
2734
1409
|
} catch {
|
|
2735
1410
|
return [];
|
|
2736
1411
|
}
|
|
@@ -2738,7 +1413,7 @@ function loadRuns(root, limit = 20) {
|
|
|
2738
1413
|
for (const name of names) {
|
|
2739
1414
|
try {
|
|
2740
1415
|
const rec = JSON.parse(
|
|
2741
|
-
|
|
1416
|
+
readFileSync4(join8(runs, name, "evidence.json"), "utf8")
|
|
2742
1417
|
);
|
|
2743
1418
|
if (rec?.schema === "veriskit/evidence@1") records.push(rec);
|
|
2744
1419
|
} catch {
|
|
@@ -2749,213 +1424,18 @@ function loadRuns(root, limit = 20) {
|
|
|
2749
1424
|
);
|
|
2750
1425
|
return records.slice(0, limit);
|
|
2751
1426
|
}
|
|
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
1427
|
|
|
2811
|
-
// src/
|
|
2812
|
-
|
|
2813
|
-
|
|
2814
|
-
import { argv } from "process";
|
|
2815
|
-
import { pathToFileURL } from "url";
|
|
2816
|
-
import cac from "cac";
|
|
2817
|
-
function buildCli() {
|
|
2818
|
-
const cli = cac("veris");
|
|
2819
|
-
cli.version(VERSION);
|
|
2820
|
-
cli.help();
|
|
2821
|
-
cli.command("doctor", "Report environment + capabilities (read-only)").action(async () => {
|
|
2822
|
-
const { runDoctor: runDoctor2 } = await Promise.resolve().then(() => (init_doctor(), doctor_exports));
|
|
2823
|
-
process.exitCode = await runDoctor2(process.cwd());
|
|
2824
|
-
});
|
|
2825
|
-
cli.command("test", "Run the detected unit test runner").action(async () => {
|
|
2826
|
-
const { runTest: runTest2 } = await Promise.resolve().then(() => (init_test(), test_exports));
|
|
2827
|
-
process.exitCode = await runTest2(process.cwd());
|
|
2828
|
-
});
|
|
2829
|
-
cli.command("init", "Detect the stack and set up .veris (idempotent)").action(async () => {
|
|
2830
|
-
const { runInit: runInit2 } = await Promise.resolve().then(() => (init_init(), init_exports));
|
|
2831
|
-
process.exitCode = await runInit2(process.cwd());
|
|
2832
|
-
});
|
|
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
|
-
);
|
|
2846
|
-
cli.command("report", "Print the latest verification report").action(async () => {
|
|
2847
|
-
const { runReport: runReport2 } = await Promise.resolve().then(() => (init_report(), report_exports));
|
|
2848
|
-
process.exitCode = await runReport2(process.cwd());
|
|
2849
|
-
});
|
|
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
|
-
);
|
|
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) => {
|
|
2864
|
-
const { runWatch: runWatch2 } = await Promise.resolve().then(() => (init_watch(), watch_exports));
|
|
2865
|
-
process.exitCode = await runWatch2(process.cwd(), { poll: opts.poll });
|
|
2866
|
-
});
|
|
2867
|
-
cli.command("scan", "Map the import graph and untested areas (read-only)").action(async () => {
|
|
2868
|
-
const { runScan: runScan2 } = await Promise.resolve().then(() => (init_scan(), scan_exports));
|
|
2869
|
-
process.exitCode = await runScan2(process.cwd());
|
|
2870
|
-
});
|
|
2871
|
-
cli.command(
|
|
2872
|
-
"plan",
|
|
2873
|
-
"Recommend what to test, from the import graph (read-only)"
|
|
2874
|
-
).option("--base <ref>", "Also factor in changes vs a git ref").action(async (opts) => {
|
|
2875
|
-
const { runPlan: runPlan2 } = await Promise.resolve().then(() => (init_plan(), plan_exports));
|
|
2876
|
-
process.exitCode = await runPlan2(process.cwd(), { base: opts.base });
|
|
2877
|
-
});
|
|
2878
|
-
cli.command(
|
|
2879
|
-
"evidence <action> [file]",
|
|
2880
|
-
"Evidence: verify <file> | bundle | show [file] | keygen | sign <file>"
|
|
2881
|
-
).option("--out <file>", "For bundle/keygen/sign: output path").option("--key <file>", "For sign: the private signing key (PEM)").option("--pubkey <file>", "For verify: assert the signer's public key").option("--key-id <id>", "For verify: assert the signer's key id").option("--sig <file>", "For verify: signature path (default <file>.sig)").action(
|
|
2882
|
-
async (action, file, opts) => {
|
|
2883
|
-
const mod = await Promise.resolve().then(() => (init_evidence(), evidence_exports));
|
|
2884
|
-
if (action === "verify") {
|
|
2885
|
-
if (!file) {
|
|
2886
|
-
process.stderr.write("veris: evidence verify needs a <file>\n");
|
|
2887
|
-
process.exitCode = 1;
|
|
2888
|
-
return;
|
|
2889
|
-
}
|
|
2890
|
-
process.exitCode = await mod.runEvidenceVerify(file, {
|
|
2891
|
-
pubkey: opts.pubkey,
|
|
2892
|
-
keyId: opts.keyId,
|
|
2893
|
-
sig: opts.sig
|
|
2894
|
-
});
|
|
2895
|
-
} else if (action === "bundle") {
|
|
2896
|
-
process.exitCode = await mod.runEvidenceBundle(process.cwd(), {
|
|
2897
|
-
out: opts.out
|
|
2898
|
-
});
|
|
2899
|
-
} else if (action === "show") {
|
|
2900
|
-
process.exitCode = await mod.runEvidenceShow(process.cwd(), file);
|
|
2901
|
-
} else if (action === "keygen") {
|
|
2902
|
-
process.exitCode = await mod.runEvidenceKeygen(process.cwd(), {
|
|
2903
|
-
out: opts.out
|
|
2904
|
-
});
|
|
2905
|
-
} else if (action === "sign") {
|
|
2906
|
-
if (!file) {
|
|
2907
|
-
process.stderr.write("veris: evidence sign needs a <file>\n");
|
|
2908
|
-
process.exitCode = 1;
|
|
2909
|
-
return;
|
|
2910
|
-
}
|
|
2911
|
-
process.exitCode = await mod.runEvidenceSign(file, {
|
|
2912
|
-
key: opts.key,
|
|
2913
|
-
out: opts.out
|
|
2914
|
-
});
|
|
2915
|
-
} else {
|
|
2916
|
-
process.stderr.write(
|
|
2917
|
-
`veris: unknown evidence action '${action}' (use verify | bundle | show | keygen | sign)
|
|
2918
|
-
`
|
|
2919
|
-
);
|
|
2920
|
-
process.exitCode = 1;
|
|
2921
|
-
}
|
|
2922
|
-
}
|
|
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
|
-
});
|
|
2938
|
-
return { raw: cli, version: VERSION };
|
|
2939
|
-
}
|
|
2940
|
-
async function main(argv2) {
|
|
2941
|
-
try {
|
|
2942
|
-
const { raw } = buildCli();
|
|
2943
|
-
if (argv2.length <= 2) {
|
|
2944
|
-
raw.outputHelp();
|
|
2945
|
-
return;
|
|
2946
|
-
}
|
|
2947
|
-
raw.parse(argv2);
|
|
2948
|
-
} catch (err) {
|
|
2949
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
2950
|
-
process.stderr.write(`veris: ${msg}
|
|
2951
|
-
`);
|
|
2952
|
-
process.exitCode = 1;
|
|
2953
|
-
}
|
|
2954
|
-
}
|
|
2955
|
-
var invokedPath = argv[1] ? realpathSync(argv[1]) : "";
|
|
2956
|
-
var isEntry = import.meta.url === pathToFileURL(invokedPath).href;
|
|
2957
|
-
if (isEntry) void main(argv);
|
|
1428
|
+
// src/index.ts
|
|
1429
|
+
init_analyze();
|
|
1430
|
+
init_graph();
|
|
2958
1431
|
export {
|
|
2959
|
-
|
|
2960
|
-
|
|
1432
|
+
affectedProject,
|
|
1433
|
+
analyze,
|
|
1434
|
+
buildGraph,
|
|
1435
|
+
detectFlaky,
|
|
1436
|
+
detectProject,
|
|
1437
|
+
getEnvironmentInfo,
|
|
1438
|
+
loadRuns,
|
|
1439
|
+
verifyEvidenceFile,
|
|
1440
|
+
verifyProject
|
|
2961
1441
|
};
|