feat(dx): gen:context generator (WP-44)
npm run gen:context scaffolds a bounded context: folders + starter page, the @<ctx>/* tsconfig alias, a dependency-cruiser boundary entry, and a lazy authGuard route. Refactors .dependency-cruiser.js's per-context contextRule calls into a single CONTEXT_ALLOWED map that every rule derives from, so adding a context is really one config entry (verified behavior-preserving: same dep:check counts, same graph output). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
+31
-19
@@ -8,16 +8,37 @@
|
||||
// Allowed cross-context edges: everyone → shared; herregistratie → registratie; showcase → *
|
||||
// (the sanctioned teaching page). Nobody imports showcase.
|
||||
|
||||
const FEATURES = 'auth|registratie|herregistratie|brief|beheer|showcase';
|
||||
// Single source of truth for bounded-context boundaries: each entry maps a context name to the
|
||||
// OTHER contexts it may additionally import (besides itself + shared). `showcase` maps to `null`
|
||||
// — sanctioned to import every context (the teaching page); nothing else may import it. Add a
|
||||
// context here — nowhere else — when scaffolding one (see `gen:context`, WP-44); FEATURES and
|
||||
// every contextRule below are derived from this object.
|
||||
const CONTEXT_ALLOWED = {
|
||||
auth: [],
|
||||
registratie: [],
|
||||
herregistratie: ['registratie'], // the one sanctioned cross-feature edge
|
||||
brief: [],
|
||||
beheer: [],
|
||||
showcase: null,
|
||||
};
|
||||
|
||||
/** A context may import shared + itself; this lists the OTHER contexts it may NOT import. */
|
||||
const contextRule = (name, from, forbiddenContexts) => ({
|
||||
name,
|
||||
comment: `${from} may depend only on its allowed contexts (+ shared). See CLAUDE.md §1.`,
|
||||
severity: 'error',
|
||||
from: { path: `^src/app/${from}/` },
|
||||
to: { path: `^src/app/(${forbiddenContexts})/` },
|
||||
});
|
||||
const FEATURES = Object.keys(CONTEXT_ALLOWED).join('|');
|
||||
|
||||
/** A context may import shared + itself + its allowed list; forbidden = every other context. */
|
||||
const contextRule = (from) => {
|
||||
const allowed = CONTEXT_ALLOWED[from];
|
||||
if (allowed === null) return null; // unrestricted (showcase) — no rule to generate
|
||||
const forbidden = Object.keys(CONTEXT_ALLOWED)
|
||||
.filter((name) => name !== from && !allowed.includes(name))
|
||||
.join('|');
|
||||
return {
|
||||
name: `${from}-scope`,
|
||||
comment: `${from} may depend only on its allowed contexts (+ shared). See CLAUDE.md §1.`,
|
||||
severity: 'error',
|
||||
from: { path: `^src/app/${from}/` },
|
||||
to: { path: `^src/app/(${forbidden})/` },
|
||||
};
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
forbidden: [
|
||||
@@ -29,16 +50,7 @@ module.exports = {
|
||||
from: { path: '^src/app/shared/', pathNot: '^src/app/shared/ui/debug-state/' },
|
||||
to: { path: `^src/app/(${FEATURES})/` },
|
||||
},
|
||||
contextRule('auth-only-shared', 'auth', 'registratie|herregistratie|brief|beheer|showcase'),
|
||||
contextRule(
|
||||
'registratie-only-shared',
|
||||
'registratie',
|
||||
'auth|herregistratie|brief|beheer|showcase',
|
||||
),
|
||||
// herregistratie MAY import registratie (+ shared) — the one sanctioned cross-feature edge.
|
||||
contextRule('herregistratie-scope', 'herregistratie', 'auth|brief|beheer|showcase'),
|
||||
contextRule('brief-only-shared', 'brief', 'auth|registratie|herregistratie|beheer|showcase'),
|
||||
contextRule('beheer-only-shared', 'beheer', 'auth|registratie|herregistratie|brief|showcase'),
|
||||
...Object.keys(CONTEXT_ALLOWED).map(contextRule).filter(Boolean),
|
||||
// showcase/ is exempt (reads every context by design); nothing imports it — covered by the
|
||||
// rules above each forbidding `→ showcase`.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user