@voltro/cli 0.48.0 → 0.50.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 +125 -0
- package/dist/apiBuild-BWo8KI6n.js +2 -0
- package/dist/{apiBuild-Dz8TJSxp.js → apiBuild-CFYXo06U.js} +1 -1
- package/dist/bin.js +1 -1
- package/dist/{build-DLme2ppa.js → build-CnSH-zru.js} +6 -6
- package/dist/{checkCommand-Dals7g-I.js → checkCommand-Do3SQbLx.js} +3 -3
- package/dist/{checkCommand-BUBqPBlI.js → checkCommand-OL41BMIb.js} +1 -1
- package/dist/{codegenCommand-L6vacVRW.js → codegenCommand-BJzaRz6q.js} +39 -47
- package/dist/{codemodRunner-DSZjgyCL.js → codemodRunner-De3k0pRd.js} +859 -629
- package/dist/{commands-CAxFLqG4.js → commands-By23Ljny.js} +68 -60
- package/dist/{dashboardCommand-CJC8Kg-s.js → dashboardCommand-DN1rAABE.js} +1 -1
- package/dist/dataCommand-B93TUBIx.js +1306 -0
- package/dist/dataProfile-Du3ztYxv.js +51 -0
- package/dist/{dbCommand-ZGNItjoJ.js → dbCommand-Bf7Cy0mn.js} +2 -2
- package/dist/dbCommand-Bnl7MSGC.js +2 -0
- package/dist/{dev-lvnVq5RD.js → dev-1yu42ubu.js} +2 -2
- package/dist/{dev-cGGsLNn6.js → dev-BWwE0QjK.js} +1 -1
- package/dist/doctorCommand-BmovAwb7.js +2 -0
- package/dist/{doctorCommand-BK0qu7eD.js → doctorCommand-y-EbP8st.js} +12 -12
- package/dist/{dormancyCommand-CyIk-zq4.js → dormancyCommand-Dc7KRNCG.js} +1 -1
- package/dist/{embeddingsCommand-D7TLklps.js → embeddingsCommand-kPs132bB.js} +1 -1
- package/dist/{envCommand-BHxKkGXq.js → envCommand-E12zSt51.js} +1 -1
- package/dist/{evolveCommand-CQh6dw21.js → evolveCommand-B3nagPVc.js} +2 -2
- package/dist/{frameworkTableAssembly-BNod_DKN.js → frameworkTableAssembly-BwJVEKLr.js} +3 -3
- package/dist/frameworkTableAssembly-CVDB2hCq.js +2 -0
- package/dist/index.js +1 -1
- package/dist/{infoCommand-CuNl9cbh.js → infoCommand-Cq0Yz98u.js} +1 -1
- package/dist/{interruptedReplace-C1ZI84xi.js → interruptedReplace-C3O3M1MM.js} +2 -1
- package/dist/interruptedReplace-CvmiAM9K.js +2 -0
- package/dist/{migrate-CL5Ed01M.js → migrate-D3MK9BpK.js} +2 -2
- package/dist/{runtimeTrace-C0SlIoQY.js → runtimeTrace-BpLUcrfy.js} +1 -1
- package/dist/{sdkgen-BXm7zx7F.js → sdkgen-dl4-WKF6.js} +1 -1
- package/dist/serveCommand-8CTzQCY0.js +2362 -0
- package/dist/serveCommand-C_WI4TTG.js +2 -0
- package/dist/serveEntry.js +5 -5
- package/dist/{subcommandNames-CKG5Dz3a.js → subcommandNames-DpYs3DXr.js} +12 -2
- package/dist/updateCommand-D91-TZ7E.js +2 -0
- package/dist/{updateCommand-BlyXavoG.js → updateCommand-i8JbrLAA.js} +2 -1
- package/dist/{webhooksCommand-QlAk_tED.js → webhooksCommand-B0bdyuQ4.js} +1 -1
- package/package.json +47 -17
- package/templates/AGENTS.md +1 -1
- package/templates/agent-docs/_index.md +1 -1
- package/templates/agent-docs/cli.md +124 -16
- package/templates/agent-docs/whats-new.md +30 -52
- package/templates/apps/api-ai/package.json +7 -7
- package/templates/apps/api-auth/package.json +8 -8
- package/templates/apps/api-backend/package.json +7 -7
- package/templates/apps/api-backend-deactivation/package.json +7 -7
- package/templates/apps/api-backend-mail/package.json +8 -8
- package/templates/apps/api-backend-mariadb/package.json +9 -9
- package/templates/apps/api-backend-sqlite/package.json +8 -8
- package/templates/apps/api-backend-storage/package.json +8 -8
- package/templates/apps/api-cms/package.json +10 -10
- package/templates/apps/api-collab/package.json +8 -8
- package/templates/apps/api-data-advanced/package.json +8 -8
- package/templates/apps/api-durable/package.json +8 -8
- package/templates/apps/api-feature-flags/package.json +9 -9
- package/templates/apps/api-governance/package.json +8 -8
- package/templates/apps/api-kv/package.json +8 -8
- package/templates/apps/api-moderation/package.json +8 -8
- package/templates/apps/api-observability/package.json +8 -8
- package/templates/apps/api-ratelimit/package.json +8 -8
- package/templates/apps/api-rbac/package.json +8 -8
- package/templates/apps/api-rest/package.json +7 -7
- package/templates/apps/api-saas/package.json +11 -11
- package/templates/apps/api-saas-starter/package.json +10 -10
- package/templates/apps/api-search/package.json +8 -8
- package/templates/apps/api-status/package.json +8 -8
- package/templates/apps/api-versioning/package.json +8 -8
- package/templates/apps/api-webhooks/package.json +9 -9
- package/templates/apps/changelog/package.json +6 -6
- package/templates/apps/edge-functions/package.json +2 -2
- package/templates/apps/frontend-admin/package.json +8 -8
- package/templates/apps/frontend-app/package.json +9 -9
- package/templates/apps/frontend-auth/package.json +8 -8
- package/templates/apps/frontend-blank/package.json +7 -7
- package/templates/apps/frontend-cms/package.json +9 -9
- package/templates/apps/frontend-collab/package.json +10 -10
- package/templates/apps/frontend-contact/package.json +7 -7
- package/templates/apps/frontend-dashboard/package.json +7 -7
- package/templates/apps/frontend-docs/package.json +7 -7
- package/templates/apps/frontend-i18n/package.json +6 -6
- package/templates/apps/frontend-landing/package.json +7 -7
- package/templates/apps/frontend-portal/package.json +8 -8
- package/templates/apps/frontend-saas/package.json +8 -8
- package/templates/apps/frontend-spa/package.json +7 -7
- package/templates/apps/frontend-ssr/package.json +7 -7
- package/templates/apps/frontend-ssr-api/package.json +8 -8
- package/templates/apps/frontend-static-blog/package.json +6 -6
- package/templates/apps/frontend-status/package.json +8 -8
- package/templates/apps/mobile-app/package.json +4 -4
- package/dist/apiBuild-DOnvi2zm.js +0 -2
- package/dist/dataCommand-hIaq2iKY.js +0 -1052
- package/dist/dataProfile-Cm0YVKSy.js +0 -18
- package/dist/dbCommand-D1Q33ktl.js +0 -2
- package/dist/doctorCommand-CWgUPye-.js +0 -2
- package/dist/frameworkTableAssembly-Rft-DPUg.js +0 -2
- package/dist/interruptedReplace-DNJ7MKph.js +0 -2
- package/dist/serveCommand-CV3YO9L3.js +0 -2035
- package/dist/serveCommand-ZP_Cb-yz.js +0 -2
- package/dist/updateCommand-CT5AvVg7.js +0 -2
package/CHANGELOG.md
CHANGED
|
@@ -39,6 +39,131 @@ _Changes staged for the next release accumulate here (rolled up from
|
|
|
39
39
|
|
|
40
40
|
---
|
|
41
41
|
|
|
42
|
+
## [0.50.0] — 2026-08-23
|
|
43
|
+
|
|
44
|
+
### ⚠ BREAKING
|
|
45
|
+
|
|
46
|
+
- **@voltro/cli** — The read-only run-history endpoint moved:
|
|
47
|
+
|
|
48
|
+
GET /_voltro/admin/imports → GET /_voltro/admin/transfers
|
|
49
|
+
|
|
50
|
+
Same secret, same query parameters, same response shape. Only the path changed.
|
|
51
|
+
|
|
52
|
+
It answers for four directions now — import, export, native backup, native restore — and it named one of them. The subcommand and the table were renamed for exactly that reason in 0.49.0 and this path was left behind, which is worse than renaming none of them: a reader who follows a rename tries the matching path, gets a 404, and concludes the instance is too old.
|
|
53
|
+
|
|
54
|
+
`voltro data transfers --target api` uses the new path for you. What needs a hand is anything calling it directly — a monitoring check, an uptime probe, a curl in a runbook, a dashboard datasource. The codemod is `manual` and declares `reach: 'beyond-source'`, so it prints whether or not the path is found in your repository.
|
|
55
|
+
- **@voltro/database, @voltro/voltro** — `InterruptedReplace.tables` is `number | null`, and the interface gains `kind?: 'replace' | 'restore'`.
|
|
56
|
+
|
|
57
|
+
`_voltro_replace_in_progress` records two destructive operations now — an import's `--mode replace` and a native `voltro data restore`. A restore replaces the whole database from an artefact, so "how many tables was this going to empty and refill" has no answer: not known up front, not meaningful after.
|
|
58
|
+
|
|
59
|
+
Writing `0` would have preserved the type and been worse. That number is rendered into the boot refusal, which is read under pressure, and it would have said `began emptying 0 table(s)` — a measurement that was never taken. The framework's own text says "this database" instead; `Number(null)` is `0`, so coercing it reintroduces exactly the sentence being avoided.
|
|
60
|
+
|
|
61
|
+
`DataTransferRun.direction` also widens to name `'backup'` and `'restore'` explicitly. That one is documentary — the union already ended in `| string`, so no assignability changes.
|
|
62
|
+
|
|
63
|
+
**`voltro update` carries you across this** — codemod `0.50.0/03_interrupted-replace-tables-nullable`.
|
|
64
|
+
|
|
65
|
+
### Added
|
|
66
|
+
|
|
67
|
+
- **@voltro/cli, @voltro/data-transfer, @voltro/database** — `voltro data backup --assets` and `voltro data restore --assets` now move the stored blobs alongside the vendor dump, through the same content-addressed phase the logical `export` / `import` path uses — streamed, deduped by sha256, verified on the way back, resumable per key.
|
|
68
|
+
|
|
69
|
+
`--assets` was accepted by `backup` and silently ignored, with the only signal a field in the closing JSON reading `NOT included`; `restore` did not accept it at all. A rows-only backup restores a database whose rows reference objects nothing puts back, and the reference and the object are checked at different times, so that state is discovered by a user rather than by the restore.
|
|
70
|
+
|
|
71
|
+
Three refusals, each for a belief that is otherwise acted on silently: `backup --assets` with no storage provider is refused rather than swallowed; `restore --assets` on a rows-only artefact is refused; a restore WITHOUT `--assets` over an artefact that has them warns and proceeds, because restoring rows without blobs is legitimate and refusing it would push people at `--force`.
|
|
72
|
+
|
|
73
|
+
The dump itself still has no resume — a vendor artefact is one opaque file with no offset to restart from, and the logical path is what exists for that.
|
|
74
|
+
- **@voltro/cli, @voltro/database, @voltro/data-transfer** — A native `backup` / `restore` now writes to the same `_voltro_data_transfers` record `import` and `export` use, so `voltro data transfers` answers "did last night's backup finish" from the instance that ran it. A native run reports blobs rather than rows — a vendor tool reports no row count we can trust, and printing `0 row(s)` over a dump that worked would be a wrong measurement.
|
|
75
|
+
|
|
76
|
+
`restore` also writes the `_voltro_replace_in_progress` marker before its first destructive statement and clears it after the last write, blobs included, so a killed restore refuses the next boot instead of serving a half-loaded database. `--allow-live` guards from the wrong side — it asks you not to — and this guards from the right one. The marker carries a `kind` so the refusal can say something different for a `replace` (re-import the capture) and a `restore` (finish the restore), and `tables` is nullable because a native restore has no table count to claim.
|
|
77
|
+
|
|
78
|
+
The sqlite / turso restore is atomic now (temp file + rename): its marker lives in the very file being replaced, so a plain in-place copy would leave a truncated database with nothing left to catch it.
|
|
79
|
+
|
|
80
|
+
A target with no `_voltro_data_transfers` table still gets its backup; the closing line says it was not recorded rather than implying it was.
|
|
81
|
+
- **@voltro/cli** — A re-issued migration note for the `voltro data imports` → `voltro data transfers` rename that shipped in 0.49.0.
|
|
82
|
+
|
|
83
|
+
Codemods are selected by `from < version <= to`, so the 0.49.0 note fires once, on the jump that crosses 0.49.0, and cannot be corrected for anyone already past it. Its gate searched `.ts` / `.tsx` while the command it is about lives in shell scripts, CI job definitions and runbooks — so a project whose only occurrence sat in `.gitlab-ci.yml` crossed 0.49.0 and was told there was nothing to apply.
|
|
84
|
+
|
|
85
|
+
The re-issue is filed under 0.50.0, prints unconditionally, and says what to grep for. Redundant for anyone already fixed; the alternative is firing for nobody.
|
|
86
|
+
|
|
87
|
+
### Fixed
|
|
88
|
+
|
|
89
|
+
- **@voltro/cli** — A `manual` codemod's `appliesTo` can now search every text file the project owns — `.sh`, `.yml`, `.json`, `.md`, `Makefile`, the `.js` scripts — through a new `ctx.text` on the predicate context, and a codemod may declare `reach: 'beyond-source'` to print its note even when nothing matched.
|
|
90
|
+
|
|
91
|
+
Both halves close the same gap, and it was in the codemod most in need of a gate. A manual codemod exists BECAUSE its subject could not be transformed, which usually means it is not source at all — and `appliesTo` was reading the ts-morph project, which holds `.ts` / `.tsx` and nothing else. Measured with one identical CLI invocation in four files: the `.ts` one printed the note, the `.sh`, `.yml` and `.md` ones printed nothing, and the run reported `codemods: nothing to apply for this jump`. That is an acquittal from a check that never looked at the file, and an acquittal gets acted on.
|
|
92
|
+
|
|
93
|
+
`reach: 'beyond-source'` covers what no scan of one repository can reach — an inline script in a CI runner's own UI, a CronJob spec in another repo, a wiki runbook. The note prints either way; only its framing changes, and an uncertain one says plainly that we could not look there.
|
|
94
|
+
- **@voltro/data-transfer** — When `mariadb-dump` is absent and the mysql-family fallback runs Oracle's `mysqldump` against a MariaDB server, the failure now names the way out.
|
|
95
|
+
|
|
96
|
+
The stderr it prints is the child's own words and is the right first thing to show — and on its own it is a dead end: `Unknown table 'COLUMN_STATISTICS' in information_schema (1109)` names a table nobody asked for, in a schema nobody wrote, about a feature nobody enabled. Everything needed to act on it was known where the fallback was DECIDED: which binary we wanted, which one we took, and why the difference matters. That travels with the step now and prints under the stderr, including the counter-move a reader reaches for on their own (`--column-statistics=0` does not exist on `mariadb-dump`, so it fixes the wrong client and breaks the right one).
|
|
97
|
+
|
|
98
|
+
---
|
|
99
|
+
|
|
100
|
+
## [0.49.0] — 2026-08-23
|
|
101
|
+
|
|
102
|
+
### ⚠ BREAKING
|
|
103
|
+
|
|
104
|
+
- **@voltro/cli, @voltro/data-transfer, @voltro/database, @voltro/sql-sqlite, @voltro/voltro** — A data transfer is a series of short requests now — no single one may outlive a caller's budget.
|
|
105
|
+
|
|
106
|
+
**The rule:** *a request that carries bytes never runs a transfer; a request that starts a transfer never carries bytes.* It was broken in the worst available place. The upload was already chunked and resumable — many short requests, each abandonable — and then the FINAL chunk fell through and ran the whole import. So the longest request of the flow arrived AFTER the entire upload had succeeded, and a caller under a policy that caps a single request (a job runner that kills a client at ten minutes; a 30 s ingress ceiling) lost the most expensive thing they had already paid for. A single-request upload had the same shape without the excuse, and the export had no protocol at all: one request that read the whole database and streamed it back.
|
|
107
|
+
|
|
108
|
+
**Import.** `POST /_voltro/admin/import` accumulates and answers `202`. `POST /_voltro/admin/import/start` begins the run and answers `202 { runId }` as soon as the run's first row exists. `start` is idempotent per upload — it is a short request and therefore a retryable one, and without a claim a retry would begin a second destructive run from the same bytes. A claim whose run has ENDED is taken over rather than honoured forever, so a process that dies holding one cannot poison a bundle.
|
|
109
|
+
|
|
110
|
+
**Export.** `POST /_voltro/admin/export` answers `202 { runId }` and produces in the background; the bytes come back from `GET /_voltro/admin/export/download?runId=&offset=&length=` in ranges, resumable, with the total in a header. Object storage (`--bundle-key`) remains for a bundle you want to KEEP — it is no longer the only way to get one out, because requiring it would leave an instance without storage unable to export at all.
|
|
111
|
+
|
|
112
|
+
**The client.** `--max-request-seconds` (or `VOLTRO_MAX_REQUEST_SECONDS`) declares the budget — declared rather than probed, because the thing that kills a request is a policy on the caller's side and only they know it. `--detach` returns once the run has started and says the outcome is NOT known. Attached, the CLI polls the record and prints per-table progress; Ctrl-C then loses the watching and never the run.
|
|
113
|
+
|
|
114
|
+
**The trap, stated because it is the one way to get this wrong:** a failure used to arrive in the response (409 on drift, 409 on a refused mode, 500 otherwise). After the split the response is a `202`, so **a client deriving its exit code from the status line reports a failed import as a success.** The exit code comes from the polled record, in one shared function, and the drift check moved into `start` where it can still be a refusal that leaves no history.
|
|
115
|
+
|
|
116
|
+
**Two knobs that were constants.** `VOLTRO_IMPORT_UPLOAD_DIR` and `VOLTRO_EXPORT_ARTIFACT_DIR` move the staging areas off the default temp filesystem — which on a container is frequently a small tmpfs, so an instance simply could not accept a bundle the size a grown database produces, and the failure arrived as a write error halfway through an upload somebody had been waiting on.
|
|
117
|
+
|
|
118
|
+
**Renamed:** `voltro data imports` → `voltro data transfers`, and `_voltro_data_imports` → `_voltro_data_transfers` with a `direction` column. The command showed one direction and now shows both; the table rename carries its rows via the declarative differ on every dialect and needs nothing from you. The CLI rename ships a `manual` codemod — the command lives in scripts, CI jobs and runbooks, which `voltro update` cannot see or rewrite. The four PUBLIC exports that named the same record were renamed with it (`ImportRun`, `describeImportRun`, `IMPORT_RUNS_TABLE`, `_voltroImportRunsTable`); those are application source, they ship their own `transform` codemod, and they have their own entry.
|
|
119
|
+
- **@voltro/database, @voltro/voltro** — The run-history exports say `transfer`, not `import`.
|
|
120
|
+
|
|
121
|
+
`@voltro/database` (and `voltro/database`) renamed four public exports along with the table behind them:
|
|
122
|
+
|
|
123
|
+
| was | is | |---|---| | `ImportRun` | `DataTransferRun` | | `describeImportRun` | `describeTransferRun` | | `IMPORT_RUNS_TABLE` | `DATA_TRANSFERS_TABLE` | | `_voltroImportRunsTable` | `_voltroDataTransfersTable` |
|
|
124
|
+
|
|
125
|
+
An EXPORT writes to this record now — the row carries a `direction` — so every one of those names described half of what it holds.
|
|
126
|
+
|
|
127
|
+
A `transform` codemod rewrites all four, alias-aware, from either module spelling. It also rewrites a hand-spelled `_voltro_data_imports` in a string, template or raw-SQL fragment, and that is the half worth stating: the four identifiers announce themselves as compile errors, while a query that addresses the table by name has nothing to fail on. The table itself moves with its rows via the declarative differ on every dialect.
|
|
128
|
+
|
|
129
|
+
This is filed apart from the transfer-protocol entry beside it deliberately. That one's codemod is `manual` and is about a CLI invocation living in scripts and CI jobs; this one is application source and is rewritten for you. Reading the first as covering both is what would leave a build broken with a note saying nothing in your source was affected.
|
|
130
|
+
|
|
131
|
+
### Added
|
|
132
|
+
|
|
133
|
+
- **@voltro/database, @voltro/data-transfer, @voltro/cli** — An import records WHETHER it staged, and a soft-dropped column says so in a drift refusal.
|
|
134
|
+
|
|
135
|
+
**`_voltro_data_imports.staged`.** The pre-upload preflight says what the instance WILL do; the run's own line says what it did — and that line is printed inside the instance, which is exactly where an operator using `--target api` cannot read it. So "we were told it would stage" and "it staged" were two claims with no way to close the gap between them from outside. The column is `null` for a mode where the question does not arise; `voltro data imports` and `GET /_voltro/admin/imports` both carry it.
|
|
136
|
+
|
|
137
|
+
**A soft-dropped column is named as one.** `<original>__dropped_<stamp>` is what the differ leaves behind when an app stops declaring a column — only the database that did the drop has it, so it drifts against every target. The refusal reported it as an ordinary missing column ("the value has nowhere to go"), which points at the TARGET's schema: the one place the fix does not lie. It now says where the column lives and how to reclaim it.
|
|
138
|
+
|
|
139
|
+
### Fixed
|
|
140
|
+
|
|
141
|
+
- **@voltro/data-transfer, @voltro/sql-sqlite, @voltro/database** — A successful `--mode replace --no-atomic` left a marker that refused the next boot.
|
|
142
|
+
|
|
143
|
+
Two defects, one visible symptom, and both were silent by construction.
|
|
144
|
+
|
|
145
|
+
**The clear was gated on the WRITE's condition.** `!(useLedger && ledger.truncated)` means "an earlier attempt already recorded this destructive run, do not write a second row" — correct on the write. Copied down to the clear its meaning inverts: `ledger.truncated` is set by the emptying step of THIS run, so on `--no-atomic` (the only mode where `useLedger` is true) the clear was skipped by the very run that wrote the marker.
|
|
146
|
+
|
|
147
|
+
**And a `Date` in a predicate is not bindable on sqlite.** `better-sqlite3` binds numbers, strings, bigints, buffers and null; the row path has coerced Dates since that store was written, and the eager-join compiler carried its own private copy of the fix, but `query` / `updateMany` / `deleteMany` bound the raw value. So every comparison of a timestamp column against a `Date` failed with `Failed to execute statement` — including the marker's clear, which swallows its errors ON PURPOSE (a store with no marker table must not fail an import over a bookkeeping row) and therefore said nothing. The retention sweep compares `lt(column, cutoff)` the same way.
|
|
148
|
+
|
|
149
|
+
Together: a fully successful replace left the marker standing, and the next boot REFUSED with "a destructive import did not finish" over a database that was completely fine. The one recovery is a command the operator has no reason to think they need. It stayed invisible because `atomic` defaults to true for `replace`, and because a staged replace writes no marker at all — two defaults hiding the one mode that exists for large, interruptible loads.
|
|
150
|
+
|
|
151
|
+
The coercion is shared now and applied at all three predicate sites, with a guard that fails on a fourth that forgets. A dry run also closes its trace row: a preview that finished instantly used to leave the record open, so a polling caller waited out its whole budget over a run that was long done.
|
|
152
|
+
- **@voltro/cli** — `voltro codegen` declared twenty framework tables fewer than a boot.
|
|
153
|
+
|
|
154
|
+
The app half of this was fixed last release and looked like the whole thing. It was not: `codegen` derived the FEATURE MIX from the file list it had just walked, and that list is the entity/relations set — which contains no `*.workflow.tsx`, no `*.agent.ts`, no `*.cron.tsx`. So every feature flag came back false, and the dialect was never passed at all, taking `_voltro_cdc_offsets` with it. Measured on an app with two workflow files: 18 framework tables where the shared assembly produces 33.
|
|
155
|
+
|
|
156
|
+
It calls `assembleFrameworkTables({ root })` now — the same entry `voltro db plan/apply` uses, which detects the mix from the root rather than being handed one.
|
|
157
|
+
|
|
158
|
+
The parity guard moved with it. Comparing the two WALKS could not see this: both walks were right about files, and the divergence was introduced one layer past them. It compares the assembled SETS now, and runs codegen's own table function on a tree whose only feature signals are a workflow file and an agent file — a source assertion that the right function is CALLED cannot see a wrong argument handed to it, and a wrong argument is what this was.
|
|
159
|
+
- **@voltro/cli** — `voltro data --help` advertised four subcommands out of nine.
|
|
160
|
+
|
|
161
|
+
It printed `<export|import|backup|restore>` while the command dispatched those four plus `imports`, `inspect`, `unpack`, `clear-replace-marker` and `clear-staging`. A quoted enumeration is read as exhaustive — the same failure `subcommandNames.ts` was written for after a `db` list cost two wrong conclusions — and the missing entry here is the one that answers "what is my import doing right now" for somebody who cannot reach the pod.
|
|
162
|
+
|
|
163
|
+
`subcommandHelpParity.test.ts` had listed `data` among the commands it could not check, loudly and correctly: there was no name list to check against. There is one now (`DATA_SUBCOMMANDS`), the dispatch is a keyed `Record`, so a subcommand with no name and a name with no handler are both compile errors, and the usage line is generated from the same array. `data` is out of the unchecked list.
|
|
164
|
+
|
|
165
|
+
---
|
|
166
|
+
|
|
42
167
|
## [0.48.0] — 2026-08-22
|
|
43
168
|
|
|
44
169
|
### Added
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { f as e } from "./dev-
|
|
1
|
+
import { f as e } from "./dev-1yu42ubu.js";
|
|
2
2
|
import { vt as t } from "./webDev-B4IEMB0n.js";
|
|
3
3
|
import { t as n } from "./dialectDriver-czCHYpeH.js";
|
|
4
4
|
import { dirname as r, isAbsolute as i, join as a, relative as o, resolve as s } from "node:path";
|
package/dist/bin.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { r as e } from "./commands-
|
|
2
|
+
import { r as e } from "./commands-By23Ljny.js";
|
|
3
3
|
import { a as t } from "./nodeEnvironment-cGFAj1J8.js";
|
|
4
4
|
import { t as n } from "./loadEnv-D9nEOClM.js";
|
|
5
5
|
import { n as r } from "./cliError-BmdYnghb.js";
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { gn as e } from "./dev-
|
|
1
|
+
import { gn as e } from "./dev-1yu42ubu.js";
|
|
2
2
|
import { $ as t, A as n, F as r, I as i, L as a, Q as o, a as s, b as c, d as l, et as u, f as d, ht as f, k as p, rt as m, s as h, v as g, vt as _, y as v } from "./webDev-B4IEMB0n.js";
|
|
3
3
|
import { t as y } from "./cliRuntime-Dh7UDinH.js";
|
|
4
4
|
import { a as b } from "./renderModeScan-D7J1B7Kw.js";
|
|
5
|
-
import { i as x, o as S, s as C } from "./apiBuild-
|
|
5
|
+
import { i as x, o as S, s as C } from "./apiBuild-CFYXo06U.js";
|
|
6
6
|
import { n as w } from "./renderProfile-1OWWAAtx.js";
|
|
7
7
|
import { dirname as T, join as E, resolve as D } from "node:path";
|
|
8
8
|
import { existsSync as O, promises as k } from "node:fs";
|
|
@@ -477,7 +477,7 @@ var N = A({ scope: "voltro:build" }), P = async (e) => {
|
|
|
477
477
|
file: n.file,
|
|
478
478
|
reason: n.reason
|
|
479
479
|
});
|
|
480
|
-
let { regenerateRpcGroup: r } = await import("./codegenCommand-
|
|
480
|
+
let { regenerateRpcGroup: r } = await import("./codegenCommand-BJzaRz6q.js"), i = await r(e);
|
|
481
481
|
if (i.kind === "refused") {
|
|
482
482
|
F.warn("could not regenerate the rpc group — building against what is on disk", { reason: i.reason });
|
|
483
483
|
return;
|
|
@@ -489,10 +489,10 @@ var N = A({ scope: "voltro:build" }), P = async (e) => {
|
|
|
489
489
|
}, K = async (e) => {
|
|
490
490
|
let n = await l(e);
|
|
491
491
|
if (!n) {
|
|
492
|
-
let { loadApiConfig: t } = await import("./dev-
|
|
492
|
+
let { loadApiConfig: t } = await import("./dev-BWwE0QjK.js"), n = await t(e);
|
|
493
493
|
if (n) {
|
|
494
494
|
F.info("building api app", { app: n.name ?? "(unnamed)" }), await G(e);
|
|
495
|
-
let { runApiBuild: t, runServeBundleBuild: r } = await import("./apiBuild-
|
|
495
|
+
let { runApiBuild: t, runServeBundleBuild: r } = await import("./apiBuild-BWo8KI6n.js");
|
|
496
496
|
await t(e);
|
|
497
497
|
try {
|
|
498
498
|
await r(e);
|
|
@@ -711,7 +711,7 @@ var N = A({ scope: "voltro:build" }), P = async (e) => {
|
|
|
711
711
|
return t.ran ? t.exitCode : (yield* j.logError("no web app found at this path", { hint: "looked for app.config.ts with `type: 'web'`" }), 1);
|
|
712
712
|
}), J = async (t) => {
|
|
713
713
|
if (t.includes("--target")) {
|
|
714
|
-
let { runSdkBuildCommand: e } = await import("./sdkgen-
|
|
714
|
+
let { runSdkBuildCommand: e } = await import("./sdkgen-dl4-WKF6.js");
|
|
715
715
|
return e(t);
|
|
716
716
|
}
|
|
717
717
|
let n = t.find((e) => !e.startsWith("-")) ?? process.cwd(), r = await y(q(n), { scope: "voltro:build" }), i = e(n);
|
|
@@ -4,7 +4,7 @@ import { r } from "./appGraph-KGDPTuTy.js";
|
|
|
4
4
|
import { n as i } from "./cliOutput-D1tSBoRM.js";
|
|
5
5
|
import { n as a, o, r as s, t as c } from "./inspectFetch-EMuhTG_9.js";
|
|
6
6
|
import { r as l } from "./fileTaxonomy-CJfgOllU.js";
|
|
7
|
-
import { S as u } from "./doctorCommand-
|
|
7
|
+
import { S as u } from "./doctorCommand-y-EbP8st.js";
|
|
8
8
|
import { resolve as d } from "node:path";
|
|
9
9
|
import { readdirSync as f, statSync as p } from "node:fs";
|
|
10
10
|
//#region src/observedDiff.ts
|
|
@@ -141,12 +141,12 @@ var m = (e) => e === void 0 ? [] : typeof e == "string" ? [e] : e, h = /* @__PUR
|
|
|
141
141
|
let t = e.severity === "error" ? "✗ error" : "⚠ warn", n = e.file ? ` (${e.file})` : "";
|
|
142
142
|
return `${t} ${e.rule} ${e.node}${n}\n ${e.breaks.join("; ")}\n fix: ${e.fix}`;
|
|
143
143
|
}).join("\n"), C = async (e) => {
|
|
144
|
-
let { walk: t, loadDiscovered: n, loadApiConfig: r } = await import("./dev-
|
|
144
|
+
let { walk: t, loadDiscovered: n, loadApiConfig: r } = await import("./dev-BWwE0QjK.js"), { buildRpcEntry: i } = await import("./manifestBuild-j0n109tt.js"), { buildCapabilityManifest: a } = await import("./inspect-DUze25t0.js"), { relative: o } = await import("node:path"), s = await n(await t(e)), c = (t, n, r) => i(t, n, o(e, r)), l = [
|
|
145
145
|
...s.queries.map((e) => c(e.descriptor, "query", e.file)),
|
|
146
146
|
...s.mutations.map((e) => c(e.descriptor, "mutation", e.file)),
|
|
147
147
|
...s.actions.map((e) => c(e.descriptor, "action", e.file)),
|
|
148
148
|
...s.streams.map((e) => c(e.descriptor, "stream", e.file))
|
|
149
|
-
], { assembleFrameworkTables: u } = await import("./frameworkTableAssembly-
|
|
149
|
+
], { assembleFrameworkTables: u } = await import("./frameworkTableAssembly-CVDB2hCq.js"), d = await u({ root: e }).catch((e) => (console.warn(`\n⚠ plugin tables could not be collected — this report is INCOMPLETE.\n ${e instanceof Error ? e.message.split("\n")[0] : String(e)}\n`), [])), f = new Set(s.tables.map((e) => e.tableName)), p = [...[...f].map((e) => ({ name: e })), ...d.map((e) => e.tableName).filter((e) => !f.has(e)).map((e) => ({
|
|
150
150
|
name: e,
|
|
151
151
|
framework: !0
|
|
152
152
|
}))], m = await r(e).then((e) => e?.plugins ?? []).catch(() => []);
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { n as e, r as t, t as n } from "./checkCommand-
|
|
1
|
+
import { n as e, r as t, t as n } from "./checkCommand-Do3SQbLx.js";
|
|
2
2
|
export { n as offlineManifest, e as runCheckCommand, t as toInput };
|
|
@@ -1,24 +1,23 @@
|
|
|
1
|
-
import { B as e, Sn as t, l as n, u as r, xn as i } from "./dev-
|
|
1
|
+
import { B as e, Sn as t, l as n, u as r, xn as i } from "./dev-1yu42ubu.js";
|
|
2
2
|
import { dt as a } from "./webDev-B4IEMB0n.js";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import { pathToFileURL as h } from "node:url";
|
|
3
|
+
import { n as o } from "./frameworkTableAssembly-BwJVEKLr.js";
|
|
4
|
+
import { i as s, r as c } from "./codegen-CrMXs4hb.js";
|
|
5
|
+
import { basename as l, join as u, relative as d, resolve as f } from "node:path";
|
|
6
|
+
import { promises as p } from "node:fs";
|
|
7
|
+
import { pathToFileURL as m } from "node:url";
|
|
9
8
|
//#region src/mobileCodegen.ts
|
|
10
|
-
var
|
|
11
|
-
let t =
|
|
9
|
+
var h = "voltro.mobile.ts", g = u(".framework", "mobileApis.generated.ts"), _ = (e) => e.replace(/[^a-zA-Z0-9]/g, "_"), v = async (e) => {
|
|
10
|
+
let t = u(e, h);
|
|
12
11
|
try {
|
|
13
|
-
await
|
|
12
|
+
await p.stat(t);
|
|
14
13
|
} catch {
|
|
15
14
|
return { present: !1 };
|
|
16
15
|
}
|
|
17
16
|
try {
|
|
18
|
-
let e = (await import(`${
|
|
17
|
+
let e = (await import(`${m(t).href}?t=${Date.now()}`)).default;
|
|
19
18
|
return e === void 0 || typeof e != "object" || e.apis === void 0 ? {
|
|
20
19
|
present: !0,
|
|
21
|
-
error: /* @__PURE__ */ Error(`${
|
|
20
|
+
error: /* @__PURE__ */ Error(`${h} has no default export with an \`apis\` map`)
|
|
22
21
|
} : {
|
|
23
22
|
present: !0,
|
|
24
23
|
binding: e
|
|
@@ -29,20 +28,20 @@ var g = "voltro.mobile.ts", _ = d(".framework", "mobileApis.generated.ts"), v =
|
|
|
29
28
|
error: e instanceof Error ? e : Error(String(e))
|
|
30
29
|
};
|
|
31
30
|
}
|
|
32
|
-
},
|
|
31
|
+
}, y = (e, t) => {
|
|
33
32
|
let n = t.filter(([, e]) => e.package !== void 0).map(([e, t]) => {
|
|
34
|
-
let n = `${
|
|
33
|
+
let n = `${_(e)}Group`, r = `${_(e)}Descriptors`;
|
|
35
34
|
return `import { ${n === "appGroup" ? "appGroup" : `appGroup as ${n}`}, ${r === "appDescriptors" ? "appDescriptors" : `appDescriptors as ${r}`} } from '${t.package}/rpcGroup'`;
|
|
36
|
-
}), r = t.map(([e, t]) => t.package === void 0 ? ` // ${JSON.stringify(e)}: declared with \`url\` only — no local rpc surface to import.\n // Add \`package: '<the api package>'\` to ${
|
|
35
|
+
}), r = t.map(([e, t]) => t.package === void 0 ? ` // ${JSON.stringify(e)}: declared with \`url\` only — no local rpc surface to import.\n // Add \`package: '<the api package>'\` to ${h} to get typed hooks for it.` : ` { ${[
|
|
37
36
|
`name: ${JSON.stringify(e)}`,
|
|
38
|
-
`group: ${
|
|
39
|
-
`descriptors: ${
|
|
37
|
+
`group: ${_(e)}Group`,
|
|
38
|
+
`descriptors: ${_(e)}Descriptors`,
|
|
40
39
|
`wsUrl: wsUrl(${JSON.stringify(e)})`
|
|
41
40
|
].join(", ")} },`), i = t.filter(([, e]) => e.url !== void 0).map(([e, t]) => ` ${JSON.stringify(e)}: ${JSON.stringify(t.url)},`);
|
|
42
41
|
return [
|
|
43
42
|
"// GENERATED BY `voltro codegen` — do not edit; regenerate with `voltro codegen`.",
|
|
44
43
|
"//",
|
|
45
|
-
`// The api bindings declared in ${
|
|
44
|
+
`// The api bindings declared in ${h}. Only the BINDING is`,
|
|
46
45
|
"// generated: the procedure types ride the imports below, so a schema change",
|
|
47
46
|
"// in an api needs no regeneration here.",
|
|
48
47
|
"",
|
|
@@ -63,24 +62,24 @@ var g = "voltro.mobile.ts", _ = d(".framework", "mobileApis.generated.ts"), v =
|
|
|
63
62
|
`export const MOBILE_API_NAMES = [${t.map(([e]) => JSON.stringify(e)).join(", ")}] as const`,
|
|
64
63
|
...i.length > 0 ? [
|
|
65
64
|
"",
|
|
66
|
-
`/** URLs declared in ${
|
|
65
|
+
`/** URLs declared in ${h} for apis this app does not build. */`,
|
|
67
66
|
"export const MOBILE_API_URLS: Readonly<Record<string, string>> = {",
|
|
68
67
|
...i,
|
|
69
68
|
"}"
|
|
70
69
|
] : [],
|
|
71
70
|
""
|
|
72
71
|
].join("\n");
|
|
73
|
-
},
|
|
74
|
-
let t =
|
|
72
|
+
}, b = async (e) => {
|
|
73
|
+
let t = f(e), n = await v(t);
|
|
75
74
|
if (!n.present) return { kind: "not-a-mobile-app" };
|
|
76
75
|
if ("error" in n) return {
|
|
77
76
|
kind: "refused",
|
|
78
|
-
reason: `${
|
|
77
|
+
reason: `${h} could not be loaded — ${n.error.message}`
|
|
79
78
|
};
|
|
80
79
|
let r = Object.entries(n.binding.apis);
|
|
81
80
|
if (r.length === 0) return {
|
|
82
81
|
kind: "refused",
|
|
83
|
-
reason: `${
|
|
82
|
+
reason: `${h} declares no apis`
|
|
84
83
|
};
|
|
85
84
|
for (let [e, t] of r) if (t.package === void 0 && t.url === void 0) return {
|
|
86
85
|
kind: "refused",
|
|
@@ -90,32 +89,32 @@ var g = "voltro.mobile.ts", _ = d(".framework", "mobileApis.generated.ts"), v =
|
|
|
90
89
|
kind: "refused",
|
|
91
90
|
reason: `api ${JSON.stringify(e)}: ${n.package} is not a workspace dependency of this app. Add it to the app's package.json (\`"${n.package}": "workspace:*"\`) and install.`
|
|
92
91
|
};
|
|
93
|
-
let i =
|
|
94
|
-
await
|
|
92
|
+
let i = u(t, g), o = y(t, r);
|
|
93
|
+
await p.mkdir(u(t, ".framework"), { recursive: !0 });
|
|
95
94
|
let s = null;
|
|
96
95
|
try {
|
|
97
|
-
s = await
|
|
96
|
+
s = await p.readFile(i, "utf8");
|
|
98
97
|
} catch {}
|
|
99
|
-
return s !== o && await
|
|
98
|
+
return s !== o && await p.writeFile(i, o, "utf8"), {
|
|
100
99
|
kind: "wrote",
|
|
101
100
|
file: i,
|
|
102
101
|
apis: r.map(([e]) => e)
|
|
103
102
|
};
|
|
104
|
-
},
|
|
103
|
+
}, x = (e, t) => t.kind === "wrote" ? `voltro codegen: wrote ${d(e, t.file)} (apis ${t.apis.join(", ")})` : t.kind === "refused" ? `voltro codegen: ${t.reason} — refusing to write.` : `voltro codegen: no ${h} here — not a mobile app.`, S = async (r) => {
|
|
105
104
|
let { config: i, present: a, error: o } = await n(r);
|
|
106
105
|
if (a && i === null) return {
|
|
107
106
|
kind: "refused",
|
|
108
107
|
reason: "app.config.ts exists but could not be loaded" + (o === void 0 ? "" : ` — ${o instanceof Error ? o.message : String(o)}`)
|
|
109
108
|
};
|
|
110
|
-
let s = i?.plugins ?? [], { pluginContributions:
|
|
109
|
+
let s = i?.plugins ?? [], { pluginContributions: u, pluginErrorImports: d, pluginRpcImports: f } = await e(s, i?.name ?? l(r), r), p = await c({
|
|
111
110
|
root: r,
|
|
112
|
-
...
|
|
111
|
+
...u.length > 0 ? { pluginContributions: u } : {},
|
|
113
112
|
...d.length > 0 ? { pluginErrorImports: d } : {},
|
|
114
113
|
...f.length > 0 ? { pluginRpcImports: f } : {},
|
|
115
114
|
...i?.name === void 0 ? {} : { apiName: i.name }
|
|
116
115
|
});
|
|
117
116
|
if (!p) return { kind: "nothing-to-write" };
|
|
118
|
-
let m = await
|
|
117
|
+
let m = await C(r, i), h = await t(p, m);
|
|
119
118
|
return {
|
|
120
119
|
kind: "wrote",
|
|
121
120
|
file: p,
|
|
@@ -125,21 +124,14 @@ var g = "voltro.mobile.ts", _ = d(".framework", "mobileApis.generated.ts"), v =
|
|
|
125
124
|
tables: m.size,
|
|
126
125
|
dts: h
|
|
127
126
|
};
|
|
128
|
-
},
|
|
129
|
-
let n = await
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
});
|
|
137
|
-
return i(a, d, t?.analytics?.tables ?? []);
|
|
138
|
-
}, T = async (e) => {
|
|
139
|
-
let t = p(e.find((e) => !e.startsWith("-")) ?? process.cwd()), n = await x(t);
|
|
140
|
-
if (n.kind !== "not-a-mobile-app") return console.log(S(t, n)), n.kind === "wrote" ? 0 : 1;
|
|
141
|
-
let r = await C(t);
|
|
142
|
-
return r.kind === "refused" ? (console.error(`voltro codegen: ${r.reason} — refusing to write.`), console.error(" The generated file would omit every plugin's error union and client routes, and would typecheck, so the omission would only surface as a missing error tag at runtime."), 1) : r.kind === "nothing-to-write" ? (console.log("voltro codegen: no rpc primitives found — nothing to write."), 0) : (console.log(`voltro codegen: wrote ${f(t, r.file)} (plugins ${r.plugins}, error schemas ${r.errorSchemas}, plugin routes ${r.routes})`), console.log(r.dts.action === "removed" ? ` removed ${f(t, r.dts.path)} — no tables declared, so \`source:\` is \`string\` again` : r.dts.action === "skipped" ? " no tables declared — `source:` stays `string`" : ` ${r.dts.action} ${f(t, r.dts.path)} (${r.tables} table name(s) typed for \`source:\`)`), r.plugins > 0 && r.errorSchemas === 0 && console.log(" note: no plugin contributed a cross-cutting error schema, so the wire error union carries only each procedure's own `error:`. Expected if none of your plugins declare `errorSchemas` — but `guards:` needs @voltro/plugin-rbac for its ScopeError to decode typed."), 0);
|
|
127
|
+
}, C = async (e, t) => {
|
|
128
|
+
let n = await s(e), { tables: a } = await r(n), c = await o({ root: e });
|
|
129
|
+
return i(a, c, t?.analytics?.tables ?? []);
|
|
130
|
+
}, w = async (e) => {
|
|
131
|
+
let t = f(e.find((e) => !e.startsWith("-")) ?? process.cwd()), n = await b(t);
|
|
132
|
+
if (n.kind !== "not-a-mobile-app") return console.log(x(t, n)), n.kind === "wrote" ? 0 : 1;
|
|
133
|
+
let r = await S(t);
|
|
134
|
+
return r.kind === "refused" ? (console.error(`voltro codegen: ${r.reason} — refusing to write.`), console.error(" The generated file would omit every plugin's error union and client routes, and would typecheck, so the omission would only surface as a missing error tag at runtime."), 1) : r.kind === "nothing-to-write" ? (console.log("voltro codegen: no rpc primitives found — nothing to write."), 0) : (console.log(`voltro codegen: wrote ${d(t, r.file)} (plugins ${r.plugins}, error schemas ${r.errorSchemas}, plugin routes ${r.routes})`), console.log(r.dts.action === "removed" ? ` removed ${d(t, r.dts.path)} — no tables declared, so \`source:\` is \`string\` again` : r.dts.action === "skipped" ? " no tables declared — `source:` stays `string`" : ` ${r.dts.action} ${d(t, r.dts.path)} (${r.tables} table name(s) typed for \`source:\`)`), r.plugins > 0 && r.errorSchemas === 0 && console.log(" note: no plugin contributed a cross-cutting error schema, so the wire error union carries only each procedure's own `error:`. Expected if none of your plugins declare `errorSchemas` — but `guards:` needs @voltro/plugin-rbac for its ScopeError to decode typed."), 0);
|
|
143
135
|
};
|
|
144
136
|
//#endregion
|
|
145
|
-
export {
|
|
137
|
+
export { S as regenerateRpcGroup, w as runCodegenCommand };
|