#!/usr/bin/env node /** * 校验代码内的契约版本常量与 contracts/VERSION 一致。 * 在构建前执行:不一致则退出码 1,阻止契约漂移。 * * 用法:node scripts/check-contracts.mjs */ import { readFileSync } from 'node:fs'; import { dirname, join } from 'node:path'; import { fileURLToPath } from 'node:url'; const here = dirname(fileURLToPath(import.meta.url)); const versionFile = join(here, '..', '..', 'contracts', 'VERSION'); const constantFile = join(here, '..', 'src', 'config', 'contract-versions.ts'); function parseVersionFile(content) { const out = {}; for (const rawLine of content.split(/\r?\n/)) { const line = rawLine.trim(); if (!line || line.startsWith('#')) continue; const idx = line.indexOf(':'); if (idx <= 0) continue; out[line.slice(0, idx).trim()] = line.slice(idx + 1).trim(); } return out; } function parseConstants(content) { const block = /CONTRACT_VERSIONS[^=]*=\s*Object\.freeze\(\{([\s\S]*?)\}\)/.exec(content); if (!block) throw new Error('无法在 contract-versions.ts 中定位 CONTRACT_VERSIONS'); const out = {}; // 键可能是带引号的 'rest-api',也可能是不带引号的 database for (const match of block[1].matchAll(/['"]?([\w-]+)['"]?\s*:\s*['"]([^'"]+)['"]/g)) { out[match[1]] = match[2]; } return out; } let fileVersions; let codeVersions; try { fileVersions = parseVersionFile(readFileSync(versionFile, 'utf8')); } catch (error) { console.error(`✗ 无法读取 ${versionFile}:${error.message}`); process.exit(1); } try { codeVersions = parseConstants(readFileSync(constantFile, 'utf8')); } catch (error) { console.error(`✗ 无法解析 ${constantFile}:${error.message}`); process.exit(1); } const keys = new Set([...Object.keys(fileVersions), ...Object.keys(codeVersions)]); const problems = []; for (const key of [...keys].sort()) { const a = fileVersions[key]; const b = codeVersions[key]; if (a === undefined) problems.push(` ${key}: 仅存在于代码常量(${b})`); else if (b === undefined) problems.push(` ${key}: 仅存在于 contracts/VERSION(${a})`); else if (a !== b) problems.push(` ${key}: contracts/VERSION=${a} vs 代码常量=${b}`); } if (problems.length > 0) { console.error('✗ 契约版本不一致:'); console.error(problems.join('\n')); console.error('\n请同步 contracts/VERSION 与 web-server/src/config/contract-versions.ts'); process.exit(1); } console.log(`✓ 契约版本一致(${Object.keys(fileVersions).length} 项):${JSON.stringify(fileVersions)}`);