From 9352f15fd6a43d589d94df7abc288a17650dcc45 Mon Sep 17 00:00:00 2001 From: time_ Date: Sun, 29 Mar 2026 14:36:10 +0300 Subject: [PATCH] chore: pin npm deps, quiet install, validate exact versions --- client/.npmrc | 3 ++ client/package-lock.json | 36 +++++++-------- client/package.json | 36 +++++++-------- generation/context/dependency-pins.md | 5 +++ package.json | 1 + prompts/validation-rules.md | 1 + server/.npmrc | 3 ++ server/package-lock.json | 64 +++++++++++++-------------- server/package.json | 64 +++++++++++++-------------- tools/pin-package-versions.mjs | 44 ++++++++++++++++++ tools/validate-generation.mjs | 20 +++++++++ 11 files changed, 177 insertions(+), 100 deletions(-) create mode 100644 client/.npmrc create mode 100644 generation/context/dependency-pins.md create mode 100644 server/.npmrc create mode 100644 tools/pin-package-versions.mjs diff --git a/client/.npmrc b/client/.npmrc new file mode 100644 index 0000000..46e9944 --- /dev/null +++ b/client/.npmrc @@ -0,0 +1,3 @@ +save-exact=true +fund=false +audit=false diff --git a/client/package-lock.json b/client/package-lock.json index e60c40f..7469aa8 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -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": { diff --git a/client/package.json b/client/package.json index 2154763..46b9b19 100644 --- a/client/package.json +++ b/client/package.json @@ -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" } } diff --git a/generation/context/dependency-pins.md b/generation/context/dependency-pins.md new file mode 100644 index 0000000..af19525 --- /dev/null +++ b/generation/context/dependency-pins.md @@ -0,0 +1,5 @@ +# Зависимости workspace (зафиксированные версии) + +- Рабочие манифесты: **`server/package.json`**, **`client/package.json`** — прямые зависимости **без** префиксов `^` / `~`; точные версии синхронизированы с **`package-lock.json`** (скрипт `tools/pin-package-versions.mjs`). +- После добавления пакета: `npm install ` в `server/` или `client/` (с `save-exact` в `.npmrc`), затем при необходимости снова **`node tools/pin-package-versions.mjs server|client`** и коммит обоих файлов. +- Генератор **`generation/generate.mjs`** не перезаписывает `package.json`; агенты не должны ослаблять диапазоны версий при правках кода. diff --git a/package.json b/package.json index 56ef58c..724cb0b 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/prompts/validation-rules.md b/prompts/validation-rules.md index 5ea158e..efaaaae 100644 --- a/prompts/validation-rules.md +++ b/prompts/validation-rules.md @@ -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 diff --git a/server/.npmrc b/server/.npmrc new file mode 100644 index 0000000..46e9944 --- /dev/null +++ b/server/.npmrc @@ -0,0 +1,3 @@ +save-exact=true +fund=false +audit=false diff --git a/server/package-lock.json b/server/package-lock.json index 5bf66a1..8074317 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -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": { diff --git a/server/package.json b/server/package.json index 73ac676..5ee21b2 100644 --- a/server/package.json +++ b/server/package.json @@ -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": [ diff --git a/tools/pin-package-versions.mjs b/tools/pin-package-versions.mjs new file mode 100644 index 0000000..9ebf610 --- /dev/null +++ b/tools/pin-package-versions.mjs @@ -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 '); + 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`); diff --git a/tools/validate-generation.mjs b/tools/validate-generation.mjs index 17fde66..f2ace9a 100644 --- a/tools/validate-generation.mjs +++ b/tools/validate-generation.mjs @@ -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() {