Redact current user in comments and token checks
Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
@@ -7,61 +7,109 @@
|
||||
* working tree (not just staged changes).
|
||||
*
|
||||
* Token list: .git/hooks/forbidden-tokens.txt (one per line, # comments ok).
|
||||
* If the file is missing, the check passes silently — other developers
|
||||
* on the project won't have this list, and that's fine.
|
||||
* If the file is missing, the check still uses the active local username when
|
||||
* available. If username detection fails, the check degrades gracefully.
|
||||
*/
|
||||
|
||||
import { execSync } from "node:child_process";
|
||||
import { existsSync, readFileSync } from "node:fs";
|
||||
import os from "node:os";
|
||||
import { resolve } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
const repoRoot = execSync("git rev-parse --show-toplevel", { encoding: "utf8" }).trim();
|
||||
const gitDir = execSync("git rev-parse --git-dir", { encoding: "utf8", cwd: repoRoot }).trim();
|
||||
const tokensFile = resolve(repoRoot, gitDir, "hooks/forbidden-tokens.txt");
|
||||
|
||||
if (!existsSync(tokensFile)) {
|
||||
console.log(" ℹ Forbidden tokens list not found — skipping check.");
|
||||
process.exit(0);
|
||||
function uniqueNonEmpty(values) {
|
||||
return Array.from(new Set(values.map((value) => value?.trim() ?? "").filter(Boolean)));
|
||||
}
|
||||
|
||||
const tokens = readFileSync(tokensFile, "utf8")
|
||||
.split("\n")
|
||||
.map((l) => l.trim())
|
||||
.filter((l) => l && !l.startsWith("#"));
|
||||
export function resolveDynamicForbiddenTokens(env = process.env, osModule = os) {
|
||||
const candidates = [env.USER, env.LOGNAME, env.USERNAME];
|
||||
|
||||
if (tokens.length === 0) {
|
||||
console.log(" ℹ Forbidden tokens list is empty — skipping check.");
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
// Use git grep to search tracked files only (avoids node_modules, dist, etc.)
|
||||
let found = false;
|
||||
|
||||
for (const token of tokens) {
|
||||
try {
|
||||
const result = execSync(
|
||||
`git grep -in --no-color -- ${JSON.stringify(token)} -- ':!pnpm-lock.yaml' ':!.git'`,
|
||||
{ encoding: "utf8", cwd: repoRoot, stdio: ["pipe", "pipe", "pipe"] },
|
||||
);
|
||||
if (result.trim()) {
|
||||
if (!found) {
|
||||
console.error("ERROR: Forbidden tokens found in tracked files:\n");
|
||||
}
|
||||
found = true;
|
||||
// Print matches but DO NOT print which token was matched (avoids leaking the list)
|
||||
const lines = result.trim().split("\n");
|
||||
for (const line of lines) {
|
||||
console.error(` ${line}`);
|
||||
}
|
||||
}
|
||||
candidates.push(osModule.userInfo().username);
|
||||
} catch {
|
||||
// git grep returns exit code 1 when no matches — that's fine
|
||||
// Some environments do not expose userInfo; env vars are enough fallback.
|
||||
}
|
||||
|
||||
return uniqueNonEmpty(candidates);
|
||||
}
|
||||
|
||||
if (found) {
|
||||
console.error("\nBuild blocked. Remove the forbidden token(s) before publishing.");
|
||||
process.exit(1);
|
||||
} else {
|
||||
console.log(" ✓ No forbidden tokens found.");
|
||||
export function readForbiddenTokensFile(tokensFile) {
|
||||
if (!existsSync(tokensFile)) return [];
|
||||
|
||||
return readFileSync(tokensFile, "utf8")
|
||||
.split("\n")
|
||||
.map((line) => line.trim())
|
||||
.filter((line) => line && !line.startsWith("#"));
|
||||
}
|
||||
|
||||
export function resolveForbiddenTokens(tokensFile, env = process.env, osModule = os) {
|
||||
return uniqueNonEmpty([
|
||||
...resolveDynamicForbiddenTokens(env, osModule),
|
||||
...readForbiddenTokensFile(tokensFile),
|
||||
]);
|
||||
}
|
||||
|
||||
export function runForbiddenTokenCheck({
|
||||
repoRoot,
|
||||
tokens,
|
||||
exec = execSync,
|
||||
log = console.log,
|
||||
error = console.error,
|
||||
}) {
|
||||
if (tokens.length === 0) {
|
||||
log(" ℹ Forbidden tokens list is empty — skipping check.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
let found = false;
|
||||
|
||||
for (const token of tokens) {
|
||||
try {
|
||||
const result = exec(
|
||||
`git grep -in --no-color -- ${JSON.stringify(token)} -- ':!pnpm-lock.yaml' ':!.git'`,
|
||||
{ encoding: "utf8", cwd: repoRoot, stdio: ["pipe", "pipe", "pipe"] },
|
||||
);
|
||||
if (result.trim()) {
|
||||
if (!found) {
|
||||
error("ERROR: Forbidden tokens found in tracked files:\n");
|
||||
}
|
||||
found = true;
|
||||
const lines = result.trim().split("\n");
|
||||
for (const line of lines) {
|
||||
error(` ${line}`);
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// git grep returns exit code 1 when no matches — that's fine
|
||||
}
|
||||
}
|
||||
|
||||
if (found) {
|
||||
error("\nBuild blocked. Remove the forbidden token(s) before publishing.");
|
||||
return 1;
|
||||
}
|
||||
|
||||
log(" ✓ No forbidden tokens found.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
function resolveRepoPaths(exec = execSync) {
|
||||
const repoRoot = exec("git rev-parse --show-toplevel", { encoding: "utf8" }).trim();
|
||||
const gitDir = exec("git rev-parse --git-dir", { encoding: "utf8", cwd: repoRoot }).trim();
|
||||
return {
|
||||
repoRoot,
|
||||
tokensFile: resolve(repoRoot, gitDir, "hooks/forbidden-tokens.txt"),
|
||||
};
|
||||
}
|
||||
|
||||
function main() {
|
||||
const { repoRoot, tokensFile } = resolveRepoPaths();
|
||||
const tokens = resolveForbiddenTokens(tokensFile);
|
||||
process.exit(runForbiddenTokenCheck({ repoRoot, tokens }));
|
||||
}
|
||||
|
||||
const isMainModule = process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.url);
|
||||
|
||||
if (isMainModule) {
|
||||
main();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user