@decohere/ci-tools 0.0.1-dev.119 → 0.0.1-dev.12
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/package.json +3 -8
- package/src/audit/enforce.mjs +5 -7
- package/src/audit/summarize.mjs +32 -10
- package/src/audit/upsert-issue.mjs +74 -25
- package/src/bench/bench.mjs +368 -134
- package/src/bench/trace.mjs +14 -18
- package/src/coverage/enforce-thresholds.mjs +144 -0
- package/src/pages/artifact.mjs +183 -38
- package/src/pages/clear-bench-history.mjs +27 -0
- package/src/pages/keep-prior-bench-badge.mjs +38 -0
- package/src/pages/restore-bench-history.mjs +28 -15
- package/src/release/npm-links.mjs +13 -6
- package/src/release/sync-dependencies.mjs +2 -1
- package/tests/lib/lcov.test.mjs +4 -4
- package/src/audit/format.mjs +0 -133
- package/src/bench/file-regressions.mjs +0 -76
- package/src/bench/history.mjs +0 -248
- package/src/bench/nightly.mjs +0 -118
- package/src/bench/regression-issue.mjs +0 -44
- package/src/github/credential-nudge.mjs +0 -245
- package/src/github/oidc-bootstrap-nudge.mjs +0 -210
- package/src/github/publish-verify.mjs +0 -206
- package/src/github/smoke-create-launcher.mjs +0 -70
- package/src/lib/github-issue.mjs +0 -75
- package/src/lib/workspace-packages.mjs +0 -30
- package/src/pages/bench-page.mjs +0 -323
- package/src/pages/restore-badges.mjs +0 -58
- package/tests/audit/format.test.mjs +0 -115
- package/tests/bench/history.test.mjs +0 -279
- package/tests/bench/regression-issue.test.mjs +0 -42
- package/tests/github/credential-nudge.test.mjs +0 -116
- package/tests/github/oidc-bootstrap-nudge.test.mjs +0 -89
- package/tests/github/publish-verify.test.mjs +0 -115
- package/tests/github/smoke-create-launcher.test.mjs +0 -71
- package/tests/lib/github-issue.test.mjs +0 -85
- package/tests/pages/bench-page.test.mjs +0 -123
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@decohere/ci-tools",
|
|
3
|
-
"version": "0.0.1-dev.
|
|
3
|
+
"version": "0.0.1-dev.12",
|
|
4
4
|
"description": "CI utility scripts for badge generation, coverage reporting, pages artifact assembly, and GitHub API tooling",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"typescript",
|
|
@@ -16,13 +16,12 @@
|
|
|
16
16
|
"exports": {
|
|
17
17
|
"./lib/svg": "./src/lib/svg.mjs",
|
|
18
18
|
"./lib/lcov": "./src/lib/lcov.mjs",
|
|
19
|
-
"./lib/github-issue": "./src/lib/github-issue.mjs",
|
|
20
19
|
"./coverage/badge": "./src/coverage/badge.mjs",
|
|
21
20
|
"./coverage/report": "./src/coverage/report.mjs",
|
|
21
|
+
"./coverage/enforce-thresholds": "./src/coverage/enforce-thresholds.mjs",
|
|
22
22
|
"./badges/ci-status": "./src/badges/ci-status.mjs",
|
|
23
23
|
"./pages/artifact": "./src/pages/artifact.mjs",
|
|
24
24
|
"./pages/smoke": "./src/pages/smoke.mjs",
|
|
25
|
-
"./audit/format": "./src/audit/format.mjs",
|
|
26
25
|
"./audit/summarize": "./src/audit/summarize.mjs",
|
|
27
26
|
"./audit/enforce": "./src/audit/enforce.mjs",
|
|
28
27
|
"./audit/upsert-issue": "./src/audit/upsert-issue.mjs",
|
|
@@ -32,11 +31,7 @@
|
|
|
32
31
|
"./changeset/check-status": "./src/changeset/check-status.mjs",
|
|
33
32
|
"./github/create-issue": "./src/github/create-issue.mjs",
|
|
34
33
|
"./github/ensure-metadata": "./src/github/ensure-metadata.mjs",
|
|
35
|
-
"./github/create-or-update-pr": "./src/github/create-or-update-pr.mjs"
|
|
36
|
-
"./github/publish-verify": "./src/github/publish-verify.mjs",
|
|
37
|
-
"./github/credential-nudge": "./src/github/credential-nudge.mjs",
|
|
38
|
-
"./github/oidc-bootstrap-nudge": "./src/github/oidc-bootstrap-nudge.mjs",
|
|
39
|
-
"./github/smoke-create-launcher": "./src/github/smoke-create-launcher.mjs"
|
|
34
|
+
"./github/create-or-update-pr": "./src/github/create-or-update-pr.mjs"
|
|
40
35
|
},
|
|
41
36
|
"scripts": {
|
|
42
37
|
"test": "vitest run"
|
package/src/audit/enforce.mjs
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { readFileSync } from "node:fs";
|
|
2
|
-
import { formatLog, highCritical, parseAudit } from "./format.mjs";
|
|
3
2
|
|
|
4
3
|
const auditFilePath = process.argv[2] ?? "npm-audit.json";
|
|
5
4
|
|
|
@@ -11,13 +10,12 @@ try {
|
|
|
11
10
|
process.exit(1);
|
|
12
11
|
}
|
|
13
12
|
|
|
14
|
-
const
|
|
15
|
-
const
|
|
13
|
+
const vulnerabilities = data.metadata?.vulnerabilities ?? {};
|
|
14
|
+
const high = vulnerabilities.high ?? 0;
|
|
15
|
+
const critical = vulnerabilities.critical ?? 0;
|
|
16
16
|
|
|
17
|
-
if (
|
|
18
|
-
console.error(
|
|
19
|
-
`Security gate failed: high=${parsed.high}, critical=${parsed.critical}\n${formatLog({ ...parsed, advisories: blocking })}`,
|
|
20
|
-
);
|
|
17
|
+
if (high > 0 || critical > 0) {
|
|
18
|
+
console.error(`Security gate failed: high=${high}, critical=${critical}`);
|
|
21
19
|
process.exit(1);
|
|
22
20
|
}
|
|
23
21
|
|
package/src/audit/summarize.mjs
CHANGED
|
@@ -1,24 +1,46 @@
|
|
|
1
1
|
import { appendFileSync, readFileSync } from "node:fs";
|
|
2
|
-
import { formatLog, formatMarkdown, parseAudit } from "./format.mjs";
|
|
3
2
|
|
|
4
3
|
const auditFilePath = process.argv[2] ?? "npm-audit.json";
|
|
5
4
|
|
|
6
|
-
function writeSummary(
|
|
5
|
+
function writeSummary(lines) {
|
|
6
|
+
const summaryText = `${lines.join("\n")}\n`;
|
|
7
7
|
if (process.env.GITHUB_STEP_SUMMARY) {
|
|
8
|
-
appendFileSync(process.env.GITHUB_STEP_SUMMARY,
|
|
8
|
+
appendFileSync(process.env.GITHUB_STEP_SUMMARY, summaryText);
|
|
9
|
+
return;
|
|
9
10
|
}
|
|
11
|
+
process.stdout.write(summaryText);
|
|
10
12
|
}
|
|
11
13
|
|
|
12
14
|
let data = null;
|
|
13
15
|
try {
|
|
14
16
|
data = JSON.parse(readFileSync(auditFilePath, "utf8"));
|
|
15
17
|
} catch {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
18
|
+
writeSummary([
|
|
19
|
+
"## Security Audit",
|
|
20
|
+
"",
|
|
21
|
+
"- npm audit output could not be parsed.",
|
|
22
|
+
`- See uploaded \`${auditFilePath}\` artifact for raw output.`,
|
|
23
|
+
]);
|
|
24
|
+
process.exit(0);
|
|
20
25
|
}
|
|
21
26
|
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
|
|
27
|
+
const vulnerabilities = data.metadata?.vulnerabilities ?? {};
|
|
28
|
+
const info = vulnerabilities.info ?? 0;
|
|
29
|
+
const low = vulnerabilities.low ?? 0;
|
|
30
|
+
const moderate = vulnerabilities.moderate ?? 0;
|
|
31
|
+
const high = vulnerabilities.high ?? 0;
|
|
32
|
+
const critical = vulnerabilities.critical ?? 0;
|
|
33
|
+
const total = vulnerabilities.total ?? info + low + moderate + high + critical;
|
|
34
|
+
|
|
35
|
+
writeSummary([
|
|
36
|
+
"## Security Audit",
|
|
37
|
+
"",
|
|
38
|
+
`| Severity | Count |`,
|
|
39
|
+
`|-----------|-------|`,
|
|
40
|
+
`| Critical | ${critical} |`,
|
|
41
|
+
`| High | ${high} |`,
|
|
42
|
+
`| Moderate | ${moderate} |`,
|
|
43
|
+
`| Low | ${low} |`,
|
|
44
|
+
`| Info | ${info} |`,
|
|
45
|
+
`| **Total** | **${total}** |`,
|
|
46
|
+
]);
|
|
@@ -1,19 +1,68 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import
|
|
1
|
+
import { spawnSync } from "node:child_process";
|
|
2
|
+
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import path from "node:path";
|
|
4
5
|
|
|
5
6
|
const auditFilePath = process.argv[2] ?? "npm-audit.json";
|
|
6
7
|
const ISSUE_TITLE = "security(ci): npm audit vulnerabilities detected";
|
|
7
8
|
|
|
8
|
-
function
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
process.env.GITHUB_RUN_ID
|
|
13
|
-
) {
|
|
14
|
-
return `${process.env.GITHUB_SERVER_URL}/${process.env.GITHUB_REPOSITORY}/actions/runs/${process.env.GITHUB_RUN_ID}`;
|
|
9
|
+
function runGh(args) {
|
|
10
|
+
const result = spawnSync("gh", args, { encoding: "utf8", env: process.env });
|
|
11
|
+
if (result.status !== 0) {
|
|
12
|
+
throw new Error(result.stderr || result.stdout || `gh command failed: ${args.join(" ")}`);
|
|
15
13
|
}
|
|
16
|
-
return
|
|
14
|
+
return result.stdout;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function readAuditSummary(filePath) {
|
|
18
|
+
const data = JSON.parse(readFileSync(filePath, "utf8"));
|
|
19
|
+
const vulnerabilities = data.metadata?.vulnerabilities ?? {};
|
|
20
|
+
const info = vulnerabilities.info ?? 0;
|
|
21
|
+
const low = vulnerabilities.low ?? 0;
|
|
22
|
+
const moderate = vulnerabilities.moderate ?? 0;
|
|
23
|
+
const high = vulnerabilities.high ?? 0;
|
|
24
|
+
const critical = vulnerabilities.critical ?? 0;
|
|
25
|
+
const total = vulnerabilities.total ?? info + low + moderate + high + critical;
|
|
26
|
+
return { total, critical, high, moderate, low, info };
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function buildBody(summary) {
|
|
30
|
+
const runUrl =
|
|
31
|
+
process.env.GITHUB_SERVER_URL && process.env.GITHUB_REPOSITORY && process.env.GITHUB_RUN_ID
|
|
32
|
+
? `${process.env.GITHUB_SERVER_URL}/${process.env.GITHUB_REPOSITORY}/actions/runs/${process.env.GITHUB_RUN_ID}`
|
|
33
|
+
: "(run URL unavailable)";
|
|
34
|
+
const sha = process.env.GITHUB_SHA ? process.env.GITHUB_SHA.slice(0, 12) : "unknown";
|
|
35
|
+
|
|
36
|
+
return [
|
|
37
|
+
"## npm audit detected vulnerabilities",
|
|
38
|
+
"",
|
|
39
|
+
`- Total: ${summary.total}`,
|
|
40
|
+
`- Critical: ${summary.critical}`,
|
|
41
|
+
`- High: ${summary.high}`,
|
|
42
|
+
`- Moderate: ${summary.moderate}`,
|
|
43
|
+
`- Low: ${summary.low}`,
|
|
44
|
+
`- Info: ${summary.info}`,
|
|
45
|
+
"",
|
|
46
|
+
`- Commit: ${sha}`,
|
|
47
|
+
`- Workflow run: ${runUrl}`,
|
|
48
|
+
"",
|
|
49
|
+
"This issue is managed by CI and will be updated on subsequent main-branch runs while vulnerabilities remain.",
|
|
50
|
+
].join("\n");
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function findExistingIssueNumber() {
|
|
54
|
+
const output = runGh([
|
|
55
|
+
"issue",
|
|
56
|
+
"list",
|
|
57
|
+
"--state",
|
|
58
|
+
"open",
|
|
59
|
+
"--limit",
|
|
60
|
+
"200",
|
|
61
|
+
"--json",
|
|
62
|
+
"number,title",
|
|
63
|
+
]);
|
|
64
|
+
const issues = JSON.parse(output);
|
|
65
|
+
return issues.find((issue) => issue.title === ISSUE_TITLE)?.number ?? null;
|
|
17
66
|
}
|
|
18
67
|
|
|
19
68
|
if (!existsSync(auditFilePath)) {
|
|
@@ -21,21 +70,21 @@ if (!existsSync(auditFilePath)) {
|
|
|
21
70
|
process.exit(0);
|
|
22
71
|
}
|
|
23
72
|
|
|
24
|
-
const
|
|
25
|
-
if (
|
|
73
|
+
const summary = readAuditSummary(auditFilePath);
|
|
74
|
+
if (summary.total === 0) {
|
|
26
75
|
console.log("No vulnerabilities found; skipping issue create/update.");
|
|
27
76
|
process.exit(0);
|
|
28
77
|
}
|
|
29
78
|
|
|
30
|
-
const
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
"",
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
79
|
+
const body = buildBody(summary);
|
|
80
|
+
const tempFile = path.join(os.tmpdir(), `npm-audit-issue-${Date.now()}.md`);
|
|
81
|
+
writeFileSync(tempFile, body, "utf8");
|
|
82
|
+
|
|
83
|
+
const existingIssueNumber = findExistingIssueNumber();
|
|
84
|
+
if (existingIssueNumber) {
|
|
85
|
+
runGh(["issue", "comment", String(existingIssueNumber), "--body-file", tempFile]);
|
|
86
|
+
console.log(`Updated existing issue #${existingIssueNumber}.`);
|
|
87
|
+
} else {
|
|
88
|
+
runGh(["issue", "create", "--title", ISSUE_TITLE, "--body-file", tempFile]);
|
|
89
|
+
console.log("Created new npm audit tracking issue.");
|
|
90
|
+
}
|