import { execFileSync } from 'node:child_process'; import { existsSync, readFileSync } from 'node:fs'; import { fileURLToPath } from 'node:url'; import { relative, resolve } from 'node:path'; const root = resolve(fileURLToPath(new URL('..', import.meta.url))); const failures = []; const warnings = []; const secretTargets = [ 'server.js', 'server', 'src', 'package.json', 'proxy.conf.json', ]; const cloudFallbackFiles = [ 'cloud-functions/06-voiceManager.js', 'cloud-functions/07-quicklyVideo.js', 'cloud-functions/08-proxyHub.js', 'cloud-functions/09-uploadManager.js', 'cloud-functions/11-jimengManager.js', 'cloud-functions/12-douyinManager.js', 'cloud-functions/13-douyinInsightManager.js', ]; const largeFileBudgets = [ { file: 'src/app/app.ts', maxLines: 3000 }, { file: 'src/app/app.css', maxLines: 7000 }, { file: 'server.js', maxLines: 1800 }, { file: 'src/app/pages/pipelines/topic-to-video/topic-to-video.component.ts', maxLines: 1200 }, ]; const secretPatterns = [ { name: 'Bearer r token', pattern: /Bearer\s+r:[A-Za-z0-9_-]{20,}/g }, { name: 'r token', pattern: /['"`]r:[A-Za-z0-9_-]{20,}['"`]/g }, { name: 'sk key', pattern: /['"`]sk-[A-Za-z0-9_-]{20,}['"`]/g }, ]; const contextualSecretPatterns = [ { name: 'long token/key literal', pattern: /\b(token|secret|apiKey|appSecret|accessKey|currentToken)\b[^;\n=:{]*[:=]\s*['"`][A-Za-z0-9+/=_-]{32,}['"`]/gi, }, ]; function runGit(args) { try { return execFileSync('git', args, { cwd: root, encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] }).trim(); } catch { return ''; } } function listTrackedFiles(paths) { const files = runGit(['ls-files', ...paths]); return files ? files.split(/\r?\n/).filter(Boolean) : []; } function read(file) { return readFileSync(resolve(root, file), 'utf8'); } function lineOf(content, index) { return content.slice(0, index).split(/\r?\n/).length; } function formatFile(file) { return relative(root, resolve(root, file)).replace(/\\/g, '/'); } const trackedEnv = runGit(['ls-files', '.env']); if (trackedEnv) { failures.push('根目录 .env 已被 Git 跟踪,请移出版本库,只保留 .env.example。'); } for (const file of listTrackedFiles(secretTargets)) { if (!existsSync(resolve(root, file))) continue; if (file.endsWith('.map') || file.endsWith('.png') || file.endsWith('.jpg') || file.endsWith('.jpeg') || file.endsWith('.webp')) continue; const content = read(file); for (const item of secretPatterns) { for (const match of content.matchAll(item.pattern)) { failures.push(`${formatFile(file)}:${lineOf(content, match.index || 0)} 命中疑似真实凭证:${item.name}`); } } for (const item of contextualSecretPatterns) { for (const match of content.matchAll(item.pattern)) { failures.push(`${formatFile(file)}:${lineOf(content, match.index || 0)} 命中疑似真实凭证:${item.name}`); } } } for (const file of cloudFallbackFiles) { if (!existsSync(resolve(root, file))) continue; const content = read(file); for (const item of secretPatterns) { if (item.pattern.test(content)) { warnings.push(`${formatFile(file)} 保留云函数源码兜底凭证。当前按用户要求暂缓治理,后续确认云函数部署窗口后再处理。`); break; } } } for (const item of largeFileBudgets) { if (!existsSync(resolve(root, item.file))) continue; const count = read(item.file).split(/\r?\n/).length; if (count > item.maxLines) { warnings.push(`${formatFile(item.file)} 当前 ${count} 行,超过治理阈值 ${item.maxLines} 行。`); } } if (warnings.length) { console.warn('\nReview guard warnings:'); for (const warning of warnings) console.warn(`- ${warning}`); } if (failures.length) { console.error('\nReview guard failed:'); for (const failure of failures) console.error(`- ${failure}`); process.exit(1); } console.log('Review guard passed');