@drzl/cli 4.35.0 → 4.36.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/dist/cli.cjs +74 -2
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +74 -2
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -2182,6 +2182,24 @@ function sqlLiterals(values) {
|
|
|
2182
2182
|
function derivedName(table, column) {
|
|
2183
2183
|
return `${table}_${column}_check`;
|
|
2184
2184
|
}
|
|
2185
|
+
var ADD_CONSTRAINT_DIALECTS = /* @__PURE__ */ new Set([
|
|
2186
|
+
"postgres",
|
|
2187
|
+
"cockroach",
|
|
2188
|
+
"gel",
|
|
2189
|
+
"mysql",
|
|
2190
|
+
"singlestore",
|
|
2191
|
+
"mssql"
|
|
2192
|
+
]);
|
|
2193
|
+
function fixFor(dialect, table, column, values) {
|
|
2194
|
+
if (!ADD_CONSTRAINT_DIALECTS.has(dialect)) return void 0;
|
|
2195
|
+
return `ALTER TABLE ${table} ADD CONSTRAINT ${derivedName(table, column)} CHECK (${column} IN (${sqlLiterals(values)}));`;
|
|
2196
|
+
}
|
|
2197
|
+
function noFixReason(dialect) {
|
|
2198
|
+
if (dialect === "sqlite") {
|
|
2199
|
+
return "SQLite cannot add a CHECK to an existing column: ALTER TABLE ... ADD CONSTRAINT is a syntax error there. Closing this means rebuilding the table, which is the twelve-step procedure SQLite documents: create the new table with the constraint, copy the rows, drop the old one, rename.";
|
|
2200
|
+
}
|
|
2201
|
+
return `no statement is emitted for the "${dialect}" dialect, which this report cannot name a fix for`;
|
|
2202
|
+
}
|
|
2185
2203
|
function buildConstraintDriftReport(analysis, schemaPath) {
|
|
2186
2204
|
const entries = [];
|
|
2187
2205
|
for (const table of analysis.tables) {
|
|
@@ -2217,7 +2235,10 @@ function buildConstraintDriftReport(analysis, schemaPath) {
|
|
|
2217
2235
|
columns: [column.name],
|
|
2218
2236
|
rule: `${column.name} IN (${sqlLiterals(values)})`,
|
|
2219
2237
|
reason: `the column is declared ${column.sqlType}, which holds any text; the set exists only in the generated schemas`,
|
|
2220
|
-
|
|
2238
|
+
...(() => {
|
|
2239
|
+
const fix = fixFor(analysis.dialect, table.name, column.name, values);
|
|
2240
|
+
return fix ? { fix } : { noFix: noFixReason(analysis.dialect) };
|
|
2241
|
+
})()
|
|
2221
2242
|
});
|
|
2222
2243
|
}
|
|
2223
2244
|
}
|
|
@@ -2292,6 +2313,8 @@ function renderConstraintDriftReport(report, style = PLAIN3) {
|
|
|
2292
2313
|
if (e.fix) {
|
|
2293
2314
|
out.push(chalk.dim(" Close it with:"));
|
|
2294
2315
|
out.push(` ${e.fix}`);
|
|
2316
|
+
} else if (e.noFix) {
|
|
2317
|
+
out.push(chalk.dim(wrap3(e.noFix, " ", " Not one statement here: ")));
|
|
2295
2318
|
}
|
|
2296
2319
|
out.push("");
|
|
2297
2320
|
}
|
|
@@ -2323,6 +2346,43 @@ function renderConstraintDriftReport(report, style = PLAIN3) {
|
|
|
2323
2346
|
);
|
|
2324
2347
|
return out.join("\n");
|
|
2325
2348
|
}
|
|
2349
|
+
function renderConstraintDriftSql(report) {
|
|
2350
|
+
const gaps = report.entries.filter((e) => e.side === "schema-only");
|
|
2351
|
+
if (!gaps.length) return "";
|
|
2352
|
+
const out = [];
|
|
2353
|
+
const runnable = gaps.filter((g) => g.fix);
|
|
2354
|
+
const unrunnable = gaps.filter((g) => !g.fix);
|
|
2355
|
+
out.push(`-- Generated by drzl doctor --constraints --sql`);
|
|
2356
|
+
out.push(`-- ${report.schema}, ${report.dialect}`);
|
|
2357
|
+
out.push(
|
|
2358
|
+
`-- ${runnable.length} constraint(s) your schemas enforce and the database does not.`
|
|
2359
|
+
);
|
|
2360
|
+
if (unrunnable.length) {
|
|
2361
|
+
out.push(
|
|
2362
|
+
`-- ${unrunnable.length} more cannot be closed by one statement on this dialect; see below.`
|
|
2363
|
+
);
|
|
2364
|
+
}
|
|
2365
|
+
out.push("");
|
|
2366
|
+
const byPlace = (a, b) => a.table.localeCompare(b.table) || a.columns.join().localeCompare(b.columns.join());
|
|
2367
|
+
for (const g of [...runnable].sort(byPlace)) {
|
|
2368
|
+
out.push(`-- ${g.table}.${g.columns.join(", ")}: ${g.reason}`);
|
|
2369
|
+
out.push(g.fix);
|
|
2370
|
+
out.push("");
|
|
2371
|
+
}
|
|
2372
|
+
if (unrunnable.length) {
|
|
2373
|
+
out.push("-- The rest, which this dialect cannot do in one statement:");
|
|
2374
|
+
for (const g of [...unrunnable].sort(byPlace)) {
|
|
2375
|
+
out.push(`--`);
|
|
2376
|
+
out.push(`-- ${g.table}.${g.columns.join(", ")}`);
|
|
2377
|
+
out.push(`-- ${g.rule}`);
|
|
2378
|
+
for (const line of (g.noFix ?? "").match(/.{1,88}(\s|$)/g) ?? []) {
|
|
2379
|
+
out.push(`-- ${line.trim()}`);
|
|
2380
|
+
}
|
|
2381
|
+
}
|
|
2382
|
+
out.push("");
|
|
2383
|
+
}
|
|
2384
|
+
return out.join("\n");
|
|
2385
|
+
}
|
|
2326
2386
|
|
|
2327
2387
|
// src/drift.ts
|
|
2328
2388
|
import { promises as fs2 } from "fs";
|
|
@@ -3249,7 +3309,7 @@ withOutputFlags(
|
|
|
3249
3309
|
program.command("doctor").description("Report what DRZL cannot type or enforce in your schema, and why").argument("[schema]", "path to drizzle schema (TS); defaults to the schema in drzl.config").option("-c, --config <path>", "path to drzl.config, read when no schema argument is given").option("--strict", "exit 2 when anything is reported", false).option(
|
|
3250
3310
|
"--constraints",
|
|
3251
3311
|
"report what each side enforces that the other does not, instead of the usual findings"
|
|
3252
|
-
)
|
|
3312
|
+
).option("--sql", "with --constraints, emit the statements alone, for redirecting to a migration")
|
|
3253
3313
|
).action(async (schema, opts) => {
|
|
3254
3314
|
const out = outputFor(opts);
|
|
3255
3315
|
{
|
|
@@ -3272,6 +3332,13 @@ withOutputFlags(
|
|
|
3272
3332
|
validateConstraints: true
|
|
3273
3333
|
});
|
|
3274
3334
|
const schemaLabel = Array.isArray(target) ? target.join(", ") : target;
|
|
3335
|
+
if (opts.sql && !opts.constraints) {
|
|
3336
|
+
const msg = "--sql reports the constraint drift as statements, so it needs --constraints.";
|
|
3337
|
+
if (opts.json) out.jsonData(jsonFailure("doctor", "DRZL_CLI_DOCTOR", msg));
|
|
3338
|
+
else out.error("Doctor failed (DRZL_CLI_DOCTOR):", msg);
|
|
3339
|
+
process.exit(EXIT_FAILED);
|
|
3340
|
+
return;
|
|
3341
|
+
}
|
|
3275
3342
|
if (opts.constraints) {
|
|
3276
3343
|
const preflight = buildDoctorReport(analysis, schemaLabel);
|
|
3277
3344
|
const fatal = preflight.findings.filter((f) => f.level === "error");
|
|
@@ -3290,6 +3357,11 @@ withOutputFlags(
|
|
|
3290
3357
|
}
|
|
3291
3358
|
const drift = buildConstraintDriftReport(analysis, schemaLabel);
|
|
3292
3359
|
const driftCode = opts.strict && drift.counts.schemaOnly ? EXIT_FINDINGS : EXIT_OK;
|
|
3360
|
+
if (opts.sql) {
|
|
3361
|
+
out.data(renderConstraintDriftSql(drift));
|
|
3362
|
+
process.exit(driftCode);
|
|
3363
|
+
return;
|
|
3364
|
+
}
|
|
3293
3365
|
if (opts.json)
|
|
3294
3366
|
out.data(
|
|
3295
3367
|
JSON.stringify({ command: "doctor", exitCode: driftCode, ...drift }, null, 2)
|