Files
Genarrative/scripts/check-server-node-freeze.mjs
2026-04-24 12:21:33 +08:00

128 lines
3.3 KiB
JavaScript

#!/usr/bin/env node
import { createHash } from 'node:crypto';
import { existsSync, readdirSync, readFileSync } from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
const baselinePath = path.join(repoRoot, 'scripts', 'server-node-freeze-baseline.json');
const needle = 'server-node';
const ignoredDirectories = new Set([
'.git',
'.codex',
'.codex-temp',
'.idea',
'node_modules',
'dist',
'build',
'coverage',
'target',
'logs',
]);
const ignoredFiles = new Set([
'scripts/check-server-node-freeze.mjs',
'scripts/server-node-freeze-baseline.json',
'scripts/server-node-frozen.mjs',
'docs/audits/engineering/SERVER_NODE_FREEZE_AND_DEPRECATION_2026-04-24.md',
]);
const allowedExtensions = new Set([
'.cjs',
'.js',
'.json',
'.md',
'.mjs',
'.ps1',
'.rs',
'.toml',
'.ts',
'.tsx',
'.yaml',
'.yml',
]);
function toRepoPath(absolutePath) {
return path.relative(repoRoot, absolutePath).replaceAll(path.sep, '/');
}
function hashLine(line) {
return createHash('sha256').update(line.trim()).digest('hex');
}
function walk(directory, output) {
for (const entry of readdirSync(directory, { withFileTypes: true })) {
if (entry.isDirectory()) {
if (!ignoredDirectories.has(entry.name)) {
walk(path.join(directory, entry.name), output);
}
continue;
}
const absolutePath = path.join(directory, entry.name);
const repoPath = toRepoPath(absolutePath);
if (ignoredFiles.has(repoPath)) {
continue;
}
if (!allowedExtensions.has(path.extname(entry.name).toLowerCase())) {
continue;
}
output.push(absolutePath);
}
}
function collectReferences() {
const files = [];
walk(repoRoot, files);
const references = new Map();
for (const file of files) {
const repoPath = toRepoPath(file);
const content = readFileSync(file, 'utf8');
const lines = content.split(/\r?\n/u);
for (const line of lines) {
if (!line.toLowerCase().includes(needle)) {
continue;
}
const key = `${repoPath}\u0000${hashLine(line)}`;
references.set(key, (references.get(key) || 0) + 1);
}
}
return references;
}
function parseBaseline() {
if (!existsSync(baselinePath)) {
return new Map();
}
const baseline = JSON.parse(readFileSync(baselinePath, 'utf8'));
return new Map(Object.entries(baseline.references || {}));
}
const currentReferences = collectReferences();
const baselineReferences = parseBaseline();
const newReferences = [];
for (const [key, count] of currentReferences.entries()) {
const allowedCount = baselineReferences.get(key) || 0;
if (count > allowedCount) {
const [repoPath] = key.split('\u0000');
newReferences.push({ repoPath, count: count - allowedCount });
}
}
if (newReferences.length > 0) {
console.error('检测到冻结后新增的 server-node 引用,请迁移到 server-rs 或更新废弃审计后再处理:');
for (const reference of newReferences.slice(0, 50)) {
console.error(`- ${reference.repoPath} (+${reference.count})`);
}
if (newReferences.length > 50) {
console.error(`... 另有 ${newReferences.length - 50}`);
}
process.exit(1);
}
console.log('server-node freeze guard passed: 未发现冻结后新增引用。');