Dependencies naar de nieuwste versies + update-check in de testsuite

Upgrades
- Frontend: React 19.2, Vite 8.2, @vitejs/plugin-react 6.1, ESLint 9.39
- Backend: Express 5.2, Helmet 8.3, express-rate-limit 8.7, pino 10.3,
  pino-http 11, pg 8.23, dotenv 17.4, bcryptjs 3.0, ESLint 10.9
- Lockfiles opnieuw gegenereerd (backend had er geen)

Breaking changes opgevangen
- Express 5 laat kale wildcards in routes niet toe: app.use("/api/*") is een
  prefix-mount geworden en app.get("*") pad-loze middleware
- Vite 8 bundelt met Rolldown, dat de object-vorm van manualChunks niet kent;
  omgezet naar build.rollupOptions.output.advancedChunks
- Rate-limit headers verschillen per RFC-draft; de test kijkt nu naar de hele
  RateLimit-headerfamilie in plaats van naar RateLimit-Limit
- ESLint blijft in de frontend op 9: eslint-plugin-react ondersteunt nog geen
  ESLint 10 (peer <= ^9.7)

Update-check bij het testen
- docker/check-updates.mjs vergelijkt beide package.json's met het
  npm-register; werkt zonder node_modules en zonder npm install
- test.ps1 draait dit als stap 1 van 4, met -Strict (falen) en -SkipDeps
  (offline). Ook informatief in de Test-stage van de pipeline
- Bewuste uitzonderingen met reden in docker/update-exceptions.json
- dev.ps1 up gebruikt --renew-anon-volumes, anders houdt de dev-container de
  oude node_modules na een upgrade

Overig
- ESLint flat configs voor beide packages; npm run lint werkte niet omdat er
  geen configuratie was. Beide draaien nu groen (alleen waarschuwingen over
  bestaande ongebruikte variabelen)
