chore(workspace): tsconfig composite refs + axe-linter false-positive config #147

Merged
julien merged 1 commits from chore/tsconfig-composite-axe-linter into main 2026-05-15 12:25:22 +02:00
Owner

Summary

Three editor-noise sources flagged by the VS Code TypeScript service + the Deque axe Linter extension, each tamed at the right layer. No runtime behaviour change.

Source Fix
TS6306 — Referenced project libs/{shared/ui,shared/state,feature/auth} must have composite: true. Nx 22's lib generator doesn't emit composite; modern VS Code TS service flags it. Add composite: true to each lib's tsconfig.lib.json, let nx sync redirect consumer references in apps/portal-{shell,admin}/tsconfig.app.json to point at the .lib.json directly.
TS6504moduleResolution: "node" / "node10" deprecated, removed in TS 7.0. Two hits on the BFF tsconfigs. Add ignoreDeprecations: "5.0" on apps/portal-bff/tsconfig.{app,spec}.json — the opt-out knob the diagnostic itself suggests. A proper migration to nodenext/node16 is a separate chantier.
axe-core/list (WCAG 1.3.1)<ul> "must only directly contain <li>, <script>, or <template>" — fires on Angular 17+ @for blocks inside lists. Pure static-linter limitation; rendered DOM is fine. New .axe-linter.yml at repo root: global-disable: [list].

What lands

composite: true on lib .lib.json

libs/shared/ui/tsconfig.lib.json, libs/shared/state/tsconfig.lib.json, libs/feature/auth/tsconfig.lib.json get composite: true added. nx sync then automatically rewrites consumer references:

- "path": "../../libs/shared/ui"
+ "path": "../../libs/shared/ui/tsconfig.lib.json"

in apps/portal-shell/tsconfig.app.json and apps/portal-admin/tsconfig.app.json. Semantically cleaner — the app references the lib's actual compile config (which produces the .d.ts it consumes), not the lib's solution-style root tsconfig.

Earlier attempt — composite on the solution tsconfig.json — silently broke vitest: the Angular Vite plugin chokes on a composite project with files: [] / include: [] and falls through, leaving spec files loaded but tests not registered ("No test suite found in file"). Moving composite to .lib.json (the project that actually has inputs) fixes the contract without poking the plugin.

ignoreDeprecations: "5.0"

apps/portal-bff/tsconfig.app.json and apps/portal-bff/tsconfig.spec.json — silences Option 'moduleResolution=node10' is deprecated and will stop functioning in TypeScript 7.0. The diagnostic suggests "6.0" as the value, but TS 5.9 (our pinned version) only accepts "5.0"; using "6.0" results in TS5103: Invalid value for '--ignoreDeprecations' and breaks every spec. "5.0" is the current-gen accepted value.

The deprecation is real — TS 7.0 will drop both "node" and "node10" moduleResolution modes. The migration target is moduleResolution: "nodenext" paired with matching module: "nodenext", but that interacts non-trivially with Nest's CommonJS pipeline and the BFF's import semantics. Out of scope for a drive-by fix; we'll handle it as a dedicated chantier when TS 7.0 lands on the roadmap.

.axe-linter.yml

New file at repo root:

global-disable:
  - list

The Deque axe Linter VS Code extension reads .axe-linter.yml at workspace root. The list rule (WCAG 1.3.1) fires false positives on Angular 17+ control-flow syntax — @for (item of list; ...) { <li>… } looks like a non-<li> child of <ul> to a static HTML scanner. The Angular compiler erases those tokens at build time; the rendered DOM is compliant. CI accessibility coverage is provided by axe-playwright per ADR-0016 §"Tooling" — it runs against the rendered DOM and is unaffected by this disable.

Notes for the reviewer

  • Why not composite: true on every lib? Per CLAUDE.md "no premature abstractions" — libs/shared/{tokens,util} are not currently referenced by any tsconfig.app.json, so they don't trigger TS6306. Adding composite to them would be future-proofing without a current consumer. When a consumer reference is added, the same one-line fix lands then.
  • Why not migrate moduleResolution properly? The BFF runs on Nest's CommonJS pipeline; nodenext brings stricter ESM resolution (.js extensions in imports, package exports map enforcement) that ripples through. Not a 5-minute change. The ignoreDeprecations knob is the textbook defer mechanism for exactly this case.
  • Why disable list globally rather than per-file? The rule's false-positive pattern (<ul><@for> / <ol><@for>) applies workspace-wide; we use @for consistently across portal-shell + portal-admin. Per-file disables would multiply as new templates land. axe-playwright remains the authoritative check on the rule.

