@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 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
- fix: `ALTER TABLE ${table.name} ADD CONSTRAINT ${derivedName(table.name, column.name)} CHECK (${column.name} IN (${sqlLiterals(values)}));`
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)