veriskit 0.5.0 → 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 +12 -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 -2614
- package/package.json +15 -2
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,1067 +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
|
-
throw new GitHubApiError(
|
|
1154
|
-
res.status,
|
|
1155
|
-
`GitHub API ${method} ${path} -> ${res.status}`
|
|
1156
|
-
);
|
|
1157
|
-
}
|
|
1158
|
-
return res.json();
|
|
1159
|
-
}
|
|
1160
|
-
function repoBase(ctx) {
|
|
1161
|
-
return `https://api.github.com/repos/${ctx.owner}/${ctx.repo}`;
|
|
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
|
+
};
|
|
1162
678
|
}
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
);
|
|
1170
|
-
const existing = comments.find((c) => (c.body ?? "").includes(MARKER));
|
|
1171
|
-
if (existing) {
|
|
1172
|
-
await gh("PATCH", `${base}/issues/comments/${existing.id}`, ctx.token, {
|
|
1173
|
-
body
|
|
1174
|
-
});
|
|
1175
|
-
} else {
|
|
1176
|
-
await gh("POST", `${base}/issues/${ctx.prNumber}/comments`, ctx.token, {
|
|
1177
|
-
body
|
|
1178
|
-
});
|
|
1179
|
-
}
|
|
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" };
|
|
1180
685
|
}
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
}
|
|
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" };
|
|
1189
694
|
}
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
"use strict";
|
|
1194
|
-
init_comment();
|
|
1195
|
-
GitHubApiError = class extends Error {
|
|
1196
|
-
status;
|
|
1197
|
-
constructor(status, message) {
|
|
1198
|
-
super(message);
|
|
1199
|
-
this.name = "GitHubApiError";
|
|
1200
|
-
this.status = status;
|
|
1201
|
-
}
|
|
1202
|
-
};
|
|
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" };
|
|
1203
698
|
}
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
if (!ctx) {
|
|
1210
|
-
process.stdout.write(
|
|
1211
|
-
"veris: --github set but no GitHub PR context found (need GITHUB_TOKEN + a pull_request event); skipping publish.\n"
|
|
1212
|
-
);
|
|
1213
|
-
return;
|
|
1214
|
-
}
|
|
1215
|
-
try {
|
|
1216
|
-
await upsertComment(ctx, renderComment(run, record));
|
|
1217
|
-
await createCheckRun(ctx, {
|
|
1218
|
-
name: "VerisKit",
|
|
1219
|
-
conclusion: CONCLUSION[run.verdict.state],
|
|
1220
|
-
title: `VerisKit: ${run.verdict.state}`,
|
|
1221
|
-
summary: renderMarkdown(run, record)
|
|
1222
|
-
});
|
|
1223
|
-
process.stdout.write(
|
|
1224
|
-
`veris: published the verdict to PR #${ctx.prNumber}
|
|
1225
|
-
`
|
|
1226
|
-
);
|
|
1227
|
-
} catch (err) {
|
|
1228
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
1229
|
-
process.stderr.write(`veris: GitHub publish failed: ${msg}
|
|
1230
|
-
`);
|
|
1231
|
-
}
|
|
1232
|
-
}
|
|
1233
|
-
var CONCLUSION;
|
|
1234
|
-
var init_publish = __esm({
|
|
1235
|
-
"src/publish/publish.ts"() {
|
|
1236
|
-
"use strict";
|
|
1237
|
-
init_markdown();
|
|
1238
|
-
init_comment();
|
|
1239
|
-
init_context();
|
|
1240
|
-
init_github();
|
|
1241
|
-
CONCLUSION = {
|
|
1242
|
-
verified: "success",
|
|
1243
|
-
failed: "failure",
|
|
1244
|
-
partial: "neutral"
|
|
1245
|
-
};
|
|
1246
|
-
}
|
|
1247
|
-
});
|
|
1248
|
-
|
|
1249
|
-
// src/cli/commands/verify.ts
|
|
1250
|
-
var verify_exports = {};
|
|
1251
|
-
__export(verify_exports, {
|
|
1252
|
-
resolveChecks: () => resolveChecks,
|
|
1253
|
-
runVerify: () => runVerify
|
|
1254
|
-
});
|
|
1255
|
-
function resolveChecks(configChecks, project, opts) {
|
|
1256
|
-
let checks = configChecks?.length ? configChecks : DEFAULT_CHECKS;
|
|
1257
|
-
if (opts.browser && !checks.includes("browser")) {
|
|
1258
|
-
const cap = project.capabilities.find((c) => c.id === "browser");
|
|
1259
|
-
if (cap?.available) checks = [...checks, "browser"];
|
|
1260
|
-
}
|
|
1261
|
-
return checks;
|
|
1262
|
-
}
|
|
1263
|
-
async function runVerify(root, opts = {}) {
|
|
1264
|
-
const project = await detectProject(root);
|
|
1265
|
-
const config = await loadConfig(root);
|
|
1266
|
-
const checks = resolveChecks(config?.checks, project, opts);
|
|
1267
|
-
const run = await runChecks(project, checks, root);
|
|
1268
|
-
const git = await gitAnchor(root);
|
|
1269
|
-
const logDigests = await digestLogs(run);
|
|
1270
|
-
const record = buildRecord(run, git, logDigests, VERSION);
|
|
1271
|
-
const reportRef = await writeReport(
|
|
1272
|
-
root,
|
|
1273
|
-
run.id,
|
|
1274
|
-
renderMarkdown(run, record)
|
|
1275
|
-
);
|
|
1276
|
-
run.reportRef = reportRef;
|
|
1277
|
-
const runDir = await createRunDir(root, run.id);
|
|
1278
|
-
await writeEvidence(runDir, record);
|
|
1279
|
-
process.stdout.write(`${renderRun(run, record)}
|
|
1280
|
-
`);
|
|
1281
|
-
if (opts.github) await publishToGitHub(run, record);
|
|
1282
|
-
return verdictExitCode(run.verdict, opts);
|
|
1283
|
-
}
|
|
1284
|
-
var DEFAULT_CHECKS;
|
|
1285
|
-
var init_verify = __esm({
|
|
1286
|
-
"src/cli/commands/verify.ts"() {
|
|
1287
|
-
"use strict";
|
|
1288
|
-
init_detect();
|
|
1289
|
-
init_load();
|
|
1290
|
-
init_orchestrator();
|
|
1291
|
-
init_verdict();
|
|
1292
|
-
init_record();
|
|
1293
|
-
init_store();
|
|
1294
|
-
init_changes();
|
|
1295
|
-
init_publish();
|
|
1296
|
-
init_markdown();
|
|
1297
|
-
init_terminal();
|
|
1298
|
-
init_version();
|
|
1299
|
-
DEFAULT_CHECKS = ["types", "lint", "unit"];
|
|
1300
|
-
}
|
|
1301
|
-
});
|
|
1302
|
-
|
|
1303
|
-
// src/cli/commands/report.ts
|
|
1304
|
-
var report_exports = {};
|
|
1305
|
-
__export(report_exports, {
|
|
1306
|
-
runReport: () => runReport
|
|
1307
|
-
});
|
|
1308
|
-
import { readdirSync as readdirSync2, readFileSync as readFileSync3 } from "fs";
|
|
1309
|
-
import { join as join6 } from "path";
|
|
1310
|
-
async function runReport(root) {
|
|
1311
|
-
const dir = join6(root, ".veris", "reports");
|
|
1312
|
-
let files;
|
|
1313
|
-
try {
|
|
1314
|
-
files = readdirSync2(dir).filter((f) => f.endsWith(".md")).sort();
|
|
1315
|
-
} catch {
|
|
1316
|
-
process.stdout.write("No reports yet. Run `veris verify` first.\n");
|
|
1317
|
-
return 0;
|
|
1318
|
-
}
|
|
1319
|
-
const latest = files.at(-1);
|
|
1320
|
-
if (!latest) {
|
|
1321
|
-
process.stdout.write("No reports yet. Run `veris verify` first.\n");
|
|
1322
|
-
return 0;
|
|
1323
|
-
}
|
|
1324
|
-
process.stdout.write(`${readFileSync3(join6(dir, latest), "utf8")}
|
|
1325
|
-
`);
|
|
1326
|
-
return 0;
|
|
699
|
+
return {
|
|
700
|
+
id: "browser",
|
|
701
|
+
available: false,
|
|
702
|
+
reason: "no browser runner configured"
|
|
703
|
+
};
|
|
1327
704
|
}
|
|
1328
|
-
var init_report = __esm({
|
|
1329
|
-
"src/cli/commands/report.ts"() {
|
|
1330
|
-
"use strict";
|
|
1331
|
-
}
|
|
1332
|
-
});
|
|
1333
705
|
|
|
1334
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;
|
|
1335
713
|
function affectedChecks(files, project) {
|
|
1336
714
|
const available = new Set(
|
|
1337
715
|
project.capabilities.filter((c) => c.available).map((c) => c.id)
|
|
@@ -1371,1327 +749,635 @@ function affectedChecks(files, project) {
|
|
|
1371
749
|
changedCount: files.length
|
|
1372
750
|
};
|
|
1373
751
|
}
|
|
1374
|
-
var ORDER, CONFIG_RE, TEST_RE, TS_RE, JS_RE, DOC_RE;
|
|
1375
|
-
var init_gate = __esm({
|
|
1376
|
-
"src/affected/gate.ts"() {
|
|
1377
|
-
"use strict";
|
|
1378
|
-
ORDER = ["types", "lint", "unit", "browser"];
|
|
1379
|
-
CONFIG_RE = /(^|\/)(tsconfig[^/]*\.json|biome\.jsonc?|\.eslintrc[^/]*|eslint\.config\.[^/]+|package\.json|veris\.config\.[^/]+)$/;
|
|
1380
|
-
TEST_RE = /(\.(test|spec)\.[cm]?[jt]sx?$)|(^|\/)(test|__tests__)\//;
|
|
1381
|
-
TS_RE = /\.[cm]?tsx?$/;
|
|
1382
|
-
JS_RE = /\.[cm]?jsx?$/;
|
|
1383
|
-
DOC_RE = /(\.(md|mdx|markdown|txt|png|jpe?g|gif|svg|webp|ico)$)|((^|\/)LICENSE$)/i;
|
|
1384
|
-
}
|
|
1385
|
-
});
|
|
1386
752
|
|
|
1387
|
-
// src/
|
|
1388
|
-
import {
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
return sep === "/" ? p : p.split(sep).join("/");
|
|
1392
|
-
}
|
|
1393
|
-
function classify(rel) {
|
|
1394
|
-
if (TEST_RE2.test(rel)) return "test";
|
|
1395
|
-
if (CONFIG_RE2.test(rel)) return "config";
|
|
1396
|
-
return "source";
|
|
1397
|
-
}
|
|
1398
|
-
function discoverFiles(root) {
|
|
1399
|
-
const out = [];
|
|
1400
|
-
const walk = (dir) => {
|
|
1401
|
-
let entries;
|
|
1402
|
-
try {
|
|
1403
|
-
entries = readdirSync3(dir);
|
|
1404
|
-
} catch {
|
|
1405
|
-
return;
|
|
1406
|
-
}
|
|
1407
|
-
for (const name of entries) {
|
|
1408
|
-
const abs = join7(dir, name);
|
|
1409
|
-
const rel = toPosix(relative2(root, abs));
|
|
1410
|
-
if (IGNORE.test(rel)) continue;
|
|
1411
|
-
let st;
|
|
1412
|
-
try {
|
|
1413
|
-
st = statSync(abs);
|
|
1414
|
-
} catch {
|
|
1415
|
-
continue;
|
|
1416
|
-
}
|
|
1417
|
-
if (st.isDirectory()) {
|
|
1418
|
-
walk(abs);
|
|
1419
|
-
} else if (CODE_RE.test(rel)) {
|
|
1420
|
-
out.push({ file: rel, kind: classify(rel) });
|
|
1421
|
-
}
|
|
1422
|
-
}
|
|
1423
|
-
};
|
|
1424
|
-
walk(root);
|
|
1425
|
-
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"));
|
|
1426
757
|
}
|
|
1427
|
-
var IGNORE, CODE_RE, TEST_RE2, CONFIG_RE2;
|
|
1428
|
-
var init_discover = __esm({
|
|
1429
|
-
"src/project-graph/discover.ts"() {
|
|
1430
|
-
"use strict";
|
|
1431
|
-
IGNORE = /(^|\/)(\.git|\.claude|\.veris|\.agentloop|\.agentflight|node_modules|dist|coverage|build|fixtures|__fixtures__)(\/|$)/;
|
|
1432
|
-
CODE_RE = /\.[cm]?[jt]sx?$/;
|
|
1433
|
-
TEST_RE2 = /(\.(test|spec)\.[cm]?[jt]sx?$)|(^|\/)(test|tests|__tests__)\//;
|
|
1434
|
-
CONFIG_RE2 = /(^|\/)([^/]+\.config\.[cm]?[jt]sx?)$/;
|
|
1435
|
-
}
|
|
1436
|
-
});
|
|
1437
758
|
|
|
1438
|
-
// src/
|
|
1439
|
-
|
|
1440
|
-
import { dirname, join as join8, relative as relative3, resolve } from "path";
|
|
1441
|
-
function extractSpecifiers(text) {
|
|
1442
|
-
const specs = [];
|
|
1443
|
-
SPEC_RE.lastIndex = 0;
|
|
1444
|
-
let m;
|
|
1445
|
-
m = SPEC_RE.exec(text);
|
|
1446
|
-
while (m !== null) {
|
|
1447
|
-
const s = m[1] ?? m[2] ?? m[3] ?? m[4];
|
|
1448
|
-
if (s) specs.push(s);
|
|
1449
|
-
m = SPEC_RE.exec(text);
|
|
1450
|
-
}
|
|
1451
|
-
return specs;
|
|
1452
|
-
}
|
|
1453
|
-
function isFile(p) {
|
|
1454
|
-
try {
|
|
1455
|
-
return statSync2(p).isFile();
|
|
1456
|
-
} catch {
|
|
1457
|
-
return false;
|
|
1458
|
-
}
|
|
1459
|
-
}
|
|
1460
|
-
function resolveRelative(fromDir, spec) {
|
|
1461
|
-
const base = resolve(fromDir, spec);
|
|
1462
|
-
const candidates = [base];
|
|
1463
|
-
for (const ext of EXTS) candidates.push(base + ext);
|
|
1464
|
-
const extMatch = base.match(/\.[cm]?[jt]sx?$/);
|
|
1465
|
-
if (extMatch) {
|
|
1466
|
-
const noExt = base.slice(0, -extMatch[0].length);
|
|
1467
|
-
for (const ext of EXTS) candidates.push(noExt + ext);
|
|
1468
|
-
}
|
|
1469
|
-
for (const ext of EXTS) candidates.push(join8(base, `index${ext}`));
|
|
1470
|
-
for (const c of candidates) {
|
|
1471
|
-
if (existsSync3(c) && isFile(c)) return c;
|
|
1472
|
-
}
|
|
1473
|
-
return null;
|
|
1474
|
-
}
|
|
1475
|
-
function scannerImports(root, file) {
|
|
1476
|
-
let text;
|
|
1477
|
-
try {
|
|
1478
|
-
text = readFileSync4(join8(root, file), "utf8");
|
|
1479
|
-
} catch {
|
|
1480
|
-
return [];
|
|
1481
|
-
}
|
|
1482
|
-
const dir = dirname(join8(root, file));
|
|
1483
|
-
const out = /* @__PURE__ */ new Set();
|
|
1484
|
-
for (const spec of extractSpecifiers(text)) {
|
|
1485
|
-
if (!spec.startsWith(".")) continue;
|
|
1486
|
-
const resolved = resolveRelative(dir, spec);
|
|
1487
|
-
if (resolved) out.add(toPosix(relative3(root, resolved)));
|
|
1488
|
-
}
|
|
1489
|
-
return [...out];
|
|
1490
|
-
}
|
|
1491
|
-
var EXTS, SPEC_RE;
|
|
1492
|
-
var init_scanner_resolver = __esm({
|
|
1493
|
-
"src/project-graph/scanner-resolver.ts"() {
|
|
1494
|
-
"use strict";
|
|
1495
|
-
init_discover();
|
|
1496
|
-
EXTS = [".ts", ".tsx", ".mts", ".cts", ".js", ".jsx", ".mjs", ".cjs"];
|
|
1497
|
-
SPEC_RE = /(?:import|export)\b[^'"]*?\bfrom\s*['"]([^'"]+)['"]|(?:^|[^.\w])import\s*['"]([^'"]+)['"]|\bimport\(\s*['"]([^'"]+)['"]\s*\)|\brequire\(\s*['"]([^'"]+)['"]\s*\)/g;
|
|
1498
|
-
}
|
|
1499
|
-
});
|
|
759
|
+
// src/core/orchestrate.ts
|
|
760
|
+
init_record();
|
|
1500
761
|
|
|
1501
|
-
// src/
|
|
1502
|
-
import {
|
|
1503
|
-
import {
|
|
1504
|
-
import { join as
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
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}`;
|
|
1508
772
|
}
|
|
1509
|
-
function
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
const mod = require2(tsPath);
|
|
1514
|
-
return hasClassicApi(mod) ? mod : null;
|
|
1515
|
-
} catch {
|
|
1516
|
-
return null;
|
|
1517
|
-
}
|
|
773
|
+
async function createRunDir(root, id) {
|
|
774
|
+
const dir = join3(root, ".veris", "runs", id);
|
|
775
|
+
await ensureDir(dir);
|
|
776
|
+
return dir;
|
|
1518
777
|
}
|
|
1519
|
-
function
|
|
1520
|
-
const
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
const parsed = tsmod.parseJsonConfigFileContent(read.config, tsmod.sys, root);
|
|
1524
|
-
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;
|
|
1525
782
|
}
|
|
1526
|
-
function
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
}
|
|
1533
|
-
const abs = join9(root, file);
|
|
1534
|
-
const out = /* @__PURE__ */ new Set();
|
|
1535
|
-
try {
|
|
1536
|
-
const pre = tsmod.preProcessFile(text, true, true);
|
|
1537
|
-
for (const imp of pre.importedFiles) {
|
|
1538
|
-
const res = tsmod.resolveModuleName(
|
|
1539
|
-
imp.fileName,
|
|
1540
|
-
abs,
|
|
1541
|
-
options,
|
|
1542
|
-
tsmod.sys
|
|
1543
|
-
);
|
|
1544
|
-
const resolved = res.resolvedModule?.resolvedFileName;
|
|
1545
|
-
if (resolved && !resolved.includes("node_modules") && resolved.startsWith(root + sep2)) {
|
|
1546
|
-
out.add(toPosix(relative4(root, resolved)));
|
|
1547
|
-
}
|
|
1548
|
-
}
|
|
1549
|
-
} catch {
|
|
1550
|
-
}
|
|
1551
|
-
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;
|
|
1552
789
|
}
|
|
1553
|
-
function
|
|
1554
|
-
const
|
|
1555
|
-
|
|
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;
|
|
1556
800
|
try {
|
|
1557
|
-
|
|
1558
|
-
return {
|
|
1559
|
-
resolver: "typescript",
|
|
1560
|
-
importsOf: (file) => tsImports(tsmod, root, options, file)
|
|
1561
|
-
};
|
|
801
|
+
out[r.checkId] = sha256(await readFile2(r.logRef, "utf8"));
|
|
1562
802
|
} catch {
|
|
1563
803
|
}
|
|
1564
804
|
}
|
|
1565
|
-
return
|
|
1566
|
-
resolver: "scanner",
|
|
1567
|
-
importsOf: (file) => scannerImports(root, file)
|
|
1568
|
-
};
|
|
805
|
+
return out;
|
|
1569
806
|
}
|
|
1570
|
-
var init_ts_resolver = __esm({
|
|
1571
|
-
"src/project-graph/ts-resolver.ts"() {
|
|
1572
|
-
"use strict";
|
|
1573
|
-
init_discover();
|
|
1574
|
-
init_scanner_resolver();
|
|
1575
|
-
}
|
|
1576
|
-
});
|
|
1577
807
|
|
|
1578
|
-
// src/
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
var init_graph = __esm({
|
|
1606
|
-
"src/project-graph/graph.ts"() {
|
|
1607
|
-
"use strict";
|
|
1608
|
-
init_discover();
|
|
1609
|
-
init_ts_resolver();
|
|
1610
|
-
}
|
|
1611
|
-
});
|
|
1612
|
-
|
|
1613
|
-
// src/project-graph/analyze.ts
|
|
1614
|
-
function transitiveDependents(graph, file) {
|
|
1615
|
-
const seen = /* @__PURE__ */ new Set();
|
|
1616
|
-
const stack = [...graph.nodes[file]?.importedBy ?? []];
|
|
1617
|
-
while (stack.length) {
|
|
1618
|
-
const f = stack.pop();
|
|
1619
|
-
if (!f || seen.has(f)) continue;
|
|
1620
|
-
seen.add(f);
|
|
1621
|
-
for (const d of graph.nodes[f]?.importedBy ?? []) stack.push(d);
|
|
1622
|
-
}
|
|
1623
|
-
return seen;
|
|
1624
|
-
}
|
|
1625
|
-
function reachableFromTests(graph) {
|
|
1626
|
-
const seen = /* @__PURE__ */ new Set();
|
|
1627
|
-
const stack = [...graph.testFiles];
|
|
1628
|
-
while (stack.length) {
|
|
1629
|
-
const f = stack.pop();
|
|
1630
|
-
if (!f || seen.has(f)) continue;
|
|
1631
|
-
seen.add(f);
|
|
1632
|
-
for (const i of graph.nodes[f]?.imports ?? []) stack.push(i);
|
|
1633
|
-
}
|
|
1634
|
-
return seen;
|
|
1635
|
-
}
|
|
1636
|
-
function analyze(graph, changed = []) {
|
|
1637
|
-
const blastRadius = {};
|
|
1638
|
-
for (const file of Object.keys(graph.nodes)) {
|
|
1639
|
-
blastRadius[file] = transitiveDependents(graph, file).size;
|
|
1640
|
-
}
|
|
1641
|
-
const reached = reachableFromTests(graph);
|
|
1642
|
-
const byBlast = (a, b) => (blastRadius[b] ?? 0) - (blastRadius[a] ?? 0);
|
|
1643
|
-
const untested = graph.sourceFiles.filter((f) => !reached.has(f)).sort(byBlast);
|
|
1644
|
-
const changedSet = new Set(changed);
|
|
1645
|
-
const untestedSet = new Set(untested);
|
|
1646
|
-
const risky = graph.sourceFiles.filter(
|
|
1647
|
-
(f) => (blastRadius[f] ?? 0) > 0 && (untestedSet.has(f) || changedSet.has(f))
|
|
1648
|
-
).sort(byBlast);
|
|
1649
|
-
return { untested, blastRadius, risky };
|
|
1650
|
-
}
|
|
1651
|
-
var init_analyze = __esm({
|
|
1652
|
-
"src/project-graph/analyze.ts"() {
|
|
1653
|
-
"use strict";
|
|
1654
|
-
}
|
|
1655
|
-
});
|
|
1656
|
-
|
|
1657
|
-
// src/affected/select.ts
|
|
1658
|
-
var select_exports = {};
|
|
1659
|
-
__export(select_exports, {
|
|
1660
|
-
selectAffectedTests: () => selectAffectedTests
|
|
1661
|
-
});
|
|
1662
|
-
function selectAffectedTests(graph, changed) {
|
|
1663
|
-
if (graph.resolver !== "typescript") {
|
|
1664
|
-
return {
|
|
1665
|
-
mode: "full",
|
|
1666
|
-
testFiles: [],
|
|
1667
|
-
reason: "scanner graph can miss aliased/subpath imports \u2014 running the full suite"
|
|
1668
|
-
};
|
|
1669
|
-
}
|
|
1670
|
-
for (const f of changed) {
|
|
1671
|
-
if (GLOBAL_RE.test(f)) {
|
|
1672
|
-
return {
|
|
1673
|
-
mode: "full",
|
|
1674
|
-
testFiles: [],
|
|
1675
|
-
reason: `global/config change (${f})`
|
|
1676
|
-
};
|
|
1677
|
-
}
|
|
1678
|
-
if (!(f in graph.nodes)) {
|
|
1679
|
-
return {
|
|
1680
|
-
mode: "full",
|
|
1681
|
-
testFiles: [],
|
|
1682
|
-
reason: `unresolved changed file (${f})`
|
|
1683
|
-
};
|
|
1684
|
-
}
|
|
1685
|
-
}
|
|
1686
|
-
const tests = /* @__PURE__ */ new Set();
|
|
1687
|
-
for (const f of changed) {
|
|
1688
|
-
if (graph.nodes[f]?.kind === "test") tests.add(f);
|
|
1689
|
-
for (const dep of transitiveDependents(graph, f)) {
|
|
1690
|
-
if (graph.nodes[dep]?.kind === "test") tests.add(dep);
|
|
1691
|
-
}
|
|
1692
|
-
}
|
|
1693
|
-
if (tests.size === 0) {
|
|
1694
|
-
return {
|
|
1695
|
-
mode: "full",
|
|
1696
|
-
testFiles: [],
|
|
1697
|
-
reason: "no tests reach the changed files"
|
|
1698
|
-
};
|
|
1699
|
-
}
|
|
1700
|
-
const selected = [...tests].sort();
|
|
1701
|
-
if (selected.some((t) => t.startsWith("-"))) {
|
|
1702
|
-
return {
|
|
1703
|
-
mode: "full",
|
|
1704
|
-
testFiles: [],
|
|
1705
|
-
reason: "a reaching test path starts with '-' (unsafe as a CLI argument)"
|
|
1706
|
-
};
|
|
1707
|
-
}
|
|
1708
|
-
return { mode: "graph", testFiles: selected, reason: "" };
|
|
1709
|
-
}
|
|
1710
|
-
var GLOBAL_RE;
|
|
1711
|
-
var init_select = __esm({
|
|
1712
|
-
"src/affected/select.ts"() {
|
|
1713
|
-
"use strict";
|
|
1714
|
-
init_analyze();
|
|
1715
|
-
GLOBAL_RE = /(^|\/)(tsconfig[^/]*\.json|package\.json|biome\.jsonc?|\.eslintrc[^/]*|eslint\.config\.[^/]+|veris\.config\.[^/]+|[^/]+\.config\.[cm]?[jt]sx?|[^/]+\.setup\.[cm]?[jt]sx?)$/;
|
|
1716
|
-
}
|
|
1717
|
-
});
|
|
1718
|
-
|
|
1719
|
-
// src/cli/commands/affected.ts
|
|
1720
|
-
var affected_exports = {};
|
|
1721
|
-
__export(affected_exports, {
|
|
1722
|
-
runAffected: () => runAffected
|
|
1723
|
-
});
|
|
1724
|
-
async function runAffected(root, opts = {}) {
|
|
1725
|
-
const project = await detectProject(root);
|
|
1726
|
-
const { files } = await changedFiles(root, { base: opts.base });
|
|
1727
|
-
const plan = affectedChecks(files, project);
|
|
1728
|
-
if (plan.checks.length === 0) {
|
|
1729
|
-
process.stdout.write(
|
|
1730
|
-
`Nothing affected \u2014 ${files.length} changed file(s), no checks to run.
|
|
1731
|
-
`
|
|
1732
|
-
);
|
|
1733
|
-
return 0;
|
|
1734
|
-
}
|
|
1735
|
-
let targetFiles;
|
|
1736
|
-
let narrowedNote = "";
|
|
1737
|
-
const unitRunner = project.capabilities.find((c) => c.id === "unit")?.runner;
|
|
1738
|
-
const canNarrow = unitRunner === "vitest" || unitRunner === "jest";
|
|
1739
|
-
if (plan.checks.includes("unit") && canNarrow) {
|
|
1740
|
-
const { buildGraph: buildGraph2 } = await Promise.resolve().then(() => (init_graph(), graph_exports));
|
|
1741
|
-
const { selectAffectedTests: selectAffectedTests2 } = await Promise.resolve().then(() => (init_select(), select_exports));
|
|
1742
|
-
const graph = await buildGraph2(project);
|
|
1743
|
-
const sel = selectAffectedTests2(graph, files);
|
|
1744
|
-
if (sel.mode === "graph") {
|
|
1745
|
-
targetFiles = { unit: sel.testFiles };
|
|
1746
|
-
narrowedNote = `unit narrowed to ${sel.testFiles.length} of ${graph.testFiles.length} test file(s) via ${graph.resolver} graph`;
|
|
1747
|
-
} else {
|
|
1748
|
-
narrowedNote = `unit ran in full \u2014 ${sel.reason}`;
|
|
1749
|
-
}
|
|
1750
|
-
} else if (plan.checks.includes("unit") && unitRunner) {
|
|
1751
|
-
narrowedNote = `unit ran in full \u2014 the ${unitRunner} runner does not support file filtering`;
|
|
1752
|
-
}
|
|
1753
|
-
const run = await runChecks(project, plan.checks, root, { targetFiles });
|
|
1754
|
-
run.scope = { kind: "affected", changedCount: files.length };
|
|
1755
|
-
const affected = new Set(plan.checks);
|
|
1756
|
-
for (const cap of project.capabilities) {
|
|
1757
|
-
if (cap.available && cap.id !== "browser" && !affected.has(cap.id)) {
|
|
1758
|
-
run.results.push({
|
|
1759
|
-
checkId: cap.id,
|
|
1760
|
-
status: "skipped",
|
|
1761
|
-
durationMs: 0,
|
|
1762
|
-
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
|
|
1763
835
|
});
|
|
1764
|
-
}
|
|
1765
|
-
|
|
1766
|
-
const git = await gitAnchor(root);
|
|
1767
|
-
const logDigests = await digestLogs(run);
|
|
1768
|
-
const record = buildRecord(run, git, logDigests, VERSION);
|
|
1769
|
-
const reportRef = await writeReport(
|
|
1770
|
-
root,
|
|
1771
|
-
run.id,
|
|
1772
|
-
renderMarkdown(run, record)
|
|
1773
|
-
);
|
|
1774
|
-
run.reportRef = reportRef;
|
|
1775
|
-
const runDir = await createRunDir(root, run.id);
|
|
1776
|
-
await writeEvidence(runDir, record);
|
|
1777
|
-
process.stdout.write(`${renderRun(run, record)}
|
|
1778
|
-
`);
|
|
1779
|
-
if (narrowedNote) process.stdout.write(`${narrowedNote}
|
|
1780
|
-
`);
|
|
1781
|
-
if (opts.github) await publishToGitHub(run, record);
|
|
1782
|
-
return verdictExitCode(run.verdict, opts);
|
|
1783
|
-
}
|
|
1784
|
-
var init_affected = __esm({
|
|
1785
|
-
"src/cli/commands/affected.ts"() {
|
|
1786
|
-
"use strict";
|
|
1787
|
-
init_gate();
|
|
1788
|
-
init_detect();
|
|
1789
|
-
init_orchestrator();
|
|
1790
|
-
init_verdict();
|
|
1791
|
-
init_record();
|
|
1792
|
-
init_store();
|
|
1793
|
-
init_changes();
|
|
1794
|
-
init_publish();
|
|
1795
|
-
init_markdown();
|
|
1796
|
-
init_terminal();
|
|
1797
|
-
init_version();
|
|
1798
|
-
}
|
|
1799
|
-
});
|
|
1800
|
-
|
|
1801
|
-
// src/watch/watcher.ts
|
|
1802
|
-
import {
|
|
1803
|
-
watch as fsWatch,
|
|
1804
|
-
readdirSync as readdirSync4,
|
|
1805
|
-
statSync as statSync3
|
|
1806
|
-
} from "fs";
|
|
1807
|
-
import { basename as basename2, join as join10, relative as relative5 } from "path";
|
|
1808
|
-
function watch(root, opts, onBatch) {
|
|
1809
|
-
const debounceMs = opts.debounceMs ?? 150;
|
|
1810
|
-
const rootBase = basename2(root);
|
|
1811
|
-
let pending = /* @__PURE__ */ new Set();
|
|
1812
|
-
let timer = null;
|
|
1813
|
-
const flush = () => {
|
|
1814
|
-
const batch = [...pending];
|
|
1815
|
-
pending = /* @__PURE__ */ new Set();
|
|
1816
|
-
timer = null;
|
|
1817
|
-
if (batch.length) onBatch(batch);
|
|
1818
|
-
};
|
|
1819
|
-
const schedule = (rel) => {
|
|
1820
|
-
if (!rel || rel === rootBase || IGNORE2.test(rel)) return;
|
|
1821
|
-
pending.add(rel);
|
|
1822
|
-
if (timer) clearTimeout(timer);
|
|
1823
|
-
timer = setTimeout(flush, debounceMs);
|
|
1824
|
-
};
|
|
1825
|
-
if (opts.poll) {
|
|
1826
|
-
const interval = opts.pollIntervalMs ?? 400;
|
|
1827
|
-
const mtimes = scanMtimes(root);
|
|
1828
|
-
const tick = setInterval(() => {
|
|
1829
|
-
const next = scanMtimes(root);
|
|
1830
|
-
for (const [p, m] of next) {
|
|
1831
|
-
if (mtimes.get(p) !== m) schedule(p);
|
|
1832
|
-
}
|
|
1833
|
-
mtimes.clear();
|
|
1834
|
-
for (const [p, m] of next) mtimes.set(p, m);
|
|
1835
|
-
}, interval);
|
|
1836
|
-
return () => {
|
|
1837
|
-
clearInterval(tick);
|
|
836
|
+
});
|
|
837
|
+
child.on("error", () => {
|
|
1838
838
|
if (timer) clearTimeout(timer);
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
839
|
+
resolve2({
|
|
840
|
+
code: 127,
|
|
841
|
+
stdout,
|
|
842
|
+
stderr: stderr || `failed to spawn ${cmd}`,
|
|
843
|
+
durationMs: Math.round(performance.now() - start),
|
|
844
|
+
timedOut
|
|
845
|
+
});
|
|
1845
846
|
});
|
|
1846
|
-
}
|
|
1847
|
-
throw new Error(
|
|
1848
|
-
"recursive fs.watch is unavailable on this platform; rerun with --poll"
|
|
1849
|
-
);
|
|
1850
|
-
}
|
|
1851
|
-
return () => {
|
|
1852
|
-
if (timer) clearTimeout(timer);
|
|
1853
|
-
watcher.close();
|
|
1854
|
-
};
|
|
1855
|
-
}
|
|
1856
|
-
function scanMtimes(root) {
|
|
1857
|
-
const out = /* @__PURE__ */ new Map();
|
|
1858
|
-
const walk = (dir) => {
|
|
1859
|
-
let entries;
|
|
1860
|
-
try {
|
|
1861
|
-
entries = readdirSync4(dir);
|
|
1862
|
-
} catch {
|
|
1863
|
-
return;
|
|
1864
|
-
}
|
|
1865
|
-
for (const name of entries) {
|
|
1866
|
-
const abs = join10(dir, name);
|
|
1867
|
-
const rel = relative5(root, abs);
|
|
1868
|
-
if (IGNORE2.test(rel)) continue;
|
|
1869
|
-
let st;
|
|
1870
|
-
try {
|
|
1871
|
-
st = statSync3(abs);
|
|
1872
|
-
} catch {
|
|
1873
|
-
continue;
|
|
1874
|
-
}
|
|
1875
|
-
if (st.isDirectory()) walk(abs);
|
|
1876
|
-
else out.set(rel, st.mtimeMs);
|
|
1877
|
-
}
|
|
1878
|
-
};
|
|
1879
|
-
walk(root);
|
|
1880
|
-
return out;
|
|
847
|
+
});
|
|
1881
848
|
}
|
|
1882
|
-
var IGNORE2;
|
|
1883
|
-
var init_watcher = __esm({
|
|
1884
|
-
"src/watch/watcher.ts"() {
|
|
1885
|
-
"use strict";
|
|
1886
|
-
IGNORE2 = /(^|\/)(\.git|\.veris|\.agentloop|\.agentflight|node_modules|dist)(\/|$)/;
|
|
1887
|
-
}
|
|
1888
|
-
});
|
|
1889
849
|
|
|
1890
|
-
// src/
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
});
|
|
1896
|
-
function buildWatchResults(project, affected, fresh, cache) {
|
|
1897
|
-
const affectedSet = new Set(affected);
|
|
1898
|
-
const freshById = new Map(fresh.map((r) => [r.checkId, r]));
|
|
1899
|
-
const out = [];
|
|
1900
|
-
for (const cap of project.capabilities) {
|
|
1901
|
-
if (!cap.available || cap.id === "browser") continue;
|
|
1902
|
-
const f = freshById.get(cap.id);
|
|
1903
|
-
if (f) {
|
|
1904
|
-
out.push({ ...f, cached: false });
|
|
1905
|
-
continue;
|
|
1906
|
-
}
|
|
1907
|
-
const c = cache.get(cap.id);
|
|
1908
|
-
if (c && !affectedSet.has(cap.id)) {
|
|
1909
|
-
out.push({ ...c, cached: true });
|
|
1910
|
-
continue;
|
|
1911
|
-
}
|
|
1912
|
-
out.push({
|
|
1913
|
-
checkId: cap.id,
|
|
1914
|
-
status: "skipped",
|
|
1915
|
-
durationMs: 0,
|
|
1916
|
-
summary: "not affected by changes"
|
|
1917
|
-
});
|
|
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);
|
|
1918
855
|
}
|
|
1919
|
-
return out;
|
|
1920
856
|
}
|
|
1921
|
-
async function
|
|
1922
|
-
const
|
|
1923
|
-
|
|
1924
|
-
const
|
|
1925
|
-
const
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
const graph = await buildGraph2(project);
|
|
1942
|
-
const sel = selectAffectedTests2(graph, files);
|
|
1943
|
-
if (sel.mode === "graph") {
|
|
1944
|
-
targetFiles = { unit: sel.testFiles };
|
|
1945
|
-
narrowedNote = `unit narrowed to ${sel.testFiles.length} of ${graph.testFiles.length} test file(s) via ${graph.resolver} graph`;
|
|
1946
|
-
} else {
|
|
1947
|
-
narrowedNote = `unit ran in full \u2014 ${sel.reason}`;
|
|
1948
|
-
}
|
|
1949
|
-
} else if (!initial && checks.includes("unit") && unitRunner) {
|
|
1950
|
-
narrowedNote = `unit ran in full \u2014 the ${unitRunner} runner does not support file filtering`;
|
|
1951
|
-
}
|
|
1952
|
-
let fresh = [];
|
|
1953
|
-
if (checks.length) {
|
|
1954
|
-
const r = await runChecks(project, checks, root, { targetFiles });
|
|
1955
|
-
fresh = r.results;
|
|
1956
|
-
for (const result of fresh) cache.set(result.checkId, { ...result });
|
|
1957
|
-
}
|
|
1958
|
-
const results = buildWatchResults(project, checks, fresh, cache);
|
|
1959
|
-
const availableCaps = project.capabilities.filter(
|
|
1960
|
-
(c) => c.available && c.id !== "browser"
|
|
1961
|
-
);
|
|
1962
|
-
const verdict = computeVerdict(results, availableCaps);
|
|
1963
|
-
const run = {
|
|
1964
|
-
id: "watch",
|
|
1965
|
-
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1966
|
-
project,
|
|
1967
|
-
results,
|
|
1968
|
-
verdict,
|
|
1969
|
-
env: {
|
|
1970
|
-
os: process.platform,
|
|
1971
|
-
node: process.version,
|
|
1972
|
-
pm: project.packageManager,
|
|
1973
|
-
ci: false,
|
|
1974
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1975
|
-
},
|
|
1976
|
-
scope: { kind: "watch", changedCount: files.length }
|
|
1977
|
-
};
|
|
1978
|
-
process.stdout.write(`${renderRun(run)}
|
|
1979
|
-
`);
|
|
1980
|
-
if (narrowedNote) process.stdout.write(`${narrowedNote}
|
|
1981
|
-
`);
|
|
1982
|
-
} finally {
|
|
1983
|
-
running = false;
|
|
1984
|
-
}
|
|
1985
|
-
};
|
|
1986
|
-
process.stdout.write("veris watch \u2014 press Ctrl-C to stop\n");
|
|
1987
|
-
await tick(true);
|
|
1988
|
-
return await new Promise((resolve2) => {
|
|
1989
|
-
let stop;
|
|
1990
|
-
try {
|
|
1991
|
-
stop = watch(root, { poll: opts.poll }, () => {
|
|
1992
|
-
void tick(false);
|
|
1993
|
-
});
|
|
1994
|
-
} catch (err) {
|
|
1995
|
-
process.stderr.write(
|
|
1996
|
-
`veris: ${err instanceof Error ? err.message : String(err)}
|
|
1997
|
-
`
|
|
1998
|
-
);
|
|
1999
|
-
resolve2(1);
|
|
2000
|
-
return;
|
|
2001
|
-
}
|
|
2002
|
-
process.on("SIGINT", () => {
|
|
2003
|
-
stop();
|
|
2004
|
-
process.stdout.write("\nStopped.\n");
|
|
2005
|
-
resolve2(0);
|
|
2006
|
-
});
|
|
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
|
|
2007
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
|
+
};
|
|
2008
887
|
}
|
|
2009
|
-
var init_watch = __esm({
|
|
2010
|
-
"src/cli/commands/watch.ts"() {
|
|
2011
|
-
"use strict";
|
|
2012
|
-
init_gate();
|
|
2013
|
-
init_detect();
|
|
2014
|
-
init_orchestrator();
|
|
2015
|
-
init_verdict();
|
|
2016
|
-
init_changes();
|
|
2017
|
-
init_terminal();
|
|
2018
|
-
init_watcher();
|
|
2019
|
-
}
|
|
2020
|
-
});
|
|
2021
888
|
|
|
2022
|
-
// src/
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
const
|
|
2034
|
-
const dim = (s) => plain ? s : pc3.dim(s);
|
|
2035
|
-
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;
|
|
2036
901
|
const lines = [];
|
|
2037
|
-
lines.push(
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
`Resolver ${graph.resolver}${graph.resolver === "scanner" ? dim(" (no TypeScript found \u2014 relative imports only)") : ""}`
|
|
2041
|
-
);
|
|
2042
|
-
lines.push(
|
|
2043
|
-
`Modules ${Object.keys(graph.nodes).length} \xB7 Source ${graph.sourceFiles.length} \xB7 Tests ${graph.testFiles.length}`
|
|
2044
|
-
);
|
|
2045
|
-
lines.push("");
|
|
2046
|
-
lines.push("Untested (top by impact)");
|
|
2047
|
-
const top = analysis.untested.slice(0, 10);
|
|
2048
|
-
if (top.length === 0)
|
|
2049
|
-
lines.push(dim(" none \u2014 every source file is reached by a test"));
|
|
2050
|
-
for (const f of top) {
|
|
902
|
+
lines.push("# VerisKit Verification Report");
|
|
903
|
+
if (run.scope?.kind) {
|
|
904
|
+
lines.push("");
|
|
2051
905
|
lines.push(
|
|
2052
|
-
|
|
906
|
+
`> **Scope:** ${run.scope.kind} \u2014 ${run.scope.changedCount} changed file(s). Only affected checks ran; this is not a full verification.`
|
|
2053
907
|
);
|
|
2054
908
|
}
|
|
2055
|
-
|
|
2056
|
-
}
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
const analysis = analyze(graph);
|
|
2061
|
-
const dir = join11(root, ".veris");
|
|
2062
|
-
await ensureDir(dir);
|
|
2063
|
-
await writeFile3(
|
|
2064
|
-
join11(dir, "graph.json"),
|
|
2065
|
-
JSON.stringify({ resolver: graph.resolver, nodes: graph.nodes }, null, 2),
|
|
2066
|
-
"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}`
|
|
2067
914
|
);
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
}
|
|
2075
|
-
var init_scan = __esm({
|
|
2076
|
-
"src/cli/commands/scan.ts"() {
|
|
2077
|
-
"use strict";
|
|
2078
|
-
init_detect();
|
|
2079
|
-
init_analyze();
|
|
2080
|
-
init_graph();
|
|
2081
|
-
init_fs_safe();
|
|
2082
|
-
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");
|
|
2083
921
|
}
|
|
2084
|
-
});
|
|
2085
|
-
|
|
2086
|
-
// src/cli/commands/plan.ts
|
|
2087
|
-
var plan_exports = {};
|
|
2088
|
-
__export(plan_exports, {
|
|
2089
|
-
renderPlan: () => renderPlan,
|
|
2090
|
-
runPlan: () => runPlan
|
|
2091
|
-
});
|
|
2092
|
-
import pc4 from "picocolors";
|
|
2093
|
-
function renderPlan(project, graph, analysis, changed) {
|
|
2094
|
-
const plain = isPlain();
|
|
2095
|
-
const bold = (s) => plain ? s : pc4.bold(s);
|
|
2096
|
-
const dim = (s) => plain ? s : pc4.dim(s);
|
|
2097
|
-
const warn = (s) => plain ? s : pc4.yellow(s);
|
|
2098
|
-
const lines = [];
|
|
2099
|
-
lines.push(bold("VerisKit \u2014 plan"));
|
|
2100
|
-
lines.push(dim(`(graph via ${graph.resolver})`));
|
|
2101
922
|
lines.push("");
|
|
2102
|
-
lines.push("
|
|
2103
|
-
const top = analysis.untested.slice(0, 5);
|
|
2104
|
-
if (top.length === 0)
|
|
2105
|
-
lines.push(
|
|
2106
|
-
dim(
|
|
2107
|
-
" none \u2014 untested files have no dependents or all files are covered"
|
|
2108
|
-
)
|
|
2109
|
-
);
|
|
2110
|
-
top.forEach((f, i) => {
|
|
2111
|
-
lines.push(
|
|
2112
|
-
` ${i + 1}. ${f} ${dim(`(${analysis.blastRadius[f] ?? 0} dependents, no test reaches it)`)}`
|
|
2113
|
-
);
|
|
2114
|
-
});
|
|
923
|
+
lines.push("## Checks");
|
|
2115
924
|
lines.push("");
|
|
2116
|
-
lines.push("
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
lines.push(dim(" \u2713 types, lint, and unit are all configured"));
|
|
2122
|
-
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";
|
|
2123
930
|
lines.push(
|
|
2124
|
-
|
|
931
|
+
`| ${r.checkId} | ${r.status} | ${dur} | ${cell(r.summary)} | ${log} |`
|
|
2125
932
|
);
|
|
2126
|
-
if (changed.length) {
|
|
2127
|
-
lines.push("");
|
|
2128
|
-
lines.push(`Risky changes (${changed.length} changed file(s))`);
|
|
2129
|
-
if (analysis.risky.length === 0) lines.push(dim(" none flagged"));
|
|
2130
|
-
for (const f of analysis.risky.slice(0, 8)) {
|
|
2131
|
-
const untested = analysis.untested.includes(f) ? ", untested" : "";
|
|
2132
|
-
lines.push(
|
|
2133
|
-
` ${warn("!")} ${f} ${dim(`${analysis.blastRadius[f] ?? 0} dependents${untested}`)}`
|
|
2134
|
-
);
|
|
2135
|
-
}
|
|
2136
933
|
}
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
}
|
|
2143
|
-
async function runPlan(root, opts = {}) {
|
|
2144
|
-
const project = await detectProject(root);
|
|
2145
|
-
const graph = await buildGraph(project);
|
|
2146
|
-
const { files } = await changedFiles(root, { base: opts.base });
|
|
2147
|
-
const analysis = analyze(graph, files);
|
|
2148
|
-
process.stdout.write(`${renderPlan(project, graph, analysis, files)}
|
|
2149
|
-
`);
|
|
2150
|
-
return 0;
|
|
2151
|
-
}
|
|
2152
|
-
var init_plan = __esm({
|
|
2153
|
-
"src/cli/commands/plan.ts"() {
|
|
2154
|
-
"use strict";
|
|
2155
|
-
init_detect();
|
|
2156
|
-
init_changes();
|
|
2157
|
-
init_analyze();
|
|
2158
|
-
init_graph();
|
|
2159
|
-
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}`);
|
|
2160
939
|
}
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
function signatureKeyId(sig) {
|
|
2180
|
-
const key = createPublicKey({
|
|
2181
|
-
key: Buffer.from(sig.publicKey, "base64"),
|
|
2182
|
-
format: "der",
|
|
2183
|
-
type: "spki"
|
|
2184
|
-
});
|
|
2185
|
-
return keyId(key);
|
|
2186
|
-
}
|
|
2187
|
-
function generateKeyPair() {
|
|
2188
|
-
const { publicKey, privateKey } = generateKeyPairSync("ed25519");
|
|
2189
|
-
return {
|
|
2190
|
-
publicKeyPem: publicKey.export({ type: "spki", format: "pem" }),
|
|
2191
|
-
privateKeyPem: privateKey.export({
|
|
2192
|
-
type: "pkcs8",
|
|
2193
|
-
format: "pem"
|
|
2194
|
-
}),
|
|
2195
|
-
keyId: keyId(publicKey)
|
|
2196
|
-
};
|
|
2197
|
-
}
|
|
2198
|
-
function signDigest(digest, privateKeyPem) {
|
|
2199
|
-
const privateKey = createPrivateKey(privateKeyPem);
|
|
2200
|
-
const publicKey = createPublicKey(
|
|
2201
|
-
privateKey
|
|
2202
|
-
);
|
|
2203
|
-
const sig = cryptoSign(null, Buffer.from(digest, "utf8"), privateKey);
|
|
2204
|
-
return {
|
|
2205
|
-
schema: SIGNATURE_SCHEMA,
|
|
2206
|
-
alg: "ed25519",
|
|
2207
|
-
keyId: keyId(publicKey),
|
|
2208
|
-
publicKey: derOf(publicKey).toString("base64"),
|
|
2209
|
-
digest,
|
|
2210
|
-
signature: sig.toString("base64"),
|
|
2211
|
-
signedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2212
|
-
};
|
|
2213
|
-
}
|
|
2214
|
-
function verifySignature(sig) {
|
|
2215
|
-
try {
|
|
2216
|
-
const publicKey = createPublicKey({
|
|
2217
|
-
key: Buffer.from(sig.publicKey, "base64"),
|
|
2218
|
-
format: "der",
|
|
2219
|
-
type: "spki"
|
|
2220
|
-
});
|
|
2221
|
-
return cryptoVerify(
|
|
2222
|
-
null,
|
|
2223
|
-
Buffer.from(sig.digest, "utf8"),
|
|
2224
|
-
publicKey,
|
|
2225
|
-
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._"
|
|
2226
958
|
);
|
|
2227
|
-
} catch {
|
|
2228
|
-
return false;
|
|
2229
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");
|
|
2230
966
|
}
|
|
2231
|
-
var SIGNATURE_SCHEMA;
|
|
2232
|
-
var init_signing = __esm({
|
|
2233
|
-
"src/evidence/signing.ts"() {
|
|
2234
|
-
"use strict";
|
|
2235
|
-
SIGNATURE_SCHEMA = "veriskit/signature@1";
|
|
2236
|
-
}
|
|
2237
|
-
});
|
|
2238
967
|
|
|
2239
|
-
// src/
|
|
2240
|
-
import {
|
|
2241
|
-
import {
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
detail: ok ? "matches the canonical record" : `recomputed ${recomputed}, record claims ${record.digest}`
|
|
2252
|
-
}
|
|
2253
|
-
];
|
|
2254
|
-
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);
|
|
2255
980
|
}
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
const
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
name: "signature",
|
|
2262
|
-
ok: validSig,
|
|
2263
|
-
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
|
|
2264
986
|
});
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
ok: matches,
|
|
2279
|
-
detail: matches ? `signer matches the expected key ${kid}` : `signer ${kid} does not match the expected key`
|
|
2280
|
-
});
|
|
2281
|
-
}
|
|
2282
|
-
return checks;
|
|
2283
|
-
}
|
|
2284
|
-
async function verifyEvidenceFile(path, opts = {}) {
|
|
2285
|
-
const parsed = JSON.parse(await readFile3(path, "utf8"));
|
|
2286
|
-
if (parsed?.schema === "veriskit/bundle@1") {
|
|
2287
|
-
const { verifyBundle: verifyBundle2 } = await Promise.resolve().then(() => (init_bundle(), bundle_exports));
|
|
2288
|
-
return verifyBundle2(parsed, opts);
|
|
2289
|
-
}
|
|
2290
|
-
const result = verifyRecord(parsed);
|
|
2291
|
-
const assertedSigner = Boolean(
|
|
2292
|
-
opts.expectedKeyId || opts.expectedPubKeyPem || opts.sigPath
|
|
2293
|
-
);
|
|
2294
|
-
const sigPath = opts.sigPath ?? `${path}.sig`;
|
|
2295
|
-
if (existsSync5(sigPath)) {
|
|
2296
|
-
const sig = JSON.parse(await readFile3(sigPath, "utf8"));
|
|
2297
|
-
result.checks.push(
|
|
2298
|
-
...signatureChecks(result.record.digest, sig, {
|
|
2299
|
-
expectedKeyId: opts.expectedKeyId,
|
|
2300
|
-
expectedPubKeyPem: opts.expectedPubKeyPem
|
|
2301
|
-
})
|
|
2302
|
-
);
|
|
2303
|
-
result.signed = true;
|
|
2304
|
-
} else if (assertedSigner) {
|
|
2305
|
-
result.checks.push({
|
|
2306
|
-
name: "signature",
|
|
2307
|
-
ok: false,
|
|
2308
|
-
detail: "a signature was required (--pubkey/--key-id/--sig) but none was found"
|
|
2309
|
-
});
|
|
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");
|
|
2310
1000
|
}
|
|
2311
|
-
result.ok = result.checks.every((c) => c.ok);
|
|
2312
1001
|
return result;
|
|
2313
1002
|
}
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
|
|
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
|
+
});
|
|
2319
1022
|
}
|
|
2320
|
-
}
|
|
1023
|
+
};
|
|
2321
1024
|
|
|
2322
|
-
// src/
|
|
2323
|
-
var
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
}
|
|
2340
|
-
function verifyBundle(bundle, opts = {}) {
|
|
2341
|
-
const checks = [];
|
|
2342
|
-
const recordResult = verifyRecord(bundle.record);
|
|
2343
|
-
checks.push(...recordResult.checks);
|
|
2344
|
-
const reportDigest = sha256(bundle.report);
|
|
2345
|
-
checks.push({
|
|
2346
|
-
name: "report",
|
|
2347
|
-
ok: reportDigest === bundle.manifest.report,
|
|
2348
|
-
detail: reportDigest === bundle.manifest.report ? "matches manifest" : "mismatch"
|
|
2349
|
-
});
|
|
2350
|
-
for (const [id, body] of Object.entries(bundle.logs)) {
|
|
2351
|
-
const d = sha256(body);
|
|
2352
|
-
checks.push({
|
|
2353
|
-
name: `log:${id}`,
|
|
2354
|
-
ok: d === bundle.manifest.logs[id],
|
|
2355
|
-
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
|
|
2356
1042
|
});
|
|
2357
1043
|
}
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
2374
|
-
|
|
2375
|
-
|
|
2376
|
-
|
|
2377
|
-
ok: false,
|
|
2378
|
-
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
|
|
2379
1063
|
});
|
|
2380
1064
|
}
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
|
|
2388
|
-
|
|
2389
|
-
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
|
|
2393
|
-
|
|
2394
|
-
|
|
2395
|
-
|
|
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
|
+
});
|
|
2396
1085
|
}
|
|
2397
|
-
}
|
|
1086
|
+
};
|
|
2398
1087
|
|
|
2399
|
-
// src/
|
|
2400
|
-
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
|
|
2407
|
-
}
|
|
2408
|
-
|
|
2409
|
-
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
|
|
2413
|
-
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
|
|
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}
|
|
2419
1116
|
`);
|
|
2420
|
-
|
|
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
|
+
};
|
|
2421
1141
|
}
|
|
1142
|
+
if (status !== "passed" && output) {
|
|
1143
|
+
result.outputTail = output.split("\n").slice(-TAIL_LINES2).join("\n");
|
|
1144
|
+
}
|
|
1145
|
+
return result;
|
|
2422
1146
|
}
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
|
|
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
|
|
2429
1188
|
});
|
|
2430
|
-
} catch (err) {
|
|
2431
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
2432
|
-
process.stderr.write(`veris: cannot read evidence at ${path}: ${msg}
|
|
2433
|
-
`);
|
|
2434
|
-
return 1;
|
|
2435
|
-
}
|
|
2436
|
-
const plain = isPlain();
|
|
2437
|
-
const mark = (ok) => plain ? ok ? "ok" : "FAIL" : ok ? pc5.green("\u2713") : pc5.red("\u2717");
|
|
2438
|
-
for (const check of result.checks) {
|
|
2439
|
-
process.stdout.write(
|
|
2440
|
-
` ${mark(check.ok)} ${check.name}: ${check.detail}
|
|
2441
|
-
`
|
|
2442
|
-
);
|
|
2443
|
-
}
|
|
2444
|
-
const g = result.record.git;
|
|
2445
|
-
const anchor = g ? `commit ${g.commit.slice(0, 7)} \xB7 ${g.dirty ? "tree dirty" : "tree clean"}` : "no git anchor";
|
|
2446
|
-
process.stdout.write(
|
|
2447
|
-
`
|
|
2448
|
-
${result.ok ? "digest OK" : "TAMPERED"} \xB7 verdict ${result.record.verdict.state} \xB7 ${anchor}
|
|
2449
|
-
`
|
|
2450
|
-
);
|
|
2451
|
-
process.stdout.write(`
|
|
2452
|
-
${HONESTY}
|
|
2453
|
-
`);
|
|
2454
|
-
if (result.signed && !opts.pubkey && !opts.keyId) {
|
|
2455
|
-
process.stdout.write(
|
|
2456
|
-
"\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"
|
|
2457
|
-
);
|
|
2458
1189
|
}
|
|
2459
|
-
|
|
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";
|
|
2460
1207
|
}
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
return 1;
|
|
2470
|
-
}
|
|
2471
|
-
const { mkdir: mkdir3 } = await import("fs/promises");
|
|
2472
|
-
await mkdir3(dirname2(out), { recursive: true });
|
|
2473
|
-
const kp = generateKeyPair();
|
|
2474
|
-
writeFileSync(out, kp.privateKeyPem, { mode: 384 });
|
|
2475
|
-
chmodSync(out, 384);
|
|
2476
|
-
writeFileSync(pub, kp.publicKeyPem, "utf8");
|
|
2477
|
-
process.stdout.write(
|
|
2478
|
-
[
|
|
2479
|
-
`Wrote signing key ${out} (keep this secret; do not commit it)`,
|
|
2480
|
-
`Wrote public key ${pub}`,
|
|
2481
|
-
`Key id ${kp.keyId}`,
|
|
2482
|
-
""
|
|
2483
|
-
].join("\n")
|
|
2484
|
-
);
|
|
2485
|
-
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
|
+
};
|
|
2486
1216
|
}
|
|
2487
|
-
|
|
2488
|
-
|
|
2489
|
-
|
|
2490
|
-
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
|
-
|
|
2494
|
-
|
|
2495
|
-
|
|
2496
|
-
|
|
2497
|
-
|
|
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;
|
|
2498
1230
|
}
|
|
2499
|
-
|
|
2500
|
-
|
|
2501
|
-
|
|
2502
|
-
|
|
2503
|
-
|
|
2504
|
-
|
|
2505
|
-
|
|
2506
|
-
|
|
2507
|
-
|
|
2508
|
-
|
|
2509
|
-
process.stderr.write(`veris: cannot read evidence at ${evidencePath}
|
|
2510
|
-
`);
|
|
2511
|
-
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"}`);
|
|
2512
1241
|
}
|
|
2513
|
-
|
|
2514
|
-
|
|
2515
|
-
);
|
|
2516
|
-
|
|
2517
|
-
|
|
2518
|
-
|
|
2519
|
-
|
|
2520
|
-
|
|
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(", ")}`);
|
|
2521
1255
|
}
|
|
2522
|
-
|
|
2523
|
-
|
|
2524
|
-
|
|
2525
|
-
|
|
2526
|
-
const
|
|
2527
|
-
|
|
2528
|
-
|
|
2529
|
-
|
|
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"];
|
|
2530
1296
|
}
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
|
|
2536
|
-
|
|
2537
|
-
|
|
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)
|
|
2538
1311
|
);
|
|
2539
|
-
|
|
1312
|
+
run.reportRef = reportRef;
|
|
1313
|
+
const runDir = await createRunDir(root, run.id);
|
|
1314
|
+
await writeEvidence(runDir, record);
|
|
1315
|
+
return { run, record };
|
|
2540
1316
|
}
|
|
2541
|
-
async function
|
|
2542
|
-
const
|
|
2543
|
-
|
|
2544
|
-
|
|
2545
|
-
|
|
2546
|
-
|
|
2547
|
-
|
|
2548
|
-
|
|
2549
|
-
|
|
2550
|
-
|
|
2551
|
-
);
|
|
2552
|
-
} catch {
|
|
2553
|
-
process.stderr.write(`veris: no evidence.json in ${runDir}
|
|
2554
|
-
`);
|
|
2555
|
-
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
|
+
};
|
|
2556
1327
|
}
|
|
2557
|
-
let
|
|
2558
|
-
|
|
2559
|
-
|
|
2560
|
-
|
|
2561
|
-
|
|
2562
|
-
);
|
|
2563
|
-
|
|
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`;
|
|
2564
1345
|
}
|
|
2565
|
-
const
|
|
2566
|
-
|
|
2567
|
-
|
|
2568
|
-
const
|
|
2569
|
-
|
|
2570
|
-
|
|
2571
|
-
|
|
2572
|
-
|
|
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
|
+
});
|
|
2573
1357
|
}
|
|
2574
1358
|
}
|
|
2575
|
-
const
|
|
2576
|
-
const
|
|
2577
|
-
const
|
|
2578
|
-
|
|
2579
|
-
|
|
2580
|
-
|
|
2581
|
-
|
|
2582
|
-
return 0;
|
|
2583
|
-
}
|
|
2584
|
-
async function runEvidenceShow(root, path) {
|
|
2585
|
-
const target = path ?? (() => {
|
|
2586
|
-
const dir = latestRunDir(root);
|
|
2587
|
-
return dir ? join12(dir, "evidence.json") : null;
|
|
2588
|
-
})();
|
|
2589
|
-
if (!target) {
|
|
2590
|
-
process.stdout.write("No evidence yet. Run `veris verify` first.\n");
|
|
2591
|
-
return 0;
|
|
2592
|
-
}
|
|
2593
|
-
let record;
|
|
2594
|
-
try {
|
|
2595
|
-
record = JSON.parse(readFileSync6(target, "utf8"));
|
|
2596
|
-
} catch {
|
|
2597
|
-
process.stderr.write(`veris: cannot read evidence at ${target}
|
|
2598
|
-
`);
|
|
2599
|
-
return 1;
|
|
2600
|
-
}
|
|
2601
|
-
const g = record.git;
|
|
2602
|
-
process.stdout.write(
|
|
2603
|
-
[
|
|
2604
|
-
`Evidence ${basename3(target)}`,
|
|
2605
|
-
`Verdict ${record.verdict.state}`,
|
|
2606
|
-
`Project ${record.project.name}`,
|
|
2607
|
-
`Scope ${record.scope.kind} (${record.scope.changedCount} changed)`,
|
|
2608
|
-
`Commit ${g ? `${g.commit.slice(0, 7)} (${g.branch}) ${g.dirty ? "\xB7 tree dirty" : "\xB7 tree clean"}` : "no git anchor"}`,
|
|
2609
|
-
`Checks ${record.checks.map((c) => `${c.id}:${c.status}`).join(", ")}`,
|
|
2610
|
-
`Digest ${record.digest}`,
|
|
2611
|
-
""
|
|
2612
|
-
].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)
|
|
2613
1366
|
);
|
|
2614
|
-
|
|
2615
|
-
|
|
2616
|
-
|
|
2617
|
-
var init_evidence = __esm({
|
|
2618
|
-
"src/cli/commands/evidence.ts"() {
|
|
2619
|
-
"use strict";
|
|
2620
|
-
init_bundle();
|
|
2621
|
-
init_record();
|
|
2622
|
-
init_signing();
|
|
2623
|
-
init_store();
|
|
2624
|
-
init_verify_evidence();
|
|
2625
|
-
init_tty();
|
|
2626
|
-
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.";
|
|
2627
|
-
}
|
|
2628
|
-
});
|
|
2629
|
-
|
|
2630
|
-
// src/publish/badge.ts
|
|
2631
|
-
function buildBadge(verdict) {
|
|
2632
|
-
const s = STYLE[verdict.state];
|
|
1367
|
+
run.reportRef = reportRef;
|
|
1368
|
+
const runDir = await createRunDir(root, run.id);
|
|
1369
|
+
await writeEvidence(runDir, record);
|
|
2633
1370
|
return {
|
|
2634
|
-
|
|
2635
|
-
|
|
2636
|
-
|
|
2637
|
-
|
|
1371
|
+
run,
|
|
1372
|
+
record,
|
|
1373
|
+
note,
|
|
1374
|
+
changedCount: files.length,
|
|
1375
|
+
nothingAffected: false
|
|
2638
1376
|
};
|
|
2639
1377
|
}
|
|
2640
|
-
var STYLE;
|
|
2641
|
-
var init_badge = __esm({
|
|
2642
|
-
"src/publish/badge.ts"() {
|
|
2643
|
-
"use strict";
|
|
2644
|
-
STYLE = {
|
|
2645
|
-
verified: { message: "verified", color: "14b8a6" },
|
|
2646
|
-
failed: { message: "failed", color: "e5484d" },
|
|
2647
|
-
partial: { message: "partial", color: "f5a623" }
|
|
2648
|
-
};
|
|
2649
|
-
}
|
|
2650
|
-
});
|
|
2651
1378
|
|
|
2652
|
-
// src/
|
|
2653
|
-
|
|
2654
|
-
__export(badge_exports, {
|
|
2655
|
-
runBadge: () => runBadge
|
|
2656
|
-
});
|
|
2657
|
-
import { readFileSync as readFileSync7 } from "fs";
|
|
2658
|
-
import { mkdir as mkdir2, writeFile as writeFile5 } from "fs/promises";
|
|
2659
|
-
import { dirname as dirname3, join as join13 } from "path";
|
|
2660
|
-
async function runBadge(root, opts = {}) {
|
|
2661
|
-
const dir = latestRunDir(root);
|
|
2662
|
-
if (!dir) {
|
|
2663
|
-
process.stdout.write("No runs yet. Run `veris verify` first.\n");
|
|
2664
|
-
return 1;
|
|
2665
|
-
}
|
|
2666
|
-
let record;
|
|
2667
|
-
try {
|
|
2668
|
-
record = JSON.parse(
|
|
2669
|
-
readFileSync7(join13(dir, "evidence.json"), "utf8")
|
|
2670
|
-
);
|
|
2671
|
-
} catch {
|
|
2672
|
-
process.stderr.write(`veris: no evidence.json in ${dir}
|
|
2673
|
-
`);
|
|
2674
|
-
return 1;
|
|
2675
|
-
}
|
|
2676
|
-
const out = opts.out ?? join13(root, ".veris", "badge.json");
|
|
2677
|
-
await mkdir2(dirname3(out), { recursive: true });
|
|
2678
|
-
await writeFile5(
|
|
2679
|
-
out,
|
|
2680
|
-
`${JSON.stringify(buildBadge(record.verdict), null, 2)}
|
|
2681
|
-
`,
|
|
2682
|
-
"utf8"
|
|
2683
|
-
);
|
|
2684
|
-
process.stdout.write(`Wrote badge ${out}
|
|
2685
|
-
`);
|
|
2686
|
-
return 0;
|
|
2687
|
-
}
|
|
2688
|
-
var init_badge2 = __esm({
|
|
2689
|
-
"src/cli/commands/badge.ts"() {
|
|
2690
|
-
"use strict";
|
|
2691
|
-
init_store();
|
|
2692
|
-
init_badge();
|
|
2693
|
-
}
|
|
2694
|
-
});
|
|
1379
|
+
// src/index.ts
|
|
1380
|
+
init_verify_evidence();
|
|
2695
1381
|
|
|
2696
1382
|
// src/history/flaky.ts
|
|
2697
1383
|
function detectFlaky(records) {
|
|
@@ -2711,20 +1397,15 @@ function detectFlaky(records) {
|
|
|
2711
1397
|
}
|
|
2712
1398
|
return flaky;
|
|
2713
1399
|
}
|
|
2714
|
-
var init_flaky = __esm({
|
|
2715
|
-
"src/history/flaky.ts"() {
|
|
2716
|
-
"use strict";
|
|
2717
|
-
}
|
|
2718
|
-
});
|
|
2719
1400
|
|
|
2720
1401
|
// src/history/read.ts
|
|
2721
|
-
import { readdirSync as
|
|
2722
|
-
import { join as
|
|
1402
|
+
import { readdirSync as readdirSync3, readFileSync as readFileSync4 } from "fs";
|
|
1403
|
+
import { join as join8 } from "path";
|
|
2723
1404
|
function loadRuns(root, limit = 20) {
|
|
2724
|
-
const runs =
|
|
1405
|
+
const runs = join8(root, ".veris", "runs");
|
|
2725
1406
|
let names;
|
|
2726
1407
|
try {
|
|
2727
|
-
names =
|
|
1408
|
+
names = readdirSync3(runs, { withFileTypes: true }).filter((d) => d.isDirectory() && d.name !== "watch").map((d) => d.name);
|
|
2728
1409
|
} catch {
|
|
2729
1410
|
return [];
|
|
2730
1411
|
}
|
|
@@ -2732,7 +1413,7 @@ function loadRuns(root, limit = 20) {
|
|
|
2732
1413
|
for (const name of names) {
|
|
2733
1414
|
try {
|
|
2734
1415
|
const rec = JSON.parse(
|
|
2735
|
-
|
|
1416
|
+
readFileSync4(join8(runs, name, "evidence.json"), "utf8")
|
|
2736
1417
|
);
|
|
2737
1418
|
if (rec?.schema === "veriskit/evidence@1") records.push(rec);
|
|
2738
1419
|
} catch {
|
|
@@ -2743,213 +1424,18 @@ function loadRuns(root, limit = 20) {
|
|
|
2743
1424
|
);
|
|
2744
1425
|
return records.slice(0, limit);
|
|
2745
1426
|
}
|
|
2746
|
-
var init_read = __esm({
|
|
2747
|
-
"src/history/read.ts"() {
|
|
2748
|
-
"use strict";
|
|
2749
|
-
}
|
|
2750
|
-
});
|
|
2751
|
-
|
|
2752
|
-
// src/cli/commands/log.ts
|
|
2753
|
-
var log_exports = {};
|
|
2754
|
-
__export(log_exports, {
|
|
2755
|
-
runLog: () => runLog
|
|
2756
|
-
});
|
|
2757
|
-
async function runLog(root, opts = {}) {
|
|
2758
|
-
const limit = Number.isFinite(opts.limit) && opts.limit > 0 ? opts.limit : 20;
|
|
2759
|
-
const records = loadRuns(root, limit);
|
|
2760
|
-
if (records.length === 0) {
|
|
2761
|
-
process.stdout.write("No runs yet. Run `veris verify` first.\n");
|
|
2762
|
-
return 0;
|
|
2763
|
-
}
|
|
2764
|
-
if (opts.flaky) {
|
|
2765
|
-
const flaky = detectFlaky(records);
|
|
2766
|
-
if (flaky.length === 0) {
|
|
2767
|
-
process.stdout.write(
|
|
2768
|
-
`No flaky checks in the last ${records.length} run(s).
|
|
2769
|
-
`
|
|
2770
|
-
);
|
|
2771
|
-
return 0;
|
|
2772
|
-
}
|
|
2773
|
-
process.stdout.write(
|
|
2774
|
-
`Flaky checks (both passed and failed in the last ${records.length} run(s), newest first):
|
|
2775
|
-
`
|
|
2776
|
-
);
|
|
2777
|
-
for (const f of flaky) {
|
|
2778
|
-
process.stdout.write(` ${f.id.padEnd(8)} ${f.statuses.join(" ")}
|
|
2779
|
-
`);
|
|
2780
|
-
}
|
|
2781
|
-
return 0;
|
|
2782
|
-
}
|
|
2783
|
-
for (const rec of records) {
|
|
2784
|
-
const date = rec.startedAt.slice(0, 19).replace("T", " ");
|
|
2785
|
-
const checks = (rec.checks ?? []).map((c) => `${c.id}:${c.status}`).join(" ");
|
|
2786
|
-
const commit = rec.git ? rec.git.commit.slice(0, 7) : "-";
|
|
2787
|
-
process.stdout.write(
|
|
2788
|
-
`${date} ${rec.verdict.state.padEnd(9)} ${checks} ${commit}
|
|
2789
|
-
`
|
|
2790
|
-
);
|
|
2791
|
-
}
|
|
2792
|
-
process.stdout.write(
|
|
2793
|
-
"\nHistory is local to this machine (.veris/runs is gitignored).\n"
|
|
2794
|
-
);
|
|
2795
|
-
return 0;
|
|
2796
|
-
}
|
|
2797
|
-
var init_log = __esm({
|
|
2798
|
-
"src/cli/commands/log.ts"() {
|
|
2799
|
-
"use strict";
|
|
2800
|
-
init_flaky();
|
|
2801
|
-
init_read();
|
|
2802
|
-
}
|
|
2803
|
-
});
|
|
2804
1427
|
|
|
2805
|
-
// src/
|
|
2806
|
-
|
|
2807
|
-
|
|
2808
|
-
import { argv } from "process";
|
|
2809
|
-
import { pathToFileURL } from "url";
|
|
2810
|
-
import cac from "cac";
|
|
2811
|
-
function buildCli() {
|
|
2812
|
-
const cli = cac("veris");
|
|
2813
|
-
cli.version(VERSION);
|
|
2814
|
-
cli.help();
|
|
2815
|
-
cli.command("doctor", "Report environment + capabilities (read-only)").action(async () => {
|
|
2816
|
-
const { runDoctor: runDoctor2 } = await Promise.resolve().then(() => (init_doctor(), doctor_exports));
|
|
2817
|
-
process.exitCode = await runDoctor2(process.cwd());
|
|
2818
|
-
});
|
|
2819
|
-
cli.command("test", "Run the detected unit test runner").action(async () => {
|
|
2820
|
-
const { runTest: runTest2 } = await Promise.resolve().then(() => (init_test(), test_exports));
|
|
2821
|
-
process.exitCode = await runTest2(process.cwd());
|
|
2822
|
-
});
|
|
2823
|
-
cli.command("init", "Detect the stack and set up .veris (idempotent)").action(async () => {
|
|
2824
|
-
const { runInit: runInit2 } = await Promise.resolve().then(() => (init_init(), init_exports));
|
|
2825
|
-
process.exitCode = await runInit2(process.cwd());
|
|
2826
|
-
});
|
|
2827
|
-
cli.command("verify", "Run the full check set and produce a verdict + report").option("--partial-ok", "Exit 0 even when the verdict is partial").option(
|
|
2828
|
-
"--github",
|
|
2829
|
-
"Publish the verdict to the GitHub PR (comment + check run)"
|
|
2830
|
-
).option("--browser", "Also run browser tests (Playwright), when available").action(
|
|
2831
|
-
async (opts) => {
|
|
2832
|
-
const { runVerify: runVerify2 } = await Promise.resolve().then(() => (init_verify(), verify_exports));
|
|
2833
|
-
process.exitCode = await runVerify2(process.cwd(), {
|
|
2834
|
-
partialOk: opts.partialOk,
|
|
2835
|
-
github: opts.github,
|
|
2836
|
-
browser: opts.browser
|
|
2837
|
-
});
|
|
2838
|
-
}
|
|
2839
|
-
);
|
|
2840
|
-
cli.command("report", "Print the latest verification report").action(async () => {
|
|
2841
|
-
const { runReport: runReport2 } = await Promise.resolve().then(() => (init_report(), report_exports));
|
|
2842
|
-
process.exitCode = await runReport2(process.cwd());
|
|
2843
|
-
});
|
|
2844
|
-
cli.command("affected", "Run only the checks affected by changed files").option("--base <ref>", "Compare against a git ref instead of HEAD").option("--partial-ok", "Exit 0 even when the verdict is partial").option(
|
|
2845
|
-
"--github",
|
|
2846
|
-
"Publish the verdict to the GitHub PR (comment + check run)"
|
|
2847
|
-
).action(
|
|
2848
|
-
async (opts) => {
|
|
2849
|
-
const { runAffected: runAffected2 } = await Promise.resolve().then(() => (init_affected(), affected_exports));
|
|
2850
|
-
process.exitCode = await runAffected2(process.cwd(), {
|
|
2851
|
-
base: opts.base,
|
|
2852
|
-
partialOk: opts.partialOk,
|
|
2853
|
-
github: opts.github
|
|
2854
|
-
});
|
|
2855
|
-
}
|
|
2856
|
-
);
|
|
2857
|
-
cli.command("watch", "Re-run affected checks as files change (Ctrl-C to stop)").option("--poll", "Use mtime polling instead of native fs.watch").action(async (opts) => {
|
|
2858
|
-
const { runWatch: runWatch2 } = await Promise.resolve().then(() => (init_watch(), watch_exports));
|
|
2859
|
-
process.exitCode = await runWatch2(process.cwd(), { poll: opts.poll });
|
|
2860
|
-
});
|
|
2861
|
-
cli.command("scan", "Map the import graph and untested areas (read-only)").action(async () => {
|
|
2862
|
-
const { runScan: runScan2 } = await Promise.resolve().then(() => (init_scan(), scan_exports));
|
|
2863
|
-
process.exitCode = await runScan2(process.cwd());
|
|
2864
|
-
});
|
|
2865
|
-
cli.command(
|
|
2866
|
-
"plan",
|
|
2867
|
-
"Recommend what to test, from the import graph (read-only)"
|
|
2868
|
-
).option("--base <ref>", "Also factor in changes vs a git ref").action(async (opts) => {
|
|
2869
|
-
const { runPlan: runPlan2 } = await Promise.resolve().then(() => (init_plan(), plan_exports));
|
|
2870
|
-
process.exitCode = await runPlan2(process.cwd(), { base: opts.base });
|
|
2871
|
-
});
|
|
2872
|
-
cli.command(
|
|
2873
|
-
"evidence <action> [file]",
|
|
2874
|
-
"Evidence: verify <file> | bundle | show [file] | keygen | sign <file>"
|
|
2875
|
-
).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(
|
|
2876
|
-
async (action, file, opts) => {
|
|
2877
|
-
const mod = await Promise.resolve().then(() => (init_evidence(), evidence_exports));
|
|
2878
|
-
if (action === "verify") {
|
|
2879
|
-
if (!file) {
|
|
2880
|
-
process.stderr.write("veris: evidence verify needs a <file>\n");
|
|
2881
|
-
process.exitCode = 1;
|
|
2882
|
-
return;
|
|
2883
|
-
}
|
|
2884
|
-
process.exitCode = await mod.runEvidenceVerify(file, {
|
|
2885
|
-
pubkey: opts.pubkey,
|
|
2886
|
-
keyId: opts.keyId,
|
|
2887
|
-
sig: opts.sig
|
|
2888
|
-
});
|
|
2889
|
-
} else if (action === "bundle") {
|
|
2890
|
-
process.exitCode = await mod.runEvidenceBundle(process.cwd(), {
|
|
2891
|
-
out: opts.out
|
|
2892
|
-
});
|
|
2893
|
-
} else if (action === "show") {
|
|
2894
|
-
process.exitCode = await mod.runEvidenceShow(process.cwd(), file);
|
|
2895
|
-
} else if (action === "keygen") {
|
|
2896
|
-
process.exitCode = await mod.runEvidenceKeygen(process.cwd(), {
|
|
2897
|
-
out: opts.out
|
|
2898
|
-
});
|
|
2899
|
-
} else if (action === "sign") {
|
|
2900
|
-
if (!file) {
|
|
2901
|
-
process.stderr.write("veris: evidence sign needs a <file>\n");
|
|
2902
|
-
process.exitCode = 1;
|
|
2903
|
-
return;
|
|
2904
|
-
}
|
|
2905
|
-
process.exitCode = await mod.runEvidenceSign(file, {
|
|
2906
|
-
key: opts.key,
|
|
2907
|
-
out: opts.out
|
|
2908
|
-
});
|
|
2909
|
-
} else {
|
|
2910
|
-
process.stderr.write(
|
|
2911
|
-
`veris: unknown evidence action '${action}' (use verify | bundle | show | keygen | sign)
|
|
2912
|
-
`
|
|
2913
|
-
);
|
|
2914
|
-
process.exitCode = 1;
|
|
2915
|
-
}
|
|
2916
|
-
}
|
|
2917
|
-
);
|
|
2918
|
-
cli.command(
|
|
2919
|
-
"badge",
|
|
2920
|
-
"Write a shields.io endpoint JSON from the latest verdict"
|
|
2921
|
-
).option("--out <file>", "Output path (default .veris/badge.json)").action(async (opts) => {
|
|
2922
|
-
const { runBadge: runBadge2 } = await Promise.resolve().then(() => (init_badge2(), badge_exports));
|
|
2923
|
-
process.exitCode = await runBadge2(process.cwd(), { out: opts.out });
|
|
2924
|
-
});
|
|
2925
|
-
cli.command("log", "List past verification runs (local history)").option("--limit <n>", "How many runs to show (default 20)").option("--flaky", "Show only checks that flip-flop across runs").action(async (opts) => {
|
|
2926
|
-
const { runLog: runLog2 } = await Promise.resolve().then(() => (init_log(), log_exports));
|
|
2927
|
-
process.exitCode = await runLog2(process.cwd(), {
|
|
2928
|
-
limit: opts.limit ? Number(opts.limit) : void 0,
|
|
2929
|
-
flaky: opts.flaky
|
|
2930
|
-
});
|
|
2931
|
-
});
|
|
2932
|
-
return { raw: cli, version: VERSION };
|
|
2933
|
-
}
|
|
2934
|
-
async function main(argv2) {
|
|
2935
|
-
try {
|
|
2936
|
-
const { raw } = buildCli();
|
|
2937
|
-
if (argv2.length <= 2) {
|
|
2938
|
-
raw.outputHelp();
|
|
2939
|
-
return;
|
|
2940
|
-
}
|
|
2941
|
-
raw.parse(argv2);
|
|
2942
|
-
} catch (err) {
|
|
2943
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
2944
|
-
process.stderr.write(`veris: ${msg}
|
|
2945
|
-
`);
|
|
2946
|
-
process.exitCode = 1;
|
|
2947
|
-
}
|
|
2948
|
-
}
|
|
2949
|
-
var invokedPath = argv[1] ? realpathSync(argv[1]) : "";
|
|
2950
|
-
var isEntry = import.meta.url === pathToFileURL(invokedPath).href;
|
|
2951
|
-
if (isEntry) void main(argv);
|
|
1428
|
+
// src/index.ts
|
|
1429
|
+
init_analyze();
|
|
1430
|
+
init_graph();
|
|
2952
1431
|
export {
|
|
2953
|
-
|
|
2954
|
-
|
|
1432
|
+
affectedProject,
|
|
1433
|
+
analyze,
|
|
1434
|
+
buildGraph,
|
|
1435
|
+
detectFlaky,
|
|
1436
|
+
detectProject,
|
|
1437
|
+
getEnvironmentInfo,
|
|
1438
|
+
loadRuns,
|
|
1439
|
+
verifyEvidenceFile,
|
|
1440
|
+
verifyProject
|
|
2955
1441
|
};
|