@onreza/sqlx-js 0.4.0 → 0.6.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/README.md +139 -48
- package/ROADMAP.md +1 -3
- package/dist/bin/sqlx-js-diagnostics.d.ts +2 -0
- package/dist/bin/sqlx-js-diagnostics.js +96 -0
- package/dist/bin/sqlx-js.js +337 -89
- package/dist/src/artifacts.d.ts +9 -0
- package/dist/src/artifacts.js +26 -0
- package/dist/src/cache.d.ts +17 -0
- package/dist/src/cache.js +83 -1
- package/dist/src/codegen.js +14 -2
- package/dist/src/commands/doctor.d.ts +16 -0
- package/dist/src/commands/doctor.js +196 -0
- package/dist/src/commands/init.js +93 -13
- package/dist/src/commands/migrate.d.ts +2 -101
- package/dist/src/commands/migrate.js +11 -566
- package/dist/src/commands/pgschema.d.ts +6 -0
- package/dist/src/commands/pgschema.js +30 -0
- package/dist/src/commands/prepare.d.ts +41 -6
- package/dist/src/commands/prepare.js +440 -63
- package/dist/src/commands/schema.js +1 -1
- package/dist/src/commands/watch.d.ts +1 -0
- package/dist/src/commands/watch.js +23 -9
- package/dist/src/config.d.ts +22 -0
- package/dist/src/config.js +149 -11
- package/dist/src/index.d.ts +4 -2
- package/dist/src/index.js +2 -1
- package/dist/src/migration-core.d.ts +157 -0
- package/dist/src/migration-core.js +578 -0
- package/dist/src/postgres-runtime.d.ts +3 -0
- package/dist/src/postgres-runtime.js +66 -29
- package/dist/src/runtime.d.ts +36 -3
- package/dist/src/runtime.js +92 -23
- package/dist/src/scan/scanner.d.ts +10 -3
- package/dist/src/scan/scanner.js +83 -32
- package/dist/src/type-inspection.d.ts +1 -0
- package/dist/src/type-inspection.js +20 -0
- package/dist/src/typed.d.ts +10 -0
- package/package.json +11 -6
package/dist/bin/sqlx-js.js
CHANGED
|
@@ -2,13 +2,8 @@
|
|
|
2
2
|
import { existsSync, readFileSync } from "node:fs";
|
|
3
3
|
import { dirname, join, resolve } from "node:path";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import { migrateArchiveList, migrateArchiveRestore, migrateCheck, migrateDev, migrateRun, migrateInfo, migrateRevert, migrateAdd, migrateSquash, migrateVerify, } from "../src/commands/migrate.js";
|
|
8
|
-
import { applyShadowMigrations, runSchemaCheck, runSchemaDump } from "../src/commands/schema.js";
|
|
9
|
-
import { runInit } from "../src/commands/init.js";
|
|
10
|
-
import { runPgschemaCommand, runPgschemaInstall } from "../src/commands/pgschema.js";
|
|
11
|
-
import { loadConfig } from "../src/config.js";
|
|
5
|
+
import { parseArgs } from "node:util";
|
|
6
|
+
import { assertSupportedRuntime, loadConfig, loadRootEnv } from "../src/config.js";
|
|
12
7
|
function packageVersion() {
|
|
13
8
|
const here = dirname(fileURLToPath(import.meta.url));
|
|
14
9
|
for (const path of [join(here, "../package.json"), join(here, "../../package.json")]) {
|
|
@@ -21,16 +16,20 @@ function packageVersion() {
|
|
|
21
16
|
throw new Error("sqlx-js: cannot locate package.json for version");
|
|
22
17
|
}
|
|
23
18
|
const VERSION = packageVersion();
|
|
24
|
-
|
|
25
|
-
|
|
19
|
+
const HELP = {
|
|
20
|
+
root: `sqlx-js — compile-time-checked SQL for TypeScript + Postgres (v${VERSION})
|
|
26
21
|
|
|
27
22
|
usage:
|
|
28
23
|
sqlx-js init [--root <dir>] [--schema-provider builtin|pgschema]
|
|
29
|
-
sqlx-js
|
|
30
|
-
sqlx-js
|
|
31
|
-
sqlx-js
|
|
32
|
-
sqlx-js
|
|
24
|
+
sqlx-js doctor [--root <dir>] [--dts <path>] [--json]
|
|
25
|
+
sqlx-js db install | check [--root <dir>]
|
|
26
|
+
sqlx-js db plan | apply [--root <dir>] [-- <pgschema args>]
|
|
27
|
+
sqlx-js prepare [--check | --verify | --watch] [--json] [--strict-inference] [--root <dir>] [--dts <path>] [--no-prune] [--shadow-url <url>]
|
|
28
|
+
sqlx-js migrate dev [--shadow-admin-url <url> | --shadow-url <url>] [--lock-timeout <ms>] [--strict-inference] | verify [--shadow-admin-url <url> | --shadow-url <url>] [--lock-timeout <ms>] [--strict-inference] | run [--dry-run] [--json] [--lock-timeout <ms>] | info [--json] | check [--json] | revert [--dry-run] [--json] [--shadow-admin-url <url> | --shadow-url <url>] [--lock-timeout <ms>] | add <name> | squash <name> [--shadow-admin-url <url> | --shadow-url <url>] [--replace] [--pg-dump <path>] [--lock-timeout <ms>] | archive list | archive restore <name> [--force]
|
|
29
|
+
sqlx-js schema dump [--schema <path>] [--manifest <path>] [--no-manifest] [--shadow-url <url>]
|
|
30
|
+
sqlx-js schema check [--schema <path>] [--shadow-url <url>]
|
|
33
31
|
sqlx-js --version
|
|
32
|
+
sqlx-js-diagnostics github|unix < prepare-diagnostics.json
|
|
34
33
|
|
|
35
34
|
env:
|
|
36
35
|
DATABASE_URL=postgres://... (supports sslmode, cert paths, application_name, connect_timeout, statement_timeout)
|
|
@@ -41,11 +40,13 @@ flags:
|
|
|
41
40
|
--root <dir> scan root (default: cwd)
|
|
42
41
|
--dts <path> declarations output (default: <root>/sqlx-js-env.d.ts)
|
|
43
42
|
--check offline mode: verify scanned queries exist in cache, no DB
|
|
43
|
+
--verify prepare against DB/shadow and compare committed generated artifacts
|
|
44
44
|
--watch re-prepare on file change (persistent PG connection)
|
|
45
45
|
--no-prune keep orphaned cache entries (default: remove)
|
|
46
46
|
--migrations <dir> migrations directory (default: <root>/migrations)
|
|
47
47
|
--dry-run validate and print migrate run/revert plan without applying migrations
|
|
48
|
-
--json machine-readable output for
|
|
48
|
+
--json machine-readable output for prepare and migration inspection/dry-run commands
|
|
49
|
+
--strict-inference fail when query inference degrades or emits unknown types
|
|
49
50
|
--force allow archive restore to overwrite existing migration files
|
|
50
51
|
--lock-timeout <ms> advisory-lock acquisition timeout for migrate run/revert/dev/verify/squash
|
|
51
52
|
--shadow-url <url> use an existing disposable shadow DB instead of auto-creating one
|
|
@@ -56,52 +57,250 @@ flags:
|
|
|
56
57
|
--manifest <path> LLM schema manifest path (default: <root>/.sqlx-js/schema/schema.md)
|
|
57
58
|
--no-manifest skip writing the LLM schema manifest during schema dump
|
|
58
59
|
--schema-provider <name> init schema workflow: builtin (default) or pgschema
|
|
59
|
-
|
|
60
|
-
|
|
60
|
+
`,
|
|
61
|
+
init: `usage: sqlx-js init [--root <dir>] [--schema-provider builtin|pgschema]`,
|
|
62
|
+
doctor: `usage: sqlx-js doctor [--root <dir>] [--dts <path>] [--json]`,
|
|
63
|
+
db: `usage: sqlx-js db install | check [--root <dir>] | plan | apply [--root <dir>] [-- <pgschema args>]`,
|
|
64
|
+
prepare: `usage: sqlx-js prepare [--check | --verify | --watch] [--json] [--strict-inference] [--root <dir>] [--dts <path>] [--no-prune] [--shadow-url <url>]`,
|
|
65
|
+
migrate: `usage: sqlx-js migrate dev [--shadow-admin-url <url> | --shadow-url <url>] [--lock-timeout <ms>] [--strict-inference] | verify [--shadow-admin-url <url> | --shadow-url <url>] [--lock-timeout <ms>] [--strict-inference] | run [--dry-run] [--json] [--lock-timeout <ms>] | info [--json] | check [--json] | revert [--dry-run] [--json] [--shadow-admin-url <url> | --shadow-url <url>] [--lock-timeout <ms>] | add <name> | squash <name> [--shadow-admin-url <url> | --shadow-url <url>] [--replace] [--pg-dump <path>] [--lock-timeout <ms>] | archive list | archive restore <name> [--force]`,
|
|
66
|
+
schema: `usage: sqlx-js schema dump [--schema <path>] [--manifest <path>] [--no-manifest] [--shadow-url <url>] | check [--schema <path>] [--shadow-url <url>]`,
|
|
67
|
+
};
|
|
68
|
+
function printHelp(scope, error = false) {
|
|
69
|
+
(error ? console.error : console.log)(HELP[scope]);
|
|
61
70
|
}
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
function arg(name, def) {
|
|
66
|
-
const eq = `${name}=`;
|
|
67
|
-
for (let i = 0; i < cliArgv.length; i++) {
|
|
68
|
-
const a = cliArgv[i];
|
|
69
|
-
if (a === name)
|
|
70
|
-
return cliArgv[i + 1] ?? def;
|
|
71
|
-
if (a.startsWith(eq))
|
|
72
|
-
return a.slice(eq.length);
|
|
73
|
-
}
|
|
74
|
-
return def;
|
|
71
|
+
function exitHelp(scope) {
|
|
72
|
+
printHelp(scope);
|
|
73
|
+
process.exit(0);
|
|
75
74
|
}
|
|
76
|
-
function
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
}
|
|
81
|
-
return false;
|
|
75
|
+
function usageError(message, scope = "root") {
|
|
76
|
+
console.error(`sqlx-js: ${message}`);
|
|
77
|
+
printHelp(scope, true);
|
|
78
|
+
process.exit(2);
|
|
82
79
|
}
|
|
83
|
-
const
|
|
80
|
+
const rawArgv = process.argv.slice(2);
|
|
81
|
+
const passthroughIndex = rawArgv.indexOf("--");
|
|
82
|
+
const cliArgv = passthroughIndex >= 0 ? rawArgv.slice(0, passthroughIndex) : rawArgv;
|
|
83
|
+
const passthroughArgs = passthroughIndex >= 0 ? rawArgv.slice(passthroughIndex + 1) : [];
|
|
84
|
+
const cmd = cliArgv[0];
|
|
85
|
+
const scopes = new Set(["init", "doctor", "db", "prepare", "migrate", "schema"]);
|
|
84
86
|
if (cmd === "--version" || cmd === "-v") {
|
|
85
87
|
console.log(VERSION);
|
|
86
88
|
process.exit(0);
|
|
87
89
|
}
|
|
88
|
-
if (cmd === "--help" || cmd === "-h"
|
|
89
|
-
|
|
90
|
+
if (!cmd || cmd === "--help" || cmd === "-h")
|
|
91
|
+
exitHelp("root");
|
|
92
|
+
if (!scopes.has(cmd))
|
|
93
|
+
usageError(`unknown command ${JSON.stringify(cmd)}`);
|
|
94
|
+
const scope = cmd;
|
|
95
|
+
if (cliArgv.includes("--help") || cliArgv.includes("-h"))
|
|
96
|
+
exitHelp(scope);
|
|
97
|
+
if (passthroughIndex >= 0 && cmd !== "db")
|
|
98
|
+
usageError("arguments after -- are only supported by sqlx-js db", scope);
|
|
99
|
+
const ROOT_OPTIONS = {
|
|
100
|
+
root: { type: "string" },
|
|
101
|
+
help: { type: "boolean", short: "h" },
|
|
102
|
+
};
|
|
103
|
+
function optionsFor(command, subcommand) {
|
|
104
|
+
if (command === "init")
|
|
105
|
+
return { ...ROOT_OPTIONS, "schema-provider": { type: "string" } };
|
|
106
|
+
if (command === "doctor")
|
|
107
|
+
return { ...ROOT_OPTIONS, dts: { type: "string" }, json: { type: "boolean" } };
|
|
108
|
+
if (command === "db")
|
|
109
|
+
return ROOT_OPTIONS;
|
|
110
|
+
if (command === "prepare") {
|
|
111
|
+
return {
|
|
112
|
+
...ROOT_OPTIONS,
|
|
113
|
+
dts: { type: "string" },
|
|
114
|
+
check: { type: "boolean" },
|
|
115
|
+
verify: { type: "boolean" },
|
|
116
|
+
watch: { type: "boolean" },
|
|
117
|
+
json: { type: "boolean" },
|
|
118
|
+
"no-prune": { type: "boolean" },
|
|
119
|
+
"shadow-url": { type: "string" },
|
|
120
|
+
migrations: { type: "string" },
|
|
121
|
+
"strict-inference": { type: "boolean" },
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
if (command === "schema") {
|
|
125
|
+
const common = {
|
|
126
|
+
...ROOT_OPTIONS,
|
|
127
|
+
schema: { type: "string" },
|
|
128
|
+
"shadow-url": { type: "string" },
|
|
129
|
+
migrations: { type: "string" },
|
|
130
|
+
};
|
|
131
|
+
return subcommand === "dump"
|
|
132
|
+
? { ...common, manifest: { type: "string" }, "no-manifest": { type: "boolean" } }
|
|
133
|
+
: common;
|
|
134
|
+
}
|
|
135
|
+
const common = { ...ROOT_OPTIONS, migrations: { type: "string" } };
|
|
136
|
+
if (subcommand === "run") {
|
|
137
|
+
return { ...common, "dry-run": { type: "boolean" }, json: { type: "boolean" }, "lock-timeout": { type: "string" } };
|
|
138
|
+
}
|
|
139
|
+
if (subcommand === "info" || subcommand === "check")
|
|
140
|
+
return { ...common, json: { type: "boolean" } };
|
|
141
|
+
if (subcommand === "dev") {
|
|
142
|
+
return {
|
|
143
|
+
...common,
|
|
144
|
+
"shadow-admin-url": { type: "string" },
|
|
145
|
+
"shadow-url": { type: "string" },
|
|
146
|
+
"lock-timeout": { type: "string" },
|
|
147
|
+
"no-prune": { type: "boolean" },
|
|
148
|
+
"strict-inference": { type: "boolean" },
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
if (subcommand === "verify") {
|
|
152
|
+
return {
|
|
153
|
+
...common,
|
|
154
|
+
"shadow-admin-url": { type: "string" },
|
|
155
|
+
"shadow-url": { type: "string" },
|
|
156
|
+
"lock-timeout": { type: "string" },
|
|
157
|
+
"strict-inference": { type: "boolean" },
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
if (subcommand === "revert") {
|
|
161
|
+
return {
|
|
162
|
+
...common,
|
|
163
|
+
"dry-run": { type: "boolean" },
|
|
164
|
+
json: { type: "boolean" },
|
|
165
|
+
"shadow-admin-url": { type: "string" },
|
|
166
|
+
"shadow-url": { type: "string" },
|
|
167
|
+
"lock-timeout": { type: "string" },
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
if (subcommand === "squash") {
|
|
171
|
+
return {
|
|
172
|
+
...common,
|
|
173
|
+
"shadow-admin-url": { type: "string" },
|
|
174
|
+
"shadow-url": { type: "string" },
|
|
175
|
+
"lock-timeout": { type: "string" },
|
|
176
|
+
replace: { type: "boolean" },
|
|
177
|
+
"pg-dump": { type: "string" },
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
if (subcommand === "archive")
|
|
181
|
+
return { ...common, force: { type: "boolean" } };
|
|
182
|
+
return common;
|
|
183
|
+
}
|
|
184
|
+
const commandArgv = cliArgv.slice(1);
|
|
185
|
+
const subcommand = commandArgv[0]?.startsWith("-") ? undefined : commandArgv[0];
|
|
186
|
+
let parsed;
|
|
187
|
+
try {
|
|
188
|
+
parsed = parseArgs({
|
|
189
|
+
args: commandArgv,
|
|
190
|
+
options: optionsFor(cmd, subcommand),
|
|
191
|
+
strict: true,
|
|
192
|
+
allowPositionals: true,
|
|
193
|
+
});
|
|
90
194
|
}
|
|
195
|
+
catch (error) {
|
|
196
|
+
usageError(error.message, scope);
|
|
197
|
+
}
|
|
198
|
+
const values = parsed.values;
|
|
199
|
+
const positionals = parsed.positionals;
|
|
200
|
+
function arg(name, def) {
|
|
201
|
+
const value = values[name.replace(/^--/, "")];
|
|
202
|
+
return typeof value === "string" ? value : def;
|
|
203
|
+
}
|
|
204
|
+
function flag(name) {
|
|
205
|
+
return values[name.replace(/^--/, "")] === true;
|
|
206
|
+
}
|
|
207
|
+
function requirePositionals(min, max, label) {
|
|
208
|
+
if (positionals.length < min || positionals.length > max) {
|
|
209
|
+
usageError(`${label}: expected ${min === max ? min : `${min} to ${max}`} positional argument(s)`, scope);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
function validateInvocation() {
|
|
213
|
+
if (cmd === "init" || cmd === "doctor" || cmd === "prepare") {
|
|
214
|
+
requirePositionals(0, 0, cmd);
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
if (cmd === "db") {
|
|
218
|
+
requirePositionals(1, 1, "db");
|
|
219
|
+
const sub = positionals[0];
|
|
220
|
+
if (sub !== "install" && sub !== "check" && sub !== "plan" && sub !== "apply") {
|
|
221
|
+
usageError(`unknown db command ${JSON.stringify(sub)}`, "db");
|
|
222
|
+
}
|
|
223
|
+
if (passthroughArgs.length > 0 && sub !== "plan" && sub !== "apply") {
|
|
224
|
+
usageError(`db ${sub} does not accept arguments after --`, "db");
|
|
225
|
+
}
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
if (cmd === "schema") {
|
|
229
|
+
requirePositionals(1, 1, "schema");
|
|
230
|
+
const sub = positionals[0];
|
|
231
|
+
if (sub !== "dump" && sub !== "check")
|
|
232
|
+
usageError(`unknown schema command ${JSON.stringify(sub)}`, "schema");
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
const sub = positionals[0];
|
|
236
|
+
if (!sub)
|
|
237
|
+
usageError("migrate command is required", "migrate");
|
|
238
|
+
if (["dev", "verify", "run", "info", "check", "revert"].includes(sub)) {
|
|
239
|
+
requirePositionals(1, 1, `migrate ${sub}`);
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
if (sub === "add" || sub === "squash") {
|
|
243
|
+
requirePositionals(2, 2, `migrate ${sub}`);
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
if (sub === "archive") {
|
|
247
|
+
const action = positionals[1];
|
|
248
|
+
if (action === "list") {
|
|
249
|
+
requirePositionals(2, 2, "migrate archive list");
|
|
250
|
+
if (flag("--force"))
|
|
251
|
+
usageError("--force is only supported by migrate archive restore", "migrate");
|
|
252
|
+
}
|
|
253
|
+
else if (action === "restore")
|
|
254
|
+
requirePositionals(3, 3, "migrate archive restore");
|
|
255
|
+
else
|
|
256
|
+
usageError(`unknown migrate archive command ${JSON.stringify(action)}`, "migrate");
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
usageError(`unknown migrate command ${JSON.stringify(sub)}`, "migrate");
|
|
260
|
+
}
|
|
261
|
+
validateInvocation();
|
|
91
262
|
const root = resolve(arg("--root", process.cwd()));
|
|
263
|
+
if (cmd !== "doctor") {
|
|
264
|
+
try {
|
|
265
|
+
assertSupportedRuntime();
|
|
266
|
+
}
|
|
267
|
+
catch (e) {
|
|
268
|
+
console.error(e.message);
|
|
269
|
+
process.exit(2);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
let envError;
|
|
273
|
+
const needsEnvironment = cmd === "doctor" ||
|
|
274
|
+
cmd === "schema" ||
|
|
275
|
+
(cmd === "db" && (positionals[0] === "plan" || positionals[0] === "apply")) ||
|
|
276
|
+
(cmd === "prepare" && !flag("--check")) ||
|
|
277
|
+
(cmd === "migrate" && !["add", "check", "archive"].includes(positionals[0]));
|
|
278
|
+
if (needsEnvironment) {
|
|
279
|
+
try {
|
|
280
|
+
loadRootEnv(root);
|
|
281
|
+
}
|
|
282
|
+
catch (e) {
|
|
283
|
+
envError = e.message;
|
|
284
|
+
if (cmd !== "doctor") {
|
|
285
|
+
console.error(envError);
|
|
286
|
+
process.exit(2);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
}
|
|
92
290
|
const databaseUrl = process.env.DATABASE_URL ?? "";
|
|
93
291
|
const shadowUrlArg = arg("--shadow-url");
|
|
94
292
|
const shadowUrl = shadowUrlArg ?? process.env.SHADOW_DATABASE_URL;
|
|
95
293
|
const shadowAdminUrl = arg("--shadow-admin-url") ?? process.env.SHADOW_ADMIN_DATABASE_URL;
|
|
96
294
|
const cacheDir = join(root, ".sqlx-js");
|
|
97
295
|
const dtsArg = arg("--dts");
|
|
98
|
-
const dtsPath = dtsArg ? resolve(dtsArg) : join(root, "sqlx-js-env.d.ts");
|
|
296
|
+
const dtsPath = dtsArg ? resolve(root, dtsArg) : join(root, "sqlx-js-env.d.ts");
|
|
99
297
|
const migrationsDir = join(root, arg("--migrations", "migrations"));
|
|
100
298
|
const schemaArg = arg("--schema");
|
|
101
|
-
const schemaPath = schemaArg ? resolve(schemaArg) : join(root, ".sqlx-js/schema/schema.json");
|
|
299
|
+
const schemaPath = schemaArg ? resolve(root, schemaArg) : join(root, ".sqlx-js/schema/schema.json");
|
|
102
300
|
const manifestArg = arg("--manifest");
|
|
103
|
-
const manifestPath = manifestArg ? resolve(manifestArg) : join(root, ".sqlx-js/schema/schema.md");
|
|
301
|
+
const manifestPath = manifestArg ? resolve(root, manifestArg) : join(root, ".sqlx-js/schema/schema.md");
|
|
104
302
|
if (cmd === "init") {
|
|
303
|
+
const { runInit } = await import("../src/commands/init.js");
|
|
105
304
|
const provider = arg("--schema-provider", "builtin");
|
|
106
305
|
if (provider !== "builtin" && provider !== "pgschema") {
|
|
107
306
|
console.error("--schema-provider must be builtin or pgschema");
|
|
@@ -109,8 +308,13 @@ if (cmd === "init") {
|
|
|
109
308
|
}
|
|
110
309
|
runInit({ root, schemaProvider: provider });
|
|
111
310
|
}
|
|
311
|
+
else if (cmd === "doctor") {
|
|
312
|
+
const { runDoctor } = await import("../src/commands/doctor.js");
|
|
313
|
+
await runDoctor({ root, databaseUrl, cacheDir, dtsPath, json: flag("--json"), envError });
|
|
314
|
+
}
|
|
112
315
|
else if (cmd === "db") {
|
|
113
|
-
const
|
|
316
|
+
const { runPgschemaCommand, runPgschemaInstall } = await import("../src/commands/pgschema.js");
|
|
317
|
+
const sub = positionals[0];
|
|
114
318
|
if (sub === "install") {
|
|
115
319
|
try {
|
|
116
320
|
await runPgschemaInstall({ root });
|
|
@@ -121,8 +325,6 @@ else if (cmd === "db") {
|
|
|
121
325
|
}
|
|
122
326
|
}
|
|
123
327
|
else {
|
|
124
|
-
if (sub !== "check" && sub !== "plan" && sub !== "apply")
|
|
125
|
-
help();
|
|
126
328
|
try {
|
|
127
329
|
runPgschemaCommand({
|
|
128
330
|
root,
|
|
@@ -139,29 +341,64 @@ else if (cmd === "db") {
|
|
|
139
341
|
}
|
|
140
342
|
}
|
|
141
343
|
else if (cmd === "prepare") {
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
344
|
+
const { PrepareFatalError, runPrepare } = await import("../src/commands/prepare.js");
|
|
345
|
+
const prepareCheck = flag("--check");
|
|
346
|
+
const prepareVerify = flag("--verify");
|
|
347
|
+
const prepareWatch = flag("--watch");
|
|
348
|
+
const prepareJson = flag("--json");
|
|
349
|
+
const prepareMode = prepareVerify ? "verify" : prepareCheck ? "check" : "prepare";
|
|
350
|
+
const failPrepare = (message, phase, exitCode = 2, location = {}) => {
|
|
351
|
+
if (prepareJson) {
|
|
352
|
+
console.log(JSON.stringify({
|
|
353
|
+
formatVersion: 1,
|
|
354
|
+
ok: false,
|
|
355
|
+
mode: prepareMode,
|
|
356
|
+
sites: 0,
|
|
357
|
+
entries: 0,
|
|
358
|
+
failures: 1,
|
|
359
|
+
pruned: 0,
|
|
360
|
+
functions: 0,
|
|
361
|
+
diagnostics: [{ severity: "error", phase, message, ...location }],
|
|
362
|
+
}, null, 2));
|
|
363
|
+
}
|
|
364
|
+
else {
|
|
365
|
+
console.error(message);
|
|
366
|
+
}
|
|
367
|
+
process.exit(exitCode);
|
|
368
|
+
};
|
|
369
|
+
if ([prepareCheck, prepareVerify, prepareWatch].filter(Boolean).length > 1) {
|
|
370
|
+
failPrepare("--check, --verify, and --watch are mutually exclusive", "config");
|
|
371
|
+
}
|
|
372
|
+
if (prepareCheck && shadowUrlArg) {
|
|
373
|
+
failPrepare("--shadow-url cannot be used with prepare --check; use live prepare or schema check --shadow-url", "config");
|
|
374
|
+
}
|
|
375
|
+
if (prepareWatch && prepareJson) {
|
|
376
|
+
failPrepare("--watch and --json are mutually exclusive", "config");
|
|
377
|
+
}
|
|
378
|
+
if ((prepareCheck || prepareVerify) && flag("--no-prune")) {
|
|
379
|
+
failPrepare("--no-prune is only supported by prepare and prepare --watch", "config");
|
|
145
380
|
}
|
|
146
|
-
const prepareShadowUrl =
|
|
381
|
+
const prepareShadowUrl = prepareCheck ? undefined : shadowUrl;
|
|
147
382
|
const prepareDatabaseUrl = prepareShadowUrl ?? databaseUrl;
|
|
148
|
-
if (!
|
|
149
|
-
|
|
150
|
-
process.exit(2);
|
|
383
|
+
if (!prepareCheck && !prepareDatabaseUrl) {
|
|
384
|
+
failPrepare("DATABASE_URL is required for prepare (use --check for offline)", "connect");
|
|
151
385
|
}
|
|
152
386
|
const opts = {
|
|
153
387
|
root,
|
|
154
388
|
databaseUrl: prepareDatabaseUrl,
|
|
155
389
|
cacheDir,
|
|
156
390
|
dtsPath,
|
|
157
|
-
check:
|
|
391
|
+
check: prepareCheck,
|
|
392
|
+
verify: prepareVerify,
|
|
393
|
+
json: prepareJson,
|
|
158
394
|
prune: !flag("--no-prune"),
|
|
395
|
+
strictInference: flag("--strict-inference"),
|
|
159
396
|
};
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
}
|
|
397
|
+
const applyShadowMigrations = prepareShadowUrl
|
|
398
|
+
? (await import("../src/commands/schema.js")).applyShadowMigrations
|
|
399
|
+
: undefined;
|
|
400
|
+
if (prepareWatch) {
|
|
401
|
+
const { runWatch } = await import("../src/commands/watch.js");
|
|
165
402
|
await runWatch({
|
|
166
403
|
...opts,
|
|
167
404
|
...(prepareShadowUrl
|
|
@@ -175,13 +412,33 @@ else if (cmd === "prepare") {
|
|
|
175
412
|
});
|
|
176
413
|
}
|
|
177
414
|
else {
|
|
178
|
-
if (prepareShadowUrl)
|
|
179
|
-
|
|
180
|
-
|
|
415
|
+
if (prepareShadowUrl) {
|
|
416
|
+
try {
|
|
417
|
+
await applyShadowMigrations(prepareShadowUrl, migrationsDir, prepareJson ? () => { } : console.log);
|
|
418
|
+
}
|
|
419
|
+
catch (e) {
|
|
420
|
+
failPrepare(e.message, "shadow", 1);
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
try {
|
|
424
|
+
await runPrepare(opts);
|
|
425
|
+
}
|
|
426
|
+
catch (e) {
|
|
427
|
+
const message = e.message;
|
|
428
|
+
const phase = e instanceof PrepareFatalError
|
|
429
|
+
? e.phase
|
|
430
|
+
: prepareVerify
|
|
431
|
+
? "verify"
|
|
432
|
+
: "scan";
|
|
433
|
+
failPrepare(message, phase, 1, e instanceof PrepareFatalError
|
|
434
|
+
? { file: e.file, line: e.line, column: e.column }
|
|
435
|
+
: {});
|
|
436
|
+
}
|
|
181
437
|
}
|
|
182
438
|
}
|
|
183
439
|
else if (cmd === "schema") {
|
|
184
|
-
const
|
|
440
|
+
const { runSchemaCheck, runSchemaDump } = await import("../src/commands/schema.js");
|
|
441
|
+
const sub = positionals[0];
|
|
185
442
|
const schemaDatabaseUrl = shadowUrl ?? databaseUrl;
|
|
186
443
|
if (!schemaDatabaseUrl) {
|
|
187
444
|
console.error("DATABASE_URL is required for schema commands (or pass --shadow-url)");
|
|
@@ -199,19 +456,21 @@ else if (cmd === "schema") {
|
|
|
199
456
|
await runSchemaDump(opts);
|
|
200
457
|
else if (sub === "check")
|
|
201
458
|
await runSchemaCheck(opts);
|
|
202
|
-
else
|
|
203
|
-
help();
|
|
204
459
|
}
|
|
205
460
|
else if (cmd === "migrate") {
|
|
206
|
-
const
|
|
461
|
+
const { migrateArchiveList, migrateArchiveRestore, migrateCheck, migrateDev, migrateRun, migrateInfo, migrateRevert, migrateAdd, migrateSquash, migrateVerify, } = await import("../src/commands/migrate.js");
|
|
462
|
+
const sub = positionals[0];
|
|
207
463
|
const revertDryRun = sub === "revert" && flag("--dry-run");
|
|
208
464
|
const workflowShadowOnly = ((sub === "dev" || sub === "verify" || sub === "squash") && !!shadowUrl) || (revertDryRun && !!shadowUrl);
|
|
209
|
-
if (!databaseUrl && sub !== "add" && sub !== "check" && sub !== "
|
|
465
|
+
if (!databaseUrl && sub !== "add" && sub !== "check" && sub !== "archive" && !workflowShadowOnly) {
|
|
210
466
|
console.error("DATABASE_URL is required");
|
|
211
467
|
process.exit(2);
|
|
212
468
|
}
|
|
213
469
|
const tRaw = arg("--lock-timeout");
|
|
214
470
|
const lockTimeoutMs = tRaw ? Number(tRaw) : undefined;
|
|
471
|
+
if (lockTimeoutMs !== undefined && !Number.isFinite(lockTimeoutMs)) {
|
|
472
|
+
usageError("--lock-timeout must be a finite number of milliseconds", "migrate");
|
|
473
|
+
}
|
|
215
474
|
if (sub === "dev") {
|
|
216
475
|
await migrateDev({
|
|
217
476
|
root,
|
|
@@ -223,6 +482,7 @@ else if (cmd === "migrate") {
|
|
|
223
482
|
shadowUrl,
|
|
224
483
|
shadowAdminUrl,
|
|
225
484
|
lockTimeoutMs,
|
|
485
|
+
strictInference: flag("--strict-inference"),
|
|
226
486
|
});
|
|
227
487
|
}
|
|
228
488
|
else if (sub === "verify") {
|
|
@@ -235,15 +495,18 @@ else if (cmd === "migrate") {
|
|
|
235
495
|
shadowUrl,
|
|
236
496
|
shadowAdminUrl,
|
|
237
497
|
lockTimeoutMs,
|
|
498
|
+
strictInference: flag("--strict-inference"),
|
|
238
499
|
});
|
|
239
500
|
}
|
|
240
501
|
else if (sub === "run") {
|
|
241
502
|
await migrateRun({ databaseUrl, migrationsDir, lockTimeoutMs, dryRun: flag("--dry-run"), json: flag("--json") });
|
|
242
503
|
}
|
|
243
|
-
else if (sub === "info")
|
|
504
|
+
else if (sub === "info") {
|
|
244
505
|
await migrateInfo({ databaseUrl, migrationsDir, json: flag("--json") });
|
|
245
|
-
|
|
506
|
+
}
|
|
507
|
+
else if (sub === "check") {
|
|
246
508
|
migrateCheck({ migrationsDir, json: flag("--json") });
|
|
509
|
+
}
|
|
247
510
|
else if (sub === "revert") {
|
|
248
511
|
await migrateRevert({
|
|
249
512
|
databaseUrl,
|
|
@@ -256,19 +519,11 @@ else if (cmd === "migrate") {
|
|
|
256
519
|
});
|
|
257
520
|
}
|
|
258
521
|
else if (sub === "add") {
|
|
259
|
-
const name =
|
|
260
|
-
if (!name) {
|
|
261
|
-
console.error("migrate add: name required");
|
|
262
|
-
process.exit(2);
|
|
263
|
-
}
|
|
522
|
+
const name = positionals[1];
|
|
264
523
|
migrateAdd({ databaseUrl, migrationsDir, name });
|
|
265
524
|
}
|
|
266
525
|
else if (sub === "squash") {
|
|
267
|
-
const name =
|
|
268
|
-
if (!name) {
|
|
269
|
-
console.error("migrate squash: name required");
|
|
270
|
-
process.exit(2);
|
|
271
|
-
}
|
|
526
|
+
const name = positionals[1];
|
|
272
527
|
await migrateSquash({
|
|
273
528
|
databaseUrl,
|
|
274
529
|
migrationsDir,
|
|
@@ -281,23 +536,16 @@ else if (cmd === "migrate") {
|
|
|
281
536
|
});
|
|
282
537
|
}
|
|
283
538
|
else if (sub === "archive") {
|
|
284
|
-
const action =
|
|
285
|
-
if (action === "list")
|
|
539
|
+
const action = positionals[1];
|
|
540
|
+
if (action === "list") {
|
|
286
541
|
migrateArchiveList({ migrationsDir });
|
|
542
|
+
}
|
|
287
543
|
else if (action === "restore") {
|
|
288
|
-
const name =
|
|
289
|
-
if (!name) {
|
|
290
|
-
console.error("migrate archive restore: name required");
|
|
291
|
-
process.exit(2);
|
|
292
|
-
}
|
|
544
|
+
const name = positionals[2];
|
|
293
545
|
migrateArchiveRestore({ migrationsDir, name, force: flag("--force") });
|
|
294
546
|
}
|
|
295
|
-
else
|
|
296
|
-
help();
|
|
297
547
|
}
|
|
298
|
-
else
|
|
299
|
-
help();
|
|
300
548
|
}
|
|
301
549
|
else {
|
|
302
|
-
|
|
550
|
+
usageError(`unknown command ${JSON.stringify(cmd)}`);
|
|
303
551
|
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { existsSync, readFileSync, readdirSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { CACHE_MANIFEST_FILE } from "./cache.js";
|
|
4
|
+
function readGeneratedFiles(set) {
|
|
5
|
+
const files = new Map();
|
|
6
|
+
if (existsSync(set.cacheDir)) {
|
|
7
|
+
for (const name of readdirSync(set.cacheDir).sort()) {
|
|
8
|
+
if (name !== CACHE_MANIFEST_FILE && !/^[0-9a-f]{16}\.json$/.test(name))
|
|
9
|
+
continue;
|
|
10
|
+
files.set(`cache/${name}`, readFileSync(join(set.cacheDir, name), "utf8"));
|
|
11
|
+
}
|
|
12
|
+
const functions = join(set.cacheDir, "functions/functions.json");
|
|
13
|
+
if (existsSync(functions))
|
|
14
|
+
files.set("cache/functions/functions.json", readFileSync(functions, "utf8"));
|
|
15
|
+
}
|
|
16
|
+
if (existsSync(set.dtsPath))
|
|
17
|
+
files.set("sqlx-js-env.d.ts", readFileSync(set.dtsPath, "utf8"));
|
|
18
|
+
return files;
|
|
19
|
+
}
|
|
20
|
+
export function compareArtifacts(expected, actual) {
|
|
21
|
+
const left = readGeneratedFiles(expected);
|
|
22
|
+
const right = readGeneratedFiles(actual);
|
|
23
|
+
const names = new Set([...left.keys(), ...right.keys()]);
|
|
24
|
+
const changed = [...names].filter((name) => left.get(name) !== right.get(name)).sort();
|
|
25
|
+
return { ok: changed.length === 0, changed };
|
|
26
|
+
}
|
package/dist/src/cache.d.ts
CHANGED
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
export declare const CACHE_FORMAT_VERSION = 2;
|
|
2
|
+
export declare const GENERATOR_REVISION = 3;
|
|
3
|
+
export declare const CACHE_MANIFEST_FILE = "cache-manifest.json";
|
|
4
|
+
export type CacheManifest = {
|
|
5
|
+
cacheFormat: typeof CACHE_FORMAT_VERSION;
|
|
6
|
+
generatorRevision: typeof GENERATOR_REVISION;
|
|
7
|
+
configHash: string;
|
|
8
|
+
};
|
|
1
9
|
export type CacheColumn = {
|
|
2
10
|
name: string;
|
|
3
11
|
typeOid: number;
|
|
@@ -19,6 +27,7 @@ export type CacheEntry = {
|
|
|
19
27
|
reason: string;
|
|
20
28
|
};
|
|
21
29
|
};
|
|
30
|
+
export declare function portableCacheOid(oid: number): number;
|
|
22
31
|
export declare function fingerprint(query: string): string;
|
|
23
32
|
export declare function effectiveNullable(c: CacheColumn): boolean;
|
|
24
33
|
export declare class Cache {
|
|
@@ -28,6 +37,10 @@ export declare class Cache {
|
|
|
28
37
|
has(fp: string): boolean;
|
|
29
38
|
read(fp: string): CacheEntry | null;
|
|
30
39
|
write(fp: string, entry: CacheEntry): void;
|
|
40
|
+
replaceAll(entries: Iterable<{
|
|
41
|
+
fp: string;
|
|
42
|
+
entry: CacheEntry;
|
|
43
|
+
}>, prune?: boolean): string[];
|
|
31
44
|
list(): {
|
|
32
45
|
fp: string;
|
|
33
46
|
entry: CacheEntry;
|
|
@@ -35,3 +48,7 @@ export declare class Cache {
|
|
|
35
48
|
remove(fp: string): void;
|
|
36
49
|
prune(keep: Iterable<string>): string[];
|
|
37
50
|
}
|
|
51
|
+
export declare function cacheManifestPath(cacheDir: string): string;
|
|
52
|
+
export declare function writeCacheManifest(cacheDir: string, configHash: string): void;
|
|
53
|
+
export declare function readCacheManifest(cacheDir: string): CacheManifest | null;
|
|
54
|
+
export declare function assertCacheManifest(cacheDir: string, configHash: string): CacheManifest;
|