@rebasepro/server-postgres 0.24.1-canary.gcb918fc → 0.25.0-canary.ge06257e

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.
Files changed (137) hide show
  1. package/dist/{BranchService-Bsn2wfaj.js → BranchService-DNg4txyf.js} +14 -4
  2. package/dist/BranchService-DNg4txyf.js.map +1 -0
  3. package/dist/PostgresBackendDriver.d.ts +13 -0
  4. package/dist/PostgresBootstrapper.d.ts +10 -0
  5. package/dist/backup/backup-cli.d.ts +88 -2
  6. package/dist/backup/backup-service.d.ts +11 -1
  7. package/dist/backup/pg-tools.d.ts +18 -0
  8. package/dist/{backup-cli-DnpJi6I7.js → backup-cli-HCEkPcQj.js} +215 -79
  9. package/dist/backup-cli-HCEkPcQj.js.map +1 -0
  10. package/dist/{backup-service-BTBB7RXn.js → backup-service-D-UpT7DH.js} +121 -11
  11. package/dist/backup-service-D-UpT7DH.js.map +1 -0
  12. package/dist/{boot-ddl-DVpsFN6r.js → boot-ddl-Dnvpmm_T.js} +3 -3
  13. package/dist/{boot-ddl-DVpsFN6r.js.map → boot-ddl-Dnvpmm_T.js.map} +1 -1
  14. package/dist/branch-argv.d.ts +18 -6
  15. package/dist/cli-console-C_ISGwJo.js +255 -0
  16. package/dist/cli-console-C_ISGwJo.js.map +1 -0
  17. package/dist/cli-console.d.ts +10 -0
  18. package/dist/{cli-errors-CaI4ntyD.js → cli-errors-mx4sSjB8.js} +477 -103
  19. package/dist/cli-errors-mx4sSjB8.js.map +1 -0
  20. package/dist/cli-errors.d.ts +40 -4
  21. package/dist/{cli-flags-JNI-pwSf.js → cli-flags-DUozBFyK.js} +70 -6
  22. package/dist/cli-flags-DUozBFyK.js.map +1 -0
  23. package/dist/cli-flags.d.ts +21 -0
  24. package/dist/cli-helpers.d.ts +53 -24
  25. package/dist/{arg-Dni7MzLB.js → cli-prompt-egtNKkZl.js} +49 -3
  26. package/dist/cli-prompt-egtNKkZl.js.map +1 -0
  27. package/dist/cli-prompt.d.ts +19 -0
  28. package/dist/cli-scratch-database.d.ts +47 -8
  29. package/dist/cli.js +548 -94
  30. package/dist/cli.js.map +1 -1
  31. package/dist/{column-plan-helpers-drZIsSNf.js → column-plan-helpers-D_V4oaZh.js} +2 -2
  32. package/dist/{column-plan-helpers-drZIsSNf.js.map → column-plan-helpers-D_V4oaZh.js.map} +1 -1
  33. package/dist/{connection-C9mT8bgf.js → connection-C938nR3E.js} +2 -2
  34. package/dist/{connection-C9mT8bgf.js.map → connection-C938nR3E.js.map} +1 -1
  35. package/dist/{databasePoolManager-BIuewidb.js → databasePoolManager-Yqdw14wY.js} +4 -4
  36. package/dist/{databasePoolManager-BIuewidb.js.map → databasePoolManager-Yqdw14wY.js.map} +1 -1
  37. package/dist/{deferred-index-build-DP1_SNFo.js → deferred-index-build-Ca6OqKyR.js} +8 -8
  38. package/dist/{deferred-index-build-DP1_SNFo.js.map → deferred-index-build-Ca6OqKyR.js.map} +1 -1
  39. package/dist/{doctor-C4WiftHm.js → doctor-CJyuhV1n.js} +8 -8
  40. package/dist/{doctor-C4WiftHm.js.map → doctor-CJyuhV1n.js.map} +1 -1
  41. package/dist/{ensure-collection-policies-CXpbeNag.js → ensure-collection-policies-C1ux3V_z.js} +79 -18
  42. package/dist/ensure-collection-policies-C1ux3V_z.js.map +1 -0
  43. package/dist/{ensure-collection-tables-BhsU6qUE.js → ensure-collection-tables-Do-Bvb64.js} +10 -10
  44. package/dist/{ensure-collection-tables-BhsU6qUE.js.map → ensure-collection-tables-Do-Bvb64.js.map} +1 -1
  45. package/dist/{ensure-tables-CVm_yvzb.js → ensure-tables-Dl_Eh9ow.js} +5 -5
  46. package/dist/{ensure-tables-CVm_yvzb.js.map → ensure-tables-Dl_Eh9ow.js.map} +1 -1
  47. package/dist/{enum-labels-kept-D8fI1MoX.js → enum-labels-kept-D9eTIWCz.js} +3 -3
  48. package/dist/{enum-labels-kept-D8fI1MoX.js.map → enum-labels-kept-D9eTIWCz.js.map} +1 -1
  49. package/dist/{generate-drizzle-schema-Bz52jH2h.js → generate-drizzle-schema-Bf8canaR.js} +3 -3
  50. package/dist/{generate-drizzle-schema-Bz52jH2h.js.map → generate-drizzle-schema-Bf8canaR.js.map} +1 -1
  51. package/dist/{generate-drizzle-schema-logic-B74WN37S.js → generate-drizzle-schema-logic-BaT0H7Gq.js} +4 -4
  52. package/dist/{generate-drizzle-schema-logic-B74WN37S.js.map → generate-drizzle-schema-logic-BaT0H7Gq.js.map} +1 -1
  53. package/dist/{generate-files-CdEckX6c.js → generate-files-D50Pxw1G.js} +6 -47
  54. package/dist/generate-files-D50Pxw1G.js.map +1 -0
  55. package/dist/generate-next-step-DCd7yiJ2.js +66 -0
  56. package/dist/generate-next-step-DCd7yiJ2.js.map +1 -0
  57. package/dist/{generate-postgres-ddl-logic-B64O2oZF.js → generate-postgres-ddl-logic-B4k9djAV.js} +4 -4
  58. package/dist/{generate-postgres-ddl-logic-B64O2oZF.js.map → generate-postgres-ddl-logic-B4k9djAV.js.map} +1 -1
  59. package/dist/{generated-schema-staleness-Di9c4ZxN.js → generated-schema-staleness-FlxM2i4i.js} +102 -12
  60. package/dist/{generated-schema-staleness-Di9c4ZxN.js.map → generated-schema-staleness-FlxM2i4i.js.map} +1 -1
  61. package/dist/index.es.js +11 -11
  62. package/dist/{introspect-db-logic-C-Di8dA9.js → introspect-db-logic-j2vYhmdP.js} +145 -9
  63. package/dist/introspect-db-logic-j2vYhmdP.js.map +1 -0
  64. package/dist/{introspect-db-queries-D6kAqReI.js → introspect-db-queries-DevUrzll.js} +41 -6
  65. package/dist/introspect-db-queries-DevUrzll.js.map +1 -0
  66. package/dist/{open-rebase-CbOur0TU.js → open-rebase-D8rytTP0.js} +95 -45
  67. package/dist/open-rebase-D8rytTP0.js.map +1 -0
  68. package/dist/open-rebase.d.ts +12 -4
  69. package/dist/{pg-error-utils-vJezlXdI.js → pg-error-utils-CdsGYMTm.js} +10 -3
  70. package/dist/pg-error-utils-CdsGYMTm.js.map +1 -0
  71. package/dist/{plan-schema-B1YF_tYq.js → plan-schema-CsNgK0qi.js} +4 -4
  72. package/dist/{plan-schema-B1YF_tYq.js.map → plan-schema-CsNgK0qi.js.map} +1 -1
  73. package/dist/{policy-drift-Dm8i-s1J.js → policy-drift-CY5P1ZRu.js} +28 -14
  74. package/dist/{policy-drift-Dm8i-s1J.js.map → policy-drift-CY5P1ZRu.js.map} +1 -1
  75. package/dist/{push-index-prebuild-Db4AQF4i.js → push-index-prebuild-BDFcfRPo.js} +4 -4
  76. package/dist/{push-index-prebuild-Db4AQF4i.js.map → push-index-prebuild-BDFcfRPo.js.map} +1 -1
  77. package/dist/{push-legacy-renames-BK6ilQq2.js → push-legacy-renames-D4bvMWAb.js} +4 -4
  78. package/dist/{push-legacy-renames-BK6ilQq2.js.map → push-legacy-renames-D4bvMWAb.js.map} +1 -1
  79. package/dist/push-policy-plan-DTXE6aVa.js +111 -0
  80. package/dist/push-policy-plan-DTXE6aVa.js.map +1 -0
  81. package/dist/{render-ddl-CtC3y9Ol.js → render-ddl-CrfEe__B.js} +10 -9
  82. package/dist/render-ddl-CrfEe__B.js.map +1 -0
  83. package/dist/{rls-bootstrap-sql-RQUljnJ1.js → rls-bootstrap-sql-Bd-hJk08.js} +3 -58
  84. package/dist/rls-bootstrap-sql-Bd-hJk08.js.map +1 -0
  85. package/dist/{rls-enforcement-DvzqlaL1.js → rls-enforcement-D7P4-ZYX.js} +2 -2
  86. package/dist/{rls-enforcement-DvzqlaL1.js.map → rls-enforcement-D7P4-ZYX.js.map} +1 -1
  87. package/dist/{__vite-browser-external-BnuHet1e.js → rolldown-runtime-V7rFbMRe.js} +1 -8
  88. package/dist/schema/atlas-apply-report.d.ts +23 -0
  89. package/dist/schema/atlas-argv.d.ts +22 -1
  90. package/dist/schema/atlas-exclude.d.ts +42 -0
  91. package/dist/schema/atlas-revisions.d.ts +4 -0
  92. package/dist/schema/carved-out-migration.d.ts +5 -4
  93. package/dist/schema/destructive-sql.d.ts +23 -1
  94. package/dist/schema/doctor-cli.js +5 -5
  95. package/dist/schema/ensure-collection-policies.d.ts +50 -0
  96. package/dist/schema/generate-drizzle-schema.js +1 -1
  97. package/dist/schema/generate-next-step.d.ts +11 -0
  98. package/dist/schema/generate-postgres-ddl.js +2 -2
  99. package/dist/schema/generated-schema-diff.d.ts +7 -0
  100. package/dist/schema/generated-schema-staleness.d.ts +7 -1
  101. package/dist/schema/introspect-db-logic.d.ts +32 -1
  102. package/dist/schema/introspect-db-queries.d.ts +18 -5
  103. package/dist/schema/introspect-db.js +53 -52
  104. package/dist/schema/introspect-db.js.map +1 -1
  105. package/dist/schema/migrate-baseline.d.ts +51 -0
  106. package/dist/schema/plan/render-ddl.d.ts +2 -1
  107. package/dist/schema/push-policy-plan.d.ts +67 -0
  108. package/dist/schema/undeclarable-constraints.d.ts +59 -0
  109. package/dist/security/policy-drift.d.ts +5 -0
  110. package/dist/services/BranchService.d.ts +7 -0
  111. package/dist/{source-DBDbBTL4.js → source-DB6xJv3p.js} +65 -3
  112. package/dist/source-DB6xJv3p.js.map +1 -0
  113. package/dist/utils/connection-string.d.ts +47 -0
  114. package/dist/{vector-index-CMJKb3C9.js → vector-index-BFhyEaiz.js} +2 -2
  115. package/dist/{vector-index-CMJKb3C9.js.map → vector-index-BFhyEaiz.js.map} +1 -1
  116. package/dist/{websocket-BLGc5yhs.js → websocket-DMeXOoyv.js} +41 -13
  117. package/dist/websocket-DMeXOoyv.js.map +1 -0
  118. package/package.json +7 -7
  119. package/dist/BranchService-Bsn2wfaj.js.map +0 -1
  120. package/dist/__vite-browser-external-BnuHet1e.js.map +0 -1
  121. package/dist/arg-Dni7MzLB.js.map +0 -1
  122. package/dist/backup-cli-DnpJi6I7.js.map +0 -1
  123. package/dist/backup-service-BTBB7RXn.js.map +0 -1
  124. package/dist/cli-console-Ca6ZlAVU.js +0 -154
  125. package/dist/cli-console-Ca6ZlAVU.js.map +0 -1
  126. package/dist/cli-errors-CaI4ntyD.js.map +0 -1
  127. package/dist/cli-flags-JNI-pwSf.js.map +0 -1
  128. package/dist/ensure-collection-policies-CXpbeNag.js.map +0 -1
  129. package/dist/generate-files-CdEckX6c.js.map +0 -1
  130. package/dist/introspect-db-logic-C-Di8dA9.js.map +0 -1
  131. package/dist/introspect-db-queries-D6kAqReI.js.map +0 -1
  132. package/dist/open-rebase-CbOur0TU.js.map +0 -1
  133. package/dist/pg-error-utils-vJezlXdI.js.map +0 -1
  134. package/dist/render-ddl-CtC3y9Ol.js.map +0 -1
  135. package/dist/rls-bootstrap-sql-RQUljnJ1.js.map +0 -1
  136. package/dist/source-DBDbBTL4.js.map +0 -1
  137. package/dist/websocket-BLGc5yhs.js.map +0 -1
