@onreza/sqlx-js 0.5.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 +59 -19
- package/ROADMAP.md +1 -1
- 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 +258 -81
- package/dist/src/commands/init.js +87 -3
- package/dist/src/commands/migrate.d.ts +2 -101
- package/dist/src/commands/migrate.js +6 -546
- package/dist/src/commands/prepare.d.ts +2 -1
- package/dist/src/commands/prepare.js +104 -5
- package/dist/src/commands/schema.js +1 -1
- package/dist/src/config.d.ts +1 -0
- package/dist/src/config.js +8 -0
- package/dist/src/migration-core.d.ts +157 -0
- package/dist/src/migration-core.js +578 -0
- package/dist/src/runtime.js +1 -1
- package/dist/src/scan/scanner.d.ts +1 -1
- package/dist/src/scan/scanner.js +4 -4
- package/dist/src/type-inspection.d.ts +1 -0
- package/dist/src/type-inspection.js +20 -0
- package/package.json +6 -2
package/dist/bin/sqlx-js.js
CHANGED
|
@@ -2,13 +2,7 @@
|
|
|
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 { runWatch } from "../src/commands/watch.js";
|
|
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 { runDoctor } from "../src/commands/doctor.js";
|
|
5
|
+
import { parseArgs } from "node:util";
|
|
12
6
|
import { assertSupportedRuntime, loadConfig, loadRootEnv } from "../src/config.js";
|
|
13
7
|
function packageVersion() {
|
|
14
8
|
const here = dirname(fileURLToPath(import.meta.url));
|
|
@@ -22,17 +16,20 @@ function packageVersion() {
|
|
|
22
16
|
throw new Error("sqlx-js: cannot locate package.json for version");
|
|
23
17
|
}
|
|
24
18
|
const VERSION = packageVersion();
|
|
25
|
-
|
|
26
|
-
|
|
19
|
+
const HELP = {
|
|
20
|
+
root: `sqlx-js — compile-time-checked SQL for TypeScript + Postgres (v${VERSION})
|
|
27
21
|
|
|
28
22
|
usage:
|
|
29
23
|
sqlx-js init [--root <dir>] [--schema-provider builtin|pgschema]
|
|
30
|
-
sqlx-js doctor [--root <dir>] [--json]
|
|
31
|
-
sqlx-js db install | check
|
|
32
|
-
sqlx-js
|
|
33
|
-
sqlx-js
|
|
34
|
-
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>]
|
|
35
31
|
sqlx-js --version
|
|
32
|
+
sqlx-js-diagnostics github|unix < prepare-diagnostics.json
|
|
36
33
|
|
|
37
34
|
env:
|
|
38
35
|
DATABASE_URL=postgres://... (supports sslmode, cert paths, application_name, connect_timeout, statement_timeout)
|
|
@@ -49,6 +46,7 @@ flags:
|
|
|
49
46
|
--migrations <dir> migrations directory (default: <root>/migrations)
|
|
50
47
|
--dry-run validate and print migrate run/revert plan without applying migrations
|
|
51
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
|
|
52
50
|
--force allow archive restore to overwrite existing migration files
|
|
53
51
|
--lock-timeout <ms> advisory-lock acquisition timeout for migrate run/revert/dev/verify/squash
|
|
54
52
|
--shadow-url <url> use an existing disposable shadow DB instead of auto-creating one
|
|
@@ -59,38 +57,208 @@ flags:
|
|
|
59
57
|
--manifest <path> LLM schema manifest path (default: <root>/.sqlx-js/schema/schema.md)
|
|
60
58
|
--no-manifest skip writing the LLM schema manifest during schema dump
|
|
61
59
|
--schema-provider <name> init schema workflow: builtin (default) or pgschema
|
|
62
|
-
|
|
63
|
-
|
|
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]);
|
|
64
70
|
}
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
function arg(name, def) {
|
|
69
|
-
const eq = `${name}=`;
|
|
70
|
-
for (let i = 0; i < cliArgv.length; i++) {
|
|
71
|
-
const a = cliArgv[i];
|
|
72
|
-
if (a === name)
|
|
73
|
-
return cliArgv[i + 1] ?? def;
|
|
74
|
-
if (a.startsWith(eq))
|
|
75
|
-
return a.slice(eq.length);
|
|
76
|
-
}
|
|
77
|
-
return def;
|
|
71
|
+
function exitHelp(scope) {
|
|
72
|
+
printHelp(scope);
|
|
73
|
+
process.exit(0);
|
|
78
74
|
}
|
|
79
|
-
function
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
}
|
|
84
|
-
return false;
|
|
75
|
+
function usageError(message, scope = "root") {
|
|
76
|
+
console.error(`sqlx-js: ${message}`);
|
|
77
|
+
printHelp(scope, true);
|
|
78
|
+
process.exit(2);
|
|
85
79
|
}
|
|
86
|
-
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"]);
|
|
87
86
|
if (cmd === "--version" || cmd === "-v") {
|
|
88
87
|
console.log(VERSION);
|
|
89
88
|
process.exit(0);
|
|
90
89
|
}
|
|
91
|
-
if (cmd === "--help" || cmd === "-h"
|
|
92
|
-
|
|
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;
|
|
93
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
|
+
});
|
|
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();
|
|
94
262
|
const root = resolve(arg("--root", process.cwd()));
|
|
95
263
|
if (cmd !== "doctor") {
|
|
96
264
|
try {
|
|
@@ -102,14 +270,21 @@ if (cmd !== "doctor") {
|
|
|
102
270
|
}
|
|
103
271
|
}
|
|
104
272
|
let envError;
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
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
|
+
}
|
|
113
288
|
}
|
|
114
289
|
}
|
|
115
290
|
const databaseUrl = process.env.DATABASE_URL ?? "";
|
|
@@ -118,13 +293,14 @@ const shadowUrl = shadowUrlArg ?? process.env.SHADOW_DATABASE_URL;
|
|
|
118
293
|
const shadowAdminUrl = arg("--shadow-admin-url") ?? process.env.SHADOW_ADMIN_DATABASE_URL;
|
|
119
294
|
const cacheDir = join(root, ".sqlx-js");
|
|
120
295
|
const dtsArg = arg("--dts");
|
|
121
|
-
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");
|
|
122
297
|
const migrationsDir = join(root, arg("--migrations", "migrations"));
|
|
123
298
|
const schemaArg = arg("--schema");
|
|
124
|
-
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");
|
|
125
300
|
const manifestArg = arg("--manifest");
|
|
126
|
-
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");
|
|
127
302
|
if (cmd === "init") {
|
|
303
|
+
const { runInit } = await import("../src/commands/init.js");
|
|
128
304
|
const provider = arg("--schema-provider", "builtin");
|
|
129
305
|
if (provider !== "builtin" && provider !== "pgschema") {
|
|
130
306
|
console.error("--schema-provider must be builtin or pgschema");
|
|
@@ -133,10 +309,12 @@ if (cmd === "init") {
|
|
|
133
309
|
runInit({ root, schemaProvider: provider });
|
|
134
310
|
}
|
|
135
311
|
else if (cmd === "doctor") {
|
|
312
|
+
const { runDoctor } = await import("../src/commands/doctor.js");
|
|
136
313
|
await runDoctor({ root, databaseUrl, cacheDir, dtsPath, json: flag("--json"), envError });
|
|
137
314
|
}
|
|
138
315
|
else if (cmd === "db") {
|
|
139
|
-
const
|
|
316
|
+
const { runPgschemaCommand, runPgschemaInstall } = await import("../src/commands/pgschema.js");
|
|
317
|
+
const sub = positionals[0];
|
|
140
318
|
if (sub === "install") {
|
|
141
319
|
try {
|
|
142
320
|
await runPgschemaInstall({ root });
|
|
@@ -147,8 +325,6 @@ else if (cmd === "db") {
|
|
|
147
325
|
}
|
|
148
326
|
}
|
|
149
327
|
else {
|
|
150
|
-
if (sub !== "check" && sub !== "plan" && sub !== "apply")
|
|
151
|
-
help();
|
|
152
328
|
try {
|
|
153
329
|
runPgschemaCommand({
|
|
154
330
|
root,
|
|
@@ -165,6 +341,7 @@ else if (cmd === "db") {
|
|
|
165
341
|
}
|
|
166
342
|
}
|
|
167
343
|
else if (cmd === "prepare") {
|
|
344
|
+
const { PrepareFatalError, runPrepare } = await import("../src/commands/prepare.js");
|
|
168
345
|
const prepareCheck = flag("--check");
|
|
169
346
|
const prepareVerify = flag("--verify");
|
|
170
347
|
const prepareWatch = flag("--watch");
|
|
@@ -198,6 +375,9 @@ else if (cmd === "prepare") {
|
|
|
198
375
|
if (prepareWatch && prepareJson) {
|
|
199
376
|
failPrepare("--watch and --json are mutually exclusive", "config");
|
|
200
377
|
}
|
|
378
|
+
if ((prepareCheck || prepareVerify) && flag("--no-prune")) {
|
|
379
|
+
failPrepare("--no-prune is only supported by prepare and prepare --watch", "config");
|
|
380
|
+
}
|
|
201
381
|
const prepareShadowUrl = prepareCheck ? undefined : shadowUrl;
|
|
202
382
|
const prepareDatabaseUrl = prepareShadowUrl ?? databaseUrl;
|
|
203
383
|
if (!prepareCheck && !prepareDatabaseUrl) {
|
|
@@ -212,8 +392,13 @@ else if (cmd === "prepare") {
|
|
|
212
392
|
verify: prepareVerify,
|
|
213
393
|
json: prepareJson,
|
|
214
394
|
prune: !flag("--no-prune"),
|
|
395
|
+
strictInference: flag("--strict-inference"),
|
|
215
396
|
};
|
|
397
|
+
const applyShadowMigrations = prepareShadowUrl
|
|
398
|
+
? (await import("../src/commands/schema.js")).applyShadowMigrations
|
|
399
|
+
: undefined;
|
|
216
400
|
if (prepareWatch) {
|
|
401
|
+
const { runWatch } = await import("../src/commands/watch.js");
|
|
217
402
|
await runWatch({
|
|
218
403
|
...opts,
|
|
219
404
|
...(prepareShadowUrl
|
|
@@ -252,7 +437,8 @@ else if (cmd === "prepare") {
|
|
|
252
437
|
}
|
|
253
438
|
}
|
|
254
439
|
else if (cmd === "schema") {
|
|
255
|
-
const
|
|
440
|
+
const { runSchemaCheck, runSchemaDump } = await import("../src/commands/schema.js");
|
|
441
|
+
const sub = positionals[0];
|
|
256
442
|
const schemaDatabaseUrl = shadowUrl ?? databaseUrl;
|
|
257
443
|
if (!schemaDatabaseUrl) {
|
|
258
444
|
console.error("DATABASE_URL is required for schema commands (or pass --shadow-url)");
|
|
@@ -270,19 +456,21 @@ else if (cmd === "schema") {
|
|
|
270
456
|
await runSchemaDump(opts);
|
|
271
457
|
else if (sub === "check")
|
|
272
458
|
await runSchemaCheck(opts);
|
|
273
|
-
else
|
|
274
|
-
help();
|
|
275
459
|
}
|
|
276
460
|
else if (cmd === "migrate") {
|
|
277
|
-
const
|
|
461
|
+
const { migrateArchiveList, migrateArchiveRestore, migrateCheck, migrateDev, migrateRun, migrateInfo, migrateRevert, migrateAdd, migrateSquash, migrateVerify, } = await import("../src/commands/migrate.js");
|
|
462
|
+
const sub = positionals[0];
|
|
278
463
|
const revertDryRun = sub === "revert" && flag("--dry-run");
|
|
279
464
|
const workflowShadowOnly = ((sub === "dev" || sub === "verify" || sub === "squash") && !!shadowUrl) || (revertDryRun && !!shadowUrl);
|
|
280
|
-
if (!databaseUrl && sub !== "add" && sub !== "check" && sub !== "
|
|
465
|
+
if (!databaseUrl && sub !== "add" && sub !== "check" && sub !== "archive" && !workflowShadowOnly) {
|
|
281
466
|
console.error("DATABASE_URL is required");
|
|
282
467
|
process.exit(2);
|
|
283
468
|
}
|
|
284
469
|
const tRaw = arg("--lock-timeout");
|
|
285
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
|
+
}
|
|
286
474
|
if (sub === "dev") {
|
|
287
475
|
await migrateDev({
|
|
288
476
|
root,
|
|
@@ -294,6 +482,7 @@ else if (cmd === "migrate") {
|
|
|
294
482
|
shadowUrl,
|
|
295
483
|
shadowAdminUrl,
|
|
296
484
|
lockTimeoutMs,
|
|
485
|
+
strictInference: flag("--strict-inference"),
|
|
297
486
|
});
|
|
298
487
|
}
|
|
299
488
|
else if (sub === "verify") {
|
|
@@ -306,15 +495,18 @@ else if (cmd === "migrate") {
|
|
|
306
495
|
shadowUrl,
|
|
307
496
|
shadowAdminUrl,
|
|
308
497
|
lockTimeoutMs,
|
|
498
|
+
strictInference: flag("--strict-inference"),
|
|
309
499
|
});
|
|
310
500
|
}
|
|
311
501
|
else if (sub === "run") {
|
|
312
502
|
await migrateRun({ databaseUrl, migrationsDir, lockTimeoutMs, dryRun: flag("--dry-run"), json: flag("--json") });
|
|
313
503
|
}
|
|
314
|
-
else if (sub === "info")
|
|
504
|
+
else if (sub === "info") {
|
|
315
505
|
await migrateInfo({ databaseUrl, migrationsDir, json: flag("--json") });
|
|
316
|
-
|
|
506
|
+
}
|
|
507
|
+
else if (sub === "check") {
|
|
317
508
|
migrateCheck({ migrationsDir, json: flag("--json") });
|
|
509
|
+
}
|
|
318
510
|
else if (sub === "revert") {
|
|
319
511
|
await migrateRevert({
|
|
320
512
|
databaseUrl,
|
|
@@ -327,19 +519,11 @@ else if (cmd === "migrate") {
|
|
|
327
519
|
});
|
|
328
520
|
}
|
|
329
521
|
else if (sub === "add") {
|
|
330
|
-
const name =
|
|
331
|
-
if (!name) {
|
|
332
|
-
console.error("migrate add: name required");
|
|
333
|
-
process.exit(2);
|
|
334
|
-
}
|
|
522
|
+
const name = positionals[1];
|
|
335
523
|
migrateAdd({ databaseUrl, migrationsDir, name });
|
|
336
524
|
}
|
|
337
525
|
else if (sub === "squash") {
|
|
338
|
-
const name =
|
|
339
|
-
if (!name) {
|
|
340
|
-
console.error("migrate squash: name required");
|
|
341
|
-
process.exit(2);
|
|
342
|
-
}
|
|
526
|
+
const name = positionals[1];
|
|
343
527
|
await migrateSquash({
|
|
344
528
|
databaseUrl,
|
|
345
529
|
migrationsDir,
|
|
@@ -352,23 +536,16 @@ else if (cmd === "migrate") {
|
|
|
352
536
|
});
|
|
353
537
|
}
|
|
354
538
|
else if (sub === "archive") {
|
|
355
|
-
const action =
|
|
356
|
-
if (action === "list")
|
|
539
|
+
const action = positionals[1];
|
|
540
|
+
if (action === "list") {
|
|
357
541
|
migrateArchiveList({ migrationsDir });
|
|
542
|
+
}
|
|
358
543
|
else if (action === "restore") {
|
|
359
|
-
const name =
|
|
360
|
-
if (!name) {
|
|
361
|
-
console.error("migrate archive restore: name required");
|
|
362
|
-
process.exit(2);
|
|
363
|
-
}
|
|
544
|
+
const name = positionals[2];
|
|
364
545
|
migrateArchiveRestore({ migrationsDir, name, force: flag("--force") });
|
|
365
546
|
}
|
|
366
|
-
else
|
|
367
|
-
help();
|
|
368
547
|
}
|
|
369
|
-
else
|
|
370
|
-
help();
|
|
371
548
|
}
|
|
372
549
|
else {
|
|
373
|
-
|
|
550
|
+
usageError(`unknown command ${JSON.stringify(cmd)}`);
|
|
374
551
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { existsSync, mkdirSync, writeFileSync } from "node:fs";
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { dirname, join } from "node:path";
|
|
3
3
|
const CONFIG_TEMPLATE = `import { defineConfig } from "@onreza/sqlx-js";
|
|
4
4
|
|
|
@@ -33,10 +33,23 @@ const PGSCHEMA_TEMPLATE = `-- Desired PostgreSQL schema managed by pgschema.
|
|
|
33
33
|
-- sqlx-js db plan -- --output-json plan.json
|
|
34
34
|
-- sqlx-js db apply -- --auto-approve
|
|
35
35
|
`;
|
|
36
|
+
const DTS_TEMPLATE = `// AUTO-GENERATED by sqlx-js. Do not edit.
|
|
37
|
+
// Run \`sqlx-js prepare\` to regenerate.
|
|
38
|
+
|
|
39
|
+
declare module "@onreza/sqlx-js" {
|
|
40
|
+
interface KnownQueries {}
|
|
41
|
+
interface KnownFileQueries {}
|
|
42
|
+
interface KnownFunctions {}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export {};
|
|
46
|
+
`;
|
|
36
47
|
export function runInit(opts) {
|
|
37
48
|
const log = opts.log ?? console.log;
|
|
38
49
|
const created = [];
|
|
50
|
+
const updated = [];
|
|
39
51
|
const skipped = [];
|
|
52
|
+
const notes = [];
|
|
40
53
|
const ensureFile = (rel, content) => {
|
|
41
54
|
const full = join(opts.root, rel);
|
|
42
55
|
if (existsSync(full)) {
|
|
@@ -63,10 +76,81 @@ export function runInit(opts) {
|
|
|
63
76
|
else
|
|
64
77
|
ensureDir("migrations");
|
|
65
78
|
ensureFile(".env.example", ENV_TEMPLATE);
|
|
79
|
+
ensureFile("sqlx-js-env.d.ts", DTS_TEMPLATE);
|
|
80
|
+
const updateJson = (rel, mutate) => {
|
|
81
|
+
const full = join(opts.root, rel);
|
|
82
|
+
if (!existsSync(full))
|
|
83
|
+
return;
|
|
84
|
+
const source = readFileSync(full, "utf8");
|
|
85
|
+
let value;
|
|
86
|
+
try {
|
|
87
|
+
value = JSON.parse(source);
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
notes.push(`${rel}: manual update required because the file is not strict JSON`);
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
94
|
+
return;
|
|
95
|
+
if (!mutate(value))
|
|
96
|
+
return;
|
|
97
|
+
const indent = source.match(/\n([ \t]+)"/)?.[1] ?? " ";
|
|
98
|
+
writeFileSync(full, JSON.stringify(value, null, indent) + "\n");
|
|
99
|
+
updated.push(rel);
|
|
100
|
+
};
|
|
101
|
+
updateJson("tsconfig.json", (value) => {
|
|
102
|
+
const files = value.files;
|
|
103
|
+
if (files !== undefined && (!Array.isArray(files) || !files.every((item) => typeof item === "string"))) {
|
|
104
|
+
notes.push("tsconfig.json: manual update required because files is not an array of strings");
|
|
105
|
+
return false;
|
|
106
|
+
}
|
|
107
|
+
if (Array.isArray(files)) {
|
|
108
|
+
if (files.includes("sqlx-js-env.d.ts"))
|
|
109
|
+
return false;
|
|
110
|
+
files.push("sqlx-js-env.d.ts");
|
|
111
|
+
return true;
|
|
112
|
+
}
|
|
113
|
+
const include = value.include;
|
|
114
|
+
if (include !== undefined && (!Array.isArray(include) || !include.every((item) => typeof item === "string"))) {
|
|
115
|
+
notes.push("tsconfig.json: manual update required because include is not an array of strings");
|
|
116
|
+
return false;
|
|
117
|
+
}
|
|
118
|
+
if (Array.isArray(include)) {
|
|
119
|
+
if (include.includes("sqlx-js-env.d.ts"))
|
|
120
|
+
return false;
|
|
121
|
+
include.push("sqlx-js-env.d.ts");
|
|
122
|
+
return true;
|
|
123
|
+
}
|
|
124
|
+
return false;
|
|
125
|
+
});
|
|
126
|
+
updateJson("package.json", (value) => {
|
|
127
|
+
if (value.scripts !== undefined && (!value.scripts || typeof value.scripts !== "object" || Array.isArray(value.scripts))) {
|
|
128
|
+
notes.push("package.json: manual script update required because scripts is not an object");
|
|
129
|
+
return false;
|
|
130
|
+
}
|
|
131
|
+
const scripts = (value.scripts ??= {});
|
|
132
|
+
const defaults = {
|
|
133
|
+
"sqlx:prepare": "sqlx-js prepare",
|
|
134
|
+
"sqlx:check": "sqlx-js prepare --check",
|
|
135
|
+
"sqlx:verify": "sqlx-js prepare --verify --strict-inference",
|
|
136
|
+
};
|
|
137
|
+
let changed = false;
|
|
138
|
+
for (const [name, command] of Object.entries(defaults)) {
|
|
139
|
+
if (scripts[name] !== undefined)
|
|
140
|
+
continue;
|
|
141
|
+
scripts[name] = command;
|
|
142
|
+
changed = true;
|
|
143
|
+
}
|
|
144
|
+
return changed;
|
|
145
|
+
});
|
|
66
146
|
for (const f of created)
|
|
67
147
|
log(`created ${f}`);
|
|
148
|
+
for (const f of updated)
|
|
149
|
+
log(`updated ${f}`);
|
|
68
150
|
for (const f of skipped)
|
|
69
151
|
log(`exists ${f} (left unchanged)`);
|
|
152
|
+
for (const note of notes)
|
|
153
|
+
log(`manual ${note}`);
|
|
70
154
|
log("");
|
|
71
155
|
log("Next steps:");
|
|
72
156
|
log(" 1. Set DATABASE_URL (see .env.example).");
|
|
@@ -78,7 +162,7 @@ export function runInit(opts) {
|
|
|
78
162
|
}
|
|
79
163
|
else {
|
|
80
164
|
log(" 2. Add a migration: sqlx-js migrate add init");
|
|
81
|
-
log(" 3.
|
|
82
|
-
log(" 4. Develop locally: sqlx-js migrate dev");
|
|
165
|
+
log(" 3. Check the project setup: sqlx-js doctor");
|
|
166
|
+
log(" 4. Develop locally: sqlx-js migrate dev --strict-inference");
|
|
83
167
|
}
|
|
84
168
|
}
|