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

Made-with: Cursor
This commit is contained in:
time_
2026-03-29 14:19:31 +03:00
parent f6cdeec918
commit a9e43c89d9
11 changed files with 177 additions and 100 deletions

3
client/.npmrc Normal file
View File

@@ -0,0 +1,3 @@
save-exact=true
fund=false
audit=false

View File

@@ -8,26 +8,26 @@
"name": "client",
"version": "0.0.0",
"dependencies": {
"@emotion/react": "^11.14.0",
"@emotion/styled": "^11.14.1",
"@mui/material": "^7.3.9",
"keycloak-js": "^26.2.3",
"ra-data-simple-rest": "^5.14.4",
"react": "^18.2.0",
"react-admin": "^5.14.4",
"react-dom": "^18.2.0"
"@emotion/react": "11.14.0",
"@emotion/styled": "11.14.1",
"@mui/material": "7.3.9",
"keycloak-js": "26.2.3",
"ra-data-simple-rest": "5.14.4",
"react": "18.3.1",
"react-admin": "5.14.4",
"react-dom": "18.3.1"
},
"devDependencies": {
"@types/react": "^18.2.55",
"@types/react-dom": "^18.2.19",
"@typescript-eslint/eslint-plugin": "^6.21.0",
"@typescript-eslint/parser": "^6.21.0",
"@vitejs/plugin-react": "^4.2.1",
"eslint": "^8.56.0",
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-react-refresh": "^0.4.5",
"typescript": "^5.2.2",
"vite": "^5.1.0"
"@types/react": "18.3.28",
"@types/react-dom": "18.3.7",
"@typescript-eslint/eslint-plugin": "6.21.0",
"@typescript-eslint/parser": "6.21.0",
"@vitejs/plugin-react": "4.7.0",
"eslint": "8.57.1",
"eslint-plugin-react-hooks": "4.6.2",
"eslint-plugin-react-refresh": "0.4.26",
"typescript": "5.9.3",
"vite": "5.4.21"
}
},
"node_modules/@babel/code-frame": {

View File

@@ -10,25 +10,25 @@
"preview": "vite preview"
},
"dependencies": {
"@emotion/react": "^11.14.0",
"@emotion/styled": "^11.14.1",
"@mui/material": "^7.3.9",
"keycloak-js": "^26.2.3",
"ra-data-simple-rest": "^5.14.4",
"react": "^18.2.0",
"react-admin": "^5.14.4",
"react-dom": "^18.2.0"
"@emotion/react": "11.14.0",
"@emotion/styled": "11.14.1",
"@mui/material": "7.3.9",
"keycloak-js": "26.2.3",
"ra-data-simple-rest": "5.14.4",
"react": "18.3.1",
"react-admin": "5.14.4",
"react-dom": "18.3.1"
},
"devDependencies": {
"@types/react": "^18.2.55",
"@types/react-dom": "^18.2.19",
"@typescript-eslint/eslint-plugin": "^6.21.0",
"@typescript-eslint/parser": "^6.21.0",
"@vitejs/plugin-react": "^4.2.1",
"eslint": "^8.56.0",
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-react-refresh": "^0.4.5",
"typescript": "^5.2.2",
"vite": "^5.1.0"
"@types/react": "18.3.28",
"@types/react-dom": "18.3.7",
"@typescript-eslint/eslint-plugin": "6.21.0",
"@typescript-eslint/parser": "6.21.0",
"@vitejs/plugin-react": "4.7.0",
"eslint": "8.57.1",
"eslint-plugin-react-hooks": "4.6.2",
"eslint-plugin-react-refresh": "0.4.26",
"typescript": "5.9.3",
"vite": "5.4.21"
}
}

View File

@@ -0,0 +1,5 @@
# Зависимости workspace (зафиксированные версии)
- Рабочие манифесты: **`server/package.json`**, **`client/package.json`** — прямые зависимости **без** префиксов `^` / `~`; точные версии синхронизированы с **`package-lock.json`** (скрипт `tools/pin-package-versions.mjs`).
- После добавления пакета: `npm install <pkg>` в `server/` или `client/` (с `save-exact` в `.npmrc`), затем при необходимости снова **`node tools/pin-package-versions.mjs server|client`** и коммит обоих файлов.
- Генератор **`generation/generate.mjs`** не перезаписывает `package.json`; агенты не должны ослаблять диапазоны версий при правках кода.

View File

