77 lines
3.0 KiB
JavaScript
77 lines
3.0 KiB
JavaScript
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;
|
||
}
|
||
}
|