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>
87 lines
4.7 KiB
Markdown
87 lines
4.7 KiB
Markdown
# Scaffolding & code generation — how it works
|
|
|
|
How new code and boilerplate get generated in this repo. Three mechanisms, by design:
|
|
**runnable plop generators** for the pure-TS leaf patterns, **standalone node generators**
|
|
for the two committed-but-generated artifacts (`gen:api`, `gen:snippets`), and
|
|
**skill-driven recipes** (`.claude/skills/`) for the patterns too awkward to templatize.
|
|
The split is deliberate — [WP-43](../project/backlog/WP-43-scaffold-generators.md)
|
|
shipped only the two leaf generators and kept the harder patterns as skills.
|
|
|
|
## The rule: generate the boilerplate, not the judgement
|
|
|
|
Anything mechanical and shape-stable gets a generator (value objects, form machines — each
|
|
ships with its co-located spec so the "pure logic must have a spec" rule holds by default).
|
|
Anything that spans a template `{{ }}` clash, the C# backend, or a design decision stays a
|
|
**skill recipe** — a generator there adds little over the prose.
|
|
|
|
## Plop generators (runnable)
|
|
|
|
Config: `plopfile.mjs`; templates: `plop-templates/*.hbs` (Handlebars, prettier-ignored).
|
|
|
|
- **`npm run gen:value-object`** — branded value object + `parseX` parser + spec
|
|
("parse, don't validate"). Prompts for `context` + PascalCase `name`; emits
|
|
`src/app/{context}/domain/value-objects/{name}.ts` and its `.spec.ts`.
|
|
- **`npm run gen:form-machine`** — Elm-style Model/Msg/pure-`reduce` state machine + spec.
|
|
Prompts for `context` + `name`; emits `src/app/{context}/domain/{name}.machine.ts` + spec.
|
|
- **`npm run gen`** — interactive menu (pick a generator).
|
|
- Positional args skip the prompts: `npx plop value-object registratie KvkNummer`.
|
|
|
|
> ⚠ Each generated `$localize` id needs an English `<target>` in
|
|
> `src/locale/messages.en.xlf` before `npm run ci` — the localize gate fails otherwise.
|
|
|
|
## Typed API client — `npm run gen:api`
|
|
|
|
Regenerates the FE⇄BE seam from the backend's OpenAPI doc (config: `nswag.json`).
|
|
The script builds `BigRegister.Api`, emits `backend/swagger.json` via `dotnet swagger`, then
|
|
NSwag regenerates the Fetch-based client into
|
|
`src/app/shared/infrastructure/api-client.ts`. Both `swagger.json` and `api-client.ts` are
|
|
**committed and never hand-edited** — a CI drift gate fails if they're stale. Run it after
|
|
any backend contract change (see the `bff-endpoint` skill).
|
|
|
|
## Showcase snippets — `npm run gen:snippets`
|
|
|
|
`scripts/gen-snippets.mjs` extracts `// #region showcase:<name>` … `// #endregion` blocks
|
|
from real source files into `src/app/showcase/snippets.generated.ts`, so the teaching
|
|
showcase shows code identical to what ships. Committed, prettier-ignored, and drift-gated in
|
|
CI (`gen:snippets && git diff --exit-code`).
|
|
|
|
## Skill recipes (`.claude/skills/`)
|
|
|
|
Patterns kept as recipes rather than generators — invoke by name (`/<skill>`):
|
|
|
|
- **`new-feature`** — the house pipeline: domain → infrastructure → application → UI last.
|
|
- **`new-context`** — scaffold a new bounded context (folders, alias, boundary rule, route).
|
|
- **`value-object`** / **`form-machine`** — the two above; each points at its `gen:*` command.
|
|
- **`bff-endpoint`** — screen-shaped read: C# endpoint + DTO + `gen:api` + adapter + store +
|
|
`<app-async>`. Skill, not generator (spans backend + regen).
|
|
- **`mutation-command`** — a write op: adapter method + `submit-*` command returning `Result`.
|
|
- **`ui-component`** — shared atom/molecule/organism + Storybook story. Skill because the
|
|
Angular template `{{ }}` collides with Handlebars.
|
|
- **`new-ssp`** — bootstrap a whole new portal from this template (keep/strip/rename/re-seed).
|
|
- **`document-feature`** — ship docs in the same diff (this doc came from it).
|
|
- **`test-strategy`** — where specs live by layer.
|
|
|
|
## Bootstrapping a whole new SSP
|
|
|
|
Today the `new-ssp` skill is a **manual** fork-and-strip recipe (keep `shared/` + tooling,
|
|
strip the business contexts, re-seed the backend, re-run `gen:api`). See the skill for the
|
|
step-by-step and its GREEN-at-every-step verify gate.
|
|
|
|
## Not yet built
|
|
|
|
- **`gen:context`** ([WP-44](../project/backlog/WP-44-context-generator.md)) — a runnable
|
|
version of the `new-context` skill. Planned, not shipped.
|
|
- **`create-frontend`** ([WP-45](../project/backlog/WP-45-create-frontend-generator.md)) —
|
|
mechanised `new-ssp`. Planned, not shipped.
|
|
|
|
Don't assume either command exists — use the corresponding skill until they land.
|
|
|
|
## See also
|
|
|
|
- `plopfile.mjs` + `plop-templates/` — the leaf generators.
|
|
- `.claude/skills/` — the recipe library.
|
|
- `nswag.json` — the typed-client generation config.
|
|
- `scripts/gen-snippets.mjs` — the showcase snippet extractor.
|
|
- [ARCHITECTURE.md](architecture/ARCHITECTURE.md) — the NSwag client seam in context.
|
|
- [WP-43](../project/backlog/WP-43-scaffold-generators.md) — why only leaves got generators.
|