@rexezuge/tooling 0.0.0-stage → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (160) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +297 -2
  3. package/dist/eslint.d.ts +120 -0
  4. package/dist/eslint.d.ts.map +1 -0
  5. package/dist/eslint.js +551 -0
  6. package/dist/eslint.js.map +1 -0
  7. package/dist/functions/pages-proxy.d.ts +113 -0
  8. package/dist/functions/pages-proxy.d.ts.map +1 -0
  9. package/dist/functions/pages-proxy.js +131 -0
  10. package/dist/functions/pages-proxy.js.map +1 -0
  11. package/dist/index.d.ts +31 -0
  12. package/dist/index.d.ts.map +1 -0
  13. package/dist/index.js +28 -0
  14. package/dist/index.js.map +1 -0
  15. package/dist/scripts/backup/d1-target.d.ts +35 -0
  16. package/dist/scripts/backup/d1-target.d.ts.map +1 -0
  17. package/dist/scripts/backup/d1-target.js +31 -0
  18. package/dist/scripts/backup/d1-target.js.map +1 -0
  19. package/dist/scripts/backup/destination-config.d.ts +61 -0
  20. package/dist/scripts/backup/destination-config.d.ts.map +1 -0
  21. package/dist/scripts/backup/destination-config.js +57 -0
  22. package/dist/scripts/backup/destination-config.js.map +1 -0
  23. package/dist/scripts/backup/encrypt-backup.d.ts +35 -0
  24. package/dist/scripts/backup/encrypt-backup.d.ts.map +1 -0
  25. package/dist/scripts/backup/encrypt-backup.js +98 -0
  26. package/dist/scripts/backup/encrypt-backup.js.map +1 -0
  27. package/dist/scripts/backup/naming.d.ts +44 -0
  28. package/dist/scripts/backup/naming.d.ts.map +1 -0
  29. package/dist/scripts/backup/naming.js +58 -0
  30. package/dist/scripts/backup/naming.js.map +1 -0
  31. package/dist/scripts/check-god-files.d.ts +164 -0
  32. package/dist/scripts/check-god-files.d.ts.map +1 -0
  33. package/dist/scripts/check-god-files.js +271 -0
  34. package/dist/scripts/check-god-files.js.map +1 -0
  35. package/dist/scripts/ensure-spa-shell-stub.d.ts +3 -0
  36. package/dist/scripts/ensure-spa-shell-stub.d.ts.map +1 -0
  37. package/dist/scripts/ensure-spa-shell-stub.js +30 -0
  38. package/dist/scripts/ensure-spa-shell-stub.js.map +1 -0
  39. package/dist/scripts/init-secrets.d.ts +63 -0
  40. package/dist/scripts/init-secrets.d.ts.map +1 -0
  41. package/dist/scripts/init-secrets.js +240 -0
  42. package/dist/scripts/init-secrets.js.map +1 -0
  43. package/dist/scripts/lib/cli-args.d.ts +78 -0
  44. package/dist/scripts/lib/cli-args.d.ts.map +1 -0
  45. package/dist/scripts/lib/cli-args.js +116 -0
  46. package/dist/scripts/lib/cli-args.js.map +1 -0
  47. package/dist/scripts/lib/github-actions.d.ts +26 -0
  48. package/dist/scripts/lib/github-actions.d.ts.map +1 -0
  49. package/dist/scripts/lib/github-actions.js +38 -0
  50. package/dist/scripts/lib/github-actions.js.map +1 -0
  51. package/dist/scripts/lib/wrangler-table.d.ts +46 -0
  52. package/dist/scripts/lib/wrangler-table.d.ts.map +1 -0
  53. package/dist/scripts/lib/wrangler-table.js +99 -0
  54. package/dist/scripts/lib/wrangler-table.js.map +1 -0
  55. package/dist/scripts/migrations-lock.d.ts +3 -0
  56. package/dist/scripts/migrations-lock.d.ts.map +1 -0
  57. package/dist/scripts/migrations-lock.js +46 -0
  58. package/dist/scripts/migrations-lock.js.map +1 -0
  59. package/dist/scripts/prepare-wrangler-config.d.ts +3 -0
  60. package/dist/scripts/prepare-wrangler-config.d.ts.map +1 -0
  61. package/dist/scripts/prepare-wrangler-config.js +50 -0
  62. package/dist/scripts/prepare-wrangler-config.js.map +1 -0
  63. package/dist/scripts/spa-shell.d.ts +41 -0
  64. package/dist/scripts/spa-shell.d.ts.map +1 -0
  65. package/dist/scripts/spa-shell.js +155 -0
  66. package/dist/scripts/spa-shell.js.map +1 -0
  67. package/dist/scripts/validate-locales.d.ts +26 -0
  68. package/dist/scripts/validate-locales.d.ts.map +1 -0
  69. package/dist/scripts/validate-locales.js +350 -0
  70. package/dist/scripts/validate-locales.js.map +1 -0
  71. package/dist/scripts/verify-migrations.d.ts +62 -0
  72. package/dist/scripts/verify-migrations.d.ts.map +1 -0
  73. package/dist/scripts/verify-migrations.js +302 -0
  74. package/dist/scripts/verify-migrations.js.map +1 -0
  75. package/dist/scripts/verify-spa-shell.d.ts +3 -0
  76. package/dist/scripts/verify-spa-shell.d.ts.map +1 -0
  77. package/dist/scripts/verify-spa-shell.js +53 -0
  78. package/dist/scripts/verify-spa-shell.js.map +1 -0
  79. package/dist/scripts/wrangler-config/cli.d.ts +22 -0
  80. package/dist/scripts/wrangler-config/cli.d.ts.map +1 -0
  81. package/dist/scripts/wrangler-config/cli.js +51 -0
  82. package/dist/scripts/wrangler-config/cli.js.map +1 -0
  83. package/dist/scripts/wrangler-config/patches.d.ts +51 -0
  84. package/dist/scripts/wrangler-config/patches.d.ts.map +1 -0
  85. package/dist/scripts/wrangler-config/patches.js +140 -0
  86. package/dist/scripts/wrangler-config/patches.js.map +1 -0
  87. package/dist/scripts/wrangler-config/resources.d.ts +70 -0
  88. package/dist/scripts/wrangler-config/resources.d.ts.map +1 -0
  89. package/dist/scripts/wrangler-config/resources.js +290 -0
  90. package/dist/scripts/wrangler-config/resources.js.map +1 -0
  91. package/dist/scripts/wrangler-config/types.d.ts +103 -0
  92. package/dist/scripts/wrangler-config/types.d.ts.map +1 -0
  93. package/dist/scripts/wrangler-config/types.js +49 -0
  94. package/dist/scripts/wrangler-config/types.js.map +1 -0
  95. package/dist/test/integration-migrations.d.ts +167 -0
  96. package/dist/test/integration-migrations.d.ts.map +1 -0
  97. package/dist/test/integration-migrations.js +171 -0
  98. package/dist/test/integration-migrations.js.map +1 -0
  99. package/dist/test/mocks/cloudflare-workers.d.ts +106 -0
  100. package/dist/test/mocks/cloudflare-workers.d.ts.map +1 -0
  101. package/dist/test/mocks/cloudflare-workers.js +90 -0
  102. package/dist/test/mocks/cloudflare-workers.js.map +1 -0
  103. package/dist/vite.d.ts +117 -0
  104. package/dist/vite.d.ts.map +1 -0
  105. package/dist/vite.js +125 -0
  106. package/dist/vite.js.map +1 -0
  107. package/dist/vitest-web.d.ts +73 -0
  108. package/dist/vitest-web.d.ts.map +1 -0
  109. package/dist/vitest-web.js +72 -0
  110. package/dist/vitest-web.js.map +1 -0
  111. package/dist/vitest.d.ts +92 -0
  112. package/dist/vitest.d.ts.map +1 -0
  113. package/dist/vitest.js +128 -0
  114. package/dist/vitest.js.map +1 -0
  115. package/package.json +58 -3
  116. package/src/eslint.test.ts +175 -0
  117. package/src/eslint.ts +640 -0
  118. package/src/functions/pages-proxy.test.ts +72 -0
  119. package/src/functions/pages-proxy.ts +187 -0
  120. package/src/github/actions/retry-step/action.yml +39 -0
  121. package/src/github/actions/setup-env/action.yml +20 -0
  122. package/src/github/dependabot.yml +30 -0
  123. package/src/github/workflows/backup-main.yml +46 -0
  124. package/src/github/workflows/continuous-deployment.yml +188 -0
  125. package/src/github/workflows/continuous-integration.yml +259 -0
  126. package/src/github/workflows/scheduled-version-update.yml +38 -0
  127. package/src/github/workflows/upstream-sync.yml +56 -0
  128. package/src/index.ts +42 -0
  129. package/src/scripts/backup/backup-rules.test.ts +105 -0
  130. package/src/scripts/backup/d1-target.ts +54 -0
  131. package/src/scripts/backup/destination-config.ts +92 -0
  132. package/src/scripts/backup/encrypt-backup.ts +107 -0
  133. package/src/scripts/backup/naming.ts +62 -0
  134. package/src/scripts/check-god-files.test.ts +131 -0
  135. package/src/scripts/check-god-files.ts +327 -0
  136. package/src/scripts/ensure-spa-shell-stub.ts +34 -0
  137. package/src/scripts/init-secrets.ts +265 -0
  138. package/src/scripts/lib/cli-args.ts +154 -0
  139. package/src/scripts/lib/github-actions.ts +41 -0
  140. package/src/scripts/lib/wrangler-table.ts +105 -0
  141. package/src/scripts/migrations-lock.ts +52 -0
  142. package/src/scripts/prepare-wrangler-config.ts +51 -0
  143. package/src/scripts/spa-shell.test.ts +91 -0
  144. package/src/scripts/spa-shell.ts +179 -0
  145. package/src/scripts/validate-locales.test.ts +89 -0
  146. package/src/scripts/validate-locales.ts +380 -0
  147. package/src/scripts/verify-migrations.test.ts +71 -0
  148. package/src/scripts/verify-migrations.ts +364 -0
  149. package/src/scripts/verify-spa-shell.ts +56 -0
  150. package/src/scripts/wrangler-config/cli.ts +51 -0
  151. package/src/scripts/wrangler-config/patches.ts +157 -0
  152. package/src/scripts/wrangler-config/resources.ts +330 -0
  153. package/src/scripts/wrangler-config/types.ts +113 -0
  154. package/src/test/integration-migrations.test.ts +169 -0
  155. package/src/test/integration-migrations.ts +267 -0
  156. package/src/test/mocks/cloudflare-workers.ts +115 -0
  157. package/src/vite.test.ts +83 -0
  158. package/src/vite.ts +202 -0
  159. package/src/vitest-web.ts +109 -0
  160. package/src/vitest.ts +185 -0