package/dist/cli.js CHANGED
@@ -1,11 +1,12 @@
1
1
  import { createRequire as __createRequire } from "module";
2
2
  __createRequire(import.meta.url);
3
- import { a as __toESM } from "./__vite-browser-external-BnuHet1e.js";
4
- import { A as queryColumnTypes, B as prepareGeneratedSqlDir, C as getForeignIndexExcludes, D as getVectorExcludes, E as getTriggerExcludes, F as resolveLocalBin, G as isSyncedPlan, H as detectDestructiveStatements, I as withAppClient, L as DESIRED_STATE_URL, M as readSearchDdl, N as readTriggersDdl, O as loadCollectionsForCli, P as readVectorDdl, R as GENERATED_SQL_DIR, S as ensureDevDatabaseExists, T as getTableExcludes, U as extractPlanStatements, V as decidePushSafety, W as fromAlterTable, _ as describeReinstallCommand, a as formatForeignGeneratedColumnBanner, b as dropDevDatabase, c as reportCommandFailure, d as applyTriggersDdl, f as applyVectorDdl, g as describeDevAddCommand, h as describeBuildScriptRemedy, i as diagnoseDbError, j as queryGeneratedColumnDependencies, k as promptConfirm, l as ExcludeIntrospectionError, m as closeAppClients, p as atlasDevDatabaseUrl, r as diagnoseAtlasFailure, t as checkDatabaseConnectivity, u as applySearchDdl, v as detectProjectPackageManager, w as getSearchExcludes, x as dropGeneratedColumns, y as diagnoseMissingBin, z as generatedSqlDir } from "./cli-errors-CaI4ntyD.js";
5
- import { a as outWarn, i as outError, r as out, t as chalk } from "./source-DBDbBTL4.js";
6
- import { o as execa, r as dropLegacyAuthSchema, s as forLibpq, t as RLS_BOOTSTRAP_SQL } from "./rls-bootstrap-sql-RQUljnJ1.js";
7
- import { t as require_arg } from "./arg-Dni7MzLB.js";
8
- import { i as parseDriverLine, n as assertKnownFlags, r as collectionsPathIn } from "./cli-flags-JNI-pwSf.js";
3
+ import { i as __toESM } from "./rolldown-runtime-V7rFbMRe.js";
4
+ import { $ as redactConnectionPasswords, A as getVectorExcludes, B as workspaceMemberDir, C as dropGeneratedColumns, D as getTableExcludes, E as getSearchExcludes, F as readSearchDdl, G as decidePushSafety, H as GENERATED_SQL_DIR, I as readTriggersDdl, J as extractPlanStatements, K as destructiveOptInFlag, L as readVectorDdl, M as queryColumnTypes, N as queryGeneratedColumnDependencies, O as getTriggerExcludes, P as queryUndeclarableConstraints, R as resolveLocalBin, S as dropDevDatabase, T as getForeignIndexExcludes, U as generatedSqlDir, V as DESIRED_STATE_URL, W as prepareGeneratedSqlDir, X as isSyncedPlan, Y as fromAlterTable, Z as forAtlas, _ as describeBuildScriptRemedy, a as formatForeignGeneratedColumnBanner, b as detectProjectPackageManager, c as isSslNotEnabled, d as ExcludeIntrospectionError, f as applySearchDdl, g as closeAppClients, h as atlasDevDatabaseUrl, i as diagnoseDbError, j as loadCollectionsForCli, k as getUndeclaredConstraintExcludes, m as applyVectorDdl, o as isAuthFailure, p as applyTriggersDdl, q as detectDestructiveStatements, r as diagnoseAtlasFailure, s as isEconnrefused, t as checkDatabaseConnectivity, u as reportCommandFailure, v as describeDevAddCommand, w as ensureDevDatabaseExists, x as diagnoseMissingBin, y as describeReinstallCommand, z as withAppClient } from "./cli-errors-mx4sSjB8.js";
5
+ import { a as outWarn, i as outError, o as atlasExcludePattern, r as out, s as excludePatternParts, t as chalk } from "./source-DB6xJv3p.js";
6
+ import { t as nextStepAfterDbGenerate } from "./generate-next-step-DCd7yiJ2.js";
7
+ import { o as execa, r as dropLegacyAuthSchema, t as RLS_BOOTSTRAP_SQL } from "./rls-bootstrap-sql-Bd-hJk08.js";
8
+ import { n as require_arg, t as promptConfirm } from "./cli-prompt-egtNKkZl.js";
9
+ import { i as collectionsPathIn, n as assertKnownFlags, o as parseDriverLine, r as closestWord, t as RELAYED_FLAGS } from "./cli-flags-DUozBFyK.js";
9
10
  import { formatRelativeTime } from "@rebasepro/utils";
10
11
  import path from "path";
11
12
  import fs from "fs";
