@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.cjs
CHANGED
|
@@ -3376,6 +3376,24 @@ function sqlLiterals(values) {
|
|
|
3376
3376
|
function derivedName(table, column) {
|
|
3377
3377
|
return `${table}_${column}_check`;
|
|
3378
3378
|
}
|
|
3379
|
+
var ADD_CONSTRAINT_DIALECTS = /* @__PURE__ */ new Set([
|
|
3380
|
+
"postgres",
|
|
3381
|
+
"cockroach",
|
|
3382
|
+
"gel",
|
|
3383
|
+
"mysql",
|
|
3384
|
+
"singlestore",
|
|
3385
|
+
"mssql"
|
|
3386
|
+
]);
|
|
3387
|
+
function fixFor(dialect, table, column, values) {
|
|
3388
|
+
if (!ADD_CONSTRAINT_DIALECTS.has(dialect)) return void 0;
|
|
3389
|
+
return `ALTER TABLE ${table} ADD CONSTRAINT ${derivedName(table, column)} CHECK (${column} IN (${sqlLiterals(values)}));`;
|
|
3390
|
+
}
|
|
3391
|
+
function noFixReason(dialect) {
|
|
3392
|
+
if (dialect === "sqlite") {
|
|
3393
|
+
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.";
|
|
3394
|
+
}
|
|
3395
|
+
return `no statement is emitted for the "${dialect}" dialect, which this report cannot name a fix for`;
|
|
3396
|
+
}
|
|
3379
3397
|
function buildConstraintDriftReport(analysis, schemaPath) {
|
|
3380
3398
|
const entries = [];
|
|
3381
3399
|
for (const table of analysis.tables) {
|
|
@@ -3411,7 +3429,10 @@ function buildConstraintDriftReport(analysis, schemaPath) {
|
|
|
3411
3429
|
columns: [column.name],
|
|
3412
3430
|
rule: `${column.name} IN (${sqlLiterals(values)})`,
|
|
3413
3431
|
reason: `the column is declared ${column.sqlType}, which holds any text; the set exists only in the generated schemas`,
|
|
3414
|
-
|
|
3432
|
+
...(() => {
|
|
3433
|
+
const fix = fixFor(analysis.dialect, table.name, column.name, values);
|
|
3434
|
+
return fix ? { fix } : { noFix: noFixReason(analysis.dialect) };
|
|
3435
|
+
})()
|
|
3415
3436
|
});
|
|
3416
3437
|
}
|
|
3417
3438
|
}
|
|
@@ -3486,6 +3507,8 @@ function renderConstraintDriftReport(report, style = PLAIN3) {
|
|
|
3486
3507
|
if (e.fix) {
|
|
3487
3508
|
out.push(chalk.dim(" Close it with:"));
|
|
3488
3509
|
out.push(` ${e.fix}`);
|
|
3510
|
+
} else if (e.noFix) {
|
|
3511
|
+
out.push(chalk.dim(wrap3(e.noFix, " ", " Not one statement here: ")));
|
|
3489
3512
|
}
|
|
3490
3513
|
out.push("");
|
|
3491
3514
|
}
|
|
@@ -3517,6 +3540,43 @@ function renderConstraintDriftReport(report, style = PLAIN3) {
|
|
|
3517
3540
|
);
|
|
3518
3541
|
return out.join("\n");
|
|
3519
3542
|
}
|
|
3543
|
+
function renderConstraintDriftSql(report) {
|
|
3544
|
+
const gaps = report.entries.filter((e) => e.side === "schema-only");
|
|
3545
|
+
if (!gaps.length) return "";
|
|
3546
|
+
const out = [];
|
|
3547
|
+
const runnable = gaps.filter((g) => g.fix);
|
|
3548
|
+
const unrunnable = gaps.filter((g) => !g.fix);
|
|
3549
|
+
out.push(`-- Generated by drzl doctor --constraints --sql`);
|
|
3550
|
+
out.push(`-- ${report.schema}, ${report.dialect}`);
|
|
3551
|
+
out.push(
|
|
3552
|
+
`-- ${runnable.length} constraint(s) your schemas enforce and the database does not.`
|
|
3553
|
+
);
|
|
3554
|
+
if (unrunnable.length) {
|
|
3555
|
+
out.push(
|
|
3556
|
+
`-- ${unrunnable.length} more cannot be closed by one statement on this dialect; see below.`
|
|
3557
|
+
);
|
|
3558
|
+
}
|
|
3559
|
+
out.push("");
|
|
3560
|
+
const byPlace = (a, b) => a.table.localeCompare(b.table) || a.columns.join().localeCompare(b.columns.join());
|
|
3561
|
+
for (const g of [...runnable].sort(byPlace)) {
|
|
3562
|
+
out.push(`-- ${g.table}.${g.columns.join(", ")}: ${g.reason}`);
|
|
3563
|
+
out.push(g.fix);
|
|
3564
|
+
out.push("");
|
|
3565
|
+
}
|
|
3566
|
+
if (unrunnable.length) {
|
|
3567
|
+
out.push("-- The rest, which this dialect cannot do in one statement:");
|
|
3568
|
+
for (const g of [...unrunnable].sort(byPlace)) {
|
|
3569
|
+
out.push(`--`);
|
|
3570
|
+
out.push(`-- ${g.table}.${g.columns.join(", ")}`);
|
|
3571
|
+
out.push(`-- ${g.rule}`);
|
|
3572
|
+
for (const line of (g.noFix ?? "").match(/.{1,88}(\s|$)/g) ?? []) {
|
|
3573
|
+
out.push(`-- ${line.trim()}`);
|
|
3574
|
+
}
|
|
3575
|
+
}
|
|
3576
|
+
out.push("");
|
|
3577
|
+
}
|
|
3578
|
+
return out.join("\n");
|
|
3579
|
+
}
|
|
3520
3580
|
|
|
3521
3581
|
// src/drift.ts
|
|
3522
3582
|
var import_node_fs = require("fs");
|
|
@@ -4443,7 +4503,7 @@ withOutputFlags(
|
|
|
4443
4503
|
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(
|
|
4444
4504
|
"--constraints",
|
|
4445
4505
|
"report what each side enforces that the other does not, instead of the usual findings"
|
|
4446
|
-
)
|
|
4506
|
+
).option("--sql", "with --constraints, emit the statements alone, for redirecting to a migration")
|
|
4447
4507
|
).action(async (schema, opts) => {
|
|
4448
4508
|
const out = outputFor(opts);
|
|
4449
4509
|
{
|
|
@@ -4466,6 +4526,13 @@ withOutputFlags(
|
|
|
4466
4526
|
validateConstraints: true
|
|
4467
4527
|
});
|
|
4468
4528
|
const schemaLabel = Array.isArray(target) ? target.join(", ") : target;
|
|
4529
|
+
if (opts.sql && !opts.constraints) {
|
|
4530
|
+
const msg = "--sql reports the constraint drift as statements, so it needs --constraints.";
|
|
4531
|
+
if (opts.json) out.jsonData(jsonFailure("doctor", "DRZL_CLI_DOCTOR", msg));
|
|
4532
|
+
else out.error("Doctor failed (DRZL_CLI_DOCTOR):", msg);
|
|
4533
|
+
process.exit(EXIT_FAILED);
|
|
4534
|
+
return;
|
|
4535
|
+
}
|
|
4469
4536
|
if (opts.constraints) {
|
|
4470
4537
|
const preflight = buildDoctorReport(analysis, schemaLabel);
|
|
4471
4538
|
const fatal = preflight.findings.filter((f) => f.level === "error");
|
|
@@ -4484,6 +4551,11 @@ withOutputFlags(
|
|
|
4484
4551
|
}
|
|
4485
4552
|
const drift = buildConstraintDriftReport(analysis, schemaLabel);
|
|
4486
4553
|
const driftCode = opts.strict && drift.counts.schemaOnly ? EXIT_FINDINGS : EXIT_OK;
|
|
4554
|
+
if (opts.sql) {
|
|
4555
|
+
out.data(renderConstraintDriftSql(drift));
|
|
4556
|
+
process.exit(driftCode);
|
|
4557
|
+
return;
|
|
4558
|
+
}
|
|
4487
4559
|
if (opts.json)
|
|
4488
4560
|
out.data(
|
|
4489
4561
|
JSON.stringify({ command: "doctor", exitCode: driftCode, ...drift }, null, 2)
|