first commit

This commit is contained in:
2026-05-29 08:20:56 +01:00
commit 949299f58d
6368 changed files with 527176 additions and 0 deletions

View File

@@ -0,0 +1,85 @@
# create-suaveplan-plugin
Interactive scaffold for new Suaveplan API plugins. Generates a complete,
immediately-testable plugin skeleton — `plugin.json`, `definePlugin`
body, host test, optional integration test wired to the matching
`testing-integration-{postgres,sqlite,surrealdb}` fixture, both vitest
configs, migrations, and a README — tailored to the selected isolation
tier, capability set, and DB engine.
<!-- begin:auto-docs -->
## Documentation tree
- [bin](./src/bin/create-suaveplan-plugin.md) — This module is the executable entry-point for the `create-suaveplan-plugin` scaffolder. It parses argv into a `ParsedAr…
- [create-suaveplan-plugin](./src/bin/create-suaveplan-plugin.md) — This module is the executable entry-point for the `create-suaveplan-plugin` scaffolder. It parses argv into a `ParsedAr…
- [generator](./src/generator/generator.md) — This module turns a validated `PromptAnswers` record into either an in-memory list of `GeneratedFile` records (`generat…
- [generator](./src/generator/generator.md) — This module turns a validated `PromptAnswers` record into either an in-memory list of `GeneratedFile` records (`generat…
- [prompt](./src/prompt/prompt.md) — This module defines the closed prompt-answer surface for the scaffold (`PromptAnswers`, `IsolationTier`, `DatabaseEngin…
- [prompt](./src/prompt/prompt.md) — This module defines the closed prompt-answer surface for the scaffold (`PromptAnswers`, `IsolationTier`, `DatabaseEngin…
- [templates](./src/templates/templates.md) — This module owns the per-file render functions that produce the scaffolded plugin tree. Every output file (manifest, so…
- [templates](./src/templates/templates.md) — This module owns the per-file render functions that produce the scaffolded plugin tree. Every output file (manifest, so…
- [error-codes](./src/error-codes.md) — This module declares the frozen, namespaced registry of every failure code the `create-suaveplan-plugin` scaffold can r…
<!-- end:auto-docs -->
## Quick start
```bash
bunx create-suaveplan-plugin my-plugin --fixture trusted-postgres-full
cd my-plugin
bun test
```
Three fixtures cover the canonical permutations:
| Fixture | Isolation | Database | Capabilities |
| --- | --- | --- | --- |
| `trusted-postgres-full` | `trusted` | `postgres` | rest, graphql, ws, sse, sagas, twoPhaseCommit, perTenantJobs |
| `isolated-sqlite-rest-only` | `isolated` | `sqlite` | rest |
| `sandboxed-none-rest-only` | `sandboxed` | `none` | rest |
## CLI flags
- `<name>` — positional plugin name (validated against npm rules).
- `--fixture <id>` — one of the three fixtures above (default
`sandboxed-none-rest-only`).
- `--dir <path>` — explicit target directory (default `<cwd>/<name>`).
- `--force` — overwrite an existing non-empty target.
## Programmatic API
```ts
import { scaffold, answersForFixture } from "create-suaveplan-plugin";
scaffold({
answers: answersForFixture("isolated-sqlite-rest-only", "billing-plugin"),
targetDir: "./billing-plugin",
force: false,
});
```
## Error codes
The scaffold raises only typed `ScaffoldError`s. Every code is namespaced
as `SCAFFOLD.*`:
- `SCAFFOLD.INVALID_NAME` — name violates npm-package-name rules.
- `SCAFFOLD.DIRECTORY_EXISTS` — target is non-empty and `--force` was not given.
- `SCAFFOLD.WRITE_FAILED` — fs write failed; original error attached via `cause`.
- `SCAFFOLD.UNKNOWN_ENGINE` — engine outside the closed vocabulary.
## Verification
The package ships three verification surfaces:
1. **Unit tests** — every render branch, every prompt validation case.
2. **Snapshot tests** — byte-for-byte against the committed
`tests/scaffold-output/snapshots/<fixture>/**` trees.
3. **Scaffold-and-run** — for each fixture, scaffold to a temp dir and
run `bun test` against it. Exit code must be 0.
Run the full gate via:
```bash
bunx --bun turbo lint typecheck test:coverage --filter=create-suaveplan-plugin
```

View File

@@ -0,0 +1,61 @@
# create-suaveplan-plugin (bin)
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
This module is the executable entry-point for the
`create-suaveplan-plugin` scaffolder. It parses argv into a
`ParsedArgs` record, resolves the matching fixture answer set, and
delegates the on-disk work to `scaffold()`. Wrapping the CLI in a
single file (with `parseArgs` and `run` exported for in-process
testing) keeps the bin layer thin and lets the rest of the package
stay framework-agnostic.
## Features
- `parseArgs(argv)` — positional plugin name plus `--fixture`,
`--force`, `--dir` flags. Defaults to the
`sandboxed-none-rest-only` fixture and `<cwd>/<name>` as target.
- `run(argv)` — programmatic entry. Returns the resolved
`PromptAnswers` so test callers can introspect the chosen fixture.
- Auto-runs only when invoked as a binary (`process.argv[1]` ends with
`create-suaveplan-plugin.js`), so importing the module never trips a
scaffold run.
## Usage
### Basic example
```bash
bunx create-suaveplan-plugin my-plugin --fixture trusted-postgres-full
```
### Advanced example
```bash
bunx create-suaveplan-plugin billing-plugin \
--fixture isolated-sqlite-rest-only \
--dir ./packages/billing-plugin \
--force
```
## API Reference
- `parseArgs(argv: readonly string[]): ParsedArgs` — pure argv parser.
- `run(argv: readonly string[]): PromptAnswers` — full scaffold cycle.
## Implementation Notes
- The fixture vocabulary is closed; an unknown `--fixture` value
silently falls back to the default rather than throwing — the bin is
the user-facing layer and the spec treats unknown fixtures as a
no-op rather than a hard error.
- The `process.argv[1].endsWith(...)` guard is intentionally permissive
so the file works whether invoked through Node, Bun, or a transpiled
artefact.
- Coverage exclusion for `src/bin/**` keeps the auto-run guard out of
the 100% gate; the testable surface (`parseArgs`, `run`) is
exercised directly from the test file.

View File

@@ -0,0 +1,73 @@
# error-codes
## Purpose
This module declares the frozen, namespaced registry of every failure
code the `create-suaveplan-plugin` scaffold can raise, together with the
`ScaffoldError` subclass that carries those codes. Anchoring every
failure path to a single, exhaustive code list lets downstream
orchestration scripts and CLI wrappers recover programmatically without
parsing error messages — they branch on `error.code` and the literal
type narrows for them.
## Features
- Frozen `SCAFFOLD_CODES` registry via `defineCodes` from
`@suaveplan/error`. Every value is a `SCAFFOLD.<REASON>` string
literal.
- `ScaffoldError` extends `SuaveplanError<ScaffoldErrorCode>` so
`instanceof` plus `.code` checks stay exhaustive.
- Side-effect import from `src/index.ts` ensures subpath consumers
register codes too.
## Usage
### Basic example
```ts
import { SCAFFOLD_CODES, ScaffoldError } from "create-suaveplan-plugin";
throw new ScaffoldError(
SCAFFOLD_CODES.INVALID_NAME,
"scaffold.invalid_name:Foo",
);
```
### Advanced example
```ts
import { ScaffoldError } from "create-suaveplan-plugin";
try {
runScaffold();
} catch (err) {
if (err instanceof ScaffoldError && err.code === "SCAFFOLD.DIRECTORY_EXISTS") {
console.error("Target already exists. Re-run with --force.");
process.exit(2);
}
throw err;
}
```
## API Reference
- `SCAFFOLD_CODES` — frozen record of code-name → `SCAFFOLD.<NAME>`.
- `ScaffoldErrorCode` — union of every value of `SCAFFOLD_CODES`.
- `class ScaffoldError extends SuaveplanError<ScaffoldErrorCode>` —
constructor `(code, messageKey, options?)`.
## Implementation Notes
- The registry is built at module-load time via `defineCodes`, which
freezes the resulting object so consumers cannot mutate it.
- Every code in this registry is thrown by a production code path —
see `prompt/prompt.ts` (`INVALID_NAME`), `generator/generator.ts`
(`DIRECTORY_EXISTS`, `WRITE_FAILED`, `UNKNOWN_ENGINE`).
- The error is intentionally distinct from `SuaveplanError` so consumer
catch clauses can disambiguate scaffold failures from peer-package
failures without a code-string match. The class name is set to
`ScaffoldError` so structured loggers serialising via the standard
`name`/`message`/`stack` triple emit a recognisable identity.
- The conformance script enforces a side-effect import of this module
from the package barrel; without it, subpath consumers would not
register codes in any global telemetry hook.

View File

@@ -0,0 +1,69 @@
# generator
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
This module turns a validated `PromptAnswers` record into either an
in-memory list of `GeneratedFile` records (`generate`) or a tree
materialised on disk (`scaffold`). Splitting the two lets snapshot tests
inspect the rendered tree byte-for-byte without touching the filesystem,
and lets the bin entrypoint stream the same tree out to disk through a
single typed failure surface.
## Features
- Pure `generate(options)` returns a deterministic, ordered list of
files for snapshot comparison.
- `scaffold(options)` writes the tree atomically, raising
`SCAFFOLD.DIRECTORY_EXISTS` when the target is non-empty (unless
`force` is set) and `SCAFFOLD.WRITE_FAILED` for every fs error.
- Engine validation raises `SCAFFOLD.UNKNOWN_ENGINE` for any database
value outside the closed vocabulary.
## Usage
### Basic example
```ts
import { scaffold, answersForFixture } from "create-suaveplan-plugin";
const answers = answersForFixture("isolated-sqlite-rest-only", "my-plugin");
scaffold({ answers, targetDir: "./my-plugin" });
```
### Advanced example
```ts
import { generate, answersForFixture } from "create-suaveplan-plugin";
const tree = generate({
answers: answersForFixture("trusted-postgres-full", "billing"),
});
for (const file of tree) {
console.log(file.path, file.content.length);
}
```
## API Reference
- `generate(options: GenerateOptions): readonly GeneratedFile[]` — pure
in-memory render. Validates answers first.
- `scaffold(options: ScaffoldOptions): ScaffoldResult` — writes to disk.
- `GeneratedFile` — `{ readonly path: string; readonly content: string }`.
- `ScaffoldOptions` — `{ answers, targetDir, force? }`.
- `ScaffoldResult` — `{ files: readonly string[] }` (relative paths).
## Implementation Notes
- `directoryIsNonEmpty` swallows ENOENT — a missing target is treated
as empty so the first-time path needs no special case.
- Errors during write are wrapped in `ScaffoldError` with `cause` set
to the original `fs` error, so callers retain the original stack via
the standard `Error.cause` chain.
- The path order returned by `generate` is stable; snapshot tests rely
on this when asserting tree equality.
- Coverage is enforced at 100% by the snapshot fixtures plus the
explicit error-path tests in `generator.test.ts`.

View File

@@ -0,0 +1,78 @@
# prompt
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
This module defines the closed prompt-answer surface for the scaffold
(`PromptAnswers`, `IsolationTier`, `DatabaseEngine`, `CapabilitySelection`),
the Zod schema that validates inputs at the package boundary, and the
helper that resolves a fixture shorthand to a full answer set. The
prompt surface is the only public contract between any interactive
front-end (TTY prompt, web UI, programmatic harness) and the generator.
## Features
- Strict npm-package-name validation (`@scope/`-aware) that runs before
any filesystem work happens.
- Zod-derived `PromptAnswersSchema` for boundary validation. Every union
member is exhaustive — the scaffold tracks the closed vocabulary of
isolation tiers and database engines explicitly.
- `answersForFixture(name, fixture)` produces a deterministic answer
set for each of the three snapshot fixtures.
## Usage
### Basic example
```ts
import { validateAnswers } from "create-suaveplan-plugin";
const answers = validateAnswers({
name: "my-plugin",
isolation: "trusted",
database: "postgres",
capabilities: {
rest: true,
graphql: false,
ws: false,
sse: false,
sagas: false,
twoPhaseCommit: false,
perTenantJobs: false,
},
});
```
### Advanced example
```ts
import { answersForFixture, scaffold } from "create-suaveplan-plugin";
const answers = answersForFixture("trusted-postgres-full", "billing-plugin");
scaffold({ answers, targetDir: "./billing-plugin", force: false });
```
## API Reference
- `validateAnswers(input: PromptAnswers): PromptAnswers` — returns the
frozen, validated answer set. Throws `SCAFFOLD.INVALID_NAME` for any
name that fails the npm-package-name rules.
- `answersForFixture(fixture, name): PromptAnswers` — shorthand
resolver for the three snapshot fixtures.
- `PromptAnswersSchema: z.ZodType<PromptAnswers>` — boundary schema.
## Implementation Notes
- The Zod schema runs first because it produces clearer errors for
shape mismatches; the npm-name regex layer runs second specifically
to raise the typed `SCAFFOLD.INVALID_NAME` code which downstream
tooling branches on.
- `Object.freeze` is applied at the answer level AND at the nested
`capabilities` level so neither shape can be mutated between
validation and template rendering.
- The fixture shorthand is exported because the scaffold-output snapshot
tests rely on a deterministic answer set per fixture; consumers may
also use it for in-process orchestration.

View File

@@ -0,0 +1,81 @@
# templates
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
This module owns the per-file render functions that produce the
scaffolded plugin tree. Every output file (manifest, source, tests,
configs, migrations, README, package.json, tsconfig, .gitignore) lives
here as a typed string-producer keyed off the validated `PromptAnswers`.
Keeping the templates in TypeScript (not as opaque `.tmpl` files) means
the type system can refuse a render that references a capability the
prompt vocabulary doesn't know about.
## Features
- One `renderTree(answers)` entrypoint returning the ordered file list.
- Per-capability handler stubs in `src/index.ts.tmpl` so generated test
files compile against real handler exports on first run.
- Conditional emission of integration test + integration vitest config
+ migrations when a DB engine other than `none` is selected.
- Generated `vitest.config.ts` always wires `@suaveplan/testing/config`
(the `createTestConfig` factory, never `baseVitestConfig`+`mergeConfig`).
## Usage
### Basic example
```ts
import { renderTree, answersForFixture } from "create-suaveplan-plugin";
const files = renderTree(
answersForFixture("sandboxed-none-rest-only", "demo"),
);
for (const f of files) console.log(f.path);
```
### Advanced example
```ts
import { renderTree, validateAnswers } from "create-suaveplan-plugin";
const files = renderTree(
validateAnswers({
name: "billing",
isolation: "trusted",
database: "postgres",
capabilities: {
rest: true,
graphql: true,
ws: true,
sse: true,
sagas: true,
twoPhaseCommit: true,
perTenantJobs: true,
},
}),
);
const migration = files.find((f) =>
f.path === "migrations/postgres/0001-init.sql",
);
```
## API Reference
- `renderTree(answers: PromptAnswers): readonly GeneratedFile[]` —
deterministic, ordered file list.
## Implementation Notes
- Path order is stable, which the snapshot tests depend on; do not
reorder file emission when adding new templates.
- Every emitted file ends with a trailing newline so they round-trip
through editors that auto-append one.
- The generated `package.json` declares no external runtime deps so
`bun test` against the produced tree works without `bun install`
resolving anything.
- Integration tests use `vitest` rather than `bun:test` since real
testcontainer fixtures are vitest-native.

View File

@@ -0,0 +1,16 @@
# demo-plugin
Suaveplan API plugin generated by `create-suaveplan-plugin`.
- **Isolation**: `isolated`
- **Database**: `sqlite`
- **Capabilities**: `rest`
## Getting started
```bash
bun install
bun test
```
Replace the stub handlers in `src/index.ts` with the real plugin implementation.

View File

@@ -0,0 +1,16 @@
# demo-plugin
Suaveplan API plugin generated by `create-suaveplan-plugin`.
- **Isolation**: `sandboxed`
- **Database**: `none`
- **Capabilities**: `rest`
## Getting started
```bash
bun install
bun test
```
Replace the stub handlers in `src/index.ts` with the real plugin implementation.

View File

@@ -0,0 +1,16 @@
# demo-plugin
Suaveplan API plugin generated by `create-suaveplan-plugin`.
- **Isolation**: `trusted`
- **Database**: `postgres`
- **Capabilities**: `rest`, `graphql`, `ws`, `sse`, `sagas`, `twoPhaseCommit`, `perTenantJobs`
## Getting started
```bash
bun install
bun test
```
Replace the stub handlers in `src/index.ts` with the real plugin implementation.