@dokebi/cli 0.4.0 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AI.md +3 -3
- package/README.md +1 -1
- package/catalog.json +13 -9
- package/dist/guide.js +2 -2
- package/dist/output.d.ts +1 -1
- package/dist/output.js +1 -1
- package/dist/project-guide.js +2 -1
- package/dist/recipe.js +10 -2
- package/package.json +1 -1
package/AI.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# AI guide — @dokebi/cli 0.4.
|
|
1
|
+
# AI guide — @dokebi/cli 0.4.1 (`dokebi`)
|
|
2
2
|
|
|
3
3
|
The command that creates Dokebi projects and installs, removes and upgrades plugins, apps and modules. It changes the project's "CLI-owned files" (generated registration code, state, the sample settings) in transactions. An AI never edits these files by hand: it always changes them with a command. Command details are in [docs/cli/README.md](https://git.nyx-zone.com/dokebi/framework/-/blob/main/docs/cli/README.md), the install lifecycle in [docs/cli/installation.md](https://git.nyx-zone.com/dokebi/framework/-/blob/main/docs/cli/installation.md), and the automation contract in [docs/cli/automation.md](https://git.nyx-zone.com/dokebi/framework/-/blob/main/docs/cli/automation.md).
|
|
4
4
|
|
|
@@ -7,7 +7,7 @@ The command that creates Dokebi projects and installs, removes and upgrades plug
|
|
|
7
7
|
- Inside a project: `init` puts `@dokebi/cli` (the same version as the CLI that created it), `typescript` and `@types/node` in devDependencies, so `npx dokebi …` at the project root runs that copy. Check the version first with `npx dokebi --version --json`. When `node_modules/@dokebi/cli` is missing (a project created by 0.2.5 or older), run `npm install -D @dokebi/cli` or use `npx -y @dokebi/cli …`.
|
|
8
8
|
- **Every command-line tool inside a project** runs by its installed bin name from the project root: `npx dokebi …` and, with @dokebi/migrate, `npx dokebi-migrate …`. Never run a file inside `node_modules/*/dist/` (the paths are private and change) and do not use `npm exec --package …` for them.
|
|
9
9
|
- Type checking is `npm run typecheck` (`tsc -p tsconfig.json --noEmit`). Never use `npx tsc` (the npm package named `tsc` is not TypeScript).
|
|
10
|
-
- The CLI, core, config and every plugin have their own versions (D-0046). The stage 3 release
|
|
10
|
+
- The CLI, core, config and every plugin have their own versions (D-0046). The stage 3 release put every package at 0.4.0 (D-0067); since then only changed packages get a new version (this CLI is 0.4.1). The CLI takes core and config in the range `^0.4.0`, and `init` installs the newest core in that range.
|
|
11
11
|
|
|
12
12
|
## 0. AI entry (D-0065): from a one-line request to `verify ok`
|
|
13
13
|
|
|
@@ -18,7 +18,7 @@ When the user writes one line, such as "make me a blog with the Dokebi framework
|
|
|
18
18
|
3. `npx dokebi recipe <name> --json`: when answers are missing it **installs nothing** and ends with exit 2, `ANSWERS_REQUIRED` and `result.questions[]`. Every question has options (`available`, `absent`, `why`, `plugins`, `alsoInstalls`, `needs`, `pros`, `cons`), `default` and `defaultReason`. When a program an option needs is not on PATH (the devInfra `docker` option without `docker`), the option has `missingNeeds: ["docker"]` and a `note`, and the default's reason says so; the default does not change: show the note and let the user decide. `waitForUser: true` and the first sentence of `next` say what to do: **Ask these questions and end your turn; do not run recipe/add/config until the user answers. Only use `--defaults` if the user said to decide.** After showing the questions, never go on with "I will install the defaults while you choose". `--defaults` is only for a user who left the choice to you; then tell the user the `report` of the result (the chosen values and why), and again at the end through verify's `tellUser` (step 7).
|
|
19
19
|
4. When the user answers, pass the answers as flags, add `--answers-from user` and run with `--dry-run --json`: `plan.steps[{id, command, why}]` (every step is a real command) and `preflight[]`. Ask the user about a preflight error (`DOCKER_MISSING`, `PORT_IN_USE`). `PORT_IN_USE` carries a free `alternative` port to suggest to the user (with `--db-port`, compose.yaml and dokebi.yaml use the same port), and, like the questions, `waitForUser`, `question` (use the proposed port, or stop the program that uses it) and `next`; the dry-run result and the `PREFLIGHT_FAILED` error (`error.details`) repeat them at the top. **Ask the user and end your turn; do not pick the port yourself unless the user left choices to you** (`--defaults` / `--answers-from defaults`: then `waitForUser` is false and you may run again with the proposed port, and tell the user which port and why). The CLI never switches a port by itself, not even with `--defaults`. Never switch the database or a port behind the user's back (`--db-port`, `--port` and `--dev-infra url` are the user's choices).
|
|
20
20
|
5. Run it without `--dry-run`. **Applying needs the source of the answers**: `--answers-from user` (the user answered the questions) or `--defaults` (= `--answers-from defaults`, the user left the choice to you). Without one it fails with `ANSWERS_SOURCE_REQUIRED` (exit 1, `mutation: none`, `details.fix`) and creates nothing. The source is kept in `answersFrom` and `report` of the result; pass `report` (one line each: the user's answers, defaults with their reasons, what to know) to the user as it is. An applied recipe is also recorded in `.dokebi/recipe.json` (the recipe, every answer with `answeredBy` and `defaultReason`, `answersFrom` and the notices; no secrets; written by the CLI, do not edit it), which verify reads for `tellUser`. A dry run works without a source (`answersFrom: null`) and its first `nextSteps` entry is the apply command. Each step calls an existing transactional command, and steps that already hold are skipped (`skippedSteps`). A failure stops with `RECIPE_STEP_FAILED` (`details.failedStep`, `completedSteps`, `error`): fix the cause and run the same command again; it resumes from the remaining steps.
|
|
21
|
-
6. Follow the `nextSteps` of the result: read the installed AI.md files (`plugins doc <id> --json`, the installed list in AGENTS.md), attach the module to the app and write the features. **Never invent** what `plugins search` reports as `absent` (auth, csrf, upload, mail, markdown, rss, rate-limit, i18n, sqlite, React, ORM, admin generator, search): build it in app code as its `instead` says, or tell the user. **Errors and forms (D-0074):** refuse input with `throw validationError({ field: 'message' })` from `@dokebi/web` (the same 422 `VALIDATION_FAILED` with `fields` and `errors` as a refused route schema; never a hand-made 422); Inertia, `fetch`/XHR, Bearer and JSON-body requests get JSON errors, only plain browser pages get HTML; give every Inertia form submit an `only` option (`form.post(url, { only: ['items'] })`, the `errors` prop always comes along); an EJS app may add `views/<template>/errors/404.ejs` and `errors/error.ejs`, an Inertia app `inertia.errorPage: 'Error'` (the `app add` skeletons have
|
|
21
|
+
6. Follow the `nextSteps` of the result: read the installed AI.md files (`plugins doc <id> --json`, the installed list in AGENTS.md), attach the module to the app and write the features. **Never invent** what `plugins search` reports as `absent` (auth, csrf, upload, mail, markdown, rss, rate-limit, i18n, sqlite, React, ORM, admin generator, search): build it in app code as its `instead` says, or tell the user. **Errors and forms (D-0074):** refuse input with `throw validationError({ field: 'message' })` from `@dokebi/web` (the same 422 `VALIDATION_FAILED` with `fields` and `errors` as a refused route schema; never a hand-made 422); Inertia, `fetch`/XHR, Bearer and JSON-body requests get JSON errors, only plain browser pages get HTML; give every Inertia form submit an `only` option (`form.post(url, { only: ['items'] })`, the `errors` prop always comes along); an EJS write form shows `data.errors.<field>` and `data.old.<field>` (`value="<%= data.old.title ?? '' %>"`), and with @dokebi/session (`npx dokebi add @dokebi/session`, ask the user) a refused HTML form comes back to its page (303) instead of the 422 page (@dokebi/app 0.4.1, D-0074 addendum 1); an EJS app may add `views/<template>/errors/404.ejs`, `errors/422.ejs` and `errors/error.ejs`, an Inertia app `inertia.errorPage: 'Error'` (the `app add` skeletons have 404, error and Error). The generated AGENTS.md says the same in "Errors and forms".
|
|
22
22
|
7. While writing code, fix problems with `npx dokebi check --json` (the convention check, see the table below), and say done only when `npx dokebi verify --json` returns `ok: true` (verify includes check). **Your final message to the user must include verify's `tellUser` lines** (when present): each choice of the recipe and who made it (`chosen by default (<reason>)`, `your answer`, or `chosen by the AI (you left the choice to it)`) and the notices, such as no CSRF protection for HTML forms.
|
|
23
23
|
|
|
24
24
|
### Recipes
|
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @dokebi/cli
|
|
2
2
|
|
|
3
|
-
The tool that creates Dokebi projects and installs plugins, version 0.4.0
|
|
3
|
+
The tool that creates Dokebi projects and installs plugins, version 0.4.1 (0.4.0 was the stage-3 release, D-0067). The command is `dokebi`. What changed since 0.1.1 (YAML settings, migrate, multi-app and module commands) is in the CHANGELOG. Node.js >=24.0.0, ESM; the default CI runs Node 24.
|
|
4
4
|
|
|
5
5
|
## Running it
|
|
6
6
|
|
package/catalog.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$comment": "Generated by scripts/catalog/build.ts from package.json dokebiCatalog fields and packages/cli/catalog-src (D-0065). Do not edit; run node scripts/catalog/build.ts.",
|
|
3
3
|
"schemaVersion": 1,
|
|
4
|
-
"cli": "0.4.
|
|
4
|
+
"cli": "0.4.1",
|
|
5
5
|
"core": {
|
|
6
6
|
"package": "@dokebi/core",
|
|
7
7
|
"version": "0.4.0"
|
|
@@ -223,7 +223,7 @@
|
|
|
223
223
|
{
|
|
224
224
|
"id": "dokebi-app",
|
|
225
225
|
"package": "@dokebi/app",
|
|
226
|
-
"version": "0.4.
|
|
226
|
+
"version": "0.4.1",
|
|
227
227
|
"kind": "plugin",
|
|
228
228
|
"summary": "Several apps chosen by request domain (src/apps/<app>) with a template each; installs web, controller and template-ejs with it",
|
|
229
229
|
"description": "Multiple apps per project for Dokebi: pick the app and template by request domain, with EJS and Inertia skeletons.",
|
|
@@ -252,7 +252,7 @@
|
|
|
252
252
|
"requires": [
|
|
253
253
|
{
|
|
254
254
|
"id": "dokebi-web",
|
|
255
|
-
"range": "^0.4.
|
|
255
|
+
"range": "^0.4.1",
|
|
256
256
|
"package": "@dokebi/web",
|
|
257
257
|
"automatic": true
|
|
258
258
|
},
|
|
@@ -273,6 +273,10 @@
|
|
|
273
273
|
{
|
|
274
274
|
"id": "dokebi-seal",
|
|
275
275
|
"range": "^0.4.0"
|
|
276
|
+
},
|
|
277
|
+
{
|
|
278
|
+
"id": "dokebi-session",
|
|
279
|
+
"range": "^0.4.0"
|
|
276
280
|
}
|
|
277
281
|
],
|
|
278
282
|
"optionKeys": [
|
|
@@ -363,7 +367,7 @@
|
|
|
363
367
|
{
|
|
364
368
|
"id": null,
|
|
365
369
|
"package": "@dokebi/cli",
|
|
366
|
-
"version": "0.4.
|
|
370
|
+
"version": "0.4.1",
|
|
367
371
|
"kind": "cli",
|
|
368
372
|
"summary": "Commands to create a project, browse the plugin catalog, run recipes, install plugins and verify (dokebi)",
|
|
369
373
|
"description": "Project scaffolding, plugin catalog, recipes, install and verification CLI for Dokebi.",
|
|
@@ -1333,7 +1337,7 @@
|
|
|
1333
1337
|
{
|
|
1334
1338
|
"id": "dokebi-template-ejs",
|
|
1335
1339
|
"package": "@dokebi/template-ejs",
|
|
1336
|
-
"version": "0.4.
|
|
1340
|
+
"version": "0.4.1",
|
|
1337
1341
|
"kind": "plugin",
|
|
1338
1342
|
"summary": "Compiles EJS templates at startup and renders them by name (layouts, partials, a scope per app)",
|
|
1339
1343
|
"description": "Precompiled EJS templates for Dokebi apps: layouts, partials and per-app scopes.",
|
|
@@ -1380,7 +1384,7 @@
|
|
|
1380
1384
|
{
|
|
1381
1385
|
"id": "dokebi-template-inertia",
|
|
1382
1386
|
"package": "@dokebi/template-inertia",
|
|
1383
|
-
"version": "0.4.
|
|
1387
|
+
"version": "0.4.1",
|
|
1384
1388
|
"kind": "plugin",
|
|
1385
1389
|
"summary": "Inertia (v3) server adapter: Vue 3 views, Vite build files and optional server-side rendering (SSR)",
|
|
1386
1390
|
"description": "Inertia v3 server adapter for Dokebi: page JSON, Vite manifest assets and optional server-side rendering.",
|
|
@@ -1437,7 +1441,7 @@
|
|
|
1437
1441
|
"requires": [
|
|
1438
1442
|
{
|
|
1439
1443
|
"id": "dokebi-web",
|
|
1440
|
-
"range": "^0.4.
|
|
1444
|
+
"range": "^0.4.1"
|
|
1441
1445
|
}
|
|
1442
1446
|
],
|
|
1443
1447
|
"optional": [
|
|
@@ -1466,7 +1470,7 @@
|
|
|
1466
1470
|
{
|
|
1467
1471
|
"id": "dokebi-web",
|
|
1468
1472
|
"package": "@dokebi/web",
|
|
1469
|
-
"version": "0.4.
|
|
1473
|
+
"version": "0.4.1",
|
|
1470
1474
|
"kind": "plugin",
|
|
1471
1475
|
"summary": "Fastify 5 HTTP server (503 before ready, health paths, drain, zero-downtime cluster replacement)",
|
|
1472
1476
|
"description": "Fastify 5 HTTP server owned by a Dokebi app: 503 before ready, health routes, drain and cluster handover.",
|
|
@@ -1748,7 +1752,7 @@
|
|
|
1748
1752
|
"then": [
|
|
1749
1753
|
"Attach the module to the app: modules: { \"/posts\": posts } in src/apps/main/app.ts (posts = the default export of src/shared/modules/posts/module.ts)",
|
|
1750
1754
|
"Write the post list (GET /posts) and the post page (GET /posts/:id) in the routes.ts and controllers of the module",
|
|
1751
|
-
"The write form (GET /posts/new → POST /posts) reads request.body (with EJS the recipe turned on dokebi-web.formBody). With --admin none it is a public form (no login); with app-code it belongs to the admin app. Either way it needs a CSRF token check in app code (csrf is absent). A refused post throws validationError({ title: \"Required\" }) from @dokebi/web (D-0074): an EJS form post
|
|
1755
|
+
"The write form (GET /posts/new → POST /posts) reads request.body (with EJS the recipe turned on dokebi-web.formBody). With --admin none it is a public form (no login); with app-code it belongs to the admin app. Either way it needs a CSRF token check in app code (csrf is absent). A refused post throws validationError({ title: \"Required\" }) from @dokebi/web (D-0074): an EJS form shows data.errors.title and data.old.title (with @dokebi/session the refused post goes back to the form, 303; without it views/default/errors/422.ejs or errors/error.ejs shows the fields), an Inertia form gets them in form.errors (submit with only). Edit the shared layout src/shared/views/layouts/main.ejs where it is"
|
|
1752
1756
|
]
|
|
1753
1757
|
}
|
|
1754
1758
|
],
|
package/dist/guide.js
CHANGED
|
@@ -29,7 +29,7 @@ export const GUIDE_STEPS = [
|
|
|
29
29
|
},
|
|
30
30
|
{
|
|
31
31
|
command: '(write the app code) src/apps/<app>, src/shared/modules/<module>',
|
|
32
|
-
why: "Follow the nextSteps of the recipe result: attach the module to the app and write the features. Build what Dokebi does not have (absent) in app code, as its instead says. Errors and forms (D-0074): refuse input with throw validationError({ field: 'message' }) from @dokebi/web (422 with errors by field, like a refused schema); Inertia form submits pass only (the props they change) and show form.errors; EJS apps may add views/<template>/errors/404.ejs and errors/error.ejs",
|
|
32
|
+
why: "Follow the nextSteps of the recipe result: attach the module to the app and write the features. Build what Dokebi does not have (absent) in app code, as its instead says. Errors and forms (D-0074): refuse input with throw validationError({ field: 'message' }) from @dokebi/web (422 with errors by field, like a refused schema); Inertia form submits pass only (the props they change) and show form.errors; EJS forms show data.errors.<field> and data.old.<field> (with @dokebi/session a refused form comes back to its page, 303); EJS apps may add views/<template>/errors/404.ejs, errors/422.ejs and errors/error.ejs",
|
|
33
33
|
},
|
|
34
34
|
{
|
|
35
35
|
command: 'npx dokebi check --json',
|
|
@@ -49,7 +49,7 @@ export const GUIDE_RULES = [
|
|
|
49
49
|
'Preview every changing command with --dry-run --json first. Never blindly re-run a failure, and never delete the lock or a backup',
|
|
50
50
|
'Only src/dokebi.generated.ts, .dokebi/state.json and the part of AGENTS.md between the dokebi:installed markers are rewritten by the CLI: never edit them by hand. Every other file the CLI or a plugin created (the shared layout src/shared/views/layouts/main.ejs, entry files, app and module files) is user code: edit it where it is (do not move it)',
|
|
51
51
|
"Never say done before verify returns ok: true. Your final message to the user must include verify's tellUser lines",
|
|
52
|
-
'Errors (D-0074): Inertia, fetch/XHR, Bearer and JSON-body requests get JSON errors and only plain browser pages get HTML, so read errors from the JSON. Refuse input with validationError({ field: message }) (422 VALIDATION_FAILED, fields and errors), never a hand-made 422; give every Inertia form submit an only option',
|
|
52
|
+
'Errors (D-0074): Inertia, fetch/XHR, Bearer and JSON-body requests get JSON errors and only plain browser pages get HTML, so read errors from the JSON. Refuse input with validationError({ field: message }) (422 VALIDATION_FAILED, fields and errors), never a hand-made 422; give every Inertia form submit an only option; an EJS write form shows data.errors.<field> and data.old.<field> (add @dokebi/session so a refused form comes back to its page)',
|
|
53
53
|
];
|
|
54
54
|
export function buildGuide(catalog) {
|
|
55
55
|
const recipes = catalog.recipes.map((recipe) => ({
|
package/dist/output.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export declare const CLI_VERSION = "0.4.
|
|
1
|
+
export declare const CLI_VERSION = "0.4.1";
|
|
2
2
|
export declare function envelope(command: string, result: unknown, ok?: boolean): object;
|
|
3
3
|
export declare function failure(command: string, error: unknown, mutationStarted: boolean): object;
|
package/dist/output.js
CHANGED
package/dist/project-guide.js
CHANGED
|
@@ -133,7 +133,8 @@ Run the command-line tools installed in this project by their bin names from the
|
|
|
133
133
|
- **Refuse input with \`throw validationError({ field: 'message' })\`** (\`import { validationError } from '@dokebi/web'\`), not with your own \`reply.code(422).send(...)\`. Clients then get exactly what a refused route schema gives them: 422 \`VALIDATION_FAILED\` with \`fields: [{ field, message }]\` and \`errors: { field: message }\`. Other refusals throw a \`DokebiError\` with a \`status\` (403, 404, 409…).
|
|
134
134
|
- **JSON or HTML is decided for you**: Inertia requests, \`fetch\`/XHR, Bearer API clients and JSON bodies get JSON errors; only plain browser pages (a navigation, an HTML form post) get an HTML page. Read errors from the JSON (\`body.errors.name\`); do not parse HTML.
|
|
135
135
|
- **Inertia forms** (\`useForm().post/put/patch/delete\`, \`router.post/…\`): pass \`only\` with the props the submit changes, for example \`form.post(url, { only: ['items'] })\`. With @dokebi/session a validation error goes back (303) with the messages in \`form.errors.<field>\` (\`errors\` always comes along); without \`only\` the GET after the redirect sends every prop of the page again. \`npx dokebi check\` warns about a submit without \`only\` (\`inertia-form-only\`).
|
|
136
|
-
- **
|
|
136
|
+
- **EJS (HTML) forms come back with their errors**: with @dokebi/session (\`npx dokebi add @dokebi/session\`) an HTML form post refused with 422 goes back (303) to the page it came from, and every EJS page has \`data.errors\` (\`{ field: message }\`), \`data.old\` (the typed values, never passwords) and \`data.flash\` (other flash, for example \`data.flash.notice\`), always objects. Write each field as \`<input name="title" value="<%= data.old.title ?? '' %>">\` with \`<% if (data.errors.title) { %><small><%= data.errors.title %></small><% } %>\`. Do not call \`request.session.takeFlash()\` in a route that renders a page (the page takes the flash). Without a session the refused form shows \`errors/422.ejs\` (else \`errors/error.ejs\`) with \`fields\`, \`errors\` and \`old\`. HTML forms need \`dokebi-web.formBody: true\`.
|
|
137
|
+
- **Error pages**: an EJS app shows \`views/<template>/errors/<status>.ejs\` (then \`errors/error.ejs\`) for browser pages, with \`status\`, \`error\`, \`message\`, \`hint\`, \`fields\`, \`errors\` and \`old\`; an Inertia app may set \`inertia: { errorPage: 'Error' }\` to render its \`Error\` page component.
|
|
137
138
|
|
|
138
139
|
## Installed packages
|
|
139
140
|
|
package/dist/recipe.js
CHANGED
|
@@ -602,9 +602,17 @@ function nextSteps(catalog, recipe, answers, plan, port) {
|
|
|
602
602
|
why: 'a refused field comes back to the form (422 → 303 back with errors, @dokebi/session); only keeps the GET after the redirect to the props that changed (npx dokebi check warns: inertia-form-only)',
|
|
603
603
|
}
|
|
604
604
|
: {
|
|
605
|
-
command:
|
|
606
|
-
why:
|
|
605
|
+
command: 'edit: every write form — <form method="post">, each field value="<%= data.old.<field> ?? \'\' %>" and its message from data.errors.<field>; in the handler throw validationError({ <field>: \'<message>\' }) from @dokebi/web',
|
|
606
|
+
why: plan.some((step) => step.id === 'session')
|
|
607
|
+
? 'a refused route schema and the app check give the same 422 VALIDATION_FAILED (D-0074); with @dokebi/session (in this plan) the refused form comes back to its page (303) with data.errors and data.old; never send a hand-made 422'
|
|
608
|
+
: 'a refused route schema and the app check give the same 422 VALIDATION_FAILED (D-0074); never send a hand-made 422. Without a session the refused form shows views/default/errors/422.ejs (else errors/error.ejs) with fields, errors and old',
|
|
607
609
|
});
|
|
610
|
+
// D-0074 addendum 1: an EJS write form comes back to its page only with a session.
|
|
611
|
+
if (recipe.web.forms && !plan.some((step) => step.id === 'view-plugin' || step.id === 'session'))
|
|
612
|
+
result.push({
|
|
613
|
+
command: 'npx dokebi add @dokebi/session',
|
|
614
|
+
why: 'so a refused HTML form goes back to its page with the typed values and the field messages (D-0074 addendum 1) instead of the 422 page; ask the user first (it writes DOKEBI_SESSION_SECRETS into .env and keeps sessions in memory until another store is configured)',
|
|
615
|
+
});
|
|
608
616
|
const absent = catalog.absent.map((entry) => entry.capability).join(', ');
|
|
609
617
|
result.push({
|
|
610
618
|
command: 'npx dokebi plugins search <feature> --json',
|