Test plan

  • pnpm nx run-many -t lint test build --projects=portal-shell,portal-admin,portal-bff,shared-ui,shared-state,feature-auth — 18/18 tasks pass. 517 specs green across the affected projects.
  • pnpm nx sync:check — workspace in sync after the changes; running sync again is a no-op.
  • Editor smoke — reopen the workspace in VS Code: the TS6306 errors on lib tsconfig.json files should be gone, the two moduleResolution=node10 deprecation lines on BFF tsconfigs should be silenced, and the list rule under sidebar.html (portal-admin) should no longer surface.
## Summary Three editor-noise sources flagged by the VS Code TypeScript service + the Deque axe Linter extension, each tamed at the right layer. No runtime behaviour change. | Source | Fix | | --- | --- | | **TS6306** — Referenced project `libs/{shared/ui,shared/state,feature/auth}` must have `composite: true`. Nx 22's lib generator doesn't emit `composite`; modern VS Code TS service flags it. | Add `composite: true` to each lib's `tsconfig.lib.json`, let `nx sync` redirect consumer references in `apps/portal-{shell,admin}/tsconfig.app.json` to point at the `.lib.json` directly. | | **TS6504** — `moduleResolution: "node"` / `"node10"` deprecated, removed in TS 7.0. Two hits on the BFF tsconfigs. | Add `ignoreDeprecations: "5.0"` on `apps/portal-bff/tsconfig.{app,spec}.json` — the opt-out knob the diagnostic itself suggests. A proper migration to `nodenext`/`node16` is a separate chantier. | | **axe-core/list (WCAG 1.3.1)** — `<ul>` "must only directly contain `<li>`, `<script>`, or `<template>`" — fires on Angular 17+ `@for` blocks inside lists. Pure static-linter limitation; rendered DOM is fine. | New `.axe-linter.yml` at repo root: `global-disable: [list]`. | ## What lands ### `composite: true` on lib `.lib.json` [`libs/shared/ui/tsconfig.lib.json`](libs/shared/ui/tsconfig.lib.json), [`libs/shared/state/tsconfig.lib.json`](libs/shared/state/tsconfig.lib.json), [`libs/feature/auth/tsconfig.lib.json`](libs/feature/auth/tsconfig.lib.json) get `composite: true` added. `nx sync` then automatically rewrites consumer references: ```diff - "path": "../../libs/shared/ui" + "path": "../../libs/shared/ui/tsconfig.lib.json" ``` in [`apps/portal-shell/tsconfig.app.json`](apps/portal-shell/tsconfig.app.json) and [`apps/portal-admin/tsconfig.app.json`](apps/portal-admin/tsconfig.app.json). Semantically cleaner — the app references the lib's actual compile config (which produces the `.d.ts` it consumes), not the lib's solution-style root tsconfig. **Earlier attempt — composite on the solution `tsconfig.json` — silently broke `vitest`**: the Angular Vite plugin chokes on a composite project with `files: []` / `include: []` and falls through, leaving spec files loaded but tests not registered (`"No test suite found in file"`). Moving `composite` to `.lib.json` (the project that actually has inputs) fixes the contract without poking the plugin. ### `ignoreDeprecations: "5.0"` [`apps/portal-bff/tsconfig.app.json`](apps/portal-bff/tsconfig.app.json) and [`apps/portal-bff/tsconfig.spec.json`](apps/portal-bff/tsconfig.spec.json) — silences `Option 'moduleResolution=node10' is deprecated and will stop functioning in TypeScript 7.0`. The diagnostic suggests `"6.0"` as the value, but TS 5.9 (our pinned version) only accepts `"5.0"`; using `"6.0"` results in `TS5103: Invalid value for '--ignoreDeprecations'` and breaks every spec. `"5.0"` is the current-gen accepted value. The deprecation is real — TS 7.0 will drop both `"node"` and `"node10"` `moduleResolution` modes. The migration target is `moduleResolution: "nodenext"` paired with matching `module: "nodenext"`, but that interacts non-trivially with Nest's CommonJS pipeline and the BFF's import semantics. Out of scope for a drive-by fix; we'll handle it as a dedicated chantier when TS 7.0 lands on the roadmap. ### `.axe-linter.yml` New file at repo root: ```yaml global-disable: - list ``` The Deque axe Linter VS Code extension reads `.axe-linter.yml` at workspace root. The `list` rule (WCAG 1.3.1) fires false positives on Angular 17+ control-flow syntax — `@for (item of list; ...) { <li>… }` looks like a non-`<li>` child of `<ul>` to a static HTML scanner. The Angular compiler erases those tokens at build time; the rendered DOM is compliant. CI accessibility coverage is provided by `axe-playwright` per [ADR-0016 §"Tooling"](docs/decisions/0016-accessibility-baseline-wcag-aa-targeted-aaa.md) — it runs against the rendered DOM and is unaffected by this disable. ## Notes for the reviewer - **Why not `composite: true` on every lib?** Per CLAUDE.md "no premature abstractions" — `libs/shared/{tokens,util}` are not currently referenced by any `tsconfig.app.json`, so they don't trigger TS6306. Adding `composite` to them would be future-proofing without a current consumer. When a consumer reference is added, the same one-line fix lands then. - **Why not migrate `moduleResolution` properly?** The BFF runs on Nest's CommonJS pipeline; `nodenext` brings stricter ESM resolution (`.js` extensions in imports, package `exports` map enforcement) that ripples through. Not a 5-minute change. The `ignoreDeprecations` knob is the textbook defer mechanism for exactly this case. - **Why disable `list` globally rather than per-file?** The rule's false-positive pattern (`<ul><@for>` / `<ol><@for>`) applies workspace-wide; we use `@for` consistently across `portal-shell` + `portal-admin`. Per-file disables would multiply as new templates land. axe-playwright remains the authoritative check on the rule. ## Test plan - [x] `pnpm nx run-many -t lint test build --projects=portal-shell,portal-admin,portal-bff,shared-ui,shared-state,feature-auth` — 18/18 tasks pass. **517 specs green** across the affected projects. - [x] `pnpm nx sync:check` — workspace in sync after the changes; running `sync` again is a no-op. - [ ] Editor smoke — reopen the workspace in VS Code: the TS6306 errors on lib `tsconfig.json` files should be gone, the two `moduleResolution=node10` deprecation lines on BFF tsconfigs should be silenced, and the `list` rule under `sidebar.html` (`portal-admin`) should no longer surface.
julien added 1 commit 2026-05-15 12:24:29 +02:00
chore(workspace): tsconfig composite refs + axe-linter false-positive config
CI / scan (pull_request) Successful in 2m20s
CI / commits (pull_request) Successful in 2m42s
CI / check (pull_request) Successful in 3m38s
CI / a11y (pull_request) Successful in 1m19s
CI / perf (pull_request) Successful in 5m24s
879390f016
Three editor-noise sources, three matching tame:

