@voltro/cli 0.13.0 → 0.15.0
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/CHANGELOG.md +412 -0
- package/dist/{apiBuild-B6_ehbem.js → apiBuild-CHItlwPO.js} +2 -2
- package/dist/apiBuild-D8juhYsS.js +2 -0
- package/dist/bin.js +3 -3
- package/dist/{commands-DYOuGlEC.js → commands-CPQuQRXh.js} +3967 -2831
- package/dist/{dbCommand-DIOJQwsq.js → dbCommand-B8F29zV0.js} +2 -1
- package/dist/dbCommand-Dt7BtdMd.js +2 -0
- package/dist/{dev-xdijqojc.js → dev-BHLM3RPX.js} +1 -1
- package/dist/{dev-Fb9BmavX.js → dev-C2lCUhYI.js} +1478 -1347
- package/dist/fileConventions-Cu0tM-Bl.js +29 -0
- package/dist/{frameworkTableAssembly-CYlrafoK.js → frameworkTableAssembly-BA2bAmYN.js} +164 -163
- package/dist/frameworkTableAssembly-D1zJSgOU.js +2 -0
- package/dist/index.js +1 -1
- package/dist/{inspectMetrics-DOiiII34.js → inspectMetrics-CYDSO3za.js} +955 -945
- package/dist/{serveCommand-CqYxzBZR.js → serveCommand-DBzQcCuv.js} +410 -396
- package/dist/serveEntry.js +2 -2
- package/dist/{start-CjG_0xhR.js → start-BtgatJN3.js} +246 -246
- package/dist/startEntry.js +2 -2
- package/package.json +17 -17
- package/templates/AGENTS.core.md +77 -3
- package/templates/AGENTS.md +78 -4
- package/templates/agent-docs/_index.md +1 -1
- package/templates/agent-docs/_manifest.json +2 -2
- package/templates/agent-docs/authentication.md +34 -2
- package/templates/agent-docs/cli.md +116 -2
- package/templates/agent-docs/data.md +195 -3
- package/templates/agent-docs/database/hosting.md +2 -2
- package/templates/agent-docs/database/migrations.md +77 -3
- package/templates/agent-docs/database/overview.md +11 -0
- package/templates/agent-docs/database/seedsdialects.md +53 -1
- package/templates/agent-docs/internationalization.md +3 -3
- package/templates/agent-docs/introduction.md +133 -2
- package/templates/agent-docs/plugins.md +2 -1
- package/templates/agent-docs/reference.md +4 -4
- package/templates/agent-docs/routing.md +48 -43
- package/templates/agent-docs/schema-driven-ui.md +14 -3
- package/templates/agent-docs/templates/apibackends.md +1 -8
- package/templates/agent-docs/templates/appshells.md +24 -24
- package/templates/agent-docs/testing.md +16 -1
- package/templates/agent-docs/whats-new.md +247 -39
- package/templates/apps/api-ai/database/schema.ts +0 -2
- package/templates/apps/api-ai/package.json +7 -7
- package/templates/apps/api-auth/package.json +8 -8
- package/templates/apps/api-backend/database/schema.ts +0 -3
- package/templates/apps/api-backend/package.json +7 -7
- package/templates/apps/api-backend-deactivation/database/schema.ts +0 -1
- package/templates/apps/api-backend-deactivation/package.json +7 -7
- package/templates/apps/api-backend-mail/database/schema.ts +0 -3
- package/templates/apps/api-backend-mail/package.json +8 -8
- package/templates/apps/api-backend-mariadb/database/schema.ts +0 -3
- package/templates/apps/api-backend-mariadb/package.json +9 -9
- package/templates/apps/api-backend-storage/database/schema.ts +0 -3
- package/templates/apps/api-backend-storage/package.json +8 -8
- package/templates/apps/api-data-advanced/database/authors.entity.ts +0 -2
- package/templates/apps/api-data-advanced/database/books.entity.ts +0 -1
- package/templates/apps/api-data-advanced/package.json +8 -8
- package/templates/apps/api-durable/database/schema.ts +0 -3
- package/templates/apps/api-durable/package.json +8 -8
- package/templates/apps/api-feature-flags/database/schema.ts +0 -1
- package/templates/apps/api-feature-flags/package.json +9 -9
- package/templates/apps/api-governance/database/schema.ts +0 -1
- package/templates/apps/api-governance/package.json +8 -8
- package/templates/apps/api-kv/database/schema.ts +0 -3
- package/templates/apps/api-kv/package.json +8 -8
- package/templates/apps/api-moderation/database/schema.ts +0 -2
- package/templates/apps/api-moderation/package.json +8 -8
- package/templates/apps/api-observability/database/schema.ts +0 -1
- package/templates/apps/api-observability/package.json +8 -8
- package/templates/apps/api-ratelimit/database/schema.ts +0 -1
- package/templates/apps/api-ratelimit/package.json +8 -8
- package/templates/apps/api-rbac/database/schema.ts +0 -2
- package/templates/apps/api-rbac/package.json +8 -8
- package/templates/apps/api-rest/database/schema.ts +0 -3
- package/templates/apps/api-rest/package.json +7 -7
- package/templates/apps/api-saas/database/schema.ts +0 -1
- package/templates/apps/api-saas/package.json +11 -11
- package/templates/apps/api-search/database/schema.ts +0 -1
- package/templates/apps/api-search/package.json +8 -8
- package/templates/apps/api-versioning/database/schema.ts +0 -1
- package/templates/apps/api-versioning/package.json +8 -8
- package/templates/apps/api-webhooks/database/schema.ts +0 -1
- package/templates/apps/api-webhooks/package.json +9 -9
- package/templates/apps/changelog/package.json +6 -6
- package/templates/apps/changelog/src/pages/[locale]/{[slug].tsx → [slug].page.tsx} +2 -2
- package/templates/apps/changelog/src/pages/[locale]/{index.tsx → index.page.tsx} +2 -2
- package/templates/apps/changelog/src/pages/[locale]/mirrors.test.tsx +2 -2
- package/templates/apps/changelog/src/pages/{[slug].test.tsx → [slug].page.test.tsx} +1 -1
- package/templates/apps/changelog/src/pages/{index.test.tsx → index.page.test.tsx} +1 -1
- package/templates/apps/edge-functions/package.json +2 -2
- package/templates/apps/frontend-admin/package.json +8 -8
- package/templates/apps/frontend-admin/src/pages/(marketing)/{index.test.tsx → index.page.test.tsx} +1 -1
- package/templates/apps/frontend-admin/src/pages/(marketing)/{login.test.tsx → login.page.test.tsx} +1 -1
- package/templates/apps/frontend-admin/src/pages/admin/entity.test.tsx +1 -1
- package/templates/apps/frontend-admin/src/pages/admin/{index.test.tsx → index.page.test.tsx} +1 -1
- package/templates/apps/frontend-app/package.json +8 -8
- package/templates/apps/frontend-app/src/pages/{index.test.tsx → index.page.test.tsx} +1 -1
- package/templates/apps/frontend-app/src/pages/{schema-ui.test.tsx → schema-ui.page.test.tsx} +1 -1
- package/templates/apps/frontend-blank/package.json +7 -7
- package/templates/apps/frontend-blank/src/pages/{index.test.tsx → index.page.test.tsx} +1 -1
- package/templates/apps/frontend-contact/package.json +7 -7
- package/templates/apps/frontend-contact/src/pages/[locale]/{index.tsx → index.page.tsx} +2 -2
- package/templates/apps/frontend-contact/src/pages/{index.test.tsx → index.page.test.tsx} +1 -1
- package/templates/apps/frontend-dashboard/package.json +7 -7
- package/templates/apps/frontend-dashboard/src/pages/(marketing)/{index.test.tsx → index.page.test.tsx} +1 -1
- package/templates/apps/frontend-dashboard/src/pages/(marketing)/{login.test.tsx → login.page.test.tsx} +1 -1
- package/templates/apps/frontend-dashboard/src/pages/dashboard/{index.test.tsx → index.page.test.tsx} +1 -1
- package/templates/apps/frontend-dashboard/src/pages/dashboard/{settings.test.tsx → settings.page.test.tsx} +1 -1
- package/templates/apps/frontend-docs/package.json +7 -7
- package/templates/apps/frontend-docs/src/pages/[locale]/docs/{[...slug].tsx → [...slug].page.tsx} +3 -3
- package/templates/apps/frontend-docs/src/pages/[locale]/{index.tsx → index.page.tsx} +2 -2
- package/templates/apps/frontend-docs/src/pages/[locale]/mirrors.test.tsx +2 -2
- package/templates/apps/frontend-docs/src/pages/docs/{[...slug].test.tsx → [...slug].page.test.tsx} +1 -1
- package/templates/apps/frontend-docs/src/pages/{index.test.tsx → index.page.test.tsx} +1 -1
- package/templates/apps/frontend-i18n/package.json +6 -6
- package/templates/apps/frontend-i18n/src/pages/[locale]/{about.tsx → about.page.tsx} +2 -2
- package/templates/apps/frontend-i18n/src/pages/[locale]/{index.tsx → index.page.tsx} +2 -2
- package/templates/apps/frontend-i18n/src/pages/[locale]/mirrors.test.tsx +2 -2
- package/templates/apps/frontend-i18n/src/pages/{about.test.tsx → about.page.test.tsx} +1 -1
- package/templates/apps/frontend-i18n/src/pages/{index.test.tsx → index.page.test.tsx} +1 -1
- package/templates/apps/frontend-landing/package.json +7 -7
- package/templates/apps/frontend-landing/src/pages/[locale]/{index.tsx → index.page.tsx} +2 -2
- package/templates/apps/frontend-landing/src/pages/[locale]/mirrors.test.tsx +1 -1
- package/templates/apps/frontend-landing/src/pages/{index.test.tsx → index.page.test.tsx} +1 -1
- package/templates/apps/frontend-spa/package.json +7 -7
- package/templates/apps/frontend-spa/src/pages/{index.test.tsx → index.page.test.tsx} +1 -1
- package/templates/apps/frontend-ssr/package.json +7 -7
- package/templates/apps/frontend-ssr/src/pages/{feed-swr.test.tsx → feed-swr.page.test.tsx} +1 -1
- package/templates/apps/frontend-ssr/src/pages/{feed.test.tsx → feed.page.test.tsx} +1 -1
- package/templates/apps/frontend-ssr/src/pages/{index.test.tsx → index.page.test.tsx} +1 -1
- package/templates/apps/frontend-ssr-api/package.json +8 -8
- package/templates/apps/frontend-ssr-api/src/pages/{index.test.tsx → index.page.test.tsx} +1 -1
- package/templates/apps/frontend-static-blog/package.json +6 -6
- package/templates/apps/frontend-static-blog/src/pages/[locale]/blog/{[slug].tsx → [slug].page.tsx} +2 -2
- package/templates/apps/frontend-static-blog/src/pages/[locale]/{index.tsx → index.page.tsx} +2 -2
- package/templates/apps/frontend-static-blog/src/pages/[locale]/mirrors.test.tsx +2 -2
- package/templates/apps/frontend-static-blog/src/pages/blog/{[slug].test.tsx → [slug].page.test.tsx} +1 -1
- package/templates/apps/frontend-static-blog/src/pages/{index.test.tsx → index.page.test.tsx} +1 -1
- package/dist/apiBuild-Bzpb383i.js +0 -2
- package/dist/dbCommand-ADD4mrTI.js +0 -2
- package/dist/frameworkTableAssembly-XvKO7wQt.js +0 -2
- /package/templates/apps/changelog/src/pages/{[slug].tsx → [slug].page.tsx} +0 -0
- /package/templates/apps/changelog/src/pages/{index.tsx → index.page.tsx} +0 -0
- /package/templates/apps/frontend-admin/src/pages/(marketing)/{index.tsx → index.page.tsx} +0 -0
- /package/templates/apps/frontend-admin/src/pages/(marketing)/{login.tsx → login.page.tsx} +0 -0
- /package/templates/apps/frontend-admin/src/pages/admin/{[entity].tsx → [entity].page.tsx} +0 -0
- /package/templates/apps/frontend-admin/src/pages/admin/{index.tsx → index.page.tsx} +0 -0
- /package/templates/apps/frontend-app/src/pages/{index.tsx → index.page.tsx} +0 -0
- /package/templates/apps/frontend-app/src/pages/{schema-ui.tsx → schema-ui.page.tsx} +0 -0
- /package/templates/apps/frontend-blank/src/pages/{index.tsx → index.page.tsx} +0 -0
- /package/templates/apps/frontend-contact/src/pages/{index.tsx → index.page.tsx} +0 -0
- /package/templates/apps/frontend-dashboard/src/pages/(marketing)/{index.tsx → index.page.tsx} +0 -0
- /package/templates/apps/frontend-dashboard/src/pages/(marketing)/{login.tsx → login.page.tsx} +0 -0
- /package/templates/apps/frontend-dashboard/src/pages/dashboard/{index.tsx → index.page.tsx} +0 -0
- /package/templates/apps/frontend-dashboard/src/pages/dashboard/{settings.tsx → settings.page.tsx} +0 -0
- /package/templates/apps/frontend-docs/src/pages/docs/{[...slug].tsx → [...slug].page.tsx} +0 -0
- /package/templates/apps/frontend-docs/src/pages/{index.tsx → index.page.tsx} +0 -0
- /package/templates/apps/frontend-i18n/src/pages/{about.tsx → about.page.tsx} +0 -0
- /package/templates/apps/frontend-i18n/src/pages/{index.tsx → index.page.tsx} +0 -0
- /package/templates/apps/frontend-landing/src/pages/{index.tsx → index.page.tsx} +0 -0
- /package/templates/apps/frontend-spa/src/pages/{index.tsx → index.page.tsx} +0 -0
- /package/templates/apps/frontend-ssr/src/pages/{feed-swr.tsx → feed-swr.page.tsx} +0 -0
- /package/templates/apps/frontend-ssr/src/pages/{feed.tsx → feed.page.tsx} +0 -0
- /package/templates/apps/frontend-ssr/src/pages/{index.tsx → index.page.tsx} +0 -0
- /package/templates/apps/frontend-ssr-api/src/pages/{index.tsx → index.page.tsx} +0 -0
- /package/templates/apps/frontend-static-blog/src/pages/blog/{[slug].tsx → [slug].page.tsx} +0 -0
- /package/templates/apps/frontend-static-blog/src/pages/{index.tsx → index.page.tsx} +0 -0
package/dist/startEntry.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { t } from "./start-
|
|
1
|
+
import { $ as e } from "./inspectMetrics-CYDSO3za.js";
|
|
2
|
+
import { t } from "./start-BtgatJN3.js";
|
|
3
3
|
export { e as loadDotEnv, t as runStartCommand };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@voltro/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.15.0",
|
|
4
4
|
"description": "The `voltro` CLI — dev server, codegen, migrations, project scaffolding, agent-docs seeding, and production serve.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"voltro",
|
|
@@ -62,22 +62,22 @@
|
|
|
62
62
|
"@effect/platform-node": "^0.107.0",
|
|
63
63
|
"@effect/sql": "^0.51.1",
|
|
64
64
|
"@effect/workflow": "^0.18.2",
|
|
65
|
-
"@voltro/ai": "0.
|
|
66
|
-
"@voltro/cache": "0.
|
|
67
|
-
"@voltro/data-transfer": "0.
|
|
68
|
-
"@voltro/database": "0.
|
|
69
|
-
"@voltro/env": "0.
|
|
70
|
-
"@voltro/kv": "0.
|
|
71
|
-
"@voltro/logger": "0.
|
|
72
|
-
"@voltro/plugin-auth": "0.
|
|
73
|
-
"@voltro/plugin-broadcast": "0.
|
|
74
|
-
"@voltro/plugin-mail": "0.
|
|
75
|
-
"@voltro/plugin-storage": "0.
|
|
76
|
-
"@voltro/plugin-webhooks": "0.
|
|
77
|
-
"@voltro/protocol": "0.
|
|
78
|
-
"@voltro/runtime": "0.
|
|
79
|
-
"@voltro/serverless": "0.
|
|
80
|
-
"@voltro/workflow": "0.
|
|
65
|
+
"@voltro/ai": "0.15.0",
|
|
66
|
+
"@voltro/cache": "0.15.0",
|
|
67
|
+
"@voltro/data-transfer": "0.15.0",
|
|
68
|
+
"@voltro/database": "0.15.0",
|
|
69
|
+
"@voltro/env": "0.15.0",
|
|
70
|
+
"@voltro/kv": "0.15.0",
|
|
71
|
+
"@voltro/logger": "0.15.0",
|
|
72
|
+
"@voltro/plugin-auth": "0.15.0",
|
|
73
|
+
"@voltro/plugin-broadcast": "0.15.0",
|
|
74
|
+
"@voltro/plugin-mail": "0.15.0",
|
|
75
|
+
"@voltro/plugin-storage": "0.15.0",
|
|
76
|
+
"@voltro/plugin-webhooks": "0.15.0",
|
|
77
|
+
"@voltro/protocol": "0.15.0",
|
|
78
|
+
"@voltro/runtime": "0.15.0",
|
|
79
|
+
"@voltro/serverless": "0.15.0",
|
|
80
|
+
"@voltro/workflow": "0.15.0",
|
|
81
81
|
"chokidar": "^5.0.0",
|
|
82
82
|
"ioredis": "^5.11.1",
|
|
83
83
|
"tinyglobby": "^0.2.17",
|
package/templates/AGENTS.core.md
CHANGED
|
@@ -219,7 +219,7 @@ a `web/` (frontend).
|
|
|
219
219
|
│ │ └── rpcGroup.generated.ts # codegen — do NOT edit
|
|
220
220
|
│ └── web/
|
|
221
221
|
│ ├── app.config.ts # { type:'web', apis, theme, locales, … }
|
|
222
|
-
│ └── src/pages/**/*.tsx # file-based routing
|
|
222
|
+
│ └── src/pages/**/*.page.tsx # file-based routing (only *.page.tsx)
|
|
223
223
|
├── packages/ # shared libs
|
|
224
224
|
└── AGENTS.md / CLAUDE.md # this file
|
|
225
225
|
```
|
|
@@ -249,7 +249,77 @@ public REST routes, declared via `restRoutes` in `app.config.ts`).
|
|
|
249
249
|
| `*.agent.tsx` + `*.agent.server.tsx` | server-side LLM chat |
|
|
250
250
|
| `*.tool.tsx` | tool an agent can call |
|
|
251
251
|
| `*.entity.ts` / `*.schema.ts` / `schema.ts` | one table per file |
|
|
252
|
-
| `src/pages
|
|
252
|
+
| `*.page.tsx` (under `src/pages/`) | web page (file-based route) + `*.page.test.tsx` |
|
|
253
|
+
| `*.component.tsx` | exactly ONE component (+ types) |
|
|
254
|
+
| `*.component.ui.tsx` | presentational: one component, READS only — never writes |
|
|
255
|
+
| `*.hook.ts` | exactly ONE `use*` hook (+ types) |
|
|
256
|
+
| `*.types.ts` | types only — zero runtime exports |
|
|
257
|
+
| `*.internal.ts` | only its own directory subtree may import it |
|
|
258
|
+
| `*.fixture.ts` | test material — no production path may reach it |
|
|
259
|
+
| `*.tracking.ts` | the ONLY place analytics may be called from |
|
|
260
|
+
| `*.store.ts` | exactly ONE `defineStore` — client state, never server state |
|
|
261
|
+
| `*.client.ts` | declares itself + its transitive imports browser-safe |
|
|
262
|
+
|
|
263
|
+
**Only `*.page.tsx` is a route.** Everything else under `src/pages/` — components,
|
|
264
|
+
hooks, tests — is ordinary code with no URL, so put it next to the page that uses
|
|
265
|
+
it rather than in a distant `components/` directory. The suffix decides *whether*
|
|
266
|
+
a file is a route; the path under `src/pages/` decides *which URL*. A `*.page.tsx`
|
|
267
|
+
outside `src/pages/` never routes. A page MUST default-export its component —
|
|
268
|
+
without it `voltro dev` refuses to boot and names the file.
|
|
269
|
+
|
|
270
|
+
**Every suffix above is a CONTRACT, not a label.** Something else depends on each
|
|
271
|
+
promise, and `voltro doctor` enforces all of them:
|
|
272
|
+
|
|
273
|
+
- `*.component.ui.tsx` may read (`useT`, `useCan`, `usePermissions`) but must
|
|
274
|
+
never import a write hook (`useMutation`, `useAction`, `useUpload`, …). That is
|
|
275
|
+
what lets a caller render it ten thousand times in a list, reuse it across
|
|
276
|
+
features, and prerender it without reading its source. Lift the write into the
|
|
277
|
+
owning `*.component.tsx` and pass a handler down.
|
|
278
|
+
- `*.internal.ts` is the promise that refactoring inside that directory breaks
|
|
279
|
+
nobody. An import from another subtree revokes it.
|
|
280
|
+
- `*.types.ts` having no runtime export is what makes importing it free and makes
|
|
281
|
+
it impossible for it to sit in a runtime import cycle.
|
|
282
|
+
- `*.store.ts` holds ONE `defineStore`. Reads go through a selector
|
|
283
|
+
(`wizard.use((s) => s.step)`) — there is no whole-state read, because a
|
|
284
|
+
component holding all of it re-renders on every field. Scope a per-entity
|
|
285
|
+
instance with a KEY (`{ key: orderId }`), never a Provider. Seed it for SSR
|
|
286
|
+
from a loader with `seedStore(...)`; it rides the hydration payload the router
|
|
287
|
+
already writes. An action that writes SEVERAL fields wraps them in
|
|
288
|
+
`store.batch('label', () => …)` — one notification, one devtools entry, one
|
|
289
|
+
undo step, and a rollback if it throws; `await` first, then batch (an async
|
|
290
|
+
callback is an error). `{ persist: { key } }` survives a reload.
|
|
291
|
+
NEVER put server data in a store: a subscription is already
|
|
292
|
+
live, and a copy is not — the page then shows the stale one.
|
|
293
|
+
- `*.tracking.ts` is where every `defineTracking(...)` spec lives — the event
|
|
294
|
+
catalogue, so "what do we send to third parties" is a file listing rather than
|
|
295
|
+
archaeology. A component wires one up with `useTracking(spec, props, sink)`; it
|
|
296
|
+
NAMES a spec, it never declares one. (`useTracking` is a hook, so it is not
|
|
297
|
+
confined — only the declaration is.)
|
|
298
|
+
- Do NOT rename a file to `*.component.ui.tsx` just because it happens not to
|
|
299
|
+
write today. The suffix is a promise about what it MAY do.
|
|
300
|
+
|
|
301
|
+
**These rules apply to code YOU write — never to vendored code.** A shadcn
|
|
302
|
+
component (`npx shadcn add`) follows shadcn's conventions and is overwritten by
|
|
303
|
+
the next `add`; renaming it breaks their convention and is undone next run. A
|
|
304
|
+
directory is exempt when the app's `components.json` names it (`aliases.ui`) or
|
|
305
|
+
it carries a `.voltro-vendored` file whose first line says where the code came
|
|
306
|
+
from. Never add that marker to a directory of your own code — the point of the
|
|
307
|
+
rules is that everything we author follows them.
|
|
308
|
+
|
|
309
|
+
**Write the test in the same step as the code — always, for every primitive.**
|
|
310
|
+
Not "afterwards", not in a cleanup pass: the filename is derivable
|
|
311
|
+
(`users/[id].page.tsx` → `users/[id].page.test.tsx`,
|
|
312
|
+
`Card.component.tsx` → `Card.component.test.tsx`,
|
|
313
|
+
`order.mutation.server.ts` → `order.mutation.server.test.ts`), so there is
|
|
314
|
+
nothing to decide. A change is not finished until its test exists and passes.
|
|
315
|
+
|
|
316
|
+
Two traps worth naming, because a green run can hide both:
|
|
317
|
+
|
|
318
|
+
- **`voltro test` transpiles without type-checking.** A type error in a TEST file
|
|
319
|
+
passes the suite and fails the package's `typecheck`. Run both.
|
|
320
|
+
- **Never fake a service to make a test pass.** A test that only re-asserts its
|
|
321
|
+
own mock's return value is worse than no test — it reports coverage it does not
|
|
322
|
+
have. Assert the descriptor shape instead and cover the real path elsewhere.
|
|
253
323
|
|
|
254
324
|
## app.config.ts
|
|
255
325
|
|
|
@@ -344,9 +414,13 @@ declare it). Mixins compose via `.with(...)` and auto-stamp bookkeeping columns:
|
|
|
344
414
|
```ts
|
|
345
415
|
export const todos = table('todos', { id: id(), title: text(), done: boolean().default(false) })
|
|
346
416
|
.with(softDelete(), tenant()) // tenant() transitively requires audit()
|
|
347
|
-
.reactive() // opt into the matcher engine (subscriptions)
|
|
348
417
|
```
|
|
349
418
|
|
|
419
|
+
- Every table is REACTIVE by default — nothing to opt into. `.nonReactive()`
|
|
420
|
+
turns reactivity OFF for a table entirely: no subscriber fires, locally or
|
|
421
|
+
across instances (the write still happens). Use it for a hot table nobody
|
|
422
|
+
subscribes to; never for one a query reads — that subscription never fires,
|
|
423
|
+
and `voltro dev` warns.
|
|
350
424
|
- `id()` defaults to a branded **TypeID** (`todo_…`). Insert auto-injects it.
|
|
351
425
|
- Indexes are **table-level** (`.index('name', [...])`) — there is NO
|
|
352
426
|
column-level `.index()`. Declare `.index([...])` AFTER `.with(...)` to address
|
package/templates/AGENTS.md
CHANGED
|
@@ -219,7 +219,7 @@ a `web/` (frontend).
|
|
|
219
219
|
│ │ └── rpcGroup.generated.ts # codegen — do NOT edit
|
|
220
220
|
│ └── web/
|
|
221
221
|
│ ├── app.config.ts # { type:'web', apis, theme, locales, … }
|
|
222
|
-
│ └── src/pages/**/*.tsx # file-based routing
|
|
222
|
+
│ └── src/pages/**/*.page.tsx # file-based routing (only *.page.tsx)
|
|
223
223
|
├── packages/ # shared libs
|
|
224
224
|
└── AGENTS.md / CLAUDE.md # this file
|
|
225
225
|
```
|
|
@@ -249,7 +249,77 @@ public REST routes, declared via `restRoutes` in `app.config.ts`).
|
|
|
249
249
|
| `*.agent.tsx` + `*.agent.server.tsx` | server-side LLM chat |
|
|
250
250
|
| `*.tool.tsx` | tool an agent can call |
|
|
251
251
|
| `*.entity.ts` / `*.schema.ts` / `schema.ts` | one table per file |
|
|
252
|
-
| `src/pages
|
|
252
|
+
| `*.page.tsx` (under `src/pages/`) | web page (file-based route) + `*.page.test.tsx` |
|
|
253
|
+
| `*.component.tsx` | exactly ONE component (+ types) |
|
|
254
|
+
| `*.component.ui.tsx` | presentational: one component, READS only — never writes |
|
|
255
|
+
| `*.hook.ts` | exactly ONE `use*` hook (+ types) |
|
|
256
|
+
| `*.types.ts` | types only — zero runtime exports |
|
|
257
|
+
| `*.internal.ts` | only its own directory subtree may import it |
|
|
258
|
+
| `*.fixture.ts` | test material — no production path may reach it |
|
|
259
|
+
| `*.tracking.ts` | the ONLY place analytics may be called from |
|
|
260
|
+
| `*.store.ts` | exactly ONE `defineStore` — client state, never server state |
|
|
261
|
+
| `*.client.ts` | declares itself + its transitive imports browser-safe |
|
|
262
|
+
|
|
263
|
+
**Only `*.page.tsx` is a route.** Everything else under `src/pages/` — components,
|
|
264
|
+
hooks, tests — is ordinary code with no URL, so put it next to the page that uses
|
|
265
|
+
it rather than in a distant `components/` directory. The suffix decides *whether*
|
|
266
|
+
a file is a route; the path under `src/pages/` decides *which URL*. A `*.page.tsx`
|
|
267
|
+
outside `src/pages/` never routes. A page MUST default-export its component —
|
|
268
|
+
without it `voltro dev` refuses to boot and names the file.
|
|
269
|
+
|
|
270
|
+
**Every suffix above is a CONTRACT, not a label.** Something else depends on each
|
|
271
|
+
promise, and `voltro doctor` enforces all of them:
|
|
272
|
+
|
|
273
|
+
- `*.component.ui.tsx` may read (`useT`, `useCan`, `usePermissions`) but must
|
|
274
|
+
never import a write hook (`useMutation`, `useAction`, `useUpload`, …). That is
|
|
275
|
+
what lets a caller render it ten thousand times in a list, reuse it across
|
|
276
|
+
features, and prerender it without reading its source. Lift the write into the
|
|
277
|
+
owning `*.component.tsx` and pass a handler down.
|
|
278
|
+
- `*.internal.ts` is the promise that refactoring inside that directory breaks
|
|
279
|
+
nobody. An import from another subtree revokes it.
|
|
280
|
+
- `*.types.ts` having no runtime export is what makes importing it free and makes
|
|
281
|
+
it impossible for it to sit in a runtime import cycle.
|
|
282
|
+
- `*.store.ts` holds ONE `defineStore`. Reads go through a selector
|
|
283
|
+
(`wizard.use((s) => s.step)`) — there is no whole-state read, because a
|
|
284
|
+
component holding all of it re-renders on every field. Scope a per-entity
|
|
285
|
+
instance with a KEY (`{ key: orderId }`), never a Provider. Seed it for SSR
|
|
286
|
+
from a loader with `seedStore(...)`; it rides the hydration payload the router
|
|
287
|
+
already writes. An action that writes SEVERAL fields wraps them in
|
|
288
|
+
`store.batch('label', () => …)` — one notification, one devtools entry, one
|
|
289
|
+
undo step, and a rollback if it throws; `await` first, then batch (an async
|
|
290
|
+
callback is an error). `{ persist: { key } }` survives a reload.
|
|
291
|
+
NEVER put server data in a store: a subscription is already
|
|
292
|
+
live, and a copy is not — the page then shows the stale one.
|
|
293
|
+
- `*.tracking.ts` is where every `defineTracking(...)` spec lives — the event
|
|
294
|
+
catalogue, so "what do we send to third parties" is a file listing rather than
|
|
295
|
+
archaeology. A component wires one up with `useTracking(spec, props, sink)`; it
|
|
296
|
+
NAMES a spec, it never declares one. (`useTracking` is a hook, so it is not
|
|
297
|
+
confined — only the declaration is.)
|
|
298
|
+
- Do NOT rename a file to `*.component.ui.tsx` just because it happens not to
|
|
299
|
+
write today. The suffix is a promise about what it MAY do.
|
|
300
|
+
|
|
301
|
+
**These rules apply to code YOU write — never to vendored code.** A shadcn
|
|
302
|
+
component (`npx shadcn add`) follows shadcn's conventions and is overwritten by
|
|
303
|
+
the next `add`; renaming it breaks their convention and is undone next run. A
|
|
304
|
+
directory is exempt when the app's `components.json` names it (`aliases.ui`) or
|
|
305
|
+
it carries a `.voltro-vendored` file whose first line says where the code came
|
|
306
|
+
from. Never add that marker to a directory of your own code — the point of the
|
|
307
|
+
rules is that everything we author follows them.
|
|
308
|
+
|
|
309
|
+
**Write the test in the same step as the code — always, for every primitive.**
|
|
310
|
+
Not "afterwards", not in a cleanup pass: the filename is derivable
|
|
311
|
+
(`users/[id].page.tsx` → `users/[id].page.test.tsx`,
|
|
312
|
+
`Card.component.tsx` → `Card.component.test.tsx`,
|
|
313
|
+
`order.mutation.server.ts` → `order.mutation.server.test.ts`), so there is
|
|
314
|
+
nothing to decide. A change is not finished until its test exists and passes.
|
|
315
|
+
|
|
316
|
+
Two traps worth naming, because a green run can hide both:
|
|
317
|
+
|
|
318
|
+
- **`voltro test` transpiles without type-checking.** A type error in a TEST file
|
|
319
|
+
passes the suite and fails the package's `typecheck`. Run both.
|
|
320
|
+
- **Never fake a service to make a test pass.** A test that only re-asserts its
|
|
321
|
+
own mock's return value is worse than no test — it reports coverage it does not
|
|
322
|
+
have. Assert the descriptor shape instead and cover the real path elsewhere.
|
|
253
323
|
|
|
254
324
|
## app.config.ts
|
|
255
325
|
|
|
@@ -344,9 +414,13 @@ declare it). Mixins compose via `.with(...)` and auto-stamp bookkeeping columns:
|
|
|
344
414
|
```ts
|
|
345
415
|
export const todos = table('todos', { id: id(), title: text(), done: boolean().default(false) })
|
|
346
416
|
.with(softDelete(), tenant()) // tenant() transitively requires audit()
|
|
347
|
-
.reactive() // opt into the matcher engine (subscriptions)
|
|
348
417
|
```
|
|
349
418
|
|
|
419
|
+
- Every table is REACTIVE by default — nothing to opt into. `.nonReactive()`
|
|
420
|
+
turns reactivity OFF for a table entirely: no subscriber fires, locally or
|
|
421
|
+
across instances (the write still happens). Use it for a hot table nobody
|
|
422
|
+
subscribes to; never for one a query reads — that subscription never fires,
|
|
423
|
+
and `voltro dev` warns.
|
|
350
424
|
- `id()` defaults to a branded **TypeID** (`todo_…`). Insert auto-injects it.
|
|
351
425
|
- Indexes are **table-level** (`.index('name', [...])`) — there is NO
|
|
352
426
|
column-level `.index()`. Declare `.index([...])` AFTER `.with(...)` to address
|
|
@@ -452,7 +526,7 @@ each plugin's own README.
|
|
|
452
526
|
|
|
453
527
|
| Topic | Open | Summary |
|
|
454
528
|
|---|---|---|
|
|
455
|
-
| **What's new in 0.
|
|
529
|
+
| **What's new in 0.14.0** | `node_modules/@voltro/cli/templates/agent-docs/whats-new.md` | Everything that changed in this version. Read it before hand-rolling something the framework may now ship. |
|
|
456
530
|
| AI | `node_modules/@voltro/cli/templates/agent-docs/ai.md` | How Voltro treats AI — agents, tools, streaming, RAG — all primitives over the same WebSocket as the rest of the framework. |
|
|
457
531
|
| Authentication | `node_modules/@voltro/cli/templates/agent-docs/authentication.md` | How @voltro/plugin-auth wires password + session-cookie auth across api + web, plus the pluggable identity-strategy protocol. |
|
|
458
532
|
| Caching | `node_modules/@voltro/cli/templates/agent-docs/caching.md` | Voltro's caching layer (@voltro/cache) — an always-on memory default, swappable Redis-compatible backends, a low-level wrap primitive, and automatic query-result invalidation. |
|
|
@@ -9,7 +9,7 @@ each plugin's own README.
|
|
|
9
9
|
|
|
10
10
|
| Topic | Open | Summary |
|
|
11
11
|
|---|---|---|
|
|
12
|
-
| **What's new in 0.
|
|
12
|
+
| **What's new in 0.14.0** | `node_modules/@voltro/cli/templates/agent-docs/whats-new.md` | Everything that changed in this version. Read it before hand-rolling something the framework may now ship. |
|
|
13
13
|
| AI | `node_modules/@voltro/cli/templates/agent-docs/ai.md` | How Voltro treats AI — agents, tools, streaming, RAG — all primitives over the same WebSocket as the rest of the framework. |
|
|
14
14
|
| Authentication | `node_modules/@voltro/cli/templates/agent-docs/authentication.md` | How @voltro/plugin-auth wires password + session-cookie auth across api + web, plus the pluggable identity-strategy protocol. |
|
|
15
15
|
| Caching | `node_modules/@voltro/cli/templates/agent-docs/caching.md` | Voltro's caching layer (@voltro/cache) — an always-on memory default, swappable Redis-compatible backends, a low-level wrap primitive, and automatic query-result invalidation. |
|
|
@@ -53,7 +53,7 @@
|
|
|
53
53
|
"group": null,
|
|
54
54
|
"description": "How Voltro's reactive data layer works — queries, mutations, actions, streams, all over one WebSocket with typed errors and tracked dependencies.",
|
|
55
55
|
"path": "agent-docs/data.md",
|
|
56
|
-
"files":
|
|
56
|
+
"files": 17
|
|
57
57
|
},
|
|
58
58
|
{
|
|
59
59
|
"id": "database/advancedqueries",
|
|
@@ -179,7 +179,7 @@
|
|
|
179
179
|
"group": null,
|
|
180
180
|
"description": "Scaffold a Voltro project and boot it locally in under a minute.",
|
|
181
181
|
"path": "agent-docs/introduction.md",
|
|
182
|
-
"files":
|
|
182
|
+
"files": 6
|
|
183
183
|
},
|
|
184
184
|
{
|
|
185
185
|
"id": "multi-tenancy",
|
|
@@ -525,6 +525,7 @@ interface AuthStrategy {
|
|
|
525
525
|
interface AuthStrategyInput {
|
|
526
526
|
readonly headers: Readonly<Record<string, string | undefined>>
|
|
527
527
|
readonly clientId: number // per-connection id (for soft re-auth)
|
|
528
|
+
readonly store?: DataStore // the app's store, for a DB-backed strategy
|
|
528
529
|
}
|
|
529
530
|
```
|
|
530
531
|
|
|
@@ -540,6 +541,34 @@ The `failed`-stops-the-chain rule is a **security** decision, not an ergonomic o
|
|
|
540
541
|
|
|
541
542
|
> Strategies must be **fast on the no-match path** — a cookie-name substring check, no IO — because every strategy runs on every request until one matches. Do JWKS fetches / DB lookups only *after* you've confirmed the request is yours, and cache them.
|
|
542
543
|
|
|
544
|
+
### Reading your database — `input.store`
|
|
545
|
+
|
|
546
|
+
A strategy that identifies the caller from a row — a session, an API key, a PAT
|
|
547
|
+
— gets the app's `DataStore` on its input:
|
|
548
|
+
|
|
549
|
+
```ts
|
|
550
|
+
const dbSession: AuthStrategy = {
|
|
551
|
+
id: 'db-session',
|
|
552
|
+
resolve: async ({ headers, store }) => {
|
|
553
|
+
const token = headers.authorization?.slice('Bearer '.length)
|
|
554
|
+
if (token === undefined) return { kind: 'skip' }
|
|
555
|
+
if (store === undefined) return { kind: 'skip' }
|
|
556
|
+
const [row] = await store.query(sessions.byToken(token))
|
|
557
|
+
return row ? { kind: 'matched', subject: toSubject(row) } : { kind: 'failed', reason: 'unknown token' }
|
|
558
|
+
},
|
|
559
|
+
}
|
|
560
|
+
```
|
|
561
|
+
|
|
562
|
+
It is the **boot** store, not a request-scoped one — strategies resolve before a
|
|
563
|
+
request store exists — and the same value `auth.resolveScopes` receives.
|
|
564
|
+
`undefined` only while the store is still being built (`voltro dev` builds it
|
|
565
|
+
after the auth chain) and on an app with no store, so a strategy should `skip`
|
|
566
|
+
rather than throw.
|
|
567
|
+
|
|
568
|
+
Read users, sessions, keys. A strategy that runs domain writes while deciding
|
|
569
|
+
who the caller is has the two jobs the wrong way round; nothing in the type
|
|
570
|
+
stops you, and it is still wrong.
|
|
571
|
+
|
|
543
572
|
## Composing the chain
|
|
544
573
|
|
|
545
574
|
`composeAuthStrategies` turns an ordered list of strategies into a single resolver. First `matched` wins; first `failed` short-circuits to anonymous.
|
|
@@ -573,8 +602,9 @@ If your authorization is a database ROLE rather than a scope on the token, the f
|
|
|
573
602
|
// app.config.ts
|
|
574
603
|
export default defineApiConfig({
|
|
575
604
|
auth: {
|
|
576
|
-
resolveScopes: async (subject) => {
|
|
577
|
-
|
|
605
|
+
resolveScopes: async (subject, { store }) => {
|
|
606
|
+
if (store === undefined) return [] // still booting — claim nothing
|
|
607
|
+
const role = await roleCache.get(subject.id, store) // cache it — see below
|
|
578
608
|
return role === 'admin' ? ['employee:admin', 'employee:read'] : ['employee:read']
|
|
579
609
|
},
|
|
580
610
|
},
|
|
@@ -591,6 +621,8 @@ export const payrollList = defineQuery({
|
|
|
591
621
|
})
|
|
592
622
|
```
|
|
593
623
|
|
|
624
|
+
**You get the app's DataStore.** A role lives in the database, and without it the only way to reach one was a second connection path beside the framework's — to the same database the request store opens a moment later. It is the BOOT store, not a request-scoped one: strategies resolve before a request store exists, so it is `undefined` while the store is still being built. Return `[]` then rather than guessing.
|
|
625
|
+
|
|
594
626
|
**Scopes only — never a Subject.** The hook cannot change `id` or `tenantId`: identity belongs to the auth strategy, and a hook that could rewrite it would be a forgery surface. The returned scopes are UNIONED with whatever the strategy already set, so a resolver can grant authority but never revoke it.
|
|
595
627
|
|
|
596
628
|
**It does not run for anonymous callers** — there is no identity to look a role up for.
|
|
@@ -154,6 +154,20 @@ requiring a scope no role grants, which makes that procedure permanently and
|
|
|
154
154
|
silently uncallable. (With a custom `resolvePermissions` the vocabulary isn't
|
|
155
155
|
exhaustive, so the rule stays quiet rather than flagging correct code.)
|
|
156
156
|
|
|
157
|
+
`rbac/unenforced-scope` is the same registry read in the other direction: a
|
|
158
|
+
scope a role GRANTS that no handler ever guards on. That direction has no
|
|
159
|
+
artefact to inspect — you cannot grep for an authorization check that was never
|
|
160
|
+
written, which is exactly why it survives review. One app modelled
|
|
161
|
+
`api-keys:write` in its role catalogue, complete and reviewed, and no handler
|
|
162
|
+
checked it: any member could mint a shared credential, and nothing failed.
|
|
163
|
+
Tests pass when an authorization check is missing.
|
|
164
|
+
|
|
165
|
+
It is a **warning**, because three innocent explanations exist: a plugin route
|
|
166
|
+
enforces it internally (the graph cannot see inside a plugin), a REST route
|
|
167
|
+
carries its own guards, or it is a UI-affordance scope that `useCan` reads to
|
|
168
|
+
hide a button and no server check backs on purpose. All three are fine. Not
|
|
169
|
+
knowing which is not.
|
|
170
|
+
|
|
157
171
|
#### Declared vs OBSERVED — reconciled against reality
|
|
158
172
|
|
|
159
173
|
A query's `source` and a mutation's `targets` are not documentation: the
|
|
@@ -466,6 +480,26 @@ The cleaner fix is on your side and worth doing anyway: start the container as
|
|
|
466
480
|
the workspace owner, `docker run --user $(id -u):$(id -g)`. Then nothing needs
|
|
467
481
|
handing over at all.
|
|
468
482
|
|
|
483
|
+
### What the boot tells you
|
|
484
|
+
|
|
485
|
+
Three checks run at boot and print one line each when they have something to say
|
|
486
|
+
— never fatal, and silent when the answer is fine:
|
|
487
|
+
|
|
488
|
+
- **A reactive table with no change trigger** (postgres). The schema fingerprint
|
|
489
|
+
covers columns, not triggers, so a restored dump or a hand-run `DROP TRIGGER`
|
|
490
|
+
leaves the schema "up to date" and the subscription silently not reaching other
|
|
491
|
+
instances. The reverse is reported too: a `.nonReactive()` table still carrying
|
|
492
|
+
a trigger pays `REPLICA IDENTITY FULL` and a NOTIFY per write for nothing.
|
|
493
|
+
- **A `.nonReactive()` table that a query reads.** That subscription will never
|
|
494
|
+
fire — first snapshot, then silence forever.
|
|
495
|
+
- **`apiKeys: true` with no `apikeys:issue:*` scope declared.** The capability is
|
|
496
|
+
on and reachable by nobody; every issue request fails its guard.
|
|
497
|
+
|
|
498
|
+
`voltro doctor` adds a fourth, over your source: an executor that writes
|
|
499
|
+
`ctx.request.subject.id` while naming no guard, which is how an anonymous caller
|
|
500
|
+
reaches a NOT NULL column and gets a raw statement failure instead of a typed
|
|
501
|
+
refusal.
|
|
502
|
+
|
|
469
503
|
## `voltro dev <appDir>`
|
|
470
504
|
|
|
471
505
|
```bash
|
|
@@ -684,6 +718,45 @@ The restart is a full re-exec — there is no in-process hot-reload of a
|
|
|
684
718
|
handler body; editing a query's executor respawns the child (debounced
|
|
685
719
|
80ms, so a burst of saves collapses into one restart).
|
|
686
720
|
|
|
721
|
+
## When the dev server stops
|
|
722
|
+
|
|
723
|
+
A restart replaces the child; the supervisor keeps watching. When the dev
|
|
724
|
+
server exits **on its own** — an aborted boot, or you stopping it — what
|
|
725
|
+
happens next depends on whether anyone is there to react.
|
|
726
|
+
|
|
727
|
+
**In a terminal**, a crashed boot is something you are about to fix, so
|
|
728
|
+
the supervisor keeps watching and tells you:
|
|
729
|
+
|
|
730
|
+
```text
|
|
731
|
+
dev server crashed — waiting for a file change exitCode=1
|
|
732
|
+
```
|
|
733
|
+
|
|
734
|
+
Fix the cause and save. The watcher restarts the server exactly as it
|
|
735
|
+
would for any other edit; you don't retype the command.
|
|
736
|
+
|
|
737
|
+
**Piped, backgrounded or in CI** — anywhere stdout is not a TTY — nobody
|
|
738
|
+
is going to fix anything, so `voltro dev` exits with the child's code:
|
|
739
|
+
|
|
740
|
+
```text
|
|
741
|
+
dev server exited — supervisor stopping exitCode=1
|
|
742
|
+
```
|
|
743
|
+
|
|
744
|
+
That half matters because the supervisor used to keep watching in *both*
|
|
745
|
+
cases: a shell that had long since closed still had a `voltro dev` behind
|
|
746
|
+
it holding a watcher, and a CI job that had "finished" kept its runner
|
|
747
|
+
busy. A failed boot is now a failed command wherever no one is looking.
|
|
748
|
+
|
|
749
|
+
A **clean** exit always stops the supervisor, watched or not — a dev
|
|
750
|
+
server ending on purpose is not something to wait out.
|
|
751
|
+
|
|
752
|
+
| Env | Effect |
|
|
753
|
+
|---|---|
|
|
754
|
+
| `VOLTRO_DEV_KEEP_ALIVE=1` | Wait for a fix even without a TTY — a CI runner with a TTY allocated, or a wrapper that pipes output while you watch it. |
|
|
755
|
+
| `VOLTRO_DEV_KEEP_ALIVE=0` | Exit on a crash even in a terminal. |
|
|
756
|
+
|
|
757
|
+
Neither can keep a clean exit alive; that would turn a deliberate
|
|
758
|
+
shutdown into a hang.
|
|
759
|
+
|
|
687
760
|
The `.env` trigger applies to **both api and web** apps — `process.env` is
|
|
688
761
|
parsed once at boot, so editing a `.env` (or re-pulling secrets, e.g.
|
|
689
762
|
`doppler secrets download > .env`) needs a full re-exec. `voltro dev` watches
|
|
@@ -1138,8 +1211,8 @@ A page is listed when ALL of these hold:
|
|
|
1138
1211
|
|
|
1139
1212
|
```text
|
|
1140
1213
|
• renderMode:'spa' candidates (2 pages — loader-free, under a layout, currently ssr/default):
|
|
1141
|
-
src/pages/dashboard/index.tsx (/dashboard) — default renderMode
|
|
1142
|
-
src/pages/admin/settings.tsx (/admin/settings) — renderMode:'ssr'
|
|
1214
|
+
src/pages/dashboard/index.page.tsx (/dashboard) — default renderMode
|
|
1215
|
+
src/pages/admin/settings.page.tsx (/admin/settings) — renderMode:'ssr'
|
|
1143
1216
|
→ renderMode:'spa' skips this page's SSR compile while its layout shell still renders server-side — adopt it if the page BODY does not need SSR (internal/authenticated pages); keep 'ssr' if the page content needs SEO or server first-paint.
|
|
1144
1217
|
```
|
|
1145
1218
|
|
|
@@ -2221,6 +2294,7 @@ voltro update # bump to the latest published version, install, r
|
|
|
2221
2294
|
voltro update --to 0.4.0 # pin an explicit target version
|
|
2222
2295
|
voltro update --dry-run # preview the bump + which codemods would run — writes nothing
|
|
2223
2296
|
voltro update --force # allow a dirty working tree (not recommended)
|
|
2297
|
+
voltro update --only 0.14.0/03_pages-suffix # run just these codemod(s), comma-separated
|
|
2224
2298
|
voltro update --exact # pin exact versions (drop the ^ / ~ range prefix)
|
|
2225
2299
|
voltro update --help # every flag — always answered, even on a dirty tree
|
|
2226
2300
|
|
|
@@ -2229,6 +2303,26 @@ voltro update --codemods-only --from 0.3.0 # re-run codemods 0.3.0
|
|
|
2229
2303
|
voltro update --codemods-only --from 0.3.0 --to 0.4.0 # explicit delta
|
|
2230
2304
|
```
|
|
2231
2305
|
|
|
2306
|
+
### Taking only part of the jump — `--only`
|
|
2307
|
+
|
|
2308
|
+
Ids are what `--dry-run` prints:
|
|
2309
|
+
|
|
2310
|
+
```bash
|
|
2311
|
+
voltro update --dry-run
|
|
2312
|
+
voltro update --codemods-only --from 0.13.0 --only 0.14.0/03_pages-suffix,0.14.0/02_reactive-by-default
|
|
2313
|
+
```
|
|
2314
|
+
|
|
2315
|
+
Useful when part of a jump is load-bearing (without it the app does not build or
|
|
2316
|
+
its routes 404) and part is elective: take the necessary ones, get back to a
|
|
2317
|
+
committable tree, then run the rest. An id that matches nothing in the jump is an
|
|
2318
|
+
error listing the ids that do — "it did nothing" and "you typed it wrong" would
|
|
2319
|
+
otherwise look identical.
|
|
2320
|
+
|
|
2321
|
+
There is no `--required` flag, deliberately. "Required" would have to mean *this
|
|
2322
|
+
app does not run without it*, and that is a property of your app rather than of
|
|
2323
|
+
the codemod: the pages rename is unavoidable for a project with pages and
|
|
2324
|
+
irrelevant to an api-only one. You know which ones you need; we would be guessing.
|
|
2325
|
+
|
|
2232
2326
|
## In a workspace, the whole workspace moves
|
|
2233
2327
|
|
|
2234
2328
|
Run `voltro update` anywhere inside a workspace — a `pnpm-workspace.yaml`, or a
|
|
@@ -2299,6 +2393,26 @@ voltro update --codemods-only --from 0.5.0 --to 0.6.0
|
|
|
2299
2393
|
|
|
2300
2394
|
Every `@voltro/*` entry in `dependencies` and `devDependencies` — in every workspace member, see above — with the range style preserved (`^0.3.0` stays caret, `~0.3.0` stays tilde) unless you pass `--exact`. Non-registry specs (`workspace:*`, `catalog:`, `link:`, …) are left untouched — they're already resolved by your monorepo or catalog.
|
|
2301
2395
|
|
|
2396
|
+
## When the install cannot run on this host
|
|
2397
|
+
|
|
2398
|
+
Some projects install in a container with their own store, from an offline
|
|
2399
|
+
mirror, or in a locked-down CI image. `voltro update` runs your package manager
|
|
2400
|
+
on the machine you invoke it from, so on those hosts the install step fails —
|
|
2401
|
+
and it fails *after* the version bump is written, which leaves the tree
|
|
2402
|
+
half-upgraded.
|
|
2403
|
+
|
|
2404
|
+
`--no-install` splits the command where those projects need it split:
|
|
2405
|
+
|
|
2406
|
+
```bash
|
|
2407
|
+
voltro update --no-install --to 0.14.0 # writes the bump, stops, says what is left
|
|
2408
|
+
# ...install however this project installs...
|
|
2409
|
+
voltro update --codemods-only --from 0.13.0 --to 0.14.0
|
|
2410
|
+
```
|
|
2411
|
+
|
|
2412
|
+
Step two is not optional and the command says so: the codemods for a jump ship
|
|
2413
|
+
**inside** the target version, so nothing can run them until the install has put
|
|
2414
|
+
that version on disk.
|
|
2415
|
+
|
|
2302
2416
|
## Which package manager
|
|
2303
2417
|
|
|
2304
2418
|
`voltro update` never assumes npm. It resolves your project's package manager in this order, starting in the app directory and walking **up to the repo root**:
|