chore: pin npm deps, quiet install, validate exact versions

This commit is contained in:
time_
2026-03-29 14:36:10 +03:00
parent 1cdd80f51b
commit 9352f15fd6
11 changed files with 177 additions and 100 deletions

View File

@@ -0,0 +1,44 @@
/**
* ╨Ч╨░╨┐╨╕╤Б╤Л╨▓╨░╨╡╤В ╨▓ package.json ╤В╨╛╤З╨╜╤Л╨╡ ╨▓╨╡╤А╤Б╨╕╨╕ ╨┐╤А╤П╨╝╤Л╤Е ╨╖╨░╨▓╨╕╤Б╨╕╨╝╨╛╤Б╤В╨╡╨╣ ╨╕╨╖ package-lock.json (lockfile v3).
* ╨Ш╤Б╨┐╨╛╨╗╤М╨╖╨╛╨▓╨░╨╜╨╕╨╡: node tools/pin-package-versions.mjs server
* node tools/pin-package-versions.mjs client
*/
import fs from 'node:fs';
import path from 'node:path';
const dir = process.argv[2];
if (!dir || !['server', 'client'].includes(dir)) {
console.error('Usage: node tools/pin-package-versions.mjs <server|client>');
process.exit(1);
}
const root = path.resolve(process.cwd(), dir);
const lockPath = path.join(root, 'package-lock.json');
const pkgPath = path.join(root, 'package.json');
if (!fs.existsSync(lockPath)) {
console.error(`Missing ${lockPath}. Run npm install in ${dir}/ first.`);
process.exit(1);
}
const lock = JSON.parse(fs.readFileSync(lockPath, 'utf8'));
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
function pinSection(section) {
if (!section) return;
for (const name of Object.keys(section)) {
const lockKey = `node_modules/${name}`;
const entry = lock.packages?.[lockKey];
if (!entry?.version) {
console.error(`Missing lock entry for ${lockKey}`);
process.exit(1);
}
section[name] = entry.version;
}
}
pinSection(pkg.dependencies);
pinSection(pkg.devDependencies);
fs.writeFileSync(pkgPath, `${JSON.stringify(pkg, null, 2)}\n`);
console.log(`Pinned ${dir}/package.json from package-lock.json`);

View File

@@ -181,6 +181,26 @@ function validateBuildChecks() {
assertCondition(Boolean(clientPackage.devDependencies?.vite), 'client/package.json must keep Vite as a dev dependency');
assertCondition(Boolean(clientPackage.devDependencies?.['@vitejs/plugin-react']), 'client/package.json must keep @vitejs/plugin-react as a dev dependency');
}
validatePinnedPackageJsonVersions();
}
function validatePinnedPackageJsonVersions() {
for (const rel of ['server/package.json', 'client/package.json']) {
const pkg = parseJson(rel);
if (!pkg) continue;
for (const section of ['dependencies', 'devDependencies', 'optionalDependencies', 'peerDependencies']) {
const deps = pkg[section];
if (!deps || typeof deps !== 'object') continue;
for (const [name, ver] of Object.entries(deps)) {
if (typeof ver !== 'string') continue;
assertCondition(
!/^[\^~]/.test(ver),
`${rel} ${section}.${name} must be pinned (no ^ or ~): got ${ver}`,
);
}
}
}
}
function validateAuthChecks() {