@voltro/cli 0.18.0 → 0.20.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 +245 -3
- package/bin/voltro.mjs +11 -0
- package/dist/apiBuild-B1FDtx1y.js +2 -0
- package/dist/{apiBuild-CPDTJHkH.js → apiBuild-CSCVI1wC.js} +2 -2
- package/dist/bin.js +2 -2
- package/dist/{commands-laJDMj2m.js → commands-DQ0mUwDe.js} +2074 -1771
- package/dist/{dev-BOFxC21E.js → dev-DnHlqqoF.js} +1607 -1566
- package/dist/{dev-DOK0w6ZW.js → dev-Z1E_twG9.js} +1 -1
- package/dist/index.js +1 -1
- package/dist/{inspectMetrics-D1DmLeJs.js → inspectMetrics-BL8kZOv3.js} +196 -176
- package/dist/serveCommand-Dh4qd49N.js +1136 -0
- package/dist/serveEntry.js +2 -2
- package/dist/{start-Clvz4IJb.js → start-IVgQe2YD.js} +368 -368
- package/dist/startEntry.js +2 -2
- package/package.json +17 -17
- package/templates/AGENTS.core.md +25 -9
- package/templates/AGENTS.md +26 -10
- package/templates/agent-docs/_index.md +1 -1
- package/templates/agent-docs/authentication.md +9 -0
- package/templates/agent-docs/database/migrations.md +35 -2
- package/templates/agent-docs/database/misc.md +42 -3
- package/templates/agent-docs/database/querying.md +2 -0
- package/templates/agent-docs/routing.md +13 -0
- package/templates/agent-docs/whats-new.md +44 -189
- 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-storage/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-search/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 +8 -8
- package/templates/apps/frontend-blank/package.json +7 -7
- 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-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/dist/apiBuild-D1UBJ4TM.js +0 -2
- package/dist/serveCommand-CxcxHc9Y.js +0 -1129
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# What's new in 0.
|
|
1
|
+
# What's new in 0.20.0
|
|
2
2
|
|
|
3
3
|
Read this FIRST when a task touches an area you have not worked in recently.
|
|
4
4
|
It is the cheapest way to notice that the framework grew the thing you were
|
|
@@ -9,228 +9,83 @@ BREAKING entries name a codemod; run `voltro update` to apply it.
|
|
|
9
9
|
|
|
10
10
|
### ⚠ BREAKING
|
|
11
11
|
|
|
12
|
-
- **@voltro/database** —
|
|
12
|
+
- **@voltro/plugin-versioning, @voltro/database, @voltro/voltro, @voltro/sql-postgres, @voltro/sql-mysql, @voltro/sql-sqlite, @voltro/sql-mssql** — `versioningPlugin({ timing: 'in-transaction' })` produced a WRONG trail, not merely a slow one. Reported and reproduced against MariaDB 11 by a team that wired both plugins and measured before migrating a single call site.
|
|
13
13
|
|
|
14
|
-
It
|
|
14
|
+
**It recorded every change twice.** The two timings are alternatives, but the post-commit change tap stayed wired when the in-transaction recorder was registered, so both ran. One `bookmarks.create` → two history rows.
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
- **@voltro/database, @voltro/runtime** — **`.where(col, 'like', value)` is removed, and `startsWith` takes the job it was pretending to do.**
|
|
16
|
+
**And the trail was mis-ordered, which is worse.** Each path numbered independently: one insert plus one update produced versions `0, 0, 1, 2` across four rows. `selectAsOf`, `sortHistory` and `diffVersionRows` all read `version`, so `rowAsOf` returned the wrong snapshot and `diffVersions` found nothing. A duplicate can be deduped; a wrong order cannot be detected from the data.
|
|
18
17
|
|
|
19
|
-
|
|
18
|
+
The recorder wrote a constant `version: 0` on purpose, with a design note arguing that ordering could come from `changedAt` and that a read per covered write was too expensive. Both halves were wrong: `changedAt` is millisecond-resolution, so two writes to one row inside one transaction tie routinely, and the number is what every reader consults.
|
|
20
19
|
|
|
21
|
-
|
|
20
|
+
**BREAKING —** a `WriteRecorder` now receives a PORT (`{ append, maxOf }`) rather than a bare `append`. `maxOf` is one aggregate with an equality filter on the connection the write already holds; it is what lets an append-only trail number its own entries. A recorder still cannot UPDATE, DELETE or open a nested transaction, and a throw from either operation still rolls the caller's write back. Apps that merely ENABLE the timing need no change — only a hand-written recorder does, and `tsc` names every site.
|
|
22
21
|
|
|
23
|
-
|
|
24
|
-
.where('key', 'startsWith', 'awb_') // ergonomic form
|
|
25
|
-
where(startsWith('key', 'awb_')) // predicate helper
|
|
26
|
-
jsonField('config', 'ns').startsWith('awb_') // inside a json() column
|
|
27
|
-
```
|
|
28
|
-
|
|
29
|
-
`startsWith` is **case-SENSITIVE**, unlike `contains`, and the asymmetry is the design rather than an oversight. `contains` is a search primitive — a human typing into a box means `hello` to find `Hello`. A prefix is a NAMESPACE: `awb_` and `AWB_` are two different key spaces, and quietly merging them is a bug. It also matches the JS method it is named after.
|
|
30
|
-
|
|
31
|
-
It is the one string predicate a database can answer from an index: it lowers to `LIKE 'literal%'`, which a btree can range-scan. `contains` (`%…%`) cannot, which is also why there is no `endsWith` — a second un-indexable operator would only look cheaper than it is. `%` and `_` in the value are escaped, so they match literally, and the ESCAPE clause is stated per dialect (sqlite and mssql have no default escape character; mysql already has backslash *and* treats it as a string escape, so the clause is omitted there).
|
|
32
|
-
|
|
33
|
-
**Migration** is deliberately manual. Rewriting `'like'` → `'contains'` would reproduce exactly what runs today, bug included, and report the migration as complete — while every site that used a wildcard keeps returning nothing. Each call site is one of two things and only its author can tell which: a wildcard pattern (→ `startsWith`, and that query has been wrong until now) or a substring search spelled oddly (→ `contains`, no behaviour change).
|
|
34
|
-
|
|
35
|
-
### Added
|
|
36
|
-
|
|
37
|
-
- **@voltro/protocol** — **`apiKeyStrategy`'s `resolveKey` receives the strategy input** — the same object a hand-written `AuthStrategy.resolve` gets, as a second argument:
|
|
38
|
-
|
|
39
|
-
```ts
|
|
40
|
-
apiKeyStrategy({
|
|
41
|
-
prefix: 'awb_',
|
|
42
|
-
resolveKey: async (hash, { store }) => {
|
|
43
|
-
const rows = await store?.query(apiKeys.byHash(hash))
|
|
44
|
-
return (rows?.[0] as ApiKeyRecord | undefined) ?? null
|
|
45
|
-
},
|
|
46
|
-
})
|
|
47
|
-
```
|
|
48
|
-
|
|
49
|
-
`AuthStrategyInput.store` shipped two releases ago and this helper was the one auth seam that could not reach it: an app using `apiKeyStrategy` had to wrap it in its own strategy purely to close over a store the framework had already handed over — or keep the second connection path to the same database that the seam exists to delete. Existing one-argument resolvers are unaffected.
|
|
50
|
-
- **@voltro/testing** — **`describeIfReachable` — because "the database was not there" must not look like "the database was fine".**
|
|
51
|
-
|
|
52
|
-
Every integration and dialect-parity suite probes a TCP port and bails when the service is down. The bail was hand-written each time, and the hand-written form ends a test body with an early `return` — which is a PASS. So with no database at all, a suite reports `Tests 2 passed`. The only trace is a smaller duration, which nobody reads, and vitest swallows the accompanying `console.warn` by default, so even the intended signal never prints.
|
|
53
|
-
|
|
54
|
-
Found by running the postgres introspection suite against port 1: `2 passed`, having connected to nothing. That suite guards the fix for a boot hang, so a green run there was load-bearing evidence that meant nothing.
|
|
55
|
-
|
|
56
|
-
`describeIfReachable(label, target, suite)` makes the honest outcome the default: an unreachable target produces a vitest SKIP — reported as `skipped`, counted separately, with the missing service named in the suite label. "We did not verify this" and "we verified this and it holds" no longer print the same.
|
|
57
|
-
|
|
58
|
-
Same class as the changelog and message-API selftests: a check that has quietly stopped checking still prints green, and green is read as evidence.
|
|
59
|
-
|
|
60
|
-
**All 36 suites carrying that shape are converted** — every one was fully gated, so wrapping the suite loses no test. Verified in both directions, because only one of them is obvious:
|
|
61
|
-
|
|
62
|
-
| | before | after | |---|---|---| | no services running | 105 tests **passed** | **0 passed, 92 skipped** | | the full stack up | 105 passed | **all pass, 0 undeclared skips** |
|
|
63
|
-
|
|
64
|
-
The second row is the point — the sweep did not quietly turn a suite off.
|
|
65
|
-
|
|
66
|
-
**It paid for itself immediately, three times.**
|
|
67
|
-
|
|
68
|
-
*Twelve replication tests had never run.* postgres streaming replica, mysql GTID replica, mssql Always-On AG — left out of CI on the grounds that starting them OOMs a standard runner, so they reported `passed` in every run without once executing. Replication and failover: precisely the behaviour nobody can verify by reading it. Measured rather than argued: baseline 2091 MiB, `postgres-replica` **82**, the mysql pair **1335**, the AG pair **2035**. The OOM claim is true of the WHOLE compose file (keydb, dragonfly, valkey, redis cluster) and not of these. `ci.yml` now starts them — plus the two one-shot containers that actually FORM the availability group, without which both nodes are healthy and the suite still cannot connect.
|
|
69
|
-
|
|
70
|
-
*Eighteen cache tests had tested one engine out of four.* The RESP suite iterates redis / valkey / keydb / dragonfly; only redis was started. The other three cost **27 MiB between them**.
|
|
71
|
-
|
|
72
|
-
*Three SQL Server instances were fighting over memory.* With the AG nodes running beside `mssql-test`, two mssql round-trip suites failed — and passed when run alone, which is how they would have been dismissed as flakes. Each instance now declares `MSSQL_MEMORY_LIMIT_MB`, so the stack is the same size on a laptop and on a runner. Under the full gate, with every package's suite running in parallel, sql-mssql is 59/59.
|
|
73
|
-
|
|
74
|
-
**What remains skipped is skipped for a reason, and the reason is written down.** Four tests, in `sql-sqlite` and `sql-turso`: `clusterTestSuite` gates cross-process resume on `clusterResume`, which needs the workflow runner's state in SQL, and neither dialect keeps it there. Not a missing service — a capability that does not exist for that dialect. That distinction is the whole content of the allowlist.
|
|
22
|
+
**Cost, stated rather than avoided:** `'in-transaction'` now takes TWO round-trips per recorded write, roughly doubling this timing's published per-write overhead. Both timings number from 1, so switching `timing` no longer shifts version numbers.
|
|
75
23
|
|
|
76
24
|
### Fixed
|
|
77
25
|
|
|
78
|
-
- **@voltro/
|
|
79
|
-
|
|
80
|
-
Both seams are documented as "not tenant-scoped", which is correct and unavoidable: they hand out a store to code that runs BEFORE a Subject exists, so tenant scope, soft-delete filtering, audit stamping and row-level security genuinely cannot apply. What that was silently taken to mean is "the raw driver", and it dropped two things that need no Subject at all.
|
|
81
|
-
|
|
82
|
-
The consequence was a silent wrong answer rather than an error. An auth strategy reading an `.encrypted()` column got the literal string `enc:v1:…` back — which compares, concatenates, renders and logs perfectly well, and simply never matches the token it is compared to. The failure surfaces as "wrong credential". On the write side it was worse and unrecoverable: an insert through that store wrote PLAINTEXT into a column the schema declares encrypted.
|
|
83
|
-
|
|
84
|
-
The line is now **everything that does not need a Subject**, not "less than `ctx.store`". Wired in `voltro dev` and `voltro serve` in the same change (`wrapStoreWithBootCodec`); `raw()` is deliberately left as a pass-through, since it is the documented escape hatch for hand-written SQL and re-encoding rows a caller asked for verbatim would be its own surprise.
|
|
85
|
-
- **@voltro/cli** — **The remaining file-moving codemods narrow their projects too**, and the shared project is no longer a stale snapshot.
|
|
86
|
-
|
|
87
|
-
`0.14.0/03_pages-suffix` is gated on `/src/pages/` throughout — plus each app's `app.config.ts`, which is how it tells a real web app's pages from a library that merely keeps components under `src/pages/`. `0.15.0/01_undo-mistaken-taxonomy-renames` only ever renames `*.component[.ui].ts(x)` and an export-less `*.types.ts(x)`. Both now declare that scope and take the importer closure instead of the workspace.
|
|
88
|
-
|
|
89
|
-
`0.14.0/04_file-taxonomy` deliberately keeps the whole project: its `isCandidate` is "any `.ts`/`.tsx` that does not already carry a convention", so there is nothing to narrow and pretending otherwise would only move the cost.
|
|
90
|
-
|
|
91
|
-
**And a real bug the equivalence test caught.** The shared project was built once, lazily, at the first whole-project codemod — *before* a narrowed one wrote its renames to disk. So `0.14.0/04` ran against the pre-`03_pages-suffix` tree and classified `alpha.tsx` as a component, where the whole-project run produced `alpha.page.tsx`. The shared project is now dropped whenever a narrowed codemod changes the disk, and each shared codemod saves before the next narrowed one reads it — the disk is the single source of truth in both directions.
|
|
92
|
-
|
|
93
|
-
Nothing in either codemod's own output hinted at it: both reported success, with different results. Verified by running the same fixture through the 0.13.0 → 0.16.0 jump both ways and diffing the trees; identical, including an aliased and a relative importer outside every scope.
|
|
94
|
-
- **@voltro/cli** — **`voltro update` sizes the codemod pass's heap from the machine.** Node caps the old space near 4 GB regardless of installed RAM, so a large monorepo died on a machine with memory to spare — and died in V8, with no line naming a limit.
|
|
95
|
-
|
|
96
|
-
The pass holds one ts-morph project whose cost is roughly linear in the file count: measured at ~97 KB per file on a synthetic fixture (818 MB at 2,320 files, 1,399 MB at 8,320 — median of three runs on an otherwise idle machine). An adopter's repo needed ~30 GB; at node's default it aborted in 83 seconds.
|
|
97
|
-
|
|
98
|
-
The re-exec'd child now gets `--max-old-space-size` at 75% of total RAM, leaving room for the OS. An explicit `NODE_OPTIONS` from the caller always wins, and nothing is set when 75% would be *below* node's own default — a lower ceiling than node would pick is a pure regression.
|
|
99
|
-
|
|
100
|
-
This does not make an impossible run possible; it stops an arbitrary limit from being the binding one. Where the machine genuinely lacks the memory, the scan's file ceiling reports it in words.
|
|
101
|
-
|
|
102
|
-
**Measured, not assumed — and three plausible fixes were measured and rejected first**: replacing the runner's per-codemod full-text bookkeeping, releasing ts-morph's node-wrapper cache, and hoisting the aliased-importer resolution out of the move loop. A fourth, chunking the pass into per-codemod projects, is correct (109/109 codemod tests pass with one file per project) but does not flatten the curve — peak still grows ~97 KB per file either way, because the codemods that move files must see the importers and so keep the whole-project view.
|
|
103
|
-
- **@voltro/cli** — **A codemod that moves files no longer loads the whole workspace.** Peak memory now follows what the codemod touches instead of how large the user's repository is.
|
|
104
|
-
|
|
105
|
-
A mover has to see its importers — `SourceFile.move()` rewrites the relative specifiers pointing at the moved file and `moveCarryingAliasedImports` does the same for aliased ones, but only for importers that are IN the project. So the movers took everything, and everything is what made the pass cost ~107 KB per file. An adopter's monorepo needed ~30 GB and died in a V8 abort.
|
|
106
|
-
|
|
107
|
-
`codemodImporterClosure` answers "who imports these" from TEXT: one streaming pass that extracts module specifiers and discards the source. A codemod declaring `scope` + `needsImporters` then gets a project of its scope plus that closure. `0.17.0/01_pages-are-directories` — the most expensive codemod in a run — is the first to use it; it only ever touches files under `src/pages/`, so the rest of the repo was pure cost.
|
|
26
|
+
- **@voltro/database, @voltro/sql-postgres, @voltro/sql-mysql, @voltro/sql-sqlite, @voltro/sql-mssql** — The correlation bridge did not survive a transaction, and did not survive CDC. Both are fixed, and both were found by measuring against live databases after a consumer isolated the symptom in a scratch app.
|
|
108
27
|
|
|
109
|
-
|
|
28
|
+
**Every write a framework mutation makes was unattributed.** `transactional()` is entered from the request's async-local scope, but its callback runs from inside the Effect the store builds — and measured against live postgres AND live mariadb, the scope is active at the call site and EMPTY inside the callback. Framework mutations are auto-transactional, so this was every handler write. Same class as the `bindMutation` defect fixed alongside it: a scope covering the construction of an Effect and not its execution. The caller's attribution is now captured at `transactional()` entry and re-established around the callback, in all four dialect stores.
|
|
110
29
|
|
|
111
|
-
|
|
30
|
+
**And the CDC transports could not carry it at all.** Under `changeStrategy: 'cdc'` — the DEFAULT — the event a subscriber receives is rebuilt from a postgres NOTIFY payload or a mysql binlog row image, neither of which can hold a request context. `registerPendingAttribution` / `claimPendingAttribution` (`@voltro/database`) let the write path hand its identity to the echo, keyed by `(table, op, id)` and claimed once. A write made on ANOTHER replica has nothing pending and stays unattributed, which is the correct answer rather than a gap.
|
|
112
31
|
|
|
113
|
-
|
|
32
|
+
**Plus one nobody had reported, found on the way:** on postgres under CDC the write path skipped `routeEvent` entirely, and `runWriteRecorders` lives inside it — so `versioningPlugin({ timing: 'in-transaction' })` with the default `CDC=1` recorded NOTHING. The mode whose entire promise is "if the change committed, the entry is there" wrote an empty trail, silently. `routeEvent` now runs in both modes; only the DELIVERY decision is strategy-dependent.
|
|
114
33
|
|
|
115
|
-
|
|
34
|
+
New live-dialect suites (`cdcAttribution.integration.test.ts` in `sql-postgres` and `sql-mysql`) pin all of it, and were verified red against the previous code.
|
|
35
|
+
- **@voltro/plugin-versioning, @voltro/runtime, @voltro/cli** — `_voltro_row_history.traceId` and `.subjectId` were NULL on every write. Three independent causes, all found from one consumer report whose evidence pinned the diagnosis before we looked: `subjectId` was NULL while `changedBy` on the SAME row carried the acting user — so the identity was known and was not travelling.
|
|
116
36
|
|
|
117
|
-
|
|
118
|
-
- **@voltro/cli** — **The codemod scan's file ceiling now applies to the git enumeration path**, which is the path every real project takes.
|
|
37
|
+
- **The adapter dropped them.** `dataStoreHistoryStore.append` hand-wrote its insert object and listed `changedBy` but not `traceId` / `subjectId`. This is the second time that shape has bitten in this file — the READ side (`rowToVersion`) had drifted identically. A row built by hand in one place and read by hand in another disagree exactly when a field is ADDED, because nothing fails. Both now spread the row. - **An Effect-returning handler was unattributed.** `bindMutation` established the scope around the CALL, which covers an async executor for its whole run — but an Effect-returning one is only CONSTRUCTED there and runs later. It is now forked inside the scope, with interruption and typed failures preserved (both pinned by tests). Verified by measurement, not assumption: an Effect forked inside an ALS scope keeps seeing it across `sleep`, `yieldNow` and a `setTimeout` promise, while the same effect merely constructed inside sees nothing. - **The devtools `/invoke` path never entered the scope at all.** It bypasses the rpc stack by design, and that also bypassed everything `bindMutation` sets up. The audit plugin recorded a traceId (it reads `requestContext.traceId`, which this path does build) while every write underneath carried none — three consumers of one call disagreeing about its trace. It also synthesised `inspect-<8 random chars>`, which no trace consumer can parse; the reporter's framing is the rule worth keeping — *a synthesised id produces a column that looks joinable and is not; NULL at least fails honestly.* It is a real 32-hex id now, and it reaches all three sinks.
|
|
119
38
|
|
|
120
|
-
`
|
|
39
|
+
`actingUserId` is imported at the new call site rather than re-derived — one answer to "who is writing", shared with what `audit()` stamps.
|
|
40
|
+
- **@voltro/cli** — Three ways a check reported nothing while checking nothing, all found by a consumer verifying the silence instead of trusting it.
|
|
121
41
|
|
|
122
|
-
The
|
|
42
|
+
- **`unexercised-row-filter` never fired on a typed registration.** The match was `/\bsetRowFilter\s*\(/`, which demands the paren directly after the name, so `setRowFilter<Ctx>({…})` — the spelling our own generic signature invites — broke it. The rule was blind for exactly the teams that had wired `load`/`predicate` carefully. It counts CALLS now. - **…and its test-side condition was satisfiable by a COMMENT.** It matched `rowFilter:` in raw text. Comments and string literals are stripped, and the suite must both call `makeTestContext` and bind `rowFilter` in real code. - **The same paren-adjacent shape sat in two shipped codemod gates.** `0.7.0/01` (row filter) and `0.7.0/02` (`invoke`) both gate on a generic export, so a typed call made `voltro update` print nothing at all: the upgrade reads as clean and the behaviour change lands unread. Both now use the shared `callPattern`, which allows type arguments including nested ones.
|
|
123
43
|
|
|
124
|
-
|
|
125
|
-
- **@voltro/cli** — **`component/one-per-file` counted things that are not components — and its message asserted they were, which is what made it expensive.**
|
|
44
|
+
Two false-positive fixes in the hand-roll detector, from the same report:
|
|
126
45
|
|
|
127
|
-
The
|
|
46
|
+
- **A file that WIRES a plugin is no longer told to adopt it.** The `presence` rule reported `app.config.ts` (which calls `presencePlugin()`) to an app that had just deleted its hand-rolled table. Rules that recommend a package now declare it, and a file referencing that package is skipped. - **Generated files and `.d.ts` are out of the scan.** A recommendation aimed at a file the next boot overwrites is never actionable.
|
|
128
47
|
|
|
129
|
-
|
|
48
|
+
And one more of the first kind, found while checking why a withdrawn report's probe had stayed silent: `raw-fetch` counted only a BARE `fetch(…)` callee, so `globalThis.fetch(url)` / `self.fetch(url)` in a server file read as clean.
|
|
49
|
+
- **@voltro/cli** — `voltro doctor`'s `serverOnly: NOT CHECKED` line now names the failure, and the field exists in `--json`.
|
|
130
50
|
|
|
131
|
-
|
|
51
|
+
The refusal to claim a pass was right. What shipped with it was nothing to act on: the `catch` discarded the error entirely, so there was no reason, no failing module, and — because the field was absent from `--json` — no way for CI to assert "still unchecked" rather than reading silence as a pass.
|
|
132
52
|
|
|
133
|
-
The
|
|
53
|
+
A consumer's verdict, which is the useful part: *"The message is honest and that is the problem."* They had already verified that every descriptor, `app.config.ts` and the generated rpc group imported cleanly under `tsx` on their own, so the difference had to be in what `loadDiscovered` does BEYOND importing — and none of that was visible from outside. It matters more than its size because `.serverOnly()` is what guards their `sessions.tokenHash` and `apiKeys.keyHash`, markers they added after finding a query whose output schema shipped a hash over the wire.
|
|
134
54
|
|
|
135
|
-
|
|
136
|
-
- **@voltro/
|
|
55
|
+
`--json` now carries `serverOnly: { checked, reason?, leaks? }`. Gate CI on `checked === false`.
|
|
56
|
+
- **@voltro/plugin-versioning** — A version snapshot no longer copies `.serverOnly()` columns into `_voltro_row_history`. `.encrypted()` columns are KEPT, and that distinction is the whole finding.
|
|
137
57
|
|
|
138
|
-
|
|
58
|
+
Reported by a team choosing which tables to version: `sessions` holds `.encrypted()` PATs and a `tokenHash`, `apiKeys` holds a `keyHash`, and they could not determine from outside what the snapshot would contain. They excluded both tables — then went and measured it, which corrected their own report:
|
|
139
59
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
Three of the five spawn sites were already correct — `envWatch`, the web-dev respawn, and the shim — and nothing said the other two were wrong. A test now fails if any non-test file under `src/` writes the flags by hand.
|
|
145
|
-
- **@voltro/cli, @voltro/devtools** — **The in-page devtools overlay had no way to authenticate against a fail-closed inspect surface — and the one channel it documented was disabled two directories away.**
|
|
146
|
-
|
|
147
|
-
Since `/_voltro/inspect/*` went fail-closed, the overlay's Traces / Webhooks / Indexes panels needed a bearer token. A browser cannot be given one: the only channel that would reach it is a `VITE_`-prefixed env var, i.e. a live credential compiled into every bundle, which this framework refuses to do anywhere. So the overlay pointed at `VITE_VOLTRO_INSPECT_TOKEN` — which `voltro dev` and `voltro build` both make unreadable on purpose, by setting vite's `envPrefix` to a sentinel that matches no real variable. A reader who followed the docs set the variable, got no header, and had no way to see why.
|
|
148
|
-
|
|
149
|
-
Three changes, one shape:
|
|
150
|
-
|
|
151
|
-
- **`voltro dev`'s vite proxy attaches the minted bearer server-side** on the `/_voltro/api/<name>` route the panels fetch through. Nothing to configure, and the token never reaches the page. It refuses two cases deliberately: a caller that already sent an `Authorization` header (the dashboard forwards a real one — overwriting it would re-scope somebody else's request), and a non-loopback target (`proxyTarget` is user config, so injecting unconditionally would hand this machine's credential to a host we do not control). - **The dead `VITE_VOLTRO_INSPECT_TOKEN` fallback is gone.** `<VoltroDevtools inspectToken>` remains for reaching an api the proxy does not front, and its doc now says plainly that whatever you pass ships in the bundle. Its test previously admitted, in a comment, that it asserted the null path and called it the fallback — which is how a documented-but-dead channel survived a green suite. - **The empty-state copy said inspect was "open in dev mode".** That stopped being true when the surface went fail-closed, so a reader who hit a 401 was told by the panel itself that it could not have been an auth failure. It now names the remedy.
|
|
152
|
-
|
|
153
|
-
Separately: the `indexes` tab-badge count polled with the overlay CLOSED. Its two neighbours (`traces`, `webhooks`) take an `enabled` flag; this one was missed, and it is the expensive one — it holds an `EventSource` open per api for the whole life of the page, plus a fallback poll whenever that stream errors.
|
|
154
|
-
- **@voltro/cli** — **`voltro doctor`'s boundary rules could not follow a `@/`-aliased import, so they under-reported — silently.**
|
|
155
|
-
|
|
156
|
-
The file-taxonomy walker resolved relative specifiers and nothing else. On an app that imports through a tsconfig `paths` alias — which is most of them — every such edge was invisible, and a rule that cannot see an edge cannot fire on it:
|
|
157
|
-
|
|
158
|
-
- `internal/foreign-import` and `fixture/production-import` came back CLEAN on code that violates them. That is the dangerous direction: no findings reads exactly like no problems. - `ui/unlinked` fired on all 16 of one app's presentational components, because each was reached only through `@/components/…`. A rule that fires on correct code teaches people to ignore it.
|
|
159
|
-
|
|
160
|
-
The graph now resolves aliases from the NEAREST `tsconfig.json` walking up to the scan root — `voltro doctor` runs at the project root while `@/*` is declared per app, so reading only the root config found no `paths` at all in the layout we scaffold.
|
|
161
|
-
|
|
162
|
-
Two more edge forms were missing for the same reason: `export … from` and dynamic `import()`. The re-export one is not a completeness flourish — a barrel is the file most likely to reach across a feature boundary, so `export { x } from './orders.internal'` is precisely the case `internal/foreign-import` exists to catch, and it was the one shape the rule could not see.
|
|
163
|
-
- **@voltro/cli** — **The release gate now fails on a skipped test it was not told about.** "All green" has to mean everything RAN, or the total is a number about how little was attempted.
|
|
164
|
-
|
|
165
|
-
Locally a skip stays fine — nobody should need six databases to run `pnpm test`, and a suite that fails without them stops being run at all. In CI it is not fine: a skipped test is an unverified claim wearing the same colour as a verified one.
|
|
166
|
-
|
|
167
|
-
`scripts/check-no-skipped-tests.mjs` reads the per-package vitest summaries out of the test step and fails on anything skipped that is not declared in its `ALLOWED` map with a reason and an **exact** count. Both directions are enforced, and the second is the one that matters:
|
|
168
|
-
|
|
169
|
-
- more skips than declared → something stopped running; - **fewer** skips than declared → the entry is stale, and a stale allowlist silently absorbs the next regression. That is the failure mode an allowlist has instead of the one it removes, and it is only survivable if the list is forced to stay exact.
|
|
170
|
-
|
|
171
|
-
It runs `--selftest` first, like the changelog and message-API gates, for the same reason: a check that has quietly stopped detecting anything still prints green. Both of its rules were verified by breaking them and watching the selftest go red — the ANSI stripping and the stale-entry direction.
|
|
172
|
-
|
|
173
|
-
**Two kinds of skip exist and only one is a defect.** "The dependency was not there" is a coverage gap — start the service. "This does not apply to this configuration" is correct — declare it. The allowlist holds exactly the second kind: four tests in `sql-sqlite` and `sql-turso` whose dialects keep no workflow-runner state in SQL, so cross-process resume is not a thing they can do. The 12 replication tests that would have been the first entries were the FIRST kind, and cost 3.4 GB on a 16 GB runner — `ci.yml` starts their services instead of declaring them away.
|
|
174
|
-
|
|
175
|
-
The check found both of its first three catches on its own first run: 18 cache tests covering one RESP engine of four, and the two dialect suites above. It also caught itself — it passed in 0.2 s on a run whose test step had aborted after 0.5 s, because an empty log has nothing to complain about. A log with no vitest summaries is now a failure, with its own selftest case.
|
|
176
|
-
- **@voltro/cli** — **Four `voltro dev` inspect endpoints answered 200 to any caller: `traces`, `webhooks`, `analytics`, `aggregates`.**
|
|
177
|
-
|
|
178
|
-
`handleInspectRequest` gates everything that reaches it. The branches in front of it are EARLY RETURNS — they answer and never reach it — so each had to remember to gate itself, and four did not. `traces` is the sharp one: an adopter measured 38 KB of live spans from an unauthenticated `curl`, a request-by-request record of what the process just did. Per the tracing docs those spans also carry `rpc.tag`, `subject.type` and `tenant.id`.
|
|
179
|
-
|
|
180
|
-
That is the split `0.12.0` was written to close — *"the absence of a secret is not consent"* — with `metrics` and `logs` gated and `traces`, which is strictly more revealing than either, not.
|
|
181
|
-
|
|
182
|
-
**Scope, because the reporter could not test it and asked: `voltro serve` and `voltro start` are NOT affected.** Neither mounts these branches — `serveApi.ts` contains no `/_voltro/inspect` path at all, and `start.ts` goes through the shared, gated `handleInspectRequest`. The leak is dev-only, which lowers the severity without removing it: a dev server on a shared machine or a bind-mounted container is not private either.
|
|
183
|
-
|
|
184
|
-
The gate now runs ONCE at the door, before any branch, so a new branch cannot be added without it. CORS preflight stays exempt — a browser sends `OPTIONS` with no `Authorization` header by construction, and the preflight carries no data.
|
|
185
|
-
|
|
186
|
-
**The failure mode was already written down one level below.** `inspectLogsEndpoint` carries the comment *"gating per-caller drifted (start was ungated, dev gated nothing, webDev gated disabled-but-not-token)"* and single-sources the gate inside the handler. The lesson was right; it was applied at the wrong depth.
|
|
187
|
-
|
|
188
|
-
**One branch changed behaviour beyond the four: `POST /_voltro/inspect/clientLog` on the API.** It is an ingest endpoint, so the risk it carried was injection rather than disclosure — anyone could write lines into the developer's terminal log. Nothing in the framework posts there: `@voltro/web`'s browser bridge ships to its own origin, which the web dev server serves and deliberately leaves ungated (a browser has no token, and that path accepts only log batches). The API's copy is reached by direct callers, which can carry one.
|
|
189
|
-
|
|
190
|
-
**The overlay is carried across this**, in the same release: the Vite dev proxy now attaches the minted bearer server-side (see the devtools-overlay entry), so the panels stay live and the browser still never holds the token.
|
|
191
|
-
- **@voltro/database** — **Postgres FK/PK introspection reads `pg_catalog`, not `information_schema` — this killed a `voltro dev` auto-migrate hang that never finished.**
|
|
192
|
-
|
|
193
|
-
This shipped in code without a changelog entry, so nobody upgrading was told either that the hang was fixed or that a new env var exists. Recording it now, with the numbers measured on our own hardware rather than taken from the report.
|
|
194
|
-
|
|
195
|
-
On a FK-dense schema, `voltro dev` hung indefinitely at `auto-migrate: planning schema` — pod 0/1, no error, no timeout. The FK query 4-way-joined `information_schema.{table_constraints, key_column_usage, constraint_column_usage, referential_constraints}`. `constraint_column_usage` is a security-barrier view whose `table_name IN (…)` predicate does **not** push down, so every batch re-scanned FK metadata for the whole catalog. The `blocked` refuse-gate sits *after* introspection, so its helpful message never printed.
|
|
196
|
-
|
|
197
|
-
Measured against a live 532-table / 2637-FK postgres 17:
|
|
198
|
-
|
|
199
|
-
| | one batch of 20 tables | full introspection | |---|---|---| | `information_schema` | **2041 ms** | ~55 s extrapolated over 27 batches | | `pg_catalog` OID join | **8.7 ms** | **404 ms cold, 368–374 ms warm** |
|
|
60
|
+
```
|
|
61
|
+
probeItems.secret enc:v1:a56iziEV9THLhzmJ:Vk0ux+0bECleTLBJkCa0Rg==:3AtMwP…
|
|
62
|
+
_voltro_row_history {"secret":"enc:v1:a56iziEV9THLhzmJ:Vk0ux+0bECleTLBJkCa0Rg==:…"}
|
|
63
|
+
```
|
|
200
64
|
|
|
201
|
-
|
|
65
|
+
**`.encrypted()` lands as ciphertext, byte-identical to the source column**, so versioning such a table widens nothing — the history is exactly as readable as the row it came from. Withholding it would have cost real audit data to prevent an exposure that does not exist.
|
|
202
66
|
|
|
203
|
-
|
|
67
|
+
**`.serverOnly()` is withheld**, and the reason is not "a second copy under different retention" — that argument is weak on its own, since the hash already sits in the source table. The decisive one: `crud.*` STRIPS `.serverOnly()` columns from every row it returns, and a snapshot would smuggle the same value back past that stripping inside a `json()` blob, where no column-level rule applies.
|
|
204
68
|
|
|
205
|
-
|
|
69
|
+
Withheld names are listed under `data._omitted`, so a reader can tell "this column was withheld" from "this column did not exist then". Both timings apply the same policy. `.sensitive()` is not involved: it is an export-masking marker for values that are legitimately readable in the app.
|
|
70
|
+
- **@voltro/cli** — A page that RE-EXPORTS its component (`export { default, renderMode } from '../page'`) no longer fails the codegen gate with "exports no default". The check required the literal words `as default`, so the one spelling that lets two routes share a screen without copying it was the one spelling it refused — and it refused in `voltro build`, while dev and tests stayed green because nothing prerenders there. `export { default as Screen }` is still correctly rejected: it renames the default away.
|
|
206
71
|
|
|
207
|
-
|
|
208
|
-
- **@voltro/cli** — **`voltro update --only <id>` works in the spelling the tool itself prints**, and an unknown flag is now refused instead of ignored.
|
|
72
|
+
Two follow-ons from the same shape:
|
|
209
73
|
|
|
210
|
-
|
|
74
|
+
- The refusal message said the file "ends in `.page.tsx`" and offered "drop the `.page` suffix" as a fix. That is the 0.15.0 convention, replaced by directory routing in 0.17.0 — it named a convention that no longer exists and a fix that could not work. It now names `page.tsx` and both real fixes. - `scanRenderProfile` read a forwarded `renderMode` as absent and fell back to `'static'`, so `staticSafe` and the deploy-target classification could call an app CDN-deployable with an `ssr` route in it. The forward is now followed (relative specifiers, depth-capped); an unresolvable one still falls back rather than failing the scan.
|
|
75
|
+
- **@voltro/database** — `VOLTRO_SOFT_DROP=1` could never converge. The applier renames the object to `<name>__dropped_<ts>` instead of dropping it, which leaves it undeclared — and the differ read that as one more forgotten table, planning the drop again. The re-plan inside `applyPlan` then found an operation still outstanding and aborted with "the DDL for these operations is a no-op — this is a framework bug", which was a wrong diagnosis of a real defect: the DDL had worked. No fingerprint was recorded, so the migration counted as unapplied and every later `db apply` / boot hit the same wall. The only exit was a hard drop of the snapshot — exactly the recoverability the flag is chosen for.
|
|
211
76
|
|
|
212
|
-
|
|
213
|
-
$ voltro update --codemods-only --only 0.17.0/01_pages-are-directories
|
|
214
|
-
voltro update: no package.json at …/web/0.17.0/01_pages-are-directories
|
|
215
|
-
```
|
|
77
|
+
The planner now treats `<name>__dropped_<YYYYMMDDHHMMSS>` as framework-managed, alongside `_voltro_*` / `cluster_*`. Deliberately not retention-aware: a planner whose output depends on the clock would produce different plans before and after midnight, and `db gc-snapshots` already owns expiry.
|
|
216
78
|
|
|
217
|
-
|
|
79
|
+
Reported against tables; the same defect existed one level down for soft-dropped COLUMNS, where it was worse — a re-planned `drop-column` carries no `dropped()` marker and so refuses to plan at all. Both are fixed.
|
|
218
80
|
|
|
219
|
-
|
|
81
|
+
The convergence message itself no longer asserts a cause it cannot know. It said "the DDL for these operations is a no-op", which was flatly wrong here and sent the reporter looking for dead DDL. It now names both causes — no-op DDL, and a planner that cannot see what the DDL did — and says which one an operation naming a just-renamed object usually is.
|
|
220
82
|
|
|
221
83
|
### Internal (no consumer-facing effect)
|
|
222
84
|
|
|
223
|
-
- **The
|
|
224
|
-
|
|
225
|
-
The changelog already states the rule: "The public API of each package is its `publishConfig.exports` entry points." Entry pointS — but every generated `api-extractor.json` pointed at the package's main entry and nothing else, so **87 declared subpaths had no golden at all**: `@voltro/protocol/apikey`, the nine `@voltro/plugin-auth/*`, `@voltro/web/hooks`, `@voltro/database/sql`, and the rest.
|
|
226
|
-
|
|
227
|
-
That is the signal that forces a `BREAKING` changelog entry and its codemod, and on a subpath it was simply absent. `ApiKeyStrategyOptions.resolveKey` gained a parameter in this same release and no gate said anything — additive, so harmless, but the repo's own "more precise is still breaking" rule (the one that cost an adopter 102 hand-fixes) would have shipped silently through the same hole.
|
|
228
|
-
|
|
229
|
-
`gen-api-extractor.mjs` — already the single source of truth for this wiring — now emits one config per entry point (73 → 160) and one golden each, derived from the exports map so the checked set is BY CONSTRUCTION the published set. It also deletes configs and goldens for entry points a package no longer exports: a golden nothing runs reads exactly like covered surface. The per-package `api:check` chains its configs with `&&` rather than a loop, so the first failure stops and reports — a loop is what let the local gate score a green `api-surface` over two stale goldens.
|
|
85
|
+
- **The `0.20.0/01_write-recorder-port` codemod gains the gate test its two predecessors have.**
|
|
230
86
|
|
|
231
|
-
|
|
87
|
+
`codemodRegistry.test.ts` asserts that every `*.codemod.ts` on disk is registered and that ids are unique — registration, not behaviour. What it cannot see is the one way a `manual` codemod fails in practice: an `appliesTo` that is too broad, so the note prints for projects that have nothing to do. That is not a cosmetic problem. A note which fires on every app is how readers learn to skip notes, and the next one carries a boot refusal.
|
|
232
88
|
|
|
233
|
-
|
|
234
|
-
- `pnpm gate` ran each ci.yml `run:` block with `pipefail` but not `-e`, while GitHub Actions' default shell is `bash --noprofile --norc -eo pipefail`. A multi-command block whose MIDDLE command failed carried on, and the step's status became the status of the last command — so the `api-surface` step, a `for` loop over every package's `api:check`, printed two API-drift warnings and still reported `✓`. The gate said green on a tree whose CI job goes red, which is the one thing it exists to prevent.
|
|
89
|
+
This codemod is the case where the silent direction matters most. The break is a TYPE error, so `tsc` already names every affected site; the note exists only to explain `maxOf`, which the compiler cannot. Apps that merely ENABLE `timing: 'in-transaction'` need to do nothing — `plugin-versioning` ships the recorder and it is already updated — and they are the large majority.
|
|
235
90
|
|
|
236
|
-
|
|
91
|
+
Four cases, covering both directions: a project registering its own recorder (note prints, and names `{ append }`, `maxOf`, and the `null`-is-not-zero distinction that a hand-written sequence gets wrong), an app that only enables the timing (silent), the identifier in a comment or a string (silent), and the generic call form `registerWriteRecorder<Row>(…)`, which `callPattern` admits and a naive match would miss.
|
|
@@ -11,16 +11,16 @@
|
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"@effect/platform": "^0.96.1",
|
|
13
13
|
"@effect/rpc": "^0.75.1",
|
|
14
|
-
"@voltro/ai": "0.
|
|
15
|
-
"@voltro/cli": "0.
|
|
16
|
-
"@voltro/database": "0.
|
|
17
|
-
"@voltro/env": "0.
|
|
18
|
-
"@voltro/protocol": "0.
|
|
19
|
-
"@voltro/runtime": "0.
|
|
14
|
+
"@voltro/ai": "0.20.0",
|
|
15
|
+
"@voltro/cli": "0.20.0",
|
|
16
|
+
"@voltro/database": "0.20.0",
|
|
17
|
+
"@voltro/env": "0.20.0",
|
|
18
|
+
"@voltro/protocol": "0.20.0",
|
|
19
|
+
"@voltro/runtime": "0.20.0",
|
|
20
20
|
"effect": "^3.21.2"
|
|
21
21
|
},
|
|
22
22
|
"devDependencies": {
|
|
23
|
-
"@voltro/testing": "0.
|
|
23
|
+
"@voltro/testing": "0.20.0",
|
|
24
24
|
"typescript": "^5.7.0",
|
|
25
25
|
"vitest": "^3.0.0"
|
|
26
26
|
}
|
|
@@ -12,17 +12,17 @@
|
|
|
12
12
|
"dependencies": {
|
|
13
13
|
"@effect/platform": "^0.96.1",
|
|
14
14
|
"@effect/rpc": "^0.75.1",
|
|
15
|
-
"@voltro/cli": "0.
|
|
16
|
-
"@voltro/database": "0.
|
|
17
|
-
"@voltro/env": "0.
|
|
18
|
-
"@voltro/plugin-auth": "0.
|
|
19
|
-
"@voltro/protocol": "0.
|
|
20
|
-
"@voltro/runtime": "0.
|
|
21
|
-
"@voltro/sql-postgres": "0.
|
|
15
|
+
"@voltro/cli": "0.20.0",
|
|
16
|
+
"@voltro/database": "0.20.0",
|
|
17
|
+
"@voltro/env": "0.20.0",
|
|
18
|
+
"@voltro/plugin-auth": "0.20.0",
|
|
19
|
+
"@voltro/protocol": "0.20.0",
|
|
20
|
+
"@voltro/runtime": "0.20.0",
|
|
21
|
+
"@voltro/sql-postgres": "0.20.0",
|
|
22
22
|
"effect": "^3.21.2"
|
|
23
23
|
},
|
|
24
24
|
"devDependencies": {
|
|
25
|
-
"@voltro/testing": "0.
|
|
25
|
+
"@voltro/testing": "0.20.0",
|
|
26
26
|
"typescript": "^5.7.0",
|
|
27
27
|
"vitest": "^3.0.0"
|
|
28
28
|
}
|
|
@@ -12,16 +12,16 @@
|
|
|
12
12
|
"dependencies": {
|
|
13
13
|
"@effect/platform": "^0.96.1",
|
|
14
14
|
"@effect/rpc": "^0.75.1",
|
|
15
|
-
"@voltro/cli": "0.
|
|
16
|
-
"@voltro/database": "0.
|
|
17
|
-
"@voltro/env": "0.
|
|
18
|
-
"@voltro/plugin-multitenancy": "0.
|
|
19
|
-
"@voltro/protocol": "0.
|
|
20
|
-
"@voltro/runtime": "0.
|
|
15
|
+
"@voltro/cli": "0.20.0",
|
|
16
|
+
"@voltro/database": "0.20.0",
|
|
17
|
+
"@voltro/env": "0.20.0",
|
|
18
|
+
"@voltro/plugin-multitenancy": "0.20.0",
|
|
19
|
+
"@voltro/protocol": "0.20.0",
|
|
20
|
+
"@voltro/runtime": "0.20.0",
|
|
21
21
|
"effect": "^3.21.2"
|
|
22
22
|
},
|
|
23
23
|
"devDependencies": {
|
|
24
|
-
"@voltro/testing": "0.
|
|
24
|
+
"@voltro/testing": "0.20.0",
|
|
25
25
|
"typescript": "^5.7.0",
|
|
26
26
|
"vitest": "^3.0.0"
|
|
27
27
|
}
|
|
@@ -12,16 +12,16 @@
|
|
|
12
12
|
"dependencies": {
|
|
13
13
|
"@effect/platform": "^0.96.1",
|
|
14
14
|
"@effect/rpc": "^0.75.1",
|
|
15
|
-
"@voltro/cli": "0.
|
|
16
|
-
"@voltro/database": "0.
|
|
17
|
-
"@voltro/env": "0.
|
|
18
|
-
"@voltro/plugin-deactivation": "0.
|
|
19
|
-
"@voltro/protocol": "0.
|
|
20
|
-
"@voltro/runtime": "0.
|
|
15
|
+
"@voltro/cli": "0.20.0",
|
|
16
|
+
"@voltro/database": "0.20.0",
|
|
17
|
+
"@voltro/env": "0.20.0",
|
|
18
|
+
"@voltro/plugin-deactivation": "0.20.0",
|
|
19
|
+
"@voltro/protocol": "0.20.0",
|
|
20
|
+
"@voltro/runtime": "0.20.0",
|
|
21
21
|
"effect": "^3.21.2"
|
|
22
22
|
},
|
|
23
23
|
"devDependencies": {
|
|
24
|
-
"@voltro/testing": "0.
|
|
24
|
+
"@voltro/testing": "0.20.0",
|
|
25
25
|
"typescript": "^5.7.0",
|
|
26
26
|
"vitest": "^3.0.0"
|
|
27
27
|
}
|
|
@@ -12,18 +12,18 @@
|
|
|
12
12
|
"dependencies": {
|
|
13
13
|
"@react-email/components": "^1.0.12",
|
|
14
14
|
"@react-email/render": "^1.4.0",
|
|
15
|
-
"@voltro/cli": "0.
|
|
16
|
-
"@voltro/database": "0.
|
|
17
|
-
"@voltro/env": "0.
|
|
18
|
-
"@voltro/plugin-mail": "0.
|
|
19
|
-
"@voltro/plugin-multitenancy": "0.
|
|
20
|
-
"@voltro/protocol": "0.
|
|
21
|
-
"@voltro/runtime": "0.
|
|
15
|
+
"@voltro/cli": "0.20.0",
|
|
16
|
+
"@voltro/database": "0.20.0",
|
|
17
|
+
"@voltro/env": "0.20.0",
|
|
18
|
+
"@voltro/plugin-mail": "0.20.0",
|
|
19
|
+
"@voltro/plugin-multitenancy": "0.20.0",
|
|
20
|
+
"@voltro/protocol": "0.20.0",
|
|
21
|
+
"@voltro/runtime": "0.20.0",
|
|
22
22
|
"effect": "^3.21.2",
|
|
23
23
|
"react": "^19.0.0"
|
|
24
24
|
},
|
|
25
25
|
"devDependencies": {
|
|
26
|
-
"@voltro/testing": "0.
|
|
26
|
+
"@voltro/testing": "0.20.0",
|
|
27
27
|
"typescript": "^5.7.0",
|
|
28
28
|
"vitest": "^3.0.0"
|
|
29
29
|
}
|
|
@@ -12,18 +12,18 @@
|
|
|
12
12
|
"dependencies": {
|
|
13
13
|
"@effect/platform": "^0.96.1",
|
|
14
14
|
"@effect/rpc": "^0.75.1",
|
|
15
|
-
"@voltro/cli": "0.
|
|
16
|
-
"@voltro/database": "0.
|
|
17
|
-
"@voltro/env": "0.
|
|
18
|
-
"@voltro/plugin-multitenancy": "0.
|
|
19
|
-
"@voltro/plugin-storage": "0.
|
|
20
|
-
"@voltro/protocol": "0.
|
|
21
|
-
"@voltro/runtime": "0.
|
|
22
|
-
"@voltro/sql-mysql": "0.
|
|
15
|
+
"@voltro/cli": "0.20.0",
|
|
16
|
+
"@voltro/database": "0.20.0",
|
|
17
|
+
"@voltro/env": "0.20.0",
|
|
18
|
+
"@voltro/plugin-multitenancy": "0.20.0",
|
|
19
|
+
"@voltro/plugin-storage": "0.20.0",
|
|
20
|
+
"@voltro/protocol": "0.20.0",
|
|
21
|
+
"@voltro/runtime": "0.20.0",
|
|
22
|
+
"@voltro/sql-mysql": "0.20.0",
|
|
23
23
|
"effect": "^3.21.2"
|
|
24
24
|
},
|
|
25
25
|
"devDependencies": {
|
|
26
|
-
"@voltro/testing": "0.
|
|
26
|
+
"@voltro/testing": "0.20.0",
|
|
27
27
|
"typescript": "^5.7.0",
|
|
28
28
|
"vitest": "^3.0.0"
|
|
29
29
|
}
|
|
@@ -10,17 +10,17 @@
|
|
|
10
10
|
"test": "voltro test"
|
|
11
11
|
},
|
|
12
12
|
"dependencies": {
|
|
13
|
-
"@voltro/cli": "0.
|
|
14
|
-
"@voltro/database": "0.
|
|
15
|
-
"@voltro/env": "0.
|
|
16
|
-
"@voltro/plugin-multitenancy": "0.
|
|
17
|
-
"@voltro/plugin-storage": "0.
|
|
18
|
-
"@voltro/protocol": "0.
|
|
19
|
-
"@voltro/runtime": "0.
|
|
13
|
+
"@voltro/cli": "0.20.0",
|
|
14
|
+
"@voltro/database": "0.20.0",
|
|
15
|
+
"@voltro/env": "0.20.0",
|
|
16
|
+
"@voltro/plugin-multitenancy": "0.20.0",
|
|
17
|
+
"@voltro/plugin-storage": "0.20.0",
|
|
18
|
+
"@voltro/protocol": "0.20.0",
|
|
19
|
+
"@voltro/runtime": "0.20.0",
|
|
20
20
|
"effect": "^3.21.2"
|
|
21
21
|
},
|
|
22
22
|
"devDependencies": {
|
|
23
|
-
"@voltro/testing": "0.
|
|
23
|
+
"@voltro/testing": "0.20.0",
|
|
24
24
|
"typescript": "^5.7.0",
|
|
25
25
|
"vitest": "^3.0.0"
|
|
26
26
|
}
|
|
@@ -11,17 +11,17 @@
|
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"@effect/platform": "^0.96.1",
|
|
13
13
|
"@effect/rpc": "^0.75.1",
|
|
14
|
-
"@voltro/cli": "0.
|
|
15
|
-
"@voltro/database": "0.
|
|
16
|
-
"@voltro/env": "0.
|
|
17
|
-
"@voltro/plugin-governance": "0.
|
|
18
|
-
"@voltro/plugin-multitenancy": "0.
|
|
19
|
-
"@voltro/protocol": "0.
|
|
20
|
-
"@voltro/runtime": "0.
|
|
14
|
+
"@voltro/cli": "0.20.0",
|
|
15
|
+
"@voltro/database": "0.20.0",
|
|
16
|
+
"@voltro/env": "0.20.0",
|
|
17
|
+
"@voltro/plugin-governance": "0.20.0",
|
|
18
|
+
"@voltro/plugin-multitenancy": "0.20.0",
|
|
19
|
+
"@voltro/protocol": "0.20.0",
|
|
20
|
+
"@voltro/runtime": "0.20.0",
|
|
21
21
|
"effect": "^3.21.2"
|
|
22
22
|
},
|
|
23
23
|
"devDependencies": {
|
|
24
|
-
"@voltro/testing": "0.
|
|
24
|
+
"@voltro/testing": "0.20.0",
|
|
25
25
|
"typescript": "^5.7.0",
|
|
26
26
|
"vitest": "^3.0.0"
|
|
27
27
|
}
|