import {existsSync, readFileSync} from 'node:fs'; import {createRequire} from 'node:module'; import {dirname, join, resolve} from 'node:path'; import {fileURLToPath} from 'node:url'; const root=resolve(dirname(fileURLToPath(import.meta.url)),'..'); /** Runtime packages that must match package-lock.json when building or deploying. */ export const LOCKED_RUNTIME=['next','react','react-dom','dotenv','hls.js','qrcode']; export function packageLockVersions(rootDir=root) { const lock=JSON.parse(readFileSync(join(rootDir,'package-lock.json'),'utf8')); const versions={}; for(const name of LOCKED_RUNTIME){ const entry=lock.packages?.[`node_modules/${name}`]; if(!entry?.version)throw new Error(`package-lock.json 缺少 ${name}`); versions[name]=entry.version; } return versions; } export function installedVersions(rootDir=root,names=LOCKED_RUNTIME) { const require=createRequire(join(rootDir,'package.json')); const versions={}; for(const name of names){ try{ versions[name]=require(`${name}/package.json`).version; }catch{ throw new Error(`未安装 ${name},请先执行 npm ci 或 pnpm install`); } } return versions; } /** Best-effort read of direct dependency versions from pnpm-lock.yaml importers. */ export function pnpmLockVersions(rootDir=root) { const file=join(rootDir,'pnpm-lock.yaml'); if(!existsSync(file))return null; const text=readFileSync(file,'utf8'); const versions={}; for(const name of LOCKED_RUNTIME){ const escaped=name.replace(/[.*+?^${}()|[\]\\]/g,'\\$&'); const match=text.match(new RegExp(`(?:^|\\n)\\s{6}${escaped}:\\n(?:\\s{8}.*\\n)*?\\s{8}version:\\s+(\\S+)`)); if(!match)throw new Error(`pnpm-lock.yaml 缺少 ${name}`); versions[name]=match[1].replace(/\(.*$/,''); } return versions; } export function checkLockSync(rootDir=root) { const expected=packageLockVersions(rootDir); const installed=installedVersions(rootDir); const mismatches=[]; for(const name of LOCKED_RUNTIME){ if(installed[name]!==expected[name])mismatches.push(`${name}: 已安装 ${installed[name]},package-lock.json 为 ${expected[name]}`); } const pnpm=pnpmLockVersions(rootDir); if(pnpm){ for(const name of LOCKED_RUNTIME){ if(pnpm[name]!==expected[name])mismatches.push(`${name}: pnpm-lock.yaml 为 ${pnpm[name]},package-lock.json 为 ${expected[name]}`); } } if(mismatches.length){ throw new Error(`依赖版本不一致(发版以 package-lock.json / npm ci 为准):\n- ${mismatches.join('\n- ')}\n请用同一版本同步:npm install 与 pnpm install,或将 next 等依赖钉死后再更新两份 lock。`); } return {expected,installed,pnpm}; } if(process.argv[1]&&resolve(process.argv[1])===fileURLToPath(import.meta.url)) { try { const result=checkLockSync(); console.log(`lock sync ok (npm${result.pnpm?'+pnpm':''}): next@${result.expected.next}`); }catch(error) { console.error(error instanceof Error?error.message:error); process.exitCode=1; } }