* Add `composite: true` to lib tsconfig.lib.json files (shared/ui,
  shared/state, feature/auth) and let `nx sync` redirect consumer
  references in apps/portal-{shell,admin}/tsconfig.app.json to point
  at the .lib.json directly. Silences TS6306 ("Referenced project
  must have setting composite: true") that the TS service raises on
  the project references our Nx 22 lib generator emits.

* Add `ignoreDeprecations: "5.0"` on apps/portal-bff/tsconfig.{app,
  spec}.json. Both inherit `moduleResolution: node` / `node10` which
  TS 7.0 will remove; the diagnostic itself suggests the opt-out
  knob. The TS 5.9 we run only accepts "5.0" as the value (the
  warning text mentions "6.0" but TS rejects it). Migration to
  nodenext/node16 is a separate chantier — too entangled with Nest
  CJS semantics for a drive-by.

* Add a repo-level .axe-linter.yml that disables the `list` rule
  globally. axe Linter (VS Code) doesn't understand Angular 17+
  `@for` blocks and flags `@for (...) { <li>… }` as a non-<li>
  child of <ul>/<ol>. Compiler erases the syntax at build time;
  axe-playwright (ADR-0016) operates on the rendered DOM and is
  unaffected.
julien merged commit 6120471b66 into main 2026-05-15 12:25:22 +02:00
julien deleted branch chore/tsconfig-composite-axe-linter 2026-05-15 12:25:24 +02:00
Sign in to join this conversation.
No Reviewers
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: julien/apf_portal#147