From 230e44929c18f1988bfcb3a5367c45a0fa59faaf Mon Sep 17 00:00:00 2001 From: razyd Date: Mon, 12 Oct 2026 22:17:32 +0200 Subject: [PATCH 01/44] test(errors): skip CLI scripts when collecting backend error codes - Add scripts/ to the directories the error-code scan skips: it walks the disk, not what git tracks, so the git-ignored scripts/update-fixture-expected.ts made it fail locally with its shift codes (PI, IT, FO) read as error codes - No tracked script sends error codes; they are CLI tools, not API responses, so the scan loses nothing --- backend/tests/frontend/error-code-message.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/tests/frontend/error-code-message.test.ts b/backend/tests/frontend/error-code-message.test.ts index e97c222..95a58b7 100644 --- a/backend/tests/frontend/error-code-message.test.ts +++ b/backend/tests/frontend/error-code-message.test.ts @@ -9,11 +9,11 @@ import { errorCodeMessage, } from '../../../frontend/app/lib/helpers/errorCodeMessage.js' -// Every ERROR_CODES.X the backend source mentions (tests and the catalogue aside), +// Every ERROR_CODES.X the backend source mentions (tests, CLI scripts and the catalogue aside), // plus any code written as a bare string (`code: 'X'`) function backendErrorCodes(): string[] { const root = resolve(__dirname, '../..') - const skip = new Set(['node_modules', 'tests', 'scheduling-solver', 'db-mysql']) + const skip = new Set(['node_modules', 'tests', 'scheduling-solver', 'db-mysql', 'scripts']) const codes = new Set() const walk = (dir: string) => { for (const entry of readdirSync(dir, { withFileTypes: true })) { From f934b4d72e2ba3088d4cb07cf9c778af20c6a710 Mon Sep 17 00:00:00 2001 From: razyd Date: Mon, 12 Oct 2026 22:18:09 +0200 Subject: [PATCH 02/44] build: pin the Node major per project to what production runs - Add backend/.nvmrc (22) and frontend/.nvmrc (24): fnm only reads the version file in the current directory, so both projects fell back to the open engines range and picked the newest installed Node (24) locally - Set engines.node to 22.x in backend and 24.x in frontend: Render builds the backend on 22.23.3 and Vercel builds the frontend on 24.x; the open >=22.16.0 range let Vercel auto-upgrade to the next major - Read each CI job's Node version from its project's .nvmrc: CI tested the frontend on 22 while production builds it on 24 --- .github/workflows/ci.yml | 4 ++-- backend/.nvmrc | 1 + backend/package.json | 2 +- frontend/.nvmrc | 1 + frontend/package.json | 2 +- 5 files changed, 6 insertions(+), 4 deletions(-) create mode 100644 backend/.nvmrc create mode 100644 frontend/.nvmrc diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8a66f85..5f952ff 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -21,7 +21,7 @@ jobs: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: - node-version: 22 + node-version-file: backend/.nvmrc - run: corepack enable - run: pnpm install --frozen-lockfile # tests/frontend loads pdf-lib from the frontend's node_modules @@ -41,7 +41,7 @@ jobs: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: - node-version: 22 + node-version-file: frontend/.nvmrc - run: corepack enable - run: pnpm install --frozen-lockfile - run: pnpm lint diff --git a/backend/.nvmrc b/backend/.nvmrc new file mode 100644 index 0000000..2bd5a0a --- /dev/null +++ b/backend/.nvmrc @@ -0,0 +1 @@ +22 diff --git a/backend/package.json b/backend/package.json index b4a00ff..1a2a9bf 100644 --- a/backend/package.json +++ b/backend/package.json @@ -28,7 +28,7 @@ "license": "ISC", "packageManager": "pnpm@10.13.1", "engines": { - "node": ">=22.16.0" + "node": "22.x" }, "dependencies": { "@types/pdf-parse": "^1.1.5", diff --git a/frontend/.nvmrc b/frontend/.nvmrc new file mode 100644 index 0000000..a45fd52 --- /dev/null +++ b/frontend/.nvmrc @@ -0,0 +1 @@ +24 diff --git a/frontend/package.json b/frontend/package.json index 740a666..1e8fa02 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -2,7 +2,7 @@ "private": true, "packageManager": "pnpm@10.28.0", "engines": { - "node": ">=22.16.0" + "node": "24.x" }, "scripts": { "build": "next build", From 53fe0280dd83af51791e6e9cc5ebefe1b1b7dfd7 Mon Sep 17 00:00:00 2001 From: razyd Date: Mon, 12 Oct 2026 22:18:29 +0200 Subject: [PATCH 03/44] build(backend): upgrade pnpm to 10.28.0 and hold new releases for 7 days - Add backend/pnpm-workspace.yaml with minimumReleaseAge: 10080, so a dependency version published less than 7 days ago is never resolved: most compromised releases are pulled from the registry well within that window - Bump packageManager from pnpm 10.13.1 to 10.28.0, the same as the frontend: minimumReleaseAge exists since pnpm 10.16 and an older pnpm ignores it without any warning - The lockfile is unchanged: a frozen install accepts it as is --- backend/package.json | 2 +- backend/pnpm-workspace.yaml | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) create mode 100644 backend/pnpm-workspace.yaml diff --git a/backend/package.json b/backend/package.json index 1a2a9bf..e51ac66 100644 --- a/backend/package.json +++ b/backend/package.json @@ -26,7 +26,7 @@ "keywords": [], "author": "", "license": "ISC", - "packageManager": "pnpm@10.13.1", + "packageManager": "pnpm@10.28.0", "engines": { "node": "22.x" }, diff --git a/backend/pnpm-workspace.yaml b/backend/pnpm-workspace.yaml new file mode 100644 index 0000000..a8fca7b --- /dev/null +++ b/backend/pnpm-workspace.yaml @@ -0,0 +1,3 @@ +# Do not install a version published less than 7 days ago (minutes). +# Needs pnpm >= 10.16; ignored silently below that. +minimumReleaseAge: 10080 From 2f2f1d4defe8a47209ee9eebaa52d3971e5eb7a6 Mon Sep 17 00:00:00 2001 From: razyd Date: Mon, 12 Oct 2026 22:18:38 +0200 Subject: [PATCH 04/44] ci(backend): run a single verify script and call scripts with pnpm run - Add a verify script to the backend (lint with --max-warnings=0, format check, typecheck, tests): one command that runs the same gate locally and in CI, and eslint no longer passes with warnings - Make the backend CI job call pnpm run verify instead of four separate steps, so CI and the local check cannot drift apart - Use pnpm run