Files
siftlode/frontend/package.json
T
peter efe106dfda build: pin one Node major across the dev, CI and build machines
The server gate ran node:22 while the dev gate ran node:24, and the two disagree about
Intl.NumberFormat's compact currency padding and about which realm's Blob a Response hands
back — so the frontend job had been red for 73 of its last 90 runs while the local gate was
green, and nothing said so.

Align everything on 24, with `.nvmrc` as the single source of truth: the CI container, the
prod build stage, `engines`, and `@types/node` (which was two majors ahead of the runtime, so
tsc was green over APIs the shipped engine does not have). The backend job's node only runs
the checkout action, so it tracks the pin as hygiene — node 20 is EOL — but deliberately
stays outside the contract.

The pin is held by a contract test that reads every one of those places AND the engine
running it, so the next drift is a red lane on its first run instead of a CI log nobody reads.

Verified on node:24-bookworm (the CI lane: 228 files / 1552 tests, typecheck, lint,
format:check, knip all exit 0) and on node:24-alpine (the prod build stage, v24.21.0, SPA
built). Each assertion of the new test was mutation-checked red.
2026-09-20 18:06:24 +02:00

66 lines
1.9 KiB
JSON

{
"name": "siftlode-frontend",
"private": true,
"license": "AGPL-3.0-only",
"type": "module",
"engines": {
"node": "24.x"
},
"scripts": {
"dev": "vite",
"typecheck": "npm run typecheck:app && npm run typecheck:node && npm run typecheck:e2e",
"typecheck:app": "tsc --noEmit -p tsconfig.json",
"typecheck:node": "tsc -p tsconfig.node.json",
"typecheck:e2e": "tsc --noEmit -p e2e/tsconfig.json",
"build": "npm run typecheck:app && npm run typecheck:node && vite build",
"preview": "vite preview",
"knip": "knip",
"lint": "eslint . --max-warnings 0",
"format": "prettier --write .",
"format:check": "prettier --check .",
"test": "vitest run",
"test:watch": "vitest",
"e2e": "playwright test"
},
"dependencies": {
"@dnd-kit/core": "^6.3.1",
"@dnd-kit/sortable": "^10.0.0",
"@dnd-kit/utilities": "^3.2.2",
"@floating-ui/react": "^0.27.20",
"@tanstack/react-query": "^5.51.0",
"@tanstack/react-virtual": "^3.14.3",
"clsx": "^2.1.1",
"flag-icons": "^7.5.0",
"hls.js": "^1.6.16",
"i18next": "^23.11.5",
"lucide-react": "^0.408.0",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-i18next": "^14.1.2"
},
"devDependencies": {
"@eslint/js": "9.39.1",
"@playwright/test": "^1.61.1",
"@tailwindcss/postcss": "^4.3.3",
"@testing-library/react": "^16.3.2",
"@types/node": "^24.13.6",
"@types/react": "^18.3.3",
"@types/react-dom": "^18.3.0",
"@vitejs/plugin-react": "^4.3.1",
"eslint": "9.39.1",
"eslint-config-prettier": "10.1.8",
"eslint-plugin-react-hooks": "5.2.0",
"eslint-plugin-react-refresh": "0.4.24",
"globals": "16.5.0",
"jsdom": "^25.0.1",
"knip": "6.27.0",
"postcss": "^8.4.39",
"prettier": "3.6.2",
"tailwindcss": "^4.3.3",
"typescript": "^5.5.3",
"typescript-eslint": "8.46.0",
"vite": "^5.3.4",
"vitest": "^3.2.7"
}
}