@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.
- package/LICENSE +21 -0
- package/README.md +297 -2
- package/dist/eslint.d.ts +120 -0
- package/dist/eslint.d.ts.map +1 -0
- package/dist/eslint.js +551 -0
- package/dist/eslint.js.map +1 -0
- package/dist/functions/pages-proxy.d.ts +113 -0
- package/dist/functions/pages-proxy.d.ts.map +1 -0
- package/dist/functions/pages-proxy.js +131 -0
- package/dist/functions/pages-proxy.js.map +1 -0
- package/dist/index.d.ts +31 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +28 -0
- package/dist/index.js.map +1 -0
- package/dist/scripts/backup/d1-target.d.ts +35 -0
- package/dist/scripts/backup/d1-target.d.ts.map +1 -0
- package/dist/scripts/backup/d1-target.js +31 -0
- package/dist/scripts/backup/d1-target.js.map +1 -0
- package/dist/scripts/backup/destination-config.d.ts +61 -0
- package/dist/scripts/backup/destination-config.d.ts.map +1 -0
- package/dist/scripts/backup/destination-config.js +57 -0
- package/dist/scripts/backup/destination-config.js.map +1 -0
- package/dist/scripts/backup/encrypt-backup.d.ts +35 -0
- package/dist/scripts/backup/encrypt-backup.d.ts.map +1 -0
- package/dist/scripts/backup/encrypt-backup.js +98 -0
- package/dist/scripts/backup/encrypt-backup.js.map +1 -0
- package/dist/scripts/backup/naming.d.ts +44 -0
- package/dist/scripts/backup/naming.d.ts.map +1 -0
- package/dist/scripts/backup/naming.js +58 -0
- package/dist/scripts/backup/naming.js.map +1 -0
- package/dist/scripts/check-god-files.d.ts +164 -0
- package/dist/scripts/check-god-files.d.ts.map +1 -0
- package/dist/scripts/check-god-files.js +271 -0
- package/dist/scripts/check-god-files.js.map +1 -0
- package/dist/scripts/ensure-spa-shell-stub.d.ts +3 -0
- package/dist/scripts/ensure-spa-shell-stub.d.ts.map +1 -0
- package/dist/scripts/ensure-spa-shell-stub.js +30 -0
- package/dist/scripts/ensure-spa-shell-stub.js.map +1 -0
- package/dist/scripts/init-secrets.d.ts +63 -0
- package/dist/scripts/init-secrets.d.ts.map +1 -0
- package/dist/scripts/init-secrets.js +240 -0
- package/dist/scripts/init-secrets.js.map +1 -0
- package/dist/scripts/lib/cli-args.d.ts +78 -0
- package/dist/scripts/lib/cli-args.d.ts.map +1 -0
- package/dist/scripts/lib/cli-args.js +116 -0
- package/dist/scripts/lib/cli-args.js.map +1 -0
- package/dist/scripts/lib/github-actions.d.ts +26 -0
- package/dist/scripts/lib/github-actions.d.ts.map +1 -0
- package/dist/scripts/lib/github-actions.js +38 -0
- package/dist/scripts/lib/github-actions.js.map +1 -0
- package/dist/scripts/lib/wrangler-table.d.ts +46 -0
- package/dist/scripts/lib/wrangler-table.d.ts.map +1 -0
- package/dist/scripts/lib/wrangler-table.js +99 -0
- package/dist/scripts/lib/wrangler-table.js.map +1 -0
- package/dist/scripts/migrations-lock.d.ts +3 -0
- package/dist/scripts/migrations-lock.d.ts.map +1 -0
- package/dist/scripts/migrations-lock.js +46 -0
- package/dist/scripts/migrations-lock.js.map +1 -0
- package/dist/scripts/prepare-wrangler-config.d.ts +3 -0
- package/dist/scripts/prepare-wrangler-config.d.ts.map +1 -0
- package/dist/scripts/prepare-wrangler-config.js +50 -0
- package/dist/scripts/prepare-wrangler-config.js.map +1 -0
- package/dist/scripts/spa-shell.d.ts +41 -0
- package/dist/scripts/spa-shell.d.ts.map +1 -0
- package/dist/scripts/spa-shell.js +155 -0
- package/dist/scripts/spa-shell.js.map +1 -0
- package/dist/scripts/validate-locales.d.ts +26 -0
- package/dist/scripts/validate-locales.d.ts.map +1 -0
- package/dist/scripts/validate-locales.js +350 -0
- package/dist/scripts/validate-locales.js.map +1 -0
- package/dist/scripts/verify-migrations.d.ts +62 -0
- package/dist/scripts/verify-migrations.d.ts.map +1 -0
- package/dist/scripts/verify-migrations.js +302 -0
- package/dist/scripts/verify-migrations.js.map +1 -0
- package/dist/scripts/verify-spa-shell.d.ts +3 -0
- package/dist/scripts/verify-spa-shell.d.ts.map +1 -0
- package/dist/scripts/verify-spa-shell.js +53 -0
- package/dist/scripts/verify-spa-shell.js.map +1 -0
- package/dist/scripts/wrangler-config/cli.d.ts +22 -0
- package/dist/scripts/wrangler-config/cli.d.ts.map +1 -0
- package/dist/scripts/wrangler-config/cli.js +51 -0
- package/dist/scripts/wrangler-config/cli.js.map +1 -0
- package/dist/scripts/wrangler-config/patches.d.ts +51 -0
- package/dist/scripts/wrangler-config/patches.d.ts.map +1 -0
- package/dist/scripts/wrangler-config/patches.js +140 -0
- package/dist/scripts/wrangler-config/patches.js.map +1 -0
- package/dist/scripts/wrangler-config/resources.d.ts +70 -0
- package/dist/scripts/wrangler-config/resources.d.ts.map +1 -0
- package/dist/scripts/wrangler-config/resources.js +290 -0
- package/dist/scripts/wrangler-config/resources.js.map +1 -0
- package/dist/scripts/wrangler-config/types.d.ts +103 -0
- package/dist/scripts/wrangler-config/types.d.ts.map +1 -0
- package/dist/scripts/wrangler-config/types.js +49 -0
- package/dist/scripts/wrangler-config/types.js.map +1 -0
- package/dist/test/integration-migrations.d.ts +167 -0
- package/dist/test/integration-migrations.d.ts.map +1 -0
- package/dist/test/integration-migrations.js +171 -0
- package/dist/test/integration-migrations.js.map +1 -0
- package/dist/test/mocks/cloudflare-workers.d.ts +106 -0
- package/dist/test/mocks/cloudflare-workers.d.ts.map +1 -0
- package/dist/test/mocks/cloudflare-workers.js +90 -0
- package/dist/test/mocks/cloudflare-workers.js.map +1 -0
- package/dist/vite.d.ts +117 -0
- package/dist/vite.d.ts.map +1 -0
- package/dist/vite.js +125 -0
- package/dist/vite.js.map +1 -0
- package/dist/vitest-web.d.ts +73 -0
- package/dist/vitest-web.d.ts.map +1 -0
- package/dist/vitest-web.js +72 -0
- package/dist/vitest-web.js.map +1 -0
- package/dist/vitest.d.ts +92 -0
- package/dist/vitest.d.ts.map +1 -0
- package/dist/vitest.js +128 -0
- package/dist/vitest.js.map +1 -0
- package/package.json +58 -3
- package/src/eslint.test.ts +175 -0
- package/src/eslint.ts +640 -0
- package/src/functions/pages-proxy.test.ts +72 -0
- package/src/functions/pages-proxy.ts +187 -0
- package/src/github/actions/retry-step/action.yml +39 -0
- package/src/github/actions/setup-env/action.yml +20 -0
- package/src/github/dependabot.yml +30 -0
- package/src/github/workflows/backup-main.yml +46 -0
- package/src/github/workflows/continuous-deployment.yml +188 -0
- package/src/github/workflows/continuous-integration.yml +259 -0
- package/src/github/workflows/scheduled-version-update.yml +38 -0
- package/src/github/workflows/upstream-sync.yml +56 -0
- package/src/index.ts +42 -0
- package/src/scripts/backup/backup-rules.test.ts +105 -0
- package/src/scripts/backup/d1-target.ts +54 -0
- package/src/scripts/backup/destination-config.ts +92 -0
- package/src/scripts/backup/encrypt-backup.ts +107 -0
- package/src/scripts/backup/naming.ts +62 -0
- package/src/scripts/check-god-files.test.ts +131 -0
- package/src/scripts/check-god-files.ts +327 -0
- package/src/scripts/ensure-spa-shell-stub.ts +34 -0
- package/src/scripts/init-secrets.ts +265 -0
- package/src/scripts/lib/cli-args.ts +154 -0
- package/src/scripts/lib/github-actions.ts +41 -0
- package/src/scripts/lib/wrangler-table.ts +105 -0
- package/src/scripts/migrations-lock.ts +52 -0
- package/src/scripts/prepare-wrangler-config.ts +51 -0
- package/src/scripts/spa-shell.test.ts +91 -0
- package/src/scripts/spa-shell.ts +179 -0
- package/src/scripts/validate-locales.test.ts +89 -0
- package/src/scripts/validate-locales.ts +380 -0
- package/src/scripts/verify-migrations.test.ts +71 -0
- package/src/scripts/verify-migrations.ts +364 -0
- package/src/scripts/verify-spa-shell.ts +56 -0
- package/src/scripts/wrangler-config/cli.ts +51 -0
- package/src/scripts/wrangler-config/patches.ts +157 -0
- package/src/scripts/wrangler-config/resources.ts +330 -0
- package/src/scripts/wrangler-config/types.ts +113 -0
- package/src/test/integration-migrations.test.ts +169 -0
- package/src/test/integration-migrations.ts +267 -0
- package/src/test/mocks/cloudflare-workers.ts +115 -0
- package/src/vite.test.ts +83 -0
- package/src/vite.ts +202 -0
- package/src/vitest-web.ts +109 -0
- 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":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2DG;AACH,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAC/E,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAmCjC;;;;;;GAMG;AACH,MAAM,SAAS,GAAG,sBAAsB,CAAC;AAEzC,qFAAqF;AACrF,MAAM,YAAY,GAAG,2BAA2B,CAAC;AAEjD,+EAA+E;AAC/E,MAAM,cAAc,GAAG,qBAAqB,CAAC;AAE7C,8EAA8E;AAC9E,MAAM,YAAY,GAAG,CAAC,CAAC;AAEvB,SAAS,SAAS,CAAC,OAAe;IAChC,OAAO,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AACpE,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,UAAU,CAAC,KAAwB;IACjD,IAAI,OAA2B,CAAC;IAChC,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,IAAI,cAAc,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,KAAK,SAAS,IAAI,IAAI,GAAG,OAAO,CAAC,EAAE,CAAC;YAC3E,OAAO,GAAG,IAAI,CAAC;QACjB,CAAC;IACH,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,oFAAoF;AACpF,SAAS,UAAU,CAAC,IAAY,EAAE,QAA4B;IAC5D,OAAO,QAAQ,KAAK,SAAS,IAAI,IAAI,IAAI,QAAQ,CAAC;AACpD,CAAC;AAED;;;;;;GAMG;AACH,SAAS,iBAAiB,CAAC,aAAqB;IAC9C,MAAM,QAAQ,GAAuB,EAAE,CAAC;IACxC,IAAI,CAAC,UAAU,CAAC,aAAa,CAAC,EAAE,CAAC;QAC/B,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,GAAG,aAAa,iBAAiB,EAAE,CAAC,CAAC;QAC9D,OAAO,EAAE,KAAK,EAAE,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;IAC7C,CAAC;IAED,+EAA+E;IAC/E,8EAA8E;IAC9E,oDAAoD;IACpD,MAAM,KAAK,GAAG,WAAW,CAAC,aAAa,CAAC;SACrC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;SACvC,IAAI,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC,IAAI,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC,CAAC;IAEpD,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;YAC7B,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,GAAG,IAAI,qCAAqC,EAAE,CAAC,CAAC;QAC3E,CAAC;IACH,CAAC;IAED,MAAM,QAAQ,GAAG,UAAU,CAAC,KAAK,CAAC,CAAC;IACnC,MAAM,QAAQ,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;IAEnE,6EAA6E;IAC7E,2EAA2E;IAC3E,yEAAyE;IACzE,KAAK,IAAI,KAAK,GAAG,CAAC,EAAE,KAAK,GAAG,QAAQ,CAAC,MAAM,EAAE,KAAK,IAAI,CAAC,EAAE,CAAC;QACxD,MAAM,QAAQ,GAAG,QAAQ,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC;QACrC,MAAM,OAAO,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC;QAChC,IAAI,QAAQ,KAAK,SAAS,IAAI,OAAO,KAAK,SAAS,IAAI,OAAO,IAAI,QAAQ,EAAE,CAAC;YAC3E,QAAQ,CAAC,IAAI,CAAC;gBACZ,OAAO,EAAE,GAAG,KAAK,CAAC,KAAK,CAAC,IAAI,GAAG,uBAAuB,KAAK,CAAC,KAAK,GAAG,CAAC,CAAC,IAAI,GAAG,kEAAkE;aAChJ,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAED,gFAAgF;IAChF,yEAAyE;IACzE,uCAAuC;IACvC,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;QAC3B,KAAK,IAAI,KAAK,GAAG,CAAC,EAAE,KAAK,GAAG,QAAQ,CAAC,MAAM,EAAE,KAAK,IAAI,CAAC,EAAE,CAAC;YACxD,MAAM,QAAQ,GAAG,MAAM,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;YACpD,IAAI,QAAQ,CAAC,KAAK,CAAC,KAAK,QAAQ,EAAE,CAAC;gBACjC,QAAQ,CAAC,IAAI,CAAC;oBACZ,OAAO,EAAE,mDAAmD,QAAQ,iBAAiB,KAAK,CAAC,KAAK,CAAC,IAAI,GAAG,EAAE;iBAC3G,CAAC,CAAC;YACL,CAAC;QACH,CAAC;IACH,CAAC;IAED,MAAM,QAAQ,GAAG,IAAI,CAAC,aAAa,EAAE,SAAS,CAAC,CAAC;IAChD,IAAI,IAAI,GAA6B,IAAI,CAAC;IAC1C,IAAI,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;QACzB,IAAI,CAAC;YACH,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAsB,CAAC;QACzE,CAAC;QAAC,MAAM,CAAC;YACP,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,GAAG,SAAS,oBAAoB,EAAE,CAAC,CAAC;QAC/D,CAAC;IACH,CAAC;IAED,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;AACnC,CAAC;AAED;;;;;;;GAOG;AACH,SAAS,gBAAgB,CACvB,aAAqB,EACrB,KAAwB,EACxB,QAAkC,EAClC,QAA4B;IAE5B,MAAM,QAAQ,GAAG,CAAC,IAAY,EAAU,EAAE,CAAC,SAAS,CAAC,YAAY,CAAC,IAAI,CAAC,aAAa,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC;IAEtG,IAAI,QAAQ,KAAK,IAAI,EAAE,CAAC;QACtB,OAAO,EAAE,OAAO,EAAE,YAAY,EAAE,GAAG,CAAC,QAAQ,KAAK,SAAS,IAAI,EAAE,QAAQ,EAAE,CAAC,EAAE,UAAU,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC;IACrJ,CAAC;IAED,MAAM,IAAI,GAAG,IAAI,GAAG,EAAkB,CAAC;IACvC,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,MAAM,QAAQ,GAAG,QAAQ,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,KAAK,IAAI,CAAC,CAAC;QAC1E,IAAI,QAAQ,KAAK,SAAS;YAAE,SAAS;QACrC,+DAA+D;QAC/D,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;IACvE,CAAC;IACD,+EAA+E;IAC/E,0EAA0E;IAC1E,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,KAAK,IAAI,CAAC,EAAE,CAAC;YACjF,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC;QACjC,CAAC;IACH,CAAC;IACD,+EAA+E;IAC/E,4EAA4E;IAC5E,gFAAgF;IAChF,uBAAuB;IACvB,KAAK,MAAM,KAAK,IAAI,QAAQ,CAAC,UAAU,EAAE,CAAC;QACxC,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,IAAI,EAAE,QAAQ,CAAC,EAAE,CAAC;YACrE,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;QACrC,CAAC;IACH,CAAC;IAED,OAAO;QACL,OAAO,EAAE,YAAY;QACrB,GAAG,CAAC,QAAQ,KAAK,SAAS,IAAI,EAAE,QAAQ,EAAE,CAAC;QAC3C,UAAU,EAAE,CAAC,GAAG,IAAI,CAAC;aAClB,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,KAAK,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC;aACpD,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,MAAM,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC;KAC/C,CAAC;AACJ,CAAC;AAED,yEAAyE;AACzE,SAAS,gBAAgB,CAAC,aAAqB,EAAE,UAA+B,EAAE;IAChF,MAAM,MAAM,GAAG,iBAAiB,CAAC,aAAa,CAAC,CAAC;IAChD,MAAM,QAAQ,GAAG,UAAU,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IAE1C,2EAA2E;IAC3E,8CAA8C;IAC9C,IAAI,MAAM,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC/B,OAAO,MAAM,CAAC;IAChB,CAAC;IAED,IAAI,OAAO,CAAC,KAAK,KAAK,IAAI,EAAE,CAAC;QAC3B,MAAM,OAAO,GAAG,gBAAgB,CAAC,aAAa,EAAE,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;QACrF,MAAM,IAAI,GAAG,GAAG,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC;QACrD,MAAM,IAAI,GAAG,IAAI,CAAC,aAAa,EAAE,SAAS,CAAC,CAAC;QAC5C,MAAM,MAAM,GAAG,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;QACpE,IAAI,MAAM,KAAK,IAAI,EAAE,CAAC;YACpB,aAAa,CAAC,IAAI,EAAE,IAAI,EAAE,MAAM,CAAC,CAAC;QACpC,CAAC;QACD,MAAM,CAAC,IAAI,GAAG,OAAO,CAAC;QACtB,OAAO,MAAM,CAAC;IAChB,CAAC;IAED,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC;IACzB,IAAI,IAAI,KAAK,IAAI,EAAE,CAAC;QAClB,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,GAAG,SAAS,mCAAmC,EAAE,CAAC,CAAC;QACnF,OAAO,MAAM,CAAC;IAChB,CAAC;IAED,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;IACrF,KAAK,MAAM,IAAI,IAAI,MAAM,CAAC,KAAK,EAAE,CAAC;QAChC,MAAM,QAAQ,GAAG,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACpC,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;YAC3B,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,GAAG,IAAI,0CAA0C,EAAE,CAAC,CAAC;YACrF,SAAS;QACX,CAAC;QACD,6EAA6E;QAC7E,kCAAkC;QAClC,IAAI,UAAU,CAAC,IAAI,EAAE,QAAQ,CAAC;YAAE,SAAS;QACzC,MAAM,MAAM,GAAG,SAAS,CAAC,YAAY,CAAC,IAAI,CAAC,aAAa,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC;QAC1E,IAAI,MAAM,KAAK,QAAQ,EAAE,CAAC;YACxB,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC;gBACnB,OAAO,EAAE,GAAG,IAAI,oCAAoC,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,UAAU,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI;aAC3G,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAED,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,UAAU,EAAE,CAAC;QACpC,IAAI,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC;YAAE,SAAS;QAChD,IAAI,UAAU,CAAC,KAAK,CAAC,IAAI,EAAE,QAAQ,CAAC;YAAE,SAAS;QAC/C,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,GAAG,KAAK,CAAC,IAAI,sCAAsC,EAAE,CAAC,CAAC;IACzF,CAAC;IAED,8EAA8E;IAC9E,6EAA6E;IAC7E,sEAAsE;IACtE,MAAM,MAAM,GAAG,CAAC,GAAG,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC,IAAI,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;IAC5F,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;QACzB,KAAK,MAAM,IAAI,IAAI,MAAM,CAAC,KAAK,EAAE,CAAC;YAChC,IAAI,IAAI,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;gBACzC,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC;oBACnB,OAAO,EAAE,GAAG,IAAI,iBAAiB,MAAM,kHAAkH,MAAM,EAAE;iBAClK,CAAC,CAAC;YACL,CAAC;QACH,CAAC;IACH,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,4EAA4E;AAC5E,SAAS,mBAAmB,CAAC,OAA0B,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC;IAC1E,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,IAAI,YAAY,CAAC;IACtE,MAAM,KAAK,GAAG,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC;IACvC,MAAM,MAAM,GAAG,gBAAgB,CAAC,GAAG,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC;IAChD,IAAI,MAAM,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC/B,OAAO,CAAC,KAAK,CAAC,mCAAmC,GAAG,GAAG,CAAC,CAAC;QACzD,KAAK,MAAM,OAAO,IAAI,MAAM,CAAC,QAAQ;YAAE,OAAO,CAAC,KAAK,CAAC,OAAO,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC;QAC/E,OAAO,CAAC,CAAC;IACX,CAAC;IACD,IAAI,KAAK,EAAE,CAAC;QACV,OAAO,CAAC,GAAG,CAAC,2BAA2B,MAAM,CAAC,KAAK,CAAC,MAAM,UAAU,CAAC,CAAC;QACtE,OAAO,CAAC,CAAC;IACX,CAAC;IACD,OAAO,CAAC,GAAG,CAAC,gCAAgC,MAAM,CAAC,KAAK,CAAC,MAAM,wBAAwB,CAAC,CAAC;IACzF,OAAO,CAAC,CAAC;AACX,CAAC;AAED,OAAO,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,SAAS,EAAE,YAAY,EAAE,CAAC;AAG7F,6CAA6C;AAC7C,EAAE;AACF,iFAAiF;AACjF,oFAAoF;AACpF,kFAAkF;AAClF,oFAAoF;AACpF,uCAAuC;AACvC,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,SAAS,IAAI,MAAM,CAAC,IAAI,CAAC,GAAG,KAAK,aAAa,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;IAC7F,OAAO,CAAC,IAAI,CAAC,mBAAmB,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAC3D,CAAC"}
|
|
@@ -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"}
|