Files
2026-07-13 13:39:12 +08:00

100 lines
3.4 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env node
// scripts/check/check-tracked-artifacts.mjs
// Gate: falha se o git rastreia artefatos de build/artefatos gerados proibidos.
// Guarda contra `git add -A` acidental em worktrees que include node_modules symlinks.
// Incidente registrado 2× neste repo (v3.8.12 / v3.8.13) — Hard Rule #7 extensão.
//
// Artefatos proibidos:
// - node_modules/ — deps de build nunca devem entrar no repo
// - .next/ — output do build Next.js
// - coverage/ — relatórios de cobertura gerados pelo c8
// - quality-metrics.json — saída do collect-metrics.mjs (gerado, não-versionado)
// - symlinks rastreados (mode 120000) — indício de `git add -A` em worktree
import { execFileSync } from "node:child_process";
import { pathToFileURL } from "node:url";
const FORBIDDEN_PREFIXES = ["node_modules/", ".next/", "coverage/"];
const FORBIDDEN_EXACT = new Set([
"quality-metrics.json", // legacy root location (still forbidden if a stale run writes it)
"config/quality/quality-metrics.json", // current generated location (collect-metrics.mjs)
]);
/**
* Verifica se algum caminho na lista de arquivos rastreados corresponde a um
* artefato proibido. Também aceita uma lista separada de symlinks rastreados.
*
* @param {string[]} trackedFiles - saída de `git ls-files` (caminhos relativos)
* @param {string[]} trackedSymlinks - caminhos com mode 120000 (saída de git ls-files -s)
* @returns {string[]} lista de violações (strings descritivas)
*/
export function checkTrackedArtifacts(trackedFiles, trackedSymlinks = []) {
const violations = [];
for (const file of trackedFiles) {
if (FORBIDDEN_EXACT.has(file)) {
violations.push(`forbidden tracked artifact: ${file}`);
continue;
}
for (const prefix of FORBIDDEN_PREFIXES) {
if (file.startsWith(prefix)) {
violations.push(`forbidden tracked artifact (${prefix}*): ${file}`);
break;
}
}
}
for (const sym of trackedSymlinks) {
violations.push(`forbidden tracked symlink (mode 120000): ${sym}`);
}
return violations;
}
function getTrackedFiles() {
const output = execFileSync("git", ["ls-files"], { encoding: "utf8" });
return output
.split("\n")
.map((l) => l.trim())
.filter(Boolean);
}
function getTrackedSymlinks() {
// git ls-files -s prints: <mode> <hash> <stage>\t<path>
// mode 120000 = symlink
const output = execFileSync("git", ["ls-files", "-s"], { encoding: "utf8" });
const symlinks = [];
for (const line of output.split("\n")) {
if (line.startsWith("120000")) {
const parts = line.split("\t");
if (parts[1]) symlinks.push(parts[1].trim());
}
}
return symlinks;
}
function main() {
const trackedFiles = getTrackedFiles();
const trackedSymlinks = getTrackedSymlinks();
const violations = checkTrackedArtifacts(trackedFiles, trackedSymlinks);
if (violations.length === 0) {
console.log("[tracked-artifacts] OK — no forbidden artifacts tracked by git");
process.exit(0);
}
console.error(
`[tracked-artifacts] FAIL — ${violations.length} forbidden artifact(s) tracked by git:`
);
for (const v of violations) {
console.error(` ✗ ${v}`);
}
console.error(
"\n → Run: git rm --cached <path> to untrack the artifact." +
"\n → Add the path to .gitignore to prevent re-tracking."
);
process.exit(1);
}
if (import.meta.url === pathToFileURL(process.argv[1] || "").href) main();