164 lines
5.3 KiB
Markdown
164 lines
5.3 KiB
Markdown
# CLI scaffolding (`toolctrl create` / `toolctrl add`)
|
|
|
|
The operator CLI under `packages/cli/` ships scaffolding generators that
|
|
emit minimal, opinionated skeletons for the most common boilerplate
|
|
chores. Each generator writes to **new paths only** and supports a
|
|
`--dry-run` flag for testability.
|
|
|
|
## Quickstart
|
|
|
|
```bash
|
|
# from the repo root, after `pnpm install`
|
|
pnpm --filter @toolctrl/cli build
|
|
|
|
# show the full command tree
|
|
node packages/cli/bin/toolctrl-cli.mjs --help
|
|
```
|
|
|
|
## Command reference
|
|
|
|
### `toolctrl create module <name>`
|
|
|
|
Scaffolds a feature-module skeleton under `apps/web/app/modules/<name>/`:
|
|
|
|
- `db/<name>.db.server.ts` — tenant-scoped CRUD helpers
|
|
- `services/<Name>Service.ts` — service layer with tenant guards
|
|
- `components/<Name>List.tsx` — list UI stub
|
|
- `<name>.db.server.test.ts` — vitest smoke test
|
|
- `README.md` — manual follow-up checklist
|
|
- `apps/web/app/routes/app.$tenant.<name>.tsx` — React-Router-v7 tenant route
|
|
|
|
Flags:
|
|
|
|
- `--permission-prefix <prefix>` — override the permission key prefix.
|
|
- `--dry-run` — print the would-be writes without touching the filesystem.
|
|
|
|
Example:
|
|
|
|
```bash
|
|
node packages/cli/bin/toolctrl-cli.mjs create module inventory
|
|
```
|
|
|
|
Manual follow-up (the README in the generated module repeats this):
|
|
|
|
1. Add a Prisma model and run `pnpm prisma migrate dev`.
|
|
2. Replace the `TODO` markers in the db layer.
|
|
3. Add the permission keys to
|
|
`packages/db/src/permissions-seed.server.ts` (see
|
|
`toolctrl add permission` below).
|
|
4. Run `pnpm typecheck` to generate the route types.
|
|
|
|
### `toolctrl create route <filename>`
|
|
|
|
Scaffolds a single React-Router-v7 route file at
|
|
`apps/web/app/routes/<filename>.tsx` plus a co-located vitest stub.
|
|
|
|
Flags:
|
|
|
|
- `--top-level` — generate a route without the `:tenant` param.
|
|
- `--dry-run` — print the would-be writes without touching the filesystem.
|
|
|
|
Example:
|
|
|
|
```bash
|
|
node packages/cli/bin/toolctrl-cli.mjs create route app.\$tenant.dashboard
|
|
```
|
|
|
|
The generated file imports `Route` types from `./+types/<filename>`
|
|
(NEVER relative paths) and contains loader, action, default component
|
|
and an `ErrorBoundary` export. Run `pnpm typecheck` after generation
|
|
to populate the `./+types/` directory.
|
|
|
|
### `toolctrl create entity <name>`
|
|
|
|
Emits a Prisma model snippet plus a timestamped migration directory:
|
|
|
|
- `prisma/_generated/<name>.prisma` — model snippet to paste into
|
|
`prisma/schema.prisma`
|
|
- `prisma/migrations/<timestamp>_create_entity_<name>/migration.sql` —
|
|
placeholder SQL
|
|
- `prisma/migrations/<timestamp>_create_entity_<name>/README.md` —
|
|
step-by-step apply instructions
|
|
|
|
The generator does NOT modify `prisma/schema.prisma` automatically.
|
|
After generation:
|
|
|
|
```bash
|
|
# paste the model from prisma/_generated/<name>.prisma into prisma/schema.prisma
|
|
pnpm prisma migrate dev --name create_entity_<name>
|
|
pnpm prisma generate
|
|
```
|
|
|
|
### `toolctrl add hook <name>`
|
|
|
|
Scaffolds a plugin-hook registration file under
|
|
`apps/web/app/hooks/_generated/`. The output is a **forward-compatible
|
|
stub** for generated hook registration. The plugin runtime and
|
|
`@toolctrl/plugin-sdk` now exist; keep generated hooks on the scaffolded
|
|
no-op shape until a focused task wires that specific hook into the runtime.
|
|
|
|
Example:
|
|
|
|
```bash
|
|
node packages/cli/bin/toolctrl-cli.mjs add hook onContactCreated
|
|
```
|
|
|
|
### `toolctrl add event-type <name>`
|
|
|
|
Scaffolds an EntityWebhook event-type definition with a zod schema, an
|
|
example payload, and an async handler stub under
|
|
`apps/web/app/modules/workflowEngine/eventTypes/_generated/`. Event
|
|
names MUST be dotted lowercase, e.g. `crm.contact.created`.
|
|
|
|
Example:
|
|
|
|
```bash
|
|
node packages/cli/bin/toolctrl-cli.mjs add event-type crm.contact.created
|
|
```
|
|
|
|
### `toolctrl add permission <name>`
|
|
|
|
Scaffolds two fragments for a new permission key:
|
|
|
|
- `packages/db/src/_generated-permissions/<key>.ts` — seed fragment
|
|
(paste into `defaultPermissionSeeds`).
|
|
- `apps/web/app/utils/permissions/_generated/<key>.ts` — frontend
|
|
permission-mapper stub.
|
|
|
|
The generator intentionally does NOT patch the hand-curated
|
|
`defaultPermissionSeeds` array in `permissions-seed.server.ts`. The
|
|
README notes spell out the manual paste step.
|
|
|
|
Example:
|
|
|
|
```bash
|
|
node packages/cli/bin/toolctrl-cli.mjs add permission app.inventory.view
|
|
```
|
|
|
|
## Safety guarantees
|
|
|
|
- Generators **never** overwrite existing files. Re-running a generator
|
|
with the same name throws and exits non-zero.
|
|
- All generators support `--dry-run`, which prints the would-be writes
|
|
without touching the filesystem.
|
|
- Generators only produce new paths inside the repo (or
|
|
`prisma/_generated/` snippets that you paste in by hand). The
|
|
hand-curated source-of-truth files (`schema.prisma`,
|
|
`permissions-seed.server.ts`, `routes.ts`) are never patched
|
|
automatically.
|
|
|
|
## Adding a new generator
|
|
|
|
1. Create `packages/cli/src/commands/{create,add}/<command>.ts` with a
|
|
`generate<Name>` function and a `defineCommand` export.
|
|
2. Re-use the helpers from `_lib/scaffold.ts`:
|
|
- `validateIdentifier`, `pascalCase`, `kebabCase`, `snakeCase`
|
|
- `createScaffoldResult` + `writeScaffoldFile`
|
|
3. Add a vitest suite next to the command file that asserts:
|
|
- The expected file paths (in dry-run mode).
|
|
- Content invariants (e.g. correct import paths).
|
|
- Refuses to overwrite existing files.
|
|
4. Register the command in the relevant `create/index.ts` or
|
|
`add/index.ts` and update the root `index.test.ts` to keep the
|
|
registry shape pinned.
|