@everystack/cli 0.4.66 → 0.4.68
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 +893 -0
- package/package.json +4 -3
- package/src/cli/commands/certs.ts +13 -2
- package/src/cli/commands/db.ts +93 -15
- package/src/cli/deparse-normal.ts +72 -5
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,893 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to `@everystack/cli` are documented here.
|
|
4
|
+
|
|
5
|
+
Format based on [Keep a Changelog](https://keepachangelog.com/).
|
|
6
|
+
|
|
7
|
+
## [Unreleased]
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
|
|
11
|
+
- secrets test expected .js extension stripped by Metro compat
|
|
12
|
+
- async RSA key generation — unblocks the event loop in parallel test runs
|
|
13
|
+
|
|
14
|
+
## [0.4.66] - 2026-08-15
|
|
15
|
+
|
|
16
|
+
### Fixed
|
|
17
|
+
|
|
18
|
+
- compile owner-scoped `manage` to write policies + add `internal` flag for functions
|
|
19
|
+
|
|
20
|
+
## [0.4.65] - 2026-08-15
|
|
21
|
+
|
|
22
|
+
### Fixed
|
|
23
|
+
|
|
24
|
+
- route rotation DB ops through the ops Lambda (VPC)
|
|
25
|
+
|
|
26
|
+
## [0.4.64] - 2026-08-15
|
|
27
|
+
|
|
28
|
+
### Fixed
|
|
29
|
+
|
|
30
|
+
- resolve all closing-gate findings (F1-F8) on Phase B
|
|
31
|
+
- resolve re-review findings N1-N4
|
|
32
|
+
|
|
33
|
+
## [0.4.63] - 2026-08-15
|
|
34
|
+
|
|
35
|
+
### Changed
|
|
36
|
+
|
|
37
|
+
- read credentials from SST secret store, not individual SSM params
|
|
38
|
+
|
|
39
|
+
## [0.4.62] - 2026-08-15
|
|
40
|
+
|
|
41
|
+
### Added
|
|
42
|
+
|
|
43
|
+
- db:provision --rotate — live credential rotation
|
|
44
|
+
|
|
45
|
+
## [0.4.61] - 2026-08-14
|
|
46
|
+
|
|
47
|
+
### Added
|
|
48
|
+
|
|
49
|
+
- add rollingRestart primitive — bump EVERYSTACK_GEN to retire warm containers
|
|
50
|
+
- update calls rollingRestart after the pointer flip
|
|
51
|
+
- update verifies origin serves the new release after restart
|
|
52
|
+
- cache:purge refuses unless origin serves the expected release
|
|
53
|
+
- deploy waits for CloudFront distribution to reach Deployed
|
|
54
|
+
|
|
55
|
+
### Fixed
|
|
56
|
+
|
|
57
|
+
- drop .js extensions from barrel re-exports so Metro resolves them
|
|
58
|
+
- strip .js extensions from all internal imports for Metro compatibility
|
|
59
|
+
- strip .js from dynamic imports too — Metro traces them the same way
|
|
60
|
+
|
|
61
|
+
## [0.4.60] - 2026-08-14
|
|
62
|
+
|
|
63
|
+
### Fixed
|
|
64
|
+
|
|
65
|
+
- the task image no longer computes the fingerprint it is judged by
|
|
66
|
+
- verify the task stamped the fingerprint the operator sent
|
|
67
|
+
- close the closing-gate findings on the backup fingerprint path
|
|
68
|
+
- bind the fixes to checks that fail, and correct the retraction order
|
|
69
|
+
|
|
70
|
+
## [0.4.59] - 2026-08-13
|
|
71
|
+
|
|
72
|
+
### Fixed
|
|
73
|
+
|
|
74
|
+
- a failed post-apply re-read is its own phase, not bookkeeping
|
|
75
|
+
- the phase messages tell the truth about the memoir and the COMMIT
|
|
76
|
+
- --only scopes the provenance re-key too
|
|
77
|
+
- a pending provenance re-key is visible; --only must not scope it
|
|
78
|
+
- two guards against orphaning a provenance row
|
|
79
|
+
- drop the plan-shape guard, keep the one at the delete site
|
|
80
|
+
- escalate the guard's dollar tag instead of refusing the object
|
|
81
|
+
- never drop provenance for an object that is still live
|
|
82
|
+
- a function whose signature names a relation waits for it
|
|
83
|
+
- the signature is an ordering edge, and quoted type names count
|
|
84
|
+
- db:sync settles in one pass
|
|
85
|
+
- report the two passes as one operation, honestly
|
|
86
|
+
|
|
87
|
+
### Documentation
|
|
88
|
+
|
|
89
|
+
- record why a legacy identity the source also claims still migrates
|
|
90
|
+
- the re-key line says it is pending, and the docs decode it
|
|
91
|
+
|
|
92
|
+
## [0.4.58] - 2026-08-12
|
|
93
|
+
|
|
94
|
+
### Fixed
|
|
95
|
+
|
|
96
|
+
- reconcile recorded provenance before it migrated it
|
|
97
|
+
- the guard's test could not see an unguarded guard
|
|
98
|
+
|
|
99
|
+
## [0.4.57] - 2026-08-12
|
|
100
|
+
|
|
101
|
+
### Fixed
|
|
102
|
+
|
|
103
|
+
- link workspace siblings instead of resolving them from the registry
|
|
104
|
+
- a tree we could not read is not evidence about the database
|
|
105
|
+
- pnpm test was green because it skipped, not because it passed
|
|
106
|
+
- an explicit DATABASE_URL that does not answer is a failure, not a skip
|
|
107
|
+
- the softDelete guard asked about visibility, not about DELETE
|
|
108
|
+
- close the gate findings — the rule was still one grant short
|
|
109
|
+
- db:pull invented a policy for every RLS-off table
|
|
110
|
+
|
|
111
|
+
## [0.4.56] - 2026-08-10
|
|
112
|
+
|
|
113
|
+
### Added
|
|
114
|
+
|
|
115
|
+
- compile and apply a function's declared owner
|
|
116
|
+
- the authz contract tells a scoped operator from a superuser
|
|
117
|
+
- db:pull declares a chosen function owner, and the builder refuses one it cannot enact
|
|
118
|
+
- the ownership mechanism is chosen from the builder, not assumed
|
|
119
|
+
- a serial column's sequence inherits its table's grants
|
|
120
|
+
- assert the schema posture, and detect who can plant a shadow object
|
|
121
|
+
- keep the index name the database already has
|
|
122
|
+
- keep the foreign key constraint name too
|
|
123
|
+
- the round-trip oracle, and the three defects it immediately found
|
|
124
|
+
- capture GENERATED ALWAYS AS IDENTITY through the round trip
|
|
125
|
+
- a view's owner is a privilege boundary too — declare it, build it
|
|
126
|
+
|
|
127
|
+
### Fixed
|
|
128
|
+
|
|
129
|
+
- never mint a REVOKE that ownership explains
|
|
130
|
+
- db:pull reads the search_path, it does not invent one
|
|
131
|
+
- close the two defects the round-trip oracle found
|
|
132
|
+
- a same-name index redefinition drops before it creates
|
|
133
|
+
|
|
134
|
+
## [0.4.55] - 2026-08-10
|
|
135
|
+
|
|
136
|
+
### Added
|
|
137
|
+
|
|
138
|
+
- db:build — the models produce a database you keep
|
|
139
|
+
|
|
140
|
+
### Fixed
|
|
141
|
+
|
|
142
|
+
- the diff path emits declared extensions — fresh builds AND stage applies
|
|
143
|
+
- the generated drizzle artifact typechecks under strict
|
|
144
|
+
|
|
145
|
+
## [0.4.54] - 2026-08-10
|
|
146
|
+
|
|
147
|
+
### Fixed
|
|
148
|
+
|
|
149
|
+
- a database and its own restored clone are one state
|
|
150
|
+
- fingerprint v10 — normalizing checks and index predicates MOVES the hash
|
|
151
|
+
|
|
152
|
+
## [0.4.53] - 2026-08-10
|
|
153
|
+
|
|
154
|
+
### Fixed
|
|
155
|
+
|
|
156
|
+
- predicate identity survives a PostgreSQL major boundary
|
|
157
|
+
- the schema-USAGE phase diffs against the live nspacl instead of emitting blind
|
|
158
|
+
- a multi-schema checkout's models are never reported unmodeled; --stage labeling discoverable from --database-url pulls
|
|
159
|
+
|
|
160
|
+
## [0.4.52] - 2026-08-08
|
|
161
|
+
|
|
162
|
+
### Added
|
|
163
|
+
|
|
164
|
+
- compile rls: false to a grants-only contract — and never downgrade on version skew
|
|
165
|
+
- db:pull transcribes rls: false for the live grants-only shape
|
|
166
|
+
- db:pull names the migration-tool tables it skipped, and what to do
|
|
167
|
+
- db:pull --govern-roles — a foreign role's grants survive migration deletion
|
|
168
|
+
|
|
169
|
+
### Fixed
|
|
170
|
+
|
|
171
|
+
- the two lint gates learn the rls: false posture — live access named as live
|
|
172
|
+
|
|
173
|
+
### Documentation
|
|
174
|
+
|
|
175
|
+
- db:check stops overclaiming; db:pull's rls: false line documented
|
|
176
|
+
|
|
177
|
+
## [0.4.51] - 2026-08-07
|
|
178
|
+
|
|
179
|
+
### Added
|
|
180
|
+
|
|
181
|
+
- db:check fails a model file the barrel never exported
|
|
182
|
+
- db:pull renders extensions, so the declared state can bootstrap itself
|
|
183
|
+
- db:pull adopts a multi-schema database in one pull
|
|
184
|
+
|
|
185
|
+
### Fixed
|
|
186
|
+
|
|
187
|
+
- db:plan's adoption class never reached the artifact it explains
|
|
188
|
+
- declared functions exist before the policies that call them
|
|
189
|
+
|
|
190
|
+
## [0.4.50] - 2026-08-07
|
|
191
|
+
|
|
192
|
+
### Fixed
|
|
193
|
+
|
|
194
|
+
- a recorded grant is reach, and a foreign grantee is never an ability
|
|
195
|
+
|
|
196
|
+
## [0.4.49] - 2026-08-06
|
|
197
|
+
|
|
198
|
+
### BREAKING CHANGES
|
|
199
|
+
|
|
200
|
+
- a grant with no policy is spellable, and no longer renders as an ability
|
|
201
|
+
- --key=value silently changed the TARGET, not just the flag
|
|
202
|
+
|
|
203
|
+
### Added
|
|
204
|
+
|
|
205
|
+
- db:check flags a CRUD grant declared with no policy
|
|
206
|
+
- read a trigger's SHAPE from the catalog, not from its prose
|
|
207
|
+
- a live EXECUTE grant is a fact, so let a function record one
|
|
208
|
+
- db:pull renders trigger bindings, as standalone descriptors
|
|
209
|
+
- record a relation's inexpressible grants instead of skipping the object
|
|
210
|
+
|
|
211
|
+
### Fixed
|
|
212
|
+
|
|
213
|
+
- an ability needs a grant AND a covering policy — on every branch
|
|
214
|
+
- a UNION typed the function identity as `name`, truncating ACLs at 63 bytes
|
|
215
|
+
- a trigger function has no caller to declare, so stop demanding one
|
|
216
|
+
|
|
217
|
+
### Documentation
|
|
218
|
+
|
|
219
|
+
- the trigger API is trigger(), not defineTrigger
|
|
220
|
+
|
|
221
|
+
## [0.4.48] - 2026-08-04
|
|
222
|
+
|
|
223
|
+
### Added
|
|
224
|
+
|
|
225
|
+
- declare the SECURITY DEFINER functions, so db:reconcile can see them
|
|
226
|
+
- writtenBy: 'functions' must have functions, checked at composition
|
|
227
|
+
|
|
228
|
+
## [0.4.47] - 2026-08-04
|
|
229
|
+
|
|
230
|
+
### Fixed
|
|
231
|
+
|
|
232
|
+
- the direct lane for db:plan and db:refresh, and stop recommending --database-url against a deployed stage
|
|
233
|
+
- db:exec's DML-only guard was blind to four things it advertised catching
|
|
234
|
+
|
|
235
|
+
## [0.4.46] - 2026-08-04
|
|
236
|
+
|
|
237
|
+
### Added
|
|
238
|
+
|
|
239
|
+
- policyRoles — the policy's audience, not its grantees
|
|
240
|
+
- field.pgType — a verbatim type is its own identity
|
|
241
|
+
- make column grants first-class
|
|
242
|
+
- db:session — N statements, one connection, one transaction
|
|
243
|
+
|
|
244
|
+
### Fixed
|
|
245
|
+
|
|
246
|
+
- a replacement is never read as equivalent
|
|
247
|
+
- name what the plan could not classify, and stop crying wolf
|
|
248
|
+
- a redundant default precision is not a type change
|
|
249
|
+
- wire the governed filter into every declared-vs-live comparison
|
|
250
|
+
- a column-scoped UPDATE is not SELECT reach
|
|
251
|
+
- a dead policy leaves the identity, not the record
|
|
252
|
+
- a subsumed policy is not part of the state
|
|
253
|
+
- one definition of a public read, and writtenBy stops guessing
|
|
254
|
+
- the stage lane reads twice, and never calls that a verification
|
|
255
|
+
- route every introspection through one session, and delete the bare SET
|
|
256
|
+
|
|
257
|
+
## [0.4.45] - 2026-08-02
|
|
258
|
+
|
|
259
|
+
### BREAKING CHANGES
|
|
260
|
+
|
|
261
|
+
- fingerprint v4 — one equivalence relation, three surfaces
|
|
262
|
+
|
|
263
|
+
### Added
|
|
264
|
+
|
|
265
|
+
- policy identity by rule — the shared matcher
|
|
266
|
+
- classify every authz statement by why it exists
|
|
267
|
+
- recognise the role split in the shared matcher
|
|
268
|
+
- a fingerprint format change refuses, it does not report drift
|
|
269
|
+
- the model can say REFERENCES, TRIGGER, TRUNCATE
|
|
270
|
+
- the pull says what it did not capture, in the file you review
|
|
271
|
+
- the owner is named, because the contract cannot carry it
|
|
272
|
+
|
|
273
|
+
### Fixed
|
|
274
|
+
|
|
275
|
+
- a migration tool's journal is not the app's model
|
|
276
|
+
- PUBLIC is one grantee, not two spellings
|
|
277
|
+
- db:pull renders a live TO PUBLIC read as PUBLIC
|
|
278
|
+
- a value that looks like a flag is still a value
|
|
279
|
+
|
|
280
|
+
## [0.4.44] - 2026-08-02
|
|
281
|
+
|
|
282
|
+
### Fixed
|
|
283
|
+
|
|
284
|
+
- db:pull --derived-out wrote a derived layer the barrel then excluded
|
|
285
|
+
- a public read and an owner read are two branches, not a contest
|
|
286
|
+
- db:pull dropped the owner branch of a `public OR mine` read
|
|
287
|
+
|
|
288
|
+
## [0.4.43] - 2026-08-01
|
|
289
|
+
|
|
290
|
+
### Fixed
|
|
291
|
+
|
|
292
|
+
- a role-scoped read compiled to a grant with no policy, and an omitted WITH CHECK is not a change
|
|
293
|
+
- soft-delete visibility is declared, never inferred from a column name
|
|
294
|
+
- a predicate is parenthesized exactly once, so a pulled policy round-trips
|
|
295
|
+
- a plan's `additive` count was a default, not a finding — and `→ text` was never free
|
|
296
|
+
- db:plan says "do NOT commit" — now it checks whether git agrees
|
|
297
|
+
|
|
298
|
+
## [0.4.42] - 2026-08-01
|
|
299
|
+
|
|
300
|
+
### Added
|
|
301
|
+
|
|
302
|
+
- a role the models cannot name keeps its grants, and is named on every run
|
|
303
|
+
- the adoption baseline — a foreign role that was not there at adoption refuses the plan
|
|
304
|
+
- stripping an undeclared grantee is destructive, and db:check verifies the baseline artifact (cli 0.4.41, model 0.4.7)
|
|
305
|
+
|
|
306
|
+
### Fixed
|
|
307
|
+
|
|
308
|
+
- array-literal quoting, an unvouchable role, a cold build, and a source file grep was skipping
|
|
309
|
+
|
|
310
|
+
## [0.4.40] - 2026-07-31
|
|
311
|
+
|
|
312
|
+
### Added
|
|
313
|
+
|
|
314
|
+
- db:authz gets a local venue, and the published bin actually runs
|
|
315
|
+
- the authz vocabulary compiles what it declares
|
|
316
|
+
- db:pull --abilities live — the authz half of the brownfield on-ramp
|
|
317
|
+
- state the operator role once, and two messages that cost an adopter time
|
|
318
|
+
- a function is identified by its name AND its argument types
|
|
319
|
+
|
|
320
|
+
### Fixed
|
|
321
|
+
|
|
322
|
+
- the red team stops crying wolf — two causes of phantom holes
|
|
323
|
+
- db:fingerprint refuses a barrel it cannot load
|
|
324
|
+
- db:pull read an unnamed multi-word argument as an argument name
|
|
325
|
+
- a killed test run no longer poisons the next one, and a numeric default survives the round trip
|
|
326
|
+
|
|
327
|
+
### Documentation
|
|
328
|
+
|
|
329
|
+
- scrub a consumer role name from the inheritance docblock
|
|
330
|
+
|
|
331
|
+
## [0.4.37] - 2026-07-29
|
|
332
|
+
|
|
333
|
+
### Fixed
|
|
334
|
+
|
|
335
|
+
- db:swap stops fighting its own backup, and its snapshot is now a fact
|
|
336
|
+
|
|
337
|
+
## [0.4.36] - 2026-07-29
|
|
338
|
+
|
|
339
|
+
### Added
|
|
340
|
+
|
|
341
|
+
- db:swap pairs the derived layer — zero downtime, no refresh
|
|
342
|
+
- db:swap --dump-build — the instrument for an unreproducible field bug
|
|
343
|
+
|
|
344
|
+
### Fixed
|
|
345
|
+
|
|
346
|
+
- db:reconcile can rebuild a view backing a function's return type
|
|
347
|
+
- db:swap re-grants schema USAGE — a successful swap dark-sited the app
|
|
348
|
+
- db:swap refuses a PARTIAL derived layer instead of reporting success
|
|
349
|
+
- the paired build sets a search_path — BARE refs could not resolve
|
|
350
|
+
- a paired swap records provenance — the next reconcile was rebuilding everything
|
|
351
|
+
- the schema rewrite must not touch string literals — it renamed JSON keys
|
|
352
|
+
- the mutation-lease refusal names a holder in ANOTHER database
|
|
353
|
+
- "provenance recorded" could print having recorded nothing
|
|
354
|
+
- db:swap will not take its target from DATABASE_URL — --stage was silently overridden
|
|
355
|
+
|
|
356
|
+
## [0.4.35] - 2026-07-28
|
|
357
|
+
|
|
358
|
+
### Added
|
|
359
|
+
|
|
360
|
+
- db:backup local upload venue — break-glass local→stage full replace (0.4.33)
|
|
361
|
+
- db:reconcile applies an authz-only change as a regrant, not a matview rebuild (0.4.34)
|
|
362
|
+
- db:swap --rebuild-derived — swap the schema, regenerate the derived layer
|
|
363
|
+
- the restore heartbeat names WHO holds the lock
|
|
364
|
+
|
|
365
|
+
### Fixed
|
|
366
|
+
|
|
367
|
+
- schema-qualify every derived CREATE so public objects land in public (0.4.34)
|
|
368
|
+
- db:provision writes CAPS secret names only (DATABASE_URL/ADMIN_DATABASE_URL)
|
|
369
|
+
- instrument db:swap's restore leg with a lock-free liveness probe
|
|
370
|
+
- TCP keepalives on every long-running pg binary
|
|
371
|
+
- db:swap refuses instead of silently destroying cross-schema dependents
|
|
372
|
+
- scope the restore heartbeat's COPY counter to our own load
|
|
373
|
+
- walk the swap's dependency gate transitively, not just direct dependents
|
|
374
|
+
- the dependent-drop notice is future tense — it announced drops that had not happened
|
|
375
|
+
- a swap artifact carries base state, not the derived layer
|
|
376
|
+
- the restore heartbeat counts schema growth as progress
|
|
377
|
+
|
|
378
|
+
## [0.4.32] - 2026-07-23
|
|
379
|
+
|
|
380
|
+
### Added
|
|
381
|
+
|
|
382
|
+
- db schema-move primitive — movedFrom marker → ALTER TABLE SET SCHEMA
|
|
383
|
+
|
|
384
|
+
## [0.4.31] - 2026-07-23
|
|
385
|
+
|
|
386
|
+
### Added
|
|
387
|
+
|
|
388
|
+
- the bring-your-own-database operator path — direct db:provision + optional migrations
|
|
389
|
+
- db:provision keeps master off argv and bakes search_path into the URL — 0.4.21
|
|
390
|
+
- credential-free db:reconcile — the derived-edge ops lane — server 0.4.8 + cli 0.4.22
|
|
391
|
+
- credential-free operator lanes — db:refresh --stage + db:doctor direct venue — server 0.4.9 + cli 0.4.23
|
|
392
|
+
- db:refresh --verify-nonempty — the credential-free dark-panel gate — server 0.4.11 + cli 0.4.25
|
|
393
|
+
- mutation-lease core — the fixed-key advisory session lock
|
|
394
|
+
- db:apply --direct — the credential-free unbounded-clock write lane
|
|
395
|
+
- db:reconcile --direct — the unbounded-clock derived-rebuild lane
|
|
396
|
+
- db:backfill --stage — the missing stage lane for data jobs
|
|
397
|
+
- db:swap --stage --direct — the credential-free hot-swap capstone
|
|
398
|
+
- search_path lives on the role, not the URL (the real fix)
|
|
399
|
+
- db:exec spine — crash-truthful audit + single-transaction invariant
|
|
400
|
+
- db:exec — DML-only by semantic catalog digest, no role or superuser
|
|
401
|
+
- db data-movement Phase 2 — ephemeral Fargate Task lane for pg-binary work
|
|
402
|
+
- db data-movement Phase 3a — db:backup/db:export run in the ephemeral Task lane
|
|
403
|
+
- db data-movement Phase 3b foundation — snapshot-verify gate + shared live fingerprint
|
|
404
|
+
- db data-movement Phase 3b — destructive apply gated on a verified backup
|
|
405
|
+
- db data-movement — restore→Fargate + delete the pg-tools layer
|
|
406
|
+
|
|
407
|
+
### Fixed
|
|
408
|
+
|
|
409
|
+
- missing optional aws-sdk clients fail with the install remedy, not an IAM red herring
|
|
410
|
+
- update resolves runtimeVersion policies — no more [object Object] release keys
|
|
411
|
+
- db:provision echoes the target database; db:doctor names the no-Ops cause
|
|
412
|
+
- db:reconcile --only <ids> — the recovery exit from a mistaken --rebaseline — server 0.4.10 + cli 0.4.24
|
|
413
|
+
- db:provision preserves connection params (sslmode) when minting the URL — cli 0.4.26
|
|
414
|
+
- --stage discovery carries backupsBucket — db:backup:download --stage works — cli 0.4.27
|
|
415
|
+
- db:swap --direct connects via PG* env, not a search_path URL — libpq rejects it
|
|
416
|
+
- db:swap re-applies authz idempotently — pg_dump carries RLS policies, so CREATE POLICY collided and rolled the swap back
|
|
417
|
+
- db data-movement — Fable build-gate fixes (Task S3 grant + provable fp)
|
|
418
|
+
|
|
419
|
+
## [0.4.18] - 2026-07-17
|
|
420
|
+
|
|
421
|
+
### Fixed
|
|
422
|
+
|
|
423
|
+
- db:swap integrity error reports the real failing statement, not an FK guess
|
|
424
|
+
- derived apply creates the schemas its objects live in
|
|
425
|
+
|
|
426
|
+
## [0.4.17] - 2026-07-17
|
|
427
|
+
|
|
428
|
+
### Added
|
|
429
|
+
|
|
430
|
+
- field.json() — the Postgres json type, distinct from jsonb
|
|
431
|
+
|
|
432
|
+
## [0.4.16] - 2026-07-17
|
|
433
|
+
|
|
434
|
+
### Fixed
|
|
435
|
+
|
|
436
|
+
- --matviews-as-tables renders the MODEL naming idiom (bare name + schema prop)
|
|
437
|
+
|
|
438
|
+
## [0.4.15] - 2026-07-17
|
|
439
|
+
|
|
440
|
+
### Added
|
|
441
|
+
|
|
442
|
+
- db:check split-brain gate + db:pull --suggest-keys + the flip doc
|
|
443
|
+
|
|
444
|
+
## [0.4.14] - 2026-07-17
|
|
445
|
+
|
|
446
|
+
### Added
|
|
447
|
+
|
|
448
|
+
- db:pull --matviews-as-tables — render matviews as defineMaterializedTable
|
|
449
|
+
|
|
450
|
+
## [0.4.13] - 2026-07-17
|
|
451
|
+
|
|
452
|
+
### Added
|
|
453
|
+
|
|
454
|
+
- db:export local venue (--database-url) — the build-locally on-ramp
|
|
455
|
+
|
|
456
|
+
## [0.4.12] - 2026-07-16
|
|
457
|
+
|
|
458
|
+
### Added
|
|
459
|
+
|
|
460
|
+
- canonical-sync artifact verbs (gap B)
|
|
461
|
+
|
|
462
|
+
## [0.4.11] - 2026-07-16
|
|
463
|
+
|
|
464
|
+
### Fixed
|
|
465
|
+
|
|
466
|
+
- introspect deparsed expressions under a canonical search_path
|
|
467
|
+
|
|
468
|
+
## [0.4.10] - 2026-07-16
|
|
469
|
+
|
|
470
|
+
### Fixed
|
|
471
|
+
|
|
472
|
+
- db:pull renders array-column defaults as JS arrays
|
|
473
|
+
|
|
474
|
+
## [0.4.9] - 2026-07-16
|
|
475
|
+
|
|
476
|
+
### Fixed
|
|
477
|
+
|
|
478
|
+
- multi-schema derived apply sets search_path so bare body refs resolve
|
|
479
|
+
|
|
480
|
+
## [0.4.8] - 2026-07-16
|
|
481
|
+
|
|
482
|
+
### Added
|
|
483
|
+
|
|
484
|
+
- zero-config backup-layer guidance — probe/doctor/apply preflight
|
|
485
|
+
|
|
486
|
+
## [0.4.7] - 2026-07-16
|
|
487
|
+
|
|
488
|
+
### Added
|
|
489
|
+
|
|
490
|
+
- db:provision announces what it touched and writes linkable keys — 0.4.5
|
|
491
|
+
- the deploy probe reads a table THROUGH the api function — cli 0.4.6
|
|
492
|
+
|
|
493
|
+
### Fixed
|
|
494
|
+
|
|
495
|
+
- retire db:migrate from every advertisement; deploy env pinned + visible
|
|
496
|
+
- db:snapshot --stage can finally see databaseInstanceId
|
|
497
|
+
|
|
498
|
+
## [0.4.4] - 2026-07-14
|
|
499
|
+
|
|
500
|
+
### Fixed
|
|
501
|
+
|
|
502
|
+
- owner write abilities no longer narrow a public read
|
|
503
|
+
- derived-compile tolerates descriptors without a triggers key
|
|
504
|
+
|
|
505
|
+
## [0.4.3] - 2026-07-14
|
|
506
|
+
|
|
507
|
+
### Added
|
|
508
|
+
|
|
509
|
+
- post-deploy probe — deploy fails loudly when a function cannot boot
|
|
510
|
+
|
|
511
|
+
### Fixed
|
|
512
|
+
|
|
513
|
+
- the observability verbs can see a dead ops plane
|
|
514
|
+
|
|
515
|
+
## [0.4.2] - 2026-07-10
|
|
516
|
+
|
|
517
|
+
### Added
|
|
518
|
+
|
|
519
|
+
- the loud undefined gate — a missing import is named at define time, never silently dropped
|
|
520
|
+
- summary-first plans — the shape of the edge prints before the SQL wall
|
|
521
|
+
- db:pull --derived-out <file> — the brownfield splice, first-class
|
|
522
|
+
|
|
523
|
+
### Fixed
|
|
524
|
+
|
|
525
|
+
- bimodal error advice — a model-compose error is never dressed as an IAM problem
|
|
526
|
+
|
|
527
|
+
## [0.4.0] - 2026-07-10
|
|
528
|
+
|
|
529
|
+
### Added
|
|
530
|
+
|
|
531
|
+
- credential-free staged db:apply
|
|
532
|
+
- ingest pipeline framework (Phase 3, Bricks 1-3)
|
|
533
|
+
- surface pipeline last-run in db:sync/db:plan (Phase 3, Brick 4)
|
|
534
|
+
- credential-free pipeline:run --stage (Phase 3, Brick 5)
|
|
535
|
+
- db:pull scaffolds the read model — the explicit on-ramp (Brick A)
|
|
536
|
+
- field.defaultSql() — the raw-default escape hatch (Brick C)
|
|
537
|
+
- the declarative derived layer — descriptor API + rejection rules (Brick B1a)
|
|
538
|
+
- descriptors compile to SourceObject — the reconciler front-end swap (Brick B1b)
|
|
539
|
+
- reconciler edges for the descriptor kinds — triggers + gate-tight escape hatch (Brick B1c)
|
|
540
|
+
- standalone sequences are state — declared, migrated, diffed, fingerprinted (Brick B1d)
|
|
541
|
+
- descriptors go live in the verbs — the B3 wiring (read-model-everywhere)
|
|
542
|
+
- the B2 gates — the read model is enforced, not just expressible (read-model-everywhere)
|
|
543
|
+
- live drift + doctor — what the content hashes can't see (Brick B4, read-model-everywhere)
|
|
544
|
+
- index identity sees everything CREATE INDEX can say (Brick E, read-model-everywhere)
|
|
545
|
+
- db:pull renders the derived layer — the consumer's migration tool (Brick B5, read-model-everywhere)
|
|
546
|
+
- typed reads — declared fields emit the derived layer into the generated schema (Brick B6a, read-model-everywhere)
|
|
547
|
+
- retire db/sql — descriptors are the single home (Brick B7, read-model-everywhere)
|
|
548
|
+
- db:generate --dry-run + the journal remembers schema-out (Brick D, read-model-everywhere)
|
|
549
|
+
- db:reconcile --rebaseline — the defHash-verified migration for source re-renders (red-team blocker, read-model-everywhere)
|
|
550
|
+
|
|
551
|
+
### Fixed
|
|
552
|
+
|
|
553
|
+
- adoption-path fixes from the red team (read-model-everywhere)
|
|
554
|
+
- reconcile apply hardening — session guard, real CONCURRENTLY fallback (red team, read-model-everywhere)
|
|
555
|
+
- reachability gate mirrors the grant compiler — private models reach their granted roles (red team, read-model-everywhere)
|
|
556
|
+
- the full workspace suite is green — five latent rots, all pre-existing, root-caused
|
|
557
|
+
|
|
558
|
+
## [0.3.29] - 2026-07-05
|
|
559
|
+
|
|
560
|
+
### Added
|
|
561
|
+
|
|
562
|
+
- db:check gates force-RLS with no read authz
|
|
563
|
+
- db:reconcile --rebuild for first-contact objects (gap 1b)
|
|
564
|
+
|
|
565
|
+
### Fixed
|
|
566
|
+
|
|
567
|
+
- deploy busts the stage cache; add --refresh
|
|
568
|
+
- optional-peer AWS clients fail clean
|
|
569
|
+
- db:backup pre-flights the pg_dump layer probe
|
|
570
|
+
- surface the real cause of an ops-handler crash
|
|
571
|
+
- reconcile + direct-connect operator gaps
|
|
572
|
+
- reconcile --apply is atomic across DDL + provenance (gap 7)
|
|
573
|
+
- db:reconcile/apply resolve ADMIN_DATABASE_URL, not the authenticator (gap 3)
|
|
574
|
+
|
|
575
|
+
## [0.3.28] - 2026-07-04
|
|
576
|
+
|
|
577
|
+
### Fixed
|
|
578
|
+
|
|
579
|
+
- fingerprint hashes expressions as the diff does, so db:check can pass
|
|
580
|
+
|
|
581
|
+
## [0.3.27] - 2026-07-04
|
|
582
|
+
|
|
583
|
+
### Fixed
|
|
584
|
+
|
|
585
|
+
- the gates and generators teach the migration-authority workflow
|
|
586
|
+
|
|
587
|
+
## [0.3.26] - 2026-07-04
|
|
588
|
+
|
|
589
|
+
### Added
|
|
590
|
+
|
|
591
|
+
- db:fork — branch stages fork the database, not just the code (branch-stages brick 1+2)
|
|
592
|
+
|
|
593
|
+
## [0.3.25] - 2026-07-04
|
|
594
|
+
|
|
595
|
+
### Added
|
|
596
|
+
|
|
597
|
+
- per-branch dev databases from a models-built template (brick 5 remainder)
|
|
598
|
+
|
|
599
|
+
## [0.3.24] - 2026-07-04
|
|
600
|
+
|
|
601
|
+
### Added
|
|
602
|
+
|
|
603
|
+
- the backfill lane — one-shot data jobs, own record (brick 8)
|
|
604
|
+
|
|
605
|
+
## [0.3.23] - 2026-07-04
|
|
606
|
+
|
|
607
|
+
### Added
|
|
608
|
+
|
|
609
|
+
- losing data should be hard — classification, deprecated(), role-gated destructive (brick 9)
|
|
610
|
+
|
|
611
|
+
## [0.3.22] - 2026-07-04
|
|
612
|
+
|
|
613
|
+
### Added
|
|
614
|
+
|
|
615
|
+
- git-descent rule + db:check — the protected stage is a git ref (brick 7)
|
|
616
|
+
|
|
617
|
+
## [0.3.21] - 2026-07-04
|
|
618
|
+
|
|
619
|
+
### Added
|
|
620
|
+
|
|
621
|
+
- table-level renamedFrom — rename intent through the diff (brick 6)
|
|
622
|
+
|
|
623
|
+
## [0.3.20] - 2026-07-04
|
|
624
|
+
|
|
625
|
+
### Added
|
|
626
|
+
|
|
627
|
+
- db:plan + db:apply — verified edges, fingerprint-pinned at both ends (brick 4)
|
|
628
|
+
|
|
629
|
+
## [0.3.19] - 2026-07-04
|
|
630
|
+
|
|
631
|
+
### Added
|
|
632
|
+
|
|
633
|
+
- db:diff — the state edge between two declared states, no database (brick 3)
|
|
634
|
+
|
|
635
|
+
## [0.3.18] - 2026-07-03
|
|
636
|
+
|
|
637
|
+
### Added
|
|
638
|
+
|
|
639
|
+
- db:sync — make the database match your checkout (brick 5 core)
|
|
640
|
+
|
|
641
|
+
## [0.3.17] - 2026-07-03
|
|
642
|
+
|
|
643
|
+
### Added
|
|
644
|
+
|
|
645
|
+
- derived-source — parse db/sql into derived objects (reconciler brick 1a)
|
|
646
|
+
- derived introspection + reconcile planner (reconciler bricks 1b/1c)
|
|
647
|
+
- db:reconcile — deploy the derived layer (reconciler bricks 1d/1e)
|
|
648
|
+
- db/models is the models home — flagless resolution, legacy fallback
|
|
649
|
+
- db:generate --apply — table changes with no tape (0.3.16)
|
|
650
|
+
- schema fingerprint + db:fingerprint — the base state gets a content address (brick 2)
|
|
651
|
+
|
|
652
|
+
### Fixed
|
|
653
|
+
|
|
654
|
+
- WITH NO DATA matview creates get a REFRESH — rebuilds arrive populated (0.3.15)
|
|
655
|
+
|
|
656
|
+
## [0.3.13] - 2026-07-02
|
|
657
|
+
|
|
658
|
+
### Added
|
|
659
|
+
|
|
660
|
+
- runbook detects instead of asserting — migration-mode tri-state, real scripts, model summary at scale
|
|
661
|
+
|
|
662
|
+
## [0.3.12] - 2026-07-02
|
|
663
|
+
|
|
664
|
+
### Added
|
|
665
|
+
|
|
666
|
+
- db:pull and db:generate accept --database-url; cli 0.3.9
|
|
667
|
+
- db:pull per-model files; serial and float round-trip; cli 0.3.10
|
|
668
|
+
- runbook R0 — detectProjectReality, pure local project detection
|
|
669
|
+
- runbook R1 — es:gen/es:slot markers, section templates, idempotent merge
|
|
670
|
+
- runbook R2 — render the app's Models as the runbook data-model chapter
|
|
671
|
+
- runbook R3 — everystack runbook command, --check/--diff/--force, /runbook API, capstone advisory
|
|
672
|
+
|
|
673
|
+
### Fixed
|
|
674
|
+
|
|
675
|
+
- guard npm publish on workspace-dep packages; cli 0.3.8
|
|
676
|
+
- db:pull field keys are lossless; array columns map; cli 0.3.11
|
|
677
|
+
|
|
678
|
+
## [0.3.7] - 2026-07-02
|
|
679
|
+
|
|
680
|
+
### Fixed
|
|
681
|
+
|
|
682
|
+
- honor EVERYSTACK_SURFACE as an explicit surface override
|
|
683
|
+
- pin React to a single 19.2.0 across the monorepo
|
|
684
|
+
- bound the S3 client and list() so a hung call can't drain the pool
|
|
685
|
+
|
|
686
|
+
## [0.3.6] - 2026-06-30
|
|
687
|
+
|
|
688
|
+
### Changed
|
|
689
|
+
|
|
690
|
+
- keep @everystack/cli/audit typescript-free
|
|
691
|
+
|
|
692
|
+
## [0.3.5] - 2026-06-30
|
|
693
|
+
|
|
694
|
+
### Added
|
|
695
|
+
|
|
696
|
+
- ui-bloat scanner — detect design bloat in a shipped bundle (Brick 5a-A)
|
|
697
|
+
- component-model — the faithful TSX foundation (Brick 5a-B)
|
|
698
|
+
- source-side UI-bloat checks — located, fixable, self-verifying (Brick 5a-C)
|
|
699
|
+
- Check 3 — you rebuilt a ui primitive (Brick 5a-D)
|
|
700
|
+
- Checks 4 & 5 — size/complexity + structural duplication (Brick 5a-E)
|
|
701
|
+
- wire ui:audit + the bundle ui-bloat dimension (Brick 5a-F)
|
|
702
|
+
- screen-aware audit + duplicate-component check (Brick 5b, first piece)
|
|
703
|
+
|
|
704
|
+
## [0.3.4] - 2026-06-30
|
|
705
|
+
|
|
706
|
+
### Added
|
|
707
|
+
|
|
708
|
+
- B2 owner-aware red-team probe — catches the IDOR class
|
|
709
|
+
- B2.2 db:authz:owner — Model-driven owner-isolation red-team
|
|
710
|
+
- @everystack/cli/model public API + dogfood the example through it
|
|
711
|
+
- everystack deploy — wrap sst deploy so the consumer never types sst
|
|
712
|
+
- db:snapshot/restore S0 — pure core (key scheme + cross-stage guards)
|
|
713
|
+
- db:snapshot — physical RDS snapshot (control-plane wrapper)
|
|
714
|
+
- db:backup B1 — pg_dump layer + version-gate probe
|
|
715
|
+
- db:backup B3 — backup/list/restore/download commands
|
|
716
|
+
- via: transitive-owner RLS — own a child through its parent
|
|
717
|
+
- compileDrizzleSource — emit the typed drizzle schema from models
|
|
718
|
+
- precise authz grants — derive the tier from abilities, not a uniform constant
|
|
719
|
+
- writtenBy + the FORCE axiom — RLS posture is a model property
|
|
720
|
+
- per-model schema — a model declares the Postgres schema it lives in
|
|
721
|
+
- greenfield generator emits non-public schemas
|
|
722
|
+
- compose storage's dist.blobs into the example — package-owned authz proven
|
|
723
|
+
- own the notification_* tables as Models + array vocabulary
|
|
724
|
+
- column-scoped grants + cross-schema FK / index emission
|
|
725
|
+
- compileModuleMigration + schema USAGE grants + db:generate --init
|
|
726
|
+
- config-free perf/security/SEO audit commands for any deployed app
|
|
727
|
+
- fold security:audit + lighthouse into the audit capstone
|
|
728
|
+
- bundle:audit gains a weight/size dimension (the 180MB gate)
|
|
729
|
+
- bundle:audit weighs the remote bundle and names what is bloating it
|
|
730
|
+
|
|
731
|
+
### Fixed
|
|
732
|
+
|
|
733
|
+
- db:snapshot error hints only mention IAM on access-denied
|
|
734
|
+
- escape the NUL delimiters in schema-source (text-safe source)
|
|
735
|
+
- db:authz:owner treats a public-read table's cross-owner read as expected
|
|
736
|
+
|
|
737
|
+
### Changed
|
|
738
|
+
|
|
739
|
+
- db backup/snapshot spec + pure cores (rename snapshot→backup)
|
|
740
|
+
- compose v3 Modules → one clean generated migration
|
|
741
|
+
|
|
742
|
+
### Documentation
|
|
743
|
+
|
|
744
|
+
- v3 (0.3.0) documentation review — accurate READMEs + guides
|
|
745
|
+
- complete the command reference — db:reset, branches, security:audit
|
|
746
|
+
|
|
747
|
+
## [0.3.0] - 2026-06-27
|
|
748
|
+
|
|
749
|
+
### Added
|
|
750
|
+
|
|
751
|
+
- security:audit — the two-surface RLS gate
|
|
752
|
+
- db:doctor — verify the DB is least-privilege + RLS-subject
|
|
753
|
+
- db:provision — the role provisioner + db:doctor remediation
|
|
754
|
+
- break-glass admin db:psql gated on AWS identity
|
|
755
|
+
- authz-contract format + the two new introspection queries
|
|
756
|
+
- db:authz:pull + db:authz:diff, end-to-end on the example app
|
|
757
|
+
- exclude drizzle journal schema + guard the pull/diff workflow
|
|
758
|
+
- the authz red-team — prove enforcement + capture column grants
|
|
759
|
+
- db:authz:test + the db:authz:probe ops action — red-team a deployed stage
|
|
760
|
+
- human-readable authorization review (the CTO can confirm intent)
|
|
761
|
+
- humanize parent-ownership + boolean predicates in the review
|
|
762
|
+
- capture SECDEF owner-bypasses-RLS — the max-blast-radius check
|
|
763
|
+
- SECDEF grant-completeness check in db:authz:test
|
|
764
|
+
- compile Model abilities to the authz contract (v3 Phase 1)
|
|
765
|
+
- compile owner-read + text-owner tables; four app tables round-trip (v3 Phase 1)
|
|
766
|
+
- emitReconcileSql — contract diff to authz SQL (v3 Phase 2)
|
|
767
|
+
- field bridge DDL compiler — fields to CREATE TABLE (v3 data slice)
|
|
768
|
+
- field bridge — CHECK constraints from .validate() Zod refinements
|
|
769
|
+
- compileMigration — Models to one ordered migration (the generator skeleton)
|
|
770
|
+
- whole-DB introspection — the data layer (columns, constraints, FKs)
|
|
771
|
+
- compileTableSchema — data layer's second producer + round-trip
|
|
772
|
+
- diffSchema — the structural schema diff (brownfield generate's missing half)
|
|
773
|
+
- renamedFrom — the opt-in column rename, so the diff preserves data
|
|
774
|
+
- ALTER COLUMN TYPE — real type-change DDL, classified safe/lossy/risky
|
|
775
|
+
- db:generate — the brownfield migration generator, wired end to end
|
|
776
|
+
- db:pull — render field() Models from a live database (the brownfield on-ramp)
|
|
777
|
+
- P0 round-trip identity test against real Postgres
|
|
778
|
+
- P1 — non-destructive by default in db:generate
|
|
779
|
+
- P3 field vocabulary — numeric, varchar, date
|
|
780
|
+
- P3 enum — CREATE TYPE, pg_enum introspection, round-trip
|
|
781
|
+
- P4a FK referential actions + tougher constraint parser
|
|
782
|
+
- P2.1 table-level constraints — composite unique, multi-col check
|
|
783
|
+
- P2.2 indexes — index() builder, round-trip
|
|
784
|
+
- P2.3 db:pull reverses a recognizable CHECK to .validate()
|
|
785
|
+
- P5 apply-time warnings for SET NOT NULL and ADD COLUMN NOT NULL
|
|
786
|
+
- P4b compile/emit/pull for composite foreign keys
|
|
787
|
+
- P6 unify authz into db:generate (one ordered migration)
|
|
788
|
+
|
|
789
|
+
### Fixed
|
|
790
|
+
|
|
791
|
+
- dedup db:logs:tail so it stops reprinting the boundary batch
|
|
792
|
+
- re-pin search_path on the four core SECDEF auth functions
|
|
793
|
+
- render the authz review legend as a list, not one long line
|
|
794
|
+
- P6 quote reserved table names in emitted authz SQL
|
|
795
|
+
|
|
796
|
+
## [0.2.37] - 2026-06-17
|
|
797
|
+
|
|
798
|
+
### Added
|
|
799
|
+
|
|
800
|
+
- rename @everystack/updates → @everystack/cli, add db:migrate/db:seed, remove AdminSecret
|
|
801
|
+
- add interactive console command with Lambda-relay REPL
|
|
802
|
+
- add CloudFront KVS cache versioning with CLI purge and auto-invalidation
|
|
803
|
+
- add StorageAdapter head(), ETag-based SSR reload, SigV4a for KVS
|
|
804
|
+
- configurable per-origin cache headers + image EXIF fix
|
|
805
|
+
- stage-aware resource discovery via AWS APIs
|
|
806
|
+
- EAS-compatible OTA updates — branches, groups, channels, SSR
|
|
807
|
+
- make S3 source of truth, database becomes optional mirror
|
|
808
|
+
- add web publish versioning matching mobile pattern
|
|
809
|
+
- export handleRegisterWeb and UpdatesHandlerOptions from ./handler
|
|
810
|
+
- use X.509 code signing certificates instead of raw RSA keys
|
|
811
|
+
- add db:reset action for dev schema rebuild
|
|
812
|
+
- mobile publish via direct S3 upload + IAM Lambda invoke
|
|
813
|
+
- move hydration fixes into framework, remove auth cache splitting
|
|
814
|
+
- add `everystack diag` command for deployed page freshness diagnostics
|
|
815
|
+
- add SSR hydration debugging tools
|
|
816
|
+
- add browser-only API detection to SSR analyzer
|
|
817
|
+
- add psql wrapper and logs commands for debugging
|
|
818
|
+
- unified querying with S3 fallback for CLI log commands
|
|
819
|
+
- add plugin architecture for composable Lambda handlers
|
|
820
|
+
- extract generic actions into package plugins
|
|
821
|
+
- add --origin flag to logs:tail for image and worker functions
|
|
822
|
+
- add [updates] debug logging to OTA manifest, asset, and route handlers
|
|
823
|
+
- dual-export OTA updates, SSR validation, eas.json env resolution
|
|
824
|
+
- secrets management and auto-injection during update
|
|
825
|
+
- re-add SST secret injection, delegate filtering to app layer
|
|
826
|
+
- content-addressed asset deduplication, fix Metro resolution
|
|
827
|
+
- add secrets import/export for bulk .env operations
|
|
828
|
+
- add deny-by-default env filtering for client bundles
|
|
829
|
+
- add createSecrets helper and transparent naming support
|
|
830
|
+
- sync media assets to S3 during web publish
|
|
831
|
+
- add multiline input and persistent history to console
|
|
832
|
+
- add dot commands and schema introspection
|
|
833
|
+
- add tab completion to console REPL
|
|
834
|
+
- add auth context with login, asUser, and pgSettings
|
|
835
|
+
- add sandbox mode for safe exploration
|
|
836
|
+
- add .login with hidden password, current_user in scope, history redaction
|
|
837
|
+
- add content-hash fingerprinting for media assets
|
|
838
|
+
- log media asset paths and fingerprints during update
|
|
839
|
+
- fail-closed env loader
|
|
840
|
+
- split operator credential from the API connection
|
|
841
|
+
- everystack status — platform health from the terminal
|
|
842
|
+
- migration runner that cannot lie
|
|
843
|
+
- DB-free alerting — artifact-backed evaluation over S3, no Postgres
|
|
844
|
+
- IAM auth for web deploys, replace Sharp layer with SST bundling
|
|
845
|
+
|
|
846
|
+
### Fixed
|
|
847
|
+
|
|
848
|
+
- normalize cache:purge --path to strip query params and ensure leading slash
|
|
849
|
+
- remove unused @everystack/jobs devDependency
|
|
850
|
+
- include updateId in register-web payload
|
|
851
|
+
- psql Lambda-only queries, composite key notify, logging defaults
|
|
852
|
+
- add AWS KVS + SigV4a as direct dependencies
|
|
853
|
+
- improve error messages and document setup requirements
|
|
854
|
+
- resolve 5 deployment issues from first external testing
|
|
855
|
+
- replace require() with ESM-compatible dynamic imports
|
|
856
|
+
- pin all dependency versions to lockfile-resolved values
|
|
857
|
+
- pgSettings on RPC, publicRpc client auth bypass, logs:tail log group resolution
|
|
858
|
+
- route GET / to manifest handler for expo-updates client
|
|
859
|
+
- propagate --stage to ENVIRONMENT and HOST_URL before config evaluation
|
|
860
|
+
- redirect OTA assets via presigned S3 URL, set publish context
|
|
861
|
+
- surface real error when app.config.js fails to load
|
|
862
|
+
- decouple --stage from ENVIRONMENT, add --environment flag
|
|
863
|
+
- revert @aws-sdk/signature-v4a to 3.1031.0, fix KVS SigV4a signing
|
|
864
|
+
- bypass SigV4a singleton for KVS signing
|
|
865
|
+
- drop GetFunctionConfiguration from log group resolution
|
|
866
|
+
- detect export completion to prevent static pre-rendering hang
|
|
867
|
+
- rewrite secrets for SST Ion, filter EXPO_PUBLIC_* only
|
|
868
|
+
- remove secret auto-injection, skip SSR check for web-only updates
|
|
869
|
+
- clear Metro transform cache on export
|
|
870
|
+
- worker function discovery and log group resolution for SQS subscribers
|
|
871
|
+
- replace infrastructure headers with single X-Server-Debug timestamp
|
|
872
|
+
- version bumps and publish fixes for cli, server, jobs
|
|
873
|
+
- invalidate stale config cache on schema change
|
|
874
|
+
- fall back to outputs.json for custom-named media buckets
|
|
875
|
+
- remove media asset pruning from update command
|
|
876
|
+
- media-sync test uses same minimatch interop as source
|
|
877
|
+
- discover the Router distribution by domain and SST tags
|
|
878
|
+
- use minimatch's named export — the package ships types now
|
|
879
|
+
- logs read S3 directly; invoke self-heals stale Lambda hash
|
|
880
|
+
- harden S3 reader and lock the CLI-reader format contract
|
|
881
|
+
|
|
882
|
+
### Changed
|
|
883
|
+
|
|
884
|
+
- remove onMutation callback — events replaces it
|
|
885
|
+
- migrate all package schemas to everyschema canonical tables
|
|
886
|
+
- lazy-load AWS SDK imports for faster CLI startup
|
|
887
|
+
- drop type module from all packages, switch Jest to CJS mode
|
|
888
|
+
- consolidate Jest configs into shared base
|
|
889
|
+
|
|
890
|
+
### Documentation
|
|
891
|
+
|
|
892
|
+
- prepare documentation for open-source release
|
|
893
|
+
- SECURITY DEFINER guidance; feat(cli): auto-export on stale dist/
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@everystack/cli",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.68",
|
|
4
4
|
"description": "CLI and OTA updates for Expo apps on everystack",
|
|
5
5
|
"license": "AGPL-3.0-only",
|
|
6
6
|
"author": "Scalable Technology, Inc. <licensing@scalable.technology>",
|
|
@@ -19,7 +19,8 @@
|
|
|
19
19
|
"files": [
|
|
20
20
|
"bin",
|
|
21
21
|
"src",
|
|
22
|
-
"README.md"
|
|
22
|
+
"README.md",
|
|
23
|
+
"CHANGELOG.md"
|
|
23
24
|
],
|
|
24
25
|
"exports": {
|
|
25
26
|
".": {
|
|
@@ -177,7 +178,7 @@
|
|
|
177
178
|
"jest": "29.7.0",
|
|
178
179
|
"react": "19.2.0",
|
|
179
180
|
"ts-jest": "29.4.9",
|
|
180
|
-
"@everystack/server": "0.4.
|
|
181
|
+
"@everystack/server": "0.4.27"
|
|
181
182
|
},
|
|
182
183
|
"scripts": {
|
|
183
184
|
"test": "jest",
|
|
@@ -1,8 +1,11 @@
|
|
|
1
|
+
import { generateKeyPair as cryptoGenerateKeyPair } from 'node:crypto';
|
|
2
|
+
import { promisify } from 'node:util';
|
|
1
3
|
import fs from 'fs/promises';
|
|
2
4
|
import path from 'path';
|
|
3
5
|
import forge from 'node-forge';
|
|
4
6
|
|
|
5
7
|
const { pki, md, random, util } = forge;
|
|
8
|
+
const generateKeyPairAsync = promisify(cryptoGenerateKeyPair);
|
|
6
9
|
|
|
7
10
|
export async function certsCommand(action: 'generate' | 'configure', flags: Record<string, string>): Promise<void> {
|
|
8
11
|
if (action === 'generate') {
|
|
@@ -26,8 +29,16 @@ async function generateCerts(flags: Record<string, string>): Promise<void> {
|
|
|
26
29
|
const validityYears = parseInt(flags.years || '10', 10);
|
|
27
30
|
await fs.mkdir(outputDir, { recursive: true });
|
|
28
31
|
|
|
29
|
-
// Generate RSA key pair
|
|
30
|
-
const
|
|
32
|
+
// Generate RSA key pair with Node.js crypto (async, native — doesn't block the event loop)
|
|
33
|
+
const { publicKey: pubPem, privateKey: privPem } = await generateKeyPairAsync('rsa', {
|
|
34
|
+
modulusLength: 2048,
|
|
35
|
+
publicKeyEncoding: { type: 'spki', format: 'pem' },
|
|
36
|
+
privateKeyEncoding: { type: 'pkcs8', format: 'pem' },
|
|
37
|
+
});
|
|
38
|
+
const keyPair = {
|
|
39
|
+
publicKey: pki.publicKeyFromPem(pubPem),
|
|
40
|
+
privateKey: pki.privateKeyFromPem(privPem),
|
|
41
|
+
};
|
|
31
42
|
|
|
32
43
|
// Create self-signed X.509 code signing certificate
|
|
33
44
|
const cert = pki.createCertificate();
|
package/src/cli/commands/db.ts
CHANGED
|
@@ -215,27 +215,40 @@ export interface ProvisionSecretPlan {
|
|
|
215
215
|
* provisioned by an older CLI is still detected. Pure — the contract is pinned by tests.
|
|
216
216
|
*/
|
|
217
217
|
export function buildProvisionSecretPlan(args: {
|
|
218
|
-
result: {
|
|
219
|
-
|
|
218
|
+
result: {
|
|
219
|
+
loginRole: string;
|
|
220
|
+
adminRole?: string;
|
|
221
|
+
adminVerified?: boolean | null;
|
|
222
|
+
/** false = the role already existed as a live login and was NOT re-passworded (additive-only),
|
|
223
|
+
* so its secret must be left untouched. Absent/true = the password was set, write the secret. */
|
|
224
|
+
passwordSet?: boolean;
|
|
225
|
+
adminPasswordSet?: boolean;
|
|
226
|
+
};
|
|
227
|
+
authUrl?: string;
|
|
220
228
|
adminUrl?: string;
|
|
221
229
|
existing: Record<string, string>;
|
|
222
230
|
}): ProvisionSecretPlan {
|
|
223
231
|
const { result, authUrl, adminUrl, existing } = args;
|
|
224
|
-
const updates: Record<string, string> = {
|
|
225
|
-
DATABASE_URL: authUrl,
|
|
226
|
-
};
|
|
232
|
+
const updates: Record<string, string> = {};
|
|
227
233
|
const notes: string[] = [];
|
|
228
234
|
const warnings: string[] = [];
|
|
229
235
|
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
+
if (result.passwordSet !== false && authUrl) {
|
|
237
|
+
updates.DATABASE_URL = authUrl;
|
|
238
|
+
const prevAuthRole = roleOfUrl(existing.DATABASE_URL ?? existing.DatabaseUrl);
|
|
239
|
+
notes.push(`DATABASE_URL: ${prevAuthRole ?? 'unset'} → ${result.loginRole}`);
|
|
240
|
+
if (prevAuthRole && prevAuthRole !== result.loginRole) {
|
|
241
|
+
warnings.push(
|
|
242
|
+
`DATABASE_URL was already set (role '${prevAuthRole}') — any function linked to it connects as '${result.loginRole}' after its next cold start. Ensure grants are in place: everystack db:reconcile && everystack db:doctor.`,
|
|
243
|
+
);
|
|
244
|
+
}
|
|
245
|
+
} else {
|
|
246
|
+
// Additive-only: the authenticator already existed as a live login, so its password was
|
|
247
|
+
// preserved and DATABASE_URL is left as-is. Never rewrite a working credential on a re-provision.
|
|
248
|
+
notes.push(`DATABASE_URL: '${result.loginRole}' already provisioned — password + secret preserved (use --rotate to change it)`);
|
|
236
249
|
}
|
|
237
250
|
|
|
238
|
-
if (result.adminRole && adminUrl) {
|
|
251
|
+
if (result.adminRole && adminUrl && result.adminPasswordSet !== false) {
|
|
239
252
|
updates.ADMIN_DATABASE_URL = adminUrl;
|
|
240
253
|
const prevAdminRole = roleOfUrl(existing.AdminDatabaseUrl ?? existing.ADMIN_DATABASE_URL);
|
|
241
254
|
const verified = result.adminVerified === true ? 'login verified' : 'login NOT verified';
|
|
@@ -245,6 +258,8 @@ export function buildProvisionSecretPlan(args: {
|
|
|
245
258
|
`The '${result.adminRole}' login could not be verified from the ops function — confirm operator connectivity before relying on it: everystack db:doctor.`,
|
|
246
259
|
);
|
|
247
260
|
}
|
|
261
|
+
} else if (result.adminRole) {
|
|
262
|
+
notes.push(`ADMIN_DATABASE_URL: '${result.adminRole}' already provisioned — password + secret preserved (use --rotate to change it)`);
|
|
248
263
|
} else {
|
|
249
264
|
warnings.push(
|
|
250
265
|
'No operator login was created — this @everystack/server predates the both-sides split (<0.3.7). Operator tasks stay on the linked Postgres master; upgrade the server pin and re-run db:provision for the full credential split.',
|
|
@@ -254,6 +269,37 @@ export function buildProvisionSecretPlan(args: {
|
|
|
254
269
|
return { updates, notes, warnings };
|
|
255
270
|
}
|
|
256
271
|
|
|
272
|
+
/**
|
|
273
|
+
* Both login passwords were preserved (an additive-only re-provision). Decide whether that is a
|
|
274
|
+
* true no-op — the secrets backing these roles already exist and name them — or a HALF-DONE stage
|
|
275
|
+
* the operator must recover with --rotate. A role can exist WITH LOGIN yet have no working secret:
|
|
276
|
+
* a prior provision set a fresh password and then failed to write it (the admin-probe abort, or a
|
|
277
|
+
* host it could not resolve). Reporting "already provisioned" there would send the operator away
|
|
278
|
+
* from the only fix, so refuse instead. Pure — pinned by tests.
|
|
279
|
+
*/
|
|
280
|
+
export function provisionPreservedIsRecoverable(args: {
|
|
281
|
+
loginRole: string;
|
|
282
|
+
adminRole?: string;
|
|
283
|
+
existing: Record<string, string>;
|
|
284
|
+
}): { ok: true } | { ok: false; reason: string } {
|
|
285
|
+
const { loginRole, adminRole, existing } = args;
|
|
286
|
+
// A rotated stage's secret names the ACTIVE rotation-pair role (e.g. authenticator_a), not the
|
|
287
|
+
// bare role — so accept the base name OR either pair variant as a valid backing credential.
|
|
288
|
+
// Without this, a healthy rotated stage false-refuses on a plain (search_path) re-provision.
|
|
289
|
+
const backsRole = (secretRole: string | undefined | null, base: string): boolean =>
|
|
290
|
+
secretRole === base || secretRole === `${base}_a` || secretRole === `${base}_b`;
|
|
291
|
+
const authOk = backsRole(roleOfUrl(existing.DATABASE_URL ?? existing.DatabaseUrl), loginRole);
|
|
292
|
+
const adminOk = !adminRole || backsRole(roleOfUrl(existing.ADMIN_DATABASE_URL ?? existing.AdminDatabaseUrl), adminRole);
|
|
293
|
+
if (authOk && adminOk) return { ok: true };
|
|
294
|
+
return {
|
|
295
|
+
ok: false,
|
|
296
|
+
reason:
|
|
297
|
+
'Role(s) already exist but the stage secret does not match them — a prior provision set a '
|
|
298
|
+
+ 'password that was never written to the secret store, so no working credential exists. '
|
|
299
|
+
+ 'Run `everystack db:provision --rotate` to set a fresh password and write the secret together.',
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
|
|
257
303
|
/** The provisioning core's shape (mirrors @everystack/server/provision — no compile-time dep,
|
|
258
304
|
* same seam as pipeline-loader). */
|
|
259
305
|
export interface ServerProvision {
|
|
@@ -263,6 +309,7 @@ export interface ServerProvision {
|
|
|
263
309
|
execute: (sql: string) => Promise<unknown>;
|
|
264
310
|
connection?: { host: string; port?: number | string; database: string } | null;
|
|
265
311
|
connectProbe?: (url: string) => Promise<void>;
|
|
312
|
+
roleHasLogin?: (role: string) => Promise<boolean>;
|
|
266
313
|
},
|
|
267
314
|
): Promise<any>;
|
|
268
315
|
}
|
|
@@ -826,6 +873,12 @@ export async function dbProvisionCommand(flags: Record<string, string>): Promise
|
|
|
826
873
|
return await runProvision({ authPassword, adminPassword, searchPath, rotationPairs: !!flags['rotation-pairs'] }, {
|
|
827
874
|
execute: async (statement) => runner(statement),
|
|
828
875
|
connection: parseUrlConnection(url),
|
|
876
|
+
// Additive-only: an already-live login role keeps its password (never reset on re-provision).
|
|
877
|
+
roleHasLogin: async (role) => {
|
|
878
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(role)) return false;
|
|
879
|
+
const rows = await runner(`SELECT 1 FROM pg_roles WHERE rolname = '${role}' AND rolcanlogin`);
|
|
880
|
+
return Array.isArray(rows) && rows.length > 0;
|
|
881
|
+
},
|
|
829
882
|
// Same probe as the ops-Lambda venue: a real login as the new role, DDL-capable.
|
|
830
883
|
connectProbe: async (probeUrl) => {
|
|
831
884
|
const { default: postgres } = await import('postgres');
|
|
@@ -891,7 +944,32 @@ export async function dbProvisionCommand(flags: Record<string, string>): Promise
|
|
|
891
944
|
}
|
|
892
945
|
success(`Role chain ready: ${result.loginRole} (LOGIN, NOINHERIT, no BYPASSRLS) → can SET ROLE to ${result.appRoles.join(', ')}`);
|
|
893
946
|
if (result.adminRole) {
|
|
894
|
-
|
|
947
|
+
const adminMsg = result.adminPasswordSet === false
|
|
948
|
+
? 'existing login preserved (not re-verified — run db:doctor)'
|
|
949
|
+
: (result.adminVerified === true ? 'connectivity verified' : 'connectivity NOT verified');
|
|
950
|
+
success(`Operator login ready: ${result.adminRole} (LOGIN, INHERIT owner) — ${adminMsg}`);
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
// Additive-only: if the login roles already existed as live logins, provision changed no
|
|
954
|
+
// credential — so there is nothing to write and no host to resolve. Never touch the secret
|
|
955
|
+
// store on a no-op re-provision (the footgun that reset a live password then locked the stage out).
|
|
956
|
+
const wroteAuth = result.passwordSet !== false;
|
|
957
|
+
const wroteAdmin = !!result.adminRole && result.adminPasswordSet !== false;
|
|
958
|
+
if (!wroteAuth && !wroteAdmin) {
|
|
959
|
+
// Both roles preserved. Only a true no-op if the secrets backing them exist and name them;
|
|
960
|
+
// a role that exists WITH LOGIN but has no matching secret is a half-done prior provision that
|
|
961
|
+
// plain db:provision cannot recover — refuse with the --rotate remedy rather than report success.
|
|
962
|
+
const recoverable = provisionPreservedIsRecoverable({
|
|
963
|
+
loginRole: result.loginRole,
|
|
964
|
+
adminRole: result.adminRole,
|
|
965
|
+
existing: stageSecrets,
|
|
966
|
+
});
|
|
967
|
+
if (recoverable.ok) {
|
|
968
|
+
success(`Roles already provisioned — passwords and secrets left unchanged. Verify the live credential with \`everystack db:doctor --stage ${flags.stage}\`; run \`everystack db:provision --rotate\` to change credentials.`);
|
|
969
|
+
return;
|
|
970
|
+
}
|
|
971
|
+
fail(recoverable.reason);
|
|
972
|
+
process.exit(1);
|
|
895
973
|
}
|
|
896
974
|
|
|
897
975
|
// Resolve host/port/db (not secret) to build the connection strings in memory.
|
|
@@ -928,8 +1006,8 @@ export async function dbProvisionCommand(flags: Record<string, string>): Promise
|
|
|
928
1006
|
const mint = (role: string, password: string): string =>
|
|
929
1007
|
preserveConnParams(`postgresql://${role}:${password}@${conn.host}:${conn.port ?? 5432}/${conn.database}`, sourceUrl);
|
|
930
1008
|
|
|
931
|
-
const authUrl = mint(result.loginRole, authPassword);
|
|
932
|
-
const adminUrl = result.adminRole ? mint(result.adminRole, adminPassword) : undefined;
|
|
1009
|
+
const authUrl = wroteAuth ? mint(result.loginRole, authPassword) : undefined;
|
|
1010
|
+
const adminUrl = wroteAdmin && result.adminRole ? mint(result.adminRole, adminPassword) : undefined;
|
|
933
1011
|
|
|
934
1012
|
// Write the secrets directly to the SST secret store — never display them. One blob
|
|
935
1013
|
// write updates every key together, so there is no window where the API side is the
|
|
@@ -24,13 +24,33 @@
|
|
|
24
24
|
* for n ≥ 1. PostgreSQL defines an array-to-array cast element-wise (parse_coerce), so
|
|
25
25
|
* the two expressions denote the same value for every input; wrapping an element in
|
|
26
26
|
* parentheses is parse-neutral. The empty array is EXCLUDED: `ARRAY[]` without a cast has
|
|
27
|
-
* no type, so its cast is load-bearing and stays.
|
|
27
|
+
* no type, so its cast is load-bearing and stays. (The measured CROSS-VERSION case: two PG
|
|
28
|
+
* majors const-fold the same DDL differently.)
|
|
29
|
+
*
|
|
30
|
+
* LITERAL-TEXT-CAST STRIP: `'lit'::text == 'lit'`, but ONLY the bare `text` cast applied
|
|
31
|
+
* directly to a single-quoted string literal. This is a CROSS-PRODUCER class, present on
|
|
32
|
+
* every version: PostgreSQL coerces an unknown-type string literal compared to a text
|
|
33
|
+
* column and RECORDS `'lit'::text` in the stored tree, while the author's model source
|
|
34
|
+
* writes the un-cast `'lit'`. The two producers' texts never converge without this rule, so
|
|
35
|
+
* a `text_col = 'lit'` partial index / CHECK / policy re-diffs DROP+CREATE forever and the
|
|
36
|
+
* fingerprint gate cannot MATCH. Safe because `text` takes no typmod and its input function
|
|
37
|
+
* is the identity on the literal's bytes — the cast cannot change the value. The rule is
|
|
38
|
+
* `text`-ONLY for exactly that reason: `bpchar`/`character(n)` blank-pad or blank-trim,
|
|
39
|
+
* `varchar(n)`/`"char"` truncate — those casts CHANGE the value, so stripping one would
|
|
40
|
+
* collapse two opposite-truth predicates to equal text (a false match, the disaster). The
|
|
41
|
+
* array-type cast `::text[]` is likewise excluded — it carries the element type. See the
|
|
42
|
+
* `char(2)` false-match test for why the family is not widened.
|
|
28
43
|
*
|
|
29
44
|
* Anything the scanner does not positively recognize is left byte-for-byte unchanged — an
|
|
30
45
|
* unrecognized spelling is a MISS (drop + create, the pre-existing behavior), never a guess.
|
|
31
|
-
* Escalation is named, not implied: the SECOND cross-
|
|
46
|
+
* Escalation is named, not implied: the SECOND cross-VERSION variant found in the field is
|
|
32
47
|
* the trigger to adopt libpg_query and move identity onto normalized ASTs (Ty, 2026-08-09) —
|
|
33
|
-
* a growing rule table over raw text is where scanning stops being defensible.
|
|
48
|
+
* a growing rule table over raw text is where scanning stops being defensible. One correction
|
|
49
|
+
* to that trigger (2026-08-24): libpg_query closes cross-VERSION SYNTAX drift (whitespace,
|
|
50
|
+
* parens, keyword case), NOT cross-PRODUCER COERCION drift — a raw parse tree carries no type
|
|
51
|
+
* resolution, so `'lit'` and `'lit'::text` still differ structurally without a catalog, which
|
|
52
|
+
* the CI-pure `db:diff` lane does not have. That class earns a provable text rewrite here,
|
|
53
|
+
* verified end-to-end by `db:check`'s from-scratch build on a real database.
|
|
34
54
|
*
|
|
35
55
|
* Applied at the PRODUCERS (live introspection's `predicate()`, the compiler's policy
|
|
36
56
|
* assembly), so every downstream comparison — the identity matcher, the canonical hash, the
|
|
@@ -121,6 +141,53 @@ function distributeOnce(s: string): string | null {
|
|
|
121
141
|
return null;
|
|
122
142
|
}
|
|
123
143
|
|
|
144
|
+
/**
|
|
145
|
+
* A bare `text` cast token — `::text` or `::pg_catalog.text` — from just past a literal's
|
|
146
|
+
* closing quote. Whitespace around `::` is tolerated (pg emits none; an author might). The
|
|
147
|
+
* trailing `\b` rejects `::textfoo`; the caller additionally rejects a following `[` so the
|
|
148
|
+
* array-type cast `::text[]` is never stripped.
|
|
149
|
+
*/
|
|
150
|
+
const LITERAL_TEXT_CAST = /^\s*::\s*(?:pg_catalog\.)?text\b/;
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Strip a bare `text` cast applied directly to a single-quoted string literal, wherever one
|
|
154
|
+
* appears. One left-to-right pass is complete: removing a cast token cannot create a new
|
|
155
|
+
* `'literal'::text` pair to its left, and it never forms an `(ARRAY[…])::T[]` for the other
|
|
156
|
+
* rule. Quoted regions are skipped so a `::text` inside a literal is content, not structure.
|
|
157
|
+
*/
|
|
158
|
+
function stripLiteralTextCast(s: string): string {
|
|
159
|
+
let out = '';
|
|
160
|
+
let i = 0;
|
|
161
|
+
while (i < s.length) {
|
|
162
|
+
const c = s[i];
|
|
163
|
+
if (c === "'") {
|
|
164
|
+
const close = skipQuoted(s, i);
|
|
165
|
+
if (close < 0) return out + s.slice(i); // unterminated literal — leave the rest alone
|
|
166
|
+
out += s.slice(i, close + 1); // the literal itself, verbatim
|
|
167
|
+
i = close + 1;
|
|
168
|
+
// Drop a bare text cast on the literal, and any stacked `::text::text`, so the result
|
|
169
|
+
// is a true fixpoint. `::text[]` is the array-type cast (element type, load-bearing),
|
|
170
|
+
// not a scalar text cast — the look-ahead skips whitespace so `::text []` is kept too.
|
|
171
|
+
let m = LITERAL_TEXT_CAST.exec(s.slice(i));
|
|
172
|
+
while (m && !/^\s*\[/.test(s.slice(i + m[0].length))) {
|
|
173
|
+
i += m[0].length; // drop the cast token
|
|
174
|
+
m = LITERAL_TEXT_CAST.exec(s.slice(i));
|
|
175
|
+
}
|
|
176
|
+
continue;
|
|
177
|
+
}
|
|
178
|
+
if (c === '"') {
|
|
179
|
+
const close = skipQuoted(s, i);
|
|
180
|
+
if (close < 0) return out + s.slice(i);
|
|
181
|
+
out += s.slice(i, close + 1); // a quoted identifier — never a string literal, skip whole
|
|
182
|
+
i = close + 1;
|
|
183
|
+
continue;
|
|
184
|
+
}
|
|
185
|
+
out += c;
|
|
186
|
+
i++;
|
|
187
|
+
}
|
|
188
|
+
return out;
|
|
189
|
+
}
|
|
190
|
+
|
|
124
191
|
/**
|
|
125
192
|
* Normalize one deparsed expression. Idempotent; unrecognized text returns unchanged.
|
|
126
193
|
* Fixpoint-bounded: nesting deeper than the cap returns its progress, which is still
|
|
@@ -130,8 +197,8 @@ export function normalizeDeparsedExpr(text: string): string {
|
|
|
130
197
|
let s = text;
|
|
131
198
|
for (let n = 0; n < 64; n++) {
|
|
132
199
|
const next = distributeOnce(s);
|
|
133
|
-
if (next === null)
|
|
200
|
+
if (next === null) break;
|
|
134
201
|
s = next;
|
|
135
202
|
}
|
|
136
|
-
return s;
|
|
203
|
+
return stripLiteralTextCast(s);
|
|
137
204
|
}
|