@voltro/cli 0.5.0 → 0.7.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.
Files changed (87) hide show
  1. package/CHANGELOG.md +68 -1
  2. package/bin/voltro.mjs +63 -2
  3. package/dist/apiBuild-CSM74oEZ.js +2 -0
  4. package/dist/{apiBuild-CZugTK46.js → apiBuild-deIjJKJm.js} +2 -1
  5. package/dist/bin.js +2 -2
  6. package/dist/{commands-CJheDVOr.js → commands-BHdc71DG.js} +2671 -2960
  7. package/dist/dev-BYfrYwrT.js +2 -0
  8. package/dist/dev-Bqyq62Ak.js +6492 -0
  9. package/dist/devActivity-1WtIVyHc.js +153 -0
  10. package/dist/devActivity.d.ts +46 -0
  11. package/dist/devActivity.js +2 -0
  12. package/dist/index.js +1 -1
  13. package/dist/inspectMetrics-BLUBAWLb.js +3711 -0
  14. package/dist/serveCommand-Dn6--0Bv.js +1111 -0
  15. package/dist/serveEntry.js +3 -2
  16. package/dist/start-CWZW_4Et.js +941 -0
  17. package/dist/startEntry.d.ts +5 -0
  18. package/dist/startEntry.js +3 -0
  19. package/package.json +22 -17
  20. package/templates/AGENTS.core.md +36 -1
  21. package/templates/AGENTS.md +36 -1
  22. package/templates/agent-docs/_manifest.json +3 -3
  23. package/templates/agent-docs/ai.md +35 -3
  24. package/templates/agent-docs/authentication.md +264 -0
  25. package/templates/agent-docs/cli.md +126 -3
  26. package/templates/agent-docs/configuration.md +38 -0
  27. package/templates/agent-docs/data.md +616 -40
  28. package/templates/agent-docs/database/querying.md +151 -3
  29. package/templates/agent-docs/database/schema.md +1 -0
  30. package/templates/agent-docs/deployment.md +171 -0
  31. package/templates/agent-docs/internationalization.md +2 -0
  32. package/templates/agent-docs/plugins.md +4 -4
  33. package/templates/agent-docs/reference.md +56 -2
  34. package/templates/agent-docs/templates/apibackends.md +13 -2
  35. package/templates/agent-docs/testing.md +215 -3
  36. package/templates/apps/api-ai/package.json +7 -7
  37. package/templates/apps/api-auth/app.config.ts +20 -13
  38. package/templates/apps/api-auth/package.json +8 -8
  39. package/templates/apps/api-backend/package.json +7 -7
  40. package/templates/apps/api-backend-deactivation/package.json +7 -7
  41. package/templates/apps/api-backend-mail/package.json +8 -8
  42. package/templates/apps/api-backend-mariadb/.env.example +10 -4
  43. package/templates/apps/api-backend-mariadb/package.json +9 -9
  44. package/templates/apps/api-backend-storage/package.json +8 -8
  45. package/templates/apps/api-data-advanced/app.config.ts +21 -8
  46. package/templates/apps/api-data-advanced/package.json +8 -8
  47. package/templates/apps/api-durable/package.json +8 -8
  48. package/templates/apps/api-feature-flags/package.json +9 -9
  49. package/templates/apps/api-governance/app.config.ts +15 -2
  50. package/templates/apps/api-governance/package.json +8 -8
  51. package/templates/apps/api-kv/package.json +8 -8
  52. package/templates/apps/api-moderation/package.json +8 -8
  53. package/templates/apps/api-observability/package.json +8 -8
  54. package/templates/apps/api-ratelimit/package.json +8 -8
  55. package/templates/apps/api-rbac/package.json +8 -8
  56. package/templates/apps/api-rest/package.json +7 -7
  57. package/templates/apps/api-saas/package.json +11 -11
  58. package/templates/apps/api-search/package.json +8 -8
  59. package/templates/apps/api-versioning/package.json +8 -8
  60. package/templates/apps/api-webhooks/app.config.ts +20 -1
  61. package/templates/apps/api-webhooks/package.json +8 -8
  62. package/templates/apps/changelog/package.json +6 -6
  63. package/templates/apps/edge-functions/package.json +2 -2
  64. package/templates/apps/frontend-admin/package.json +8 -8
  65. package/templates/apps/frontend-app/package.json +8 -8
  66. package/templates/apps/frontend-blank/package.json +7 -7
  67. package/templates/apps/frontend-contact/package.json +7 -7
  68. package/templates/apps/frontend-dashboard/package.json +7 -7
  69. package/templates/apps/frontend-docs/package.json +7 -7
  70. package/templates/apps/frontend-i18n/package.json +6 -6
  71. package/templates/apps/frontend-landing/package.json +7 -7
  72. package/templates/apps/frontend-spa/package.json +7 -7
  73. package/templates/apps/frontend-ssr/package.json +7 -7
  74. package/templates/apps/frontend-ssr-api/package.json +8 -8
  75. package/templates/apps/frontend-static-blog/package.json +6 -6
  76. package/templates/baselines/bare/.env.example +10 -3
  77. package/templates/baselines/compose/.env.example +10 -2
  78. package/templates/baselines/compose-mariadb/.env.example +10 -2
  79. package/templates/baselines/helm/.env.example +10 -3
  80. package/dist/apiBuild-D-iBYKa3.js +0 -2
  81. package/dist/dev-B71ICpXJ.js +0 -9896
  82. package/dist/dev-DNUetyG2.js +0 -2
  83. package/dist/serveCommand-C0WFA396.js +0 -1084
  84. package/templates/apps/api-auth/.env +0 -17
  85. package/templates/apps/api-data-advanced/.env +0 -17
  86. package/templates/apps/api-governance/.env +0 -4
  87. package/templates/apps/api-webhooks/.env +0 -6
