60 lines
2.4 KiB
JavaScript
60 lines
2.4 KiB
JavaScript
#!/usr/bin/env node
|
|
|
|
import { readFileSync } from "node:fs";
|
|
|
|
const workflowFiles = [".github/workflows/lint.yml", ".github/workflows/playwright.yml"];
|
|
const violations = [];
|
|
|
|
const assertIncludes = (file, source, expected, reason) => {
|
|
if (!source.includes(expected)) {
|
|
violations.push(`${file}: missing ${reason}: ${expected}`);
|
|
}
|
|
};
|
|
|
|
const assertNotMatches = (file, source, pattern, reason) => {
|
|
const lines = source.split(/\r?\n/);
|
|
lines.forEach((line, index) => {
|
|
if (pattern.test(line)) {
|
|
violations.push(`${file}:${index + 1}: ${reason}: ${line.trim()}`);
|
|
}
|
|
});
|
|
};
|
|
|
|
for (const file of workflowFiles) {
|
|
const source = readFileSync(file, "utf8");
|
|
|
|
assertIncludes(file, source, "permissions:\n contents: read", "read-only workflow permissions");
|
|
assertIncludes(file, source, "concurrency:", "workflow concurrency");
|
|
assertIncludes(file, source, "cancel-in-progress: true", "stale run cancellation");
|
|
assertIncludes(file, source, "runs-on: ubuntu-22.04", "pinned Ubuntu runner");
|
|
assertIncludes(file, source, "pnpm/action-setup@v4", "pinned pnpm setup action major");
|
|
assertIncludes(file, source, "version: 9.12.3", "pinned pnpm version");
|
|
assertIncludes(file, source, "pnpm install --frozen-lockfile", "frozen dependency install");
|
|
|
|
assertNotMatches(file, source, /ubuntu-latest/, "floating runner");
|
|
assertNotMatches(file, source, /actions\/cache@v3/, "outdated cache action");
|
|
assertNotMatches(file, source, /@[Ll]atest\b/, "floating action or tool version");
|
|
assertNotMatches(file, source, /--no-lint/, "build or test bypass flag");
|
|
}
|
|
|
|
const packageJson = JSON.parse(readFileSync("package.json", "utf8"));
|
|
for (const [name, command] of Object.entries(packageJson.scripts ?? {})) {
|
|
if (/@latest\b/.test(command)) {
|
|
violations.push(`package.json scripts.${name}: floating tool version: ${command}`);
|
|
}
|
|
if (/npx\s+ultracite/.test(command)) {
|
|
violations.push(`package.json scripts.${name}: use pnpm exec ultracite instead of npx: ${command}`);
|
|
}
|
|
if (/--no-lint|build:skip-prerender|Build fallback/.test(command)) {
|
|
violations.push(`package.json scripts.${name}: build bypass is not allowed: ${command}`);
|
|
}
|
|
}
|
|
|
|
if (violations.length > 0) {
|
|
console.error("CI hygiene check failed. Keep workflows pinned, least-privileged, and non-bypassing.");
|
|
console.error(violations.join("\n"));
|
|
process.exit(1);
|
|
}
|
|
|
|
console.log(`CI hygiene check passed for ${workflowFiles.length} workflows.`);
|