#!/usr/bin/env node /** * 版本号一致性校验(文档 §11.2「单一来源 / 可核对」)。 * * 守护的缺陷:`/health` 曾经用 * process.env.npm_package_version ?? '0.1.0' * 而正式部署方式是原生服务(`ExecStart=node dist/main.js`),npm 不参与, * 该环境变量为 undefined —— 于是真实部署里 `/health` 永远报 **0.1.0**, * 与管理端左下角展示的 vX.Y.Z 不一致。 * * 校验项: * 1. web-server/package.json 的 version === project.yaml 的 version * 2. web-server/admin/package.json 的 version === web-server/package.json 的 version * 3. src/config/app-version.ts 存在,且 health.controller 通过它取版本 * 4. health.controller 里不得再出现硬编码的版本回退(如 `?? '0.1.0'`) * 5. 若管理端已构建(web-server/admin/dist 存在),其 JS 包内必须含 `v` * * 用法:node scripts/check-version.mjs */ import { existsSync, readFileSync, readdirSync } from 'node:fs'; import { dirname, join } from 'node:path'; import { fileURLToPath } from 'node:url'; const here = dirname(fileURLToPath(import.meta.url)); const serverDir = join(here, '..'); const repoDir = join(serverDir, '..'); const problems = []; const notes = []; function readJson(file) { try { return JSON.parse(readFileSync(file, 'utf8')); } catch (error) { problems.push(`无法解析 ${file}:${error.message}`); return null; } } function readYamlVersion(file) { if (!existsSync(file)) { problems.push(`缺少 ${file}`); return null; } const line = readFileSync(file, 'utf8') .split(/\r?\n/) .find((l) => l.trimStart().startsWith('version:')); if (!line) { problems.push(`无法在 ${file} 中定位 version:`); return null; } return line.slice(line.indexOf(':') + 1).trim(); } // ---- 1. package.json 与 project.yaml ---- const serverPkg = readJson(join(serverDir, 'package.json')); const yamlVersion = readYamlVersion(join(repoDir, 'project.yaml')); if (serverPkg && yamlVersion && serverPkg.version !== yamlVersion) { problems.push(`版本不一致:web-server/package.json=${serverPkg.version} vs project.yaml=${yamlVersion}`); } // ---- 2. 管理端 package.json 与之同源 ---- const adminPkgPath = join(serverDir, 'admin', 'package.json'); if (existsSync(adminPkgPath)) { const adminPkg = readJson(adminPkgPath); if (serverPkg && adminPkg && adminPkg.version !== serverPkg.version) { problems.push( `版本不一致:web-server/admin/package.json=${adminPkg.version} vs web-server/package.json=${serverPkg.version}`, ); } } // ---- 3 & 4. /health 的版本必须来自 package.json,不能再硬编码 ---- const healthPath = join(serverDir, 'src', 'health', 'health.controller.ts'); const versionModulePath = join(serverDir, 'src', 'config', 'app-version.ts'); if (!existsSync(versionModulePath)) { problems.push('缺少 src/config/app-version.ts(版本号运行时唯一解析点)'); } if (!existsSync(healthPath)) { problems.push('缺少 src/health/health.controller.ts'); } else { const health = readFileSync(healthPath, 'utf8'); if (!health.includes('resolveAppVersion')) { problems.push('health.controller.ts 未使用 resolveAppVersion(),/health 的版本可能不是单一来源'); } // 硬编码版本回退:`?? 'x.y.z'` 或 `|| "x.y.z"` const hardcoded = health.match(/\?\?\s*['"]\d+\.\d+\.\d+['"]|\|\|\s*['"]\d+\.\d+\.\d+['"]/); if (hardcoded) { problems.push( `health.controller.ts 存在硬编码版本回退 ${hardcoded[0]};应使用 resolveAppVersion()(否则原生部署下 /health 会报错版本)`, ); } } // ---- 5. 管理端构建产物必须带上与 package.json 相同的版本 ---- const adminDist = join(serverDir, 'admin', 'dist', 'assets'); if (serverPkg && existsSync(adminDist)) { const files = readdirSync(adminDist).filter((f) => f.endsWith('.js')); const found = new Set(); for (const file of files) { const text = readFileSync(join(adminDist, file), 'utf8'); for (const m of text.matchAll(/v\d+\.\d+\.\d+/g)) found.add(m[0]); } const expected = `v${serverPkg.version}`; if (found.size === 0) { problems.push(`管理端构建产物中找不到任何 vX.Y.Z(期望 ${expected})`); } else if (!found.has(expected)) { problems.push(`管理端构建产物中的版本 ${[...found].join('/')} 与 package.json 的 ${expected} 不一致`); } else { notes.push(`管理端构建产物含 ${expected}`); } } else { notes.push('管理端尚未构建,跳过产物内版本校验(打包脚本会在构建后再次执行本脚本)'); } if (problems.length > 0) { console.error('✗ 版本一致性校验失败:'); for (const p of problems) console.error(` - ${p}`); console.error('\n请同步 web-server/package.json、web-server/admin/package.json、project.yaml;'); console.error('/health 的版本必须来自 src/config/app-version.ts(读 package.json),不得硬编码。'); process.exit(1); } for (const n of notes) console.log(` · ${n}`); console.log( `✓ 版本一致(${serverPkg?.version}):web-server/package.json = admin/package.json = project.yaml,` + `/health 由 resolveAppVersion() 读取 package.json`, );