@@ -765,12 +765,13 @@ It covers both halves of the stack:
765
765
  | Scope | It notices | Reach for |
766
766
  |---|---|---|
767
767
  | server | `if (!rows[0]) throw …` | `.one()` / `.first()` |
768
- | server | 3+ sequential `store.query` in one handler | `relations()` + `.with()` |
768
+ | server | 3+ sequential `store.query` in one handler | `relations()` + `.with()` — or `Effect.all` |
769
769
  | server | `Effect.promise(() => ctx.store.…)` | `yield* EffectStore` |
770
770
  | server | `requireScope(...)` at the top of an executor | `guards:` on the descriptor |
771
771
  | server | a `token` / `secret` / `password` column with no encryption | `.encrypted()` |
772
772
  | server | a notify / webhook helper called at a mutation's tail | `defineSubscriber` / `defineReaction` |
773
773
  | server | `hasMore` + `limit + 1` | `paginateById` |
774
+ | server | `.getTime()` / `.toISOString()` mapping a row on the way out | `rowSchema(table)` as the descriptor's `output` |
774
775
  | client | per-field `useState` + a submit flag | `useFormBinding` |
775
776
  | client | a table with local sort/filter state | `useDataTable` |
776
777
  | client | `FileReader` / `readAsDataURL` | `useUpload` |
@@ -783,6 +784,71 @@ The rules are deliberately conservative — a detector that cries wolf trains yo
783
784
  to ignore it. A column that already carries `.encrypted()`, or a handler that
784
785
  already uses `.one()`, stays silent.
785
786
 
787
+ Two of them are worth spelling out, because their advice is not one-line:
788
+
789
+ **The credential-column rule skips names that aren't credentials.** A name ending
790
+ in `Id` / `_id`, a name ending in `Hash` / `_hash`, and a name beginning with
791
+ `vault` are all left alone:
792
+
793
+ | Column | Why it's skipped |
794
+ |---|---|
795
+ | `jiraSecretId`, `token_id` | an IDENTIFIER of a secret held elsewhere, not the secret |
796
+ | `apiKeyHash`, `password_hash` | the hash IS the protection — encrypting it is nonsense, and it breaks the column as a unique lookup key |
797
+ | `vaultToken` | a HANDLE into a secret store, naming a secret held elsewhere |
798
+
799
+ The suffix tests use a camelCase / underscore boundary on purpose: a blind
800
+ `/id$/i` would also swallow `apiKeyValid`, while `tokenIdentifier` — which ends
801
+ in neither — must still fire.
802
+
803
+ **The sequential-reads rule names TWO levers, and the criterion for choosing.**
804
+ Both shapes chain later reads off earlier results, so no text-level heuristic can
805
+ split them — you make the call:
806
+
807
+ - The reads are a **parent → child walk on ONE key** → declare `relations()` in a
808
+ `*.relations.ts` and collapse them into `.with({ … })`: one JSON-aggregate
809
+ query, on every dialect.
810
+ - The reads **collect ids from SEVERAL sources** (JSON-array references, a
811
+ junction carrying extra columns, JS-side sorting) → keep the assembly and run
812
+ the independent LEADING reads under `Effect.all`. Same queries, same results,
813
+ only concurrent — zero parity risk.
814
+
815
+ The second case is the common one. Measured on a real 74-hit codebase, about two
816
+ handlers were clean full-parity `relations()` conversions and the other ~72 were
817
+ multi-source assemblies where `.with()` covers only part of the work or subtly
818
+ changes behaviour. Prescribing `relations()` for all of them would be wrong ~97%
819
+ of the time — and advice that is usually wrong trains you to ignore the finding.
820
+
821
+ The human view shows the first three file paths per finding and says how many it
822
+ withheld. Those paths are the actionable part — a count you cannot turn back
823
+ into a work list tells you the size of the problem, not how to fix it — and the
824
+ matching rule lives inside the CLI, so you cannot re-derive the list with your
825
+ own grep. `--json` prints the complete scan, nothing elided, with no preflight
826
+ output mixed in:
827
+
828
+ ```bash
829
+ voltro doctor . --json # the complete scan: every file path, machine-readable
830
+ ```
831
+
832
+ ```json
833
+ {
834
+ "root": "/app/api",
835
+ "scannedFiles": 214,
836
+ "scannedDirs": ["queries", "mutations", "database"],
837
+ "findings": [
838
+ {
839
+ "id": "row-not-found",
840
+ "scope": "server",
841
+ "smell": "hand-written not-found branch on rows[0]",
842
+ "use": ".one() — fails with the typed NoRowFound on zero rows AND on more than one",
843
+ "files": ["queries/team.get.ts", "queries/user.get.ts", "…"]
844
+ }
845
+ ]
846
+ }
847
+ ```
848
+
849
+ That is the form to hand an agent, or to pipe into a script that works the list
850
+ file by file.
851
+
786
852
  ## `voltro capabilities` — what the framework actually exports
