Files
atomic-design-poc/plopfile.mjs
T
ehoandClaude Sonnet 5 920ce138cb fix(scaffolding): rename create-ssp to create-frontend, fix 2 bugs it surfaced
Renamed scripts/create-ssp.mjs -> create-frontend.mjs (+ its WP-45 doc, npm
script, and every prose/command reference) since "ssp" reads as an acronym
where "create-frontend" says what it does.

Also fixes two real bugs found while running it for real during WP-61:
scripts/ci-local.sh was missing from RENAME_CONTENT_FILES (any --name'd
clone that keeps a backend would break `npm run ci`, still hardcoding
BigRegister.slnx), and plopfile.mjs's `gen:context` insertion into
.dependency-cruiser.js anchored on the `showcase: null,` line, which
create-ssp/create-frontend has already stripped by the time gen:context
runs in the same invocation — silently leaving a freshly scaffolded
context with no CONTEXT_ALLOWED fence entry at all. Re-anchored on the
`const CONTEXT_ALLOWED = {` line instead, which never moves.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-31 23:26:28 +02:00

140 lines
5.3 KiB
JavaScript

/**
* Runnable house-pattern generators (WP-43). `npm run gen <name>` or the `gen:*` scripts.
* These emit the boilerplate the skills used to describe by hand; each generated unit ships
* with its co-located spec so the "domain/pure logic must have a spec" rule holds by default.
*
* Generators here are the pure-TS patterns (no Angular-template escaping): value objects and
* form/wizard state machines. The UI-component and bff-endpoint patterns stay skill-driven —
* they span the template `{{ }}` syntax / the C# backend + `gen:api` regen, where a generator
* adds little over the recipe. `context` (WP-44) mechanises the `new-context` skill's manual
* multi-file edit: folders + tsconfig alias + boundary entry + lazy route.
*
* Positional args skip the prompts, e.g. `npx plop value-object registratie KvkNummer`.
*/
export default function (plop) {
const CONTEXT_PROMPT = {
type: 'input',
name: 'context',
message: 'Context (shared | auth | registratie | herregistratie | brief | beheer):',
};
const remindEnXlf = () =>
'⚠ The generated $localize id needs an English <target> in src/locale/messages.en.xlf before `npm run ci` (the localize gate fails otherwise).';
plop.setGenerator('value-object', {
description: 'Branded value object + parser + spec ("parse, don\'t validate")',
prompts: [
CONTEXT_PROMPT,
{ type: 'input', name: 'name', message: 'Name (PascalCase, e.g. KvkNummer):' },
],
actions: [
{
type: 'add',
path: 'src/app/{{context}}/domain/value-objects/{{kebabCase name}}.ts',
templateFile: 'plop-templates/value-object.hbs',
},
{
type: 'add',
path: 'src/app/{{context}}/domain/value-objects/{{kebabCase name}}.spec.ts',
templateFile: 'plop-templates/value-object.spec.hbs',
},
remindEnXlf,
],
});
plop.setGenerator('form-machine', {
description: 'Elm-style form/wizard state machine (Model/Msg/pure reduce) + spec',
prompts: [
CONTEXT_PROMPT,
{ type: 'input', name: 'name', message: 'Machine name (PascalCase, e.g. Adreswijziging):' },
],
actions: [
{
type: 'add',
path: 'src/app/{{context}}/domain/{{kebabCase name}}.machine.ts',
templateFile: 'plop-templates/form-machine.hbs',
},
{
type: 'add',
path: 'src/app/{{context}}/domain/{{kebabCase name}}.machine.spec.ts',
templateFile: 'plop-templates/form-machine.spec.hbs',
},
remindEnXlf,
],
});
plop.setGenerator('context', {
description:
'Scaffold a bounded context: folders + tsconfig alias + boundary entry + lazy route',
prompts: [
{
type: 'input',
name: 'name',
message: 'Context name (lowercase, single Dutch ubiquitous term, e.g. vergunning):',
},
],
actions: [
{
type: 'add',
path: 'src/app/{{kebabCase name}}/ui/{{kebabCase name}}.page.ts',
templateFile: 'plop-templates/context.page.hbs',
},
{
type: 'add',
path: 'src/app/{{kebabCase name}}/domain/.gitkeep',
templateFile: 'plop-templates/gitkeep.hbs',
},
{
type: 'add',
path: 'src/app/{{kebabCase name}}/application/.gitkeep',
templateFile: 'plop-templates/gitkeep.hbs',
},
{
type: 'add',
path: 'src/app/{{kebabCase name}}/infrastructure/.gitkeep',
templateFile: 'plop-templates/gitkeep.hbs',
},
{
type: 'add',
path: 'src/app/{{kebabCase name}}/contracts/.gitkeep',
templateFile: 'plop-templates/gitkeep.hbs',
},
{
// tsconfig path alias — inserted right after the `"paths": {` line so it's stable
// across repeated runs regardless of what's already been added.
type: 'modify',
path: 'tsconfig.json',
pattern: /"paths": \{\n/,
template: '"paths": {\n "@{{kebabCase name}}/*": ["src/app/{{kebabCase name}}/*"],\n',
},
{
// Boundary entry (WP-38's single source of truth) — inserted right after the
// `const CONTEXT_ALLOWED = {` opening line, which never moves. (Previously anchored
// on the `showcase: null,` line, but create-frontend.mjs strips that entry before running
// this generator, so the modify silently no-op'd and the new context got no fence at
// all — dep:check stayed green with the boundary rule simply missing.)
type: 'modify',
path: '.dependency-cruiser.js',
pattern: /const CONTEXT_ALLOWED = \{\n/,
template: 'const CONTEXT_ALLOWED = {\n {{kebabCase name}}: [],\n',
},
{
// Lazy route — inserted right before the catch-all, which never moves.
type: 'modify',
path: 'src/app/app.routes.ts',
pattern: " { path: '**', redirectTo: 'login' },",
template: ` {
path: '{{kebabCase name}}',
canActivate: [authGuard],
loadComponent: () =>
import('@{{kebabCase name}}/ui/{{kebabCase name}}.page').then(
(m) => m.{{pascalCase name}}Page,
),
},
{ path: '**', redirectTo: 'login' },`,
},
() =>
'Next: npm run dep:check && npm run lint && npm run build to verify, then build the first feature with the new-feature skill.',
],
});
}