@decohere/ci-tools 0.0.1-dev.126 → 0.0.1-dev.13

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.
Files changed (40) hide show
  1. package/package.json +3 -18
  2. package/src/audit/enforce.mjs +5 -7
  3. package/src/audit/summarize.mjs +32 -10
  4. package/src/audit/upsert-issue.mjs +74 -25
  5. package/src/bench/bench.mjs +368 -134
  6. package/src/bench/trace.mjs +14 -18
  7. package/src/coverage/enforce-thresholds.mjs +144 -0
  8. package/src/pages/artifact.mjs +183 -38
  9. package/src/pages/clear-bench-history.mjs +27 -0
  10. package/src/pages/keep-prior-bench-badge.mjs +38 -0
  11. package/src/pages/restore-bench-history.mjs +28 -15
  12. package/src/release/npm-links.mjs +13 -6
  13. package/src/release/sync-dependencies.mjs +2 -1
  14. package/tests/lib/lcov.test.mjs +4 -4
  15. package/LICENSE +0 -15
  16. package/src/audit/format.mjs +0 -133
  17. package/src/bench/file-regressions.mjs +0 -76
  18. package/src/bench/history.mjs +0 -248
  19. package/src/bench/nightly.mjs +0 -118
  20. package/src/bench/regression-issue.mjs +0 -44
  21. package/src/github/credential-nudge.mjs +0 -226
  22. package/src/github/npm-bootstrap.mjs +0 -179
  23. package/src/github/oidc-bootstrap-nudge.mjs +0 -214
  24. package/src/github/publish-verify.mjs +0 -206
  25. package/src/github/smoke-create-launcher.mjs +0 -70
  26. package/src/lib/github-issue.mjs +0 -75
  27. package/src/lib/workspace-packages.mjs +0 -30
  28. package/src/pages/bench-page.mjs +0 -323
  29. package/src/pages/restore-badges.mjs +0 -58
  30. package/tests/audit/format.test.mjs +0 -115
  31. package/tests/bench/history.test.mjs +0 -279
  32. package/tests/bench/regression-issue.test.mjs +0 -42
  33. package/tests/github/credential-nudge.test.mjs +0 -112
  34. package/tests/github/npm-bootstrap.test.mjs +0 -125
  35. package/tests/github/oidc-bootstrap-nudge.test.mjs +0 -89
  36. package/tests/github/package-metadata.test.mjs +0 -69
  37. package/tests/github/publish-verify.test.mjs +0 -115
  38. package/tests/github/smoke-create-launcher.test.mjs +0 -71
  39. package/tests/lib/github-issue.test.mjs +0 -85
  40. 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.126",
3
+ "version": "0.0.1-dev.13",
4
4
  "description": "CI utility scripts for badge generation, coverage reporting, pages artifact assembly, and GitHub API tooling",
5
5
  "keywords": [
6
6
  "typescript",
@@ -10,28 +10,18 @@
10
10
  "github-actions",
11
11
  "decohere"
12
12
  ],
13
- "homepage": "https://github.com/typesmiths/decohere/tree/main/projects/ci-tools#readme",
14
- "bugs": {
15
- "url": "https://github.com/typesmiths/decohere/issues"
16
- },
17
- "repository": {
18
- "type": "git",
19
- "url": "git+https://github.com/typesmiths/decohere.git",
20
- "directory": "projects/ci-tools"
21
- },
22
13
  "license": "ISC",
23
14
  "author": "Typesmiths",
24
15
  "type": "module",
25
16
  "exports": {
26
17
  "./lib/svg": "./src/lib/svg.mjs",
27
18
  "./lib/lcov": "./src/lib/lcov.mjs",
28
- "./lib/github-issue": "./src/lib/github-issue.mjs",
29
19
  "./coverage/badge": "./src/coverage/badge.mjs",
30
20
  "./coverage/report": "./src/coverage/report.mjs",
21
+ "./coverage/enforce-thresholds": "./src/coverage/enforce-thresholds.mjs",
31
22
  "./badges/ci-status": "./src/badges/ci-status.mjs",
32
23
  "./pages/artifact": "./src/pages/artifact.mjs",
33
24
  "./pages/smoke": "./src/pages/smoke.mjs",
34
- "./audit/format": "./src/audit/format.mjs",
35
25
  "./audit/summarize": "./src/audit/summarize.mjs",
36
26
  "./audit/enforce": "./src/audit/enforce.mjs",
37
27
  "./audit/upsert-issue": "./src/audit/upsert-issue.mjs",
@@ -41,12 +31,7 @@
41
31
  "./changeset/check-status": "./src/changeset/check-status.mjs",
42
32
  "./github/create-issue": "./src/github/create-issue.mjs",
43
33
  "./github/ensure-metadata": "./src/github/ensure-metadata.mjs",
44
- "./github/create-or-update-pr": "./src/github/create-or-update-pr.mjs",
45
- "./github/publish-verify": "./src/github/publish-verify.mjs",
46
- "./github/credential-nudge": "./src/github/credential-nudge.mjs",
47
- "./github/oidc-bootstrap-nudge": "./src/github/oidc-bootstrap-nudge.mjs",
48
- "./github/npm-bootstrap": "./src/github/npm-bootstrap.mjs",
49
- "./github/smoke-create-launcher": "./src/github/smoke-create-launcher.mjs"
34
+ "./github/create-or-update-pr": "./src/github/create-or-update-pr.mjs"
50
35
  },
51
36
  "scripts": {
52
37
  "test": "vitest run"
@@ -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 parsed = parseAudit(data);
15
- const blocking = highCritical(parsed);
13
+ const vulnerabilities = data.metadata?.vulnerabilities ?? {};
14
+ const high = vulnerabilities.high ?? 0;
15
+ const critical = vulnerabilities.critical ?? 0;
16
16
 
17
- if (blocking.length > 0) {
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
 
@@ -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(text) {
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, text);
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
- const msg = `npm audit output could not be parsed (${auditFilePath}).\n`;
17
- process.stderr.write(msg);
18
- writeSummary(`## Security Audit\n\n- ${msg}`);
19
- process.exit(1);
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 parsed = parseAudit(data);
23
- process.stdout.write(formatLog(parsed));
24
- writeSummary(formatMarkdown(parsed));
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 { existsSync, readFileSync } from "node:fs";
2
- import { upsertIssue } from "../lib/github-issue.mjs";
3
- import { formatMarkdown, parseAudit } from "./format.mjs";
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 runUrl() {
9
- if (
10
- process.env.GITHUB_SERVER_URL &&
11
- process.env.GITHUB_REPOSITORY &&
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 "(run URL unavailable)";
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 parsed = parseAudit(JSON.parse(readFileSync(auditFilePath, "utf8")));
25
- if (parsed.total === 0) {
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 sha = process.env.GITHUB_SHA ? process.env.GITHUB_SHA.slice(0, 12) : "unknown";
31
- const body = [
32
- formatMarkdown(parsed).trim(),
33
- "",
34
- `- Commit: \`${sha}\``,
35
- `- Workflow run: ${runUrl()}`,
36
- "",
37
- "This issue is managed by CI: main-branch runs and the nightly audit update it in place while vulnerabilities remain.",
38
- ].join("\n");
39
-
40
- const result = upsertIssue({ title: ISSUE_TITLE, body });
41
- console.log(`${result.created ? "Opened" : "Updated"} issue #${result.number} ${result.url}`);
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
+ }