787
853
 
788
854
  Asked "what does this framework export", a language model will produce a
@@ -1756,7 +1822,7 @@ _voltro update — bump the framework to the latest version and run the codemods
1756
1822
  `voltro update` upgrades an app to the latest framework release. It does three things in order:
1757
1823
 
1758
1824
  1. **Bump** every `@voltro/*` dependency in `package.json` to the target version.
1759
- 2. **Install** with your package manager (detected from the lockfile pnpm / npm / yarn / bun).
1825
+ 2. **Install** with your project's package manager — see [Which package manager](#which-package-manager) below.
1760
1826
  3. **Run the codemods** shipped with the target version — automatic source rewrites for any breaking API change, plus printed manual steps for anything that can't be automated.
1761
1827
 
1762
1828
  ```bash
@@ -1765,12 +1831,47 @@ voltro update --to 0.4.0 # pin an explicit target version
1765
1831
  voltro update --dry-run # preview the bump + which codemods would run — writes nothing
1766
1832
  voltro update --force # allow a dirty working tree (not recommended)
1767
1833
  voltro update --exact # pin exact versions (drop the ^ / ~ range prefix)
1834
+ voltro update --help # every flag — always answered, even on a dirty tree
1768
1835
 
1769
1836
  # Recover the codemods after a MANUAL version bump (no bump, no install):
1770
1837
  voltro update --codemods-only --from 0.3.0 # re-run codemods 0.3.0 → installed
1771
1838
  voltro update --codemods-only --from 0.3.0 --to 0.4.0 # explicit delta
1772
1839
  ```
1773
1840
 
1841
+ ## In a workspace, the whole workspace moves
1842
+
1843
+ Run `voltro update` anywhere inside a workspace — a `pnpm-workspace.yaml`, or a
1844
+ `workspaces` field in an ancestor `package.json` — and **every member
1845
+ `package.json` that declares `@voltro/*` is bumped to the same version**, with
1846
+ the install running **once at the workspace root**.
1847
+
1848
+ This is not a convenience. Your api and your web app share generated types (the
1849
+ rpcGroup) and a session cookie shape; if the api moves to 0.6.0 while
1850
+ `apps/web` and `packages/ui-*` stay on 0.5.0, the mismatch shows up as a runtime
1851
+ decode error in the browser, not as a build failure. Half-upgraded is the worst
1852
+ state to be in, so `voltro update` never leaves you there.
1853
+
1854
+ The plan output — and `--dry-run` — lists every file it will touch:
1855
+
1856
+ ```text
1857
+ voltro update: 0.5.0 → 0.6.0
1858
+ workspace: /repo (4 package.json with @voltro/* deps)
1859
+ package.json
1860
+ @voltro/cli: ^0.5.0 → ^0.6.0
1861
+ apps/api/package.json
1862
+ @voltro/cli: ^0.5.0 → ^0.6.0
1863
+ @voltro/database: ^0.5.0 → ^0.6.0
1864
+ apps/web/package.json
1865
+ @voltro/client: ^0.5.0 → ^0.6.0
1866
+ packages/ui-admin/package.json
1867
+ @voltro/web: ~0.5.0 → ~0.6.0
1868
+ package manager: pnpm
1869
+ install runs in: /repo
1870
+ ```
1871
+
1872
+ A standalone (non-workspace) project is unaffected: its own `package.json`, its
1873
+ own install, in place.
1874
+
1774
1875
  ## Already bumped by hand? Recover the codemods
1775
1876
 
1776
1877
  If you bump `@voltro/*` versions in `package.json` yourself and install first, a