- README.md, BEHEER.md en Changelog.md bijgewerkt

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Jordy Hoogeveen
2026-09-04 12:21:06 +02:00
co-authored by Claude Opus 5
parent bfec3a25f3
commit 1c0eed61f6
17 changed files with 6887 additions and 4999 deletions
+57 -4
View File
@@ -37,25 +37,43 @@ Vite proxyt `/api` naar `localhost:3001` binnen dezelfde container.
## Testen ## Testen
```powershell ```powershell
.\test.ps1 # backend-tests + productiebuild + smoketest .\test.ps1 # dependency-check + backend-tests + build + smoketest
.\test.ps1 -Unit # alleen de backend-tests .\test.ps1 -Unit # dependency-check + backend-tests
.\test.ps1 -NoSmoke # tests + build, zonder de stack te starten .\test.ps1 -NoSmoke # zonder de stack te starten
.\test.ps1 -Strict # faal als een dependency achterloopt
.\test.ps1 -SkipDeps # dependency-check overslaan (bijv. offline)
``` ```
Alles loopt in Docker; Node op de host is niet nodig. Alles loopt in Docker; Node op de host is niet nodig.
| Stap | Wat er gebeurt | | Stap | Wat er gebeurt |
|---|---| |---|---|
| Dependency-check | `docker/check-updates.mjs` vergelijkt `package.json` met het npm-register |
| Backend-tests | `Dockerfile` target `test` → `node --test` in `backend/test/`, zonder database | | Backend-tests | `Dockerfile` target `test` → `node --test` in `backend/test/`, zonder database |
| Productiebuild | `Dockerfile` target `production` — faalt de Vite-build, faalt deze stap | | Productiebuild | `Dockerfile` target `production` — faalt de Vite-build, faalt deze stap |
| Smoketest | app + postgres starten en `/api/health` en `/` opvragen | | Smoketest | app + postgres starten en `/api/health` en `/` opvragen |
Losse backend-tests draaien in een lopende dev-container: De dependency-check draait standaard bij elke testronde, dus je ziet direct of
er updates open staan. Pakketten die bewust achterblijven zet je met een reden
in `docker/update-exceptions.json`; die worden als `[vast ]` gemeld en laten de
check niet falen. Zonder netwerk: `-SkipDeps`. In een CI-gate: `-Strict`.
De check los draaien:
```bash
docker run --rm -v ${PWD}:/repo -w /repo node:22-alpine node docker/check-updates.mjs
docker run --rm -v ${PWD}:/repo -w /repo node:22-alpine node docker/check-updates.mjs --strict
```
Losse backend-tests en linting in een lopende dev-container:
```bash ```bash
docker compose -f docker-compose.dev.yml exec app npm test docker compose -f docker-compose.dev.yml exec app npm test
docker compose -f docker-compose.dev.yml exec app npm run lint
docker compose -f docker-compose.dev.yml exec -w /app/frontend app npm run lint
``` ```
---
--- ---
## Release workflow ## Release workflow
@@ -224,6 +242,41 @@ Rollen:
--- ---
## Dependencies bijwerken
Node hoeft niet op de host te staan; de lockfiles zijn met een container bij te
werken. Per map (`frontend` en `backend`):
```bash
# Laatste versies opzoeken
docker run --rm -v ${PWD}/frontend:/w -w /w node:22-alpine npm outdated
# Versies in package.json aanpassen, daarna de lockfile herschrijven
docker run --rm -v ${PWD}/frontend:/w -w /w node:22-alpine npm install --package-lock-only
```
Daarna altijd de testsuite draaien — die bouwt ook het productie-image, dus een
kapotte upgrade valt meteen op:
```powershell
.\test.ps1
```
En de dev-stack herbouwen. `node_modules` staan in anonieme volumes; zonder
`--renew-anon-volumes` blijven de oude versies staan. `.\dev.ps1 up` doet dit
al automatisch:
```powershell
.\dev.ps1 up
```
> Let op bij majors: `eslint-plugin-react` loopt achter op ESLint (peer
> `<= ^9.7`), daarom staat de frontend op ESLint 9 en de backend op 10.
> Express 5 vereist benoemde wildcards in routes (`/api/*splat`); `server.js`
> gebruikt daarom pad-loze middleware in plaats van `app.get("*")`.
---
## Registry ## Registry
``` ```
+19
View File
@@ -25,6 +25,16 @@
`dev.ps1 up` wist de dev-database niet langer (alleen `dev.ps1 down`) `dev.ps1 up` wist de dev-database niet langer (alleen `dev.ps1 down`)
- `release.ps1` draait de testsuite vóór het publiceren en bouwt voor - `release.ps1` draait de testsuite vóór het publiceren en bouwt voor
`linux/amd64` + `linux/arm64` `linux/amd64` + `linux/arm64`
- Dependencies naar de nieuwste versies: React 19.2, Vite 8.2,
@vitejs/plugin-react 6.1, Express 5.2, Helmet 8.3, express-rate-limit 8.7,
pino 10.3 / pino-http 11, pg 8.23, dotenv 17.4, bcryptjs 3.0, ESLint 9.39
(frontend) / 10.9 (backend)
- Express 5 vereist benoemde wildcards in routes; `app.use("/api/*")` en
`app.get("*")` vervangen door een prefix-mount en pad-loze middleware
- `dev.ps1 up` gebruikt `--renew-anon-volumes`, anders blijven de oude
`node_modules` in de dev-container staan na een upgrade
- Vite 8 bundelt met Rolldown: `manualChunks` (object-vorm) vervangen door
`build.rollupOptions.output.advancedChunks`
### Toegevoegd ### Toegevoegd
- Testsuite: `backend/test/app.test.js` (11 tests met `node --test`) voor de - Testsuite: `backend/test/app.test.js` (11 tests met `node --test`) voor de
@@ -35,6 +45,15 @@
Publish (multi-arch push, alleen op main/master of een tag) Publish (multi-arch push, alleen op main/master of een tag)
- `.env.example`, `.dockerignore` en `.gitignore` - `.env.example`, `.dockerignore` en `.gitignore`
- `STATIC_DIR` als omgevingsvariabele (map met de frontend-build) - `STATIC_DIR` als omgevingsvariabele (map met de frontend-build)
- Dependency-check als eerste stap van de testsuite: `docker/check-updates.mjs`
vergelijkt beide `package.json`'s met het npm-register, met bewuste
uitzonderingen in `docker/update-exceptions.json` (`-Strict` om te falen,
`-SkipDeps` om over te slaan). Draait ook informatief in de pipeline
- ESLint flat configs (`frontend/eslint.config.js`, `backend/eslint.config.js`) —
`npm run lint` werkte voorheen niet, er was geen configuratie
- Lockfiles voor beide packages (`backend/package-lock.json` was er niet)
- `.gitattributes`: shell-scripts en Docker-bestanden altijd met LF, anders
faalt het shebang in de container
--- ---
+20 -4
View File
@@ -82,14 +82,30 @@ uitzonderingen nodig. Handige acties:
### Testen ### Testen
```powershell ```powershell
.\test.ps1 # backend-tests + productiebuild + smoketest .\test.ps1 # dependency-check + backend-tests + build + smoketest
.\test.ps1 -Unit # alleen de backend-tests .\test.ps1 -Unit # dependency-check + backend-tests
.\test.ps1 -NoSmoke # tests + build, zonder de stack te starten .\test.ps1 -NoSmoke # zonder de stack te starten
.\test.ps1 -Strict # faal als een dependency achterloopt
.\test.ps1 -SkipDeps # dependency-check overslaan (bijv. offline)
``` ```
Alles loopt in Docker — Node op de host is niet nodig. De backend-tests draaien Alles loopt in Docker — Node op de host is niet nodig. De backend-tests draaien
zonder database via het `test`-target in de `Dockerfile`. zonder database via het `test`-target in de `Dockerfile`.
Stap 1 vergelijkt elke dependency in `frontend/` en `backend/package.json` met
de laatste versie in het npm-register, zodat je bij elke testronde ziet of er
updates open staan. Pakketten die bewust achterblijven staan met reden in
[`docker/update-exceptions.json`](docker/update-exceptions.json) en laten de
check niet falen:
```
frontend/package.json (9 dependencies)
[vast ] eslint ^9.39.5 → 10.9.1 eslint-plugin-react ondersteunt nog geen ESLint 10
actueel op de vastgezette versies na
backend/package.json (15 dependencies)
alles actueel
```
### Productie release ### Productie release
```powershell ```powershell
@@ -165,7 +181,7 @@ vibefinance/
└── stores/ # userStore, dataStore └── stores/ # userStore, dataStore
``` ```
**Stack:** React 18 · Vite · Node.js 22 · Express 4 · PostgreSQL 18 · JWT · bcrypt · Docker Compose v2 **Stack:** React 19 · Vite 8 · Node.js 22 · Express 5 · PostgreSQL 18 · JWT · bcrypt · Docker Compose v2
### Waarom 2 containers ### Waarom 2 containers
+8
View File
@@ -53,6 +53,14 @@ stages:
- checkout: self - checkout: self
fetchDepth: 1 fetchDepth: 1
# Informatief: meldt dependencies die niet op de laatste versie staan.
# Bewuste uitzonderingen staan in docker/update-exceptions.json.
- script: |
set -euo pipefail
node docker/check-updates.mjs
displayName: Dependency-check (informatief)
continueOnError: true
- script: | - script: |
set -euo pipefail set -euo pipefail
docker build --target test -t vibefinance/app:test . docker build --target test -t vibefinance/app:test .
+17
View File
@@ -0,0 +1,17 @@
import js from "@eslint/js";
import globals from "globals";
export default [
js.configs.recommended,
{
files: ["src/**/*.js", "test/**/*.js"],
languageOptions: {
ecmaVersion: "latest",
sourceType: "module",
globals: { ...globals.node },
},
rules: {
"no-unused-vars": ["warn", { argsIgnorePattern: "^_" }],
},
},
];
+2429
View File
File diff suppressed because it is too large Load Diff
+18 -16
View File
@@ -4,25 +4,27 @@
"type": "module", "type": "module",
"scripts": { "scripts": {
"start": "node src/server.js", "start": "node src/server.js",
"dev": "node --watch src/server.js", "dev": "node --watch src/server.js",
"test": "node --test", "test": "node --test",
"lint": "eslint src --ext .js" "lint": "eslint src test"
}, },
"dependencies": { "dependencies": {
"bcryptjs": "^2.4.3", "bcryptjs": "^3.0.3",
"cookie-parser": "^1.4.6", "cookie-parser": "^1.4.7",
"cors": "^2.8.5", "cors": "^2.8.6",
"dotenv": "^16.4.5", "dotenv": "^17.4.2",
"express": "^4.19.2", "express": "^5.2.1",
"express-rate-limit": "^7.4.0", "express-rate-limit": "^8.7.0",
"helmet": "^7.1.0", "helmet": "^8.3.0",
"jsonwebtoken": "^9.0.2", "jsonwebtoken": "^9.0.3",
"pg": "^8.13.3", "pg": "^8.23.0",
"pino": "^9.0.0", "pino": "^10.3.1",
"pino-http": "^10.0.0", "pino-http": "^11.0.0",
"pino-pretty": "^13.0.0" "pino-pretty": "^13.1.3"
}, },
"devDependencies": { "devDependencies": {
"eslint": "^8.57.0" "@eslint/js": "^10.0.1",
"eslint": "^10.9.1",
"globals": "^17.12.0"
} }
} }
+7 -3
View File
@@ -64,7 +64,9 @@ app.use("/api/data", dataRouter);
app.use("/api/users", usersRouter); app.use("/api/users", usersRouter);
// ── 404 voor overige /api/* ─────────────────────────────────────────────────── // ── 404 voor overige /api/* ───────────────────────────────────────────────────
app.use("/api/*", (_req, res) => res.status(404).json({ error: "Endpoint niet gevonden." })); // Zonder pad-patroon met wildcard: Express 5 vereist benoemde wildcards
// (/api/*splat), en een prefix-mount doet hier precies hetzelfde.
app.use("/api", (_req, res) => res.status(404).json({ error: "Endpoint niet gevonden." }));
// ── Frontend (Vite build) ───────────────────────────────────────────────────── // ── Frontend (Vite build) ─────────────────────────────────────────────────────
const indexHtml = join(config.STATIC_DIR, "index.html"); const indexHtml = join(config.STATIC_DIR, "index.html");
@@ -82,8 +84,10 @@ if (servesFrontend) {
}, },
})); }));
// SPA-fallback: alle overige GET-routes naar index.html. // SPA-fallback: alle overige GET-routes naar index.html. Pathloze middleware
app.get("*", (_req, res) => { // in plaats van app.get("*") — dat patroon is in Express 5 ongeldig.
app.use((req, res, next) => {
if (req.method !== "GET" && req.method !== "HEAD") return next();
res.setHeader("Cache-Control", "no-store, no-cache, must-revalidate"); res.setHeader("Cache-Control", "no-store, no-cache, must-revalidate");
res.sendFile(indexHtml); res.sendFile(indexHtml);
}); });
+7 -2
View File
@@ -105,13 +105,18 @@ describe("Security headers", () => {
assert.doesNotMatch(csp, /upgrade-insecure-requests/); assert.doesNotMatch(csp, /upgrade-insecure-requests/);
}); });
// express-rate-limit gebruikt per RFC-draft andere headernamen
// (RateLimit-Limit vs. RateLimit); daarom op de hele familie testen.
const heeftRateLimitHeaders = (res) =>
[...res.headers.keys()].some((naam) => naam.startsWith("ratelimit"));
it("statische bestanden krijgen geen rate-limit headers", async () => { it("statische bestanden krijgen geen rate-limit headers", async () => {
const res = await fetch(`${basis}/assets/index-abc12345.js`); const res = await fetch(`${basis}/assets/index-abc12345.js`);
assert.equal(res.headers.get("ratelimit-limit"), null); assert.equal(heeftRateLimitHeaders(res), false);
}); });
it("de API krijgt wel rate-limit headers", async () => { it("de API krijgt wel rate-limit headers", async () => {
const res = await fetch(`${basis}/api/health`); const res = await fetch(`${basis}/api/health`);
assert.notEqual(res.headers.get("ratelimit-limit"), null); assert.equal(heeftRateLimitHeaders(res), true);
}); });
}); });
+4 -2
View File
@@ -117,13 +117,15 @@ switch ($Action) {
} }
"up" { "up" {
# --renew-anon-volumes: node_modules zitten in anonieme volumes; zonder
# dit blijven ze na een dependency-upgrade op de oude versies staan.
if ($Target -eq "all") { if ($Target -eq "all") {
# Let op: geen -v hier, dat zou de dev-database wissen (zie actie down) # Let op: geen -v hier, dat zou de dev-database wissen (zie actie down)
docker compose -f $devCompose down docker compose -f $devCompose down
docker compose --progress plain -f $devCompose up --build -d docker compose --progress plain -f $devCompose up --build -d --renew-anon-volumes
} else { } else {
docker compose -f $devCompose stop $Target docker compose -f $devCompose stop $Target
docker compose --progress plain -f $devCompose up --build -d $Target docker compose --progress plain -f $devCompose up --build -d --renew-anon-volumes $Target
} }
if ($LASTEXITCODE -ne 0) { throw "Opstarten mislukt" } if ($LASTEXITCODE -ne 0) { throw "Opstarten mislukt" }
Write-Host "" Write-Host ""
+131
View File
@@ -0,0 +1,131 @@
/**
* Controleert of de dependencies in package.json op de laatste versie staan.
*
* Vergelijkt de versie in package.json met "latest" in het npm-register. Werkt
* zonder node_modules en zonder npm install, dus geschikt als snelle check in
* de testsuite.
*
* Gebruik: node docker/check-updates.mjs [map ...]
* node docker/check-updates.mjs frontend backend
*
* Exit 0 = alles actueel (of alleen waarschuwingen)
* Exit 1 = achterstand gevonden én --strict meegegeven
*/
import { readFile } from "node:fs/promises";
import { join } from "node:path";
const args = process.argv.slice(2);
const strict = args.includes("--strict");
const mappen = args.filter((a) => !a.startsWith("--"));
const doelen = mappen.length ? mappen : ["frontend", "backend"];
/** "^1.2.3" → [1,2,3] */
function ontleed(versie) {
const schoon = String(versie).replace(/^[\^~>=<\s]*/, "").split("-")[0];
const delen = schoon.split(".").map((n) => parseInt(n, 10) || 0);
return [delen[0] ?? 0, delen[1] ?? 0, delen[2] ?? 0];
}
/** Vergelijkt twee versies: -1 kleiner, 0 gelijk, 1 groter */
function vergelijk(a, b) {
const va = ontleed(a), vb = ontleed(b);
for (let i = 0; i < 3; i++) {
if (va[i] !== vb[i]) return va[i] < vb[i] ? -1 : 1;
}
return 0;
}
/** Welk deel van het versienummer verschilt */
function soort(huidig, laatste) {
const va = ontleed(huidig), vb = ontleed(laatste);
if (va[0] !== vb[0]) return "major";
if (va[1] !== vb[1]) return "minor";
return "patch";
}
async function laatsteVersie(naam) {
const url = `https://registry.npmjs.org/${naam.replace("/", "%2F")}/latest`;
const res = await fetch(url, { signal: AbortSignal.timeout(10000) });
if (!res.ok) throw new Error(`register gaf ${res.status}`);
return (await res.json()).version;
}
/**
* Bewuste uitzonderingen: dependencies die om een reden achterblijven.
* Staan in docker/update-exceptions.json en laten de check niet falen.
*/
async function leesUitzonderingen() {
try {
const ruw = await readFile(join(process.cwd(), "docker", "update-exceptions.json"), "utf8");
return JSON.parse(ruw);
} catch {
return {};
}
}
const uitzonderingen = await leesUitzonderingen();
let achterstand = 0;
let mislukt = 0;
for (const map of doelen) {
const pad = join(process.cwd(), map, "package.json");
let pkg;
try {
pkg = JSON.parse(await readFile(pad, "utf8"));
} catch {
console.log(`\n${map}/package.json niet gevonden — overgeslagen`);
continue;
}
const deps = { ...(pkg.dependencies ?? {}), ...(pkg.devDependencies ?? {}) };
const namen = Object.keys(deps).sort();
console.log(`\n${map}/package.json (${namen.length} dependencies)`);
const resultaten = await Promise.all(namen.map(async (naam) => {
const huidig = deps[naam];
// Alleen echte versienummers vergelijken (geen git/file/workspace ranges)
if (!/^[\^~]?\d/.test(huidig)) return { naam, huidig, status: "overgeslagen" };
try {
const laatste = await laatsteVersie(naam);
return vergelijk(huidig, laatste) < 0
? { naam, huidig, laatste, status: "verouderd", soort: soort(huidig, laatste) }
: { naam, huidig, laatste, status: "actueel" };
} catch (err) {
return { naam, huidig, status: "onbekend", fout: err.message };
}
}));
const verouderd = resultaten.filter((r) => r.status === "verouderd" && !(uitzonderingen[map]?.[r.naam]));
const vastgezet = resultaten.filter((r) => r.status === "verouderd" && uitzonderingen[map]?.[r.naam]);
const onbekend = resultaten.filter((r) => r.status === "onbekend");
for (const r of verouderd) {
console.log(` [${r.soort.padEnd(5)}] ${r.naam.padEnd(24)} ${r.huidig.padEnd(12)} → ${r.laatste}`);
}
for (const r of vastgezet) {
console.log(` [vast ] ${r.naam.padEnd(24)} ${r.huidig.padEnd(12)} → ${String(r.laatste).padEnd(10)} ${uitzonderingen[map][r.naam]}`);
}
for (const r of onbekend) {
console.log(` [? ] ${r.naam.padEnd(24)} kon niet worden opgezocht (${r.fout})`);
}
if (!verouderd.length && !vastgezet.length && !onbekend.length) {
console.log(" alles actueel");
} else if (!verouderd.length && !onbekend.length) {
console.log(" actueel op de vastgezette versies na");
}
achterstand += verouderd.length;
mislukt += onbekend.length;
}
console.log("");
if (achterstand === 0) {
console.log(mislukt ? `Geen achterstand gevonden (${mislukt} niet opgezocht).` : "Alle dependencies staan op de laatste versie.");
} else {
console.log(`${achterstand} dependency(s) niet op de laatste versie.`);
console.log("Bijwerken: pas package.json aan en herschrijf de lockfile:");
console.log(" docker run --rm -v ${PWD}/<map>:/w -w /w node:22-alpine npm install --package-lock-only");
}
process.exit(achterstand > 0 && strict ? 1 : 0);
+8
View File
@@ -0,0 +1,8 @@
{
"_uitleg": "Dependencies die bewust niet op de laatste versie staan. Sleutel = map, daarin pakketnaam → reden. check-updates.mjs meldt deze apart en laat de check niet falen.",
"frontend": {
"eslint": "eslint-plugin-react ondersteunt nog geen ESLint 10 (peer <= ^9.7)",
"@eslint/js": "moet gelijk oplopen met de eslint-versie"
},
"backend": {}
}
+30
View File
@@ -0,0 +1,30 @@
// ESLint 9 (niet 10): eslint-plugin-react ondersteunt nog geen ESLint 10
// (peer: eslint <= ^9.7). Zodra de plugin bijtrekt kan dit mee omhoog.
import js from "@eslint/js";
import react from "eslint-plugin-react";
import globals from "globals";
export default [
js.configs.recommended,
react.configs.flat.recommended,
react.configs.flat["jsx-runtime"],
{
files: ["src/**/*.{js,jsx}"],
languageOptions: {
ecmaVersion: "latest",
sourceType: "module",
globals: { ...globals.browser },
parserOptions: { ecmaFeatures: { jsx: true } },
},
settings: { react: { version: "detect" } },
rules: {
"no-unused-vars": ["warn", { argsIgnorePattern: "^_" }],
"react/prop-types": "off",
// Lege catch-blokken zijn hier bewust (localStorage kan gooien)
"no-empty": ["error", { allowEmptyCatch: true }],
// De icoon-arrays worden per index gerenderd, niet als lijst; geen
// runtime-waarschuwing van React, dus een hint in plaats van een fout.
"react/jsx-key": "warn",
},
},
];
+4060 -4930
View File
File diff suppressed because it is too large Load Diff
+23 -21
View File
@@ -1,23 +1,25 @@
{ {
"name": "vibefinance-frontend", "name": "vibefinance-frontend",
"version": "0.1.6-dev", "version": "0.1.6-dev",
"private": true, "private": true,
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",
"build": "vite build", "build": "vite build",
"preview": "vite preview", "preview": "vite preview",
"lint": "eslint src --ext .js,.jsx" "lint": "eslint src"
}, },
"dependencies": { "dependencies": {
"jszip": "^3.10.1", "jszip": "^3.10.1",
"react": "^18.3.1", "react": "^19.2.8",
"react-dom": "^18.3.1" "react-dom": "^19.2.8"
}, },
"devDependencies": { "devDependencies": {
"@vitejs/plugin-react": "^4.3.1", "@eslint/js": "^9.39.5",
"eslint": "^8.57.0", "@vitejs/plugin-react": "^6.1.1",
"eslint-plugin-react": "^7.34.0", "eslint": "^9.39.5",
"vite": "^5.4.0" "eslint-plugin-react": "^7.37.5",
} "globals": "^17.12.0",
"vite": "^8.2.2"
}
} }
+8 -3
View File
@@ -26,9 +26,14 @@ export default defineConfig({
chunkSizeWarningLimit: 800, chunkSizeWarningLimit: 800,
rollupOptions: { rollupOptions: {
output: { output: {
// Splits vendor chunks voor betere caching // Splits vendor chunks voor betere caching. Vite 8 bundelt met
manualChunks: { // Rolldown; die kent de object-vorm van manualChunks niet, dus dit
react: ["react", "react-dom"], // gaat via advancedChunks.
advancedChunks: {
groups: [
// RegExp via constructor: module-ids zijn genormaliseerd met "/"
{ name: "react", test: new RegExp("node_modules/(react|react-dom|scheduler)/") },
],
}, },
}, },
}, },
+40 -13
View File
@@ -2,21 +2,26 @@
# Draait de volledige testsuite voor de 2-container opzet. Alles gebeurt in # Draait de volledige testsuite voor de 2-container opzet. Alles gebeurt in
# Docker, dus je hebt geen Node op de host nodig. # Docker, dus je hebt geen Node op de host nodig.
# #
# 1. Backend-tests (Dockerfile target 'test' → node --test, zonder database) # 1. Dependency-check (staan package.json's op de laatste versies?)
# 2. Productiebuild (Vite-build + app-image; faalt de frontend, faalt dit) # 2. Backend-tests (Dockerfile target 'test' → node --test, zonder database)
# 3. Smoketest (productie-image + postgres, /api/health en / opvragen) # 3. Productiebuild (Vite-build + app-image; faalt de frontend, faalt dit)
# 4. Smoketest (productie-image + postgres, /api/health en / opvragen)
# #
# De smoketest publiceert geen hostpoorten en gebruikt een eigen netwerk, dus # De smoketest publiceert geen hostpoorten en gebruikt een eigen netwerk, dus
# hij botst niet met een draaiende dev-stack. # hij botst niet met een draaiende dev-stack.
# #
# Gebruik: # Gebruik:
# .\test.ps1 # alle stappen # .\test.ps1 # alle stappen
# .\test.ps1 -Unit # alleen de backend-tests # .\test.ps1 -Unit # dependency-check + backend-tests
# .\test.ps1 -NoSmoke # tests + build, zonder de stack te starten # .\test.ps1 -NoSmoke # zonder de stack te starten
# .\test.ps1 -Strict # faal als een dependency achterloopt
# .\test.ps1 -SkipDeps # dependency-check overslaan (bijv. offline)
param( param(
[switch]$Unit, [switch]$Unit,
[switch]$NoSmoke [switch]$NoSmoke,
[switch]$Strict,
[switch]$SkipDeps
) )
$ErrorActionPreference = "Stop" $ErrorActionPreference = "Stop"
@@ -34,8 +39,31 @@ function Write-Step {
Write-Host "── $Label" -ForegroundColor Cyan Write-Host "── $Label" -ForegroundColor Cyan
} }
# ── 1. Backend-tests ────────────────────────────────────────────────────── # ── 1. Dependency-check ───────────────────────────────────────────────────
Write-Step "[1/3] Backend-tests" # Vergelijkt de versies in frontend/ en backend/package.json met het
# npm-register. Bewuste uitzonderingen staan in docker/update-exceptions.json.
if (-not $SkipDeps) {
Write-Step "[1/4] Dependency-check"
$checkArgs = @("node", "docker/check-updates.mjs")
if ($Strict) { $checkArgs += "--strict" }
docker run --rm -v "${PSScriptRoot}:/repo" -w /repo node:22-alpine @checkArgs
$checkCode = $LASTEXITCODE
if ($checkCode -ne 0) {
if ($Strict) {
Write-Error "Dependencies lopen achter — bijwerken of -SkipDeps gebruiken."
exit 1
}
Write-Host "Let op: er zijn nieuwere versies beschikbaar (zie hierboven)." -ForegroundColor Yellow
}
} else {
Write-Step "[1/4] Dependency-check overgeslagen (-SkipDeps)"
}
# ── 2. Backend-tests ──────────────────────────────────────────────────────
Write-Step "[2/4] Backend-tests"
docker build --target test -t $testImage $PSScriptRoot docker build --target test -t $testImage $PSScriptRoot
if ($LASTEXITCODE -ne 0) { Write-Error "Testimage bouwen mislukt"; exit 1 } if ($LASTEXITCODE -ne 0) { Write-Error "Testimage bouwen mislukt"; exit 1 }
@@ -49,8 +77,8 @@ if ($Unit) {
exit 0 exit 0
} }
# ── 2. Productiebuild (inclusief Vite-build) ────────────────────────────── # ── 3. Productiebuild (inclusief Vite-build) ──────────────────────────────
Write-Step "[2/3] Productie-image bouwen" Write-Step "[3/4] Productie-image bouwen"
docker build --target production ` docker build --target production `
--build-arg APP_VERSION=$appVersion ` --build-arg APP_VERSION=$appVersion `
--build-arg VITE_APP_VERSION=$appVersion ` --build-arg VITE_APP_VERSION=$appVersion `
@@ -64,9 +92,8 @@ if ($NoSmoke) {
Write-Host "Smoketest overgeslagen (-NoSmoke) — klaar." -ForegroundColor Green Write-Host "Smoketest overgeslagen (-NoSmoke) — klaar." -ForegroundColor Green
exit 0 exit 0
} }
# ── 4. Smoketest op het productie-image + postgres ────────────────────────
# ── 3. Smoketest op het productie-image + postgres ──────────────────────── Write-Step "[4/4] Smoketest (app + postgres)"
Write-Step "[3/3] Smoketest (app + postgres)"
function Remove-SmokeStack { function Remove-SmokeStack {
docker rm -f $smokeApp $smokeDb 2>$null | Out-Null docker rm -f $smokeApp $smokeDb 2>$null | Out-Null