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:
co-authored by
Claude Opus 5
parent
bfec3a25f3
commit
1c0eed61f6
@@ -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
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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 .
|
||||||
|
|||||||
@@ -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: "^_" }],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
Generated
+2429
File diff suppressed because it is too large
Load Diff
+18
-16
@@ -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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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 ""
|
||||||
|
|||||||
@@ -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);
|
||||||
@@ -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": {}
|
||||||
|
}
|
||||||
@@ -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",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
Generated
+4060
-4930
File diff suppressed because it is too large
Load Diff
+23
-21
@@ -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"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -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)/") },
|
||||||
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user