@@ -147,7 +148,7 @@ function partitionByOwnership(conflicts, excludePatterns) {
147
148
  const managed = [];
148
149
  const foreign = [];
149
150
  for (const conflict of conflicts) {
150
- const target = `${conflict.schema}.${conflict.table}.${conflict.column}`;
151
+ const target = atlasExcludePattern(conflict.schema, conflict.table, conflict.column);
151
152
  (owned.has(target) ? managed : foreign).push(conflict);
152
153
  }
153
154
  return {
@@ -242,16 +243,17 @@ var DROP_INDEX_RE = new RegExp(`^DROP\\s+INDEX\\s+(?:CONCURRENTLY\\s+)?(?:IF\\s+
242
243
  */
243
244
  var MENTIONS_DROP = /\bDROP\b/i;
244
245
  /**
245
- * Split `schema.table.object` patterns. Anything not in three parts is
246
- * ignored rather than guessed at — the two-part form is the one Atlas itself
247
- * silently mis-reads, and repeating that mistake here would be worse than
248
- * doing nothing.
246
+ * Split `schema.table.object` patterns back into the names they were built
247
+ * from (`excludePatternParts`, the inverse of how every exclude is spelled).
248
+ * Anything not in three parts is ignored rather than guessed at — the two-part
249
+ * form is the one Atlas itself silently mis-reads, and repeating that mistake
250
+ * here would be worse than doing nothing.
249
251
  */
250
252
  function parseExcludePatterns(patterns) {
251
253
  const parsed = [];
252
254
  for (const pattern of patterns) {
253
- const parts = pattern.split(".");
254
- if (parts.length !== 3) continue;
255
+ const parts = excludePatternParts(pattern);
256
+ if (parts === void 0 || parts.length !== 3) continue;
255
257
  if (parts.some((p) => p.length === 0)) continue;
256
258
  parsed.push({
257
259
  schema: parts[0],
@@ -638,18 +640,36 @@ function acceptsExcludeFlag(domain, args) {
638
640
  function takesDevUrl(domain, args) {
639
641
  return domain === "schema" && args.includes("apply") || domain === "migrate" && args.includes("diff");
640
642
  }
641
- /** Assemble the full argv, connection flags and all. */
643
+ /**
644
+ * Does this Atlas invocation read or write the revisions table
645
+ * (`rebase.atlas_schema_revisions`)? `migrate apply`, `set` and `status` do;
646
+ * they are the ones handed `--revisions-schema`.
647
+ */
648
+ function readsRevisions(domain, args) {
649
+ return domain === "migrate" && (args.includes("apply") || args.includes("status") || args.includes("set"));
650
+ }
651
+ /**
652
+ * Assemble the full argv, connection flags and all.
653
+ *
654
+ * On a single-session database `migrate apply` skips its advisory lock. The
655
+ * lock is session-level, and a session every connection shares grants it to
656
+ * all of them, so it excludes no one; what it does cost there is a connection.
657
+ * The managed database accepts four and a running `rebase dev` holds two, so
658
+ * `migrate apply`'s third — its own, the lock's, the revisions table's — was
659
+ * closed unanswered and Atlas said only `Error: EOF`.
660
+ */
642
661
  function buildAtlasArgs(invocation) {
643
- const { domain, args, url, devUrl, excludes = [] } = invocation;
662
+ const { domain, args, url, devUrl, excludes = [], singleSession = false } = invocation;
644
663
  const argv = [domain, ...args];
645
664
  if (domain === "schema") {
646
665
  if (args.includes("apply")) argv.push("--url", url, "--dev-url", devUrl);
647
666
  else if (args.includes("clean") || args.includes("inspect")) argv.push("--url", url);
648
667
  } else if (domain === "migrate") {
649
668
  if (args.includes("diff")) argv.push("--dev-url", devUrl);
650
- else if (args.includes("apply") || args.includes("status")) {
669
+ else if (readsRevisions(domain, args)) {
651
670
  argv.push("--url", url, "--revisions-schema", "rebase");
652
671
  if (args.includes("apply") && !args.includes("--baseline")) argv.push("--allow-dirty");
672
+ if (args.includes("apply") && singleSession) argv.push("--skip-lock");
653
673
  }
654
674
  }
655
675
  if (acceptsExcludeFlag(domain, args)) for (const exclude of excludes) argv.push("--exclude", exclude);
@@ -672,10 +692,111 @@ function migrateApplyArgs(positionals, baseline) {
672
692
  ];
673
693
  }
674
694
  //#endregion
695
+ //#region src/schema/atlas-revisions.ts
696
+ /**
697
+ * Atlas's revisions table on the managed development database, made by Rebase.
698
+ *
699
+ * `rebase db migrate` passes `--revisions-schema rebase`, so Atlas keeps its
700
+ * history in `rebase.atlas_schema_revisions`. Atlas finds that table by
701
+ * qualified name, but creates it through a second connection whose startup
702
+ * message sets `search_path=rebase`, with an unqualified `CREATE TABLE`. The
703
+ * managed database is one PGlite session that every connection shares, so a
704
+ * connection's own `search_path` is never applied: the table landed in
705
+ * `public`, the read that followed failed on `relation
706
+ * "rebase.atlas_schema_revisions" does not exist (42P01)`, and every later run
707
+ * failed on `relation "atlas_schema_revisions" already exists (42P07)`, with or
708
+ * without `--baseline`.
709
+ *
710
+ * So on that database the table is created here first, qualified, and Atlas
711
+ * finds it where it looks. The definition is Atlas's own for the pinned
712
+ * `@ariga/atlas`, read off a table Atlas created on PostgreSQL 18. Should a
713
+ * later Atlas want a different one, it alters the table through the same
714
+ * unqualified path and fails loudly there, and the managed `db migrate`
715
+ * integration test fails with it.
716
+ *
717
+ * Not used on any other database: there the connection's `search_path` holds,
718
+ * and Atlas creates and maintains its table itself.
719
+ */
720
+ /** Atlas's revisions table, in the schema `--revisions-schema rebase` names. */
721
+ var ATLAS_REVISIONS_TABLE_SQL = `CREATE SCHEMA IF NOT EXISTS "rebase";
722
+ CREATE TABLE IF NOT EXISTS "rebase"."atlas_schema_revisions" (
723
+ "version" character varying NOT NULL,
724
+ "description" character varying NOT NULL,
725
+ "type" bigint NOT NULL DEFAULT 2,
726
+ "applied" bigint NOT NULL DEFAULT 0,
727
+ "total" bigint NOT NULL DEFAULT 0,
728
+ "executed_at" timestamp with time zone NOT NULL,
729
+ "execution_time" bigint NOT NULL,
730
+ "error" text NULL,
731
+ "error_stmt" text NULL,
732
+ "hash" character varying NOT NULL,
733
+ "partial_hashes" jsonb NULL,
734
+ "operator_version" character varying NOT NULL,
735
+ PRIMARY KEY ("version")
736
+ );`;
737
+ /** Create `rebase.atlas_schema_revisions` if it is not there yet. */
738
+ async function ensureAtlasRevisionsTable(databaseUrl) {
739
+ await withAppClient(databaseUrl, (client) => client.query(ATLAS_REVISIONS_TABLE_SQL));
740
+ }
741
+ //#endregion
742
+ //#region src/schema/atlas-apply-report.ts
743
+ /**
744
+ * Atlas's `schema apply --auto-approve` report, as `rebase db push` shows it.
745
+ *
746
+ * Atlas prints its plan ("Planning migration statements …"), a separator, and
747
+ * then the run ("Applying approved migration …"), echoing every statement in
748
+ * both. The push has planned already — the plan it gated on, which a
749
+ * destructive change listed in full — so the first section is a repeat, and a
750
+ * reader saw each statement twice in a row. The run's section is kept: it is
751
+ * the progress, the `-- ok` per statement and the summary.
752
+ */
753
+ var PLANNING_HEADER = /^Planning migration statements\b/;
754
+ /** The separator Atlas ends a section with, at the start of a line. */
755
+ var SECTION_SEPARATOR = /^-{3,}\s*$/;
756
+ /**
757
+ * A stream filter that drops the planning section of an apply report, its
758
+ * separator and the blank line after it, and passes everything else through
759
+ * unchanged. A report with no planning section (`Schema is synced…`, an
760
+ * error) comes out as it went in.
761
+ */
762
+ function createApplyReportFilter() {
763
+ let pending = "";
764
+ let state = "pass";
765
+ const keep = (line) => {
766
+ if (state === "pass" && PLANNING_HEADER.test(line)) {
767
+ state = "planning";
768
+ return false;
769
+ }
770
+ if (state === "planning") {
771
+ if (SECTION_SEPARATOR.test(line)) state = "after-separator";
772
+ return false;
773
+ }
774
+ if (state === "after-separator") {
775
+ state = "pass";
776
+ if (line.trim() === "") return false;
777
+ }
778
+ return true;
779
+ };
780
+ return {
781
+ push(chunk) {
782
+ pending += chunk;
783
+ const lines = pending.split("\n");
784
+ pending = lines.pop() ?? "";
785
+ return lines.filter(keep).map((line) => `${line}\n`).join("");
786
+ },
787
+ flush() {
788
+ const rest = pending;
789
+ pending = "";
790
+ return rest !== "" && keep(rest) ? rest : "";
791
+ }
792
+ };
793
+ }
794
+ //#endregion
675
795
  //#region src/cli-scratch-database.ts
676
796
  var ScratchDatabase = class {
677
797
  ops;
678
798
  created;
799
+ keptAsEvidence = false;
679
800
  constructor(ops) {
680
801
  this.ops = ops;
681
802
  }
@@ -686,21 +807,166 @@ var ScratchDatabase = class {
686
807
  devDatabaseUrl
687
808
  };
688
809
  }
810
+ /** An Atlas run against it failed: keep it, whatever the command does next. */
811
+ keepAsEvidence() {
812
+ this.keptAsEvidence = true;
813
+ }
689
814
  /**
690
- * Run a command, then drop the scratch database if the command created it.
691
- * A command that throws skips the drop; one that exits the process never
692
- * returns here, which is the same thing.
815
+ * An Atlas run failed with `stderr`. Failing before it planned, it left
816
+ * nothing here to read, so the database is dropped now — before the
817
+ * command exits, which may not unwind through {@link around}. Otherwise
818
+ * it is kept as the evidence.
819
+ */
820
+ async afterAtlasFailure(stderr) {
821
+ if (atlasFailedBeforePlanning(stderr)) await this.release();
822
+ else this.keepAsEvidence();
823
+ }
824
+ /**
825
+ * Run a command, then drop the scratch database if the command created it,
826
+ * whether the command returned or threw. A throw after
827
+ * {@link keepAsEvidence} keeps it. A command that wants to stop with exit 1
828
+ * throws {@link CommandStopped} rather than calling `process.exit`, which
829
+ * would skip this.
693
830
  */
694
831
  async around(command) {
695
- const result = await command();
696
- const created = this.created;
697
- if (created) {
698
- this.created = void 0;
699
- await this.ops.drop(created.databaseUrl, created.devDatabaseUrl);
832
+ let result;
833
+ try {
834
+ result = await command();
835
+ } catch (err) {
836
+ if (!this.keptAsEvidence) await this.release();
837
+ throw err;
700
838
  }
839
+ await this.release();
701
840
  return result;
702
841
  }
842
+ /** Drop it now if this process created it. Safe to call twice. */
843
+ async release() {
844
+ const created = this.created;
845
+ if (!created) return;
846
+ this.created = void 0;
847
+ await this.ops.drop(created.databaseUrl, created.devDatabaseUrl);
848
+ }
703
849
  };
850
+ /**
851
+ * Whether Atlas failed before it planned anything: on connecting, which it
852
+ * reports as `querying system variables: …` — SSL the server does not offer, a
853
+ * login refused, a socket that closed or never opened.
854
+ */
855
+ function atlasFailedBeforePlanning(stderr) {
856
+ return /querying system variables:/.test(stderr);
857
+ }
858
+ /**
859
+ * A db command stopping with exit 1 after it has said why: a destructive push
860
+ * the user declined, or one with no terminal to ask on.
861
+ *
862
+ * Thrown instead of `process.exit(1)`, so it unwinds through
863
+ * {@link ScratchDatabase.around} and the scratch database is dropped on the way
864
+ * out. `alreadyReported` tells `reportCommandFailure` the explanation is on the
865
+ * terminal already; the entry point sets the exit code.
866
+ */
867
+ var CommandStopped = class extends Error {
868
+ alreadyReported = true;
869
+ constructor(reason) {
870
+ super(reason);
871
+ this.name = "CommandStopped";
872
+ }
873
+ };
874
+ //#endregion
875
+ //#region src/schema/migrate-baseline.ts
876
+ /**
877
+ * What `rebase db migrate --baseline <version>` does, given the history the
878
+ * database already has.
879
+ *
880
+ * The flag promises to record `<version>` as applied and migrate from the next
881
+ * one. Atlas's own `migrate apply --baseline` keeps that promise only on a
882
+ * database with no revision history: once one row exists it ignores the flag
883
+ * without a word, applies the migration anyway, and the CLI had already printed
884
+ * "Recording … as already applied". That is the normal second time: a project
885
+ * baselines once, then `rebase dev` adds a column at boot, `db generate` writes
886
+ * the migration for it, and `db migrate` fails on `column … already exists
887
+ * (42701)` however the baseline is spelled.
888
+ *
889
+ * So the history is read first. With none, Atlas's baseline does the work.
890
+ * Behind `<version>`, `atlas migrate set` marks every migration up to it as
891
+ * applied, which is what a baseline means. At or past it, there is nothing to
892
+ * record, and the command says so instead of claiming it recorded something.
893
+ */
894
+ /**
895
+ * Atlas's version order for the versions Rebase writes: numeric timestamps,
896
+ * compared as numbers so a shorter one is not read as later; anything else as
897
+ * text.
898
+ */
899
+ function compareMigrationVersions(a, b) {
900
+ if (/^\d+$/.test(a) && /^\d+$/.test(b)) {
901
+ const x = BigInt(a);
902
+ const y = BigInt(b);
903
+ return x === y ? 0 : x < y ? -1 : 1;
904
+ }
905
+ return a.localeCompare(b);
906
+ }
907
+ /**
908
+ * The version Atlas records for a migration file: its numeric prefix
909
+ * (`20260906101530_init.sql` → `20260906101530`), or the name without `.sql`.
910
+ */
911
+ function migrationVersionOf(file) {
912
+ return /^(\d+)/.exec(file)?.[1] ?? file.replace(/\.sql$/, "");
913
+ }
914
+ /** How many versions a refused baseline names, newest last. */
915
+ var VERSIONS_NAMED = 5;
916
+ /**
917
+ * Why `--baseline <version>` cannot run, or `null` when it names a migration
918
+ * file in `drizzle/migrations`.
919
+ *
920
+ * Asked before anything else, the database included. A version no file has
921
+ * was refused only by Atlas, after the command had said "Marking every
922
+ * migration up to abc as applied…"; and one older than every file read
923
+ * "Nothing to record" and migrated on, though it named nothing to baseline.
924
+ *
925
+ * @param files the migration files, sorted oldest first.
926
+ * @returns the refusal, its first line the error and the rest the detail.
927
+ */
928
+ function baselineVersionProblem(version, files) {
929
+ const versions = files.map(migrationVersionOf);
930
+ if (versions.length === 0) return `--baseline ${version}: There are no migrations in drizzle/migrations to baseline. Nothing was run.\nRun \`rebase db generate\` to write the first one.`;
931
+ if (versions.includes(version)) return null;
932
+ const named = versions.slice(-5).join(", ");
933
+ return `--baseline ${version} names no migration in drizzle/migrations. Nothing was run.\n${versions.length > VERSIONS_NAMED ? `The newest ${VERSIONS_NAMED} of the ${versions.length} versions there` : "The versions there"}: ${named}. A baseline is one of them: the newest migration the database already holds.`;
934
+ }
935
+ /** How to keep `--baseline <version>`'s promise, given the newest recorded version. */
936
+ function planBaseline(version, latestRecorded) {
937
+ if (latestRecorded === null) return { kind: "atlas-baseline" };
938
+ if (compareMigrationVersions(version, latestRecorded) > 0) return {
939
+ kind: "set",
940
+ latest: latestRecorded
941
+ };
942
+ return {
943
+ kind: "already-recorded",
944
+ latest: latestRecorded
945
+ };
946
+ }
947
+ /** What the command says before it migrates, for each plan. */
948
+ function describeBaselinePlan(version, plan) {
949
+ switch (plan.kind) {
950
+ case "atlas-baseline": return `Recording ${version} as already applied, then migrating from the next one.`;
951
+ case "set": return `This database already records migrations up to ${plan.latest}, and Atlas applies --baseline only to a database with none.\n Marking every migration up to ${version} as applied with \`atlas migrate set\`, then migrating from the next one.`;
952
+ case "already-recorded": return `Nothing to record: this database already records migrations up to ${plan.latest}, which includes ${version}.\n Migrating from the next one.`;
953
+ }
954
+ }
955
+ /**
956
+ * The newest version Atlas has recorded on this database, or `null` when it
957
+ * has recorded none (no revisions table yet included).
958
+ *
959
+ * The table is `rebase.atlas_schema_revisions`: the CLI passes
960
+ * `--revisions-schema rebase` to every `migrate apply`.
961
+ */
962
+ async function latestRecordedMigration(databaseUrl) {
963
+ return withAppClient(databaseUrl, async (client) => {
964
+ if (!(await client.query("SELECT to_regclass('rebase.atlas_schema_revisions') IS NOT NULL AS present")).rows[0]?.present) return null;
965
+ const versions = (await client.query("SELECT version FROM rebase.atlas_schema_revisions WHERE version NOT LIKE '.%'")).rows.map((row) => row.version);
966
+ if (versions.length === 0) return null;
967
+ return versions.reduce((latest, version) => compareMigrationVersions(version, latest) > 0 ? version : latest);
968
+ });
969
+ }
704
970
  //#endregion
705
971
  //#region src/cli-push.ts
706
972
  /**
@@ -788,7 +1054,7 @@ async function applyPoliciesFile(client, policiesSql, stamping) {
788
1054
  //#endregion
789
1055
  //#region src/branch-argv.ts
790
1056
  /**
791
- * Argument shapes for `rebase db branch`, with no dependencies.
1057
+ * Argument shapes for `rebase db branch`, with no runtime dependencies.
792
1058
  *
793
1059
  * Its own module for the same reason `schema/atlas-argv.ts` is: `cli.ts` pulls
794
1060
  * in chalk, execa and the driver runtime, so a unit test that imports it cannot
@@ -796,6 +1062,58 @@ async function applyPoliciesFile(client, policiesSql, stamping) {
796
1062
  * of any of that.
797
1063
  */
798
1064
  /**
1065
+ * The flags each driver-side `db branch` action takes, besides the ones the CLI
1066
+ * relays on every line (`--database-url`, `--docker`, `--debug`).
1067
+ *
1068
+ * `switch` is absent: the CLI answers it before the driver is ever spawned.
1069
+ * An action not listed here is not checked, so the dispatch can say it does
1070
+ * not exist in its own words.
1071
+ */
1072
+ var BRANCH_ACTION_FLAGS = {
1073
+ create: {
1074
+ "--from": String,
1075
+ "--force": Boolean
1076
+ },
1077
+ list: {},
1078
+ info: {},
1079
+ delete: {
1080
+ "--force": Boolean,
1081
+ "--yes": Boolean,
1082
+ "-y": "--yes"
1083
+ },
1084
+ prune: {
1085
+ "--older-than": String,
1086
+ "--include-dev-diff": Boolean,
1087
+ "--yes": Boolean,
1088
+ "-y": "--yes"
1089
+ }
1090
+ };
1091
+ /**
1092
+ * Refuse a flag `action` does not take, naming the one it was probably meant
1093
+ * to be — the same sentence `db backup` and `db restore` say.
1094
+ *
1095
+ * Throws: the driver's entry turns a thrown error into one red line and exit 1,
1096
+ * and this runs before anything connects.
1097
+ */
1098
+ function assertKnownBranchFlags(action, rest) {
1099
+ const spec = BRANCH_ACTION_FLAGS[action];
1100
+ if (!spec) return;
1101
+ const full = {
1102
+ ...RELAYED_FLAGS,
1103
+ ...spec
1104
+ };
1105
+ try {
1106
+ (0, import_arg.default)(full, { argv: [...rest] });
1107
+ } catch (error) {
1108
+ if (!(error instanceof Error) || !("code" in error) || error.code !== "ARG_UNKNOWN_OPTION") throw error;
1109
+ const typed = (/option: (\S+)$/.exec(error.message)?.[1] ?? error.message).split("=")[0];
1110
+ const suggestion = closestWord(typed, Object.keys(full).filter((flag) => flag.startsWith("--")));
1111
+ const own = Object.keys(spec).filter((flag) => flag.startsWith("--"));
1112
+ const takes = own.length > 0 ? `it takes ${own.join(", ")}` : "it takes no options of its own";
1113
+ throw new Error(`\`rebase db branch ${action}\` has no option ${typed}` + (suggestion ? ` — did you mean ${suggestion}?` : ` — ${takes}.`) + " Nothing was run. See `rebase db branch --help`.");
1114
+ }
1115
+ }
1116
+ /**
799
1117
  * Words on a `db branch` command line that no argument accounts for.
800
1118
  *
801
1119
  * `rebase db branch create alpha beta` created a branch called `alpha` and
@@ -806,15 +1124,18 @@ async function applyPoliciesFile(client, policiesSql, stamping) {
806
1124
  * the branch is not the one asked for — and branch names are the thing you
807
1125
  * later type to switch, delete, or point a deploy at.
808
1126
  *
809
- * Anything starting with `-` is left alone rather than validated. This runs on
810
- * every branch subcommand, including ones added later with flags this function
811
- * has never heard of, and rejecting an unrecognised flag here would break them
812
- * from a distance. The value after a flag that takes one is skipped for the
813
- * same reason: it is that flag's argument, not a stray word.
1127
+ * A flag the action does not take is refused outright, by throwing — see
1128
+ * {@link BRANCH_ACTION_FLAGS}. It was let through on the reasoning that a
1129
+ * subcommand added later might take flags this file had never heard of; what
1130
+ * that bought was every typo running without the flag it meant:
1131
+ * `prune --older-then 1d --yes` pruned by no age, `create x --froce` ran
1132
+ * without --force. A new action gets its line in the table. The value after a
1133
+ * flag that takes one is skipped: it is that flag's argument, not a stray word.
814
1134
  *
815
1135
  * @param words The command's own arguments: action, name, then the rest.
816
1136
  */
817
1137
  function unexpectedBranchArgs(words) {
1138
+ if (words[0]) assertKnownBranchFlags(words[0], words.slice(1));
818
1139
  const extras = [];
819
1140
  let seenName = false;
820
1141
  for (let index = 1; index < words.length; index += 1) {
@@ -1061,7 +1382,7 @@ function generatesInProcess() {
1061
1382
  async function generateInProcess(what, label, run) {
1062
1383
  let generators;
1063
1384
  try {
1064
- generators = await import("./generate-files-CdEckX6c.js").then((n) => n.t);
1385
+ generators = await import("./generate-files-D50Pxw1G.js").then((n) => n.t);
1065
1386
  } catch (err) {
1066
1387
  outError(chalk.red(`✗ Failed to run ${what}: ${err instanceof Error ? err.message : String(err)}`));
1067
1388
  process.exit(1);
@@ -1107,7 +1428,7 @@ async function runPluginCommand(args) {
1107
1428
  else if (domain === "schema") await schemaCommand(subcommand, args);
1108
1429
  else if (domain === "doctor") await doctorPluginCommand(args);
1109
1430
  else if (domain === "console") {
1110
- const { consoleCommand } = await import("./cli-console-Ca6ZlAVU.js");
1431
+ const { consoleCommand } = await import("./cli-console-C_ISGwJo.js");
1111
1432
  process.exitCode = await consoleCommand(args);
1112
1433
  } else {
1113
1434
  outError(chalk.red(`Unknown domain command: ${domain}`));
@@ -1208,6 +1529,20 @@ async function columnTypesOrNone(databaseUrl) {
1208
1529
  }
1209
1530
  }
1210
1531
  /**
1532
+ * The CHECK and EXCLUDE constraints `detectDestructiveStatements` reads a
1533
+ * `DROP CONSTRAINT` against, or none when the database cannot be asked. A
1534
+ * report then cannot tell a dropped CHECK from a dropped foreign key, and says
1535
+ * nothing about either, as before.
1536
+ */
1537
+ async function constraintsOrNone(databaseUrl) {
1538
+ if (!databaseUrl) return [];
1539
+ try {
1540
+ return await queryUndeclarableConstraints(databaseUrl);
1541
+ } catch {
1542
+ return [];
1543
+ }
1544
+ }
1545
+ /**
1211
1546
  * Say what in the migration `db generate` just wrote destroys data.
1212
1547
  *
1213
1548
  * `db push` stops before a destructive change; `db generate` wrote the same
@@ -1217,7 +1552,7 @@ async function columnTypesOrNone(databaseUrl) {
1217
1552
  * A report, not a refusal — the exit code is unchanged.
1218
1553
  */
1219
1554
  async function reportDestructiveMigration(migrationSql, migrationFile, databaseUrl) {
1220
- const destructive = detectDestructiveStatements(migrationSql, await columnTypesOrNone(databaseUrl));
1555
+ const destructive = detectDestructiveStatements(migrationSql, await columnTypesOrNone(databaseUrl), await constraintsOrNone(databaseUrl));
1221
1556
  if (destructive.length === 0) return;
1222
1557
  outWarn("");
1223
1558
  outWarn(chalk.yellow(` ⚠️ ${migrationFile} includes ${destructive.length} destructive change(s) that will DESTROY data when applied:`));
@@ -1269,7 +1604,7 @@ async function dbCommand(subcommand, rawArgs) {
1269
1604
  return;
1270
1605
  }
1271
1606
  if (subcommand === "backup" || subcommand === "restore" || subcommand === "backups") {
1272
- const { backupCommand, restoreCommand, backupsCommand } = await import("./backup-cli-DnpJi6I7.js");
1607
+ const { backupCommand, restoreCommand, backupsCommand } = await import("./backup-cli-HCEkPcQj.js");
1273
1608
  if (subcommand === "backup" && backupActionOf(rawArgs) === "list") await backupsCommand(rawArgs);
1274
1609
  else if (subcommand === "backup") await backupCommand(rawArgs);
1275
1610
  else if (subcommand === "restore") await restoreCommand(rawArgs);
@@ -1316,7 +1651,7 @@ async function dbCommand(subcommand, rawArgs) {
1316
1651
  }
1317
1652
  const searchContent = readSearchDdl();
1318
1653
  if (searchContent) {
1319
- const { searchColumnDependencies } = await import("./generate-postgres-ddl-logic-B64O2oZF.js").then((n) => n.o);
1654
+ const { searchColumnDependencies } = await import("./generate-postgres-ddl-logic-B4k9djAV.js").then((n) => n.o);
1320
1655
  const migrationConflicts = findGeneratedColumnConflicts(parseColumnMutations(migrationContent), searchColumnDependencies(await loadCollectionsForCli(collectionsPath)));
1321
1656
  const preamble = migrationDropPreamble(migrationConflicts);
1322
1657
  if (preamble) {
@@ -1365,7 +1700,7 @@ async function dbCommand(subcommand, rawArgs) {
1365
1700
  boot by the schema ensure. For a migration-only deployment, add
1366
1701
  ${GENERATED_SQL_DIR}/search.sql, vector.sql, triggers.sql and\n policies.sql to a migration by hand.`));
1367
1702
  out("");
1368
- out(` You can now run ${chalk.bold.green("rebase db migrate")} to apply the migrations to your database.`);
1703
+ for (const line of nextStepAfterDbGenerate(latestMigrationVersion())) out(line);
1369
1704
  out("");
1370
1705
  } else {
1371
1706
  out("");
@@ -1391,20 +1726,44 @@ async function dbCommand(subcommand, rawArgs) {
1391
1726
  DESIRED_STATE_URL,
1392
1727
  "--dry-run"
1393
1728
  ], collectionsPath, { captureStdout: true });
1394
- const destructive = detectDestructiveStatements(plan, databaseUrl ? await queryColumnTypes(databaseUrl) : []);
1729
+ const destructive = detectDestructiveStatements(plan, databaseUrl ? await queryColumnTypes(databaseUrl) : [], databaseUrl ? await queryUndeclarableConstraints(databaseUrl) : []);
1395
1730
  const unreadable = destructive.some((d) => d.kind === "UNREADABLE PLAN");
1396
1731
  const planStatements = extractPlanStatements(plan);
1397
1732
  const planListing = unreadable ? plan.trim() : planStatements.map((statement) => `${statement};`).join("\n");
1398
1733
  const { managed: rebuildable, foreign: unowned } = partitionByOwnership(databaseUrl ? findGeneratedColumnConflicts(parseColumnMutations(plan), await queryGeneratedColumnDependencies(databaseUrl)) : [], collectionsPath ? await getSearchExcludes(collectionsPath) : []);
1399
1734
  if (databaseUrl) await reportKeptEnumLabels(databaseUrl, collectionsPath);
1735
+ const policyPlan = databaseUrl ? await readPolicyPushPlan(databaseUrl, collectionsPath) : void 0;
1736
+ const policyLines = policyPlan ?? {
1737
+ changes: [],
1738
+ notes: []
1739
+ };
1400
1740
  if (dryRun) {
1401
1741
  out(chalk.gray(" Step 3/3: --dry-run — nothing was applied."));
1402
1742
  out("");
1403
- if (planStatements.length > 0 || unreadable) {
1743
+ const schemaChanges = planStatements.length > 0 || unreadable;
1744
+ if (schemaChanges) {
1404
1745
  out(chalk.bold(" Planned changes:"));
1405
1746
  out(chalk.gray(planListing.split("\n").map((l) => ` ${l}`).join("\n")));
1406
- } else out(chalk.green(" ✓ No changes: the database already matches these collections."));
1407
- out("");
1747
+ out("");
1748
+ }
1749
+ const [rlsHeading, ...rlsChanges] = policyLines.changes;
1750
+ if (rlsHeading) {
1751
+ out(chalk.bold(rlsHeading));
1752
+ for (const line of rlsChanges) out(chalk.gray(line));
1753
+ out("");
1754
+ }
1755
+ if (!schemaChanges && !rlsHeading) {
1756
+ out(chalk.green(policyPlan ? " ✓ No changes: the database already matches these collections." : " ✓ No schema changes: the tables already match these collections."));
1757
+ out("");
1758
+ }
1759
+ if (databaseUrl && !policyPlan) {
1760
+ outWarn(chalk.yellow(" ⚠️ The row-level security the push would change could not be read, so none is listed."));
1761
+ outWarn("");
1762
+ }
1763
+ if (policyLines.notes.length > 0) {
1764
+ for (const line of policyLines.notes) out(chalk.gray(line));
1765
+ out("");
1766
+ }
1408
1767
  if (prebuilt.length > 0) {
1409
1768
  out(chalk.gray(` ${prebuilt.length} foreign-key index(es) on existing tables would be built first,`));
1410
1769
  out(chalk.gray(" CONCURRENTLY, so the apply would not build them under a write lock (the plan"));
@@ -1433,12 +1792,12 @@ async function dbCommand(subcommand, rawArgs) {
1433
1792
  }
1434
1793
  if (unowned.length > 0) {
1435
1794
  outError(formatForeignGeneratedColumnBanner(unowned));
1436
- process.exit(1);
1795
+ throw new CommandStopped("A generated column Rebase does not own blocks the push.");
1437
1796
  }
1438
- const allowDestructive = argsList["--allow-destructive"] === true || argsList["--yes"] === true;
1797
+ const optIn = destructiveOptInFlag(argsList);
1439
1798
  const decision = decidePushSafety({
1440
1799
  destructiveCount: destructive.length,
1441
- allowDestructive,
1800
+ allowDestructive: optIn !== void 0,
1442
1801
  interactive: process.stdin.isTTY === true
1443
1802
  });
1444
1803
  if (destructive.length > 0) {
@@ -1452,14 +1811,18 @@ async function dbCommand(subcommand, rawArgs) {
1452
1811
  if (decision === "refuse") {
1453
1812
  outError(chalk.red(" ✗ Aborting: destructive changes require confirmation."));
1454
1813
  outError(chalk.gray(" Re-run interactively, or pass --allow-destructive to proceed. Back up first: rebase db backup"));
1455
- process.exit(1);
1814
+ throw new CommandStopped("Destructive changes need confirmation.");
1456
1815
  }
1457
1816
  if (decision === "confirm") {
1458
1817
  if (!await promptConfirm(chalk.yellow(" Type 'yes' to apply these destructive changes (this cannot be undone): "))) {
1459
1818
  out(chalk.gray(" Aborted. No changes were made."));
1460
- process.exit(1);
1819
+ throw new CommandStopped("The destructive push was declined.");
1461
1820
  }
1462
- } else outWarn(chalk.yellow(" Proceeding because --allow-destructive was passed."));
1821
+ } else outWarn(chalk.yellow(` Proceeding because ${optIn ?? "--allow-destructive"} was passed.`));
1822
+ }
1823
+ if (policyPlan?.summary) {
1824
+ out(chalk.gray(policyPlan.summary));
1825
+ out("");
1463
1826
  }
1464
1827
  await applyPushPlan({
1465
1828
  dryRun: plan,
@@ -1476,7 +1839,10 @@ async function dbCommand(subcommand, rawArgs) {
1476
1839
  "--to",
1477
1840
  DESIRED_STATE_URL,
1478
1841
  "--auto-approve"
1479
- ], collectionsPath, { throwOnFailure: true });
1842
+ ], collectionsPath, {
1843
+ throwOnFailure: true,
1844
+ omitPlanEcho: true
1845
+ });
1480
1846
  },
1481
1847
  reportSynced: () => out("Schema is synced, no changes to be made"),
1482
1848
  rebuildAfterFailure: async () => {
@@ -1504,9 +1870,18 @@ async function dbCommand(subcommand, rawArgs) {
1504
1870
  } else if (subcommand === "migrate") {
1505
1871
  const databaseUrl = process.env.DATABASE_URL;
1506
1872
  const baseline = argsList["--baseline"];
1873
+ const baselineProblem = baseline ? baselineVersionProblem(baseline, listMigrationFiles()) : null;
1874
+ if (baselineProblem) {
1875
+ const [message, ...detail] = baselineProblem.split("\n");
1876
+ outError(chalk.red(` ✗ ${message}`));
1877
+ for (const line of detail) outError(chalk.gray(` ${line}`));
1878
+ throw new CommandStopped("The baseline names no migration.");
1879
+ }
1880
+ const baselinePlan = baseline ? planBaseline(baseline, databaseUrl ? await latestRecordedMigration(databaseUrl) : null) : null;
1881
+ const atlasBaseline = baselinePlan?.kind === "atlas-baseline" ? baseline : void 0;
1507
1882
  if (argsList["--refuse-destructive"] === true) {
1508
- const plan = await runAtlas("migrate", [...migrateApplyArgs(argsList._, baseline), "--dry-run"], collectionsPath, { captureStdout: true });
1509
- const destructive = detectDestructiveStatements(plan, await columnTypesOrNone(databaseUrl));
1883
+ const plan = await runAtlas("migrate", [...migrateApplyArgs(argsList._, atlasBaseline), "--dry-run"], collectionsPath, { captureStdout: true });
1884
+ const destructive = detectDestructiveStatements(plan, await columnTypesOrNone(databaseUrl), await constraintsOrNone(databaseUrl));
1510
1885
  if (destructive.length > 0) {
1511
1886
  outError(chalk.red(` ✗ Refusing to migrate: the pending migrations include ${destructive.length} destructive change(s):`));
1512
1887
  outError("");
@@ -1520,11 +1895,17 @@ async function dbCommand(subcommand, rawArgs) {
1520
1895
  out("");
1521
1896
  }
1522
1897
  if (databaseUrl) await ensureAuthSchemaAndFunctions(databaseUrl);
1523
- if (baseline) {
1524
- out(chalk.gray(` Recording ${baseline} as already applied, then migrating from the next one.`));
1898
+ if (baseline && baselinePlan) {
1899
+ out(chalk.gray(` ${describeBaselinePlan(baseline, baselinePlan)}`));
1525
1900
  out("");
1901
+ if (baselinePlan.kind === "set") await runAtlas("migrate", [
1902
+ "set",
1903
+ baseline,
1904
+ "--dir",
1905
+ "file://drizzle/migrations"
1906
+ ], collectionsPath);
1526
1907
  }
1527
- await runAtlas("migrate", migrateApplyArgs(argsList._, baseline), collectionsPath);
1908
+ await runAtlas("migrate", migrateApplyArgs(argsList._, atlasBaseline), collectionsPath);
1528
1909
  if (databaseUrl) {
1529
1910
  await ensureRlsUserRole(databaseUrl);
1530
1911
  await retireLegacyAuthSchema(databaseUrl);
@@ -1549,8 +1930,8 @@ async function dbCommand(subcommand, rawArgs) {
1549
1930
  async function renameLegacyForeignKeys(databaseUrl, collectionsPath, dryRun) {
1550
1931
  let renames;
1551
1932
  try {
1552
- const { loadCollections } = await import("./doctor-C4WiftHm.js").then((n) => n.t);
1553
- const { legacyForeignKeyRenames } = await import("./push-legacy-renames-BK6ilQq2.js");
1933
+ const { loadCollections } = await import("./doctor-CJyuhV1n.js").then((n) => n.t);
1934
+ const { legacyForeignKeyRenames } = await import("./push-legacy-renames-D4bvMWAb.js");
1554
1935
  const collections = await loadCollections(path.resolve(process.cwd(), collectionsPath));
1555
1936
  renames = await withAppClient(databaseUrl, (client) => legacyForeignKeyRenames({ query: async (text) => ({ rows: (await client.query(text)).rows }) }, collections));
1556
1937
  } catch {
@@ -1569,8 +1950,8 @@ async function renameLegacyForeignKeys(databaseUrl, collectionsPath, dryRun) {
1569
1950
  /** Names the enum labels the database keeps after the collections dropped them. See `schema/enum-labels-kept.ts`. */
1570
1951
  async function reportKeptEnumLabels(databaseUrl, collectionsPath) {
1571
1952
  try {
1572
- const { loadCollections } = await import("./doctor-C4WiftHm.js").then((n) => n.t);
1573
- const { keptEnumLabels, formatKeptEnumLabelsNotice } = await import("./enum-labels-kept-D8fI1MoX.js");
1953
+ const { loadCollections } = await import("./doctor-CJyuhV1n.js").then((n) => n.t);
1954
+ const { keptEnumLabels, formatKeptEnumLabelsNotice } = await import("./enum-labels-kept-D9eTIWCz.js");
1574
1955
  const collections = await loadCollections(path.resolve(process.cwd(), collectionsPath));
1575
1956
  const kept = await withAppClient(databaseUrl, (client) => keptEnumLabels({ query: async (text) => ({ rows: (await client.query(text)).rows }) }, collections));
1576
1957
  if (kept.length > 0) outWarn(chalk.yellow(formatKeptEnumLabelsNotice(kept)));
@@ -1579,8 +1960,8 @@ async function reportKeptEnumLabels(databaseUrl, collectionsPath) {
1579
1960
  async function prebuildForeignKeyIndexes(databaseUrl, collectionsPath, dryRun) {
1580
1961
  let missing;
1581
1962
  try {
1582
- const { loadCollections } = await import("./doctor-C4WiftHm.js").then((n) => n.t);
1583
- const { missingForeignKeyIndexes } = await import("./push-index-prebuild-Db4AQF4i.js");
1963
+ const { loadCollections } = await import("./doctor-CJyuhV1n.js").then((n) => n.t);
1964
+ const { missingForeignKeyIndexes } = await import("./push-index-prebuild-BDFcfRPo.js");
1584
1965
  const collections = await loadCollections(path.resolve(process.cwd(), collectionsPath));
1585
1966
  missing = await withAppClient(databaseUrl, (client) => missingForeignKeyIndexes({ query: async (text) => ({ rows: (await client.query(text)).rows }) }, collections));
1586
1967
  } catch {
@@ -1590,7 +1971,7 @@ async function prebuildForeignKeyIndexes(databaseUrl, collectionsPath, dryRun) {
1590
1971
  if (missing.length === 0 || dryRun) return names;
1591
1972
  out(chalk.gray(` Building ${missing.length} foreign-key index(es) on existing tables first — CONCURRENTLY, so writes go on:`));
1592
1973
  try {
1593
- const { buildDeferredIndexes } = await import("./deferred-index-build-DP1_SNFo.js").then((n) => n.r);
1974
+ const { buildDeferredIndexes } = await import("./deferred-index-build-Ca6OqKyR.js").then((n) => n.r);
1594
1975
  const outcome = await withAppClient(databaseUrl, (client) => buildDeferredIndexes(async () => ({
1595
1976
  dedicated: true,
1596
1977
  query: async (text) => ({ rows: (await client.query(text)).rows }),
@@ -1609,6 +1990,10 @@ async function ensureAuthSchemaAndFunctions(databaseUrl) {
1609
1990
  await client.query(RLS_BOOTSTRAP_SQL);
1610
1991
  });
1611
1992
  } catch (err) {
1993
+ if (isEconnrefused(err) || isAuthFailure(err) || isSslNotEnabled(err)) {
1994
+ outError(diagnoseDbError(err, databaseUrl) ?? ` ✗ ${err instanceof Error ? err.message : String(err)}`);
1995
+ process.exit(1);
1996
+ }
1612
1997
  outWarn(chalk.yellow(` ⚠️ Failed to bootstrap the RLS helper functions: ${err instanceof Error ? err.message : String(err)}`));
1613
1998
  }
1614
1999
  }
@@ -1634,9 +2019,9 @@ async function lockManagedTables(databaseUrl, collectionsPath) {
1634
2019
  let unsecured;
1635
2020
  let userRole;
1636
2021
  try {
1637
- const { lockCollectionTables } = await import("./ensure-collection-policies-CXpbeNag.js");
1638
- const { loadCollections } = await import("./doctor-C4WiftHm.js").then((n) => n.t);
1639
- userRole = (await import("./rls-enforcement-DvzqlaL1.js").then((n) => n.c)).REBASE_USER_ROLE;
2022
+ const { lockCollectionTables } = await import("./ensure-collection-policies-C1ux3V_z.js");
2023
+ const { loadCollections } = await import("./doctor-CJyuhV1n.js").then((n) => n.t);
2024
+ userRole = (await import("./rls-enforcement-D7P4-ZYX.js").then((n) => n.c)).REBASE_USER_ROLE;
1640
2025
  const collections = await loadCollections(path.resolve(process.cwd(), collectionsPath));
1641
2026
  ({unsecured} = await withAppClient(databaseUrl, (client) => lockCollectionTables({ query: async (text) => ({ rows: (await client.query(text)).rows }) }, collections)));
1642
2027
  } catch (err) {
@@ -1671,8 +2056,8 @@ async function lockManagedTables(databaseUrl, collectionsPath) {
1671
2056
  async function ensureAuthTables(databaseUrl, collectionsPath) {
1672
2057
  try {
1673
2058
  const { drizzle } = await import("drizzle-orm/node-postgres");
1674
- const { ensureAuthTablesExist } = await import("./ensure-tables-CVm_yvzb.js").then((n) => n.n);
1675
- const { loadCollections } = await import("./doctor-C4WiftHm.js").then((n) => n.t);
2059
+ const { ensureAuthTablesExist } = await import("./ensure-tables-Dl_Eh9ow.js").then((n) => n.n);
2060
+ const { loadCollections } = await import("./doctor-CJyuhV1n.js").then((n) => n.t);
1676
2061
  const authCollection = (await loadCollections(path.resolve(process.cwd(), collectionsPath))).find((c) => {
1677
2062
  const a = c.auth;
1678
2063
  return a === true || typeof a === "object" && a !== null && a.enabled === true;
@@ -1692,7 +2077,7 @@ async function ensureRlsUserRole(databaseUrl) {
1692
2077
  const schemas = ["public", "rebase"];
1693
2078
  let rls;
1694
2079
  try {
1695
- rls = await import("./rls-enforcement-DvzqlaL1.js").then((n) => n.c);
2080
+ rls = await import("./rls-enforcement-D7P4-ZYX.js").then((n) => n.c);
1696
2081
  } catch (err) {
1697
2082
  outError(chalk.red(`\n ✗ Could not load the RLS provisioning module: ${err instanceof Error ? err.message : String(err)}`));
1698
2083
  return;
@@ -1752,10 +2137,10 @@ async function applyPolicies(databaseUrl, collectionsPath) {
1752
2137
  */
1753
2138
  async function policyStamping(databaseUrl, collectionsPath, policiesSql) {
1754
2139
  try {
1755
- const { loadCollections } = await import("./doctor-C4WiftHm.js").then((n) => n.t);
1756
- const { planCollectionPolicies } = await import("./generate-postgres-ddl-logic-B64O2oZF.js").then((n) => n.o);
1757
- const { policyStampSql } = await import("./ensure-collection-policies-CXpbeNag.js");
1758
- const { pinSearchPath } = await import("./connection-C9mT8bgf.js").then((n) => n.n);
2140
+ const { loadCollections } = await import("./doctor-CJyuhV1n.js").then((n) => n.t);
2141
+ const { planCollectionPolicies } = await import("./generate-postgres-ddl-logic-B4k9djAV.js").then((n) => n.o);
2142
+ const { policyStampSql } = await import("./ensure-collection-policies-C1ux3V_z.js");
2143
+ const { pinSearchPath } = await import("./connection-C938nR3E.js").then((n) => n.n);
1759
2144
  return {
1760
2145
  statements: stampableTables(planCollectionPolicies(await loadCollections(path.resolve(process.cwd(), collectionsPath))), policiesSql).map((plan) => policyStampSql(plan)).filter((statement) => statement !== void 0),
1761
2146
  searchPath: pinSearchPath(databaseUrl) === databaseUrl ? void 0 : "public"
@@ -1765,6 +2150,28 @@ async function policyStamping(databaseUrl, collectionsPath, policiesSql) {
1765
2150
  }
1766
2151
  }
1767
2152
  /**
2153
+ * `planPolicyPush` for this database and these collections, read under the
2154
+ * `search_path` the push stamps policies under, as the lines the dry run
2155
+ * prints and the one the push does; `undefined`, having said why, when it
2156
+ * cannot be read.
2157
+ */
2158
+ async function readPolicyPushPlan(databaseUrl, collectionsPath) {
2159
+ try {
2160
+ const { formatPolicyPushPlan, planPolicyPush, summarizePolicyPushPlan } = await import("./push-policy-plan-DTXE6aVa.js");
2161
+ const { loadCollections } = await import("./doctor-CJyuhV1n.js").then((n) => n.t);
2162
+ const { pinSearchPath } = await import("./connection-C938nR3E.js").then((n) => n.n);
2163
+ const collections = await loadCollections(path.resolve(process.cwd(), collectionsPath));
2164
+ const plan = await withAppClient(databaseUrl, (client) => planPolicyPush({ query: async (text, values) => ({ rows: (await client.query(text, values)).rows }) }, collections, { searchPath: pinSearchPath(databaseUrl) === databaseUrl ? void 0 : "public" }));
2165
+ return {
2166
+ ...formatPolicyPushPlan(plan),
2167
+ summary: summarizePolicyPushPlan(plan)
2168
+ };
2169
+ } catch (err) {
2170
+ outWarn(chalk.yellow(` ⚠️ Could not read the database's row-level security: ${err instanceof Error ? err.message : String(err)}`));
2171
+ return;
2172
+ }
2173
+ }
2174
+ /**
1768
2175
  * Remove the policies an earlier push superseded but never dropped.
1769
2176
  *
1770
2177
  * `policies.sql` only DROPs the names it is about to CREATE, and a rule's
@@ -1779,8 +2186,8 @@ async function policyStamping(databaseUrl, collectionsPath, policiesSql) {
1779
2186
  */
1780
2187
  async function reconcilePolicies(databaseUrl, collectionsPath) {
1781
2188
  try {
1782
- const { checkPolicyDrift, dropOrphanedPolicies, formatPolicyDrift, hasDrift } = await import("./policy-drift-Dm8i-s1J.js").then((n) => n.a);
1783
- const { loadCollections } = await import("./doctor-C4WiftHm.js").then((n) => n.t);
2189
+ const { checkPolicyDrift, dropOrphanedPolicies, formatPolicyDrift, hasDrift } = await import("./policy-drift-CY5P1ZRu.js").then((n) => n.s);
2190
+ const { loadCollections } = await import("./doctor-CJyuhV1n.js").then((n) => n.t);
1784
2191
  const collections = await loadCollections(path.resolve(process.cwd(), collectionsPath));
1785
2192
  await withAppClient(databaseUrl, async (client) => {
1786
2193
  const drift = await checkPolicyDrift(client, collections);
@@ -1790,7 +2197,8 @@ async function reconcilePolicies(databaseUrl, collectionsPath) {
1790
2197
  if (kept.length > 0) {
1791
2198
  outWarn(chalk.yellow(" ⚠️ Policies in the database that no collection describes:"));
1792
2199
  for (const p of kept) outWarn(chalk.yellow(` • ${p.schema}.${p.table} → "${p.name}" (${p.command} TO ${p.roles.join(", ")})`));
1793
- outWarn(chalk.yellow(" These still grant access. Drop them by hand if they are stale."));
2200
+ outWarn(chalk.yellow(" Each is still in force. To keep one, describe it in its collection's `securityRules`"));
2201
+ outWarn(chalk.yellow(" (Studio's RLS view imports it); if it is stale, drop it by hand."));
1794
2202
  }
1795
2203
  const remaining = {
1796
2204
  ...drift,
@@ -1856,10 +2264,12 @@ async function branchCommand(rawArgs) {
1856
2264
  outError(chalk.red("✗ DATABASE_URL is not set. Make sure your .env file is configured."));
1857
2265
  process.exit(1);
1858
2266
  }
1859
- const { DatabasePoolManager } = await import("./databasePoolManager-BIuewidb.js").then((n) => n.n);
1860
- const { BranchService } = await import("./BranchService-Bsn2wfaj.js").then((n) => n.n);
2267
+ const { DatabasePoolManager } = await import("./databasePoolManager-Yqdw14wY.js").then((n) => n.n);
2268
+ const { BranchService } = await import("./BranchService-DNg4txyf.js").then((n) => n.n);
1861
2269
  const { drizzle } = await import("drizzle-orm/node-postgres");
1862
2270
  const { Pool } = await import("pg");
2271
+ await checkDatabaseConnectivity(databaseUrl);
2272
+ await closeAppClients();
1863
2273
  const pool = new Pool({
1864
2274
  connectionString: databaseUrl,
1865
2275
  max: 3
@@ -1867,8 +2277,8 @@ async function branchCommand(rawArgs) {
1867
2277
  const db = drizzle(pool);
1868
2278
  const poolManager = new DatabasePoolManager(databaseUrl);
1869
2279
  const branchService = new BranchService(db, poolManager);
1870
- await branchService.ensureBranchMetadataTable();
1871
2280
  try {
2281
+ if (branchAction === "create" || branchAction === "delete" || branchAction === "prune") await branchService.ensureBranchMetadataTable();
1872
2282
  switch (branchAction) {
1873
2283
  case "create": {
1874
2284
  const name = rawArgs[3];
@@ -1898,7 +2308,7 @@ async function branchCommand(rawArgs) {
1898
2308
  break;
1899
2309
  }
1900
2310
  case "list": {
1901
- const branches = await branchService.listBranches();
2311
+ const branches = await branchService.hasBranchRegistry() ? await branchService.listBranches() : [];
1902
2312
  out("");
1903
2313
  if (branches.length === 0) out(chalk.gray(" No branches found. Create one with: rebase db branch create <name>"));
1904
2314
  else {
@@ -1935,7 +2345,7 @@ async function branchCommand(rawArgs) {
1935
2345
  out(chalk.gray(" Usage: rebase db branch info <name>"));
1936
2346
  process.exit(1);
1937
2347
  }
1938
- const info = await branchService.getBranchInfo(name);
2348
+ const info = await branchService.hasBranchRegistry() ? await branchService.getBranchInfo(name) : void 0;
1939
2349
  out("");
1940
2350
  if (!info) {
1941
2351
  outError(chalk.red(` ✗ Branch "${name}" not found.`));
@@ -1990,6 +2400,12 @@ async function branchCommand(rawArgs) {
1990
2400
  else if (plan.devDiff.length > 0) out(chalk.gray(` (${plan.devDiff.length} Atlas scratch database(s) not listed — --include-dev-diff)`));
1991
2401
  out("");
1992
2402
  if (parsed["--yes"] !== true) {
2403
+ if (!process.stdin.isTTY) {
2404
+ outError(chalk.red(" ✗ Nothing was pruned: there is no terminal to confirm on."));
2405
+ outError(chalk.gray(" Re-run with --yes to remove what is listed above."));
2406
+ outError("");
2407
+ process.exit(1);
2408
+ }
1993
2409
  if (!await promptConfirm(" Remove these? (y/N) ")) {
1994
2410
  out(chalk.gray(" Nothing was changed."));
1995
2411
  out("");
@@ -2088,6 +2504,13 @@ function timeAgo(date) {
2088
2504
  return formatRelativeTime(date, { maxMs: Number.POSITIVE_INFINITY }) ?? "unknown";
2089
2505
  }
2090
2506
  /**
2507
+ * Whether DATABASE_URL is the managed development database (PGlite), as the
2508
+ * CLI resolved it and says in `REBASE_DEV_DATABASE_KIND`.
2509
+ */
2510
+ function managedDatabase() {
2511
+ return process.env.REBASE_DEV_DATABASE_KIND === "managed";
2512
+ }
2513
+ /**
2091
2514
  * The Atlas scratch database this process created, dropped once the db
2092
2515
  * command that needed it succeeds — see `ScratchDatabase`.
2093
2516
  */
@@ -2114,8 +2537,9 @@ async function runAtlas(domain, args, collectionsPath, opts = {}) {
2114
2537
  outError("");
2115
2538
  } else {
2116
2539
  outError(chalk.gray(" It is not installed in this project.\n"));
2117
- outError(" Install it with:\n");
2118
- outError(chalk.bold(` ${describeDevAddCommand("@ariga/atlas", manager)}\n`));
2540
+ const member = workspaceMemberDir();
2541
+ outError(member === null ? " Install it with:\n" : " Install it with (from the project root):\n");
2542
+ outError(chalk.bold(` ${describeDevAddCommand("@ariga/atlas", manager, member)}\n`));
2119
2543
  outError(chalk.gray(" If the install then reports blocked or ignored build scripts, allow them too —\n the package carries a `preinstall` script that fetches the binary.\n"));
2120
2544
  }
2121
2545
  process.exit(1);
@@ -2155,8 +2579,8 @@ async function runAtlas(domain, args, collectionsPath, opts = {}) {
2155
2579
  }
2156
2580
  const devDatabaseUrl = devDatabase.url;
2157
2581
  if (takesDevUrl(domain, args) && !devDatabase.named) await scratchDatabase.ensure(databaseUrl, devDatabaseUrl);
2158
- const atlasUrl = forLibpq(databaseUrl);
2159
- const atlasDevUrl = forLibpq(devDatabaseUrl);
2582
+ const atlasUrl = forAtlas(databaseUrl);
2583
+ const atlasDevUrl = forAtlas(devDatabaseUrl);
2160
2584
  const excludes = [];
2161
2585
  if (collectionsPath && acceptsExcludeFlag(domain, args)) {
2162
2586
  excludes.push(...await getSearchExcludes(collectionsPath));
@@ -2171,7 +2595,7 @@ async function runAtlas(domain, args, collectionsPath, opts = {}) {
2171
2595
  outError(chalk.gray(" Refusing to apply — a partial exclude list could drop tables Rebase does not manage."));
2172
2596
  const hint = diagnoseDbError(err.cause ?? err, databaseUrl);
2173
2597
  if (hint) outError(hint);
2174
- process.exit(1);
2598
+ throw new CommandStopped("Could not determine which tables to protect.");
2175
2599
  }
2176
2600
  throw err;
2177
2601
  }
@@ -2184,20 +2608,39 @@ async function runAtlas(domain, args, collectionsPath, opts = {}) {
2184
2608
  outError(chalk.gray(" Refusing to apply — a partial exclude list could drop an index Rebase does not manage."));
2185
2609
  const hint = diagnoseDbError(err.cause ?? err, databaseUrl);
2186
2610
  if (hint) outError(hint);
2187
- process.exit(1);
2611
+ throw new CommandStopped("Could not determine which indexes to protect.");
2612
+ }
2613
+ throw err;
2614
+ }
2615
+ try {
2616
+ const desiredSqlFile = path.join(generatedSqlDir(), "schema.sql");
2617
+ const desiredSql = fs.existsSync(desiredSqlFile) ? fs.readFileSync(desiredSqlFile, "utf-8") : "";
2618
+ excludes.push(...await getUndeclaredConstraintExcludes(databaseUrl, desiredSql));
2619
+ } catch (err) {
2620
+ if (err instanceof ExcludeIntrospectionError) {
2621
+ outError(chalk.red("\n✗ Aborting push: could not determine which constraints to protect."));
2622
+ outError(chalk.gray(` ${err.message}`));
2623
+ outError(chalk.gray(" Refusing to apply — a partial exclude list could drop a CHECK or EXCLUDE constraint the database enforces."));
2624
+ const hint = diagnoseDbError(err.cause ?? err, databaseUrl);
2625
+ if (hint) outError(hint);
2626
+ throw new CommandStopped("Could not determine which constraints to protect.");
2188
2627
  }
2189
2628
  throw err;
2190
2629
  }
2191
2630
  }
2192
- const subprocess = execa(atlasBin, buildAtlasArgs({
2631
+ const atlasArgs = buildAtlasArgs({
2193
2632
  domain,
2194
2633
  args,
2195
2634
  url: atlasUrl,
2196
2635
  devUrl: atlasDevUrl,
2197
- excludes
2198
- }), {
2636
+ excludes,
2637
+ singleSession: managedDatabase()
2638
+ });
2639
+ if (managedDatabase() && readsRevisions(domain, args)) await ensureAtlasRevisionsTable(databaseUrl);
2640
+ if (managedDatabase()) await closeAppClients();
2641
+ const subprocess = execa(atlasBin, atlasArgs, {
2199
2642
  cwd: process.cwd(),
2200
- stdout: opts.captureStdout ? "pipe" : "inherit",
2643
+ stdout: opts.captureStdout || opts.omitPlanEcho ? "pipe" : "inherit",
2201
2644
  stderr: "pipe",
2202
2645
  env
2203
2646
  });
@@ -2205,22 +2648,29 @@ async function runAtlas(domain, args, collectionsPath, opts = {}) {
2205
2648
  if (opts.captureStdout) subprocess.stdout?.on("data", (chunk) => {
2206
2649
  stdoutText += chunk.toString();
2207
2650
  });
2651
+ else if (opts.omitPlanEcho) {
2652
+ const report = createApplyReportFilter();
2653
+ subprocess.stdout?.on("data", (chunk) => process.stdout.write(report.push(chunk.toString())));
2654
+ subprocess.stdout?.on("end", () => process.stdout.write(report.flush()));
2655
+ }
2208
2656
  let stderrText = "";
2209
2657
  subprocess.stderr?.on("data", (chunk) => {
2210
2658
  const text = chunk.toString();
2211
2659
  stderrText += text;
2212
- process.stderr.write(text);
2660
+ process.stderr.write(redactConnectionPasswords(text));
2213
2661
  });
2214
2662
  try {
2215
2663
  await subprocess;
2216
2664
  } catch {
2665
+ await scratchDatabase.afterAtlasFailure(stderrText);
2217
2666
  outError(chalk.red(`\n✗ atlas ${domain} ${args.join(" ")} failed.\n`));
2218
2667
  const hint = await diagnoseAtlasFailure({
2219
2668
  domain,
2220
2669
  args,
2221
2670
  stderr: stderrText,
2222
2671
  databaseUrl,
2223
- latestMigrationVersion: latestMigrationVersion()
2672
+ latestMigrationVersion: latestMigrationVersion(),
2673
+ managedDatabase: managedDatabase()
2224
2674
  }) ?? diagnoseDbError({ message: stderrText }, databaseUrl);
2225
2675
  if (hint) outError(hint);
2226
2676
  if (opts.throwOnFailure) throw Object.assign(/* @__PURE__ */ new Error(`atlas ${domain} ${args.join(" ")} failed`), { alreadyReported: true });
@@ -2312,9 +2762,9 @@ async function schemaStaleCommand(rawArgs) {
2312
2762
  const schemaFile = path.resolve(process.cwd(), outputPath);
2313
2763
  const fix = Boolean(argsList["--fix"]);
2314
2764
  const generatedExists = fs.existsSync(schemaFile);
2315
- const { loadCollections } = await import("./doctor-C4WiftHm.js").then((n) => n.t);
2316
- const { findLegacyForeignKeyNames, compareGeneratedDeclarations, staleVerdict } = await import("./generated-schema-staleness-Di9c4ZxN.js").then((n) => n.r);
2317
- const { generateSchema } = await import("./generate-drizzle-schema-logic-B74WN37S.js").then((n) => n.n);
2765
+ const { loadCollections } = await import("./doctor-CJyuhV1n.js").then((n) => n.t);
2766
+ const { findLegacyForeignKeyNames, compareGeneratedDeclarations, staleVerdict } = await import("./generated-schema-staleness-FlxM2i4i.js").then((n) => n.r);
2767
+ const { generateSchema } = await import("./generate-drizzle-schema-logic-BaT0H7Gq.js").then((n) => n.n);
2318
2768
  const { relationalCollections } = await import("@rebasepro/common");
2319
2769
  let stale = [];
2320
2770
  let differences = [];
@@ -2426,6 +2876,8 @@ async function schemaCommand(subcommand, rawArgs, options = {}) {
2426
2876
  "--collections": String,
2427
2877
  "--force": Boolean,
2428
2878
  "--schema": String,
2879
+ "--data-inference": Boolean,
2880
+ "--no-data-inference": Boolean,
2429
2881
  "-o": "--output",
2430
2882
  "--out": "--output",
2431
2883
  "-c": "--collections",
@@ -2448,7 +2900,9 @@ async function schemaCommand(subcommand, rawArgs, options = {}) {
2448
2900
  ...introspect.args,
2449
2901
  `--output=${outputPath}`,
2450
2902
  ...argsList["--force"] ? ["--force"] : [],
2451
- ...argsList["--schema"] ? [`--schema=${argsList["--schema"]}`] : []
2903
+ ...argsList["--schema"] ? [`--schema=${argsList["--schema"]}`] : [],
2904
+ ...argsList["--data-inference"] ? ["--data-inference"] : [],
2905
+ ...argsList["--no-data-inference"] ? ["--no-data-inference"] : []
2452
2906
  ];
2453
2907
  try {
2454
2908
  await execa(cmdParts[0], cmdParts.slice(1), {
@@ -2457,7 +2911,7 @@ async function schemaCommand(subcommand, rawArgs, options = {}) {
2457
2911
  env: { ...process.env }
2458
2912
  });
2459
2913
  } catch (err) {
2460
- outError(chalk.red(`✗ Failed to run schema introspector: ${err instanceof Error ? err.message : String(err)}`));
2914
+ if (typeof (typeof err === "object" && err !== null ? Reflect.get(err, "exitCode") : void 0) !== "number") outError(chalk.red(`✗ Failed to run schema introspector: ${err instanceof Error ? err.message : String(err)}`));
2461
2915
  process.exit(1);
2462
2916
  }
2463
2917
  } else {