@@ -1793,9 +1894,31 @@ source version.
1793
1894
 
1794
1895
  Codemods **rewrite your source**, so you need a clean diff to review afterwards. `voltro update` refuses to run on a dirty git working tree — commit or stash first. Use `--dry-run` to preview without touching anything, or `--force` to override the guard (you accept a mixed diff).
1795
1896
 
1897
+ `--help` / `-h` is answered *before* the guard, so `voltro update --help` prints the flag list even on a dirty tree. The same holds for `voltro doctor --help`.
1898
+
1899
+ ## If the install fails
1900
+
1901
+ The bump is written before the install runs, so a failed install leaves your `package.json` on the target version — and **no codemods applied**. `voltro update` says so explicitly, because the codemods for a jump ship *inside* the target version: a failed install never put them on disk, so there is nothing that could have run them. Fix the install, run it, then apply the codemods you are missing with the command the failure message prints for you:
1902
+
1903
+ ```bash
1904
+ voltro update --codemods-only --from 0.5.0 --to 0.6.0
1905
+ ```
1906
+
1796
1907
  ## What gets bumped
1797
1908
 
1798
- Every `@voltro/*` entry in `dependencies` and `devDependencies`, 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.
1909
+ 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.
1910
+
1911
+ ## Which package manager
1912
+
1913
+ `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**:
1914
+
1915
+ 1. The **`packageManager` field** in a `package.json` (the corepack standard) — authoritative, wins over any lockfile.
1916
+ 2. A **lockfile** at that level — `pnpm-lock.yaml`, `yarn.lock`, `bun.lock` / `bun.lockb`, `package-lock.json`.
1917
+ 3. **npm**, only when nothing declares one.
1918
+
1919
+ Walking up matters in a workspace: a scaffolded Voltro project keeps its lockfile at the monorepo root, so running `voltro update` from `apps/api` still finds `pnpm` rather than falling back to npm and running `npm install` against a pnpm workspace.
1920
+
1921
+ The same resolved manager is used for the **registry lookup** of the latest version (`pnpm view`, `yarn npm info`, `bun pm view`), so a private or scoped registry configured in your `.npmrc` / `.yarnrc.yml` is honored. `npm view` is only a last-resort fallback.
1799
1922
 
1800
1923
  ## Codemods
1801
1924
 
@@ -91,6 +91,44 @@ build-time constant, still works.)
91
91
  | `envVar.boolean(opts)` | `boolean` | `true/1/yes/on` ↔ `false/0/no/off/""` |
92
92
  | `envVar.url(opts)` | `string` | validated with `URL` |
93
93
  | `envVar.enum([…] as const, opts)` | literal union | must be one of the values |
94
+ | `envVar.secret(opts)` | `string` | server-only, required, length floor |
95
+
96
+ ### `envVar.secret` — for values that must not be guessable
97
+
98
+ ```ts
99
+ env: defineEnv({
100
+ // Ours to invent → `voltro dev` mints one per project.
101
+ VOLTRO_SESSION_SECRET: envVar.secret({ generate: 'base64url' }),
102
+ // Someone else's to issue → must be fetched, never invented.
103
+ STRIPE_SECRET_KEY: envVar.secret({ minLength: 20 }),
104
+ })
105
+ ```
106
+
107
+ It differs from `envVar.string({ access: 'secret' })` in three ways, each
108
+ closing a specific failure:
109
+
110
+ - **`access` is forced to `'secret'`** — a value with a length floor is never
111
+ something you meant to bundle into a browser.
112
+ - **There is no `default`.** A default secret is not a secret: every
113
+ deployment that forgot to set the variable would share it.
114
+ - **A length floor** (default 32). Presence alone does not catch the real
115
+ failure mode — a variable that is *set* but reads `change-me` signs forgeable
116
+ cookies while looking completely healthy.
117
+
118
+ **`generate` opts a variable into per-project minting.** On first `voltro dev`,
119
+ any declared-but-unset mintable secret is written to a gitignored `.env.local`
120
+ and the boot continues. That is why no Voltro template ships a secret value: a
121
+ placeholder in a template is a signing key published to everyone who downloads
122
+ it, and it passes every check you could write.
123
+
124
+ Leave `generate` off for anything a third party issues. A WorkOS API key is
125
+ just as secret and just as required, but inventing one produces a value that
126
+ merely *looks* right and authenticates nobody — better that the boot gate fails
127
+ and a human fetches the real one.
128
+
129
+ Minting is **development only**. `voltro serve`, `build` and `start` never mint:
130
+ in production a missing secret is a boot failure, which is the whole point.
131
+ Generate deployment values with `voltro secret generate <purpose>`.
94
132
 
95
133
  Every builder takes `{ access, optional?, default?, description?, example? }`.
96
134
  Use `default` for a fallback (the value type stays present, `A`); use