@@ -0,0 +1,302 @@
1
+ /**
2
+ * Verify the migration set is complete, ordered, and locked; write the lock.
3
+ *
4
+ * Provenance: Edge-Sonic's `scripts/migrations/{verify-migrations,lock-check}.ts`
5
+ * (the rule module plus its entrypoint) and AWS's `verify-migrations.ts`,
6
+ * converged. Every copy checks the same properties of a `migrations/` directory;
7
+ * they differ in lock-file naming, in whether the lock is enforced on CI, and in
8
+ * how much of D1's ordering semantics they encode.
9
+ *
10
+ * ### Why a checksum lock at all
11
+ *
12
+ * D1 records *which* migrations it has applied — the filename and a timestamp —
13
+ * but not *what the file contained* when it applied them. So editing a migration
14
+ * that has already run is not caught by anything: the deploy still succeeds, the
15
+ * local database built from the baseline plus the edited file picks up the new
16
+ * statements, and production keeps the schema it had. The two databases then
17
+ * differ silently and permanently, and the next migration that assumes the newer
18
+ * shape fails only in production. A committed digest per file turns that into a
19
+ * CI failure.
20
+ *
21
+ * ### What the checks are, and where each comes from
22
+ *
23
+ * - **`NNNN_snake_case.sql`** (AWS) — the numbering *is* the ordering: D1 sorts by
24
+ * filename, so a file that sorts *after* the ones already applied is the only
25
+ * kind that can be introduced safely.
26
+ * - **Contiguous prefixes** (AWS) — a hole means one database applied 0001 and
27
+ * 0003 while another applied only 0001, and nothing downstream can tell which.
28
+ * Checked **only when no squash baseline exists**: a squash legitimately
29
+ * absorbs its predecessors, so numbering restarts wherever it starts, and a
30
+ * contiguity rule that fires on a squash is a rule that blocks the squash.
31
+ * - **One entry per file** (all) — a file with no recorded digest is one nothing
32
+ * can say anything about.
33
+ * - **Digest unchanged** (all) — the edited-after-locking failure.
34
+ * - **No orphan entries** (Edge-Sonic) — a locked file that is gone has been
35
+ * applied to a live database and is no longer reproducible from this repo.
36
+ * - **Strictly increasing prefixes** (Edge-Sonic's `out-of-order`) — a file
37
+ * sorting *before* the newest locked one can never reach a database that has
38
+ * applied that newest one, so its statements would only run on a fresh build.
39
+ * - **No duplicate prefixes** (Edge-Sonic) — two files numbered `0033_` are not a
40
+ * style problem: D1 orders them by the rest of the filename, so which one lands
41
+ * on a given database depends on a lexicographic tiebreak nobody intended.
42
+ *
43
+ * ### The squash baseline
44
+ *
45
+ * `0008_squash.sql` holds the combined schema of every migration before it, and
46
+ * those files are deleted when it is created. A later squash repeats that. So a
47
+ * squash legitimately (a) rewrites one file and (b) removes files the lock still
48
+ * lists, and neither may be reported as drift. The baseline is the
49
+ * highest-numbered `NNNN_squash.sql` **on disk**, never the value the lock claims
50
+ * — deriving it means a stale or forged `baseline` cannot widen the exempt set.
51
+ *
52
+ * ### The one write path
53
+ *
54
+ * `--write` is add-only where it counts: it records unlocked files, refreshes the
55
+ * baseline's own entry, and drops entries the baseline absorbed. Every other
56
+ * entry is left exactly as it is, so an incremental `edited` finding **survives a
57
+ * `--write`** — re-baselining one means deleting its entry by hand, which shows
58
+ * up in the diff. There is deliberately no `--force`, because a write that could
59
+ * bless a drift would be the operator blessing their own edit.
60
+ */
61
+ import { createHash } from 'node:crypto';
62
+ import { pathToFileURL } from 'node:url';
63
+ import { existsSync, readFileSync, readdirSync, writeFileSync } from 'node:fs';
64
+ import { join } from 'node:path';
65
+ /**
66
+ * The lock's filename, beside the `.sql` files.
67
+ *
68
+ * Named `migrations.lock.json` rather than `migrations.json` so it cannot be
69
+ * mistaken for a migration: wrangler applies `*.sql` only, and a lock named like
70
+ * one would be a deploy-time mystery.
71
+ */
72
+ const LOCK_NAME = 'migrations.lock.json';
73
+ /** The filename shape every migration must have. `NNNN_` is fixed at four digits. */
74
+ const NAME_PATTERN = /^(\d{4})_[a-z0-9_]+\.sql$/;
75
+ /** The squashed-baseline shape. The highest-numbered match is the baseline. */
76
+ const SQUASH_PATTERN = /^\d{4}_squash\.sql$/;
77
+ /** The lock's format version. Bumped only on an incompatible shape change. */
78
+ const LOCK_VERSION = 1;
79
+ function sha256Hex(content) {
80
+ return createHash('sha256').update(content, 'utf8').digest('hex');
81
+ }
82
+ /**
83
+ * The highest-numbered `NNNN_squash.sql` on disk, or `undefined` when the repo
84
+ * has not squashed — in which case every migration is incremental and therefore
85
+ * every one of them is immutable.
86
+ */
87
+ export function baselineOf(files) {
88
+ let highest;
89
+ for (const name of files) {
90
+ if (SQUASH_PATTERN.test(name) && (highest === undefined || name > highest)) {
91
+ highest = name;
92
+ }
93
+ }
94
+ return highest;
95
+ }
96
+ /** True when `name` is at or before the baseline, i.e. the set a squash absorbs. */
97
+ function isSubsumed(name, baseline) {
98
+ return baseline !== undefined && name <= baseline;
99
+ }
100
+ /**
101
+ * Read the migration set and validate its shape without consulting the lock.
102
+ *
103
+ * The lock is read here too, so one walk answers both "what is on disk" and "what
104
+ * is recorded"; a lock that is merely *absent* is reported by `verifyMigrations`
105
+ * rather than here, because that is the bootstrap case rather than a defect.
106
+ */
107
+ function inspectMigrations(migrationsDir) {
108
+ const problems = [];
109
+ if (!existsSync(migrationsDir)) {
110
+ problems.push({ problem: `${migrationsDir} does not exist` });
111
+ return { files: [], problems, lock: null };
112
+ }
113
+ // Top level only, matching wrangler's default `${migrationsDir}/*.sql`, so the
114
+ // lock describes exactly the set wrangler will run. `migrations.lock.json` is
115
+ // not one of them, because it is not a `.sql` file.
116
+ const files = readdirSync(migrationsDir)
117
+ .filter((name) => name.endsWith('.sql'))
118
+ .sort((left, right) => left.localeCompare(right));
119
+ for (const name of files) {
120
+ if (!NAME_PATTERN.test(name)) {
121
+ problems.push({ problem: `${name} does not match NNNN_snake_case.sql` });
122
+ }
123
+ }
124
+ const baseline = baselineOf(files);
125
+ const prefixes = files.map((name) => NAME_PATTERN.exec(name)?.[1]);
126
+ // Strictly increasing: the property D1 actually depends on. A duplicate or a
127
+ // descending prefix means two files compete for the same slot in the apply
128
+ // order, and which one wins is a lexicographic tiebreak nobody intended.
129
+ for (let index = 1; index < prefixes.length; index += 1) {
130
+ const previous = prefixes[index - 1];
131
+ const current = prefixes[index];
132
+ if (previous !== undefined && current !== undefined && current <= previous) {
133
+ problems.push({
134
+ problem: `${files[index] ?? '?'} sorts at or before ${files[index - 1] ?? '?'} — D1 applies in filename order, so one of the two can never run`,
135
+ });
136
+ }
137
+ }
138
+ // Contiguity is checked only with no squash on disk: a squash deletes the files
139
+ // it absorbs, so numbering restarts wherever it starts and a hole is the
140
+ // expected shape rather than a defect.
141
+ if (baseline === undefined) {
142
+ for (let index = 0; index < prefixes.length; index += 1) {
143
+ const expected = String(index + 1).padStart(4, '0');
144
+ if (prefixes[index] !== expected) {
145
+ problems.push({
146
+ problem: `migration numbering is not contiguous: expected ${expected}_*.sql, found ${files[index] ?? '?'}`,
147
+ });
148
+ }
149
+ }
150
+ }
151
+ const lockPath = join(migrationsDir, LOCK_NAME);
152
+ let lock = null;
153
+ if (existsSync(lockPath)) {
154
+ try {
155
+ lock = JSON.parse(readFileSync(lockPath, 'utf8'));
156
+ }
157
+ catch {
158
+ problems.push({ problem: `${LOCK_NAME} is not valid JSON` });
159
+ }
160
+ }
161
+ return { files, problems, lock };
162
+ }
163
+ /**
164
+ * The lock as `--write` would leave it.
165
+ *
166
+ * `null` when the existing lock cannot be trusted as a base, which is exactly
167
+ * when a `--write` must refuse rather than overwrite contents it failed to
168
+ * understand. A lock that is merely *absent* is not malformed: that is the
169
+ * bootstrap case, and it is built from disk like any other.
170
+ */
171
+ function buildUpdatedLock(migrationsDir, files, existing, baseline) {
172
+ const digestOf = (name) => sha256Hex(readFileSync(join(migrationsDir, name), 'utf8'));
173
+ if (existing === null) {
174
+ return { version: LOCK_VERSION, ...(baseline !== undefined && { baseline }), migrations: files.map((name) => ({ name, sha256: digestOf(name) })) };
175
+ }
176
+ const kept = new Map();
177
+ for (const file of files) {
178
+ const recorded = existing.migrations.find((entry) => entry.name === file);
179
+ if (recorded === undefined)
180
+ continue;
181
+ // The baseline is the one file a squash is allowed to rewrite.
182
+ kept.set(file, file === baseline ? digestOf(file) : recorded.sha256);
183
+ }
184
+ // Unlocked files are added — that is the new-migration case. Every other entry
185
+ // is carried across untouched, so an `edited` finding survives the write.
186
+ for (const file of files) {
187
+ if (!kept.has(file) && !existing.migrations.some((entry) => entry.name === file)) {
188
+ kept.set(file, digestOf(file));
189
+ }
190
+ }
191
+ // Entries the baseline absorbed are dropped: committing a squash looks exactly
192
+ // like this, and neither it nor a rewritten baseline can hide a divergence,
193
+ // because a database that applied those files already has them recorded in D1's
194
+ // own `d1_migrations`.
195
+ for (const entry of existing.migrations) {
196
+ if (!files.includes(entry.name) && !isSubsumed(entry.name, baseline)) {
197
+ kept.set(entry.name, entry.sha256);
198
+ }
199
+ }
200
+ return {
201
+ version: LOCK_VERSION,
202
+ ...(baseline !== undefined && { baseline }),
203
+ migrations: [...kept]
204
+ .sort(([left], [right]) => left.localeCompare(right))
205
+ .map(([name, sha256]) => ({ name, sha256 })),
206
+ };
207
+ }
208
+ /** Verify (or with `write: true`, record) the lock against the files. */
209
+ function verifyMigrations(migrationsDir, options = {}) {
210
+ const report = inspectMigrations(migrationsDir);
211
+ const baseline = baselineOf(report.files);
212
+ // A malformed lock is a finding in its own right, and a `--write` must not
213
+ // overwrite contents it failed to understand.
214
+ if (report.problems.length > 0) {
215
+ return report;
216
+ }
217
+ if (options.write === true) {
218
+ const updated = buildUpdatedLock(migrationsDir, report.files, report.lock, baseline);
219
+ const next = `${JSON.stringify(updated, null, 2)}\n`;
220
+ const path = join(migrationsDir, LOCK_NAME);
221
+ const before = existsSync(path) ? readFileSync(path, 'utf8') : null;
222
+ if (before !== next) {
223
+ writeFileSync(path, next, 'utf8');
224
+ }
225
+ report.lock = updated;
226
+ return report;
227
+ }
228
+ const lock = report.lock;
229
+ if (lock === null) {
230
+ report.problems.push({ problem: `${LOCK_NAME} is missing — run migrations:lock` });
231
+ return report;
232
+ }
233
+ const recorded = new Map(lock.migrations.map((entry) => [entry.name, entry.sha256]));
234
+ for (const name of report.files) {
235
+ const expected = recorded.get(name);
236
+ if (expected === undefined) {
237
+ report.problems.push({ problem: `${name} has no lock entry — run migrations:lock` });
238
+ continue;
239
+ }
240
+ // At or before the baseline, a squash rewrites its own file, and that is the
241
+ // whole reason a baseline exists.
242
+ if (isSubsumed(name, baseline))
243
+ continue;
244
+ const actual = sha256Hex(readFileSync(join(migrationsDir, name), 'utf8'));
245
+ if (actual !== expected) {
246
+ report.problems.push({
247
+ problem: `${name} changed after locking (expected ${expected.slice(0, 12)}…, got ${actual.slice(0, 12)}…)`,
248
+ });
249
+ }
250
+ }
251
+ for (const entry of lock.migrations) {
252
+ if (report.files.includes(entry.name))
253
+ continue;
254
+ if (isSubsumed(entry.name, baseline))
255
+ continue;
256
+ report.problems.push({ problem: `${entry.name} is in the lock but no longer exists` });
257
+ }
258
+ // A file sorting before the newest locked one can never reach a database that
259
+ // has already applied that newest one, so its statements would only run on a
260
+ // fresh build — the same silent split the checksum exists to prevent.
261
+ const newest = [...recorded.keys()].sort((left, right) => left.localeCompare(right)).at(-1);
262
+ if (newest !== undefined) {
263
+ for (const name of report.files) {
264
+ if (name < newest && !recorded.has(name)) {
265
+ report.problems.push({
266
+ problem: `${name} sorts before ${newest}, which is already locked — D1 applies in filename order, so it would never run on a database that has applied ${newest}`,
267
+ });
268
+ }
269
+ }
270
+ }
271
+ return report;
272
+ }
273
+ /** CLI entry: `verifyMigrationsCli(['[migrations-dir]?', '--write'?'])`. */
274
+ function verifyMigrationsCli(argv = process.argv.slice(2)) {
275
+ const dir = argv.find((arg) => !arg.startsWith('--')) ?? 'migrations';
276
+ const write = argv.includes('--write');
277
+ const report = verifyMigrations(dir, { write });
278
+ if (report.problems.length > 0) {
279
+ console.error(`Migration validation failed for ${dir}:`);
280
+ for (const problem of report.problems)
281
+ console.error(` - ${problem.problem}`);
282
+ return 1;
283
+ }
284
+ if (write) {
285
+ console.log(`Migration lock written (${report.files.length} files).`);
286
+ return 0;
287
+ }
288
+ console.log(`Migration validation passed (${report.files.length} files, lock in sync).`);
289
+ return 0;
290
+ }
291
+ export { inspectMigrations, verifyMigrations, verifyMigrationsCli, LOCK_NAME, LOCK_VERSION };
292
+ // Run only when this file is the entrypoint.
293
+ //
294
+ // The rules above are pure and the tests import them directly, so a module-level
295
+ // call would shell out (and exit) during a test run. The URL comparison rather than
296
+ // an unconditional call is the same guard Durable-DAV's init-secrets.ts uses, and
297
+ // it is what keeps the rule modules importable — which is the whole reason they are
298
+ // separate modules in the first place.
299
+ if (process.argv[1] !== undefined && import.meta.url === pathToFileURL(process.argv[1]).href) {
300
+ process.exit(verifyMigrationsCli(process.argv.slice(2)));
301
+ }
302
+ //# sourceMappingURL=verify-migrations.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"verify-migrations.js","sourceRoot":"","sources":["../../src/scripts/verify-migrations.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env tsx
2
+ export {};
3
+ //# sourceMappingURL=verify-spa-shell.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"verify-spa-shell.d.ts","sourceRoot":"","sources":["../../src/scripts/verify-spa-shell.ts"],"names":[],"mappings":""}
@@ -0,0 +1,53 @@
1
+ #!/usr/bin/env tsx
2
+ /**
3
+ * Fail when the SPA shell the API worker serves is missing or self-inconsistent.
4
+ *
5
+ * Provenance: Durable-DAV's and Durable-DAV-Router's `scripts/verify-spa-shell.mjs`
6
+ * and Edge-Sonic's typed `scripts/build/verify-spa-shell.ts`. The rules — which of
7
+ * the ways a shell can be wrong to look for — live in `spa-shell.ts`, so they are
8
+ * unit tested without a build on disk; this file is the entrypoint that turns the
9
+ * verdict into an exit code.
10
+ *
11
+ * What it catches:
12
+ *
13
+ * - the `postinstall` stub, or no build at all, still in place;
14
+ * - `spa-shell.ts` referencing `/assets/...` bundles that were not emitted (a
15
+ * partially cleaned or half-copied `dist/`, which serves a shell whose JS 404s
16
+ * and therefore renders no operator UI at all);
17
+ * - `spa-shell.ts` and `apps/web/dist/index.html` disagreeing, i.e. only one of
18
+ * the two halves of the build was refreshed.
19
+ *
20
+ * **The build is a precondition of this check, not a convenience.** Both paths are
21
+ * gitignored and `postinstall` writes a deliberately empty stub, so verifying
22
+ * without building cannot pass — and it fails with precisely the message above,
23
+ * which is the check working rather than the check being wrong.
24
+ *
25
+ * Usage, from the repo root:
26
+ *
27
+ * ```bash
28
+ * pnpm exec tsx scripts/verify-spa-shell.ts # defaults to ./ as root
29
+ * pnpm exec tsx scripts/verify-spa-shell.ts --root /path/to/repo
30
+ * ```
31
+ */
32
+ import path from 'node:path';
33
+ import { fileURLToPath } from 'node:url';
34
+ import { verifySpaShell } from './spa-shell';
35
+ /**
36
+ * The repository root, derived from this file rather than from `process.cwd()`.
37
+ *
38
+ * Two levels up, because this file sits in `scripts/`. Deriving it means the gate
39
+ * reads the same tree whatever directory it is invoked from — which is the
40
+ * difference between a gate and a gate that passes because nobody ran it from the
41
+ * right place.
42
+ */
43
+ const REPOSITORY_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
44
+ const root = process.argv[2] === undefined ? REPOSITORY_ROOT : path.resolve(process.argv[2]);
45
+ const { problems } = verifySpaShell({ root });
46
+ if (problems.length > 0) {
47
+ console.error('SPA shell check failed:');
48
+ for (const problem of problems)
49
+ console.error(` - ${problem}`);
50
+ process.exit(1);
51
+ }
52
+ console.log(`SPA shell check passed (${path.join(root, 'apps/api/src/generated/spa-shell.ts')} matches ${path.join(root, 'apps/web/dist')}).`);
53
+ //# sourceMappingURL=verify-spa-shell.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"verify-spa-shell.js","sourceRoot":"","sources":["../../src/scripts/verify-spa-shell.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAE7C;;;;;;;GAOG;AACH,MAAM,eAAe,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC;AAEzF,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,SAAS,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;AAE7F,MAAM,EAAE,QAAQ,EAAE,GAAG,cAAc,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC;AAE9C,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;IACxB,OAAO,CAAC,KAAK,CAAC,yBAAyB,CAAC,CAAC;IACzC,KAAK,MAAM,OAAO,IAAI,QAAQ;QAAE,OAAO,CAAC,KAAK,CAAC,OAAO,OAAO,EAAE,CAAC,CAAC;IAChE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC;AAED,OAAO,CAAC,GAAG,CAAC,2BAA2B,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,qCAAqC,CAAC,YAAY,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,eAAe,CAAC,IAAI,CAAC,CAAC"}
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Running `wrangler` and reading its output.
3
+ *
4
+ * Provenance: ported from Edge-Sonic's `scripts/lib/wrangler-config/cli.ts`.
5
+ */
6
+ /**
7
+ * Runs a wrangler subcommand and returns stdout.
8
+ *
9
+ * Arguments are passed as an argv array rather than interpolated into a shell
10
+ * string, so a value containing shell metacharacters cannot alter the command.
11
+ */
12
+ export declare function runWrangler(args: readonly string[], input?: string): string;
13
+ /**
14
+ * Parses a JSON array from a command's stdout, naming the command on failure.
15
+ *
16
+ * Several wrangler subcommands print a human-readable table when they detect a
17
+ * TTY, so "not JSON" is an expected outcome that the caller often has a table
18
+ * parser for. Passing the command description turns that into a catchable,
19
+ * self-explanatory error instead of an unlabelled `SyntaxError`.
20
+ */
21
+ export declare function parseJsonArray<T>(output: string, commandDescription: string): T[];
22
+ //# sourceMappingURL=cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../../../src/scripts/wrangler-config/cli.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAIH;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAgB3E;AAED;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,kBAAkB,EAAE,MAAM,GAAG,CAAC,EAAE,CAUjF"}
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Running `wrangler` and reading its output.
3
+ *
4
+ * Provenance: ported from Edge-Sonic's `scripts/lib/wrangler-config/cli.ts`.
5
+ */
6
+ import { execFileSync } from 'node:child_process';
7
+ /**
8
+ * Runs a wrangler subcommand and returns stdout.
9
+ *
10
+ * Arguments are passed as an argv array rather than interpolated into a shell
11
+ * string, so a value containing shell metacharacters cannot alter the command.
12
+ */
13
+ export function runWrangler(args, input) {
14
+ try {
15
+ // `pnpm` is resolved from PATH deliberately: the scripts run on CI runners and
16
+ // in local shells, and both resolve it the same way.
17
+ // eslint-disable-next-line sonarjs/no-os-command-from-path
18
+ return execFileSync('pnpm', ['exec', 'wrangler', ...args], {
19
+ encoding: 'utf8',
20
+ stdio: [input === undefined ? 'ignore' : 'pipe', 'pipe', 'pipe'],
21
+ ...(input !== undefined && { input }),
22
+ });
23
+ }
24
+ catch (error) {
25
+ const maybeProcessError = error;
26
+ const stdout = maybeProcessError.stdout ? maybeProcessError.stdout.toString() : '';
27
+ const stderr = maybeProcessError.stderr ? maybeProcessError.stderr.toString() : '';
28
+ throw new Error(`Command failed: pnpm exec wrangler ${args.join(' ')}\n${stdout}${stderr || maybeProcessError.message || ''}`);
29
+ }
30
+ }
31
+ /**
32
+ * Parses a JSON array from a command's stdout, naming the command on failure.
33
+ *
34
+ * Several wrangler subcommands print a human-readable table when they detect a
35
+ * TTY, so "not JSON" is an expected outcome that the caller often has a table
36
+ * parser for. Passing the command description turns that into a catchable,
37
+ * self-explanatory error instead of an unlabelled `SyntaxError`.
38
+ */
39
+ export function parseJsonArray(output, commandDescription) {
40
+ try {
41
+ const parsed = JSON.parse(output);
42
+ if (Array.isArray(parsed)) {
43
+ return parsed;
44
+ }
45
+ }
46
+ catch {
47
+ // Fall through to the explicit error below.
48
+ }
49
+ throw new Error(`Expected JSON array output from ${commandDescription}. Output:\n${output}`);
50
+ }
51
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../../../src/scripts/wrangler-config/cli.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAElD;;;;;GAKG;AACH,MAAM,UAAU,WAAW,CAAC,IAAuB,EAAE,KAAc;IACjE,IAAI,CAAC;QACH,+EAA+E;QAC/E,qDAAqD;QACrD,2DAA2D;QAC3D,OAAO,YAAY,CAAC,MAAM,EAAE,CAAC,MAAM,EAAE,UAAU,EAAE,GAAG,IAAI,CAAC,EAAE;YACzD,QAAQ,EAAE,MAAM;YAChB,KAAK,EAAE,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC;YAChE,GAAG,CAAC,KAAK,KAAK,SAAS,IAAI,EAAE,KAAK,EAAE,CAAC;SACtC,CAAC,CAAC;IACL,CAAC;IAAC,OAAO,KAAc,EAAE,CAAC;QACxB,MAAM,iBAAiB,GAAG,KAAiF,CAAC;QAC5G,MAAM,MAAM,GAAW,iBAAiB,CAAC,MAAM,CAAC,CAAC,CAAC,iBAAiB,CAAC,MAAM,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC3F,MAAM,MAAM,GAAW,iBAAiB,CAAC,MAAM,CAAC,CAAC,CAAC,iBAAiB,CAAC,MAAM,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC3F,MAAM,IAAI,KAAK,CAAC,sCAAsC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,MAAM,GAAG,MAAM,IAAI,iBAAiB,CAAC,OAAO,IAAI,EAAE,EAAE,CAAC,CAAC;IACjI,CAAC;AACH,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,cAAc,CAAI,MAAc,EAAE,kBAA0B;IAC1E,IAAI,CAAC;QACH,MAAM,MAAM,GAAY,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;QAC3C,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;YAC1B,OAAO,MAAa,CAAC;QACvB,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,4CAA4C;IAC9C,CAAC;IACD,MAAM,IAAI,KAAK,CAAC,mCAAmC,kBAAkB,cAAc,MAAM,EAAE,CAAC,CAAC;AAC/F,CAAC"}
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Reading, writing and patching `wrangler.jsonc`.
3
+ *
4
+ * Provenance: ported from Edge-Sonic's `scripts/lib/wrangler-config/patches.ts`.
5
+ *
6
+ * The config is JSONC — `//` comments and trailing commas — so it is edited
7
+ * through jsonc-parser's `modify`/`applyEdits` rather than parsed and re-serialized.
8
+ * A round trip through `JSON.parse` would drop every comment and reformat the
9
+ * file, which is a diff nobody can review and a reason to regenerate the config by
10
+ * hand instead.
11
+ */
12
+ import { type WranglerConfig } from './types';
13
+ /** The config's current text and its parsed form. */
14
+ export interface ConfigFile {
15
+ content: string;
16
+ config: WranglerConfig;
17
+ }
18
+ export declare function readConfig(): ConfigFile;
19
+ /**
20
+ * Apply one edit to the config text, preserving comments and formatting.
21
+ */
22
+ export declare function writeConfigValue(content: string, path: Array<string | number>, value: unknown): string;
23
+ /** Write the config, guaranteeing a trailing newline so Prettier sees a fixed point. */
24
+ export declare function writeConfig(content: string): void;
25
+ /**
26
+ * The top-level keys a caller wants to merge into the config, from the environment.
27
+ *
28
+ * `WRANGLER_PATCH_JSON` is how a repository variable supplies per-environment
29
+ * settings without a second file: a repo with one worker and three environments
30
+ * patches the routes and the vars rather than keeping three templates in step.
31
+ */
32
+ export declare function parseTopLevelPatch(): Record<string, unknown> | undefined;
33
+ /** The `vars` entries a caller wants to merge, from the environment. */
34
+ export declare function parseVarsPatch(): Record<string, string> | undefined;
35
+ /**
36
+ * Materialize the config: the repository variable when it is set, otherwise the
37
+ * committed template.
38
+ */
39
+ export declare function prepareConfigFile(): void;
40
+ /**
41
+ * Refuse a config older than the template requires.
42
+ *
43
+ * The template is the one file in the repo that says which wrangler behaviour the
44
+ * worker was written against, and a config written by an older template can name
45
+ * bindings the worker no longer reads. Failing here rather than at deploy time is
46
+ * the whole reason the check exists.
47
+ */
48
+ export declare function ensureMinimumConfigVersion(): void;
49
+ export declare function applyTopLevelPatch(): void;
50
+ export declare function applyVarsPatch(): void;
51
+ //# sourceMappingURL=patches.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"patches.d.ts","sourceRoot":"","sources":["../../../src/scripts/wrangler-config/patches.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAIH,OAAO,EAA8B,KAAK,cAAc,EAAE,MAAM,SAAS,CAAC;AAE1E,qDAAqD;AACrD,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,cAAc,CAAC;CACxB;AAED,wBAAgB,UAAU,IAAI,UAAU,CAGvC;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,CAAC,MAAM,GAAG,MAAM,CAAC,EAAE,KAAK,EAAE,OAAO,GAAG,MAAM,CAGtG;AAED,wFAAwF;AACxF,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAEjD;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAExE;AAED,wEAAwE;AACxE,wBAAgB,cAAc,IAAI,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,SAAS,CAgBnE;AAkBD;;;GAGG;AACH,wBAAgB,iBAAiB,IAAI,IAAI,CASxC;AAED;;;;;;;GAOG;AACH,wBAAgB,0BAA0B,IAAI,IAAI,CAYjD;AAED,wBAAgB,kBAAkB,IAAI,IAAI,CAYzC;AAED,wBAAgB,cAAc,IAAI,IAAI,CAmBrC"}