import console from "node:console"; import { execFileSync } from "node:child_process"; import { existsSync, readFileSync, unlinkSync, writeFileSync } from "node:fs"; import { dirname, join, resolve } from "node:path"; import { fileURLToPath } from "node:url"; import process from "node:process"; const root = resolve(dirname(fileURLToPath(import.meta.url)), ".."); const dryRun = process.argv.includes("--check"); const removed = []; const untracked = []; const warnings = []; function removeLocal(path) { const absolute = join(root, path); if (!existsSync(absolute)) return; if (!dryRun) unlinkSync(absolute); removed.push(path); } function git(args, capture = false) { return execFileSync("git", args, { cwd: root, encoding: "utf8", stdio: capture ? ["ignore", "pipe", "pipe"] : ["ignore", "ignore", "pipe"], }); } function isGitRepository() { try { return git(["rev-parse", "--is-inside-work-tree"], true).trim() === "true"; } catch { return false; } } function trackedFiles() { try { return git(["ls-files", "-z"], true).split("\0").filter(Boolean); } catch { return []; } } function ensureIgnoreRules() { const path = join(root, ".gitignore"); const current = existsSync(path) ? readFileSync(path, "utf8") : ""; const required = [ ".env", ".env.*", "!.env.example", "!.env.*.example", "focus-shims.d.ts", "tsconfig.focus.json", ]; const lines = new Set(current.split(/\r?\n/).map((line) => line.trim())); const missing = required.filter((line) => !lines.has(line)); if (missing.length === 0) return; if (!dryRun) { const separator = current.length > 0 && !current.endsWith("\n") ? "\n" : ""; writeFileSync(path, `${current}${separator}${missing.join("\n")}\n`); } warnings.push(`Added missing .gitignore rules: ${missing.join(", ")}`); } removeLocal("focus-shims.d.ts"); removeLocal("tsconfig.focus.json"); ensureIgnoreRules(); if (isGitRepository()) { const secretPattern = /(?:^|\/)(?:\.env(?:\..+)?|id_rsa|id_ed25519|.*\.(?:pem|p12|pfx|key))$/i; const safeTemplate = /(?:^|\/)\.env(?:\.[^/]+)*\.(?:example|sample|template)$/i; const temporaryPattern = /(?:^|\/)(?:focus-shims\.d\.ts|tsconfig\.focus\.json)$/i; const unsafeTracked = trackedFiles().filter( (path) => temporaryPattern.test(path) || (secretPattern.test(path) && !safeTemplate.test(path)), ); for (const path of unsafeTracked) { if (!dryRun) git(["rm", "--cached", "--ignore-unmatch", "--", path]); untracked.push(path); } } else { warnings.push( "Git metadata was not found, so tracked secret files could not be removed from the Git index.", ); } console.log(dryRun ? "Workspace repair check:" : "Workspace repaired:"); console.log( `- ${dryRun ? "would remove" : "removed"} temporary files: ${removed.length ? removed.join(", ") : "none"}`, ); console.log( `- ${dryRun ? "would remove" : "removed"} from Git tracking: ${untracked.length ? untracked.join(", ") : "none"}`, ); for (const warning of warnings) console.warn(`- warning: ${warning}`); if (dryRun && (removed.length > 0 || untracked.length > 0)) { console.error("Workspace repair is required. Run: bun run repair:workspace"); process.exitCode = 1; }