Files
brain/tools/registry-render.mjs
T

108 lines
3.9 KiB
JavaScript

// tools/registry-render.mjs
// Pure script — рендерит секции реестра в Markdown auto-region.
// Запуск: node tools/registry-render.mjs [--check]
// --check режим: возвращает exit 1 если rendered ≠ файлу (lefthook job).
import { readFileSync, writeFileSync } from 'fs';
import { join, dirname, resolve } from 'path';
import { fileURLToPath } from 'url';
import { loadRegistry } from './registry-load.mjs';
const __dirname = dirname(fileURLToPath(import.meta.url));
const REPO_ROOT = join(__dirname, '..');
const REGIONS = [
{
file: join(REPO_ROOT, 'docs', 'Tooling_v8_3.md'),
marker: 'tooling-registry-summary',
renderer: renderToolingSummary,
},
{
file: join(REPO_ROOT, 'docs', 'routing-off-phase.md'),
marker: 'routing-table',
renderer: renderRoutingTable,
},
];
function renderToolingSummary(registry) {
const lines = ['<!-- This block is auto-generated from docs/registry/nodes.yaml. Do not edit by hand. -->', ''];
lines.push(`| ID | Узел | Категория | Статус |`);
lines.push(`|---|---|---|---|`);
for (const node of registry.nodes) {
lines.push(`| ${node.id} | ${node.name} | ${node.category} | ${node.status} |`);
}
lines.push('');
return lines.join('\n');
}
function renderRoutingTable(registry) {
const lines = ['<!-- This block is auto-generated from docs/registry/nodes.yaml. Do not edit by hand. -->', ''];
const byClass = new Map();
for (const node of registry.nodes) {
if (node.status !== 'active') continue;
for (const t of node.triggers) {
if (!t.classification) continue;
if (!byClass.has(t.classification)) byClass.set(t.classification, []);
byClass.get(t.classification).push({ node, weight: t.weight ?? 1.0 });
}
}
lines.push(`| Классификация | Рекомендуемый узел | Вес |`);
lines.push(`|---|---|---|`);
for (const [cls, entries] of [...byClass.entries()].sort()) {
entries.sort((a, b) => b.weight - a.weight);
for (const e of entries) {
lines.push(`| \`${cls}\` | ${e.node.id} ${e.node.name} | ${e.weight} |`);
}
}
lines.push('');
return lines.join('\n');
}
export function replaceRegion(content, marker, rendered) {
const start = `<!-- auto:${marker}:begin -->`;
const end = `<!-- auto:${marker}:end -->`;
const startIdx = content.indexOf(start);
const endIdx = content.indexOf(end);
if (startIdx === -1 || endIdx === -1) {
throw new Error(`Markers <!-- auto:${marker}:begin/end --> not found`);
}
const before = content.slice(0, startIdx + start.length);
const after = content.slice(endIdx);
return `${before}\n${rendered}\n${after}`;
}
export function renderAll({ check = false } = {}) {
const registry = loadRegistry();
const results = [];
for (const region of REGIONS) {
const current = readFileSync(region.file, 'utf-8');
const rendered = region.renderer(registry);
const next = replaceRegion(current, region.marker, rendered);
const drift = current !== next;
if (check && drift) {
results.push({ file: region.file, drift: true });
continue;
}
if (!check && drift) {
writeFileSync(region.file, next, 'utf-8');
}
results.push({ file: region.file, drift });
}
return results;
}
// Точка входа CLI
// Используем fileURLToPath + resolve для совместимости с кириллическими путями (Windows quirk)
const __filename = fileURLToPath(import.meta.url);
if (process.argv[1] && resolve(process.argv[1]) === __filename) {
const check = process.argv.includes('--check');
const results = renderAll({ check });
for (const r of results) {
console.log(`${r.drift ? '⚠️ drift' : '✅ ok '} ${r.file}`);
}
if (check && results.some(r => r.drift)) {
console.error('\nDrift detected. Run `node tools/registry-render.mjs` to regenerate.');
process.exit(1);
}
}