@@ -2,6 +2,7 @@
"name": "toir-generation-context",
"private": true,
"scripts": {
"pin:deps": "node tools/pin-package-versions.mjs server && node tools/pin-package-versions.mjs client",
"generate:domain-summary": "node tools/generate-domain-summary.mjs",
"validate:generation": "node tools/validate-generation.mjs",
"validate:generation:runtime": "node tools/validate-generation.mjs --run-runtime",

View File

@@ -30,6 +30,7 @@ Validation is now a lightweight automated gate instead of a prose-only checklist
- generation must not pass validation if framework scaffolding files were deleted and replaced by a hand-written minimal skeleton
- if dependencies are installed, build verification runs for `server/` and `client/`
- if dependencies are missing, build verification is reported as skipped with reason instead of green
- `server/package.json` and `client/package.json`: every entry in `dependencies`, `devDependencies`, `optionalDependencies`, and `peerDependencies` must use an exact version string (no `^` or `~` prefix)
### Auth checks

3
server/.npmrc Normal file
View File

@@ -0,0 +1,3 @@
save-exact=true
fund=false
audit=false

View File

@@ -10,40 +10,40 @@
"hasInstallScript": true,
"license": "UNLICENSED",
"dependencies": {
"@nestjs/common": "^10.0.0",
"@nestjs/config": "^4.0.3",
"@nestjs/core": "^10.0.0",
"@nestjs/platform-express": "^10.0.0",
"@prisma/client": "^5.22.0",
"class-transformer": "^0.5.1",
"class-validator": "^0.14.1",
"jose": "^6.2.2",
"reflect-metadata": "^0.2.0",
"rxjs": "^7.8.1"
"@nestjs/common": "10.4.22",
"@nestjs/config": "4.0.3",
"@nestjs/core": "10.4.22",
"@nestjs/platform-express": "10.4.22",
"@prisma/client": "5.22.0",
"class-transformer": "0.5.1",
"class-validator": "0.14.4",
"jose": "6.2.2",
"reflect-metadata": "0.2.2",
"rxjs": "7.8.2"
},
"devDependencies": {
"@nestjs/cli": "^10.0.0",
"@nestjs/schematics": "^10.0.0",
"@nestjs/testing": "^10.0.0",
"@types/express": "^5.0.0",
"@types/jest": "^29.5.2",
"@types/node": "^20.3.1",
"@types/supertest": "^6.0.0",
"@typescript-eslint/eslint-plugin": "^8.0.0",
"@typescript-eslint/parser": "^8.0.0",
"eslint": "^8.0.0",
"eslint-config-prettier": "^9.0.0",
"eslint-plugin-prettier": "^5.0.0",
"jest": "^29.5.0",
"prettier": "^3.0.0",
"prisma": "^5.22.0",
"source-map-support": "^0.5.21",
"supertest": "^7.0.0",
"ts-jest": "^29.1.0",
"ts-loader": "^9.4.3",
"ts-node": "^10.9.2",
"tsconfig-paths": "^4.2.0",
"typescript": "^5.1.3"
"@nestjs/cli": "10.4.9",
"@nestjs/schematics": "10.2.3",
"@nestjs/testing": "10.4.22",
"@types/express": "5.0.6",
"@types/jest": "29.5.14",
"@types/node": "20.19.37",
"@types/supertest": "6.0.3",
"@typescript-eslint/eslint-plugin": "8.57.0",
"@typescript-eslint/parser": "8.57.0",
"eslint": "8.57.1",
"eslint-config-prettier": "9.1.2",
"eslint-plugin-prettier": "5.5.5",
"jest": "29.7.0",
"prettier": "3.8.1",
"prisma": "5.22.0",
"source-map-support": "0.5.21",
"supertest": "7.2.2",
"ts-jest": "29.4.6",
"ts-loader": "9.5.4",
"ts-node": "10.9.2",
"tsconfig-paths": "4.2.0",
"typescript": "5.9.3"
}
},
"node_modules/@angular-devkit/core": {

View File

@@ -26,40 +26,40 @@
"seed": "ts-node prisma/seed.ts"
},
"dependencies": {
"@nestjs/common": "^10.0.0",
"@nestjs/config": "^4.0.3",
"@nestjs/core": "^10.0.0",
"@nestjs/platform-express": "^10.0.0",
"@prisma/client": "^5.22.0",
"class-transformer": "^0.5.1",
"class-validator": "^0.14.1",
"jose": "^6.2.2",
"reflect-metadata": "^0.2.0",
"rxjs": "^7.8.1"
"@nestjs/common": "10.4.22",
"@nestjs/config": "4.0.3",
"@nestjs/core": "10.4.22",
"@nestjs/platform-express": "10.4.22",
"@prisma/client": "5.22.0",
"class-transformer": "0.5.1",
"class-validator": "0.14.4",
"jose": "6.2.2",
"reflect-metadata": "0.2.2",
"rxjs": "7.8.2"
},
"devDependencies": {
"@nestjs/cli": "^10.0.0",
"@nestjs/schematics": "^10.0.0",
"@nestjs/testing": "^10.0.0",
"@types/express": "^5.0.0",
"@types/jest": "^29.5.2",
"@types/node": "^20.3.1",
"@types/supertest": "^6.0.0",
"@typescript-eslint/eslint-plugin": "^8.0.0",
"@typescript-eslint/parser": "^8.0.0",
"eslint": "^8.0.0",
"eslint-config-prettier": "^9.0.0",
"eslint-plugin-prettier": "^5.0.0",
"jest": "^29.5.0",
"prettier": "^3.0.0",
"prisma": "^5.22.0",
"source-map-support": "^0.5.21",
"supertest": "^7.0.0",
"ts-jest": "^29.1.0",
"ts-loader": "^9.4.3",
"ts-node": "^10.9.2",
"tsconfig-paths": "^4.2.0",
"typescript": "^5.1.3"
"@nestjs/cli": "10.4.9",
"@nestjs/schematics": "10.2.3",
"@nestjs/testing": "10.4.22",
"@types/express": "5.0.6",
"@types/jest": "29.5.14",
"@types/node": "20.19.37",
"@types/supertest": "6.0.3",
"@typescript-eslint/eslint-plugin": "8.57.0",
"@typescript-eslint/parser": "8.57.0",
"eslint": "8.57.1",
"eslint-config-prettier": "9.1.2",
"eslint-plugin-prettier": "5.5.5",
"jest": "29.7.0",
"prettier": "3.8.1",
"prisma": "5.22.0",
"source-map-support": "0.5.21",
"supertest": "7.2.2",
"ts-jest": "29.4.6",
"ts-loader": "9.5.4",
"ts-node": "10.9.2",
"tsconfig-paths": "4.2.0",
"typescript": "5.9.3"
},
"jest": {
"moduleFileExtensions": [

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() {