Commit Graph

67 Commits

Author SHA1 Message Date
APF Portal Bot 06c7cb9195 fix(deps): update nx to v22.7.5
CI / commits (pull_request) Has been skipped
CI / perf (pull_request) Has been skipped
CI / scan (pull_request) Successful in 2m26s
CI / a11y (pull_request) Successful in 6m11s
CI / check (pull_request) Successful in 7m1s
Docs site / build (pull_request) Successful in 4m32s
2026-05-29 10:03:16 +00:00
APF Portal Bot a848e0fabd chore(deps): update dependency @swc/helpers to v0.5.23 (#239)
CI / commits (push) Has been skipped
CI / check (push) Successful in 3m25s
CI / scan (push) Successful in 1m54s
CI / a11y (push) Successful in 2m55s
CI / perf (push) Successful in 5m2s
Docs site / build (push) Successful in 4m42s
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [@swc/helpers](https://swc.rs) ([source](https://github.com/swc-project/swc/tree/HEAD/packages/helpers)) | devDependencies | patch | [`0.5.21` -> `0.5.23`](https://renovatebot.com/diffs/npm/@swc%2fhelpers/0.5.21/0.5.23) |

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined).

🚦 **Automerge**: Enabled.

♻ **Rebasing**: Whenever PR is behind base branch, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0MC42Mi4xIiwidXBkYXRlZEluVmVyIjoiNDAuNjIuMSIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOlsiZGVwZW5kZW5jaWVzIl19-->

Reviewed-on: #239
Co-authored-by: APF Portal Bot <jgautier.webdev+apf-portal-bot@gmail.com>
Co-committed-by: APF Portal Bot <jgautier.webdev+apf-portal-bot@gmail.com>
2026-05-27 17:34:46 +02:00
julien e2894afc4a chore(deps): bump tmp override to >=0.2.6 (GHSA-ph9p-34f9-6g65) (#240)
CI / commits (push) Has been skipped
CI / check (push) Successful in 3m39s
CI / scan (push) Successful in 2m21s
CI / a11y (push) Successful in 2m43s
CI / perf (push) Successful in 4m44s
Docs site / build (push) Successful in 4m25s
## Summary

Bump the existing `tmp` security override from `>=0.2.4` to `>=0.2.6` to cover [GHSA-ph9p-34f9-6g65](https://github.com/advisories/GHSA-ph9p-34f9-6g65) — a path-traversal vulnerability via unsanitized prefix/postfix in `tmp@<0.2.6`. Surfaced by `pnpm ci:audit` on the next CI run; both Gitea and the in-flight GitLab Phase 2 pipeline fail without this.

## Why this isn't an upstream upgrade

`tmp` is a transitive dependency. The two consuming paths today:

| Path | Latest available |
| --- | --- |
| `.>nx>tmp` | `nx@22.7.4` still declares `tmp@^0.2.4` |
| `.>@lhci/cli>tmp` | `@lhci/cli@0.15.1` (latest) still declares `tmp@^0.1.0` |

Upgrading `nx` or `@lhci/cli` does not move `tmp` to 0.2.6. The pre-existing `pnpm.overrides` entry was already pinning `tmp@<0.2.4` → `>=0.2.4` against the prior advisory; this PR just widens the lower bound to match the new one. Same pattern, one line.

## What lands

| File | Change |
| --- | --- |
| `package.json` | `"tmp@<0.2.4": ">=0.2.4"` → `"tmp@<0.2.6": ">=0.2.6"` (one line). |
| `pnpm-lock.yaml` | `tmp@0.2.4` → `tmp@0.2.6` resolved; `@scalar/nestjs-api-reference@1.1.16 → 1.1.19` picked up as a natural transitive resolution during `pnpm install` (no semver-major, both are within the existing `^1.1.14` range and match Renovate's "patch + auto-merge" policy). |

## Risk

Patch bump (0.2.4 → 0.2.6) on a tiny library with a stable public API since v0.2.0. The release notes for 0.2.5 and 0.2.6 are sanitization-only fixes — no API surface change. Risk of regression in nx / lhci consumers: negligible.

## Test plan

- [x] `pnpm audit --audit-level=moderate` returns `No known vulnerabilities found` (exit 0).
- [x] `pnpm why tmp` shows `tmp@0.2.6` as the single resolved version (no duplicate).
- [ ] CI green on Gitea (`check` + `scan` jobs).
- [ ] Once merged, rebase `ci/gitlab-pipeline-phase2` on top and retry the GitLab `audit` job — expected green.

## Related

- [GHSA-ph9p-34f9-6g65](https://github.com/advisories/GHSA-ph9p-34f9-6g65) — the advisory.
- Unblocks [ADR-0028](../docs/decisions/0028-migrate-cicd-and-git-hosting-to-gitlab.md) Phase 2 pipeline parity validation (see `ci/gitlab-pipeline-phase2` branch).

---------

Co-authored-by: Julien Gautier <julien.gautier@apf.asso.fr>
Reviewed-on: #240
2026-05-27 17:29:41 +02:00
julien 55338b83c0 fix(seed): use tsconfig.app.json instead of inline --compiler-options (#235)
CI / commits (push) Has been skipped
CI / check (push) Successful in 3m22s
CI / scan (push) Successful in 2m43s
CI / a11y (push) Successful in 3m37s
CI / perf (push) Successful in 6m12s
Docs site / build (push) Successful in 5m24s
## Summary

Follow-up to [#234](#234). The `db:seed` script's inline `--compiler-options '{"module":"CommonJS",…}'` was getting its quotes stripped by the shell when `pnpm run` re-spawned `ts-node`:

```
> ts-node --compiler-options {module:CommonJS,esModuleInterop:true,target:ES2020} apps/portal-bff/prisma/seed.ts

SyntaxError: Expected property name or '}' in JSON at position 1
```

Cross-platform JSON-on-cmdline quoting through nested `package.json → shell → npm-script → shell → ts-node` is a notorious pain. `apps/portal-bff/tsconfig.app.json` already declares everything `ts-node` needs to transpile the seed (module=commonjs, esModuleInterop=true, target=es2021, moduleResolution=node, types=[node]). Point at it with `-P` and drop the inline JSON entirely.

## What lands

| File | Change |
| --- | --- |
| `package.json` (root) | `db:seed` script: `--compiler-options '{...}'` → `-P apps/portal-bff/tsconfig.app.json`. One-line diff. |

That's it.

## Why `tsconfig.app.json` works for the seed even though seed.ts isn't under `src/`

`ts-node --transpile-only` reads `compilerOptions` from the `-P` tsconfig but **ignores the `include` / `exclude` fields** for the file passed explicitly on the command line. So the fact that `tsconfig.app.json` has `"include": ["src/**/*.ts"]` and the seed lives under `prisma/` doesn't matter — `ts-node` happily transpiles whatever file you point it at.

## Test plan

- [ ] **Locally**: `cd apps/portal-bff && pnpm exec prisma db seed` — no more `SyntaxError` on the compiler-options, seed runs and reports `[seed] test-tenant complete …`.
- [ ] **Idempotency** — re-run, expect `created 0 / 0 / 0`.
- [x] No application code changed — drift gate / lint / tests untouched.

## Notes for the reviewer

- Could have created a dedicated `apps/portal-bff/prisma/tsconfig.json` extending the app config. Less coupling but more files; pointing at the existing `tsconfig.app.json` is the minimum-surface fix.
- The Prisma 7 deprecation warning about `package.json#prisma` is unchanged; migrating to `prisma.config.ts` is a separate future PR (would also let us inline the seed config without shell quoting at all).

---------

Co-authored-by: Julien Gautier <julien.gautier@apf.asso.fr>
Reviewed-on: #235
2026-05-26 14:53:12 +02:00
julien 827d69594c fix(seed): route prisma db seed through pnpm run for cwd resolution (#234)
CI / commits (push) Has been skipped
CI / check (push) Successful in 1m52s
CI / scan (push) Successful in 2m15s
CI / a11y (push) Successful in 3m28s
CI / perf (push) Successful in 6m8s
Docs site / build (push) Successful in 5m15s
## Summary

Follow-up fix on [#233](#233). The `prisma db seed` command failed with `Cannot find module './seed.ts'` because the seed command's path was resolved against the user's cwd (`apps/portal-bff/`) and doubled into `apps/portal-bff/apps/portal-bff/prisma/seed.ts`.

Root cause: Prisma CLI spawns the seed command with cwd = wherever the user invoked `prisma db seed` from. The path `apps/portal-bff/prisma/seed.ts` only works if cwd is the workspace root, but the user's habit (matching `prisma migrate dev`) is to run prisma commands from `apps/portal-bff/`.

Fix: indirect through `pnpm run`. `pnpm run` for a root-level script always sets cwd = workspace root regardless of where invoked from. The seed path then resolves predictably.

## What lands

| File | Change |
| --- | --- |
| `package.json` (root) | New `db:seed` npm script in `scripts` carrying the ts-node invocation. `prisma.seed` becomes `pnpm run db:seed` — the indirection forces cwd = workspace root before ts-node resolves the seed path. |

That's it. Two-line diff.

## Why `pnpm run` indirection works

| Step | cwd | Path resolution |
| --- | --- | --- |
| User invokes `pnpm exec prisma db seed` from `apps/portal-bff/` | `apps/portal-bff/` | — |
| Prisma CLI walks up, finds the workspace `package.json`, reads `prisma.seed` | `apps/portal-bff/` | — |
| Prisma spawns the seed command | `apps/portal-bff/` (inherited) | — |
| Seed command is `pnpm run db:seed` | `apps/portal-bff/` | — |
| **`pnpm run` resets cwd to the package's root (workspace root)** | **workspace root** | — |
| ts-node sees `apps/portal-bff/prisma/seed.ts` | workspace root | resolves correctly ✓ |

The same flow works if the user invokes from the workspace root — `pnpm run` still ends up at workspace root, idempotent.

## Test plan

- [ ] **Locally**: `cd apps/portal-bff && pnpm exec prisma db seed` — should report `[seed] test-tenant complete — created … Person + … User + … UserScope rows; …`. No `MODULE_NOT_FOUND`.
- [ ] **Also locally**: same command from the workspace root (`pnpm exec prisma db seed`) — same result.
- [ ] **Idempotency** — re-run, expect `created 0 Person + 0 User + 0 UserScope rows`.
- [x] No application code changed — drift gate, lint, tests untouched.
- [ ] **Review focus** — the `pnpm run` indirection trick + the cwd table above.

## Notes for the reviewer

- The deprecation warning about `package.json#prisma` (Prisma 7 migration to `prisma.config.ts`) is unchanged — not in this fix's scope.
- Could have also fixed by changing the path to be relative-to-bff (`prisma/seed.ts`) and forcing the user to run from `apps/portal-bff/`. The `pnpm run` approach is cwd-invariant — more robust to where the user invokes from.
- The new `db:seed` script is callable directly (`pnpm db:seed`) which is a small ergonomic bonus.

---------

Co-authored-by: Julien Gautier <julien.gautier@apf.asso.fr>
Reviewed-on: #234
2026-05-26 14:45:16 +02:00
julien 9b4b4b90d0 feat(auth): swap stub for PrismaScopeResolver + add test-tenant seed (ADR-0026 PR 2a) (#233)
CI / check (push) Successful in 4m42s
CI / commits (push) Has been skipped
CI / scan (push) Successful in 3m25s
CI / a11y (push) Successful in 4m47s
CI / perf (push) Successful in 7m30s
Docs site / build (push) Successful in 7m27s
## Summary

ADR-0026 PR 2 (first half — backend). Ships:

- **`PrismaScopeResolver`** replacing `StubScopeResolver` in `AuthModule`. Queries `user_scopes WHERE userId = ? AND (expiresAt IS NULL OR expiresAt > NOW())` and maps each row to a typed `Scope` from `shared-auth`.
- **`prisma/seed.ts`** populating Person + User + UserScope rows for the 19 test-tenant personas per `notes/test-tenant-role-assignments.md`. Idempotent — re-running preserves existing rows.
- **`infra/test-tenant.personas.example.json`** — schema template for the gitignored per-persona Entra `oid` map the seed reads.

The admin UI scope-seeding screen `/admin/users/:id/scopes` is split into **PR 2b** (Angular work, follows separately).

## What lands

| File | Change |
| --- | --- |
| `apps/portal-bff/src/auth/prisma-scope-resolver.ts` + `.spec.ts` | **New** Prisma-backed resolver. Server-side `expiresAt` filter (`null OR > NOW()`). Maps `(kind, value)` rows to the discriminated `Scope` union via a pure `toScope` helper. Off-catalogue `kind` values are skipped + WARN-logged (defense in depth — the drift gate is the canonical write-side guard). 10 spec tests covering query shape, valueless / value-bearing kinds, defensive skip on off-catalogue, edge cases on `toScope`. |
| `apps/portal-bff/src/auth/auth.module.ts` | `{ provide: ScopeResolver, useClass: StubScopeResolver }` → `useClass: PrismaScopeResolver`. The `StubScopeResolver` class stays exported from `scope-resolver.ts` (handy for spec fixtures / future "force-unrestricted" dev modes) but is no longer the default wiring. |
| `apps/portal-bff/prisma/seed.ts` | **New** seed script. Hardcoded `PERSONAS` array (19 entries, slug + email + displayName + scope tuples — transcribed from `notes/test-tenant-role-assignments.md`). For each persona: reads its Entra `oid` from `TEST_TENANT_PERSONAS_PATH` (env var, default `infra/test-tenant.personas.json`); if missing → skip with WARN; otherwise idempotent upsert (findUnique by `entraOid` → reuse, or create Person + User in nested-create transaction with `Person.source = 'seed'`). Then idempotent upserts for each scope (findUnique by `(userId, kind, value)` → skip, or create with `UserScope.source = 'seed'`). Final log: counts of rows created + personas skipped. |
| `infra/test-tenant.personas.example.json` | **New** schema template — flat `{ slug: entra-oid }` map for the 19 personas + a `_README` field documenting the shape. Distinct from `test-tenant.entra.json` (the 24-entry GROUP-guid map for `EntraGroupToRoleResolver`). Real file is gitignored. |
| `apps/portal-bff/.env.example` | New `TEST_TENANT_PERSONAS_PATH` block with the same documentation pattern as `ENTRA_GROUP_MAP_PATH`. |
| `package.json` | `prisma.seed` field added: `ts-node --transpile-only --compiler-options '...' apps/portal-bff/prisma/seed.ts`. Lets `pnpm exec prisma db seed` run the script without a project-specific tsconfig dance. |

## Key choices

- **`PrismaScopeResolver` swap is the default for AuthModule.** No "fallback to stub when DB is unreachable" — a Postgres outage that prevents reading `user_scopes` should fail the sign-in (same posture as `PersonAndUserProvisioner.ensureUser`, which is also blocking). The `StubScopeResolver` class remains in `scope-resolver.ts` for spec fixtures + as a hint of how to wire a "force-everyone-unrestricted" dev override if we ever want one.
- **Defensive `toScope` mapping.** The drift gate enforces catalogue membership at the write site (the future admin scope-seeding UI from PR 2b). The Prisma read side defends in depth by skipping off-catalogue rows — a row with `kind = 'something-bogus'` (e.g. a future migration mistake) is dropped from the resolved scopes + logged, rather than throwing on every sign-in for that user. The unit test `'skips + warns on off-catalogue kinds'` pins the behaviour.
- **Seed reads Entra `oid`s from a gitignored file.** Same pattern as `EntraGroupToRoleResolver` (path via env var, file is gitignored, schema template in `infra/*.example.json`). The `oid`s themselves are tenant-private; the 19 persona slugs + their scope tuples are project-wide and live in `seed.ts`.
- **Seed is idempotent on every level.** The unique constraint on `User.entraOid` lets us "create if missing" without locks; the unique on `(userId, kind, value)` does the same for `UserScope`. Re-running the seed after a partial run picks up where it stopped — no `--reset` needed.
- **No spec for `seed.ts` itself.** It's an integration data loader; meaningful test would require a real Prisma client + DB (or a heavy mock fixture). The persona matrix is hand-verified at PR review; the seed's correctness is exercised by running it on the dev DB (test-plan checkbox below).

## Verification path

- [x] `node scripts/check-catalogue-drift.mjs` — clean (`4 / 24 / 7 / 3`).
- [x] `node --test scripts/check-catalogue-drift.spec.mjs` — 29 tests passing.
- [x] `pnpm exec nx lint portal-bff` — **0 errors**, 13 warnings (all pre-existing).
- [x] `pnpm exec nx test portal-bff` (affected specs filtered) — **774 tests passing** (was 766; +8 for `prisma-scope-resolver.spec.ts`).
- [ ] **Locally / on dev DB**:
  - `cp infra/test-tenant.personas.example.json infra/test-tenant.personas.json` + fill in real `oid`s from the Entra admin centre.
  - `cd apps/portal-bff && pnpm exec prisma db seed` — should report `created 19 Person + 19 User + N UserScope rows; skipped 0 personas` on a fresh DB.
  - Re-run the seed → second invocation reports `0 / 0 / 0 created; skipped 0` (idempotent).
  - Sign in via the user portal as one of the 19 personas → `principal.scopes` on the session contains the seeded scopes (e.g. `directeur-bordeaux` sees `[{ kind: 'etablissement', value: '0330800013' }]`).
- [ ] **Review focus** — the `toScope` mapping (especially the defensive `null` branch on off-catalogue kinds), the seed's idempotency invariants, the `prisma.seed` command in `package.json`, the comments-only fields in `test-tenant.personas.example.json`.

## What's next

**PR 2b — Admin `/admin/users/:id/scopes` screen.** Angular admin-app SPA screen + BFF read/write controllers, against the schema this PR + ADR-0026 PR 1 + ADR-0027 PR 1 have now stood up. A11y review per ADR-0016 §"Manual testing cadence". Operator workflow only at that point — the seed in this PR is the bootstrap path for the test tenant.

Once both PR 2a + PR 2b ship: **the `@RequireScope` stack is end-to-end live** for the first time. ADR-0025's stubs (`StubScopeResolver`, the entraOid placeholder on `Principal.user.{id, personId}`) are then fully retired.

---------

Co-authored-by: Julien Gautier <julien.gautier@apf.asso.fr>
Reviewed-on: #233
2026-05-26 14:36:18 +02:00
julien 2eb01f59b3 feat(ci): catalogue-drift gate for @RequirePrivilege/@RequireRole literals (ADR-0025) (#211)
CI / check (push) Successful in 3m4s
CI / commits (push) Has been skipped
CI / scan (push) Successful in 2m53s
CI / a11y (push) Successful in 3m50s
CI / perf (push) Successful in 5m23s
Docs site / build (push) Successful in 5m26s
## Summary

Phase 3 (and last) of [ADR-0025](docs/decisions/0025-authorization-model-privileges-roles-scopes.md)'s implementation phasing (§"More Information" / §347): a small CI gate that asserts every string literal passed to the authorization decorators belongs to the closed catalogue declared in `libs/shared/auth/src/lib/authorization.types.ts`.

The TypeScript decorator signatures (`(...privileges: [Privilege, ...Privilege[]])`) already enforce this at compile time. The gate is **defence-in-depth** against the escape hatches the type system cannot catch:

- explicit `as Privilege` / `as FunctionalRole` casts (`'Portal.Foo' as Privilege`),
- the rare case where a developer hand-edits the catalogue type union without updating the runtime constant.

Runs in `<1s`, so it rides the existing `check` CI job rather than spinning up its own.

## What lands

| File | Role |
| --- | --- |
| `scripts/check-catalogue-drift.mjs` | The gate. TypeScript compiler API. Parses `authorization.types.ts` to extract the catalogues, walks every `.ts` file under `apps/` and `libs/`, finds each `CallExpression` whose callee identifier matches `RequirePrivilege` / `RequireRole`, validates every string-literal argument. Non-literal args are skipped (the TypeScript signature catches them already). Reports grouped by file with `line:column` per violation, exits 1 on drift. |
| `scripts/check-catalogue-drift.spec.mjs` | 13 tests via `node --test` (built-in runner, no Vitest dep). Covers catalogue parsing, file-level violation detection, workspace-wide aggregation, skipped folders, gen-stub exclusion, line/column reporting. |
| `package.json` | `ci:catalogue-drift` + `ci:catalogue-drift:test` scripts. |
| `.gitea/workflows/ci.yml` | The existing `check` job now runs the gate's unit tests first (fail-fast if the gate itself is broken), then the gate against the live workspace. |

## Notes for the reviewer

- **Why not an ESLint custom rule.** ADR-0025 §347 floats either option. The ESLint route would give editor-time feedback (red squiggles) but means standing up a local ESLint plugin lib — net ~300 lines of plumbing for a gate the TypeScript signature already enforces in real time via the literal-union types. The pnpm-script route mirrors the existing `ci:gzip-budgets` pattern; ~250 lines including tests; runs in the same `check` CI job that already installs deps. Editor feedback is **already provided by `tsc`**, so the gate's job reduces to "catch escape hatches in CI", which the script does fine.

- **Why `node --test` rather than Vitest / Jest.** The script lives in `scripts/`, outside any Nx project. Wiring Vitest for one spec file would mean a vitest.config + tsconfig.spec + Nx project just to host it. Node's built-in test runner ships with the runtime we already pin (Node 24 in `.nvmrc`), no config, ~250ms wall clock for 13 tests.

- **Generated gRPC stubs are skipped.** `apps/portal-bff/src/grpc/gen/apf-ai/common.ts` defines a `roles: string[]` proto field used by the AI-bridge — entirely unrelated to the ADR-0025 functional-role catalogue. The skip list is explicit (`SKIPPED_SUBPATHS`); adding a new codegen output later is one line.

- **Spec files are NOT skipped.** `auth-guards.persona-matrix.spec.ts` references catalogue values via the decorators in its test fixtures. Those are deliberate references and must stay in sync — if a future ADR amendment removes a role, the spec catches it on the same run as the production code. Explicitly tested in `does NOT skip spec files`.

- **Non-literal arguments (variable indirection) are skipped.** A pattern like `const slug = getRole(); @RequireRole(slug)` would not be caught by string-literal inspection. This is intentional: the TypeScript decorator signature already requires `slug` to be typed `FunctionalRole`, so the type system handles it; the gate's value-add is on literal misspellings the type system cannot see past an `as Privilege` cast.

- **Self-test confirmed the gate works.** Injected `RequirePrivilege('Portal.RogueDrift')` and `RequireRole('rogue-role-x')` into an existing spec, ran `pnpm ci:catalogue-drift`, observed:

  ```
  catalogue-drift: violations found

    apps/portal-bff/src/auth/require-privilege.guard.spec.ts
      135:18  @RequirePrivilege('Portal.RogueDrift') — not in PRIVILEGES
      136:13  @RequireRole('rogue-role-x') — not in FUNCTIONAL_ROLES
  ```

  Exit code 1. Reverted the injection; gate green again.

- **Adding a third decorator** (per ADR-0025's anticipated growth) is one line in `DECORATOR_TO_CATALOGUE` plus the matching test fixture. The script does not hardcode the decorator name list beyond that map.

- **Why no scope-kind check.** `@RequireScope` takes an `(req) => ScopableResource` extractor, not literals. The scope `kind` values are constrained by the `ScopeKind` discriminated union, which TypeScript enforces at every `{ kind: ... }` construction site. No literal escape hatch worth gating in v1.

## Test plan

- [x] `pnpm ci:catalogue-drift:test` — 13/13 green via `node --test`.
- [x] `pnpm ci:catalogue-drift` — clean against the live workspace: `catalogue-drift: clean (catalogues: 4 privileges, 24 roles).`
- [x] Self-test by injection — script catches `'Portal.RogueDrift'` and `'rogue-role-x'` with file:line:column, exits 1.
- [x] `pnpm exec prettier --check` on the new files — clean.
- [ ] CI run on this PR exercises the new step in the `check` job.

## What's next

ADR-0025's phasing closes with this PR. Remaining authorization work waits on [ADR-0026](docs/decisions/) (proposed) — the `Person` + `User` schema brings the Prisma-backed `user_scopes` table; that PR replaces `StubScopeResolver` with a `PrismaScopeResolver` and unlocks the first concrete `@RequireScope` consumer surfaces.

---------

Co-authored-by: Julien Gautier <julien.gautier@apf.asso.fr>
Reviewed-on: #211
2026-05-24 01:07:13 +02:00
julien 77af6951c9 fix(deps): bump uuid to >=11.1.1 via pnpm.overrides (GHSA-w5hq-g745-h8pq) (#210)
CI / commits (push) Has been skipped
CI / check (push) Successful in 4m55s
CI / scan (push) Successful in 2m36s
CI / a11y (push) Successful in 3m54s
CI / perf (push) Successful in 5m40s
Docs site / build (push) Successful in 5m29s
## Summary

`pnpm ci:audit` was failing on `main` with one moderate-severity finding:

```
uuid: Missing buffer bounds check in v3/v5/v6 when buf is provided
GHSA-w5hq-g745-h8pq
Vulnerable: uuid < 11.1.1
Path:       . > @lhci/cli@0.15.1 > uuid@8.3.2
```

`@lhci/cli@0.15.1` is the latest release and still ships `uuid@8.3.2`; no upstream fix yet. Renovate cannot solve this on its own.

## What lands

| File | Change |
| --- | --- |
| `package.json` | `pnpm.overrides`: add `"uuid@<11.1.1": ">=11.1.1"` alongside the existing axios / ajv / brace-expansion / esbuild / follow-redirects / ip-address / protobufjs / tmp / ws / yaml entries. |
| `pnpm-lock.yaml` | Refreshed. Both `@lhci/cli` and `sockjs` (transitive via the rspack / webpack dev-servers) now resolve to `uuid@14.0.0` — the same major already in the tree via `mermaid`. Net `uuid` versions: 2 → 1. |

## Notes for the reviewer

- **Why an override and not an `auditConfig.ignoreGhsas`.** The repo already uses `pnpm.overrides` for 10 prior advisories on transitive deps with no patched release in their consumer — same pattern applied here. An exemption-document path was drafted then dropped: forcing the fix is cleaner than waving away the warning, and the upgrade actually works.

- **Why our usage was not at risk (context, not justification to skip).** The advisory affects `uuid.v3` / `uuid.v5` / `uuid.v6` when called with the optional `buf` argument. Both consumers in the tree only use `uuid.v4()` (random):
  - `@lhci/cli/src/collect/node-runner.js:65` — `\`flags-${uuid.v4()}.json\``
  - `sockjs/lib/transport.js:9` — `uuidv4 = require('uuid').v4`
  But the audit gate is set to `moderate` per [ADR-0015](docs/decisions/0015-cicd-gitea-actions.md); every flagged advisory blocks merge regardless of reachability. Forcing the fix is the documented response.

- **Why `uuid@14` works for the CJS consumers.** `uuid@14` is ESM-only (`type: module` + conditional `exports`). `@lhci/cli` does `const uuid = require('uuid')` which is a CJS-requires-ESM pattern. Node `>= 22.12` supports it natively via the stable `require(esm)` capability; the workspace pins Node 24 in [`.nvmrc`](.nvmrc), so the consumer is fine. Verified locally:

  ```
  $ node -e "require('@lhci/cli/src/collect/node-runner.js')"
  # no error
  ```

- **Side benefit.** `uuid@8.3.2` was also pulled in transitively via `sockjs`. After the override, the dep tree consolidates onto a single `uuid@14.0.0` — removes the `Found 2 versions of uuid` dedup notice and aligns with the version `mermaid` already uses.

- **Closure condition.** When `@lhci/cli` ships a release with `uuid >= 11.1.1` (or with `uuid@14` directly), Renovate will surface the bump; this override row becomes redundant and can be deleted. Same removal cadence as the other `pnpm.overrides` entries.

## Test plan

- [x] `pnpm ci:audit` — clean: `No known vulnerabilities found`.
- [x] `pnpm ci:check` — 13 projects green (no behavioural change against the previous run).
- [x] Loaded `@lhci/cli`'s node-runner module under the override to confirm `require('uuid')` resolves without error on Node 24.
- [ ] Post-merge CI on `main` runs `pnpm ci:audit` cleanly.

---------

Co-authored-by: Julien Gautier <julien.gautier@apf.asso.fr>
Reviewed-on: #210
2026-05-24 00:40:38 +02:00
APF Portal Bot 273d96551f chore(deps): update dependency @swc/core to v1.15.40 (#204)
CI / commits (push) Has been skipped
CI / check (push) Failing after 16s
CI / perf (push) Failing after 32s
CI / a11y (push) Failing after 34s
CI / scan (push) Failing after 53s
Docs site / build (push) Failing after 21s
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [@swc/core](https://swc.rs) ([source](https://github.com/swc-project/swc/tree/HEAD/packages/core)) | devDependencies | patch | [`1.15.33` -> `1.15.40`](https://renovatebot.com/diffs/npm/@swc%2fcore/1.15.33/1.15.40) |

---

### Release Notes

<details>
<summary>swc-project/swc (@&#8203;swc/core)</summary>

### [`v1.15.40`](https://github.com/swc-project/swc/blob/HEAD/CHANGELOG.md#11540---2026-05-23)

[Compare Source](https://github.com/swc-project/swc/compare/v1.15.33...v1.15.40)

##### Bug Fixes

- **(es/minifier)** Preserve args for destructured callbacks ([#&#8203;11830](https://github.com/swc-project/swc/issues/11830)) ([21873b0](https://github.com/swc-project/swc/commit/21873b06df3fd62d952a21cf879e14d11d4b39d7))

- **(es/minifier)** Avoid generating mangled property names that collide with existing properties ([#&#8203;11839](https://github.com/swc-project/swc/issues/11839)) ([9b4fab5](https://github.com/swc-project/swc/commit/9b4fab58c90256a6da688de87ea405225a5a6fdb))

- **(es/minifier)** Respect ecma for iife temp vars ([#&#8203;11873](https://github.com/swc-project/swc/issues/11873)) ([e481934](https://github.com/swc-project/swc/commit/e481934a63c0ee891e4a770c4f0cd5ec3fd8624e))

- **(es/minifier)** Preserve default parameter object props ([#&#8203;11884](https://github.com/swc-project/swc/issues/11884)) ([71ff84f](https://github.com/swc-project/swc/commit/71ff84f19762306ab9b86accb29eb6ed83c46f84))

- **(es/parser)** Reject object-rest assignment to array/object literal ([#&#8203;11875](https://github.com/swc-project/swc/issues/11875)) ([7b57d1f](https://github.com/swc-project/swc/commit/7b57d1f8717d8bf6be0b617b04bc6e219a2b3775))

- **(es/parser)** Reject object rest assignment to literals ([#&#8203;11881](https://github.com/swc-project/swc/issues/11881)) ([4ec2eaf](https://github.com/swc-project/swc/commit/4ec2eaf4d89ddd95293b8f09169a88b0434c5a13))

- **(es/react)** Exclude self-recursive hooks from refresh dependency array ([#&#8203;11838](https://github.com/swc-project/swc/issues/11838)) ([9101c71](https://github.com/swc-project/swc/commit/9101c719fa8f3f5cb410d716d4f50544650cd81e))

- **(ts/fast-dts)** Strip definite assertions in dts ([#&#8203;11858](https://github.com/swc-project/swc/issues/11858)) ([2ab1b8a](https://github.com/swc-project/swc/commit/2ab1b8a50f2af3d8b4c42d6c4dd4f2051940cae0))

- **(ts/fast-strip)** Reject unsafe assertion erasure in binary expressions ([#&#8203;11828](https://github.com/swc-project/swc/issues/11828)) ([aa5b539](https://github.com/swc-project/swc/commit/aa5b539b277dbf4c68c87380d16f4b8713145df3))

- **(typescript)** Strip parameter binding defaults in dts ([#&#8203;11857](https://github.com/swc-project/swc/issues/11857)) ([800bc17](https://github.com/swc-project/swc/commit/800bc170334a74191eb5ae21e3bfc96bf6f7fe56))

##### Documentation

- Update agent guidance ([#&#8203;11842](https://github.com/swc-project/swc/issues/11842)) ([bf2d015](https://github.com/swc-project/swc/commit/bf2d0154cf8b66fdab16085585fda0086d297a64))

- Add security policy ([#&#8203;11876](https://github.com/swc-project/swc/issues/11876)) ([6c43c2d](https://github.com/swc-project/swc/commit/6c43c2de9cb9d5516b0ac87101345940964e943e))

- Clarify security scope for npm packages ([#&#8203;11877](https://github.com/swc-project/swc/issues/11877)) ([4662db8](https://github.com/swc-project/swc/commit/4662db8fe3e503f298a285697ea63ecc1ca3b958))

- Clarify untrusted input security model ([#&#8203;11882](https://github.com/swc-project/swc/issues/11882)) ([5463777](https://github.com/swc-project/swc/commit/546377770e164aead174404fb678319c9c56a9dc))

##### Features

- **(es/minifier)** Fine grained effect analysis of class ([#&#8203;11814](https://github.com/swc-project/swc/issues/11814)) ([c9058ad](https://github.com/swc-project/swc/commit/c9058adb5bb7d6bbe354e6136685271f722354a0))

- **(swc\_cli)** Implement all features for `swc_cli` ([#&#8203;11797](https://github.com/swc-project/swc/issues/11797)) ([9300ede](https://github.com/swc-project/swc/commit/9300ede1d495463042da1db11754c76057a50954))

##### Miscellaneous Tasks

- **(es/minifier)** Fix typo in debug log ([#&#8203;11866](https://github.com/swc-project/swc/issues/11866)) ([3de0254](https://github.com/swc-project/swc/commit/3de0254db7fae5a2883af78b8b7d57af4cb94531))

- **(html)** Add webcontainer fallback for `@swc/html` ([#&#8203;11860](https://github.com/swc-project/swc/issues/11860)) ([7692eed](https://github.com/swc-project/swc/commit/7692eed981916bb01a3c4c231b3af012d4993d9e))

##### Performance

- **(ecma)** Reduce transformer compat overhead ([#&#8203;11856](https://github.com/swc-project/swc/issues/11856)) ([d03cb71](https://github.com/swc-project/swc/commit/d03cb71fb8b39f01f0ad704473109294e52e07fd))

- **(es/codegen)** Speed up JsWriter position and srcmap tracking ([#&#8203;11867](https://github.com/swc-project/swc/issues/11867)) ([dbceade](https://github.com/swc-project/swc/commit/dbceade22809ac9a9cb7548456ab335df26fb046))

- **(es/codegen)** Remove JsWriter last\_srcmap cache ([#&#8203;11869](https://github.com/swc-project/swc/issues/11869)) ([3bc1c2b](https://github.com/swc-project/swc/commit/3bc1c2b9b2594b05478dc4240977b981d6f8521b))

- **(es/minifier)** Reduce minifier profiling hotspots ([#&#8203;11853](https://github.com/swc-project/swc/issues/11853)) ([28c1091](https://github.com/swc-project/swc/commit/28c1091adb2c6f6d0e46daa5595908d1ba6fccb7))

- Optimize es parser comment finalization ([#&#8203;11852](https://github.com/swc-project/swc/issues/11852)) ([2959ddf](https://github.com/swc-project/swc/commit/2959ddf87af0ac95eb5176180782819bd1073d66))

##### Testing

- **(es/minifier)** Move issue\_11835 fixture out of terser folder ([#&#8203;11840](https://github.com/swc-project/swc/issues/11840)) ([3dd3431](https://github.com/swc-project/swc/commit/3dd34310d429baff6e8d1a6393266c648684d3c6))

##### Ci

- Update corepack in publish docker jobs ([#&#8203;11885](https://github.com/swc-project/swc/issues/11885)) ([9a7d954](https://github.com/swc-project/swc/commit/9a7d954c4939b12da4c60023eba50d6df8086fd7))

- Pass publish docker env explicitly ([#&#8203;11888](https://github.com/swc-project/swc/issues/11888)) ([c5f7547](https://github.com/swc-project/swc/commit/c5f7547cf68a4803aec3e88901e0d6b57ebbeb55))

- Lock issues closed by merged prs ([#&#8203;11887](https://github.com/swc-project/swc/issues/11887)) ([6bd74e5](https://github.com/swc-project/swc/commit/6bd74e5683ed43640db64f78dc74001a056c1bfa))

- Provide aarch64 musl linker in publish job ([#&#8203;11889](https://github.com/swc-project/swc/issues/11889)) ([20234fd](https://github.com/swc-project/swc/commit/20234fd265f8f86f0c81c31c36e467d405a04d01))

- Fix publish musl linker and windows tests ([#&#8203;11890](https://github.com/swc-project/swc/issues/11890)) ([a798a23](https://github.com/swc-project/swc/commit/a798a23e5f5018e5c01f874457aa20370c0d7058))

- Make minifier test path explicit ([#&#8203;11891](https://github.com/swc-project/swc/issues/11891)) ([e7cba97](https://github.com/swc-project/swc/commit/e7cba972ff25565208c4448accab19c259e6947c))

##### Security

- Save CI caches only on main ([#&#8203;11848](https://github.com/swc-project/swc/issues/11848)) ([7582529](https://github.com/swc-project/swc/commit/75825293150b548216bf5c08531e1850bd064fcb))

- Update rkyv and Rust dependencies ([#&#8203;11851](https://github.com/swc-project/swc/issues/11851)) ([20d92eb](https://github.com/swc-project/swc/commit/20d92eb3c8dee378f046a6bff839913600a1fbdb))

- Harden PR workflow permissions ([#&#8203;11849](https://github.com/swc-project/swc/issues/11849)) ([e199564](https://github.com/swc-project/swc/commit/e199564ebaae88ec121a777cf0ec4eec9644aed5))

</details>

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined).

🚦 **Automerge**: Enabled.

♻ **Rebasing**: Whenever PR is behind base branch, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0MC42Mi4xIiwidXBkYXRlZEluVmVyIjoiNDAuNjIuMSIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOlsiZGVwZW5kZW5jaWVzIl19-->

Reviewed-on: https://git.unespace.com/julien/apf_portal/pulls/204
Co-authored-by: APF Portal Bot <jgautier.webdev+apf-portal-bot@gmail.com>
Co-committed-by: APF Portal Bot <jgautier.webdev+apf-portal-bot@gmail.com>
2026-05-23 21:13:06 +02:00
julien e389567a3c fix(ci): move grpc-tools to optionalDependencies (revert NODE_OPTIONS attempt) (#200)
CI / commits (push) Has been skipped
CI / check (push) Successful in 5m33s
CI / scan (push) Successful in 6m39s
CI / a11y (push) Successful in 4m16s
CI / perf (push) Successful in 7m25s
Docs site / build (push) Successful in 3m29s
## Summary

The `NODE_OPTIONS=--use-system-ca` workaround merged in #199 did not clear the CI install failure — the runner still rejects the TLS chain to `node-precompiled-binaries.grpc.io`. Either the runner's OS CA bundle is missing the same intermediate Node's bundled set is missing, or `--use-system-ca` does not propagate down to the `node-fetch@2` that `node-pre-gyp` uses. Without runner shell access the exact reason is not worth chasing.

Switch angle: **the protoc binary `grpc-tools` downloads is never used in CI**. The generated TypeScript stubs in `apps/portal-bff/src/grpc/gen/` are committed per [ADR-0024](docs/decisions/0024-ai-service-relay-grpc-sse-bridge.md) §"Sub-decision 3 — vendored protos", so CI only needs to type-check them; protoc only runs when a developer regenerates from `apps/portal-bff/src/grpc/proto/apf-ai/*.proto`.

This PR moves `grpc-tools` from `devDependencies` to `optionalDependencies`. Per pnpm semantics, when an optional dep's install (including postinstall) fails, pnpm logs a warning and the overall install completes. CI's `pnpm install --frozen-lockfile` therefore succeeds even when the binary cannot be fetched; developer machines (where TLS validates) install grpc-tools normally and `pnpm grpc:codegen` works unchanged.

## What lands

- `package.json` — `grpc-tools: "^1.13.0"` moves out of `devDependencies` and into a new `optionalDependencies` block. The entry in `pnpm.onlyBuiltDependencies` stays — it controls whether pnpm *attempts* the postinstall, not whether failure is fatal. Local installs (where TLS works) still run the postinstall and download the binary.
- `pnpm-lock.yaml` — refreshed; the lockfile reflects the new optional-dep classification. No version changes to anything else.
- `.gitea/workflows/ci.yml` — revert the workflow-level `env: NODE_OPTIONS: --use-system-ca` block added in #199. Did not help; left in place it would be a misleading "this is supposed to fix CI TLS" signpost.
- `.gitea/workflows/docs-site.yml` — same revert.

## Notes for the reviewer

- **Why `optionalDependencies` is the right primitive.** pnpm 10 documents the contract: a package listed in `optionalDependencies` is *attempted*; if the install fails for any reason (platform mismatch, postinstall script error, network failure), the failure is logged and the overall command continues. That maps exactly to what this PR needs: try in CI, fail gracefully, succeed locally.
- **Why not remove `grpc-tools` from `pnpm.onlyBuiltDependencies` instead.** Removing it from the allowlist tells pnpm not to even *try* the postinstall, which would also fix CI. But it would also break the developer workflow — locally, the protoc binary would never download, and `pnpm grpc:codegen` would fail. The dev would have to manually trigger the build (`pnpm approve-builds` then `pnpm rebuild grpc-tools`), or worse, devs would commit accidental `package.json` mutations from `approve-builds`. The `optionalDependencies` route keeps the local DX identical.
- **Why revert the workflow `env:` blocks.** They do not help here, and leaving them in place implies that `--use-system-ca` is the canonical fix for this class of failure — which it is *not*, at least not for this runner / this CDN. If a future native dep hits a similar wall and `--use-system-ca` *does* fix it for that case, the env var lands in a focused PR with a real validation. Carrying it now as a "maybe useful later" workaround is noise.
- **Codegen workflow on developer machines.** Unchanged. `pnpm install` runs the postinstall (TLS validates locally), the protoc binary lands in `node_modules/`, `pnpm grpc:codegen` regenerates stubs. The only visible difference is a one-line `WARN GET_RESOLVED_FROM_REGISTRY ...` if a contributor ever encounters the same TLS failure locally (e.g., on a corporate-proxied machine) — pnpm will mark grpc-tools as failed-optional and the rest of the install proceeds; the dev can then debug their own network without blocking the whole repo.
- **Idempotence with CI's `--frozen-lockfile`.** The lockfile change is small (re-classifies grpc-tools from `dev` to `optional`) and lands in this PR. After merge, CI runs against the new lockfile; no further coordination needed.

## Test plan

- [x] `pnpm install` locally — clean, grpc-tools postinstall runs successfully (TLS validates), protoc binary present.
- [x] `pnpm grpc:codegen` — regenerates the TypeScript stubs identically (no diff in `apps/portal-bff/src/grpc/gen/`).
- [x] `pnpm nx run-many -t lint test build -p portal-shell,portal-admin,portal-bff,shared-ui,shared-charts` — all five projects green.
- [ ] **CI green on this PR's first run.** The validation that matters: `pnpm install --frozen-lockfile` completes despite grpc-tools' postinstall failure; `ci:check` runs to completion.
- [ ] Spot-check of post-merge CI runs over the next few days to confirm the warning is recurring (expected) and the install never fails (the contract).

## What's next

- If the CI behaviour is what this PR predicts (warning instead of failure), no further action required.
- If `optionalDependencies` does not work as documented (highly unlikely, but possible if the pnpm version has a regression), the fallback is to drop `grpc-tools` from `pnpm.onlyBuiltDependencies` and add a small dev-onboarding note. One-line change away.
- Long-term cleanup: if a future PR migrates the codegen step to a Docker-based tool (`buf`, system protoc) the optional-dep entry can be removed entirely. Out of scope here.

---------

Co-authored-by: Julien Gautier <julien.gautier@apf.asso.fr>
Reviewed-on: #200
2026-05-20 15:06:25 +02:00
julien 9b7d16601d feat(portal-bff): ai-client skeleton — vendored protos + grpc client + Principal mapper (#195)
CI / commits (push) Has been skipped
CI / scan (push) Successful in 3m6s
CI / check (push) Successful in 5m33s
CI / a11y (push) Successful in 2m33s
CI / perf (push) Successful in 6m33s
Docs site / build (push) Successful in 2m24s
## Summary

Step 2 of the AI-relay chantier (after [ADR-0024](docs/decisions/0024-ai-service-relay-grpc-sse-bridge.md) merged in #194). Lands the BFF-side **skeleton** that talks to `apf-ai-service` over gRPC: vendored protos, generated TypeScript stubs, typed wrapper clients, Principal mapper, and metadata builder — all tested against an in-process fake gRPC server. **No HTTP route is exposed in this PR**; the SSE bridge (`POST /api/ai/chat`, `GET /api/ai/rag/search`, `GET /api/ai/models`) ships in the next PR.

The skeleton is self-contained: `AiClientModule` is built but is NOT imported in `AppModule` yet. The BFF runtime is byte-for-byte unchanged. Everything below exists for the next PR to wire into a controller.

## What lands

### Proto vendoring + codegen

- `apps/portal-bff/src/grpc/proto/apf-ai/` — mirror of `apf-ai-service/contract/proto/` (common, chat, rag, ingestion, models). Both the `.proto` files and the regenerated `ts-proto` output under `grpc/gen/` are committed for hermetic builds and reviewable diffs (per ADR-0024 §"Sub-decision 3").
- `pnpm run grpc:codegen` — regenerates the stubs via `grpc-tools`' bundled `protoc` and the `ts-proto` plugin (`outputServices=grpc-js, esModuleInterop, forceLong=long, useOptionals=messages, exportCommonSymbols=false`).
- `pnpm run grpc:sync` — copies the vendored `.proto` files from the sibling `apf-ai-service` working tree (`../apf-ai-service/contract/proto/`); developer convenience, never invoked from CI. Errors with an actionable message when the sibling tree is not where it expects.
- Generated tree (`grpc/gen/**`) excluded from Prettier (`.prettierignore`) and ESLint (`eslint.config.mjs` ignores). Hand-rules apply to wrappers under `ai-client/`, not to codegen output.

### Dependencies

- `@grpc/grpc-js@^1.13.0` — runtime gRPC client.
- `@bufbuild/protobuf@^2.10.2` — wire codec used by `ts-proto`'s emitted code.
- `long@^5.2.3` — int64 representation for proto Long fields (`forceLong=long`).
- `ts-proto@^2.7.0` — devDep, TypeScript codegen plugin.
- `grpc-tools@^1.13.0` — devDep, ships `protoc` + the gRPC plugin; added to `pnpm.onlyBuiltDependencies` so the postinstall binary download runs.

### Env validator

`apps/portal-bff/src/config/check-ai-service-config.ts` follows the same posture as the other validators (per ADR-0018 §"BFF env-var loading"): small, per-key, runs at module init, throws with an actionable message on misconfiguration. Three vars:

| Var | Mandatory | Purpose |
|---|---|---|
| `AI_SERVICE_GRPC_ENDPOINT` | yes | `host:port` reachable from the BFF — `apf-ai-service:8080` in dev Compose, service DNS + 443 in prod |
| `AI_SERVICE_CLIENT_ID` | yes | Deployment slug propagated as the `x-client-id` metadata. Convention: `apf-portal-<env>` |
| `AI_SERVICE_GRPC_TLS` | no (default `true`) | `false` for h2c in dev, `true` for h2 + TLS in prod |

7 spec cases lock the validation contract end-to-end.

### `AiClientModule`

`apps/portal-bff/src/grpc/ai-client/` houses:

- **`tokens.ts`** — DI tokens (`AI_CONFIG`, `AI_CREDENTIALS`, one per generated stub).
- **`principal.mapper.ts`** — `PrincipalMapper.fromInputs({oid, tid, roles, extraAttributes})` returns the proto `Principal`. `subject` is hashed via `HashUserIdService` (the **same** salt + algorithm the audit writer uses) so `Principal.subject` matches `audit.events.actor_id_hash` byte-for-byte. `roles` passes through verbatim — inclusive expansion lands with a future role-hierarchy ADR; the wire contract on the AI side is unchanged.
- **`grpc-metadata.builder.ts`** — stamps every outbound call with `x-client-id` (from config) and `x-correlation-id` (active OTel span's trace-id when present, else explicit override, else fresh UUID).
- **`chat.client.ts`** — server-stream wrapper around `ChatServiceClient`. Returns the raw `ClientReadableStream<ChatEvent>` (Node `Readable` is async-iterable so the SSE bridge consumes with `for await`). Optional `AbortSignal` propagates browser disconnect to `call.cancel()`.
- **`rag.client.ts`**, **`models.client.ts`**, **`ingestion.client.ts`** — unary promisified wrappers. `IngestionClient` is unused in v1 (the admin "manage AI corpus" surface lands later) but wired now so the future controller is one new file, not a new module.
- **`ai-client.module.ts`** — NestJS module wiring the providers. `HashUserIdService` is declared locally rather than imported via `AuditModule` (the global module) to keep the module unit-testable in isolation; runtime equivalence is guaranteed by the service being a pure function of `LOG_USER_ID_SALT`.

### Tests

5 new spec files, 18 new test cases. All run against in-process fake gRPC servers; no network, no live `apf-ai-service`:

- `check-ai-service-config.spec.ts` — 7 cases (happy path + 6 rejection branches).
- `principal.mapper.spec.ts` — 7 cases including the cross-service hash-stability invariant and the `tenantId` reserved-key contract.
- `grpc-metadata.builder.spec.ts` — 5 cases covering all three correlation-id resolution paths and metadata immutability.
- `chat.client.spec.ts` — 4 cases: happy-path stream, metadata propagation, mid-stream cancel via AbortSignal, pre-aborted signal.
- `rag.client.spec.ts` — 2 cases: unary happy path, `ServiceError` propagation.
- `ai-client.module.spec.ts` — 4 cases: module bootstrap, all four wrappers resolved, env-driven `AI_CONFIG`, shared credentials across stubs.

**Total BFF spec suite: 443 → 461 (after merge accounting), all passing.**

## Notes for the reviewer

- **Why both `.proto` and generated `.ts` are committed.** Hermetic builds. Reviewers see both sides of a contract change in one diff. CI never runs codegen — drift between proto and stub would otherwise hide behind a successful build. The drift gate that compares the vendored copy against an upstream tag of `apf-ai-service` is the post-v1 follow-up listed in ADR-0024's "What's next".
- **`HashUserIdService` declared locally in `AiClientModule`.** Two instances of the service exist when both `AuditModule` (global) and `AiClientModule` are wired into `AppModule`. The cost is one extra constructor call at bootstrap; the value is full test isolation of `AiClientModule` and a self-contained Principal-mapping boundary. The cross-service hash join invariant from ADR-0013 still holds because the service is a pure function of the `LOG_USER_ID_SALT` env var.
- **`AiClientModule` is NOT imported by `AppModule`.** Deliberately. The skeleton compiles, type-checks, and unit-tests cleanly with no runtime side effects. The next PR (SSE bridge controller) adds the `imports: [AiClientModule]` line + the controller, in one focused change.
- **`ts-proto` flat-oneof emission.** `ChatEvent` is generated with optional siblings (`token?`, `citation?`, `done?`, …) rather than a discriminated union (`$case: 'token'`). The flatter shape composes more naturally with the SSE writer the next PR will introduce (`event:` field name maps directly to the populated sibling).
- **Cancellation test deliberately relaxed.** The "AbortSignal already aborted before call dial" test asserts the client-side outcome (no payload, error or clean end) but not server-side observation. gRPC-js may or may not propagate a cancel frame depending on whether the call had time to dial — both outcomes are correct per the contract; only the absence of payload matters.
- **Lifecycle (`onApplicationShutdown`) deferred.** The module does not close the gRPC channel on shutdown today. Process termination closes the sockets via OS-level descriptor reclaim — sufficient for dev/preprod. The next PR wires the module into `AppModule` and adds an explicit Nest lifecycle hook in the same change (paired with `app.enableShutdownHooks()` in `main.ts`).

## Test plan

- [x] `pnpm run grpc:codegen` — clean regeneration. Generated tree byte-identical to what's committed.
- [x] `pnpm nx test portal-bff` — **443 specs pass** (was 425).
- [x] `pnpm nx lint portal-bff` — clean. The eslint ignore for `grpc/gen/**` covers ts-proto's relaxed style; hand-written `ai-client/` files pass the project's full rule set.
- [x] `pnpm nx build portal-bff` — clean, webpack-compiled. No bundle-size delta on the runtime artefact yet (the module is unimported).
- [x] `pnpm install` — lockfile reconciled; `grpc-tools` postinstall fetches `protoc` from the precompiled-binaries mirror without errors on Linux x64.
- [ ] **Manual smoke (next PR)** — once the SSE bridge ships, point `AI_SERVICE_GRPC_ENDPOINT` at the local `apf-ai-service` Compose service and run an end-to-end chat against the canned stub responses on the AI side. Not in scope for this PR.

## What's next

1. **PR — SSE bridge controller.** Wires `AiClientModule` into `AppModule`, adds `POST /api/ai/chat` (SSE), `GET /api/ai/rag/search`, `GET /api/ai/models`. Adds the `OnApplicationShutdown` hook + `enableShutdownHooks()`. Adds `apf-ai-service` to `infra/local/dev.compose.yml`. Promotes ADR-0024 from `proposed` to `accepted` and updates `CLAUDE.md`'s ADR roll-up.
2. **PR (frontend chantier)** — chatbot widget on `portal-shell` consuming the SSE endpoint.
3. **PR (post-v1)** — proto-drift CI gate diffing the vendored `proto/apf-ai/` against the upstream tag.
4. **Coordinated amendment** — when the first production deployment is in scope, both repos record the same prod-hardening choice (signed envelope or mTLS) on the same date.

---------

Co-authored-by: Julien Gautier <julien.gautier@apf.asso.fr>
Reviewed-on: #195
2026-05-19 21:30:04 +02:00
julien 8136695fa8 chore(deps): bump brace-expansion + ws overrides to clear audit (#190)
CI / check (push) Successful in 5m10s
CI / commits (push) Has been skipped
CI / scan (push) Successful in 2m27s
CI / a11y (push) Successful in 2m11s
CI / perf (push) Successful in 5m18s
Docs site / build (push) Successful in 5m1s
## Summary

Clears the two moderate GHSA advisories that started failing `ci:audit`:

- **`brace-expansion`** `>=5.0.0 <5.0.6` — DoS via large numeric range that bypasses the documented `max` protection ([GHSA-jxxr-4gwj-5jf2](https://github.com/advisories/GHSA-jxxr-4gwj-5jf2)). The existing override floor was at `5.0.5`; bumped one patch to `5.0.6`.
- **`ws`** `>=8.0.0 <8.20.1` — uninitialized memory disclosure ([GHSA-58qx-3vcg-4xpx](https://github.com/advisories/GHSA-58qx-3vcg-4xpx)). No prior override; added one scoped to the 8.x advisory window only.

Both are reached transitively through Nx; nothing in this repo's own dependency surface is vulnerable directly.

## What lands

`package.json` (`pnpm.overrides` block):

```diff
- "brace-expansion@<5.0.5": ">=5.0.5",
+ "brace-expansion@<5.0.6": ">=5.0.6",

+ "ws@>=8.0.0 <8.20.1": ">=8.20.1",
```

`pnpm-lock.yaml` reconciled with `pnpm install`.

## Notes for the reviewer

- **Why the `ws` override is range-scoped, not a blanket `ws@<8.20.1`.** Lighthouse pulls `ws@7.5.10` via its own tree; the 7.x line sits entirely outside the advisory window (`>=8.0.0 <8.20.1`). A blanket `<8.20.1` override would force-bump that transitive across a major version boundary and risk Lighthouse breakage with no security gain. The range form `>=8.0.0 <8.20.1 → >=8.20.1` patches exactly the vulnerable consumers (`@module-federation/dts-plugin`, etc.) and leaves Lighthouse's pin untouched.
- **Why an override and not waiting for Nx to bump.** Nx 22.7.2 is already on `main` (commits `bd94bb4` / `6b20c34`) but its sub-chain still resolves `ws@8.18.0` and `brace-expansion@5.0.5`. Upstream pickup will land eventually via Renovate; in the meantime the audit gate blocks CI on every PR. Overrides are the standard pnpm escape hatch for this exact situation. The pattern is already used in this file (`axios`, `ajv`, `esbuild`, `follow-redirects`, `ip-address`, `protobufjs`, `tmp`, `yaml`).
- **Pruning policy.** Once Nx ships a release whose `pnpm-lock.yaml` resolves both packages at or above the patched versions on its own, both override entries can be removed. The convention in this file's existing entries is to leave overrides in place even when redundant (cheap insurance against silent regressions); pruning is a separate sweep, not part of this fix.
- **No application code touched.** Both packages live deep in the Nx/Module Federation build tooling — `brace-expansion` inside `minimatch`'s glob expansion, `ws` inside Module Federation's HMR dev socket. Neither surfaces in the BFF or SPA runtime bundles. Build + test + lint were re-run across `portal-shell`, `portal-admin`, `portal-bff`, `shared-charts`, `shared-ui` as a sanity check; all green.

## Test plan

- [x] `pnpm install` — lockfile reconciled, no extraneous package churn.
- [x] `pnpm audit --audit-level=moderate` — "No known vulnerabilities found" (replaces the two-row failure that started this PR).
- [x] `pnpm nx run-many -t build test lint -p portal-shell,portal-admin,portal-bff,shared-charts,shared-ui` — all green. Module Federation's HMR socket (the surface that *uses* `ws`) is exercised implicitly by every Angular build via `@nx/angular`'s webpack pipeline.
- [ ] **Manual smoke** — `pnpm nx serve portal-shell` + `pnpm nx serve portal-admin`: dev servers come up, HMR reloads on a trivial edit, no warnings or stack traces about `ws` or `brace-expansion` resolution.

## What's next

- Renovate will eventually pick up an Nx release whose own sub-chain ships `ws@>=8.20.1` and `brace-expansion@>=5.0.6` natively. At that point, the two override entries here are dead weight and can be pruned as a follow-up sweep alongside any other stale overrides.

---------

Co-authored-by: Julien Gautier <julien.gautier@apf.asso.fr>
Reviewed-on: #190
2026-05-19 12:28:13 +02:00
APF Portal Bot 93cf30679e fix(deps): update opentelemetry-js-contrib monorepo (#184)
CI / scan (push) Successful in 4m43s
CI / commits (push) Has been skipped
CI / check (push) Successful in 7m47s
CI / a11y (push) Successful in 3m17s
CI / perf (push) Successful in 5m56s
Docs site / build (push) Successful in 3m15s
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [@opentelemetry/instrumentation-document-load](https://github.com/open-telemetry/opentelemetry-js-contrib/tree/main/packages/instrumentation-document-load#readme) ([source](https://github.com/open-telemetry/opentelemetry-js-contrib/tree/HEAD/packages/instrumentation-document-load)) | dependencies | minor | [`^0.62.0` -> `^0.63.0`](https://renovatebot.com/diffs/npm/@opentelemetry%2finstrumentation-document-load/0.62.0/0.63.0) |
| [@opentelemetry/instrumentation-express](https://github.com/open-telemetry/opentelemetry-js-contrib/tree/main/packages/instrumentation-express#readme) ([source](https://github.com/open-telemetry/opentelemetry-js-contrib/tree/HEAD/packages/instrumentation-express)) | dependencies | minor | [`^0.65.0` -> `^0.66.0`](https://renovatebot.com/diffs/npm/@opentelemetry%2finstrumentation-express/0.65.0/0.66.0) |
| [@opentelemetry/instrumentation-ioredis](https://github.com/open-telemetry/opentelemetry-js-contrib/tree/main/packages/instrumentation-ioredis#readme) ([source](https://github.com/open-telemetry/opentelemetry-js-contrib/tree/HEAD/packages/instrumentation-ioredis)) | dependencies | minor | [`^0.65.0` -> `^0.66.0`](https://renovatebot.com/diffs/npm/@opentelemetry%2finstrumentation-ioredis/0.65.0/0.66.0) |
| [@opentelemetry/instrumentation-nestjs-core](https://github.com/open-telemetry/opentelemetry-js-contrib/tree/main/packages/instrumentation-nestjs-core#readme) ([source](https://github.com/open-telemetry/opentelemetry-js-contrib/tree/HEAD/packages/instrumentation-nestjs-core)) | dependencies | minor | [`^0.63.0` -> `^0.64.0`](https://renovatebot.com/diffs/npm/@opentelemetry%2finstrumentation-nestjs-core/0.63.0/0.64.0) |
| [@opentelemetry/instrumentation-pg](https://github.com/open-telemetry/opentelemetry-js-contrib/tree/main/packages/instrumentation-pg#readme) ([source](https://github.com/open-telemetry/opentelemetry-js-contrib/tree/HEAD/packages/instrumentation-pg)) | dependencies | minor | [`^0.69.0` -> `^0.70.0`](https://renovatebot.com/diffs/npm/@opentelemetry%2finstrumentation-pg/0.69.0/0.70.0) |
| [@opentelemetry/instrumentation-pino](https://github.com/open-telemetry/opentelemetry-js-contrib/tree/main/packages/instrumentation-pino#readme) ([source](https://github.com/open-telemetry/opentelemetry-js-contrib/tree/HEAD/packages/instrumentation-pino)) | dependencies | minor | [`^0.63.0` -> `^0.64.0`](https://renovatebot.com/diffs/npm/@opentelemetry%2finstrumentation-pino/0.63.0/0.64.0) |
| [@opentelemetry/instrumentation-user-interaction](https://github.com/open-telemetry/opentelemetry-js-contrib/tree/main/packages/instrumentation-user-interaction#readme) ([source](https://github.com/open-telemetry/opentelemetry-js-contrib/tree/HEAD/packages/instrumentation-user-interaction)) | dependencies | minor | [`^0.61.0` -> `^0.62.0`](https://renovatebot.com/diffs/npm/@opentelemetry%2finstrumentation-user-interaction/0.61.0/0.62.0) |

---

### Release Notes

<details>
<summary>open-telemetry/opentelemetry-js-contrib (@&#8203;opentelemetry/instrumentation-document-load)</summary>

### [`v0.63.0`](https://github.com/open-telemetry/opentelemetry-js-contrib/blob/HEAD/packages/instrumentation-document-load/CHANGELOG.md#0630-2026-05-13)

[Compare Source](https://github.com/open-telemetry/opentelemetry-js-contrib/compare/b68fb6dcc0649631ebecab7bde81879486f74f0b...15ef7506553f631ea4181391e0c5725a56f0d082)

##### Features

- **deps:** update deps matching '@&#8203;opentelemetry/\*' ([#&#8203;3523](https://github.com/open-telemetry/opentelemetry-js-contrib/issues/3523)) ([e26a90a](https://github.com/open-telemetry/opentelemetry-js-contrib/commit/e26a90af6e2fb4666b22388b770add7a60140c9b))

</details>

<details>
<summary>open-telemetry/opentelemetry-js-contrib (@&#8203;opentelemetry/instrumentation-express)</summary>

### [`v0.66.0`](https://github.com/open-telemetry/opentelemetry-js-contrib/blob/HEAD/packages/instrumentation-express/CHANGELOG.md#0660-2026-05-13)

[Compare Source](https://github.com/open-telemetry/opentelemetry-js-contrib/compare/b68fb6dcc0649631ebecab7bde81879486f74f0b...15ef7506553f631ea4181391e0c5725a56f0d082)

##### Features

- **deps:** update deps matching '@&#8203;opentelemetry/\*' ([#&#8203;3523](https://github.com/open-telemetry/opentelemetry-js-contrib/issues/3523)) ([e26a90a](https://github.com/open-telemetry/opentelemetry-js-contrib/commit/e26a90af6e2fb4666b22388b770add7a60140c9b))

##### Dependencies

- The following workspace dependencies were updated
  - devDependencies
    - [@&#8203;opentelemetry/contrib-test-utils](https://github.com/opentelemetry/contrib-test-utils) bumped from ^0.64.0 to ^0.65.0

</details>

<details>
<summary>open-telemetry/opentelemetry-js-contrib (@&#8203;opentelemetry/instrumentation-ioredis)</summary>

### [`v0.66.0`](https://github.com/open-telemetry/opentelemetry-js-contrib/blob/HEAD/packages/instrumentation-ioredis/CHANGELOG.md#0660-2026-05-13)

[Compare Source](https://github.com/open-telemetry/opentelemetry-js-contrib/compare/b68fb6dcc0649631ebecab7bde81879486f74f0b...15ef7506553f631ea4181391e0c5725a56f0d082)

##### Features

- **deps:** update deps matching '@&#8203;opentelemetry/\*' ([#&#8203;3523](https://github.com/open-telemetry/opentelemetry-js-contrib/issues/3523)) ([e26a90a](https://github.com/open-telemetry/opentelemetry-js-contrib/commit/e26a90af6e2fb4666b22388b770add7a60140c9b))

##### Dependencies

- The following workspace dependencies were updated
  - devDependencies
    - [@&#8203;opentelemetry/contrib-test-utils](https://github.com/opentelemetry/contrib-test-utils) bumped from ^0.64.0 to ^0.65.0

</details>

<details>
<summary>open-telemetry/opentelemetry-js-contrib (@&#8203;opentelemetry/instrumentation-nestjs-core)</summary>

### [`v0.64.0`](https://github.com/open-telemetry/opentelemetry-js-contrib/blob/HEAD/packages/instrumentation-nestjs-core/CHANGELOG.md#0640-2026-05-13)

[Compare Source](https://github.com/open-telemetry/opentelemetry-js-contrib/compare/b68fb6dcc0649631ebecab7bde81879486f74f0b...15ef7506553f631ea4181391e0c5725a56f0d082)

##### Features

- **deps:** update deps matching '@&#8203;opentelemetry/\*' ([#&#8203;3523](https://github.com/open-telemetry/opentelemetry-js-contrib/issues/3523)) ([e26a90a](https://github.com/open-telemetry/opentelemetry-js-contrib/commit/e26a90af6e2fb4666b22388b770add7a60140c9b))

</details>

<details>
<summary>open-telemetry/opentelemetry-js-contrib (@&#8203;opentelemetry/instrumentation-pg)</summary>

### [`v0.70.0`](https://github.com/open-telemetry/opentelemetry-js-contrib/blob/HEAD/packages/instrumentation-pg/CHANGELOG.md#0700-2026-05-13)

[Compare Source](https://github.com/open-telemetry/opentelemetry-js-contrib/compare/b68fb6dcc0649631ebecab7bde81879486f74f0b...15ef7506553f631ea4181391e0c5725a56f0d082)

##### Features

- **deps:** update deps matching '@&#8203;opentelemetry/\*' ([#&#8203;3523](https://github.com/open-telemetry/opentelemetry-js-contrib/issues/3523)) ([e26a90a](https://github.com/open-telemetry/opentelemetry-js-contrib/commit/e26a90af6e2fb4666b22388b770add7a60140c9b))

##### Dependencies

- The following workspace dependencies were updated
  - devDependencies
    - [@&#8203;opentelemetry/contrib-test-utils](https://github.com/opentelemetry/contrib-test-utils) bumped from ^0.64.0 to ^0.65.0

</details>

<details>
<summary>open-telemetry/opentelemetry-js-contrib (@&#8203;opentelemetry/instrumentation-pino)</summary>

### [`v0.64.0`](https://github.com/open-telemetry/opentelemetry-js-contrib/blob/HEAD/packages/instrumentation-pino/CHANGELOG.md#0640-2026-05-13)

[Compare Source](https://github.com/open-telemetry/opentelemetry-js-contrib/compare/b68fb6dcc0649631ebecab7bde81879486f74f0b...15ef7506553f631ea4181391e0c5725a56f0d082)

##### Features

- **deps:** update deps matching '@&#8203;opentelemetry/\*' ([#&#8203;3523](https://github.com/open-telemetry/opentelemetry-js-contrib/issues/3523)) ([e26a90a](https://github.com/open-telemetry/opentelemetry-js-contrib/commit/e26a90af6e2fb4666b22388b770add7a60140c9b))

##### Dependencies

- The following workspace dependencies were updated
  - devDependencies
    - [@&#8203;opentelemetry/contrib-test-utils](https://github.com/opentelemetry/contrib-test-utils) bumped from ^0.64.0 to ^0.65.0

</details>

<details>
<summary>open-telemetry/opentelemetry-js-contrib (@&#8203;opentelemetry/instrumentation-user-interaction)</summary>

### [`v0.62.0`](https://github.com/open-telemetry/opentelemetry-js-contrib/blob/HEAD/packages/instrumentation-user-interaction/CHANGELOG.md#0620-2026-05-13)

[Compare Source](https://github.com/open-telemetry/opentelemetry-js-contrib/compare/b68fb6dcc0649631ebecab7bde81879486f74f0b...15ef7506553f631ea4181391e0c5725a56f0d082)

##### Features

- **deps:** update deps matching '@&#8203;opentelemetry/\*' ([#&#8203;3523](https://github.com/open-telemetry/opentelemetry-js-contrib/issues/3523)) ([e26a90a](https://github.com/open-telemetry/opentelemetry-js-contrib/commit/e26a90af6e2fb4666b22388b770add7a60140c9b))

</details>

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

👻 **Immortal**: This PR will be recreated if closed unmerged. Get [config help](https://github.com/renovatebot/renovate/discussions) if that's undesired.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0MC42Mi4xIiwidXBkYXRlZEluVmVyIjoiNDAuNjIuMSIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOlsiZGVwZW5kZW5jaWVzIl19-->

Reviewed-on: https://git.unespace.com/julien/apf_portal/pulls/184
Co-authored-by: APF Portal Bot <jgautier.webdev+apf-portal-bot@gmail.com>
Co-committed-by: APF Portal Bot <jgautier.webdev+apf-portal-bot@gmail.com>
2026-05-18 01:22:56 +02:00
APF Portal Bot ee59fd900c fix(deps): update opentelemetry-js monorepo (#183)
CI / scan (push) Successful in 1m32s
CI / commits (push) Has been skipped
CI / a11y (push) Successful in 1m53s
CI / perf (push) Successful in 4m57s
CI / check (push) Successful in 5m52s
Docs site / build (push) Successful in 2m14s
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [@opentelemetry/exporter-trace-otlp-http](https://github.com/open-telemetry/opentelemetry-js/tree/main/experimental/packages/exporter-trace-otlp-http) ([source](https://github.com/open-telemetry/opentelemetry-js)) | dependencies | minor | [`^0.217.0` -> `^0.218.0`](https://renovatebot.com/diffs/npm/@opentelemetry%2fexporter-trace-otlp-http/0.217.0/0.218.0) |
| [@opentelemetry/exporter-trace-otlp-proto](https://github.com/open-telemetry/opentelemetry-js/tree/main/experimental/packages/exporter-trace-otlp-proto) ([source](https://github.com/open-telemetry/opentelemetry-js)) | dependencies | minor | [`^0.217.0` -> `^0.218.0`](https://renovatebot.com/diffs/npm/@opentelemetry%2fexporter-trace-otlp-proto/0.217.0/0.218.0) |
| [@opentelemetry/instrumentation](https://github.com/open-telemetry/opentelemetry-js/tree/main/experimental/packages/opentelemetry-instrumentation) ([source](https://github.com/open-telemetry/opentelemetry-js)) | dependencies | minor | [`^0.217.0` -> `^0.218.0`](https://renovatebot.com/diffs/npm/@opentelemetry%2finstrumentation/0.217.0/0.218.0) |
| [@opentelemetry/instrumentation-fetch](https://github.com/open-telemetry/opentelemetry-js/tree/main/experimental/packages/opentelemetry-instrumentation-fetch) ([source](https://github.com/open-telemetry/opentelemetry-js)) | dependencies | minor | [`^0.217.0` -> `^0.218.0`](https://renovatebot.com/diffs/npm/@opentelemetry%2finstrumentation-fetch/0.217.0/0.218.0) |
| [@opentelemetry/instrumentation-http](https://github.com/open-telemetry/opentelemetry-js/tree/main/experimental/packages/opentelemetry-instrumentation-http) ([source](https://github.com/open-telemetry/opentelemetry-js)) | dependencies | minor | [`^0.217.0` -> `^0.218.0`](https://renovatebot.com/diffs/npm/@opentelemetry%2finstrumentation-http/0.217.0/0.218.0) |
| [@opentelemetry/sdk-node](https://github.com/open-telemetry/opentelemetry-js/tree/main/experimental/packages/opentelemetry-sdk-node) ([source](https://github.com/open-telemetry/opentelemetry-js)) | dependencies | minor | [`^0.217.0` -> `^0.218.0`](https://renovatebot.com/diffs/npm/@opentelemetry%2fsdk-node/0.217.0/0.218.0) |
| [@opentelemetry/semantic-conventions](https://github.com/open-telemetry/opentelemetry-js/tree/main/semantic-conventions) ([source](https://github.com/open-telemetry/opentelemetry-js)) | dependencies | minor | [`1.40.0` -> `1.41.1`](https://renovatebot.com/diffs/npm/@opentelemetry%2fsemantic-conventions/1.40.0/1.41.1) |

---

### Release Notes

<details>
<summary>open-telemetry/opentelemetry-js (@&#8203;opentelemetry/exporter-trace-otlp-http)</summary>

### [`v0.218.0`](https://github.com/open-telemetry/opentelemetry-js/compare/74cde1b674508ccc0ed2601ac43a80ff2d35114c...06ad0eaaecbd49f5ead871325f852cc2a3454079)

[Compare Source](https://github.com/open-telemetry/opentelemetry-js/compare/74cde1b674508ccc0ed2601ac43a80ff2d35114c...06ad0eaaecbd49f5ead871325f852cc2a3454079)

</details>

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

👻 **Immortal**: This PR will be recreated if closed unmerged. Get [config help](https://github.com/renovatebot/renovate/discussions) if that's undesired.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0MC42Mi4xIiwidXBkYXRlZEluVmVyIjoiNDAuNjIuMSIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOlsiZGVwZW5kZW5jaWVzIl19-->

Reviewed-on: #183
Co-authored-by: APF Portal Bot <jgautier.webdev+apf-portal-bot@gmail.com>
Co-committed-by: APF Portal Bot <jgautier.webdev+apf-portal-bot@gmail.com>
2026-05-17 23:47:04 +02:00
APF Portal Bot e376ea1295 chore(deps): update dependency @oxc-project/runtime to ^0.131.0 (#181)
CI / scan (push) Successful in 3m7s
CI / commits (push) Has been skipped
CI / check (push) Successful in 6m30s
CI / a11y (push) Successful in 2m53s
CI / perf (push) Successful in 9m49s
Docs site / build (push) Successful in 5m35s
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [@oxc-project/runtime](https://oxc.rs) ([source](https://github.com/oxc-project/oxc/tree/HEAD/npm/runtime)) | devDependencies | minor | [`^0.129.0` -> `^0.131.0`](https://renovatebot.com/diffs/npm/@oxc-project%2fruntime/0.129.0/0.131.0) |

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0MC42Mi4xIiwidXBkYXRlZEluVmVyIjoiNDAuNjIuMSIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOlsiZGVwZW5kZW5jaWVzIl19-->

Reviewed-on: #181
Co-authored-by: APF Portal Bot <jgautier.webdev+apf-portal-bot@gmail.com>
Co-committed-by: APF Portal Bot <jgautier.webdev+apf-portal-bot@gmail.com>
2026-05-17 23:42:58 +02:00
APF Portal Bot bd94bb4ffe fix(deps): update nx to v22.7.2 (#179)
CI / commits (push) Has been skipped
CI / scan (push) Successful in 4m26s
CI / check (push) Successful in 7m6s
CI / a11y (push) Successful in 2m46s
CI / perf (push) Successful in 9m15s
Docs site / build (push) Successful in 3m56s
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [@nx/devkit](https://nx.dev) ([source](https://github.com/nrwl/nx/tree/HEAD/packages/devkit)) | devDependencies | patch | [`22.7.1` -> `22.7.2`](https://renovatebot.com/diffs/npm/@nx%2fdevkit/22.7.1/22.7.2) |
| [@nx/eslint-plugin](https://nx.dev) ([source](https://github.com/nrwl/nx/tree/HEAD/packages/eslint-plugin)) | devDependencies | patch | [`22.7.1` -> `22.7.2`](https://renovatebot.com/diffs/npm/@nx%2feslint-plugin/22.7.1/22.7.2) |
| [@nx/jest](https://nx.dev) ([source](https://github.com/nrwl/nx/tree/HEAD/packages/jest)) | devDependencies | patch | [`22.7.1` -> `22.7.2`](https://renovatebot.com/diffs/npm/@nx%2fjest/22.7.1/22.7.2) |
| [@nx/js](https://nx.dev) ([source](https://github.com/nrwl/nx/tree/HEAD/packages/js)) | devDependencies | patch | [`22.7.1` -> `22.7.2`](https://renovatebot.com/diffs/npm/@nx%2fjs/22.7.1/22.7.2) |
| [@nx/node](https://nx.dev) ([source](https://github.com/nrwl/nx/tree/HEAD/packages/node)) | devDependencies | patch | [`22.7.1` -> `22.7.2`](https://renovatebot.com/diffs/npm/@nx%2fnode/22.7.1/22.7.2) |
| [@nx/playwright](https://nx.dev) ([source](https://github.com/nrwl/nx/tree/HEAD/packages/playwright)) | devDependencies | patch | [`22.7.1` -> `22.7.2`](https://renovatebot.com/diffs/npm/@nx%2fplaywright/22.7.1/22.7.2) |
| [@nx/vite](https://nx.dev) ([source](https://github.com/nrwl/nx/tree/HEAD/packages/vite)) | dependencies | patch | [`22.7.1` -> `22.7.2`](https://renovatebot.com/diffs/npm/@nx%2fvite/22.7.1/22.7.2) |
| [@nx/vitest](https://nx.dev) ([source](https://github.com/nrwl/nx/tree/HEAD/packages/vitest)) | devDependencies | patch | [`22.7.1` -> `22.7.2`](https://renovatebot.com/diffs/npm/@nx%2fvitest/22.7.1/22.7.2) |
| [@nx/web](https://nx.dev) ([source](https://github.com/nrwl/nx/tree/HEAD/packages/web)) | devDependencies | patch | [`22.7.1` -> `22.7.2`](https://renovatebot.com/diffs/npm/@nx%2fweb/22.7.1/22.7.2) |
| [@nx/webpack](https://nx.dev) ([source](https://github.com/nrwl/nx/tree/HEAD/packages/webpack)) | devDependencies | patch | [`22.7.1` -> `22.7.2`](https://renovatebot.com/diffs/npm/@nx%2fwebpack/22.7.1/22.7.2) |
| [nx](https://nx.dev) ([source](https://github.com/nrwl/nx/tree/HEAD/packages/nx)) | devDependencies | patch | [`22.7.1` -> `22.7.2`](https://renovatebot.com/diffs/npm/nx/22.7.1/22.7.2) |

---

### Release Notes

<details>
<summary>nrwl/nx (@&#8203;nx/devkit)</summary>

### [`v22.7.2`](https://github.com/nrwl/nx/releases/tag/22.7.2)

[Compare Source](https://github.com/nrwl/nx/compare/22.7.1...22.7.2)

##### 22.7.2 (2026-05-14)

##### 🚀 Features

- **gradle:** stream batch task results to nx as they finish ([#&#8203;35487](https://github.com/nrwl/nx/pull/35487))
- **nx-dev:** track docs analytics for code copy, LLM prompt, YouTube ([#&#8203;35526](https://github.com/nrwl/nx/pull/35526))
- **testing:** add migration for Jest 30 snapshot guide link ([#&#8203;35629](https://github.com/nrwl/nx/pull/35629))

##### 🩹 Fixes

- **angular:** disable vitest watch by default ([#&#8203;35493](https://github.com/nrwl/nx/pull/35493))
- **angular-rspack:** keep root-scoped assets out of per-locale i18n emit ([#&#8203;35621](https://github.com/nrwl/nx/pull/35621))
- **bundling:** include tsconfig solution input for rollup ([#&#8203;35476](https://github.com/nrwl/nx/pull/35476))
- **bundling:** include tsconfig solution input for webpack ([#&#8203;35477](https://github.com/nrwl/nx/pull/35477), [#&#8203;35476](https://github.com/nrwl/nx/issues/35476))
- **core:** bump axios to 1.16.0 for all packages ([#&#8203;35568](https://github.com/nrwl/nx/pull/35568))
- **core:** add provenance check in nx console status path ([#&#8203;35485](https://github.com/nrwl/nx/pull/35485))
- **core:** remove access control header from graph app ([#&#8203;35494](https://github.com/nrwl/nx/pull/35494))
- **core:** ensure verbose logs go to stderr and daemon logs are properly decorated ([#&#8203;34358](https://github.com/nrwl/nx/pull/34358))
- **core:** show flaky-task count in run summary ([#&#8203;35491](https://github.com/nrwl/nx/pull/35491))
- **core:** unique telemetry user\_id; expose workspace\_id dimension ([#&#8203;35553](https://github.com/nrwl/nx/pull/35553))
- **core:** update minimatch to 10.2.5 ([#&#8203;35569](https://github.com/nrwl/nx/pull/35569), [#&#8203;34660](https://github.com/nrwl/nx/issues/34660))
- **core:** restore use-legacy-versioning shim for [@&#8203;nx/js](https://github.com/nx/js)[@&#8203;21](https://github.com/21) ensurePackage path ([#&#8203;35574](https://github.com/nrwl/nx/pull/35574))
- **core:** isolate NX\_PARALLEL env var in parallel-related specs ([#&#8203;35579](https://github.com/nrwl/nx/pull/35579))
- **core:** skip handleimport miss path when nx key packages are absent ([#&#8203;35596](https://github.com/nrwl/nx/pull/35596))
- **core:** use gethostuuid(3) instead of ioreg on macOS ([#&#8203;35599](https://github.com/nrwl/nx/pull/35599))
- **core:** isolate cache env vars in splitArgs spec ([#&#8203;35584](https://github.com/nrwl/nx/pull/35584))
- **core:** enable node's native v8 compile cache support ([#&#8203;35415](https://github.com/nrwl/nx/pull/35415), [#&#8203;20454](https://github.com/nrwl/nx/issues/20454))
- **core:** support skipped batch tasks end-to-end and fix TUI double logs ([#&#8203;35617](https://github.com/nrwl/nx/pull/35617))
- **core:** keep TUI task selection on the in-progress section ([#&#8203;35640](https://github.com/nrwl/nx/pull/35640))
- **core:** allow `nx mcp` to run outside of an Nx workspace ([#&#8203;35655](https://github.com/nrwl/nx/pull/35655))
- **core:** cast perf entries to PerformanceMeasure for detail access ([43c0c821ba](https://github.com/nrwl/nx/commit/43c0c821ba))
- **devkit:** exclude dist from jest module path scan ([#&#8203;35615](https://github.com/nrwl/nx/pull/35615))
- **devkit:** expand @&#8203;nx/devkit/internal re-exports for cherry-picked v23 deep-import migration ([#&#8203;35541](https://github.com/nrwl/nx/issues/35541))
- **dotnet:** correct output paths for Web SDK and centralized dist setups ([#&#8203;35398](https://github.com/nrwl/nx/pull/35398))
- **gradle:** exclude batch-runner from jest haste-map crawl ([#&#8203;35501](https://github.com/nrwl/nx/pull/35501))
- **gradle:** exclude project-graph from jest module path scan ([#&#8203;35609](https://github.com/nrwl/nx/pull/35609))
- **gradle:** support Windows file paths ([#&#8203;35184](https://github.com/nrwl/nx/pull/35184), [#&#8203;34987](https://github.com/nrwl/nx/issues/34987))
- **js:** strip glob from inferred outputs before resolving as path ([#&#8203;35463](https://github.com/nrwl/nx/pull/35463), [#&#8203;35452](https://github.com/nrwl/nx/issues/35452))
- **js:** reference vitest.config in eslint dep-checks for vitest libs ([#&#8203;35460](https://github.com/nrwl/nx/pull/35460), [#&#8203;33670](https://github.com/nrwl/nx/issues/33670), [#&#8203;35450](https://github.com/nrwl/nx/issues/35450))
- **js:** include transitive workspace deps in pruned pnpm lockfile ([#&#8203;35532](https://github.com/nrwl/nx/pull/35532), [#&#8203;35347](https://github.com/nrwl/nx/issues/35347), [#&#8203;34655](https://github.com/nrwl/nx/issues/34655))
- **linter:** prevent ENOENT crash in getRelativeImportPath for unresolvable paths ([#&#8203;35007](https://github.com/nrwl/nx/pull/35007), [#&#8203;13872](https://github.com/nrwl/nx/issues/13872), [#&#8203;34066](https://github.com/nrwl/nx/issues/34066), [#&#8203;30491](https://github.com/nrwl/nx/issues/30491), [#&#8203;16716](https://github.com/nrwl/nx/issues/16716), [#&#8203;35006](https://github.com/nrwl/nx/issues/35006), [#&#8203;21889](https://github.com/nrwl/nx/issues/21889), [#&#8203;32190](https://github.com/nrwl/nx/issues/32190))
- **maven:** skip attached artifacts that fail to materialize in batch record ([#&#8203;35473](https://github.com/nrwl/nx/pull/35473))
- **maven:** serialize Maven 4 build state recording ([#&#8203;35555](https://github.com/nrwl/nx/pull/35555))
- **maven:** widen runCLI timeout for --no-batch maven.test.ts cases ([#&#8203;35589](https://github.com/nrwl/nx/pull/35589))
- **nx-dev:** document nested CLI subcommands beyond two levels ([#&#8203;35519](https://github.com/nrwl/nx/pull/35519))
- **nx-dev:** short-circuit bot probes in framer rewrite edge function ([#&#8203;35527](https://github.com/nrwl/nx/pull/35527))
- **react:** withSvgr migration preserves other properties ([#&#8203;35484](https://github.com/nrwl/nx/pull/35484))
- **repo:** clear NX\_INVOCATION\_ROOT\_PID in run-native-target to avoid recursion false-positive ([443dee0b22](https://github.com/nrwl/nx/commit/443dee0b22))
- **repo:** revert deep-import rewrites that targeted v23-only @&#8203;nx/devkit/internal entry ([ac8187963d](https://github.com/nrwl/nx/commit/ac8187963d))
- **repo:** unblock 22.7.x cargo tests and nx-build e2e ([#&#8203;34285](https://github.com/nrwl/nx/issues/34285))
- **repo:** expand "..." spread token in graph typecheck inputs ([#&#8203;34285](https://github.com/nrwl/nx/issues/34285), [#&#8203;35458](https://github.com/nrwl/nx/issues/35458))
- **testing:** pin jest to ~30.3.0 to avoid jest-runtime 30.4 RN incompat ([#&#8203;35618](https://github.com/nrwl/nx/pull/35618))
- **testing:** handle absolute cypress screenshotsFolder/videosFolder paths ([#&#8203;35624](https://github.com/nrwl/nx/pull/35624))
- **testing:** exclude dist and out-tsc from default jest module path scan ([#&#8203;35619](https://github.com/nrwl/nx/pull/35619))
- **testing:** update remaining snapshot guide links missed by migration ([cd350c1140](https://github.com/nrwl/nx/commit/cd350c1140))

##### ❤️ Thank You

- Adam Keenan [@&#8203;adamk33n3r](https://github.com/adamk33n3r)
- AgentEnder [@&#8203;AgentEnder](https://github.com/AgentEnder)
- beeman
- Claude
- Craigory Coppola [@&#8203;AgentEnder](https://github.com/AgentEnder)
- FrozenPandaz [@&#8203;FrozenPandaz](https://github.com/FrozenPandaz)
- Jack Hsu [@&#8203;jaysoo](https://github.com/jaysoo)
- Jason Jean [@&#8203;FrozenPandaz](https://github.com/FrozenPandaz)
- Leosvel Pérez Espinosa [@&#8203;leosvelperez](https://github.com/leosvelperez)
- Max Kless
- MaxKless [@&#8203;MaxKless](https://github.com/MaxKless)
- Optischa [@&#8203;Optischa](https://github.com/Optischa)
- Sharon Lougheed

</details>

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled because a matching PR was automerged previously.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about these updates again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0MC42Mi4xIiwidXBkYXRlZEluVmVyIjoiNDAuNjIuMSIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOlsiZGVwZW5kZW5jaWVzIl19-->

Reviewed-on: https://git.unespace.com/julien/apf_portal/pulls/179
Co-authored-by: APF Portal Bot <jgautier.webdev+apf-portal-bot@gmail.com>
Co-committed-by: APF Portal Bot <jgautier.webdev+apf-portal-bot@gmail.com>
2026-05-17 22:15:42 +02:00
julien eb8b65c7bc feat(shared-charts): foundations + bar / donut / stacked-bar components (#171)
CI / commits (push) Has been skipped
CI / check (push) Successful in 5m30s
CI / scan (push) Successful in 2m44s
CI / a11y (push) Successful in 2m37s
CI / perf (push) Successful in 4m21s
Docs site / build (push) Successful in 3m1s
## Summary

Implementation of [ADR-0023](docs/decisions/0023-charts-d3-observable-plot.md) — the foundations of the workspace's chart library. PR 2 of the chantier:

| PR | Périmètre |
| --- | --- |
| PR 1  | ADR-0023 — decision + a11y contract + bundle plan. |
| **PR 2 (this one)** | `libs/shared/charts/` foundations + `<lib-bar-chart>`, `<lib-donut-chart>`, `<lib-stacked-bar-chart>`. |
| PR 3 | Integration on the `/audit` page — daily-volume bar + outcome-breakdown donut + event-type-over-time stacked bar. |

## What lands

### Workspace deps

```
d3                       — top-level toolkit, types via @types/d3
d3-shape                 — used directly by <lib-donut-chart>
d3-scale-chromatic       — colour-blind-safe palette source
@observablehq/plot       — declarative layer over D3, used by bar + stacked-bar
```

All four (+ matching `@types/*`) land in the workspace root `devDependencies`. Tree-shaken at build time per ADR-0023's bundle plan.

### New lib `libs/shared/charts/`

```
libs/shared/charts/src/lib/
├── _internal/                   ← single source of truth for the a11y contract
│   ├── a11y.ts                  ← chartId, findChartSvg, injectSvgTitleDesc, prefersReducedMotion, resolveTheme
│   ├── chart-envelope.scss      ← shared figure / caption / fallback / dark-mode rules
│   ├── chart-types.ts           ← `ChartBaseInputs<T>` extended by each component
│   └── palette.ts               ← Viridis / Cividis (sequential) + ColorBrewer Set2 (categorical)
├── bar-chart/                   ← Plot.barY
├── donut-chart/                 ← raw d3-shape (pie + arc); Plot has no donut mark
└── stacked-bar-chart/           ← Plot.barY with `fill: <seriesKey>` (auto-stacked, legend on)
```

### A11y contract baked in for v1

Per ADR-0023's six commitments, every chart component produces (and unit-tests for):

1. `<figure role="img" aria-labelledby aria-describedby>` wrapping the SVG.
2. SVG `<title>` + `<desc>` as the **first two children** — injected post-render via `injectSvgTitleDesc` because Plot doesn't emit them itself. `findChartSvg` handles both Plot output shapes (bare SVG, or `<figure>` wrapping a legend + SVG for `legend: true` configs).
3. A `<details>` disclosure with a `<table>` rendering every data point — the keyboard-navigable / screen-reader-friendly fallback for non-visual users.
4. Palette from `_internal/palette.ts` only — Viridis / Cividis for sequential, ColorBrewer Set2 for categorical. Both colour-blind-safe.
5. AA-contrast axis text via `:where(.dark)` flips in `_internal/chart-envelope.scss`.
6. `prefers-reduced-motion` → `data-no-transitions` marker on the SVG, CSS strips animations + transitions.

A custom ESLint rule in [`libs/shared/charts/eslint.config.mjs`](libs/shared/charts/eslint.config.mjs) bans direct imports of `d3-scale-chromatic` outside `_internal/palette.ts` so a future contributor can't bypass the colour-blind-safe contract.

### Component contract

Every `<lib-*-chart>` exposes the same Signal-based shape per ADR-0023:

```ts
[data]: readonly T[];
[caption]: string;
[description]: string;
[ariaLabel]: string;
[colorScheme]?: 'sequential' | 'categorical';
// + chart-specific keys (xKey, yKey, categoryKey, valueKey, seriesKey, …)
```

Re-renders triggered by Angular's `effect()` on input changes; the previous SVG is `replaceChildren`-d out so there's no DOM accumulation across data updates.

## Notes for the reviewer

- **Why three SCSS files importing one shared envelope?** Extracted at the third consumer per CLAUDE.md's "three similar lines is better than a premature abstraction" rule — bar + donut + stacked-bar share ~70 LOC of figure / caption / fallback / dark-mode chrome. `_internal/chart-envelope.scss` is the consolidation; each chart's `.scss` is now 4-20 LOC of chart-specific tweaks.
- **Why is `<lib-donut-chart>` raw D3 rather than Plot?** Plot's design philosophy explicitly excludes pie/donut marks ("a bar chart is almost always more legible"). The audit-log outcome breakdown reads naturally as a donut (the centre carries the total). Raw `d3-shape` is the lower-level fallback ADR-0023 reserves precisely for this kind of case; the component's API is identical to the Plot-backed siblings.
- **Why does the donut also stamp per-slice `<title>`?** Belt-and-suspenders. The top-level SVG `<title>` reads the caption; per-slice `<title>` reads "category: value" on hover (the SVG-native tooltip convention) for keyboard / screen-reader users who land on a specific slice.
- **Why no `pnpm.overrides` adjustment for `d3-*` transitives?** None of the new deps brought a vulnerability in this install. The `pnpm audit --audit-level=moderate` gate from #161 stays green.
- **What's deliberately deferred to PR 3?** The `/audit`-page integration — data aggregation from the current page (`AdminAuditPage` rows already loaded), the actual `<lib-*-chart>` placements above the existing table, the i18n strings for the captions / descriptions / aria-labels. No mock SAMPLE data shipped in this PR — every test uses local fixtures so the lib stays decoupled from any specific consumer.

## Test plan

- [x] `pnpm nx test shared-charts` — **13 specs pass** across the three components.
- [x] `pnpm nx lint shared-charts` — clean, including the custom `no-restricted-imports` guard on `d3-scale-chromatic`.
- [x] `pnpm nx build shared-charts` — clean (TS strict + ng-packagr).
- [x] `pnpm nx run-many -t lint test build --projects=shared-charts,portal-shell,portal-admin` — 12/12 tasks green.
- [x] `pnpm nx build portal-shell --configuration=production` — i18n-strict prod build clean. The lib ships no i18n marks (axis labels are caller-supplied per ADR-0023's i18n posture), so no xlf entry change here.
- [ ] **Visual smoke (deferred to PR 3 when the components are placed on `/audit`)** — `<figure>` + caption visible, SVG `<title>` exposed by VoiceOver / NVDA, `<details>` fallback expands to a table, dark-mode toggle flips axis text + Cividis palette, `prefers-reduced-motion` strips Plot's fade-in.

## What's next

PR 3 wires the three components onto the `/audit` page: aggregates `AdminAuditPage.items` client-side into the three required shapes (daily totals, outcome counts, daily-by-event-type pivots), places them above the existing filter form, ships the i18n strings for the captions and descriptions in `messages.fr.xlf`. Bundle impact on the lazy `audit` chunk gets verified there (the ~65 KB gzip plan from the ADR).

---------

Co-authored-by: Julien Gautier <julien.gautier@apf.asso.fr>
Reviewed-on: #171
2026-05-16 21:56:28 +02:00
julien bba1703622 fix(deps): scope the vite < 7 override to vitepress so @angular/build keeps vite 8 (#163)
CI / check (push) Successful in 3m15s
CI / commits (push) Has been skipped
CI / scan (push) Successful in 1m25s
CI / a11y (push) Successful in 2m31s
Docs site / build (push) Successful in 4m2s
CI / perf (push) Successful in 5m0s
## Summary

Hotfix on the override added in [#161](#161). The unconditional `"vite": ">=6.4.2 <7"` was too broad — it downgraded **every** vite consumer in the workspace to vite 6.4.2, including `@angular/build@21.2.11`. Angular's dev-server plugin (`angular-memory-plugin.js > loadViteClientCode`) monkey-patches Vite's client error-overlay code; the patch targets vite 8.x's internal layout, fails against vite 6, and the home page blank-screens with:

```
[vite] Internal server error: Failed to update Vite client error overlay text.
  at loadViteClientCode (angular-memory-plugin.js:140:31)
```

…on the very first request to `pnpm nx serve portal-shell`.

## Fix

One-line change in [`package.json`](package.json) → `pnpm.overrides`:

```diff
- "vite":          ">=6.4.2 <7",
+ "vitepress>vite": ">=6.4.2 <7",
```

pnpm's `parent>child` selector syntax scopes the override to vitepress's transitive resolution only. Other vite consumers (Angular, Nx, Vitest, the analog plugin) follow their own peer constraints and resolve to vite 8.0.13 again.

## Resolution after the fix

| Consumer | vite version | Why |
| --- | --- | --- |
| `vitepress 1.6.4` | **6.4.2** | Override target — keeps VitePress 1.x off rolldown-vite |
| `@angular/build 21.2.11` | **8.0.13** | Restored; its dev-server plugin needs vite 8's client layout |
| `@nx/vite`, `@analogjs/vite-plugin-angular`, Vitest | **8.0.13** | Restored |
| `@vitejs/plugin-basic-ssl` (analog transitive) | 7.3.2 | Its own peer range; HTTPS-cert helper only, not the dev-server host. Doesn't affect us. |

## Why this regression didn't surface in #161's CI

The `docs-site.yml` workflow and `pnpm exec nx run-many -t lint test build` exercise the **production build** paths. Vite 6 ↔ Angular-build 21.2's monkey-patch incompatibility is a **dev-server-only** failure (the prod Rollup pipeline doesn't go through `loadViteClientCode`). CI couldn't have caught it; manual `nx serve portal-shell` is the only repro path. Lesson logged.

## Test plan

- [x] `pnpm install` — clean. Lockfile diff confirms: vite 6.4.2 only under vitepress, vite 8.0.13 restored everywhere else.
- [x] `pnpm audit --audit-level=moderate` — no known vulnerabilities.
- [x] `pnpm docs:build` — ~9.5 s, Mermaid still renders in ADR-0009's HTML (regression fence passes).
- [x] `pnpm exec nx run-many -t lint test build --projects=portal-shell,portal-admin,portal-bff,shared-ui,shared-state,feature-auth` — green.
- [x] **`pnpm nx serve portal-shell`** — boots cleanly, `curl http://localhost:4200/` returns HTTP 200 with the SPA shell, no `loadViteClientCode` error in the dev log. (This is the path that broke.)
- [ ] **Manual smoke (the user's repro)** — `pnpm nx serve portal-shell`, open the home page in a browser, confirm:
  - No "Failed to update Vite client error overlay text" in the browser DevTools console.
  - Page renders; navigating to `/accessibility`, `/profile` works.
  - Theme switcher in the footer toggles light / dark / system; locale switcher likewise.

## Notes for the reviewer

- **Lesson on override scoping**: the version-selector form (`"package@<vuln-range>": "patched"`) is the safest default — it only kicks in for the vulnerable range. The unconditional form (`"package": "<range>"`) is a sledgehammer and should be reserved for cases where the version-selector form genuinely can't express the intent (as was the case in #161 where the resolved version was already past the vulnerable range, so the selector was a no-op). When the unconditional form is needed, **always scope to a parent** (`"parent>package": …`) to avoid the workspace-wide blast radius.
- **No documentation change in this PR**: development.md's "Transitive vulnerabilities — `pnpm.overrides`" subsection (from #160) is still accurate. A follow-up edit could add a "scope to a parent when possible" sentence; not blocking.

---------

Co-authored-by: Julien Gautier <julien.gautier@apf.asso.fr>
Reviewed-on: #163
2026-05-16 02:07:43 +02:00
julien 2dbf2d8ce4 fix(docs): cap vite below 7 (rolldown-vite) and pin mermaid transitives (#161)
CI / check (push) Successful in 3m32s
CI / commits (push) Has been skipped
CI / scan (push) Successful in 1m54s
CI / a11y (push) Successful in 3m7s
CI / perf (push) Successful in 4m54s
Docs site / build (push) Successful in 4m45s
## Summary

`pnpm docs:dev` failed after #159's transitive-vuln fix landed. Two distinct symptoms in the same log :

1. **VitePress refused to boot** — `VitePress v1 is not compatible with rolldown-vite. Use VitePress v2 instead.` The override added in #159 (`vite@<6.4.2 → >=6.4.2`) had no upper bound; pnpm resolved vitepress's `vite ^5.0.0` constraint up to **vite 7.3.2**, which uses the new rolldown bundler. VitePress 1.x explicitly rejects rolldown-vite.

2. **Resolution-warning flood** — Even on a fallback port, the console showed `Failed to resolve dependency: dayjs / debug / @braintree/sanitize-url / cytoscape / cytoscape-cose-bilkent` from `optimizeDeps.include`. The vitepress-plugin-mermaid wrapper injects those into the optimizer's include list, but under pnpm's strict isolation they aren't reachable from the workspace root (transitives of mermaid, never hoisted).

## What lands

### 1. Vite override is now an **unconditional** `>=6.4.2 <7` range

[`package.json`](package.json):

```diff
- "vite@<6.4.2": ">=6.4.2",
+ "vite": ">=6.4.2 <7",
```

The selector form (`vite@<6.4.2`) was a no-op once vite had resolved into 7.x — the override only activates when the resolved version is _in_ the vulnerable range. Vite 7 is ≥ 6.4.2, so the override stayed dormant and rolldown-vite slipped through.

The unconditional form forces a downgrade across every consumer (vitepress, `@nx/vite`, `@analogjs/vite-plugin-angular`, Vitest, etc.). All six top-level projects still lint, test, and build under vite 6.4.2.

**Trade-off acknowledged**: we're now pinning the whole workspace to vite 6.x to keep VitePress 1.x happy. The day VitePress 2 ships a 1.0 (currently in beta), we revisit and let vite advance again. Tracked as a soft follow-up.

### 2. `optimizeDeps.include` simplified to `['mermaid']`

[`docs/.vitepress/config.mts`](docs/.vitepress/config.mts):

```diff
- include: ['mermaid', 'dayjs', 'debug', '@braintree/sanitize-url'],
+ include: ['mermaid'],
```

Vite 6's dep optimizer walks Mermaid's transitives automatically once Mermaid itself is in `include`. The explicit child list from #157 was carried forward in the rolldown attempt and tripped on vite's stricter resolver — collapsing it now both removes the noise and matches what the plugin's docs recommend for vite 6.

### 3. Mermaid transitives pinned as top-level devDeps

[`package.json`](package.json):

```diff
+ "@braintree/sanitize-url": "^7.1.2",
+ "cytoscape": "^3.33.3",
+ "cytoscape-cose-bilkent": "^4.1.0",
+ "dayjs": "^1.11.20",
+ "debug": "^4.4.3",
```

These are already in `node_modules` (pulled in by mermaid). Declaring them at the workspace root makes them reachable from `optimizeDeps.include` under pnpm's strict isolation, which silences the five "Failed to resolve dependency" warnings the plugin's wrapper produced.

Cost: five extra devDep lines in `package.json` whose only purpose is to make the optimizer happy. Acceptable — they don't influence the resolved tree, just the resolver's reachability rules.

## Notes for the reviewer

- **Why not bump VitePress 1 → 2?** VitePress 2 is still beta. Per [CLAUDE.md](CLAUDE.md) §"Project rules": pre-1.0 dependencies and one-maintainer projects are rejected unless an ADR justifies the exception. ADR-0022 already records VitePress 1.6.4 as the chosen baseline; switching to a beta on the very first follow-up PR would burn the rationale.
- **Why an unconditional vite range, not a tighter selector?** The selector form (`vite@vulnerable-range → patched-range`) is the standard pattern when the parent dep's own range *includes* a patched version — pnpm picks it naturally and the override never fires. Here vite's 5.x branch was never patched (5.4.21 stayed vulnerable; vite team moved on to 6.x), so we need to force the downgrade from 7.x to 6.x regardless of the previous resolution. An unconditional override is the cleanest expression of that intent.
- **Why not extract the mermaid-transitive pins into the ADR-0022 trail?** They're plumbing for the plugin wrapper, not an architectural decision worth recording. If the plugin ships a fix that removes the include list, these can be removed without consequence. Pinning them is reversible.

## Test plan

- [x] `pnpm install` clean; lockfile changes reflect vite 6.4.2 across all consumers.
- [x] `pnpm audit --audit-level=moderate` — **No known vulnerabilities found**.
- [x] `pnpm docs:dev` — server boots cleanly on `:5173`, no warnings, home + ADR-0009 page return 200.
- [x] `pnpm docs:build` — clean build in ~9 s (back to Rollup-based timings; the rolldown 3.87 s we saw briefly was the incompatible path).
- [x] `pnpm exec nx run-many -t lint test --projects=portal-shell,portal-admin,portal-bff,shared-ui,shared-state,feature-auth` — 12/12 tasks green under the vite 6 downgrade.
- [x] `pnpm exec nx build portal-shell` — clean Angular production build; no Vite 6 incompatibility surfaced.
- [ ] **Manual smoke (visual)** — `pnpm docs:dev`, open `http://localhost:5173`, navigate to `/decisions/0009-…` and `/architecture`, confirm Mermaid diagrams render inline (this is the actual UX the user opened the issue on). Dark mode toggle still flips diagrams.

## Follow-ups (optional)

- When VitePress 2 reaches 1.0 (`vue/vitepress > releases`), revisit this override and let vite resume its mainline cadence.
- If the next Renovate cycle proposes a `cytoscape` / `dayjs` / `debug` major bump that VitePress 1.x can't keep up with, the pins above act as the safety net — Renovate will open a PR rather than silently break the dev server.

---------

Co-authored-by: Julien Gautier <julien.gautier@apf.asso.fr>
Reviewed-on: #161
2026-05-15 23:57:57 +02:00
julien 779061660b chore(security): override vite + esbuild past vitepress's vulnerable transitives (#159)
CI / commits (push) Has been skipped
CI / check (push) Successful in 3m46s
CI / scan (push) Successful in 2m4s
CI / a11y (push) Successful in 2m3s
Docs site / build (push) Successful in 3m44s
CI / perf (push) Successful in 3m59s
## Summary

Unblocks `pnpm ci:audit`. Two moderate vulnerabilities surfaced after the docs-site chantier (#154):

| Advisory | Package | Vulnerable | Patched | Path |
| --- | --- | --- | --- | --- |
| [GHSA-67mh-4wv8-2f99](https://github.com/advisories/GHSA-67mh-4wv8-2f99) | esbuild | ≤ 0.24.2 | ≥ 0.25.0 | `. > vitepress > vite > esbuild` |
| [GHSA-4w7w-66w2-5vf9](https://github.com/advisories/GHSA-4w7w-66w2-5vf9) | vite | ≤ 6.4.1 | ≥ 6.4.2 | `. > vitepress > vite` |

Both come from VitePress 1.6.4's pinned dep tree (`vite@5.4.21 → esbuild@0.21.5`). The rest of the workspace was already on vite 8.0.13 + esbuild 0.27.3 — only the VitePress branch was stuck on the vulnerable line.

## What lands

Two new entries in `package.json`'s existing `pnpm.overrides` block:

```json
"esbuild@<0.25.0": ">=0.25.0",
"vite@<6.4.2":     ">=6.4.2",
```

Same version-selector pattern as the other overrides already in the file (axios, follow-redirects, ip-address, …). The override only kicks in when the resolved version is in the vulnerable range, so it becomes a no-op the day the underlying dep ships a clean version of its own.

After `pnpm install`, the resolver picks **vite 7.3.2** + **esbuild 0.27.3** for the VitePress branch (the workspace's other vite consumers stay on 8.0.13, deduped on esbuild).

## Why couldn't we pin a patched vite 5.x?

The vite team did **not** backport the security fix to the 5.x line. `vite@5.4.22` is not published — the latest 5.x stays at 5.4.21, which is vulnerable. The only path forward is to let pnpm pick a patched 6.x or 7.x major. Verified that VitePress 1.6.4 still:

- builds cleanly (`pnpm docs:build` succeeds in ~4 s, down from 9 s on the older vite);
- renders Mermaid (regression fence in `.gitea/workflows/docs-site.yml` still grep-matches `class="mermaid"` / `<svg>` in ADR-0009's HTML);
- runs the dev server (`pnpm docs:dev` boots, the dayjs CJS-interop fix from #156 still applies for the same reason — Mermaid's CJS deps need pre-bundling regardless of vite major).

## Why didn't Renovate propose this PR itself?

The user reported that Renovate wasn't creating PRs for these advisories, not even listing them on the Dependency Dashboard.

**Root cause: both vite and esbuild are transitive dependencies** — declared by vitepress, not by us. Renovate's `vulnerabilityAlerts` flow handles **direct** package.json deps. For pnpm transitives, the remediation would have to land in `pnpm.overrides`, which the renovatebot/renovate:40 image doesn't write automatically.

Adjacent points:

- The Renovate workflow runs on a daily 03:00 UTC cron only (plus manual dispatch). If the user wants an immediate dashboard refresh now, the workflow accepts `workflow_dispatch` — fire it once from the Gitea Actions UI.
- After this PR merges, Renovate's dashboard should also stop flagging these advisories (the overrides count as remediation).
- Renovate config itself is unchanged — no `ignorePaths`, no exclude of vite/esbuild/vitepress. The silence was purely about the transitive-remediation gap, not a config bug.

## Test plan

- [x] `pnpm install` — clean, no peer warnings beyond the pre-existing `nestjs-prisma → chokidar` one.
- [x] `pnpm audit --audit-level=moderate` — **"No known vulnerabilities found"**.
- [x] `pnpm docs:build` — clean build in ~4 s.
- [x] Mermaid regression fence — `grep 'class="mermaid"' docs/.vitepress/dist/decisions/0009-…html` matches.
- [ ] `pnpm ci:audit` on CI — should now pass (the goal of this PR).
- [ ] Manual smoke: `pnpm docs:dev`, navigate to `/decisions/0009-…`, confirm the OIDC sequence diagram renders. Dark-mode toggle still flips theme.

## Follow-ups (optional)

- Trigger the Renovate workflow manually (Gitea Actions → Renovate → Run workflow) so the Dependency Dashboard refreshes against this overridden state.
- If we hit this transitive-remediation gap again, consider raising a `renovateConfig.transitiveRemediation` story or switching the workflow to `renovate/renovate:latest` — newer point releases sometimes ship better pnpm-overrides authoring. Not urgent.

---------

Co-authored-by: Julien Gautier <julien.gautier@apf.asso.fr>
Reviewed-on: #159
2026-05-15 22:18:07 +02:00
julien 7579b25dfe feat(docs): vitepress site for docs/, mermaid rendering, ci build workflow (#154)
CI / commits (push) Has been skipped
CI / check (push) Successful in 2m39s
CI / scan (push) Failing after 2m31s
CI / a11y (push) Successful in 2m14s
CI / perf (push) Successful in 4m12s
Docs site / build (push) Successful in 2m27s
## Summary

Implementation of [ADR-0022](docs/decisions/0022-docs-site-vitepress.md). Stands up the static documentation site that renders `docs/**/*.md` (architecture diagrams, daily-dev guide, ADRs, onboarding) via **VitePress + `vitepress-plugin-mermaid`**, behind a Gitea Actions build gate.

Local dev: `pnpm docs:dev`. Full build: `pnpm docs:build` (~9 s, output in `docs/.vitepress/dist/`).

## What lands

### Dependencies

`vitepress 1.6.4`, `vitepress-plugin-mermaid 2.0.17`, `mermaid 11.15.0` — workspace devDependencies. No runtime impact on `portal-shell` / `portal-admin` / `portal-bff`.

### [`docs/.vitepress/config.mts`](docs/.vitepress/config.mts)

The single source of truth for the site. Highlights:

- **`srcExclude`** drops `docs/README.md` (git/IDE-only index per ADR-0022's option A) and `docs/decisions/template.md` (authoring scaffold).
- **`rewrites`** maps `decisions/README.md` → `decisions/index.md` so `/decisions/` resolves to the curated tag-grouped landing while the source filename stays git-conventional.
- **`ignoreDeadLinks`** skips:
  - `localhost:*` URLs (Jaeger, OTLP — only resolve in a live dev session),
  - cross-repo references (`../CLAUDE`, `../../apps/**`, `../../infra/**`, `../../notes/**`) — intentional from git/IDE consumers; not the site's job to render them,
  - excluded targets (`./template`, `./README`) — file exists in the repo, just not in the site.
- **Auto-sidebar for `/decisions/`** — `adrSidebarItems()` walks `docs/decisions/00*-*.md` and emits sorted `ADR-NNNN — title` entries. Adding an ADR is a single-file change, no `config.mts` edit.
- **Hand-curated top-level nav** (Development, Architecture, Decisions, Onboarding).
- **Mermaid via `withMermaid()`** with `securityLevel: 'strict'` so diagrams can't inject arbitrary HTML.

### [`docs/index.md`](docs/index.md)

VitePress Hero landing with four feature cards (Architecture, Decisions, Development, Onboarding).

### [`docs/development.md`](docs/development.md) — two surgical fixes

- Line ~5: `[setup/](setup/)` → `[setup/01-wsl-terminal-setup.md](setup/01-wsl-terminal-setup.md)`. Folder-style links don't resolve cleanly under `cleanUrls: true`; pointing at the first onboarding page is both correct and useful.
- Line 330: wrap `${{ github.* }}` in `<code v-pre>…</code>`. VitePress runs every Markdown file through the Vue template compiler, which sees the inline `{{ … }}` as an interpolation. `v-pre` keeps the literal text intact. The rest of the source is unaffected.

### [`package.json`](package.json)

Three new scripts:

```
docs:dev      → vitepress dev docs
docs:build    → vitepress build docs
docs:preview  → vitepress preview docs
```

Pure pnpm scripts, no Nx project — the site has no cross-project dependency graph to track.

### [`.gitea/workflows/docs-site.yml`](.gitea/workflows/docs-site.yml)

Triggers on push to `main` and on PR, scoped by `paths:` to `docs/**`, `package.json`, `pnpm-lock.yaml`, and the workflow itself. Three steps:

1. `pnpm install --frozen-lockfile`
2. `pnpm docs:build`
3. Regression fence: `grep` ADR-0009's rendered HTML for `class="mermaid"` or `<svg>` so a silent Mermaid-plugin breakage on a major upgrade fails the workflow rather than ship a site with raw code blocks where diagrams should be.
4. On push only: upload `docs/.vitepress/dist/` as a `docs-site` artifact (30-day retention). The actual rsync to the static host lands when the future infrastructure ADR locks the deployment target.

### [`.gitignore`](.gitignore)

Excludes `docs/.vitepress/{cache,dist}/` so local builds don't leak into commits.

## Notes for the reviewer

- **Why `config.mts` and not `config.ts`?** VitePress is ESM-only, and `vitepress-plugin-mermaid` follows. Vite loads `.ts` config files via its CJS bundler in this workspace's setup and chokes on the ESM imports. `.mts` flips the loader to ESM and the build succeeds. Same pattern is used elsewhere in the workspace (`jest.config.cts`, app `vite.config.mts`).
- **Why no Nx project (`docs/project.json`)?** The doc site has no Nx-trackable dependencies (it consumes `.md` files, not TypeScript projects). Putting it in the Nx graph adds ceremony with no caching benefit — VitePress's incremental rebuilds are sub-second already, and the site never has cross-project `affected` semantics. Pure pnpm scripts keep the surface small.
- **Why the regression fence on Mermaid?** ADR-0022 §"Confirmation" promises it. The plugin is a community dep (sub-1.0 wrapper around the official Mermaid renderer); a major upgrade or a Mermaid runtime change could leave fenced ` ```mermaid ` blocks rendered as raw code without anyone noticing — until an RSSI clicks ADR-0009 and sees no diagram. Cheap grep gate, real signal.
- **Why upload as artifact, not deploy?** Per [ADR-0022](docs/decisions/0022-docs-site-vitepress.md) §"Deployment & CI": the host (`docs.portal.apf.fr` or a sub-path) is provisional. Locking an rsync target now would couple this PR to a not-yet-made infra decision. Artifact upload is the staging mechanism — manual drop on the host until the infrastructure ADR formalises the target.
- **Why `ignoreDeadLinks` rather than fixing every cross-repo reference?** The cross-repo links are genuinely useful from a git/IDE perspective (where the docs/ markdown is browsed alongside the rest of the codebase). Rewriting them to `https://git.unespace.com/julien/apf_portal/src/branch/main/…` would make them work on the site but lose the IDE quick-jump. Skipping at site-build time is the right trade-off — the site reader gets a graceful "link doesn't exist here" if they click, the IDE reader gets a working jump.

## Test plan

- [x] `pnpm docs:build` succeeds in ~9 s. Output at `docs/.vitepress/dist/` contains an `index.html`, every ADR, the development guide, the architecture diagrams, and the three setup pages.
- [x] Mermaid renders: `grep 'class="mermaid"' docs/.vitepress/dist/decisions/0009-…html` returns a match.
- [x] `pnpm exec nx run-many -t format:check lint test build` for the 6 main projects — 18/18 tasks green, no Nx regression from the new top-level config.
- [ ] **Manual smoke**: `pnpm docs:dev`, open `http://localhost:5173`, walk through:
  - Landing renders Hero + 4 feature cards.
  - Search box returns hits for "audit", "MFA", "OBO".
  - `/decisions/0009-…` renders the OIDC sequence diagram (Mermaid SVG, not raw text).
  - `/decisions/0010-…` ERD or `/architecture` C4 diagrams likewise.
  - Dark-mode toggle flips diagrams to dark theme without page reload.
  - Sidebar shows the 22 ADRs auto-listed under `/decisions/`.
  - The "Decisions" curated index at `/decisions/` lists ADRs by tag (no regression on the source markdown).

## What's next

Once the deployment target is fixed (future infra ADR), wire the rsync step into the workflow — that lands as a small follow-up PR. Until then the artifact carries the bundle.

---------

Co-authored-by: Julien Gautier <julien.gautier@apf.asso.fr>
Reviewed-on: #154
2026-05-15 19:14:14 +02:00
julien 1513ad327c feat(portal-bff): openapi spec + scalar api reference UI (dev-only) (#143)
CI / scan (push) Successful in 2m5s
CI / commits (push) Has been skipped
CI / check (push) Successful in 3m33s
CI / a11y (push) Successful in 1m22s
CI / perf (push) Successful in 3m34s
## Summary

Adds an OpenAPI 3 spec + a [Scalar API Reference](https://scalar.com/) UI to `portal-bff`, dev-only. The BFF previously had no way to *see* its HTTP surface short of grepping for `@Get` / `@Post`; this PR generates the spec from the existing Nest controllers via [`@nestjs/swagger`](https://docs.nestjs.com/openapi/introduction) and renders it through Scalar — a modern alternative to the classic Swagger UI (single-page, fast, dark-mode native, better typography).

## What lands

### Two new dev-only routes

| Route | What it serves |
| --- | --- |
| `GET /api/openapi.json` | Raw OpenAPI 3 document. External tools (Bruno / Insomnia / Postman) import from here. |
| `GET /api/docs` | Scalar API Reference HTML page. Loads the JSON spec at render time and renders the full endpoint catalogue with a "Try it" panel. |

Both routes are gated behind `process.env.NODE_ENV !== 'production'` in [`setupOpenApi`](apps/portal-bff/src/openapi/openapi.ts) — production deployments don't need the docs surface, and publishing it would hand an attacker a curated map of every authenticated endpoint + every DTO shape. If a future ops use-case wants the spec in prod (internal gateway, contract testing), the gate is one line away from an opt-in `OPENAPI_PUBLISH=true` env knob.

### Core implementation — [`apps/portal-bff/src/openapi/openapi.ts`](apps/portal-bff/src/openapi/openapi.ts)

Two exported helpers:

- **`buildOpenApiDocument(app)`** — wraps Nest's `DocumentBuilder` + `SwaggerModule.createDocument`. Sets title, description (mentions the CSRF caveat — see below), version, and registers **two** cookie security schemes:
  - `portal_session` for the user-portal surface ([ADR-0009](docs/decisions/0009-auth-flow-oidc-pkce-msal-node.md)).
  - `portal_admin_session` for the admin-portal surface ([ADR-0020](docs/decisions/0020-portal-admin-app.md)).
  No `@ApiBearerAuth` is declared — the BFF never exposes a bearer-auth surface (SPA never holds tokens per ADR-0009; downstream OBO tokens are server-side only per ADR-0014).

- **`setupOpenApi(app, globalPrefix)`** — short-circuits in production, otherwise binds the two routes via the Express adapter directly (`app.getHttpAdapter().get(...)` and `app.use(...)`). The OpenAPI JSON is a static asset and Scalar is a vanilla Express middleware — wrapping either in a Nest controller would add zero value and an extra layer of indirection.

Wired into bootstrap at [`apps/portal-bff/src/main.ts:220`](apps/portal-bff/src/main.ts#L220), immediately after the JWKS endpoint mount and before `app.listen()`.

### Controllers decorated with `@ApiTags` / `@ApiOperation` / `@ApiCookieAuth`

Annotations are cosmetic but make the spec actually browsable. Tag taxonomy:

| Controller | Tag | Security |
| --- | --- | --- |
| [`AppController`](apps/portal-bff/src/app/app.controller.ts) | `app (scaffolding)` | — |
| [`HealthController`](apps/portal-bff/src/health/health.controller.ts) | `health` | — |
| [`AuthController`](apps/portal-bff/src/auth/auth.controller.ts) | `auth (user portal)` | `portal_session` on `/me` + `/logout` |
| [`AdminAuthController`](apps/portal-bff/src/admin/admin-auth.controller.ts) | `auth (admin portal)` | `portal_admin_session` on `/me` + `/logout` |
| [`AdminController`](apps/portal-bff/src/admin/admin.controller.ts) | `admin (self-test)` | class-level `portal_admin_session` |
| [`AdminAuditController`](apps/portal-bff/src/admin/admin-audit.controller.ts) | `admin (audit log)` | class-level `portal_admin_session` |
| [`AdminUsersController`](apps/portal-bff/src/admin/admin-users.controller.ts) | `admin (user directory)` | class-level `portal_admin_session` |

`@ApiOperation({ summary: … })` added on every route — populates the one-line description Scalar shows in its left-rail TOC.

### Deps + Jest

- `@nestjs/swagger ^11` (matches the Nest 11 major already pinned) and `@scalar/nestjs-api-reference` added to the workspace root.
- [`jest.config.cts`](apps/portal-bff/jest.config.cts) — widened `transformIgnorePatterns` from `/node_modules/(?!.*jose)/` to `/node_modules/(?!.*(jose|@scalar/))/`. `@scalar/client-side-rendering` (a transitive dep) ships ESM-only; without this widening the spec suite fails to load the module under ts-jest.

## Notes for the reviewer

- **Why two cookie schemes rather than one?** Scalar renders a per-endpoint lock icon driven by the security scheme name. Splitting `portal_session` / `portal_admin_session` keeps the indicator semantically truthful — `/api/auth/me` and `/api/admin/auth/me` look identical otherwise.
- **CSRF caveat.** Mutating routes (`POST` / `PUT` / `PATCH` / `DELETE`) require `X-CSRF-Token` per [ADR-0009](docs/decisions/0009-auth-flow-oidc-pkce-msal-node.md). The header must be set manually in Scalar's "Try it" panel to the value of the `portal_csrf` cookie when exercising those routes. The spec description mentions it; auto-injecting the header from the cookie is a future polish.
- **No ADR for this.** `@nestjs/swagger` is the framework's own first-party tooling; Scalar is a thin UI on top of a standard OpenAPI 3 document. Both replaceable without touching the controllers (the `@Api*` annotations are spec-standard). Dev-only, no prod surface — doesn't cross any of the bars that warrant an ADR per [CLAUDE.md](CLAUDE.md).
- **Express-layer routing.** Same pattern as the JWKS endpoint (#139): the OpenAPI JSON is a static asset and Scalar a vanilla Express handler, so wiring through Nest's router adds no value.

## Test plan

- [x] **5 new specs** in [`apps/portal-bff/src/openapi/openapi.spec.ts`](apps/portal-bff/src/openapi/openapi.spec.ts) — document shape (openapi version, title, version), both cookie schemes declared, smoke controller route captured in `paths`, production short-circuit (no routes mounted, no `app.use` called), dev mount (JSON at `/api/openapi.json` via the HTTP adapter, Scalar UI at `/api/docs` via `app.use`).
- [x] `pnpm nx test portal-bff` — **396 specs pass** (was 391).
- [x] `pnpm exec nx affected -t format:check lint test build --base=origin/main` — clean.
- [x] Manual dev smoke: `pnpm nx serve portal-bff`, `curl /api/openapi.json | jq .info` returns title + version, open `/api/docs` in a browser, every controller's routes visible under their tag, lock icons match the cookie scheme on guarded routes.

## What's next — light follow-ups

Not blocking this PR; mentioned so they're not lost:

- Auto-inject the `X-CSRF-Token` header in Scalar from the `portal_csrf` cookie (custom Scalar config preset).
- Promote `@ApiOperation` summaries with multi-line `description`s on the more involved routes (`/api/admin/audit`, `/api/admin/users`).
- Annotate DTOs with `@ApiProperty` once the first contract-test consumer arrives — Nest can also pick them up automatically with the `@nestjs/swagger` ts-plugin if we wire it into the Nx build target. Deferred until the spec is consumed by tooling that benefits from the precision.

---------

Co-authored-by: Julien Gautier <julien.gautier@apf.asso.fr>
Reviewed-on: #143
2026-05-14 20:52:03 +02:00
julien 282a972346 feat(portal-bff): signed-assertion strategy + /.well-known/jwks.json (#138)
CI / scan (push) Successful in 2m40s
CI / commits (push) Has been skipped
CI / check (push) Successful in 3m38s
CI / a11y (push) Successful in 1m38s
CI / perf (push) Successful in 3m36s
## Summary

Second half of the **DownstreamApiClient + OBO** chantier per [ADR-0014](docs/decisions/0014-downstream-api-access-obo-pattern.md). Ships the **signed-assertion strategy** (non-Entra downstreams) and the **JWKS publishing endpoint** as testable primitives, completing the strategy layer the OBO PR (#137) started. The framework around them (DownstreamApiClientFactory, cockatiel, audience pre-check, error translation) still waits for the first concrete integration per the ADR's own "until then" clause.

After this PR the BFF has, ready to plug into a future integration:

- `OboStrategy` — Entra-protected downstreams (PR #137)
- `SignedAssertionStrategy` — non-Entra downstreams (this PR)
- `DownstreamTokenCache` — encrypted-at-rest OBO token cache (PR #137)
- `GET /.well-known/jwks.json` — public key publication (this PR)

## What lands

### [`assertJwksConfig`](apps/portal-bff/src/config/check-jwks-config.ts)

Boot validator for `BFF_JWKS_PRIVATE_KEY_PATH` + `BFF_JWKS_KID`. Reads the PEM file once at startup, refuses missing / unreadable / weak material (RSA < 2048, Ed25519, unknown key type), derives the JOSE algorithm (`RS256` / `ES256` / `ES384`) from the key shape, and validates the kid against `[A-Za-z0-9_-]{4,128}` so the value lives unescaped in JWT headers + JWKS payloads.

### [`BffSigningKey`](apps/portal-bff/src/downstream/bff-signing-key.ts)

Singleton holding `{ config: JwksConfig, publicJwk: JWK }`. The `publicJwk` is derived from the **public half** of the key (via `jose.exportJWK` on a `createPublicKey`-derived `KeyObject`) so no private material can leak through. Single DI source for both consumers (strategy + JWKS controller) so a key rotation only changes one provider.

### [`SignedAssertionStrategy`](apps/portal-bff/src/downstream/strategies/signed-assertion.strategy.ts)

Wraps `jose.SignJWT` with the ADR-0014 claim shape:

```json
{
  "iss": "portal-bff",
  "sub": "<actor_id_hash>",
  "aud": "<downstream-name>",
  "audience": "workforce" | "customer",
  "claims": { /* curated subset */ },
  "exp": <now + 60s>,
  "iat": <now>,
  "trace_id": "<W3C trace id>"
}
```

- **60 s TTL** hard-coded — the ADR mandates it.
- **No JWT cache** — at 60 s lifetime the savings would be negligible and a cache would let replayed assertions linger past their useful life. The signing operation itself is cheap (~hundreds of µs for RS256 with a 3 KB key).
- **kid in the protected header** matches the JWKS so a downstream picks the right key during rotation.
- Supports **RS256 / ES256 / ES384** transparently — picks the alg the validator derived at boot.

### [`JwksController`](apps/portal-bff/src/downstream/jwks.controller.ts)

`GET /.well-known/jwks.json` returns `{ keys: [<single jwk>] }`. v1 publishes one key; the rotation chantier will add a second entry + window-based eviction so a downstream that cached the previous JWK keeps verifying during cut-over.

[`main.ts`](apps/portal-bff/src/main.ts) excludes `/.well-known/*` from the global `/api` prefix so the route lands at the bare root per RFC 8615. No auth gate — the JWKS is the verification anchor; gating it would defeat the purpose. The CSRF middleware already exempts GET methods, so the route comes out clean.

## Required env update (mandatory at boot)

Generate the key:

```bash
mkdir -p apps/portal-bff/.secrets
openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:3072 \
  -out apps/portal-bff/.secrets/jwks.pem
```

Set in `apps/portal-bff/.env`:

```env
BFF_JWKS_PRIVATE_KEY_PATH=apps/portal-bff/.secrets/jwks.pem
BFF_JWKS_KID=bff-2026-05
```

The repo's existing `*.pem` / `*.key` gitignore patterns cover `.secrets/`.

## Dependency

- **`jose@^6`** added as a direct dep (was transitive via MSAL). Pinned at the workspace root since the BFF is the only consumer today and the package isn't part of the Angular bundle graph.
- `jest.config.cts`: `jose` ships ESM-only, so its `node_modules` path is removed from `transformIgnorePatterns`. The pattern walks pnpm's deep `.pnpm/` layout — anything under `/node_modules/` whose path also contains `jose` somewhere gets transformed by ts-jest.

## Out of scope (deferred until the first concrete integration)

Per ADR-0014's "until then" clause:

- `DownstreamApiClientFactory` + per-service typed `DownstreamApiConfig`.
- `cockatiel` resilience composition (timeout, retry, circuit breaker, bulkhead).
- Audience pre-check at the call site (`audienceConstraint` → `authz.deny` audit).
- Error translation tables per service.
- OTel custom spans `downstream.<service>.<verb>.<path>`.
- The framework code that actually calls `SignedAssertionStrategy.sign()` and attaches `X-User-Assertion` + the `ServiceCredential` auth header to an outbound HTTP request.
- Key rotation (the JWKS lists one key for now; the rotation chantier adds the second entry + eviction policy).

These land alongside the first concrete integration so the framework shape is validated against a real consumer, not speculative needs.

## Test plan

- [x] `pnpm nx test portal-bff` — **358 specs pass** (was 334; +24: env validators 11, signing key 4, strategy 6, controller 3).
- [x] `pnpm exec nx affected -t format:check lint test build --base=origin/main` — clean.
- [x] Env validator: missing path, unreadable file, garbage PEM, RSA-1024 (weak), Ed25519 (unsupported), missing kid, illegal kid charset, kid too short.
- [x] Signing key: RSA / EC P-256 / EC P-384 round-trip to public JWK with no private material (`d`, `p`, `q`, `dp`, `dq`, `qi` all absent from the published JWK).
- [x] Strategy: claim shape matches ADR-0014, `exp - iat == 60`, audience mismatch rejected, signature mismatch rejected, EC P-256 signing path (ES256), per-call freshness.
- [x] Controller: returns JWKS with the single public key, no private material leaks.
- [ ] Manual smoke: generate a key locally + set the two env vars + `curl http://localhost:3000/.well-known/jwks.json` should return the JWKS shape with the chosen kid.

## Notes for the reviewer

- The strategy uses `setProtectedHeader({ alg, kid })` — the kid in the protected header is the canonical way to tell a verifier "use the entry with this kid in the JWKS". Without it, a verifier holding two keys during rotation has to try both.
- The `60 s` TTL is intentionally not env-overridable. ADR-0014 mandates it; making it tunable would create a tempting knob to widen the replay window for "performance".
- `jose` was already in the tree transitively (likely via MSAL). Promoting it to a direct dep + pinning means a future hoist deduplication can't silently remove it without our review.

## What's next

The chantier's strategy layer is complete. Open follow-ups on the roadmap:

- **First concrete downstream integration** — when a real consumer arrives, the framework gets built around the two strategies (DownstreamApiClientFactory, cockatiel resilience, audience pre-check, error translation, OTel spans, audit events). Until then the strategies + cache + JWKS sit ready.
- **Strategic security baseline ADR** — RSSI sign-off on ASVS / HDS / GDPR / NIS 2. Paused per [CLAUDE.md](CLAUDE.md) §"Repository status".
- **portal-admin v1 modules** — CMS pages, menu management, user list. Each is its own self-contained chantier.

---------

Co-authored-by: Julien Gautier <julien.gautier@apf.asso.fr>
Reviewed-on: #138
2026-05-14 18:34:07 +02:00
julien aea395ae65 feat(ci): assert gzip transfer sizes against ADR-0017 budgets (#133)
CI / check (push) Successful in 1m59s
CI / commits (push) Has been skipped
CI / scan (push) Successful in 2m34s
CI / a11y (push) Successful in 1m8s
CI / perf (push) Successful in 4m8s
## Summary

Closes the ADR-0017 follow-up that was sitting on an unpushed local branch (`feat/ci/gzip-transfer-size-budgets`) for ~5 days, while ADRs 0018–0021 and the auth/admin/security tracks landed. Cherry-picked onto current `main`, with a small follow-up fix to make the script work against the locale-split build layout introduced by ADR-0019 between the two commits.

## What lands

### Commit 1 — `feat(ci): assert gzip transfer sizes against ADR-0017 budgets`

[`scripts/check-gzip-budgets.mjs`](scripts/check-gzip-budgets.mjs) — plain-Node, no deps, ~120 LOC at landing. Closes the ADR-0017 §Confirmation gap (Angular CLI's `budgets` only compare RAW sizes):

- Parses Angular's emitted `index.html` to separate **initial** assets (anything referenced via `src=` / `href=`) from **lazy** chunks (the rest).
- Gzips every JS / CSS file at level 9 — what most HTTP servers serve for static assets — and reports a per-file + per-bucket table.
- Asserts the [ADR-0017](docs/decisions/0017-performance-budgets-lighthouse-ci.md) thresholds (`initial JS total ≤ 300 KB`, `any lazy chunk ≤ 100 KB`, `total CSS ≤ 150 KB`) and exits non-zero on any breach.
- Wired through:
  - `pnpm ci:gzip-budgets` invokes the script with the default dist path.
  - `ci:perf` chains build → gzip check → Lighthouse, so a budget breach short-circuits before Lighthouse even runs.

ADR-0017 §Confirmation also updated to point at the script instead of describing it as a "future follow-up".

### Commit 2 — `fix(ci): adapt gzip-budget check for the @angular/localize multi-bundle layout`

The original commit predated PR #91 / [ADR-0019](docs/decisions/0019-internationalisation-angular-localize.md), which made `@angular/localize` emit one self-contained bundle per locale under `dist/apps/portal-shell/browser/<locale>/`. The script looked for `index.html` directly under the dist root and failed with ENOENT on every build since.

Fix:

- Auto-detects layout. If `dist/index.html` exists → flat mode (unchanged behaviour, kept for single-locale apps like `portal-admin`). Otherwise enumerates immediate subdirectories with their own `index.html` and runs the check **per locale**.
- Budget thresholds apply per locale — each bundle is what the user's browser actually downloads. Aggregating across locales would understate the worst case.
- Violations across locales are collected + reported together with the locale tag prefixed so a single CI run surfaces every breach.
- ADR-0017 §Confirmation amendment expanded to spell out the per-locale check + cite ADR-0019.

## Test plan

- [x] `node --check scripts/check-gzip-budgets.mjs` — syntax OK.
- [x] `pnpm ci:gzip-budgets` against the current production build:
  - 2 locale bundles detected (`en`, `fr`).
  - Initial JS total: ~141 KB / 300 KB budget ✓ (each locale).
  - CSS total: 4.62 KB / 150 KB budget ✓ (each locale).
  - Largest lazy chunk: 1.55 KB / 100 KB per-chunk budget ✓.
- [x] No conflict on `package.json` despite 11 PRs touching it since the branch base — cherry-pick auto-merged cleanly.
- [ ] CI: `pnpm ci:perf` end-to-end (build → gzip-budgets → Lighthouse). Validates on the runner.

## Notes for the reviewer

- The script lives at the repo root under `scripts/` rather than as an Nx target — it's a pure CI helper, not project-scoped. Matches the existing `ci:audit` / `ci:commits` pattern in `package.json`.
- No dependencies added. Uses `node:fs/promises` and `node:zlib`.
- The original branch (`feat/ci/gzip-transfer-size-budgets`) is preserved in local-only state for paranoia; once this PR merges, it can be `git branch -D`'d.

---------

Co-authored-by: Julien Gautier <julien.gautier@apf.asso.fr>
Reviewed-on: #133
2026-05-14 16:22:54 +02:00
julien 0e6c114ba7 feat(portal-bff): rate limiting + structured error filter (#123)
CI / scan (push) Successful in 1m42s
CI / commits (push) Has been skipped
CI / check (push) Successful in 2m59s
CI / a11y (push) Successful in 55s
CI / perf (push) Successful in 2m43s
## Summary

Closes the phase-2 hardening list that `main.ts` has been advertising since the security PR (#122). Two new middlewares + one alignment pass on the response shape so every BFF error follows a single contract.

### Structured error filter

A global `ExceptionFilter` (registered via `app.useGlobalFilters(...)` at the top of `bootstrap()`) normalises every 4xx/5xx response to a single envelope :

```json
{
  "error": {
    "code": "csrf",
    "message": "CSRF token missing or invalid",
    "traceId": "abc123…"
  }
}
```

- `code` — stable token the SPA can `switch` on. Either explicit on the `HttpException`'s response object (`new UnauthorizedException({ code: 'unauthenticated', message: '...' })`) or derived from the status (`STATUS_CODE_MAP` for the common cases, `'http_error'` fallback). 500s always use `'internal'`.
- `message` — safe human-readable text. **500s never leak the underlying exception** (the full message + stack go to the Pino `error` log line as `err: exception` — Pino's stack-serialiser does the rest).
- `traceId` — current OTel trace id (or `null` when no span is active). Makes cross-correlation with the audit log + Pino lines trivial.

An exported `errorResponse(code, message)` helper produces the same envelope for code paths that write the response directly (raw Express middlewares like the CSRF one, the rate-limit handler) — single contract everywhere.

### Rate limiting

`express-rate-limit` mounted after the session middleware:

- **Dynamic max per request**: 10/min on `/api/auth/login` + `/api/auth/callback` (`RATE_LIMIT_AUTH_PER_MINUTE` env), 120/min everywhere else (`RATE_LIMIT_PER_MINUTE`).
- **Bucket key** = session id when the request carries an active session, remote IP otherwise. A single attacker can't dodge the limit by rotating sessions; an authenticated user gets per-account fairness regardless of source IP.
- **`/api/health` is skipped** so orchestrator polls don't burn the user quota.
- 429 response uses the same envelope as everything else (`{ error: { code: 'rate_limited', … } }`) via the shared `errorResponse()` helper.
- In-memory store (single-instance v1 per ADR-0015). Redis-backed store is a one-line config change when we scale out.

### Alignment pass

- **CSRF middleware** previously returned `{ error: 'csrf' }`. Now returns the full envelope via `errorResponse('csrf', 'CSRF token missing or invalid')`.
- **`/auth/me` 401** previously wrote `{ error: 'unauthenticated' }` directly. Now throws `UnauthorizedException({ code: 'unauthenticated', message: 'Unauthenticated' })` so the filter formats it. Identical response shape on the wire as the CSRF path.

Both spec assertions updated to the new shape.

### Type-resolution fix (transitive)

`@types/express@4.17.25` was being pulled in transitively by `http-proxy-middleware` (Nx's webpack-dev-server). `express-rate-limit`'s `.d.ts` files import `'express'` and the type resolver was matching the v4 copy, causing `Request` type mismatches with our v5-based code. Added `"@types/express": "^5.0.6"` to `pnpm.overrides` so the workspace pins a single version everywhere.

## Notable choices

**`StructuredErrorFilter` is the source of truth, but raw middlewares are still allowed to write responses directly** (rate-limit, CSRF). The reason: Nest's filter chain only handles exceptions thrown from controllers/guards/interceptors. Express middleware short-circuits before that. Both paths now use the same envelope shape through the `errorResponse()` helper.

**No `traceId` in non-5xx responses?** It IS included. The filter writes it on every status — useful for any client-server debugging conversation ("send me your traceId from the 403 you got").

**500s strip the exception message.** Even if a developer accidentally surfaces a sensitive detail via `throw new Error('connection to postgres://user:secret@host failed')`, the response body just says "Internal server error". The full message goes to the log — visible to ops, never to clients. This is the standard secure-by-default for unhandled errors.

**Dynamic `max` per request, not two separate `rateLimit()` instances.** Two instances would each maintain a separate store, so the `/auth/login` bucket would be independent of the general one for the same IP. A single instance with a path-conditional max gives consistent bucket accounting.

## Out of scope

- Redis-backed rate-limit store. v1 ships in-memory; the BFF runs as a single instance. The migration is `new RedisStore({ ... })` when we scale out (ADR-0015 mentions this).
- Per-user override of `RATE_LIMIT_PER_MINUTE` (e.g. admins / service accounts with higher quotas). No code path for this in v1.
- CSP fine-tuning for portal-shell + portal-admin once Caddy serves them.

## Test plan

- [x] `pnpm nx test portal-bff` (clean env) → **199/199 pass** (+25 specs: StructuredErrorFilter, rate-limit middleware, CSRF + /me alignments).
- [x] `pnpm nx test feature-auth` (clean env) → **28/28 pass**.
- [x] `pnpm nx test portal-shell` (clean env) → **34/34 pass**.
- [x] `pnpm nx run-many -t lint build --projects=portal-bff,feature-auth,portal-shell` → clean.
- [x] Prettier-clean.
- [x] CI clean-env repro: every env var unset (including new `RATE_LIMIT_*`) → 261/261 pass.
- [ ] Manual smoke against running BFF:
  - [ ] Throw any error from a controller → response is `{ error: { code, message, traceId } }`. Pino log has the full exception under `err`.
  - [ ] Curl `/api/auth/me` without a session cookie → 401 + same envelope, `code: 'unauthenticated'`.
  - [ ] Hit `/api/auth/login` 11 times in a minute → 11th returns 429 + `code: 'rate_limited'`. `/api/health` hit 100 times → all 200.
  - [ ] POST without `X-CSRF-Token` → 403 + `code: 'csrf'`.

---------

Co-authored-by: Julien Gautier <julien.gautier@apf.asso.fr>
Reviewed-on: #123
2026-05-13 21:34:33 +02:00
julien 5bbe2304ff feat(portal-bff): helmet + env-driven CORS allowlist + double-submit CSRF (#122)
CI / commits (push) Has been skipped
CI / scan (push) Successful in 2m10s
CI / check (push) Successful in 3m16s
CI / a11y (push) Successful in 1m6s
CI / perf (push) Successful in 4m16s
## Summary

Phase-2 security baseline that the `main.ts` placeholder note has been advertising since the auth/session work began. Three independent middlewares + their SPA counterparts, all mounted in a single PR because they only become meaningful together.

### Helmet on the BFF

`helmet()` with three overrides matching our specific shape:

- **HSTS only in production** — dev runs on plain HTTP, HSTS is just noise.
- **`crossOriginResourcePolicy: 'cross-origin'`** — the SPA on its own origin reads JSON from the BFF; the default `same-origin` would block it.
- **CSP disabled in non-production** — the BFF doesn't render HTML, so CSP on JSON responses is mostly inert, but Helmet's default CSP triggers noisy `connect-src` violations in browser devtools that we don't need.

Everything else is Helmet defaults: `X-Frame-Options=SAMEORIGIN`, `X-Content-Type-Options=nosniff`, `Referrer-Policy=no-referrer`, `X-Powered-By` removed, etc.

### CORS allowlist, env-driven

`CORS_ALLOWED_ORIGINS` env (comma-separated) is now **mandatory** at boot. The BFF refuses to start without it via `readCorsAllowlist()` — same boot-time validator family as `assertSessionSecret` etc. The previous hardcoded `http://localhost:4200` fallback is gone; getting CORS wrong silently is the kind of "works in dev, breaks in prod" trap the validator is specifically designed to catch. `X-CSRF-Token` is now in the allowed headers.

### Double-submit CSRF

- BFF mints a 256-bit `csrfToken` at session creation (`/auth/callback`), stored on `req.session.csrfToken` and mirrored to a JS-readable cookie (`__Host-portal_csrf` prod / `portal_csrf` dev). The cookie is the SPA's read-only view; the server-side session is the source of truth.
- `createCsrfMiddleware` (mounted after the session middleware in `main.ts`) compares the `X-CSRF-Token` header with `req.session.csrfToken` using `crypto.timingSafeEqual`. Skips:
  - safe methods (`GET / HEAD / OPTIONS`),
  - anonymous requests (no `req.session.user`),
  - `/api/auth/login` and `/api/auth/callback` (those mint the token themselves).
- Mismatch → `403 {"error":"csrf"}` with a structured Pino warn.
- SPA's `csrfInterceptor` reads the cookie via `document.cookie` and copies its value into `X-CSRF-Token` on every mutating BFF request. The header is omitted on `GET / HEAD / OPTIONS` (BFF skips them anyway) and on non-BFF origins.
- Logout and the absolute-timeout middleware both clear the CSRF cookie alongside the session cookie.

## Notable choices

**Session-bound double-submit, not pure cookie-vs-header.** A naive "compare cookie with header" check is defeated when an attacker can plant a cookie (subdomain takeover, etc.). Comparing the header to the server-side session-stored token instead means the attacker would also need to be the authenticated user — which is what CSRF defense is supposed to prevent in the first place.

**No CSRF for anonymous mutating routes (v1).** None exist today; we don't have an unauthenticated POST endpoint anywhere. Generating a CSRF token for anonymous sessions would conflict with `saveUninitialized: false` on express-session and add complexity we don't need yet. Anonymous public-form CSRF defenses (site-key, captcha) land if and when those routes ship.

**`SameSite=Lax`, not `Strict`, on the CSRF cookie.** Matches the session cookie's policy so the two travel together on the SPA→BFF cross-origin same-site fetch (different ports = different origin, same registrable domain). The double-submit pattern is what gives the protection; `SameSite=Lax` is a belt-and-braces layer.

**`csrfInterceptor` runs after `bffCredentialsInterceptor` and before `bffUnauthorizedInterceptor` in the chain.** Order: credentials first (set `withCredentials`), then CSRF (set the header), then unauthorized handling (catch 401s). Forward order, no surprises.

**`CORS_ALLOWED_ORIGINS` has no localhost fallback.** I considered keeping the fallback for ergonomics but it makes the BFF silently misconfigured if someone forgets the env. The error message points straight at the file to edit.

## Out of scope (next PRs)

- Rate limiting + structured error filter (still in the phase-2 to-do).
- CSP fine-tuning when we have actual HTML pages (portal-shell + portal-admin static serving).
- CSRF token rotation on idle-extension (today the token lives the session's lifetime; refreshing on each request would invalidate in-flight mutations).

## Test plan

- [x] `pnpm nx run-many -t test --projects=portal-bff,feature-auth,portal-shell` clean env → **177 + 28 + 34 = 239/239 pass** (was 144 + 19 + 34 = 197 before; +42 specs across CSRF middleware, CSRF cookie helpers, CORS allowlist parser, csrfInterceptor, and extended auth.controller / absolute-timeout coverage).
- [x] `pnpm nx run-many -t lint build --projects=portal-bff,feature-auth,portal-shell` → clean.
- [x] **CI clean-env repro** (lesson from prior PRs): every env var unset (including new `CORS_ALLOWED_ORIGINS`) → tests still pass. The BFF refuses to boot without `CORS_ALLOWED_ORIGINS`, which is the intended behaviour.
- [x] Prettier-clean.
- [ ] Manual smoke against running BFF:
  - [ ] Sign in → `__Host-portal_csrf` (prod) / `portal_csrf` (dev) cookie set, value matches `audit.events.payload->>actorIdHash`-style traceability via `req.session.csrfToken` in Redis.
  - [ ] Hit a future POST route from the SPA → request carries `X-CSRF-Token`, BFF accepts.
  - [ ] Forge a POST without the header (curl) → 403 `{"error":"csrf"}`.
  - [ ] Sign out → both cookies cleared.

---------

Co-authored-by: Julien Gautier <julien.gautier@apf.asso.fr>
Reviewed-on: #122
2026-05-13 20:50:44 +02:00
julien 2e9605a078 chore(deps): pin protobufjs to >=8.0.2 to clear 7 transitive advisories (#111)
CI / scan (push) Successful in 1m54s
CI / commits (push) Has been skipped
CI / check (push) Successful in 2m52s
CI / a11y (push) Successful in 1m1s
CI / perf (push) Successful in 2m50s
## Summary

`protobufjs 8.0.0` / `8.0.1` ship 7 fresh advisories (4 high, 3 moderate) pulled in transitively as `@opentelemetry/exporter-trace-otlp-http → @opentelemetry/otlp-transformer → protobufjs`. `pnpm audit --audit-level=moderate` flags them, which blocks the `ci:audit` gate on every push.

Pins the package directly via `pnpm.overrides` — same shape we already use for `axios`, `brace-expansion`, `follow-redirects`, `ip-address`, `tmp`, `yaml`. Resolution lands on `protobufjs@8.2.0` (latest stable). The override is `protobufjs@<8.0.2: ">=8.0.2"` so it auto-yields the moment a non-vulnerable transitive lands and the override becomes a no-op — no need to remember to remove it.

## Advisories cleared

| ID                  | Severity | Issue                                                 |
| ------------------- | -------- | ----------------------------------------------------- |
| GHSA-66ff-xgx4-vchm | high     | code generation gadget                                |
| GHSA-75px-5xx7-5xc7 | high     | code generation gadget after prototype pollution      |
| GHSA-jvwf-75h9-cwgg | high     | process-wide DoS through unsafe option paths          |
| GHSA-685m-2w69-288q | high     | DoS through unbounded protobuf recursion              |
| GHSA-q6x5-8v7m-xcrf | moderate | overlong UTF-8 decoding                               |
| GHSA-2pr8-phx7-x9h3 | moderate | DoS from crafted field names in generated code        |
| GHSA-fx83-v9x8-x52w | moderate | prototype injection in generated message constructors |

## Test plan

- [x] `pnpm audit --audit-level=moderate` → **No known vulnerabilities found**
- [x] `pnpm nx test portal-bff` → 99/99 pass
- [x] `pnpm nx build portal-bff` → webpack compiled successfully
- [x] `pnpm install` resolves `protobufjs@8.2.0` (verified in lockfile)

---------

Co-authored-by: Julien Gautier <julien.gautier@apf.asso.fr>
Reviewed-on: #111
2026-05-12 19:16:18 +02:00
julien 758d723744 feat(portal-bff): session middleware with AES-256-GCM at rest per ADR-0010 (#110)
CI / commits (push) Has been skipped
CI / scan (push) Failing after 2m25s
CI / check (push) Successful in 3m48s
CI / a11y (push) Successful in 1m27s
CI / perf (push) Successful in 4m28s
## Summary

Mounts `express-session` + `connect-redis` at bootstrap on top of the shared `ioredis` client, with **AES-256-GCM applied to the full JSON payload before it lands in Redis** (per ADR-0010). The configured middleware is exposed as a NestJS provider (`SESSION_MIDDLEWARE`) and `main.ts` mounts it through `app.get(...)` so it sits on the same Redis connection the rest of the BFF uses — no second client at the bootstrap layer.

Envelope is versioned (`v1.<iv>.<tag>.<ciphertext>`, all base64url) so the algorithm / key derivation can rotate without a flag-day re-encryption. Tamper / wrong-key / unknown-version all raise `SessionDecryptError`; for now the failure is logged via Pino with `event: session.decrypt_failed` — the first-class audit event lands with ADR-0013.

Scope is intentionally **infrastructure only**:
- middleware mounted on every request, `req.session` available downstream
- session id = `crypto.randomBytes(32).toString('base64url')` (256 bits per ADR-0010)
- cookie name: `__Host-portal_session` in production, `portal_session` in dev (the `__Host-` prefix mandates `Secure`, which dev HTTP can't satisfy)
- `httpOnly + sameSite=lax + path=/`; `resave:false`, `saveUninitialized:false`, `rolling:true`
- cookie `maxAge` follows `SESSION_IDLE_TIMEOUT_SECONDS` (default 1800)
- encryption-at-rest active end-to-end

Out of scope, landing in follow-ups: `/auth/callback` populating `req.session.user`, `/me`, `/auth/logout`, the absolute-timeout interceptor, and the `user_sessions:{userId}` secondary index.

## Notable shape choices (ADR-0010 amended in the same commit)

**Full-payload encryption vs. just the `tokens` field.** The first draft of ADR-0010 scoped at-rest encryption to a `tokens` sub-field. The session also carries claims (`oid`, `tid`, `preferred_username`, …) that qualify as PII under GDPR — for an APF-Handicap portal handling health-adjacent data this matters. Encrypting the envelope is strictly stronger and removes the need to classify fields one by one. The ADR text is updated to match.

**`ioredis` + adapter vs. switching the BFF to `node-redis`.** `connect-redis` v9 was rewritten for `node-redis` v4 and no longer accepts `ioredis` directly. Two reasonable paths:
1. **Adapter (chosen)** — keep the shared `ioredis` client; shim the six commands `connect-redis` actually calls (`get`, `set` with `{expiration:{type:'EX',value}}`, `expire`, `del`, `mGet`, `scanIterator`) to the node-redis shape. Smallest blast radius — RedisModule, OBO cache (ADR-0014), future pub/sub all stay on a single Redis library.
2. **Switch RedisModule to `node-redis`** — clean alignment with `connect-redis`'s expectations, but touches every Redis consumer and would itself require an ADR amendment.

The adapter is reversible: if we ever decide to standardise on `node-redis`, deleting one file removes it. Happy to switch if you'd rather take that path.

## Env vars

- `SESSION_ENCRYPTION_KEY` — **mandatory**, AES-256-GCM key (32 bytes after base64url decode). New `assertSessionEncryptionKey()` validator wired in `main.ts` alongside the other pre-flight checks.
- `SESSION_IDLE_TIMEOUT_SECONDS` — optional, default `1800`.
- `SESSION_ABSOLUTE_TIMEOUT_SECONDS` — optional, default `43200` (consumed by the absolute-timeout interceptor in a follow-up).

`.env.example` updated; the three variables are promoted from the "future vars" block to the active section.

## Test plan

- [x] `pnpm nx test portal-bff` — **99/99 pass** (was 62 before this PR; +37 new specs across the 5 new files).
- [x] `pnpm nx build portal-bff` — clean webpack build.
- [x] `pnpm nx lint portal-bff` — clean.
- [x] Prettier-clean for all PR source files.
- [ ] Local smoke test once the next PR wires `/auth/callback` → `req.session.user`; this PR has no user-visible behaviour to exercise on its own.

---------

Co-authored-by: Julien Gautier <julien.gautier@apf.asso.fr>
Reviewed-on: #110
2026-05-12 18:06:13 +02:00
julien d4b5ed1c5d feat(portal-bff): redis client foundation per ADR-0010 (#109)
CI / scan (push) Successful in 2m35s
CI / commits (push) Has been skipped
CI / check (push) Successful in 3m29s
CI / a11y (push) Successful in 1m43s
CI / perf (push) Successful in 3m38s
## Summary

First step toward Redis-backed sessions (ADR-0010). Adds the shared `ioredis` connection that every downstream consumer (session storage, OBO token cache, …) injects via the new `REDIS_CLIENT` DI token. No session logic in this PR — that's the next one.

## What lands

- **`ioredis@^5.10.1`** as a direct dependency. Chosen by ADR-0010 for its mature Sentinel support — single-instance URL today, Sentinel-HA configuration lands with the prod infrastructure ADR.
- **[`.env.example`](apps/portal-bff/.env.example)** promotes `REDIS_URL` from its future-vars comment to an active variable, defaulting to the local Compose stack's address. The Sentinel-style keys (`REDIS_SENTINEL_HOSTS`, `REDIS_SENTINEL_NAME`, `REDIS_TLS`) stay in the future-vars comment until the prod deploy.
- **[`check-redis-config.ts`](apps/portal-bff/src/config/check-redis-config.ts)** — boot-time guard mirroring the existing four:
  - Refuses to start on missing / non-`redis(s)://` / passwordless / placeholder URLs.
  - Returns a typed `RedisConfig` with parsed `host` + `port` for downstream observability.
- **[`redis.token.ts`](apps/portal-bff/src/redis/redis.token.ts)** — `REDIS_CLIENT` string token + `Redis` type alias. Same shape as the existing `ENTRA_CONFIG` / `MSAL_CLIENT`.
- **[`redis.module.ts`](apps/portal-bff/src/redis/redis.module.ts)** — `RedisModule` factory provider:
  - Caps `maxRetriesPerRequest: 3` so an unreachable Redis surfaces a clear command-time error rather than an infinite reconnect storm.
  - Wires `connect` / `ready` / `error` / `close` / `reconnecting` events into the Pino stream under the `redis` context — easy log isolation.
  - Non-global; consumers import the module to state "I depend on Redis".
- **`main.ts`** calls `assertRedisConfig()` alongside the other three validators; **`AppModule`** imports `RedisModule`.

## Decisions worth flagging

- **`maxRetriesPerRequest: 3`** rather than the ioredis default of 20. With the default, a Redis outage masquerades as request-level timeouts spread over minutes. Capping low surfaces the outage in the first command failure — the BFF can then return 503 and recover quickly when Redis comes back.
- **Single shared client.** Pub/sub use-cases (when they appear) duplicate via `redis.duplicate()` per ioredis convention. Connect/disconnect is one socket per BFF instance.
- **No explicit shutdown hook yet.** Node's process-exit handlers and ioredis's own cleanup take care of the socket on SIGTERM / Ctrl+C. If we see stuck connections in real load, we wire `OnApplicationShutdown` + `redis.quit()`.
- **Sentinel-style config stays in the future-vars comment.** ioredis supports it natively, but plumbing it on top of the URL form complicates the validator and the factory for zero v1 payoff. Lands with the prod infrastructure ADR.

## Verification

- `nx run-many -t lint test build --projects=portal-bff` — green.
- **62 / 62 specs** (was 52; +10 — `check-redis-config` covers happy path + 6 failure modes; `redis.module` covers DI resolution against an unreachable URL plus the missing-env failure).
- Boot smoke against the local Compose stack: Pino's `redis` context shows `redis.connect` → `redis.ready` on startup; killing the Redis container produces `redis.close` / `redis.reconnecting` lines.

## What this PR explicitly does NOT do

- Mount `express-session` + `connect-redis` middleware. The next PR wires the session cookie (`__Host-portal_session`), the encrypted payload, and the lookup middleware that attaches `user` to every request.
- Plug the callback into session creation. Auth still ends with a Pino log + redirect; the SPA still sees the user anonymous on the next request.
- Sentinel / TLS configuration. Future-var keys are documented in `.env.example` for when the prod deploy lands.

---------

Co-authored-by: Julien Gautier <julien.gautier@apf.asso.fr>
Reviewed-on: #109
2026-05-12 16:48:20 +02:00
julien 0eb404d111 feat(portal-bff): /auth/login route — pkce flow start + signed cookie (#105)
CI / scan (push) Successful in 2m27s
CI / commits (push) Has been skipped
CI / check (push) Successful in 3m29s
CI / a11y (push) Successful in 1m58s
CI / perf (push) Successful in 4m3s
## Summary

Third step of ADR-0009 wiring. Adds the first OIDC route, `GET /api/auth/login`: it 302s the browser to Entra's authorize endpoint with a freshly-generated state + PKCE challenge, and stashes the matching `{state, codeVerifier}` payload in a short-lived signed cookie so the next-PR callback can verify the round-trip.

## What lands

- **Cookie infra**: `cookie-parser` + `@types/express` deps; `main.ts` mounts the cookie middleware with the `SESSION_SECRET` signing key. Signed cookies are now available via `req.signedCookies` for the upcoming callback.
- **[`.env.example`](apps/portal-bff/.env.example)** promotes `SESSION_SECRET` from a future-vars comment into an active section, with a one-liner showing how to generate 32 random bytes.
- **[`check-session-secret.ts`](apps/portal-bff/src/config/check-session-secret.ts)** — boot-time guard: refuses to start if `SESSION_SECRET` is unset, still the .env.example placeholder, or decodes below 32 bytes of entropy. Same family as `check-database-url` / `check-entra-config`.
- **[`auth.service.ts`](apps/portal-bff/src/auth/auth.service.ts)** — `beginAuthCodeFlow()` uses MSAL's `CryptoProvider` for canonical PKCE verifier / challenge generation and a fresh GUID state per call, calls `msal.getAuthCodeUrl()` with the configured redirect URI + OIDC scopes (`openid profile email` — no `offline_access` in v1), and returns `{ authUrl, preAuthPayload }`.
- **[`auth.cookie.ts`](apps/portal-bff/src/auth/auth.cookie.ts)** — `portal_pre_auth` name, 5-minute TTL, shared `CookieOptions`: `signed`, `httpOnly`, `sameSite: 'lax'` (lets Entra's cross-site top-level redirect back through), `secure` toggled by `NODE_ENV`.
- **[`auth.controller.ts`](apps/portal-bff/src/auth/auth.controller.ts)** — `@Controller('auth') @Get('login')`: writes the cookie then 302s. Thin shell around the service.
- **AuthModule** registers the new controller + service alongside the existing `ENTRA_CONFIG` and `MSAL_CLIENT` providers.

## Decisions worth flagging

- **Scope deliberately stops before the callback.** It's the next PR. Clicking `/auth/login` today round-trips through Entra and lands on a 404 — bounded mid-state, documented in the commit and here.
- **State + verifier in the cookie, not in Redis.** Keeps `/login` stateless (no server-side store), which means the BFF stays horizontally scalable from day one without sticky-session config. The next-PR callback reads `req.signedCookies` to recover the payload.
- **`portal_pre_auth`, not `__Host-portal_pre_auth`.** `__Host-` mandates `Secure`, and local dev is HTTP. The prefix + `Secure: true` lands together with the production TLS hardening ADR.
- **No `offline_access` scope.** Sessions are short-lived (per ADR-0010); the user re-authenticates through Entra rather than the BFF refreshing tokens behind their back. Smaller token footprint, less code to write, easier to reason about.
- **5-minute cookie TTL.** Enough for the Entra round-trip (including a fresh MFA prompt), short enough that a stale cookie can't be replayed long after the user abandoned the flow.

## Verification

- `nx run-many -t lint test build --projects=portal-bff` — green.
- **39 / 39 specs** (was 30; +9 across `check-session-secret`, `auth.service`, `auth.controller`).
- The service spec mocks `getAuthCodeUrl`, asserts the redirect URI / scopes / S256 method, the state-verifier identity between the cookie payload and what's sent to Entra, and fresh-per-call replay protection.
- The controller spec asserts the cookie name + options + serialized payload and the 302 redirect.

## Manual smoke test (next PR completes the loop)

1. `apps/portal-bff/.env` has real `ENTRA_*` + `SESSION_SECRET`.
2. `nx serve portal-bff`.
3. `curl -i http://localhost:3000/api/auth/login` → 302 with `Set-Cookie: portal_pre_auth=…; HttpOnly; SameSite=Lax; Path=/`, `Location: https://login.microsoftonline.com/<tenant>/oauth2/v2.0/authorize?...`.
4. Open the `Location` in a browser, authenticate, Entra redirects to `http://localhost:3000/api/auth/callback?code=…&state=…` → 404 today, will be the next PR.

## Next PR on the auth track

`GET /api/auth/callback` — reads the signed cookie, verifies `state` matches, calls `acquireTokenByCode` with the stored verifier, validates the ID token (issuer, audience, exp, nonce, `amr` per ADR-0011), clears the pre-auth cookie, logs the resolved user identity, redirects to `/` (SPA). Still no session — that's the PR after.

---------

Co-authored-by: Julien Gautier <julien.gautier@apf.asso.fr>
Reviewed-on: #105
2026-05-12 11:20:03 +02:00
julien b7093d61de feat(portal-bff): msal confidential client provider in AuthModule (#104)
CI / check (push) Successful in 3m21s
CI / commits (push) Has been skipped
CI / scan (push) Successful in 2m43s
CI / a11y (push) Successful in 1m56s
CI / perf (push) Successful in 3m34s
Second step of ADR-0009 wiring. AuthModule now exposes the
`@azure/msal-node` confidential client alongside the parsed Entra
config — the building block the upcoming OIDC routes inject to issue
the auth-code URL, exchange the callback code for tokens, and
acquire downstream tokens on behalf of the user.

What lands:

- `@azure/msal-node` added as a direct dependency (^5.2.1).
- `apps/portal-bff/src/auth/msal-client.token.ts` — `MSAL_CLIENT`
  string token + `ConfidentialClientApplication` type re-export.
  Mirrors the `ENTRA_CONFIG` token shape from PR #102.
- AuthModule grows a factory provider for `MSAL_CLIENT`:
  - Injects `ENTRA_CONFIG` + nestjs-pino `Logger`.
  - Builds a `ConfidentialClientApplication` with `clientId`,
    `authority`, `clientSecret` from the parsed config.
  - Wires `system.loggerOptions.loggerCallback` to forward MSAL's
    internal log lines into the Pino stream (per ADR-0012) — Error
    → logger.error, Warning → logger.warn, Verbose / Trace →
    logger.debug, Info → logger.log. PII logging is disabled by
    default so tokens / user identifiers never leak into our
    structured log records.
  - Sets MSAL's `logLevel` to Info — Pino's own threshold
    re-filters from there.
  - All MSAL log lines carry the `msal` Pino context for easy
    isolation in log queries.
- `AuthModule.exports` extended to include `MSAL_CLIENT`.

Verification:

- `nx run-many -t lint test build --projects=portal-bff` — green.
- 30/30 specs (was 29; +1 covering MSAL client construction).
- New spec imports `nestjs-pino`'s `LoggerModule.forRoot({ pinoHttp:
  { level: 'silent' } })` to provide the same Logger the production
  app supplies via `ObservabilityModule`, without flooding test
  stdout. Two tests assert the provider tree resolves correctly
  (ENTRA_CONFIG + MSAL_CLIENT) and one re-checks the missing-env
  failure mode still propagates through the new factory.

Construction is cheap — MSAL Node defers authority discovery to the
first auth call — so the client is built eagerly at module init.
The factory is injection-only; no MSAL methods get invoked yet.
Routes land in the next PR.

<!--
PR title format — becomes the squash-merge subject on main, validated by commitlint.

  <type>(<scope>): <short description>

Examples:
  feat(portal-shell): add user-preferences panel skeleton
  fix(portal-bff): correct env var bracket access
  docs(decisions): add ADR-0018 for security baseline
  chore(deps): bump @nx/* to 22.7.2

Imperative mood, lowercase, no trailing period, target ≤ 70 chars.
See docs/development.md §5 for the full convention (types, scopes).
-->

## Summary

## Motivation

## Implementation notes

## Verification

- [ ] `pnpm ci:check` green locally
- [ ] `pnpm ci:audit` green (or pre-existing drift acknowledged)
- [ ] Tested manually:
- [ ] Architecture diagram updated (if `docs/architecture.md` was affected)
- [ ] ADR amended or added (if a decision changed)

## Related

---------

Co-authored-by: Julien Gautier <julien.gautier@apf.asso.fr>
Reviewed-on: #104
2026-05-12 10:34:23 +02:00
APF Portal Bot abd651b697 chore(deps): update dependency @types/node to v24.12.4 (#103)
CI / commits (push) Has been skipped
CI / scan (push) Successful in 2m37s
CI / check (push) Successful in 4m17s
CI / a11y (push) Successful in 1m43s
CI / perf (push) Successful in 5m32s
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/node) ([source](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node)) | devDependencies | patch | [`24.12.3` -> `24.12.4`](https://renovatebot.com/diffs/npm/@types%2fnode/24.12.3/24.12.4) |

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined).

🚦 **Automerge**: Enabled.

♻ **Rebasing**: Whenever PR is behind base branch, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0MC42Mi4xIiwidXBkYXRlZEluVmVyIjoiNDAuNjIuMSIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOlsiZGVwZW5kZW5jaWVzIl19-->

Reviewed-on: #103
Co-authored-by: APF Portal Bot <jgautier.webdev+apf-portal-bot@gmail.com>
Co-committed-by: APF Portal Bot <jgautier.webdev+apf-portal-bot@gmail.com>
2026-05-12 09:41:08 +02:00
julien 29d16c7527 feat(portal-shell): wire @angular/localize plumbing per ADR-0019 (#91)
CI / check (push) Successful in 2m49s
CI / commits (push) Has been skipped
CI / scan (push) Successful in 1m42s
CI / a11y (push) Successful in 1m30s
CI / perf (push) Failing after 56s
## Summary

First implementation step of ADR-0019. Wire the `@angular/localize` plumbing into `portal-shell` so the next sweep PR can start marking UI strings without any infrastructure work.

## What changes

- **Promote `@angular/localize` to a direct dependency** (it was already a transitive via the Angular metapackage; promoting it makes the `init` polyfill explicitly resolvable from the project).
- **Configure the `i18n` block** in [`apps/portal-shell/project.json`](apps/portal-shell/project.json):
  - `sourceLocale: { code: "en", baseHref: "/en/" }` — matches the project English-only rule.
  - `locales.fr: { translation: "...messages.fr.xlf", baseHref: "/fr/" }` — single target locale for now.
- **Add the `init` polyfill** to the build target (`"polyfills": ["@angular/localize/init"]`).
- **Add an `extract-i18n` Nx target** that wraps Angular's `@angular/build:extract-i18n` executor and drops the source XLF next to the translation files.
- **Enable `--localize` on the production build** — `nx build portal-shell --configuration=production` now emits two folders side by side: `dist/apps/portal-shell/browser/en/` and `.../fr/`. Each carries its own `<html lang>` and `<base href>` per the ADR.
- **Seed an empty `messages.fr.xlf`** (XLIFF 1.2 skeleton with sourceLanguage="en" / targetLanguage="fr" and an inline editor convention note). The sweep PR drops `<trans-unit>` entries directly into the body block.

## Verification

```
dist/apps/portal-shell/browser/
├── en/
│   └── index.html   ← <html lang="en">, <base href="/en/">
└── fr/
    └── index.html   ← <html lang="fr">, <base href="/fr/">
```

Until the sweep PR marks strings, both bundles ship the same English source text — that's expected and matches what the ADR calls out ("the FR bundle falls back to source text for every untranslated key").

## What this PR explicitly does NOT do

- **Mark UI strings.** Every `i18n` attribute / `$localize` call lands in the next PR. Pure infra commit here.
- **Locale switcher in the footer** + `__Host-portal_locale` cookie + smart `/` redirect. Lands once switching shows a meaningful difference.
- **Collapse `/accessibility` + `/accessibilite` into a single localised route.** Depends on marked text + localized route paths — sweep PR territory.
- **CI gate** that fails the build on missing translations. Lands when there are translations to be missing.

## Test plan

- [x] `pnpm exec nx run-many -t lint test build --projects=portal-shell` — green (36 / 36 specs unchanged).
- [x] `pnpm exec nx build portal-shell --configuration=production` — produces both locale folders with correct `<html lang>` and `<base href>`.
- [x] `pnpm exec nx run portal-shell:extract-i18n` — runs cleanly, reports `(Messages: 0)` as expected.
- [x] Production initial bundle: **123 kB gzip per locale** (vs 121 kB on `main`; +1.5 kB for the `@angular/localize` runtime polyfill). Both stay well under the 300 KB budget.
- [ ] Manual: `pnpm exec nx serve portal-shell` runs unchanged (source locale `en`, no `--localize` in dev for now).

---------

Co-authored-by: Julien Gautier <julien.gautier@apf.asso.fr>
Reviewed-on: #91
2026-05-11 16:46:08 +02:00
julien 0ae7e0e23d feat(portal-shell): light / dark / auto theme switcher (#86)
CI / check (push) Successful in 3m38s
CI / commits (push) Has been skipped
CI / scan (push) Successful in 1m53s
CI / a11y (push) Successful in 1m27s
CI / perf (push) Successful in 2m28s
## Summary

- Add a header dropdown letting users pick **light**, **dark**, or **auto** (follow the OS) color schemes. Choice persists in `localStorage`, applies on next reload, and reacts live to OS theme changes when in `auto`.
- Built on `@angular/cdk/menu` for accessible roving focus, escape-to-close, and proper `menuitemradio` semantics on the three options.
- Apply `dark:` variants across the shell (header, sidebar, main bg) and the existing two pages. No semantic-token refactor yet — that belongs in a future ADR (`--color-surface-1`, `--color-text-1`, …).

## Architecture

- [`LayoutStateService`](apps/portal-shell/src/app/state/layout-state.service.ts) grows a `themeMode: signal<'light'|'dark'|'auto'>` alongside the existing `sidebarCollapsed`, plus an `effectiveTheme` computed that resolves `auto` against the system preference. A side-effect toggles the `.dark` class on `<html>`, so every `dark:` Tailwind utility flips at once.
- Tailwind v4 dark mode is rewired to **class-based** via `@custom-variant dark (&:where(.dark, .dark *));` in `styles.css`. This overrides the v4 default (`prefers-color-scheme` only) so the user's explicit override wins over the OS preference.
- The switcher trigger reflects the **selected** mode (sun / moon / monitor), not the effective theme — so users can tell which mode they're in even when `auto` happens to resolve to the same scheme as a manual pick.

## Side-edits in the same PR (already validated in the chat)

- **Logo asset.** Replace `apf-small.svg` (94 kB — a base64-PNG wrapped in SVG markup, not actually vector) with `apf-small.png` (144×144, 7.6 kB after `sharp --kernel lanczos3 --compressionLevel 9`). Header swaps to the PNG. The wide vector `apf-portal.svg` stays around for future surfaces that want the horizontal lockup.
- **Revert FR strings** that crept into the header template — project rule (CLAUDE.md) is English-only for source artefacts; FR localisation will happen properly via `@angular/localize` (separate ADR).

## Decisions worth flagging

- **Dropdown over segmented control.** The CDK Menu pays its weight: accessible by default (proper `aria-haspopup`, focus management, ESC handling, click-outside dismissal), reusable for future header menus (user, language, notifications), and one tidy primitive rather than three competing buttons.
- **`auto` is the default,** not `light`. Most users have an OS-level preference already; respecting it is the least surprising baseline.
- **`<html>.dark` class lives at the root,** not on `<app-root>`. That's the Tailwind convention and it means CDK overlay popups (the menu itself, future dialogs) inherit the right theme without extra wiring.
- **Bundle delta +21 kB gzip** (100 → 121 kB initial). All of it is `@angular/cdk/{menu,overlay,a11y}` and the dark CSS rules. We stay well under the 300 kB budget. The CDK is already on the architecture menu (ADR-0016 — *UI stack: Angular CDK + TailwindCSS*) so this is on-strategy spend.
- **No semantic tokens yet.** The dark variants use raw Tailwind gray ramps (`dark:bg-gray-900`, etc.) instead of a `--color-surface-1` / `--color-text-1` token layer. That keeps the change tractable for now; promotion to semantic tokens deserves its own ADR with the design team in the loop.

## Accessibility (ADR-0016)

- Menu trigger has `aria-haspopup="menu"`, `aria-label` announcing the current mode + "(open menu)".
- Menu uses `role="menu"`, items use `role="menuitemradio"` with `aria-checked` — assistive tech announces the selection state correctly.
- All interactive controls keep the 44×44 px touch target.
- `prefers-reduced-motion: reduce` already covered by the sidebar transitions; theme switcher has no animations of its own.
- Contrast: dark surfaces are gray-900 + gray-800 border / gray-100 text — passes WCAG AA. Brand primary shifted to the `300` step in dark mode so the active states keep contrast against gray-900.

## What this PR explicitly does NOT do

- Tokenise the palette into semantic surface / text / border roles (next iteration, ADR-led).
- Localise UI strings (separate `@angular/localize` ADR + PR).
- Animate the theme transition (FOIT-style flicker on toggle is acceptable in v1; we can soften later with a `color-scheme` CSS transition if it bothers users).

## Test plan

- [x] `pnpm exec nx run-many -t lint test build --projects=portal-shell` — green (**33 / 33 specs**, +8 for the theme work).
- [x] Production build: **121 kB gzip initial** (was 100 kB). Under the 300 kB budget.
- [ ] Manual: toggle each of the three modes → header / sidebar / main / cards switch surface colors instantly; trigger glyph updates.
- [ ] Manual: pick `auto`, change OS theme → UI follows live (Chrome DevTools → Rendering → "Emulate CSS media feature prefers-color-scheme").
- [ ] Manual: reload after each pick → the chosen mode is restored.
- [ ] Manual: keyboard the trigger → ENTER opens menu, arrow keys navigate, ENTER selects, ESC closes; focus returns to the trigger on close.
- [ ] Manual: Lighthouse accessibility on `/` in dark mode — score unchanged from light mode.

---------

Co-authored-by: Julien Gautier <julien.gautier@apf.asso.fr>
Reviewed-on: #86
2026-05-11 03:01:11 +02:00
julien 3371fbd613 feat(portal-shell): app-shell layout with collapsable sidebar (#83)
CI / commits (push) Has been skipped
CI / scan (push) Successful in 1m37s
CI / check (push) Successful in 2m25s
CI / a11y (push) Successful in 57s
CI / perf (push) Successful in 3m5s
## Summary

- Replace the flat header+main+footer layout with a real app-shell: fixed header on top, collapsable sidebar + scrollable main below. Sidebar state (collapsed / expanded) persists across reloads via `localStorage`.
- Introduce the APF brand palette as Tailwind v4 `@theme` tokens (primary teal `#12546c`, accent orange `#f7a919`) so every utility (`bg-brand-primary-500`, `text-brand-accent-400`, `ring-brand-primary-200`, …) is available from now on.
- Add an `<app-icon>` façade backed by `lucide-angular` for v1. Logical kebab-case names already match the icomoon-sprite convention, so the future migration is a single-file change in `icon.ts` and consumers stay untouched.
- Migrate the accessibility-statement links from the (now-deleted) footer to the bottom of the sidebar.

## Decisions worth flagging

- **Static menu, permission-shaped data.** Items point to `#` placeholders in v1, except *Dashboard* which is `routerLink="/"` so the active-state styling is visible on the home page. The `MenuItem` shape already carries an optional `requiredPermissions: string[]` so the permission-aware filter (PR 2, alongside ADR-0009 auth) plugs in without restructuring.
- **`<app-icon>` over direct lucide imports.** Consumers write `<app-icon name="bell">` rather than importing the lucide pascal-case symbol. When the icomoon sprite lands, only the registry in `icon.ts` changes — templates do not.
- **Sidebar persistence via `localStorage`, not backend.** Zero round-trip, survives reloads, falls back gracefully when storage is blocked (private mode). Eventually mirrored server-side if the user-preferences feature lands.
- **Footer removed entirely.** With the sidebar carrying the FR + EN accessibility-statement links and the role badge, the bottom rail no longer earned its vertical real estate. The version badge moved out for now; it will return as part of a debug/help menu when there's a real release to surface.

## Accessibility (ADR-0016)

- Skip-link preserved (WCAG 2.4.1 *Bypass Blocks*) and restyled in the brand palette.
- Sidebar exposes named landmarks (`<nav aria-label="Sections">`, `<nav aria-label="Accessibility">`) and the collapse button uses `aria-expanded` + a descriptive `aria-label`.
- Active links carry `ariaCurrentWhenActive="page"`.
- All interactive controls (header action buttons, sidebar links/toggle) meet the 44×44 px minimum hit-target.
- Sidebar width transition is skipped under `prefers-reduced-motion: reduce`.
- Lucide SVGs are marked `aria-hidden` (decorative); accessible names live on the parent control.

## Perf (ADR-0017)

- Production build: **100 kB gzip** initial transfer (budget: 300 kB). Lucide imports are tree-shaken — only the ~18 icons actually used ship.

## What this PR explicitly does NOT do

- Wire icon-set migration to icomoon (kept as a deferred swap behind `<app-icon>`).
- Filter the menu by permission (deferred to PR 2 once the auth flow lands).
- Replace the avatar placeholder with a real user menu (waits on ADR-0009).
- Implement the search input behavior (placeholder only; needs a search backend).

## Test plan

- [x] `pnpm exec nx run-many -t lint test build --projects=portal-shell` — green (19 / 19 specs).
- [x] Production build under bundle budgets (100 kB gzip initial).
- [ ] Manual: load `/`, confirm Dashboard appears active in the sidebar, collapse → reload → still collapsed, focus the address bar then Tab → skip-link visible, keyboard-traverse the sidebar.
- [ ] Manual: `prefers-reduced-motion: reduce` → no width animation when toggling.
- [ ] Manual: zoom to 200 % → no horizontal scroll, header search hides at narrow widths (`md:` breakpoint).

---------

Co-authored-by: Julien Gautier <julien.gautier@apf.asso.fr>
Reviewed-on: #83
2026-05-11 00:36:41 +02:00
julien 8f2cd4e068 feat(portal-shell): wire spa-side opentelemetry tracing (#72)
CI / commits (push) Has been skipped
CI / scan (push) Successful in 1m15s
CI / a11y (push) Successful in 1m6s
CI / perf (push) Successful in 2m0s
CI / check (push) Successful in 43s
## Summary

Phase 2 of ADR-0012 — closes the loop SPA → BFF → DB. After this PR, a single user action (initial page load, click, form submit) produces one trace whose root span is owned by the SPA and whose child spans cover the BFF request, Postgres queries through Prisma, and (eventually) Redis / downstream-API hops.

## What lands

**Browser-side OTel libs** (production deps):

- `@opentelemetry/sdk-trace-web` — browser tracer + provider
- `@opentelemetry/exporter-trace-otlp-http` — OTLP/HTTP+JSON exporter
- `@opentelemetry/instrumentation` — auto-instrumentation runtime
- `@opentelemetry/instrumentation-fetch` — `fetch` + W3C `traceparent` propagation
- `@opentelemetry/instrumentation-document-load` — initial-paint timings
- `@opentelemetry/instrumentation-user-interaction` — click / keypress / submit

No `@opentelemetry/context-zone`: the workspace is zoneless per ADR-0004; the default `StackContextManager` covers the auto-instrumented paths. Custom spans across `await` will need explicit `context.with(...)` plumbing — fine, encountered as code lands.

**Code**:

- [`apps/portal-shell/src/observability/tracing.ts`](apps/portal-shell/src/observability/tracing.ts) — `WebTracerProvider` bootstrap. Documents the load-order constraint inline (same pattern as the BFF: must be the very first import of `main.ts`, otherwise auto-instrumentations miss everything imported above).
- `apps/portal-shell/src/main.ts` now imports the tracing module as line 1.

**CORS plumbing** for end-to-end trace propagation:

- BFF (`apps/portal-bff/src/main.ts`) calls `enableCors` with a minimal dev allowlist (`http://localhost:4200`) and explicit permission for the W3C `traceparent` / `tracestate` headers. The full security-grade CORS (per-environment allowlists, helmet, cookie-session, CSRF) belongs to the future phase-2 security ADR — this PR adds the strict minimum for the SPA→BFF trace context to survive cross-origin pre-flight.
- OTel Collector (`infra/local/otel-collector.yaml`) gains a `cors` block on its OTLP/HTTP receiver so the browser's own OTLP POST clears its pre-flight.

**ADR-0012 §Confirmation** rewritten: a new "Wired in the SPA foundation PR (phase 2)" block enumerates what landed here; the carry-over "Wired as features land" list drops the SPA-side SDK item and adds a follow-up note about the security-grade CORS.

## Verification

```bash
pnpm exec nx run-many -t lint test build           # 8 projects green
pnpm audit                                          # 0 vulns
./infra/local/dev.sh up observability               # bring up Collector + Jaeger
./infra/local/dev.sh                                # (separately, BFF stack — your choice)
pnpm nx serve portal-bff                           # localhost:3000
pnpm nx serve portal-shell                         # localhost:4200
```

Open http://localhost:4200 → a `document_load` trace appears in http://localhost:16686 with `service.name=portal-shell`. From DevTools, run `fetch('http://localhost:3000/api/health').then(r => r.json())` → a fetch span appears with a child BFF span on the same trace.

## Test plan

- [ ] CI green on this PR.
- [ ] After local up, `document_load` span visible in Jaeger UI for the SPA.
- [ ] Cross-origin fetch from SPA carries `traceparent` (visible in Network tab) and produces a single end-to-end trace SPA → BFF in Jaeger.
- [ ] DevTools console shows no CORS warnings about `traceparent`, `tracestate`, or the `localhost:4318/v1/traces` POST.

---------

Co-authored-by: Julien Gautier <julien.gautier@apf.asso.fr>
Reviewed-on: #72
2026-05-09 23:23:18 +02:00
julien b74d3f1b9b feat(portal-bff): observability foundations (Pino + CLS + OTel) (#70)
CI / commits (push) Has been skipped
CI / scan (push) Successful in 1m33s
CI / check (push) Successful in 2m3s
CI / a11y (push) Successful in 1m11s
CI / perf (push) Successful in 2m14s
## Summary

Implements ADR-0012 phase 1, BFF side. The SPA wiring is a separate phase-2 PR.

The BFF now emits structured JSON logs to stdout, tagged with `trace_id` / `span_id` from the active OTel context, and exports OTLP traces over HTTP/Protobuf to the Collector that already runs in the local-dev compose. Anything Nest, Express, HTTP-out, Prisma (Postgres) or `ioredis` does is auto-spanned. A `GET /api/health` liveness endpoint is added to round things out.

## What lands

**Runtime libs added** (production deps):

- `nestjs-pino`, `pino`, `pino-http` — structured logging
- `nestjs-cls` — request-scoped context
- `@opentelemetry/api` / `sdk-node` / `resources` / `semantic-conventions`
- `@opentelemetry/exporter-trace-otlp-proto` (HTTP/Protobuf, port 4318)
- `@opentelemetry/instrumentation-{http,express,nestjs-core,pg,ioredis,pino}` — curated, **no** `auto-instrumentations-node` mass-import (anti-bricolage)

Dev: `pino-pretty` (gated by `NODE_ENV`).

**Code:**

- `apps/portal-bff/src/observability/tracing.ts` — OTel `NodeSDK` bootstrap. Documents the load-order constraint inline (must be the very first import of `main.ts`). Pure side-effect module.
- `apps/portal-bff/src/observability/observability.module.ts` — composes `ClsModule` (UUID per request stored as `request_id`) and `LoggerModule` (`pino-pretty` in dev, raw JSON in prod, `LOG_LEVEL` env-driven, `/health` excluded from auto-logging, `X-Request-Id` honoured if inbound).
- `apps/portal-bff/src/health/{health.controller,health.module,health.controller.spec}.ts` — `GET /api/health` returning `{status, uptimeSeconds, service, version}`. Cheap liveness only — `/readiness` lands when dependencies have a readiness story.
- `apps/portal-bff/src/config/check-database-url.{ts,spec.ts}` — fail-fast validator called from `main.ts` before NestFactory boots. Catches the same family of bug that bit pgweb in #63: a literal special character in `POSTGRES_PASSWORD` that needs URL-encoding in `DATABASE_URL`. Prisma requires a URL string (no discrete-flag escape hatch), so early validation + a clear error message is the v1 mitigation. Six unit tests cover happy path, missing URL, wrong scheme, encoded special chars, literal `@` in password, malformed URL.

**Wiring:**

- `main.ts` imports `./observability/tracing` as line 1, then uses `app.get(Logger)` from `nestjs-pino` with `bufferLogs: true` so early-bootstrap lines are not lost.
- `app.module.ts` imports `ObservabilityModule` first, then `PrismaModule`, then `HealthModule`.
- `apps/portal-bff/.env.example` promotes `LOG_LEVEL`, `OTEL_SERVICE_NAME`, `OTEL_SERVICE_VERSION`, `OTEL_EXPORTER_OTLP_ENDPOINT`, `OTEL_EXPORTER_OTLP_PROTOCOL`, `OTEL_TRACES_SAMPLER` from the "future" comment to active settings — defaults target the local-dev Collector.
- Both `apps/portal-bff/.env.example` and `infra/local/.env.example` now spell out the URL-encoding constraint on `POSTGRES_PASSWORD` with the char-by-char encoding table (`@` → `%40`, etc.).

**ADR-0012 §Confirmation** rewritten to distinguish what landed in this PR from what is wired as the corresponding feature ADRs ship (CLS keys for `session_id` / `user_id_hash` / `audience`, `LOG_USER_ID_SALT` enforcement, redact list, custom spans, SPA-side SDK, full integration tests, prod Collector config).

## Trace ↔ log correlation

Automatic via `@opentelemetry/instrumentation-pino` — every Pino record gets `trace_id` and `span_id` injected from the active OTel context. No CLS gymnastics needed for that concern.

## Verification

```bash
pnpm exec nx run-many -t lint test build       # 8 projects green
pnpm audit --audit-level=moderate              # 0 vulnerabilities
./infra/local/dev.sh up observability          # start Collector + Jaeger
cp apps/portal-bff/.env.example apps/portal-bff/.env
pnpm nx serve portal-bff
curl http://localhost:3000/api/health
# → {"status":"ok","uptimeSeconds":N,"service":"portal-bff","version":"dev"}
```

Then hit `GET http://localhost:3000/api` once or twice and open http://localhost:16686 — the corresponding spans appear in Jaeger, and Pino logs on stdout carry the matching `trace_id`.

## Test plan

- [ ] `nx run-many -t lint test build` green on this PR's CI run.
- [ ] `pnpm audit` clean.
- [ ] BFF boots, `/api/health` returns the expected JSON.
- [ ] Pino logs in dev are colourised one-liners; in prod they would be raw JSON (toggled by `NODE_ENV=production`).
- [ ] With the local-dev stack's `--profile observability` active, traces are visible in Jaeger UI.
- [ ] Each Pino log line for a request carries the same `trace_id` as the trace span in Jaeger.

---------

Co-authored-by: Julien Gautier <julien.gautier@apf.asso.fr>
Reviewed-on: #70
2026-05-09 22:28:17 +02:00
APF Portal Bot 48e7a72f61 chore(deps): update dependency @types/node to v24.12.3 (#52)
CI / commits (push) Has been skipped
CI / scan (push) Successful in 3m4s
CI / check (push) Successful in 3m22s
CI / a11y (push) Successful in 1m30s
CI / perf (push) Successful in 4m49s
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/node) ([source](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node)) | devDependencies | patch | [`24.12.2` -> `24.12.3`](https://renovatebot.com/diffs/npm/@types%2fnode/24.12.2/24.12.3) |

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0MC42Mi4xIiwidXBkYXRlZEluVmVyIjoiNDAuNjIuMSIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOlsiZGVwZW5kZW5jaWVzIl19-->

Reviewed-on: #52
Co-authored-by: APF Portal Bot <jgautier.webdev+apf-portal-bot@gmail.com>
Co-committed-by: APF Portal Bot <jgautier.webdev+apf-portal-bot@gmail.com>
2026-05-08 01:26:17 +02:00
julien f5d3697466 fix(deps): revert TS6/ESLint10/webpack-cli7 majors and gate future majors (#43)
CI / commits (push) Has been skipped
CI / check (push) Successful in 2m25s
CI / a11y (push) Successful in 48s
CI / perf (push) Successful in 3m25s
CI / scan (push) Failing after 7m18s
## Summary
Three Renovate major bumps merged silently because `nx affected` doesn't see deps-only PRs as affecting any project — CI passed trivially, the breakage only surfaced when `nx run-many` was run locally:

- **TypeScript 5→6** (#33) — `tsconfig.lib.json` fails with `TS5101: Option 'baseUrl' is deprecated`. Revert to 5.9.x.
- **ESLint 9→10** (#36) — `@nx/eslint@22.7.1` not compatible: project graph fails with "Unable to find eslint". Revert eslint, `@eslint/js`, `jsonc-eslint-parser`, `eslint-plugin-playwright` to ESLint-9-compatible versions.
- **webpack-cli 5→7** (#34) — webpack-cli 7 removed the `--node-env=production` flag Nx generates. Revert to 5.x.

Bonus side-fix: the `ajv@<8.18.0` override added in #42 was over-broad and was forcing ESLint's bundled ajv to v8 (incompatible with ESLint 9's option contract). Narrow the override to `@angular-devkit/core>ajv@<8.18.0` so only the targeted nestjs-prisma chain is bumped.

## Prevention — gate majors behind the dependency dashboard

Add a Renovate `packageRule` with `dependencyDashboardApproval: true` for `matchUpdateTypes: ["major"]`. Renovate stops auto-creating PRs for majors; they appear as checkboxes in the dashboard issue, and only get a PR after a human ticks the box (presumably after reading the changelog and confirming Nx-plugin / Angular / NestJS readiness).

This is the surgical fix for the gap. The deeper fix (making `nx affected` correctly mark all projects as affected on package.json changes) is a separate investigation worth doing later — but the dashboard gate prevents the same trap regardless.

## Verification
Locally on this branch:
- `pnpm exec nx run-many -t lint test build --parallel=2` → ✓ 8 projects pass.
- `pnpm audit --audit-level=moderate` → 0 vulnerabilities.

## Test plan
- [ ] `check` job goes green on this PR (would have caught the regressions if `nx affected` were broader).
- [ ] After merge, the next Renovate run does not create new PRs for any major (TS, eslint, webpack-cli, etc.).
- [ ] Any pending major in the dashboard issue still appears, but only as a checkbox awaiting approval.

## Out of scope (follow-up)
Investigate why `nx affected` misses package.json-only changes. Likely a missing entry in `nx.json` `namedInputs` (`default`) or `targetDefaults`. Worth its own focused PR; the dashboard gate is the conservative fix in the meantime.

---------

Co-authored-by: Julien Gautier <julien.gautier@apf.asso.fr>
Reviewed-on: #43
2026-05-06 22:43:35 +02:00
julien 25bfba97cb chore(deps): pin patched transitive deps via pnpm.overrides (#42)
CI / commits (push) Has been skipped
CI / check (push) Failing after 1m48s
CI / perf (push) Failing after 1m59s
CI / a11y (push) Successful in 41s
CI / scan (push) Failing after 7m12s
## Summary
Direct-dep updates from Renovate could not clear the `scan` gate — 20 vulnerabilities (4 high + 13 moderate + 3 low) lived inside transitive chains pinned by upstream tooling:

- `nx > axios` (8× CVEs), `nx > yaml`, `nx > follow-redirects`
- `@nx/devkit > minimatch > brace-expansion`
- `nestjs-prisma > @angular-devkit/core > ajv`
- `@angular/cli > @modelcontextprotocol/sdk > express-rate-limit > ip-address`
- `@lhci/cli > tmp`

Patched versions exist upstream but the parents pin tighter ranges. Renovate cannot help here — `pnpm.overrides` with the **upper-bound** form (`pkg@<x.y.z`) is the right tool: it forces patched resolutions today and self-expires once a parent legitimately reaches the patched version (the override stops matching).

After overrides: `pnpm audit --audit-level=moderate` → **0 vulnerabilities**.

## Bonus fix
Lint-staged was running prettier on `pnpm-lock.yaml` because the glob `*.yaml` matched. Tightened the glob to `!(pnpm-lock).{...,yaml}` so the lockfile stays under pnpm's own formatting authority.

## Test plan
- [ ] `scan` job goes green on this PR.
- [ ] No regression on `check` (lint/test/build).
- [ ] On the next deps-bump commit (any future Renovate PR rebased), the local pre-commit hook does **not** reformat `pnpm-lock.yaml`.

## Out of scope (separate follow-up PRs)
While testing locally, two pre-existing regressions on `main` surfaced (introduced by recent Renovate major bumps that passed CI through `nx affected` reporting "nothing affected"):

- **TypeScript 6 (PR #33)** — `tsconfig.lib.json` → `TS5101: Option 'baseUrl' is deprecated` on every lib build.
- **ESLint 10 (PR #36)** — `@nx/eslint@22.7.1` plugin can't resolve eslint, project graph fails.

Both deserve their own investigation before we move on to phase-2 chantiers.

---------

Co-authored-by: Julien Gautier <julien.gautier@apf.asso.fr>
Reviewed-on: #42
2026-05-06 21:38:52 +02:00
APF Portal Bot 6545c3a176 chore(deps): update dependency lint-staged to v17 (#41)
CI / commits (push) Has been skipped
CI / check (push) Failing after 1m47s
CI / scan (push) Failing after 1m37s
CI / perf (push) Failing after 1m38s
CI / a11y (push) Successful in 1m11s
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [lint-staged](https://github.com/lint-staged/lint-staged) | devDependencies | major | [`^16.4.0` -> `^17.0.0`](https://renovatebot.com/diffs/npm/lint-staged/16.4.0/17.0.2) |

---

### Release Notes

<details>
<summary>lint-staged/lint-staged (lint-staged)</summary>

### [`v17.0.2`](https://github.com/lint-staged/lint-staged/blob/HEAD/CHANGELOG.md#1702)

[Compare Source](https://github.com/lint-staged/lint-staged/compare/v17.0.1...v17.0.2)

##### Patch Changes

- [#&#8203;1779](https://github.com/lint-staged/lint-staged/pull/1779) [`88670ca`](https://github.com/lint-staged/lint-staged/commit/88670ca2278200f6348ed663358895ddc4bfff3c) Thanks [@&#8203;iiroj](https://github.com/iiroj)! - Enable immutable GitHub releases

### [`v17.0.1`](https://github.com/lint-staged/lint-staged/blob/HEAD/CHANGELOG.md#1701)

[Compare Source](https://github.com/lint-staged/lint-staged/compare/v17.0.0...v17.0.1)

##### Patch Changes

- [#&#8203;1776](https://github.com/lint-staged/lint-staged/pull/1776) [`4a5664b`](https://github.com/lint-staged/lint-staged/commit/4a5664be63af19590ec37940f705dad870ac5cfb) Thanks [@&#8203;iiroj](https://github.com/iiroj)! - Adjust GitHub Actions workflow so that automatic publishing works with signed commits.

### [`v17.0.0`](https://github.com/lint-staged/lint-staged/blob/HEAD/CHANGELOG.md#1700)

[Compare Source](https://github.com/lint-staged/lint-staged/compare/v16.4.0...v17.0.0)

##### Major Changes

- [#&#8203;1745](https://github.com/lint-staged/lint-staged/pull/1745) [`e244adf`](https://github.com/lint-staged/lint-staged/commit/e244adfab430be95803e74b20acf518517054c9f) Thanks [@&#8203;iiroj](https://github.com/iiroj)! - **Node.js v20 is no longer supported, and the oldest supported version is now `22.22.1`**, which is an active LTS version at the time of this release. Node.js 20 will be EOL after April 2026. Please upgrade your Node.js version!

- [#&#8203;1676](https://github.com/lint-staged/lint-staged/pull/1676) [`0584e0b`](https://github.com/lint-staged/lint-staged/commit/0584e0b8824a07ea4d0151f2c17fc37c4905a421) Thanks [@&#8203;outslept](https://github.com/outslept)! - *Lint-staged* now tries to verify the installed Git version is at least `2.32.0`, released in 2021. If you're using an even older Git version, you need to [upgrade](https://git-scm.com/install/mac) it before running *lint-staged*!

- [#&#8203;1745](https://github.com/lint-staged/lint-staged/pull/1745) [`2dcc40a`](https://github.com/lint-staged/lint-staged/commit/2dcc40a1a98aea20d38f76031ac30b278f81682a) Thanks [@&#8203;iiroj](https://github.com/iiroj)! - The dependency `yaml` is now marked as optional and probably won't be installed by default. If you're using a YAML configuration file you should install the package separately:

  ```shell
  npm install --development yaml
  ```

  If you're using `.lintstagedrc` as the config file name (without a file extension), it will be treated as a YAML file. If the content is JSON, consider renaming it to `.lintstagedrc.json` to avoid needing to install `yaml`.

##### Minor Changes

- [#&#8203;1748](https://github.com/lint-staged/lint-staged/pull/1748) [`809d5ef`](https://github.com/lint-staged/lint-staged/commit/809d5ef0a66edb2b26b233d33ce8e14af6c978e7) Thanks [@&#8203;iiroj](https://github.com/iiroj)! - Add new option `--hide-all` for hiding all unstaged changes and untracked files, before running tasks. This makes it easier to run tools like [Knip](https://knip.dev) which check for unused code. Untracked files are included in the backup stash and restored automatically after running.

- [#&#8203;1759](https://github.com/lint-staged/lint-staged/pull/1759) [`f13045a`](https://github.com/lint-staged/lint-staged/commit/f13045a5eae28c3233fc37146b0e1f51739c254b) Thanks [@&#8203;iiroj](https://github.com/iiroj)! - Update dependencies, including [`tinyexec@1.1.1`](https://github.com/tinylibs/tinyexec/releases/tag/1.1.1) to fix the following issues:
  - When using a Node.js version manager with multiple versions installed ([nvm](https://github.com/nvm-sh/nvm), [n](https://github.com/tj/n), for example), scripts with the `#!/usr/bin/env node` shebang ([Prettier](https://github.com/prettier/prettier), [ESLint](https://github.com/eslint/eslint), for example) were previously spawned using the default Node.js version configured by the version manager (the one `which node` points to) on POSIX systems. Now, they will be spawned with the same version that *lint-staged* itself was started with.
    - For example, if your default Node.js version is 24.14.1 but *lint-staged* is run with the latest version 25.9.0, the tasks spawned by *lint-staged* will now also use version 25.9.0. Previously they were spawned using 24.14.1.
  - When installing Node.js from the Ubuntu App Center ([Snap store](https://snapcraft.io/store)), the `node` executable available in `PATH` is a symlink pointing to Snap itself. The sandboxing features of Snap prevented *lint-staged* from spawning scripts with the `#!/usr/bin/env node` shebang, because it meant *lint-staged* tried to spawn Snap via the symlink. This resulted in an `ENOENT` error when trying to run `prettier`, for example. Now, since the real `node` executable's directory is available in the `PATH`, *lint-staged* will instead spawn the script with the real `node` binary succesfully.

- [#&#8203;1761](https://github.com/lint-staged/lint-staged/pull/1761) [`d3251b1`](https://github.com/lint-staged/lint-staged/commit/d3251b192d7116f059e7cabeffa3bfd7788dedeb) Thanks [@&#8203;iiroj](https://github.com/iiroj)! - *Lint-staged* now runs `git update-index --again` after running tasks, instead of `git add <originally staged files>`. This should improve compatibility when using non-default indexes, for example when committing with a pathspec `git commit -m "message" .` instead of adding files to the index.

- [#&#8203;1745](https://github.com/lint-staged/lint-staged/pull/1745) [`a9585ac`](https://github.com/lint-staged/lint-staged/commit/a9585ac7ce0162c5c6c9aa88a28c11c812abedaf) Thanks [@&#8203;iiroj](https://github.com/iiroj)! - Remove `commander` as a dependency and use the built-in `parseArgs` from `node:util` to parse CLI flags.

##### Patch Changes

- [#&#8203;1755](https://github.com/lint-staged/lint-staged/pull/1755) [`c82d30b`](https://github.com/lint-staged/lint-staged/commit/c82d30bda8c80f886bdfead2e7aa123f7337aa76) Thanks [@&#8203;iiroj](https://github.com/iiroj)! - All tests now pass on the [Bun](https://bun.com) runtime (latest).

- [#&#8203;1750](https://github.com/lint-staged/lint-staged/pull/1750) [`a401818`](https://github.com/lint-staged/lint-staged/commit/a4018185016617b02e4473d14e036a5f1a9b3f85) Thanks [@&#8203;iiroj](https://github.com/iiroj)! - Remove manual handling for `git stash --keep-index` resurrecting deleted files, because the issue was fixed in Git `2.23.0` and *lint-staged* requires at least Git `2.32.0`.

- [#&#8203;1771](https://github.com/lint-staged/lint-staged/pull/1771) [`c4b8936`](https://github.com/lint-staged/lint-staged/commit/c4b893665bf39670650ae71b4ec2073025e9984e) Thanks [@&#8203;iiroj](https://github.com/iiroj)! - Fix documentation about multiple config files and the `--cwd` option. When using it, all tasks will be run in the specified directory. For example, to run everything in the actual `process.cwd()`, use `lint-staged --cwd="."`.

</details>

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0MC42Mi4xIiwidXBkYXRlZEluVmVyIjoiNDAuNjIuMSIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOlsiZGVwZW5kZW5jaWVzIl19-->

Reviewed-on: #41
Co-authored-by: APF Portal Bot <jgautier.webdev+apf-portal-bot@gmail.com>
Co-committed-by: APF Portal Bot <jgautier.webdev+apf-portal-bot@gmail.com>
2026-05-06 19:54:32 +02:00
APF Portal Bot 4a73d2e8db chore(deps): update pnpm to v10.33.4 (#40)
CI / commits (push) Has been skipped
CI / scan (push) Failing after 1m37s
CI / check (push) Successful in 1m51s
CI / perf (push) Failing after 2m5s
CI / a11y (push) Successful in 1m21s
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [pnpm](https://pnpm.io) ([source](https://github.com/pnpm/pnpm/tree/HEAD/pnpm)) | packageManager | patch | [`10.33.3` -> `10.33.4`](https://renovatebot.com/diffs/npm/pnpm/10.33.3/10.33.4) |

---

### Release Notes

<details>
<summary>pnpm/pnpm (pnpm)</summary>

### [`v10.33.4`](https://github.com/pnpm/pnpm/releases/tag/v10.33.4): pnpm 10.33.4

[Compare Source](https://github.com/pnpm/pnpm/compare/v10.33.3...v10.33.4)

#### Patch Changes

- Pin the integrity of git-hosted tarballs (codeload.github.com, gitlab.com, bitbucket.org) in the lockfile so that subsequent installs detect a tampered or substituted tarball and refuse to install it. Previously the lockfile only stored the tarball URL for git dependencies, so a compromised git host or a man-in-the-middle could serve arbitrary code on later installs without lockfile changes.

  A new `gitHosted: true` field is recorded on git-hosted tarball resolutions in the lockfile, letting every reader/writer route them by a single typed check instead of pattern-matching the tarball URL in each call site. Lockfiles written by older pnpm versions are enriched on load (URL fallback) so the field can be relied on uniformly across the codebase.

- Fix a regression where `pnpm --recursive --filter '!<pkg>' run/exec/test/add` would include the workspace root in the matched projects. The workspace root is now correctly excluded by default when only negative `--filter` arguments are provided, matching the [documented behavior](https://pnpm.io/cli/recursive). To include the root, pass `--include-workspace-root` [#&#8203;11341](https://github.com/pnpm/pnpm/issues/11341).

<!-- sponsors -->

#### Platinum Sponsors

<table>
  <tbody>
    <tr>
      <td align="center" valign="middle">
        <a href="https://bit.cloud/?utm_source=pnpm&utm_medium=release_notes" target="_blank"><img src="https://pnpm.io/img/users/bit.svg" width="80" alt="Bit"></a>
      </td>
    </tr>
  </tbody>
</table>

#### Gold Sponsors

<table>
  <tbody>
    <tr>
      <td align="center" valign="middle">
        <a href="https://sanity.io/?utm_source=pnpm&utm_medium=release_notes" target="_blank">
          <picture>
            <source media="(prefers-color-scheme: light)" srcset="https://pnpm.io/img/users/sanity.svg" />
            <source media="(prefers-color-scheme: dark)" srcset="https://pnpm.io/img/users/sanity_light.svg" />
            <img src="https://pnpm.io/img/users/sanity.svg" width="120" alt="Sanity" />
          </picture>
        </a>
      </td>
      <td align="center" valign="middle">
        <a href="https://discord.com/?utm_source=pnpm&utm_medium=release_notes" target="_blank">
          <picture>
            <source media="(prefers-color-scheme: light)" srcset="https://pnpm.io/img/users/discord.svg" />
            <source media="(prefers-color-scheme: dark)" srcset="https://pnpm.io/img/users/discord_light.svg" />
            <img src="https://pnpm.io/img/users/discord.svg" width="220" alt="Discord" />
          </picture>
        </a>
      </td>
      <td align="center" valign="middle">
        <a href="https://vite.dev/?utm_source=pnpm&utm_medium=release_notes" target="_blank"><img src="https://pnpm.io/img/users/vitejs.svg" width="42" alt="Vite"></a>
      </td>
    </tr>
    <tr>
      <td align="center" valign="middle">
        <a href="https://serpapi.com/?utm_source=pnpm&utm_medium=release_notes" target="_blank">
          <picture>
            <source media="(prefers-color-scheme: light)" srcset="https://pnpm.io/img/users/serpapi_dark.svg" />
            <source media="(prefers-color-scheme: dark)" srcset="https://pnpm.io/img/users/serpapi_light.svg" />
            <img src="https://pnpm.io/img/users/serpapi_dark.svg" width="160" alt="SerpApi" />
          </picture>
        </a>
      </td>
      <td align="center" valign="middle">
        <a href="https://coderabbit.ai/?utm_source=pnpm&utm_medium=release_notes" target="_blank">
          <picture>
            <source media="(prefers-color-scheme: light)" srcset="https://pnpm.io/img/users/coderabbit.svg" />
            <source media="(prefers-color-scheme: dark)" srcset="https://pnpm.io/img/users/coderabbit_light.svg" />
            <img src="https://pnpm.io/img/users/coderabbit.svg" width="220" alt="CodeRabbit" />
          </picture>
        </a>
      </td>
      <td align="center" valign="middle">
        <a href="https://stackblitz.com/?utm_source=pnpm&utm_medium=release_notes" target="_blank">
          <picture>
            <source media="(prefers-color-scheme: light)" srcset="https://pnpm.io/img/users/stackblitz.svg" />
            <source media="(prefers-color-scheme: dark)" srcset="https://pnpm.io/img/users/stackblitz_light.svg" />
            <img src="https://pnpm.io/img/users/stackblitz.svg" width="190" alt="Stackblitz" />
          </picture>
        </a>
      </td>
    </tr>
    <tr>
      <td align="center" valign="middle">
        <a href="https://workleap.com/?utm_source=pnpm&utm_medium=release_notes" target="_blank">
          <picture>
            <source media="(prefers-color-scheme: light)" srcset="https://pnpm.io/img/users/workleap.svg" />
            <source media="(prefers-color-scheme: dark)" srcset="https://pnpm.io/img/users/workleap_light.svg" />
            <img src="https://pnpm.io/img/users/workleap.svg" width="190" alt="Workleap" />
          </picture>
        </a>
      </td>
      <td align="center" valign="middle">
        <a href="https://nx.dev/?utm_source=pnpm&utm_medium=release_notes" target="_blank">
          <picture>
            <source media="(prefers-color-scheme: light)" srcset="https://pnpm.io/img/users/nx.svg" />
            <source media="(prefers-color-scheme: dark)" srcset="https://pnpm.io/img/users/nx_light.svg" />
            <img src="https://pnpm.io/img/users/nx.svg" width="50" alt="Nx" />
          </picture>
        </a>
      </td>
    </tr>
  </tbody>
</table>

<!-- sponsors end -->

</details>

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0MC42Mi4xIiwidXBkYXRlZEluVmVyIjoiNDAuNjIuMSIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOlsiZGVwZW5kZW5jaWVzIl19-->

Reviewed-on: https://git.unespace.com/julien/apf_portal/pulls/40
Co-authored-by: APF Portal Bot <jgautier.webdev+apf-portal-bot@gmail.com>
Co-committed-by: APF Portal Bot <jgautier.webdev+apf-portal-bot@gmail.com>
2026-05-06 19:53:46 +02:00
APF Portal Bot 68a819d88a chore(deps): update eslint (major) (#36)
CI / commits (push) Has been skipped
CI / scan (push) Failing after 1m42s
CI / check (push) Failing after 2m14s
CI / perf (push) Failing after 2m15s
CI / a11y (push) Successful in 1m4s
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [@eslint/js](https://eslint.org) ([source](https://github.com/eslint/eslint/tree/HEAD/packages/js)) | devDependencies | major | [`^9.8.0` -> `^10.0.0`](https://renovatebot.com/diffs/npm/@eslint%2fjs/9.39.4/10.0.1) |
| [eslint](https://eslint.org) ([source](https://github.com/eslint/eslint)) | devDependencies | major | [`^9.8.0` -> `^10.0.0`](https://renovatebot.com/diffs/npm/eslint/9.39.4/10.3.0) |
| [eslint-plugin-playwright](https://github.com/mskelton/eslint-plugin-playwright) | devDependencies | major | [`^1.6.2` -> `^2.0.0`](https://renovatebot.com/diffs/npm/eslint-plugin-playwright/1.8.3/2.10.2) |

---

### Release Notes

<details>
<summary>eslint/eslint (@&#8203;eslint/js)</summary>

### [`v10.0.1`](https://github.com/eslint/eslint/releases/tag/v10.0.1)

[Compare Source](https://github.com/eslint/eslint/compare/v10.0.0...v10.0.1)

##### Bug Fixes

- [`c87d5bd`](https://github.com/eslint/eslint/commit/c87d5bded54c5cf491eb04c24c9d09bbbd42c23e) fix: update eslint ([#&#8203;20531](https://github.com/eslint/eslint/issues/20531)) (renovate\[bot])
- [`d841001`](https://github.com/eslint/eslint/commit/d84100115c14691691058f00779c94e74fca946a) fix: update `minimatch` to `10.2.1` to address security vulnerabilities ([#&#8203;20519](https://github.com/eslint/eslint/issues/20519)) (루밀LuMir)
- [`04c2147`](https://github.com/eslint/eslint/commit/04c21475b3004904948f02049f2888b401d82c78) fix: update error message for unused suppressions ([#&#8203;20496](https://github.com/eslint/eslint/issues/20496)) (fnx)
- [`38b089c`](https://github.com/eslint/eslint/commit/38b089c1726feac0e31a31d47941bd99e29ce003) fix: update dependency [@&#8203;eslint/config-array](https://github.com/eslint/config-array) to ^0.23.1 ([#&#8203;20484](https://github.com/eslint/eslint/issues/20484)) (renovate\[bot])

##### Documentation

- [`5b3dbce`](https://github.com/eslint/eslint/commit/5b3dbce50a1404a9f118afe810cefeee79388a2a) docs: add AI acknowledgement section to templates ([#&#8203;20431](https://github.com/eslint/eslint/issues/20431)) (루밀LuMir)
- [`6f23076`](https://github.com/eslint/eslint/commit/6f23076037d5879f20fb3be2ef094293b1e8d38c) docs: toggle nav in no-JS mode ([#&#8203;20476](https://github.com/eslint/eslint/issues/20476)) (Tanuj Kanti)
- [`b69cfb3`](https://github.com/eslint/eslint/commit/b69cfb32a16c5d5e9986390d484fae1d21e406f9) docs: Update README (GitHub Actions Bot)

##### Chores

- [`e5c281f`](https://github.com/eslint/eslint/commit/e5c281ffd038a3a7a3e5364db0b9378e0ad83020) chore: updates for v9.39.3 release (Jenkins)
- [`8c3832a`](https://github.com/eslint/eslint/commit/8c3832adb77cd993b4a24891900d5eeaaf093cdc) chore: update [@&#8203;typescript-eslint/parser](https://github.com/typescript-eslint/parser) to ^8.56.0 ([#&#8203;20514](https://github.com/eslint/eslint/issues/20514)) (Milos Djermanovic)
- [`8330d23`](https://github.com/eslint/eslint/commit/8330d238ae6adb68bb6a1c9381e38cfedd990d94) test: add tests for config-api ([#&#8203;20493](https://github.com/eslint/eslint/issues/20493)) (Milos Djermanovic)
- [`37d6e91`](https://github.com/eslint/eslint/commit/37d6e91e88fa6a2ca6d8726679096acff21ba6cc) chore: remove eslint v10 prereleases from eslint-config-eslint deps ([#&#8203;20494](https://github.com/eslint/eslint/issues/20494)) (Milos Djermanovic)
- [`da7cd0e`](https://github.com/eslint/eslint/commit/da7cd0e79197ad16e17052eef99df141de6dbfb1) refactor: cleanup error message templates ([#&#8203;20479](https://github.com/eslint/eslint/issues/20479)) (Francesco Trotta)
- [`84fb885`](https://github.com/eslint/eslint/commit/84fb885d49ac810e79a9491276b4828b53d913e5) chore: package.json update for [@&#8203;eslint/js](https://github.com/eslint/js) release (Jenkins)
- [`1f66734`](https://github.com/eslint/eslint/commit/1f667344b57c4c09b548d94bcfac1f91b6e5c63d) chore: add `eslint` to `peerDependencies` of `@eslint/js` ([#&#8203;20467](https://github.com/eslint/eslint/issues/20467)) (Milos Djermanovic)

### [`v10.0.0`](https://github.com/eslint/eslint/releases/tag/v10.0.0)

[Compare Source](https://github.com/eslint/eslint/compare/v9.39.4...v10.0.0)

##### Breaking Changes

- [`f9e54f4`](https://github.com/eslint/eslint/commit/f9e54f43a5e497cdfa179338b431093245cb787b) feat!: estimate rule-tester failure location ([#&#8203;20420](https://github.com/eslint/eslint/issues/20420)) (ST-DDT)
- [`a176319`](https://github.com/eslint/eslint/commit/a176319d8ade1a7d9b2d7fb8f038f55a2662325f) feat!: replace `chalk` with `styleText` and add `color` to `ResultsMeta` ([#&#8203;20227](https://github.com/eslint/eslint/issues/20227)) (루밀LuMir)
- [`c7046e6`](https://github.com/eslint/eslint/commit/c7046e6c1e03c4ca0eee4888a1f2eba4c6454f84) feat!: enable JSX reference tracking ([#&#8203;20152](https://github.com/eslint/eslint/issues/20152)) (Pixel998)
- [`fa31a60`](https://github.com/eslint/eslint/commit/fa31a608901684fbcd9906d1907e66561d16e5aa) feat!: add `name` to configs ([#&#8203;20015](https://github.com/eslint/eslint/issues/20015)) (Kirk Waiblinger)
- [`3383e7e`](https://github.com/eslint/eslint/commit/3383e7ec9028166cafc8ea7986c2f7498d0049f0) fix!: remove deprecated `SourceCode` methods ([#&#8203;20137](https://github.com/eslint/eslint/issues/20137)) (Pixel998)
- [`501abd0`](https://github.com/eslint/eslint/commit/501abd0e916a35554c58b7c0365537f1fa3880ce) feat!: update dependency minimatch to v10 ([#&#8203;20246](https://github.com/eslint/eslint/issues/20246)) (renovate\[bot])
- [`ca4d3b4`](https://github.com/eslint/eslint/commit/ca4d3b40085de47561f89656a2207d09946ed45e) fix!: stricter rule tester assertions for valid test cases ([#&#8203;20125](https://github.com/eslint/eslint/issues/20125)) (唯然)
- [`96512a6`](https://github.com/eslint/eslint/commit/96512a66c86402fb0538cdcb6cd30b9073f6bf3b) fix!: Remove deprecated rule context methods ([#&#8203;20086](https://github.com/eslint/eslint/issues/20086)) (Nicholas C. Zakas)
- [`c69fdac`](https://github.com/eslint/eslint/commit/c69fdacdb2e886b9d965568a397aa8220db3fe90) feat!: remove eslintrc support ([#&#8203;20037](https://github.com/eslint/eslint/issues/20037)) (Francesco Trotta)
- [`208b5cc`](https://github.com/eslint/eslint/commit/208b5cc34a8374ff81412b5bec2e0800eebfbd04) feat!: Use `ScopeManager#addGlobals()` ([#&#8203;20132](https://github.com/eslint/eslint/issues/20132)) (Milos Djermanovic)
- [`a2ee188`](https://github.com/eslint/eslint/commit/a2ee188ea7a38a0c6155f3d39e2b00e1d0f36e14) fix!: add `uniqueItems: true` in `no-invalid-regexp` option ([#&#8203;20155](https://github.com/eslint/eslint/issues/20155)) (Tanuj Kanti)
- [`a89059d`](https://github.com/eslint/eslint/commit/a89059dbf2832d417dd493ee81483227ec44e4ab) feat!: Program range span entire source text ([#&#8203;20133](https://github.com/eslint/eslint/issues/20133)) (Pixel998)
- [`39a6424`](https://github.com/eslint/eslint/commit/39a6424373d915fa9de0d7b0caba9a4dc3da9b53) fix!: assert 'text' is a string across all RuleFixer methods ([#&#8203;20082](https://github.com/eslint/eslint/issues/20082)) (Pixel998)
- [`f28fbf8`](https://github.com/eslint/eslint/commit/f28fbf846244e043c92b355b224d121b06140b44) fix!: Deprecate `"always"` and `"as-needed"` options of the `radix` rule ([#&#8203;20223](https://github.com/eslint/eslint/issues/20223)) (Milos Djermanovic)
- [`aa3fb2b`](https://github.com/eslint/eslint/commit/aa3fb2b233e929b37220be940575f42c280e0b98) fix!: tighten `func-names` schema ([#&#8203;20119](https://github.com/eslint/eslint/issues/20119)) (Pixel998)
- [`f6c0ed0`](https://github.com/eslint/eslint/commit/f6c0ed0311dcfee853367d5068c765d066e6b756) feat!: report `eslint-env` comments as errors ([#&#8203;20128](https://github.com/eslint/eslint/issues/20128)) (Francesco Trotta)
- [`4bf739f`](https://github.com/eslint/eslint/commit/4bf739fb533e59f7f0a66b65f7bc80be0f37d8db) fix!: remove deprecated `LintMessage#nodeType` and `TestCaseError#type` ([#&#8203;20096](https://github.com/eslint/eslint/issues/20096)) (Pixel998)
- [`523c076`](https://github.com/eslint/eslint/commit/523c076866400670fb2192a3f55dbf7ad3469247) feat!: drop support for jiti < 2.2.0 ([#&#8203;20016](https://github.com/eslint/eslint/issues/20016)) (michael faith)
- [`454a292`](https://github.com/eslint/eslint/commit/454a292c95f34dad232411ddac06408e6383bb64) feat!: update `eslint:recommended` configuration ([#&#8203;20210](https://github.com/eslint/eslint/issues/20210)) (Pixel998)
- [`4f880ee`](https://github.com/eslint/eslint/commit/4f880ee02992e1bf0e96ebaba679985e2d1295f1) feat!: remove `v10_*` and inactive `unstable_*` flags ([#&#8203;20225](https://github.com/eslint/eslint/issues/20225)) (sethamus)
- [`f18115c`](https://github.com/eslint/eslint/commit/f18115c363a4ac7671a4c7f30ee13d57ebba330f) feat!: `no-shadow-restricted-names` report `globalThis` by default ([#&#8203;20027](https://github.com/eslint/eslint/issues/20027)) (sethamus)
- [`c6358c3`](https://github.com/eslint/eslint/commit/c6358c31fbd3937b92d89be2618ffdf5a774604e) feat!: Require Node.js `^20.19.0 || ^22.13.0 || >=24` ([#&#8203;20160](https://github.com/eslint/eslint/issues/20160)) (Milos Djermanovic)

##### Features

- [`bff9091`](https://github.com/eslint/eslint/commit/bff9091927811497dbf066b0e3b85ecb37d43822) feat: handle `Array.fromAsync` in `array-callback-return` ([#&#8203;20457](https://github.com/eslint/eslint/issues/20457)) (Francesco Trotta)
- [`290c594`](https://github.com/eslint/eslint/commit/290c594bb50c439fb71bc75521ee5360daa8c222) feat: add `self` to `no-implied-eval` rule ([#&#8203;20468](https://github.com/eslint/eslint/issues/20468)) (sethamus)
- [`43677de`](https://github.com/eslint/eslint/commit/43677de07ebd6e14bfac40a46ad749ba783c45f2) feat: fix handling of function and class expression names in `no-shadow` ([#&#8203;20432](https://github.com/eslint/eslint/issues/20432)) (Milos Djermanovic)
- [`f0cafe5`](https://github.com/eslint/eslint/commit/f0cafe5f37e7765e9d8c2751b5f5d33107687009) feat: rule tester add assertion option `requireData` ([#&#8203;20409](https://github.com/eslint/eslint/issues/20409)) (fnx)
- [`f7ab693`](https://github.com/eslint/eslint/commit/f7ab6937e63bc618d326710858f5861a68f80616) feat: output RuleTester test case failure index ([#&#8203;19976](https://github.com/eslint/eslint/issues/19976)) (ST-DDT)
- [`7cbcbf9`](https://github.com/eslint/eslint/commit/7cbcbf9c3c2008deee7d143ae35e668e8ffbccb3) feat: add `countThis` option to `max-params` ([#&#8203;20236](https://github.com/eslint/eslint/issues/20236)) (Gerkin)
- [`f148a5e`](https://github.com/eslint/eslint/commit/f148a5eaa1e89dd80ade62f0a690186b00b9f6e1) feat: add error assertion options ([#&#8203;20247](https://github.com/eslint/eslint/issues/20247)) (ST-DDT)
- [`09e6654`](https://github.com/eslint/eslint/commit/09e66549ecada6dcb8c567a60faf044fce049188) feat: update error loc of `require-yield` and `no-useless-constructor` ([#&#8203;20267](https://github.com/eslint/eslint/issues/20267)) (Tanuj Kanti)

##### Bug Fixes

- [`436b82f`](https://github.com/eslint/eslint/commit/436b82f3c0a8cfa2fdc17d173e95ea11d5d3ee03) fix: update eslint ([#&#8203;20473](https://github.com/eslint/eslint/issues/20473)) (renovate\[bot])
- [`1d29d22`](https://github.com/eslint/eslint/commit/1d29d22fe302443cec2a11da0816397f94af97ec) fix: detect default `this` binding in `Array.fromAsync` callbacks ([#&#8203;20456](https://github.com/eslint/eslint/issues/20456)) (Francesco Trotta)
- [`727451e`](https://github.com/eslint/eslint/commit/727451eff55b35d853e0e443d0de58f4550762bf) fix: fix regression of global mode report range in `strict` rule ([#&#8203;20462](https://github.com/eslint/eslint/issues/20462)) (ntnyq)
- [`e80485f`](https://github.com/eslint/eslint/commit/e80485fcd27196fa0b6f6b5c7ac8cf49ad4b079d) fix: remove fake `FlatESLint` and `LegacyESLint` exports ([#&#8203;20460](https://github.com/eslint/eslint/issues/20460)) (Francesco Trotta)
- [`9eeff3b`](https://github.com/eslint/eslint/commit/9eeff3bc13813a786b8a4c3815def97c0fb646ef) fix: update esquery ([#&#8203;20423](https://github.com/eslint/eslint/issues/20423)) (cryptnix)
- [`b34b938`](https://github.com/eslint/eslint/commit/b34b93852d014ebbcf3538d892b55e0216cdf681) fix: use `Error.prepareStackTrace` to estimate failing test location ([#&#8203;20436](https://github.com/eslint/eslint/issues/20436)) (Francesco Trotta)
- [`51aab53`](https://github.com/eslint/eslint/commit/51aab5393b058f7cbed69041a9069b2bd106aabd) fix: update eslint ([#&#8203;20443](https://github.com/eslint/eslint/issues/20443)) (renovate\[bot])
- [`23490b2`](https://github.com/eslint/eslint/commit/23490b266276792896a0b7b43c49a1ce87bf8568) fix: handle space before colon in `RuleTester` location estimation ([#&#8203;20433](https://github.com/eslint/eslint/issues/20433)) (Francesco Trotta)
- [`f244dbf`](https://github.com/eslint/eslint/commit/f244dbf2191267a4cafd08645243624baf3e8c83) fix: use `MessagePlaceholderData` type from `@eslint/core` ([#&#8203;20348](https://github.com/eslint/eslint/issues/20348)) (루밀LuMir)
- [`d186f8c`](https://github.com/eslint/eslint/commit/d186f8c0747f14890e86a5a39708b052b391ddaf) fix: update eslint ([#&#8203;20427](https://github.com/eslint/eslint/issues/20427)) (renovate\[bot])
- [`2332262`](https://github.com/eslint/eslint/commit/2332262deb4ef3188b210595896bb0ff552a7e66) fix: error location should not modify error message in RuleTester ([#&#8203;20421](https://github.com/eslint/eslint/issues/20421)) (Milos Djermanovic)
- [`ab99b21`](https://github.com/eslint/eslint/commit/ab99b21a6715dee1035d8f4e6d6841853eb5563f) fix: ensure `filename` is passed as third argument to `verifyAndFix()` ([#&#8203;20405](https://github.com/eslint/eslint/issues/20405)) (루밀LuMir)
- [`8a60f3b`](https://github.com/eslint/eslint/commit/8a60f3bc80ad96c65feeb29886342623c630199c) fix: remove `ecmaVersion` and `sourceType` from `ParserOptions` type ([#&#8203;20415](https://github.com/eslint/eslint/issues/20415)) (Pixel998)
- [`eafd727`](https://github.com/eslint/eslint/commit/eafd727a060131f7fc79b2eb5698d8d27683c3a2) fix: remove `TDZ` scope type ([#&#8203;20231](https://github.com/eslint/eslint/issues/20231)) (jaymarvelz)
- [`39d1f51`](https://github.com/eslint/eslint/commit/39d1f51680d4fbade16b4d9c07ad61a87ee3b1ea) fix: correct `Scope` typings ([#&#8203;20404](https://github.com/eslint/eslint/issues/20404)) (sethamus)
- [`2bd0f13`](https://github.com/eslint/eslint/commit/2bd0f13a92fb373827f16210aa4748d4885fddb1) fix: update `verify` and `verifyAndFix` types ([#&#8203;20384](https://github.com/eslint/eslint/issues/20384)) (Francesco Trotta)
- [`ba6ebfa`](https://github.com/eslint/eslint/commit/ba6ebfa78de0b8522cea5ee80179887e92c6c935) fix: correct typings for `loadESLint()` and `shouldUseFlatConfig()` ([#&#8203;20393](https://github.com/eslint/eslint/issues/20393)) (루밀LuMir)
- [`e7673ae`](https://github.com/eslint/eslint/commit/e7673ae096900330599680efe91f8a199a5c2e59) fix: correct RuleTester typings ([#&#8203;20105](https://github.com/eslint/eslint/issues/20105)) (Pixel998)
- [`53e9522`](https://github.com/eslint/eslint/commit/53e95222af8561a8eed282fa9fd44b2f320a3c37) fix: strict removed formatters check ([#&#8203;20241](https://github.com/eslint/eslint/issues/20241)) (ntnyq)
- [`b017f09`](https://github.com/eslint/eslint/commit/b017f094d4e53728f8d335b9cf8b16dc074afda3) fix: correct `no-restricted-import` messages ([#&#8203;20374](https://github.com/eslint/eslint/issues/20374)) (Francesco Trotta)

##### Documentation

- [`e978dda`](https://github.com/eslint/eslint/commit/e978ddaab7e6a3c38b4a2afa721148a6ef38f29a) docs: Update README (GitHub Actions Bot)
- [`4cecf83`](https://github.com/eslint/eslint/commit/4cecf8393ae9af18c4cfd50621115eb23b3d0cb6) docs: Update README (GitHub Actions Bot)
- [`c79f0ab`](https://github.com/eslint/eslint/commit/c79f0ab2e2d242a93b08ff2f6a0712e2ef60b7b8) docs: Update README (GitHub Actions Bot)
- [`773c052`](https://github.com/eslint/eslint/commit/773c0527c72c09fb5e63c2036b5cb9783f1f04d3) docs: Update README (GitHub Actions Bot)
- [`f2962e4`](https://github.com/eslint/eslint/commit/f2962e46a0e8ee8e04d76e9d899f6a7c73a646f1) docs: document `meta.docs.frozen` property ([#&#8203;20475](https://github.com/eslint/eslint/issues/20475)) (Pixel998)
- [`8e94f58`](https://github.com/eslint/eslint/commit/8e94f58bebfd854eed814a39e19dea4e3c3ee4a3) docs: fix broken anchor links from gerund heading updates ([#&#8203;20449](https://github.com/eslint/eslint/issues/20449)) (Copilot)
- [`1495654`](https://github.com/eslint/eslint/commit/14956543d42ab542f72820f38941d0bcc39a1fbb) docs: Update README (GitHub Actions Bot)
- [`0b8ed5c`](https://github.com/eslint/eslint/commit/0b8ed5c0aa4222a9b6b185c605cfedaef4662dcb) docs: document support for `:is` selector alias ([#&#8203;20454](https://github.com/eslint/eslint/issues/20454)) (sethamus)
- [`1c4b33f`](https://github.com/eslint/eslint/commit/1c4b33fe8620dcaafbe6e8f4e9515b624476548c) docs: Document policies about ESM-only dependencies ([#&#8203;20448](https://github.com/eslint/eslint/issues/20448)) (Milos Djermanovic)
- [`3e5d38c`](https://github.com/eslint/eslint/commit/3e5d38cdd5712bef50d440585b0f6669a2e9a9b9) docs: add missing indentation space in rule example ([#&#8203;20446](https://github.com/eslint/eslint/issues/20446)) (fnx)
- [`63a0c7c`](https://github.com/eslint/eslint/commit/63a0c7c84bf5b12357893ea2bf0482aa3c855bac) docs: Update README (GitHub Actions Bot)
- [`65ed0c9`](https://github.com/eslint/eslint/commit/65ed0c94e7cd1e3f882956113228311d8c7b3463) docs: Update README (GitHub Actions Bot)
- [`b0e4717`](https://github.com/eslint/eslint/commit/b0e4717d6619ffd02913cf3633b44d8e6953d938) docs: \[no-await-in-loop] Expand inapplicability ([#&#8203;20363](https://github.com/eslint/eslint/issues/20363)) (Niklas Hambüchen)
- [`fca421f`](https://github.com/eslint/eslint/commit/fca421f6a4eecd52f2a7ae5765bd9008f62f9994) docs: Update README (GitHub Actions Bot)
- [`d925c54`](https://github.com/eslint/eslint/commit/d925c54f045b2230d3404e8aa18f4e2860a35e1d) docs: update config syntax in `no-lone-blocks` ([#&#8203;20413](https://github.com/eslint/eslint/issues/20413)) (Pixel998)
- [`7d5c95f`](https://github.com/eslint/eslint/commit/7d5c95f281cb88868f4e09ca07fbbc6394d78c41) docs: remove redundant `sourceType: "module"` from rule examples ([#&#8203;20412](https://github.com/eslint/eslint/issues/20412)) (Pixel998)
- [`02e7e71`](https://github.com/eslint/eslint/commit/02e7e7126366fc5eeffb713f865d80a759dc14b0) docs: correct `.mts` glob pattern in files with extensions example ([#&#8203;20403](https://github.com/eslint/eslint/issues/20403)) (Ali Essalihi)
- [`264b981`](https://github.com/eslint/eslint/commit/264b981101a3cf0c12eba200ac64e5523186a89f) docs: Update README (GitHub Actions Bot)
- [`5a4324f`](https://github.com/eslint/eslint/commit/5a4324f38e7ce370038351ef7412dcf8548c105e) docs: clarify `"local"` option of `no-unused-vars` ([#&#8203;20385](https://github.com/eslint/eslint/issues/20385)) (Milos Djermanovic)
- [`e593aa0`](https://github.com/eslint/eslint/commit/e593aa0fd29f51edea787815ffc847aa723ef1f8) docs: improve clarity, grammar, and wording in documentation site README ([#&#8203;20370](https://github.com/eslint/eslint/issues/20370)) (Aditya)
- [`3f5062e`](https://github.com/eslint/eslint/commit/3f5062ed5f27eb25414faced2478ae076906874e) docs: Add messages property to rule meta documentation ([#&#8203;20361](https://github.com/eslint/eslint/issues/20361)) (Sabya Sachi)
- [`9e5a5c2`](https://github.com/eslint/eslint/commit/9e5a5c2b6b368cdacd678eabf36b441bd8bb726c) docs: remove `Examples` headings from rule docs ([#&#8203;20364](https://github.com/eslint/eslint/issues/20364)) (Milos Djermanovic)
- [`194f488`](https://github.com/eslint/eslint/commit/194f488a8dc97850485afe704d2a64096582f96d) docs: Update README (GitHub Actions Bot)
- [`0f5a94a`](https://github.com/eslint/eslint/commit/0f5a94a84beee19f376025c74f703f275d52c94b) docs: \[class-methods-use-this] explain purpose of rule ([#&#8203;20008](https://github.com/eslint/eslint/issues/20008)) (Kirk Waiblinger)
- [`df5566f`](https://github.com/eslint/eslint/commit/df5566f826d9f5740546e473aa6876b1f7d2f12c) docs: add Options section to all rule docs ([#&#8203;20296](https://github.com/eslint/eslint/issues/20296)) (sethamus)
- [`adf7a2b`](https://github.com/eslint/eslint/commit/adf7a2b202743a98edc454890574292dd2b34837) docs: no-unsafe-finally note for generator functions ([#&#8203;20330](https://github.com/eslint/eslint/issues/20330)) (Tom Pereira)
- [`ef7028c`](https://github.com/eslint/eslint/commit/ef7028c9688dc931051a4217637eb971efcbd71b) docs: Update README (GitHub Actions Bot)
- [`fbae5d1`](https://github.com/eslint/eslint/commit/fbae5d18854b30ea3b696672c7699cef3ec92140) docs: consistently use "v10.0.0" in migration guide ([#&#8203;20328](https://github.com/eslint/eslint/issues/20328)) (Pixel998)
- [`778aa2d`](https://github.com/eslint/eslint/commit/778aa2d83e1ef1e2bd1577ee976c5a43472a3dbe) docs: ignoring default file patterns ([#&#8203;20312](https://github.com/eslint/eslint/issues/20312)) (Tanuj Kanti)
- [`4b5dbcd`](https://github.com/eslint/eslint/commit/4b5dbcdae52c1c16293dc68028cab18ed2504841) docs: reorder v10 migration guide ([#&#8203;20315](https://github.com/eslint/eslint/issues/20315)) (Milos Djermanovic)
- [`5d84a73`](https://github.com/eslint/eslint/commit/5d84a7371d01ead1b274600c055fe49150d487f1) docs: Update README (GitHub Actions Bot)
- [`37c8863`](https://github.com/eslint/eslint/commit/37c8863088a2d7e845d019f68a329f53a3fe2c35) docs: fix incorrect anchor link in v10 migration guide ([#&#8203;20299](https://github.com/eslint/eslint/issues/20299)) (Pixel998)
- [`077ff02`](https://github.com/eslint/eslint/commit/077ff028b6ce036da091d2f7ed8c606c9d017468) docs: add migrate-to-10.0.0 doc ([#&#8203;20143](https://github.com/eslint/eslint/issues/20143)) (唯然)
- [`3822e1b`](https://github.com/eslint/eslint/commit/3822e1b768bb4a64b72b73b5657737a6ee5c8afe) docs: Update README (GitHub Actions Bot)

##### Build Related

- [`9f08712`](https://github.com/eslint/eslint/commit/9f0871236e90ec78bcdbfa352cc1363b4bae5596) Build: changelog update for 10.0.0-rc.2 (Jenkins)
- [`1e2c449`](https://github.com/eslint/eslint/commit/1e2c449701524b426022fde19144b1d22d8197b0) Build: changelog update for 10.0.0-rc.1 (Jenkins)
- [`c4c72a8`](https://github.com/eslint/eslint/commit/c4c72a8d996dda629e85e78a6ef5417242594b5d) Build: changelog update for 10.0.0-rc.0 (Jenkins)
- [`7e4daf9`](https://github.com/eslint/eslint/commit/7e4daf93d255ed343d68e999aad167bb20e5a96b) Build: changelog update for 10.0.0-beta.0 (Jenkins)
- [`a126a2a`](https://github.com/eslint/eslint/commit/a126a2ab136406017f2dac2d7632114e37e62dc2) build: add .scss files entry to knip ([#&#8203;20389](https://github.com/eslint/eslint/issues/20389)) (Francesco Trotta)
- [`f5c0193`](https://github.com/eslint/eslint/commit/f5c01932f69189b260646d60b28011c55870e65d) Build: changelog update for 10.0.0-alpha.1 (Jenkins)
- [`165326f`](https://github.com/eslint/eslint/commit/165326f0469dd6a9b33598a6fceb66336bb2deb5) Build: changelog update for 10.0.0-alpha.0 (Jenkins)

##### Chores

- [`1ece282`](https://github.com/eslint/eslint/commit/1ece282c2286b5dc187ece2a793dbd8798f20bd7) chore: ignore `/docs/v9.x` in link checker ([#&#8203;20452](https://github.com/eslint/eslint/issues/20452)) (Milos Djermanovic)
- [`034e139`](https://github.com/eslint/eslint/commit/034e1397446205e83eb341354605380195c88633) ci: add type integration test for `@html-eslint/eslint-plugin` ([#&#8203;20345](https://github.com/eslint/eslint/issues/20345)) (sethamus)
- [`f3fbc2f`](https://github.com/eslint/eslint/commit/f3fbc2f60cbe2c718364feb8c3fc0452c0df3c56) chore: set `@eslint/js` version to 10.0.0 to skip releasing it ([#&#8203;20466](https://github.com/eslint/eslint/issues/20466)) (Milos Djermanovic)
- [`afc0681`](https://github.com/eslint/eslint/commit/afc06817bbd0625c7b0a46bdc81c38dab0c99441) chore: remove scopeManager.addGlobals patch for typescript-eslint parser ([#&#8203;20461](https://github.com/eslint/eslint/issues/20461)) (fnx)
- [`3e5a173`](https://github.com/eslint/eslint/commit/3e5a173053fe0bb3d0f29aff12eb2c19ae21aa36) refactor: use types from `@eslint/plugin-kit` ([#&#8203;20435](https://github.com/eslint/eslint/issues/20435)) (Pixel998)
- [`11644b1`](https://github.com/eslint/eslint/commit/11644b1dc2bdf4c4f3a97901932e5f25c9f60775) ci: rename workflows ([#&#8203;20463](https://github.com/eslint/eslint/issues/20463)) (Milos Djermanovic)
- [`2d14173`](https://github.com/eslint/eslint/commit/2d14173729ae75fe562430dd5e37c457f44bc7ac) chore: fix typos in docs and comments ([#&#8203;20458](https://github.com/eslint/eslint/issues/20458)) (o-m12a)
- [`6742f92`](https://github.com/eslint/eslint/commit/6742f927ba6afb1bce6f64b9b072a1a11dbf53c4) test: add endLine/endColumn to invalid test case in no-alert ([#&#8203;20441](https://github.com/eslint/eslint/issues/20441)) (경하)
- [`3e22c82`](https://github.com/eslint/eslint/commit/3e22c82a87f44f7407ff75b17b26f1ceed3edd14) test: add missing location data to no-template-curly-in-string tests ([#&#8203;20440](https://github.com/eslint/eslint/issues/20440)) (Haeun Kim)
- [`b4b3127`](https://github.com/eslint/eslint/commit/b4b3127f8542c599ce2dea804b6582ebc40c993d) chore: package.json update for [@&#8203;eslint/js](https://github.com/eslint/js) release (Jenkins)
- [`f658419`](https://github.com/eslint/eslint/commit/f6584191cb5cabd62f6a197339a91e1f9b3f8432) refactor: remove `raw` parser option from JS language ([#&#8203;20416](https://github.com/eslint/eslint/issues/20416)) (Pixel998)
- [`2c3efb7`](https://github.com/eslint/eslint/commit/2c3efb728b294b74a240ec24c7be8137a31cf5f0) chore: remove `category` from type test fixtures ([#&#8203;20417](https://github.com/eslint/eslint/issues/20417)) (Pixel998)
- [`36193fd`](https://github.com/eslint/eslint/commit/36193fd9ad27764d8e4a24ce7c7bbeeaf5d4a6ba) chore: remove `category` from formatter test fixtures ([#&#8203;20418](https://github.com/eslint/eslint/issues/20418)) (Pixel998)
- [`e8d203b`](https://github.com/eslint/eslint/commit/e8d203b0d9f66e55841863f90d215fd83b7eee0f) chore: add JSX language tag validation to `check-rule-examples` ([#&#8203;20414](https://github.com/eslint/eslint/issues/20414)) (Pixel998)
- [`bc465a1`](https://github.com/eslint/eslint/commit/bc465a1e9d955b6e53a45d1b5da7c632dae77262) chore: pin dependencies ([#&#8203;20397](https://github.com/eslint/eslint/issues/20397)) (renovate\[bot])
- [`703f0f5`](https://github.com/eslint/eslint/commit/703f0f551daea28767e5a68a00e335928919a7ff) test: replace deprecated rules in `linter` tests ([#&#8203;20406](https://github.com/eslint/eslint/issues/20406)) (루밀LuMir)
- [`ba71baa`](https://github.com/eslint/eslint/commit/ba71baa87265888b582f314163df1d727441e2f1) test: enable `strict` mode in type tests ([#&#8203;20398](https://github.com/eslint/eslint/issues/20398)) (루밀LuMir)
- [`f9c4968`](https://github.com/eslint/eslint/commit/f9c49683a6d69ff0b5425803955fc226f7e05d76) refactor: remove `lib/linter/rules.js` ([#&#8203;20399](https://github.com/eslint/eslint/issues/20399)) (Francesco Trotta)
- [`6f1c48e`](https://github.com/eslint/eslint/commit/6f1c48e5e7f8195f7796ea04e756841391ada927) chore: updates for v9.39.2 release (Jenkins)
- [`54bf0a3`](https://github.com/eslint/eslint/commit/54bf0a3646265060f5f22faef71ec840d630c701) ci: create package manager test ([#&#8203;20392](https://github.com/eslint/eslint/issues/20392)) (루밀LuMir)
- [`3115021`](https://github.com/eslint/eslint/commit/3115021439490d1ed12da5804902ebbf8a5e574b) refactor: simplify JSDoc comment detection logic ([#&#8203;20360](https://github.com/eslint/eslint/issues/20360)) (Pixel998)
- [`4345b17`](https://github.com/eslint/eslint/commit/4345b172a81e1394579ec09df51ba460b956c3b5) chore: update `@eslint-community/regexpp` to `4.12.2` ([#&#8203;20366](https://github.com/eslint/eslint/issues/20366)) (루밀LuMir)
- [`772c9ee`](https://github.com/eslint/eslint/commit/772c9ee9b65b6ad0be3e46462a7f93c37578cfa8) chore: update dependency [@&#8203;eslint/eslintrc](https://github.com/eslint/eslintrc) to ^3.3.3 ([#&#8203;20359](https://github.com/eslint/eslint/issues/20359)) (renovate\[bot])
- [`0b14059`](https://github.com/eslint/eslint/commit/0b14059491d830a49b3577931f4f68fbcfce6be5) chore: package.json update for [@&#8203;eslint/js](https://github.com/eslint/js) release (Jenkins)
- [`d6e7bf3`](https://github.com/eslint/eslint/commit/d6e7bf3064be01d159d6856e3718672c6a97a8e1) ci: bump actions/checkout from 5 to 6 ([#&#8203;20350](https://github.com/eslint/eslint/issues/20350)) (dependabot\[bot])
- [`139d456`](https://github.com/eslint/eslint/commit/139d4567d4afe3f1e1cdae21769d5e868f90ef0d) chore: require mandatory headers in rule docs ([#&#8203;20347](https://github.com/eslint/eslint/issues/20347)) (Milos Djermanovic)
- [`3b0289c`](https://github.com/eslint/eslint/commit/3b0289c7b605b2d94fe2d0c347d07eea4b6ba1d4) chore: remove unused `.eslintignore` and test fixtures ([#&#8203;20316](https://github.com/eslint/eslint/issues/20316)) (Pixel998)
- [`a463e7b`](https://github.com/eslint/eslint/commit/a463e7bea0d18af55e5557e33691e4b0685d9523) chore: update dependency js-yaml to v4 \[security] ([#&#8203;20319](https://github.com/eslint/eslint/issues/20319)) (renovate\[bot])
- [`ebfe905`](https://github.com/eslint/eslint/commit/ebfe90533d07a7020a5c63b93763fe537120f61f) chore: remove redundant rules from eslint-config-eslint ([#&#8203;20327](https://github.com/eslint/eslint/issues/20327)) (Milos Djermanovic)
- [`88dfdb2`](https://github.com/eslint/eslint/commit/88dfdb23ee541de4e9c3aa5d8a152c5980f6cc3f) test: add regression tests for message placeholder interpolation ([#&#8203;20318](https://github.com/eslint/eslint/issues/20318)) (fnx)
- [`6ed0f75`](https://github.com/eslint/eslint/commit/6ed0f758ff460b7a182c8d16b0487ae707e43cc9) chore: skip type checking in `eslint-config-eslint` ([#&#8203;20323](https://github.com/eslint/eslint/issues/20323)) (Francesco Trotta)
- [`1e2cad5`](https://github.com/eslint/eslint/commit/1e2cad5f6fa47ed6ed89d2a29798dda926d50990) chore: package.json update for [@&#8203;eslint/js](https://github.com/eslint/js) release (Jenkins)
- [`9da2679`](https://github.com/eslint/eslint/commit/9da26798483270a2c3c490c41cbd8f0c28edf75a) chore: update `@eslint/*` dependencies ([#&#8203;20321](https://github.com/eslint/eslint/issues/20321)) (Milos Djermanovic)
- [`0439794`](https://github.com/eslint/eslint/commit/043979418161e1c17becef31b1dd5c6e1b031e98) refactor: use types from [@&#8203;eslint/core](https://github.com/eslint/core) ([#&#8203;20235](https://github.com/eslint/eslint/issues/20235)) (jaymarvelz)
- [`cb51ec2`](https://github.com/eslint/eslint/commit/cb51ec2d6d3b729bf02a5e6b58b236578c6cce42) test: cleanup `SourceCode#traverse` tests ([#&#8203;20289](https://github.com/eslint/eslint/issues/20289)) (Milos Djermanovic)
- [`897a347`](https://github.com/eslint/eslint/commit/897a3471d6da073c1a179fa84f7a3fe72973ec45) chore: remove restriction for `type` in rule tests ([#&#8203;20305](https://github.com/eslint/eslint/issues/20305)) (Pixel998)
- [`d972098`](https://github.com/eslint/eslint/commit/d9720988579734da7323fbacca4c67058651d6ff) chore: ignore prettier updates in renovate to keep in sync with trunk ([#&#8203;20304](https://github.com/eslint/eslint/issues/20304)) (Pixel998)
- [`a086359`](https://github.com/eslint/eslint/commit/a0863593872fe01b5dd0e04c682450c26ae40ac8) chore: remove redundant `fast-glob` dev-dependency ([#&#8203;20301](https://github.com/eslint/eslint/issues/20301)) (루밀LuMir)
- [`564b302`](https://github.com/eslint/eslint/commit/564b30215c3c1aba47bc29f948f11db5c824cacd) chore: install `prettier` as a dev dependency ([#&#8203;20302](https://github.com/eslint/eslint/issues/20302)) (michael faith)
- [`8257b57`](https://github.com/eslint/eslint/commit/8257b5729d6a26f88b079aa389df4ecea4451a80) refactor: correct regex for `eslint-plugin/report-message-format` ([#&#8203;20300](https://github.com/eslint/eslint/issues/20300)) (루밀LuMir)
- [`e251671`](https://github.com/eslint/eslint/commit/e2516713bc9ae62117da3f490d9cb6a9676f44fe) refactor: extract assertions in RuleTester ([#&#8203;20135](https://github.com/eslint/eslint/issues/20135)) (唯然)
- [`2e7f25e`](https://github.com/eslint/eslint/commit/2e7f25e18908e66d9bd1a4dc016709e39e19a24d) chore: add `legacy-peer-deps` to `.npmrc` ([#&#8203;20281](https://github.com/eslint/eslint/issues/20281)) (Milos Djermanovic)
- [`39c638a`](https://github.com/eslint/eslint/commit/39c638a9aeb7ddc353684d536bbf69d1d39380bd) chore: update eslint-config-eslint dependencies for v10 prereleases ([#&#8203;20278](https://github.com/eslint/eslint/issues/20278)) (Milos Djermanovic)
- [`8533b3f`](https://github.com/eslint/eslint/commit/8533b3fa281e6ecc481083ee83e9c34cae22f31c) chore: update dependency [@&#8203;eslint/json](https://github.com/eslint/json) to ^0.14.0 ([#&#8203;20288](https://github.com/eslint/eslint/issues/20288)) (renovate\[bot])
- [`796ddf6`](https://github.com/eslint/eslint/commit/796ddf6db5c8fe3e098aa3198128f8ce3c58f8e0) chore: update dependency [@&#8203;eslint/js](https://github.com/eslint/js) to ^9.39.1 ([#&#8203;20285](https://github.com/eslint/eslint/issues/20285)) (renovate\[bot])

</details>

<details>
<summary>mskelton/eslint-plugin-playwright (eslint-plugin-playwright)</summary>

### [`v2.10.2`](https://github.com/mskelton/eslint-plugin-playwright/releases/tag/v2.10.2)

[Compare Source](https://github.com/mskelton/eslint-plugin-playwright/compare/v2.10.1...v2.10.2)

##### Bug Fixes

- **missing-playwright-await:** Fix false positive when re-assigning awaited variable ([8cca0ac](https://github.com/mskelton/eslint-plugin-playwright/commit/8cca0ac362d9ddbce899195f1433f8d853efc3d0)), closes [#&#8203;456](https://github.com/mskelton/eslint-plugin-playwright/issues/456)
- **no-duplicate-hooks:** handle anonymous describe blocks in forEach loops ([8b4ec60](https://github.com/mskelton/eslint-plugin-playwright/commit/8b4ec601a0f801dc2a8701d66f12e28102ffc934)), closes [#&#8203;459](https://github.com/mskelton/eslint-plugin-playwright/issues/459)
- **valid-test-tags:** Support template literal strings ([d98a05c](https://github.com/mskelton/eslint-plugin-playwright/commit/d98a05cb51150bee283109e041e8e458f6d7bc5f)), closes [#&#8203;460](https://github.com/mskelton/eslint-plugin-playwright/issues/460)

### [`v2.10.1`](https://github.com/mskelton/eslint-plugin-playwright/releases/tag/v2.10.1)

[Compare Source](https://github.com/mskelton/eslint-plugin-playwright/compare/v2.10.0...v2.10.1)

##### Bug Fixes

- **missing-playwright-await:** Don't flag Array.fill as missing await ([cff9640](https://github.com/mskelton/eslint-plugin-playwright/commit/cff96403204e3cac83faf2d1768e3ded1378302d)), closes [#&#8203;450](https://github.com/mskelton/eslint-plugin-playwright/issues/450)
- Narrow page detection to prefer false positives ([10238e1](https://github.com/mskelton/eslint-plugin-playwright/commit/10238e173e42725a369db5ee7fb162b1ee99d790))

### [`v2.10.0`](https://github.com/mskelton/eslint-plugin-playwright/releases/tag/v2.10.0)

[Compare Source](https://github.com/mskelton/eslint-plugin-playwright/compare/v2.9.0...v2.10.0)

##### Bug Fixes

- **missing-playwright-await:** Fix false positive with `expect().resolves` ([352e15e](https://github.com/mskelton/eslint-plugin-playwright/commit/352e15e0e28cda5c7f7fbcd5bd6d01cf634aea3e)), closes [#&#8203;448](https://github.com/mskelton/eslint-plugin-playwright/issues/448)
- Support additional promise methods ([8646e62](https://github.com/mskelton/eslint-plugin-playwright/commit/8646e62527202cf11da6c00afc7f7e376d00773f)), closes [#&#8203;444](https://github.com/mskelton/eslint-plugin-playwright/issues/444)

##### Features

- **missing-playwright-await:** Add `includePageLocatorMethods` flag for checking more missing awaits ([#&#8203;438](https://github.com/mskelton/eslint-plugin-playwright/issues/438)) ([41921f8](https://github.com/mskelton/eslint-plugin-playwright/commit/41921f8509bfa90ccef91d86ed874408b60a7abb)), closes [#&#8203;159](https://github.com/mskelton/eslint-plugin-playwright/issues/159)
- **no-skipped-test:** Support `disallowFixme` to optionally disable `.fixme()` annotations ([6b42fdb](https://github.com/mskelton/eslint-plugin-playwright/commit/6b42fdb5cf74c6a98b7544e2931bd157cda88e51)), closes [#&#8203;446](https://github.com/mskelton/eslint-plugin-playwright/issues/446)

### [`v2.9.0`](https://github.com/mskelton/eslint-plugin-playwright/releases/tag/v2.9.0)

[Compare Source](https://github.com/mskelton/eslint-plugin-playwright/compare/v2.8.0...v2.9.0)

##### Bug Fixes

- **no-restricted-roles:** Catch all uses, not just on page methods ([1861fa5](https://github.com/mskelton/eslint-plugin-playwright/commit/1861fa57fd21b8d2d17cbd96238fdf5970277686))
- Support nested locators everywhere ([0e48186](https://github.com/mskelton/eslint-plugin-playwright/commit/0e48186885a8868d3163cdd2d2732c3f227056ab))

##### Features

- **no-duplicate-hooks:** Mark as recommended ([fe3ca54](https://github.com/mskelton/eslint-plugin-playwright/commit/fe3ca54178cde017388aa5d844553a9b7a9d1307))
- **no-duplicate-slow:** Mark as recommended ([2f0b67d](https://github.com/mskelton/eslint-plugin-playwright/commit/2f0b67d841dd8f091f7c8ab44a6eadb34255127b))
- **prefer-hooks-in-order:** Mark as recommended ([e8ae16e](https://github.com/mskelton/eslint-plugin-playwright/commit/e8ae16ef219ce943749b194fc972b27a0162e8cb))
- **prefer-hooks-on-top:** Mark as recommended ([5ab9296](https://github.com/mskelton/eslint-plugin-playwright/commit/5ab929677dea5263b69e2fe44a43cf711b1bbb16))
- **prefer-locator:** Mark as recommended ([fcab221](https://github.com/mskelton/eslint-plugin-playwright/commit/fcab221c092c290c8b2b851b669ea0b1774ec75c))
- **prefer-to-have-count:** Mark as recommended ([fcbf086](https://github.com/mskelton/eslint-plugin-playwright/commit/fcbf086ae637cf569530856e7690b6da85a5c5fe))
- **prefer-to-have-length:** Mark as recommended ([c6c923e](https://github.com/mskelton/eslint-plugin-playwright/commit/c6c923e6b382c1bee4de89e2cf9781511a37e7a0))

### [`v2.8.0`](https://github.com/mskelton/eslint-plugin-playwright/releases/tag/v2.8.0)

[Compare Source](https://github.com/mskelton/eslint-plugin-playwright/compare/v2.7.1...v2.8.0)

##### Bug Fixes

- Add missing test coverage and fix several minor bugs ([#&#8203;434](https://github.com/mskelton/eslint-plugin-playwright/issues/434)) ([e3398ec](https://github.com/mskelton/eslint-plugin-playwright/commit/e3398ec61da52de205e7c9af2896633357769f74))
- **missing-playwright-await:** Handle spread elements ([df30163](https://github.com/mskelton/eslint-plugin-playwright/commit/df3016323819f7bc335fd1841971dccc2ae64f51)), closes [#&#8203;430](https://github.com/mskelton/eslint-plugin-playwright/issues/430)
- **missing-playwright-await:** Support more promise edge cases ([b4cdcbd](https://github.com/mskelton/eslint-plugin-playwright/commit/b4cdcbd010a2b4dfc7ee14ab5bdc655897389f19))

##### Features

- Auto-detect `test.extend()` fixtures and import aliases ([#&#8203;432](https://github.com/mskelton/eslint-plugin-playwright/issues/432)) ([8b22ee7](https://github.com/mskelton/eslint-plugin-playwright/commit/8b22ee7b1f7823d81bafda82e240dd51106726dd))

### [`v2.7.1`](https://github.com/mskelton/eslint-plugin-playwright/releases/tag/v2.7.1)

[Compare Source](https://github.com/mskelton/eslint-plugin-playwright/compare/v2.7.0...v2.7.1)

##### Bug Fixes

- **missing-playwirght-await:** Fix false positive with promise chains ([6e4f5ff](https://github.com/mskelton/eslint-plugin-playwright/commit/6e4f5ff4876c4e92542e7fde20ec5314d4b36ba5))

### [`v2.7.0`](https://github.com/mskelton/eslint-plugin-playwright/releases/tag/v2.7.0)

[Compare Source](https://github.com/mskelton/eslint-plugin-playwright/compare/v2.6.1...v2.7.0)

##### Features

- Support ESLint 10 ([aa5315b](https://github.com/mskelton/eslint-plugin-playwright/commit/aa5315b70cd2481d3093d27435d1938585e1de9a)), closes [#&#8203;424](https://github.com/mskelton/eslint-plugin-playwright/issues/424)

### [`v2.6.1`](https://github.com/mskelton/eslint-plugin-playwright/releases/tag/v2.6.1)

[Compare Source](https://github.com/mskelton/eslint-plugin-playwright/compare/v2.6.0...v2.6.1)

##### Bug Fixes

- Exclude `@typescript-eslint/utils` from the bundle ([6547702](https://github.com/mskelton/eslint-plugin-playwright/commit/6547702acf8b09736f36d79a2daec0219b930986)), closes [#&#8203;425](https://github.com/mskelton/eslint-plugin-playwright/issues/425)

### [`v2.6.0`](https://github.com/mskelton/eslint-plugin-playwright/releases/tag/v2.6.0)

[Compare Source](https://github.com/mskelton/eslint-plugin-playwright/compare/v2.5.1...v2.6.0)

##### Bug Fixes

- **docs:** consistent-spacing-between-blocks name ([#&#8203;421](https://github.com/mskelton/eslint-plugin-playwright/issues/421)) ([f2306ed](https://github.com/mskelton/eslint-plugin-playwright/commit/f2306ed279a1434beef3f5eca83ce384b848dce4))
- **valid-title:** Ignore variables we can't statically determine ([1597555](https://github.com/mskelton/eslint-plugin-playwright/commit/15975557f1e6a792a6e0ae843ae5c98496ae8f91)), closes [#&#8203;368](https://github.com/mskelton/eslint-plugin-playwright/issues/368)

##### Features

- Add `no-duplicate-slow` rule ([dac9495](https://github.com/mskelton/eslint-plugin-playwright/commit/dac94950502698708f7e001f54fa7c505c20dafc)), closes [#&#8203;418](https://github.com/mskelton/eslint-plugin-playwright/issues/418)
- Add `no-restricted-roles` rule ([#&#8203;422](https://github.com/mskelton/eslint-plugin-playwright/issues/422)) ([91817bf](https://github.com/mskelton/eslint-plugin-playwright/commit/91817bf63fb6b764af6e85f4968eff7165ae52a3))
- Add `require-to-pass-timeout` rule ([e620e87](https://github.com/mskelton/eslint-plugin-playwright/commit/e620e87ccf3e585a99acb500c4c635e8d1320cf8)), closes [#&#8203;345](https://github.com/mskelton/eslint-plugin-playwright/issues/345)
- Add require-tags rule ([c83b13a](https://github.com/mskelton/eslint-plugin-playwright/commit/c83b13ab08cd4631129a28a44b7f1ea29b17585b)), closes [#&#8203;401](https://github.com/mskelton/eslint-plugin-playwright/issues/401)
- **missing-playwright-await:** Add support for waitForResponse and other similar functions ([960be8a](https://github.com/mskelton/eslint-plugin-playwright/commit/960be8a5a7418f42a9485f8e00e71a6729c46f70)), closes [#&#8203;199](https://github.com/mskelton/eslint-plugin-playwright/issues/199)

### [`v2.5.1`](https://github.com/mskelton/eslint-plugin-playwright/releases/tag/v2.5.1)

[Compare Source](https://github.com/mskelton/eslint-plugin-playwright/compare/v2.5.0...v2.5.1)

##### Bug Fixes

- **no-conditional-in-test:** Fix false positive for `||` ([611657c](https://github.com/mskelton/eslint-plugin-playwright/commit/611657c3cb5b932c9f261c297c4ab01d1fed4a6c))

### [`v2.5.0`](https://github.com/mskelton/eslint-plugin-playwright/releases/tag/v2.5.0)

[Compare Source](https://github.com/mskelton/eslint-plugin-playwright/compare/v2.4.1...v2.5.0)

##### Bug Fixes

- Fix lint ([4e8461d](https://github.com/mskelton/eslint-plugin-playwright/commit/4e8461dc5b73018d706782c729fbcce67347b7f6))
- Fix TypeScript types ([af7d870](https://github.com/mskelton/eslint-plugin-playwright/commit/af7d8702121f58eb5974b81a514470542162823c))

##### Features

- Add `enforce-consistent-spacing-between-blocks` rule ([#&#8203;411](https://github.com/mskelton/eslint-plugin-playwright/issues/411)) ([a9b78d5](https://github.com/mskelton/eslint-plugin-playwright/commit/a9b78d5d0c7a7c051d9bee85a584ca483dd22777))
- Add no-restricted-locators rule ([a65200b](https://github.com/mskelton/eslint-plugin-playwright/commit/a65200b1773b49ccafbd9a9b8a81e4e9f700bd67)), closes [#&#8203;407](https://github.com/mskelton/eslint-plugin-playwright/issues/407)
- **prefer-web-first-assertions:** Support `allInnerTexts()` and `allTextContents()` ([36917a8](https://github.com/mskelton/eslint-plugin-playwright/commit/36917a86fb7e4ef49837e7657a8363d55f06e461)), closes [#&#8203;362](https://github.com/mskelton/eslint-plugin-playwright/issues/362)

### [`v2.4.1`](https://github.com/mskelton/eslint-plugin-playwright/releases/tag/v2.4.1)

[Compare Source](https://github.com/mskelton/eslint-plugin-playwright/compare/v2.4.0...v2.4.1)

##### Bug Fixes

- **no-conditional-in-test:** allow nullish coalescing operator ([2c25b4f](https://github.com/mskelton/eslint-plugin-playwright/commit/2c25b4fe3b7d487a8cc06c43a1bf91fea3f3c7ea)), closes [#&#8203;406](https://github.com/mskelton/eslint-plugin-playwright/issues/406)

### [`v2.4.0`](https://github.com/mskelton/eslint-plugin-playwright/releases/tag/v2.4.0)

[Compare Source](https://github.com/mskelton/eslint-plugin-playwright/compare/v2.3.0...v2.4.0)

##### Bug Fixes

- **missing-playwright-await:** prevent infinite recursion in checkValidity ([9ce346d](https://github.com/playwright-community/eslint-plugin-playwright/commit/9ce346ddde659050714bbe770363e3cbe1361c9c))

##### Features

- **expect-expect:** Support regex patterns ([#&#8203;390](https://github.com/playwright-community/eslint-plugin-playwright/issues/390)) ([fdd0253](https://github.com/playwright-community/eslint-plugin-playwright/commit/fdd025339b68173cb5aec57f83c8bc9792388be1))

### [`v2.3.0`](https://github.com/mskelton/eslint-plugin-playwright/releases/tag/v2.3.0)

[Compare Source](https://github.com/mskelton/eslint-plugin-playwright/compare/v2.2.2...v2.3.0)

##### Bug Fixes

- Check for test tags in titles ([377238c](https://github.com/playwright-community/eslint-plugin-playwright/commit/377238ccdecb88c8fd62abbb8dd8e89d8397236b)), closes [#&#8203;392](https://github.com/playwright-community/eslint-plugin-playwright/issues/392)

##### Features

- Add no-unused-locators rule ([#&#8203;396](https://github.com/playwright-community/eslint-plugin-playwright/issues/396)) ([c0937d7](https://github.com/playwright-community/eslint-plugin-playwright/commit/c0937d72d2f102fd7531c86fb135256293e9be85))

### [`v2.2.2`](https://github.com/mskelton/eslint-plugin-playwright/releases/tag/v2.2.2)

[Compare Source](https://github.com/mskelton/eslint-plugin-playwright/compare/v2.2.1...v2.2.2)

##### Bug Fixes

- **prefer-web-first-assertions:** Fix false positive ([#&#8203;384](https://github.com/playwright-community/eslint-plugin-playwright/issues/384)) ([38a559e](https://github.com/playwright-community/eslint-plugin-playwright/commit/38a559e69978c19206d4a7a032f8fb4227306a11))
- **valid-test-tags:** disallow extra properties in rule options and add to recommended ([#&#8203;381](https://github.com/playwright-community/eslint-plugin-playwright/issues/381)) ([4762bbd](https://github.com/playwright-community/eslint-plugin-playwright/commit/4762bbdc13a5832266538b6fbcace391cc3aadfd))

### [`v2.2.1`](https://github.com/mskelton/eslint-plugin-playwright/releases/tag/v2.2.1)

[Compare Source](https://github.com/mskelton/eslint-plugin-playwright/compare/v2.2.0...v2.2.1)

##### Features

- Support addInitScript in no-unsafe-references
- Add toContainClass method
- Add valid-test-tags rule
- Add no-wait-for-navigation rule

##### Bug Fixes

- clean published package.json ([#&#8203;371](https://github.com/playwright-community/eslint-plugin-playwright/issues/371)) ([b8401e5](https://github.com/playwright-community/eslint-plugin-playwright/commit/b8401e51669c251ae31b4cdc610bdc4a0b3e9aba)), closes [#&#8203;360](https://github.com/playwright-community/eslint-plugin-playwright/issues/360)
- no-conditional-in-test does not trigger for conditionals in test metadata (fixes [#&#8203;363](https://github.com/playwright-community/eslint-plugin-playwright/issues/363)) ([#&#8203;372](https://github.com/playwright-community/eslint-plugin-playwright/issues/372)) ([12b0832](https://github.com/playwright-community/eslint-plugin-playwright/commit/12b083248e50f6e23e95f7d3fbc6034672e87ba7))
- Remove no-slowed-test from recommended list ([#&#8203;348](https://github.com/playwright-community/eslint-plugin-playwright/issues/348)) ([6baec3a](https://github.com/playwright-community/eslint-plugin-playwright/commit/6baec3ac2861b6cd2c8fcb83c61d00b4e3c82128))
- Support non-awaited expressions in prefer-web-first-assertions
- Allow valid locators declared as variables
- Fix false positive when using allowConditional

### [`v2.2.0`](https://github.com/mskelton/eslint-plugin-playwright/releases/tag/v2.2.0)

[Compare Source](https://github.com/mskelton/eslint-plugin-playwright/compare/v2.1.0...v2.2.0)

##### Features

- Add `no-slowed-test` rule ([#&#8203;302](https://github.com/playwright-community/eslint-plugin-playwright/issues/302)) ([53df693](https://github.com/playwright-community/eslint-plugin-playwright/commit/53df693a1c9348205d453c1b791adf4f60242f0d)), closes [#&#8203;296](https://github.com/playwright-community/eslint-plugin-playwright/issues/296)
- Add support for Playwright 1.50 ([#&#8203;347](https://github.com/playwright-community/eslint-plugin-playwright/issues/347)) ([956fe62](https://github.com/playwright-community/eslint-plugin-playwright/commit/956fe626c06bd12f57a1ed6fa009421bb0ce296a))

##### Bug Fixes

- **expect-expert:** report on test function identifier rather than body ([#&#8203;337](https://github.com/playwright-community/eslint-plugin-playwright/issues/337)) ([35e37a1](https://github.com/playwright-community/eslint-plugin-playwright/commit/35e37a12fb0691f6e549b01621c75c9556121899))
- **prefer-comparison-matcher:** Fix typo in docs ([c269371](https://github.com/playwright-community/eslint-plugin-playwright/commit/c269371be2dbc4557dcd24c24e06b8db056f681a)), closes [#&#8203;343](https://github.com/playwright-community/eslint-plugin-playwright/issues/343)

### [`v2.1.0`](https://github.com/mskelton/eslint-plugin-playwright/releases/tag/v2.1.0)

[Compare Source](https://github.com/mskelton/eslint-plugin-playwright/compare/v2.0.1...v2.1.0)

##### Features

- Add `test.fail.only` as a valid chain ([c067ad2](https://github.com/playwright-community/eslint-plugin-playwright/commit/c067ad203fba4617a9e04ee610dc0ee3d1b40f04))

### [`v2.0.1`](https://github.com/mskelton/eslint-plugin-playwright/releases/tag/v2.0.1)

[Compare Source](https://github.com/mskelton/eslint-plugin-playwright/compare/v2.0.0...v2.0.1)

##### Bug Fixes

- Fix types for native TypeScript ESM ([4c61256](https://github.com/playwright-community/eslint-plugin-playwright/commit/4c61256978556570b4f528eb2ba932ebee110609))

### [`v2.0.0`](https://github.com/mskelton/eslint-plugin-playwright/releases/tag/v2.0.0)

[Compare Source](https://github.com/mskelton/eslint-plugin-playwright/compare/v1.8.3...v2.0.0)

##### ⚠ BREAKING CHANGES

- Remove jest-playwright configs

##### Features

- Remove jest-playwright configs ([ba82509](https://github.com/playwright-community/eslint-plugin-playwright/commit/ba82509db582481df8805e310b797ac710c5e37b))

##### Bug Fixes

- Fix type exports ([f566df5](https://github.com/playwright-community/eslint-plugin-playwright/commit/f566df55f139a6ef37253cbda90f28889ed768f8))

</details>

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

👻 **Immortal**: This PR will be recreated if closed unmerged. Get [config help](https://github.com/renovatebot/renovate/discussions) if that's undesired.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0MC42Mi4xIiwidXBkYXRlZEluVmVyIjoiNDAuNjIuMSIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOlsiZGVwZW5kZW5jaWVzIl19-->

Reviewed-on: https://git.unespace.com/julien/apf_portal/pulls/36
Co-authored-by: APF Portal Bot <jgautier.webdev+apf-portal-bot@gmail.com>
Co-committed-by: APF Portal Bot <jgautier.webdev+apf-portal-bot@gmail.com>
2026-05-06 01:45:24 +02:00
APF Portal Bot eb87f0e6bb chore(deps): update dependency webpack-cli to v7 (#34)
CI / commits (push) Has been skipped
CI / scan (push) Failing after 1m11s
CI / check (push) Failing after 1m34s
CI / perf (push) Failing after 1m30s
CI / a11y (push) Successful in 1m24s
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [webpack-cli](https://github.com/webpack/webpack-cli/tree/main/packages/webpack-cli) ([source](https://github.com/webpack/webpack-cli)) | devDependencies | major | [`^5.1.4` -> `^7.0.0`](https://renovatebot.com/diffs/npm/webpack-cli/5.1.4/7.0.2) |

---

### Release Notes

<details>
<summary>webpack/webpack-cli (webpack-cli)</summary>

### [`v7.0.2`](https://github.com/webpack/webpack-cli/blob/HEAD/CHANGELOG.md#702)

[Compare Source](https://github.com/webpack/webpack-cli/compare/webpack-cli@7.0.1...webpack-cli@7.0.2)

##### Patch Changes

- Resolve configuration path for cache build dependencies. (by [@&#8203;alexander-akait](https://github.com/alexander-akait) in [#&#8203;4707](https://github.com/webpack/webpack-cli/pull/4707))

### [`v7.0.1`](https://github.com/webpack/webpack-cli/blob/HEAD/CHANGELOG.md#701)

[Compare Source](https://github.com/webpack/webpack-cli/compare/webpack-cli@7.0.0...webpack-cli@7.0.1)

##### Patch Changes

- The `file` protocol for configuration options (`--config`/`--extends`) is supported. (by [@&#8203;alexander-akait](https://github.com/alexander-akait) in [#&#8203;4702](https://github.com/webpack/webpack-cli/pull/4702))

### [`v7.0.0`](https://github.com/webpack/webpack-cli/blob/HEAD/CHANGELOG.md#700)

[Compare Source](https://github.com/webpack/webpack-cli/compare/webpack-cli@6.0.1...webpack-cli@7.0.0)

##### Major Changes

- The minimum supported version of Node.js is `20.9.0`. (by [@&#8203;alexander-akait](https://github.com/alexander-akait) in [#&#8203;4677](https://github.com/webpack/webpack-cli/pull/4677))

- Use dynamic import to load `webpack.config.js`, fallback to interpret only when configuration can't be load by dynamic import. Using dynamic imports allows you to take advantage of Node.js's built-in TypeScript support. (by [@&#8203;alexander-akait](https://github.com/alexander-akait) in [#&#8203;4677](https://github.com/webpack/webpack-cli/pull/4677))

- Removed the `--node-env` argument in favor of the `--config-node-env` argument. (by [@&#8203;alexander-akait](https://github.com/alexander-akait) in [#&#8203;4677](https://github.com/webpack/webpack-cli/pull/4677))

- The `version` command only output versions right now. (by [@&#8203;alexander-akait](https://github.com/alexander-akait) in [#&#8203;4677](https://github.com/webpack/webpack-cli/pull/4677))

- Removed deprecated API, no action required unless you use `import cli from "webpack-cli";`/`const cli = require("webpack-cli");`. (by [@&#8203;alexander-akait](https://github.com/alexander-akait) in [#&#8203;4677](https://github.com/webpack/webpack-cli/pull/4677))

##### Patch Changes

- Allow configuration freezing. (by [@&#8203;alexander-akait](https://github.com/alexander-akait) in [#&#8203;4677](https://github.com/webpack/webpack-cli/pull/4677))

- Use graceful shutdown when file system cache is enabled. (by [@&#8203;alexander-akait](https://github.com/alexander-akait) in [#&#8203;4677](https://github.com/webpack/webpack-cli/pull/4677))

- Performance improved. (by [@&#8203;alexander-akait](https://github.com/alexander-akait) in [#&#8203;4677](https://github.com/webpack/webpack-cli/pull/4677))

### [`v6.0.1`](https://github.com/webpack/webpack-cli/blob/HEAD/CHANGELOG.md#601-2024-12-20)

[Compare Source](https://github.com/webpack/webpack-cli/compare/webpack-cli@6.0.0...webpack-cli@6.0.1)

##### Bug Fixes

- update peer dependencies ([#&#8203;4356](https://github.com/webpack/webpack-cli/issues/4356)) ([7a7e5d9](https://github.com/webpack/webpack-cli/commit/7a7e5d9f4bd796c7d1089db228b9581e97cc897e))

### [`v6.0.0`](https://github.com/webpack/webpack-cli/blob/HEAD/CHANGELOG.md#600-2024-12-19)

[Compare Source](https://github.com/webpack/webpack-cli/compare/webpack-cli@5.1.4...webpack-cli@6.0.0)

##### BREAKING CHANGES

- the minimum required Node.js version is `18.12.0`
- removed `init`, `loader` and `plugin` commands in favor [`create-webpack-app`](https://github.com/webpack/webpack-cli/tree/main/packages/create-webpack-app)
- dropped support for `webpack-dev-server@v4`
- minimum supported webpack version is `5.82.0`
- the `--define-process-env-node-env` option was renamed to `--config-node-env`

##### Bug Fixes

- allow to require `webpack.config.js` in ESM format ([#&#8203;4346](https://github.com/webpack/webpack-cli/issues/4346)) ([5106684](https://github.com/webpack/webpack-cli/commit/51066846326bcae5f9793d3496325213342d3dd2))
- correct the minimum help output ([#&#8203;4057](https://github.com/webpack/webpack-cli/issues/4057)) ([c727c4f](https://github.com/webpack/webpack-cli/commit/c727c4f3c790797cf46a6c0bc83ba77803d3eb05))
- gracefully shutting down ([#&#8203;4145](https://github.com/webpack/webpack-cli/issues/4145)) ([90720e2](https://github.com/webpack/webpack-cli/commit/90720e26ba3b0d115ed066fb8ec3db074751163e))
- improve help output for possible values ([#&#8203;4316](https://github.com/webpack/webpack-cli/issues/4316)) ([4cd5aef](https://github.com/webpack/webpack-cli/commit/4cd5aef3b93e3d73b5175c36cf9e8f9ae4455cb2))
- no serve when dev-server is false ([#&#8203;2947](https://github.com/webpack/webpack-cli/issues/2947)) ([a93e860](https://github.com/webpack/webpack-cli/commit/a93e8603a4c2639916152a013afed04c0e8f3a35))

##### Features

- output pnpm version with `info`/`version` command ([#&#8203;3906](https://github.com/webpack/webpack-cli/issues/3906)) ([38f3c6f](https://github.com/webpack/webpack-cli/commit/38f3c6f2b99f098d2f4afd60f005e8ff5cd44435))

#### [5.1.4](https://github.com/webpack/webpack-cli/compare/webpack-cli@5.1.3...webpack-cli@5.1.4) (2023-06-07)

##### Bug Fixes

- multi compiler progress output ([f659624](https://github.com/webpack/webpack-cli/commit/f6596242c74100bfd6fa391ed2071402a3bd4785))

#### [5.1.3](https://github.com/webpack/webpack-cli/compare/webpack-cli@5.1.2...webpack-cli@5.1.3) (2023-06-04)

##### Bug Fixes

- regression for custom configurations ([#&#8203;3834](https://github.com/webpack/webpack-cli/issues/3834)) ([bb4f8eb](https://github.com/webpack/webpack-cli/commit/bb4f8eb4325219afae3203dc4893af2b4655d5fa))

#### [5.1.2](https://github.com/webpack/webpack-cli/compare/webpack-cli@5.1.1...webpack-cli@5.1.2) (2023-06-04)

##### Bug Fixes

- improve check for custom webpack and webpack-dev-server package existance ([0931ab6](https://github.com/webpack/webpack-cli/commit/0931ab6dfd8d9f511036bcb7c1a4ea8dde1ff1cb))
- improve help for some flags ([f468614](https://github.com/webpack/webpack-cli/commit/f4686141681cfcbc74d57e69a732e176decff225))
- improved support for `.cts` and `.mts` extensions ([a77daf2](https://github.com/webpack/webpack-cli/commit/a77daf28f8a8ad96410a39d565f011f6bb14f6bb))

#### [5.1.1](https://github.com/webpack/webpack-cli/compare/webpack-cli@5.1.0...webpack-cli@5.1.1) (2023-05-09)

##### Bug Fixes

- false positive warning when `--watch` used ([#&#8203;3783](https://github.com/webpack/webpack-cli/issues/3783)) ([c0436ba](https://github.com/webpack/webpack-cli/commit/c0436baca2da7a8ce9e53bbbe960dd1951fe6404))

</details>

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0MC42Mi4xIiwidXBkYXRlZEluVmVyIjoiNDAuNjIuMSIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOlsiZGVwZW5kZW5jaWVzIl19-->

Reviewed-on: https://git.unespace.com/julien/apf_portal/pulls/34
Co-authored-by: APF Portal Bot <jgautier.webdev+apf-portal-bot@gmail.com>
Co-committed-by: APF Portal Bot <jgautier.webdev+apf-portal-bot@gmail.com>
2026-05-06 01:17:56 +02:00
APF Portal Bot 12e3055772 chore(deps): update dependency typescript to v6 (#33)
CI / commits (push) Has been skipped
CI / scan (push) Failing after 1m46s
CI / check (push) Failing after 2m2s
CI / perf (push) Failing after 2m2s
CI / a11y (push) Successful in 1m35s
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [typescript](https://www.typescriptlang.org/) ([source](https://github.com/microsoft/TypeScript)) | devDependencies | major | [`~5.9.2` -> `~6.0.0`](https://renovatebot.com/diffs/npm/typescript/5.9.3/6.0.3) |

---

### Release Notes

<details>
<summary>microsoft/TypeScript (typescript)</summary>

### [`v6.0.3`](https://github.com/microsoft/TypeScript/releases/tag/v6.0.3): TypeScript 6.0.3

[Compare Source](https://github.com/microsoft/TypeScript/compare/v6.0.2...v6.0.3)

For release notes, check out the [release announcement blog post](https://devblogs.microsoft.com/typescript/announcing-typescript-6-0/).

- [fixed issues query for TypeScript 6.0.0 (Beta)](https://github.com/Microsoft/TypeScript/issues?utf8=%E2%9C%93\&q=milestone%3A%22TypeScript+6.0.0%22).
- [fixed issues query for TypeScript 6.0.1 (RC)](https://github.com/Microsoft/TypeScript/issues?utf8=%E2%9C%93\&q=milestone%3A%22TypeScript+6.0.1%22).
- [fixed issues query for TypeScript 6.0.2 (Stable)](https://github.com/Microsoft/TypeScript/issues?utf8=%E2%9C%93\&q=milestone%3A%22TypeScript+6.0.2%22).
- [fixed issues query for TypeScript 6.0.3 (Stable)](https://github.com/Microsoft/TypeScript/issues?utf8=%E2%9C%93\&q=milestone%3A%22TypeScript+6.0.3%22).

Downloads are available on:

- [npm](https://www.npmjs.com/package/typescript)

### [`v6.0.2`](https://github.com/microsoft/TypeScript/releases/tag/v6.0.2): TypeScript 6.0

[Compare Source](https://github.com/microsoft/TypeScript/compare/v5.9.3...v6.0.2)

For release notes, check out the [release announcement blog post](https://devblogs.microsoft.com/typescript/announcing-typescript-6-0/).

- [fixed issues query for TypeScript 6.0.0 (Beta)](https://github.com/Microsoft/TypeScript/issues?utf8=%E2%9C%93\&q=milestone%3A%22TypeScript+6.0.0%22).
- [fixed issues query for TypeScript 6.0.1 (RC)](https://github.com/Microsoft/TypeScript/issues?utf8=%E2%9C%93\&q=milestone%3A%22TypeScript+6.0.1%22).
- [fixed issues query for TypeScript 6.0.2 (Stable)](https://github.com/Microsoft/TypeScript/issues?utf8=%E2%9C%93\&q=milestone%3A%22TypeScript+6.0.2%22).

Downloads are available on:

- [npm](https://www.npmjs.com/package/typescript)

</details>

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0MC42Mi4xIiwidXBkYXRlZEluVmVyIjoiNDAuNjIuMSIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOlsiZGVwZW5kZW5jaWVzIl19-->

Reviewed-on: #33
Co-authored-by: APF Portal Bot <jgautier.webdev+apf-portal-bot@gmail.com>
Co-committed-by: APF Portal Bot <jgautier.webdev+apf-portal-bot@gmail.com>
2026-05-06 00:30:54 +02:00
APF Portal Bot a60fb35e89 chore(deps): update dependency jsdom to v29 (#30)
CI / commits (push) Has been skipped
CI / perf (push) Failing after 11m11s
CI / scan (push) Failing after 11m20s
CI / check (push) Failing after 11m35s
CI / a11y (push) Successful in 32s
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [jsdom](https://github.com/jsdom/jsdom) | devDependencies | major | [`^27.1.0` -> `^29.0.0`](https://renovatebot.com/diffs/npm/jsdom/27.4.0/29.1.1) |

---

### Release Notes

<details>
<summary>jsdom/jsdom (jsdom)</summary>

### [`v29.1.1`](https://github.com/jsdom/jsdom/releases/tag/v29.1.1)

[Compare Source](https://github.com/jsdom/jsdom/compare/v29.1.0...v29.1.1)

- Fixed `'border-radius'` computed style serialization. ([@&#8203;asamuzaK](https://github.com/asamuzaK))
- Fixed computed style computation when using `'background-origin'` and `'background-clip'` CSS properties. ([@&#8203;asamuzaK](https://github.com/asamuzaK))
- Significantly optimized initial calls to `getComputedStyle()`, before the cache warms up. ([@&#8203;asamuzaK](https://github.com/asamuzaK))

### [`v29.1.0`](https://github.com/jsdom/jsdom/releases/tag/v29.1.0)

[Compare Source](https://github.com/jsdom/jsdom/compare/v29.0.2...v29.1.0)

- Added basic support for the ratio CSS type. ([@&#8203;asamuzaK](https://github.com/asamuzaK))
- Fixed `getComputedStyle()` sometimes returning outdated results after CSS was modified. ([@&#8203;asamuzaK](https://github.com/asamuzaK))

### [`v29.0.2`](https://github.com/jsdom/jsdom/releases/tag/v29.0.2)

[Compare Source](https://github.com/jsdom/jsdom/compare/v29.0.1...v29.0.2)

- Significantly improved and sped up `getComputedStyle()`. Computed value rules are now applied across a broader set of properties, and include fixes related to inheritance, defaulting keywords, custom properties, and color-related values such as `currentcolor` and system colors. ([@&#8203;asamuzaK](https://github.com/asamuzaK))
- Fixed CSS `'background`' and `'border'` shorthand parsing. ([@&#8203;asamuzaK](https://github.com/asamuzaK))

### [`v29.0.1`](https://github.com/jsdom/jsdom/releases/tag/v29.0.1)

[Compare Source](https://github.com/jsdom/jsdom/compare/v29.0.0...v29.0.1)

- Fixed CSS parsing of `'border'`, `'background'`, and their sub-shorthands containing keywords or `var()`. ([@&#8203;asamuzaK](https://github.com/asamuzaK))
- Fixed `getComputedStyle()` to return a more functional `CSSStyleDeclaration` object, including indexed access support, which regressed in v29.0.0.

### [`v29.0.0`](https://github.com/jsdom/jsdom/releases/tag/v29.0.0)

[Compare Source](https://github.com/jsdom/jsdom/compare/v28.1.0...v29.0.0)

Breaking changes:

- Node.js v22.13.0+ is now the minimum supported v22 version (was v22.12.0+).

Other changes:

- Overhauled the CSSOM implementation, replacing the [`@acemir/cssom`](https://www.npmjs.com/package/@&#8203;acemir/cssom) and [`cssstyle`](https://github.com/jsdom/cssstyle) dependencies with fresh internal implementations built on webidl2js wrappers and the [`css-tree`](https://www.npmjs.com/package/css-tree) parser. Serialization, parsing, and API behavior is improved in various ways, especially around edge cases.
- Added `CSSCounterStyleRule` and `CSSNamespaceRule` to jsdom `Window`s.
- Added `cssMediaRule.matches` and `cssSupportsRule.matches` getters.
- Added proper media query parsing in `MediaList`, using `css-tree` instead of naive comma-splitting. Invalid queries become `"not all"` per spec.
- Added `cssKeyframeRule.keyText` getter/setter validation.
- Added `cssStyleRule.selectorText` setter validation: invalid selectors are now rejected.
- Added `styleSheet.ownerNode`, `styleSheet.href`, and `styleSheet.title`.
- Added bad port blocking per the [fetch specification](https://fetch.spec.whatwg.org/#bad-port), preventing fetches to commonly-abused ports.
- Improved `Document` initialization performance by lazily initializing the CSS selector engine, avoiding ~0.5 ms of overhead per `Document`. ([@&#8203;thypon](https://github.com/thypon))
- Fixed a memory leak when stylesheets were removed from the document.
- Fixed `CSSStyleDeclaration` modifications to properly trigger custom element reactions.
- Fixed nested `@media` rule parsing.
- Fixed `CSSStyleSheet`'s "disallow modification" flag not being checked in all mutation methods.
- Fixed `XMLHttpRequest`'s `response` getter returning parsed JSON during the `LOADING` state instead of `null`.
- Fixed `getComputedStyle()` crashing in XHTML documents when stylesheets contained at-rules such as `@page` or `@font-face`.
- Fixed a potential hang in synchronous `XMLHttpRequest` caused by a race condition with the worker thread's idle timeout.

### [`v28.1.0`](https://github.com/jsdom/jsdom/releases/tag/v28.1.0)

[Compare Source](https://github.com/jsdom/jsdom/compare/v28.0.0...v28.1.0)

- Added `blob.text()`, `blob.arrayBuffer()`, and `blob.bytes()` methods.
- Improved `getComputedStyle()` to account for CSS specificity when multiple rules apply. ([@&#8203;asamuzaK](https://github.com/asamuzaK))
- Improved synchronous `XMLHttpRequest` performance by using a persistent worker thread, avoiding ~400ms of setup overhead on every synchronous request after the first one.
- Improved performance of `node.getRootNode()`, `node.isConnected`, and `event.dispatchEvent()` by caching the root node of document-connected trees.
- Fixed `getComputedStyle()` to correctly handle `!important` priority. ([@&#8203;asamuzaK](https://github.com/asamuzaK))
- Fixed `document.getElementById()` to return the first element in tree order when multiple elements share the same ID.
- Fixed `<svg>` elements to no longer incorrectly proxy event handlers to the `Window`.
- Fixed `FileReader` event timing and `fileReader.result` state to more closely follow the spec.
- Fixed a potential hang when synchronous `XMLHttpRequest` encountered dispatch errors.
- Fixed compatibility with environments where Node.js's built-in `fetch()` has been used before importing jsdom, by working around undici v6/v7 incompatibilities.

### [`v28.0.0`](https://github.com/jsdom/jsdom/releases/tag/v28.0.0)

[Compare Source](https://github.com/jsdom/jsdom/compare/v27.4.0...v28.0.0)

- Overhauled resource loading customization. See [the new README](https://github.com/jsdom/jsdom/blob/2b65c6a80af2c899e32933c5e0cb842164852149/README.md#loading-subresources) for details on the new API.
- Added MIME type sniffing to `<iframe>` and `<frame>` loads.
- Regression: `WebSocket`s are no longer correctly throttled to one connection per origin. This is a result of the bug at [nodejs/undici#4743](https://github.com/nodejs/undici/issues/4743).
- Fixed decoding of the query components of `<a>` and `<area>` elements in non-UTF-8 documents.
- Fixed `XMLHttpRequest` fetches and `WebSocket` upgrade requests to be interceptable by the new customizable resource loading. (Except synchronous `XMLHttpRequest`s.)
- Fixed the referrer of a document to be set correctly when redirects are involved; it is now the initiating page, not the last hop in the redirect chain.
- Fixed correctness bugs when passing `ArrayBuffer`s or typed arrays to various APIs, where they would not correctly snapshot the data.
- Fixed `require("url").parse()` deprecation warning when using `WebSocket`s.
- Fixed `<iframe>`, `<frame>`, and `<img>` (when `canvas` is installed) to fire `load` events, not `error` events, on non-OK HTTP responses.
- Fixed many small issues in `XMLHttpRequest`.

</details>

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0MC42Mi4xIiwidXBkYXRlZEluVmVyIjoiNDAuNjIuMSIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOlsiZGVwZW5kZW5jaWVzIl19-->

Reviewed-on: https://git.unespace.com/julien/apf_portal/pulls/30
Co-authored-by: APF Portal Bot <jgautier.webdev+apf-portal-bot@gmail.com>
Co-committed-by: APF Portal Bot <jgautier.webdev+apf-portal-bot@gmail.com>
2026-05-05 23:58:54 +02:00
APF Portal Bot 7fc2abf0fc chore(deps): update dependency jsonc-eslint-parser to v3 (#31)
CI / commits (push) Has been skipped
CI / check (push) Successful in 2m29s
CI / a11y (push) Successful in 2m3s
CI / scan (push) Failing after 2m36s
CI / perf (push) Successful in 4m2s
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [jsonc-eslint-parser](https://github.com/ota-meshi/jsonc-eslint-parser) | devDependencies | major | [`^2.1.0` -> `^3.0.0`](https://renovatebot.com/diffs/npm/jsonc-eslint-parser/2.4.2/3.1.0) |

---

### Release Notes

<details>
<summary>ota-meshi/jsonc-eslint-parser (jsonc-eslint-parser)</summary>

### [`v3.1.0`](https://github.com/ota-meshi/jsonc-eslint-parser/blob/HEAD/CHANGELOG.md#310)

[Compare Source](https://github.com/ota-meshi/jsonc-eslint-parser/compare/v3.0.0...v3.1.0)

##### Minor Changes

- [#&#8203;275](https://github.com/ota-meshi/jsonc-eslint-parser/pull/275) [`38d5905`](https://github.com/ota-meshi/jsonc-eslint-parser/commit/38d5905ede808f7cc5ff2530552cebd0fadc6c5e) Thanks [@&#8203;ota-meshi](https://github.com/ota-meshi)! - feat: add `tokenize()`

### [`v3.0.0`](https://github.com/ota-meshi/jsonc-eslint-parser/blob/HEAD/CHANGELOG.md#300)

[Compare Source](https://github.com/ota-meshi/jsonc-eslint-parser/compare/v2.4.2...v3.0.0)

##### Major Changes

- [#&#8203;266](https://github.com/ota-meshi/jsonc-eslint-parser/pull/266) [`6d1679d`](https://github.com/ota-meshi/jsonc-eslint-parser/commit/6d1679d4d9dfac8f28b1cc0519a2aee50671efaa) Thanks [@&#8203;copilot-swe-agent](https://github.com/apps/copilot-swe-agent)! - Drop support for Node.js versions older than 20.19.0. The new minimum supported versions are: ^20.19.0 || ^22.13.0 || >=24

- [#&#8203;268](https://github.com/ota-meshi/jsonc-eslint-parser/pull/268) [`e1c554a`](https://github.com/ota-meshi/jsonc-eslint-parser/commit/e1c554a1e16d09585fc2fb3d00c9739d330c3e3d) Thanks [@&#8203;copilot-swe-agent](https://github.com/apps/copilot-swe-agent)! - Change to ESM-only package. This is a breaking change that requires Node.js environments that support ESM.

</details>

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0MC42Mi4xIiwidXBkYXRlZEluVmVyIjoiNDAuNjIuMSIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOlsiZGVwZW5kZW5jaWVzIl19-->

Reviewed-on: #31
Co-authored-by: APF Portal Bot <jgautier.webdev+apf-portal-bot@gmail.com>
Co-committed-by: APF Portal Bot <jgautier.webdev+apf-portal-bot@gmail.com>
2026-05-05 23:32:06 +02:00
APF Portal Bot afc9474bed chore(deps): update dependency @types/node to v24 (#29)
CI / check (push) Successful in 2m26s
CI / commits (push) Has been skipped
CI / scan (push) Failing after 1m26s
CI / a11y (push) Successful in 2m25s
CI / perf (push) Successful in 3m41s
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/node) ([source](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node)) | devDependencies | major | [`20.19.39` -> `24.12.2`](https://renovatebot.com/diffs/npm/@types%2fnode/20.19.39/24.12.2) |

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0MC42Mi4xIiwidXBkYXRlZEluVmVyIjoiNDAuNjIuMSIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOlsiZGVwZW5kZW5jaWVzIl19-->

Reviewed-on: #29
Co-authored-by: APF Portal Bot <jgautier.webdev+apf-portal-bot@gmail.com>
Co-committed-by: APF Portal Bot <jgautier.webdev+apf-portal-bot@gmail.com>
2026-05-05 23:26:40 +02:00
APF Portal Bot c86804effa fix(deps): update dependency reflect-metadata to ^0.2.0 (#25)
CI / commits (push) Has been skipped
CI / scan (push) Failing after 2m24s
CI / check (push) Successful in 2m55s
CI / a11y (push) Successful in 1m5s
CI / perf (push) Successful in 3m41s
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [reflect-metadata](http://rbuckton.github.io/reflect-metadata) ([source](https://github.com/rbuckton/reflect-metadata)) | dependencies | minor | [`^0.1.13` -> `^0.2.0`](https://renovatebot.com/diffs/npm/reflect-metadata/0.1.14/0.2.2) |

---

### Release Notes

<details>
<summary>rbuckton/reflect-metadata (reflect-metadata)</summary>

### [`v0.2.2`](https://github.com/rbuckton/reflect-metadata/compare/v0.2.1...ca9650a46e3dfa32d0b384936eed539bd9109b12)

[Compare Source](https://github.com/rbuckton/reflect-metadata/compare/v0.2.1...ca9650a46e3dfa32d0b384936eed539bd9109b12)

### [`v0.2.1`](https://github.com/microsoft/reflect-metadata/releases/tag/v0.2.1)

[Compare Source](https://github.com/rbuckton/reflect-metadata/compare/v0.2.0...v0.2.1)

#### What's Changed

- Fix stack overflow crash in isProviderFor by [@&#8203;rbuckton](https://github.com/rbuckton) in https://github.com/rbuckton/reflect-metadata/pull/155
- Update main to v0.2.1 by [@&#8203;rbuckton](https://github.com/rbuckton) in https://github.com/rbuckton/reflect-metadata/pull/156

**Full Changelog**: https://github.com/rbuckton/reflect-metadata/compare/v0.2.0...v0.2.1

### [`v0.2.0`](https://github.com/microsoft/reflect-metadata/releases/tag/v0.2.0): reflect-metadata 0.2.0

[Compare Source](https://github.com/rbuckton/reflect-metadata/compare/v0.1.14...v0.2.0)

#### What's Changed

- Add /lite and /no-conflict exports by [@&#8203;rbuckton](https://github.com/rbuckton) in https://github.com/rbuckton/reflect-metadata/pull/144
- No dynamic evaluation in `/lite` mode by [@&#8203;rbuckton](https://github.com/rbuckton) in https://github.com/rbuckton/reflect-metadata/pull/149

**Full Changelog**: https://github.com/rbuckton/reflect-metadata/compare/v0.1.14...v0.2.0

</details>

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0MC42Mi4xIiwidXBkYXRlZEluVmVyIjoiNDAuNjIuMSIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOlsiZGVwZW5kZW5jaWVzIl19-->

Reviewed-on: #25
Co-authored-by: APF Portal Bot <jgautier.webdev+apf-portal-bot@gmail.com>
Co-committed-by: APF Portal Bot <jgautier.webdev+apf-portal-bot@gmail.com>
2026-05-05 23:17:25 +02:00
APF Portal Bot 4f6765c5c1 chore(deps): update dependency @oxc-project/runtime to ^0.129.0 (#22)
CI / commits (push) Has been skipped
CI / scan (push) Failing after 2m13s
CI / check (push) Successful in 3m10s
CI / a11y (push) Successful in 1m7s
CI / perf (push) Successful in 3m25s
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [@oxc-project/runtime](https://oxc.rs) ([source](https://github.com/oxc-project/oxc/tree/HEAD/npm/runtime)) | devDependencies | minor | [`^0.115.0` -> `^0.129.0`](https://renovatebot.com/diffs/npm/@oxc-project%2fruntime/0.115.0/0.129.0) |

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0MC42Mi4xIiwidXBkYXRlZEluVmVyIjoiNDAuNjIuMSIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOlsiZGVwZW5kZW5jaWVzIl19-->

Reviewed-on: #22
Co-authored-by: APF Portal Bot <jgautier.webdev+apf-portal-bot@gmail.com>
Co-committed-by: APF Portal Bot <jgautier.webdev+apf-portal-bot@gmail.com>
2026-05-05 23:07:09 +02:00