@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.
- package/dist/{BranchService-Bsn2wfaj.js → BranchService-DNg4txyf.js} +14 -4
- package/dist/BranchService-DNg4txyf.js.map +1 -0
- package/dist/PostgresBackendDriver.d.ts +13 -0
- package/dist/PostgresBootstrapper.d.ts +10 -0
- package/dist/backup/backup-cli.d.ts +88 -2
- package/dist/backup/backup-service.d.ts +11 -1
- package/dist/backup/pg-tools.d.ts +18 -0
- package/dist/{backup-cli-DnpJi6I7.js → backup-cli-HCEkPcQj.js} +215 -79
- package/dist/backup-cli-HCEkPcQj.js.map +1 -0
- package/dist/{backup-service-BTBB7RXn.js → backup-service-D-UpT7DH.js} +121 -11
- package/dist/backup-service-D-UpT7DH.js.map +1 -0
- package/dist/{boot-ddl-DVpsFN6r.js → boot-ddl-Dnvpmm_T.js} +3 -3
- package/dist/{boot-ddl-DVpsFN6r.js.map → boot-ddl-Dnvpmm_T.js.map} +1 -1
- package/dist/branch-argv.d.ts +18 -6
- package/dist/cli-console-C_ISGwJo.js +255 -0
- package/dist/cli-console-C_ISGwJo.js.map +1 -0
- package/dist/cli-console.d.ts +10 -0
- package/dist/{cli-errors-CaI4ntyD.js → cli-errors-mx4sSjB8.js} +477 -103
- package/dist/cli-errors-mx4sSjB8.js.map +1 -0
- package/dist/cli-errors.d.ts +40 -4
- package/dist/{cli-flags-JNI-pwSf.js → cli-flags-DUozBFyK.js} +70 -6
- package/dist/cli-flags-DUozBFyK.js.map +1 -0
- package/dist/cli-flags.d.ts +21 -0
- package/dist/cli-helpers.d.ts +53 -24
- package/dist/{arg-Dni7MzLB.js → cli-prompt-egtNKkZl.js} +49 -3
- package/dist/cli-prompt-egtNKkZl.js.map +1 -0
- package/dist/cli-prompt.d.ts +19 -0
- package/dist/cli-scratch-database.d.ts +47 -8
- package/dist/cli.js +548 -94
- package/dist/cli.js.map +1 -1
- package/dist/{column-plan-helpers-drZIsSNf.js → column-plan-helpers-D_V4oaZh.js} +2 -2
- package/dist/{column-plan-helpers-drZIsSNf.js.map → column-plan-helpers-D_V4oaZh.js.map} +1 -1
- package/dist/{connection-C9mT8bgf.js → connection-C938nR3E.js} +2 -2
- package/dist/{connection-C9mT8bgf.js.map → connection-C938nR3E.js.map} +1 -1
- package/dist/{databasePoolManager-BIuewidb.js → databasePoolManager-Yqdw14wY.js} +4 -4
- package/dist/{databasePoolManager-BIuewidb.js.map → databasePoolManager-Yqdw14wY.js.map} +1 -1
- package/dist/{deferred-index-build-DP1_SNFo.js → deferred-index-build-Ca6OqKyR.js} +8 -8
- package/dist/{deferred-index-build-DP1_SNFo.js.map → deferred-index-build-Ca6OqKyR.js.map} +1 -1
- package/dist/{doctor-C4WiftHm.js → doctor-CJyuhV1n.js} +8 -8
- package/dist/{doctor-C4WiftHm.js.map → doctor-CJyuhV1n.js.map} +1 -1
- package/dist/{ensure-collection-policies-CXpbeNag.js → ensure-collection-policies-C1ux3V_z.js} +79 -18
- package/dist/ensure-collection-policies-C1ux3V_z.js.map +1 -0
- package/dist/{ensure-collection-tables-BhsU6qUE.js → ensure-collection-tables-Do-Bvb64.js} +10 -10
- package/dist/{ensure-collection-tables-BhsU6qUE.js.map → ensure-collection-tables-Do-Bvb64.js.map} +1 -1
- package/dist/{ensure-tables-CVm_yvzb.js → ensure-tables-Dl_Eh9ow.js} +5 -5
- package/dist/{ensure-tables-CVm_yvzb.js.map → ensure-tables-Dl_Eh9ow.js.map} +1 -1
- package/dist/{enum-labels-kept-D8fI1MoX.js → enum-labels-kept-D9eTIWCz.js} +3 -3
- package/dist/{enum-labels-kept-D8fI1MoX.js.map → enum-labels-kept-D9eTIWCz.js.map} +1 -1
- package/dist/{generate-drizzle-schema-Bz52jH2h.js → generate-drizzle-schema-Bf8canaR.js} +3 -3
- package/dist/{generate-drizzle-schema-Bz52jH2h.js.map → generate-drizzle-schema-Bf8canaR.js.map} +1 -1
- package/dist/{generate-drizzle-schema-logic-B74WN37S.js → generate-drizzle-schema-logic-BaT0H7Gq.js} +4 -4
- package/dist/{generate-drizzle-schema-logic-B74WN37S.js.map → generate-drizzle-schema-logic-BaT0H7Gq.js.map} +1 -1
- package/dist/{generate-files-CdEckX6c.js → generate-files-D50Pxw1G.js} +6 -47
- package/dist/generate-files-D50Pxw1G.js.map +1 -0
- package/dist/generate-next-step-DCd7yiJ2.js +66 -0
- package/dist/generate-next-step-DCd7yiJ2.js.map +1 -0
- package/dist/{generate-postgres-ddl-logic-B64O2oZF.js → generate-postgres-ddl-logic-B4k9djAV.js} +4 -4
- package/dist/{generate-postgres-ddl-logic-B64O2oZF.js.map → generate-postgres-ddl-logic-B4k9djAV.js.map} +1 -1
- package/dist/{generated-schema-staleness-Di9c4ZxN.js → generated-schema-staleness-FlxM2i4i.js} +102 -12
- package/dist/{generated-schema-staleness-Di9c4ZxN.js.map → generated-schema-staleness-FlxM2i4i.js.map} +1 -1
- package/dist/index.es.js +11 -11
- package/dist/{introspect-db-logic-C-Di8dA9.js → introspect-db-logic-j2vYhmdP.js} +145 -9
- package/dist/introspect-db-logic-j2vYhmdP.js.map +1 -0
- package/dist/{introspect-db-queries-D6kAqReI.js → introspect-db-queries-DevUrzll.js} +41 -6
- package/dist/introspect-db-queries-DevUrzll.js.map +1 -0
- package/dist/{open-rebase-CbOur0TU.js → open-rebase-D8rytTP0.js} +95 -45
- package/dist/open-rebase-D8rytTP0.js.map +1 -0
- package/dist/open-rebase.d.ts +12 -4
- package/dist/{pg-error-utils-vJezlXdI.js → pg-error-utils-CdsGYMTm.js} +10 -3
- package/dist/pg-error-utils-CdsGYMTm.js.map +1 -0
- package/dist/{plan-schema-B1YF_tYq.js → plan-schema-CsNgK0qi.js} +4 -4
- package/dist/{plan-schema-B1YF_tYq.js.map → plan-schema-CsNgK0qi.js.map} +1 -1
- package/dist/{policy-drift-Dm8i-s1J.js → policy-drift-CY5P1ZRu.js} +28 -14
- package/dist/{policy-drift-Dm8i-s1J.js.map → policy-drift-CY5P1ZRu.js.map} +1 -1
- package/dist/{push-index-prebuild-Db4AQF4i.js → push-index-prebuild-BDFcfRPo.js} +4 -4
- package/dist/{push-index-prebuild-Db4AQF4i.js.map → push-index-prebuild-BDFcfRPo.js.map} +1 -1
- package/dist/{push-legacy-renames-BK6ilQq2.js → push-legacy-renames-D4bvMWAb.js} +4 -4
- package/dist/{push-legacy-renames-BK6ilQq2.js.map → push-legacy-renames-D4bvMWAb.js.map} +1 -1
- package/dist/push-policy-plan-DTXE6aVa.js +111 -0
- package/dist/push-policy-plan-DTXE6aVa.js.map +1 -0
- package/dist/{render-ddl-CtC3y9Ol.js → render-ddl-CrfEe__B.js} +10 -9
- package/dist/render-ddl-CrfEe__B.js.map +1 -0
- package/dist/{rls-bootstrap-sql-RQUljnJ1.js → rls-bootstrap-sql-Bd-hJk08.js} +3 -58
- package/dist/rls-bootstrap-sql-Bd-hJk08.js.map +1 -0
- package/dist/{rls-enforcement-DvzqlaL1.js → rls-enforcement-D7P4-ZYX.js} +2 -2
- package/dist/{rls-enforcement-DvzqlaL1.js.map → rls-enforcement-D7P4-ZYX.js.map} +1 -1
- package/dist/{__vite-browser-external-BnuHet1e.js → rolldown-runtime-V7rFbMRe.js} +1 -8
- package/dist/schema/atlas-apply-report.d.ts +23 -0
- package/dist/schema/atlas-argv.d.ts +22 -1
- package/dist/schema/atlas-exclude.d.ts +42 -0
- package/dist/schema/atlas-revisions.d.ts +4 -0
- package/dist/schema/carved-out-migration.d.ts +5 -4
- package/dist/schema/destructive-sql.d.ts +23 -1
- package/dist/schema/doctor-cli.js +5 -5
- package/dist/schema/ensure-collection-policies.d.ts +50 -0
- package/dist/schema/generate-drizzle-schema.js +1 -1
- package/dist/schema/generate-next-step.d.ts +11 -0
- package/dist/schema/generate-postgres-ddl.js +2 -2
- package/dist/schema/generated-schema-diff.d.ts +7 -0
- package/dist/schema/generated-schema-staleness.d.ts +7 -1
- package/dist/schema/introspect-db-logic.d.ts +32 -1
- package/dist/schema/introspect-db-queries.d.ts +18 -5
- package/dist/schema/introspect-db.js +53 -52
- package/dist/schema/introspect-db.js.map +1 -1
- package/dist/schema/migrate-baseline.d.ts +51 -0
- package/dist/schema/plan/render-ddl.d.ts +2 -1
- package/dist/schema/push-policy-plan.d.ts +67 -0
- package/dist/schema/undeclarable-constraints.d.ts +59 -0
- package/dist/security/policy-drift.d.ts +5 -0
- package/dist/services/BranchService.d.ts +7 -0
- package/dist/{source-DBDbBTL4.js → source-DB6xJv3p.js} +65 -3
- package/dist/source-DB6xJv3p.js.map +1 -0
- package/dist/utils/connection-string.d.ts +47 -0
- package/dist/{vector-index-CMJKb3C9.js → vector-index-BFhyEaiz.js} +2 -2
- package/dist/{vector-index-CMJKb3C9.js.map → vector-index-BFhyEaiz.js.map} +1 -1
- package/dist/{websocket-BLGc5yhs.js → websocket-DMeXOoyv.js} +41 -13
- package/dist/websocket-DMeXOoyv.js.map +1 -0
- package/package.json +7 -7
- package/dist/BranchService-Bsn2wfaj.js.map +0 -1
- package/dist/__vite-browser-external-BnuHet1e.js.map +0 -1
- package/dist/arg-Dni7MzLB.js.map +0 -1
- package/dist/backup-cli-DnpJi6I7.js.map +0 -1
- package/dist/backup-service-BTBB7RXn.js.map +0 -1
- package/dist/cli-console-Ca6ZlAVU.js +0 -154
- package/dist/cli-console-Ca6ZlAVU.js.map +0 -1
- package/dist/cli-errors-CaI4ntyD.js.map +0 -1
- package/dist/cli-flags-JNI-pwSf.js.map +0 -1
- package/dist/ensure-collection-policies-CXpbeNag.js.map +0 -1
- package/dist/generate-files-CdEckX6c.js.map +0 -1
- package/dist/introspect-db-logic-C-Di8dA9.js.map +0 -1
- package/dist/introspect-db-queries-D6kAqReI.js.map +0 -1
- package/dist/open-rebase-CbOur0TU.js.map +0 -1
- package/dist/pg-error-utils-vJezlXdI.js.map +0 -1
- package/dist/render-ddl-CtC3y9Ol.js.map +0 -1
- package/dist/rls-bootstrap-sql-RQUljnJ1.js.map +0 -1
- package/dist/source-DBDbBTL4.js.map +0 -1
- 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 {
|
|
4
|
-
import { A as
|
|
5
|
-
import { a as outWarn, i as outError, r as out, t as chalk } from "./source-
|
|
6
|
-
import {
|
|
7
|
-
import { t as
|
|
8
|
-
import {
|
|
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 =
|
|
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
|
|
246
|
-
*
|
|
247
|
-
*
|
|
248
|
-
*
|
|
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
|
|
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
|
-
/**
|
|
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 (
|
|
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
|
-
*
|
|
691
|
-
*
|
|
692
|
-
*
|
|
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
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
await this.
|
|
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
|
-
*
|
|
810
|
-
*
|
|
811
|
-
*
|
|
812
|
-
*
|
|
813
|
-
*
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1407
|
-
|
|
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
|
-
|
|
1795
|
+
throw new CommandStopped("A generated column Rebase does not own blocks the push.");
|
|
1437
1796
|
}
|
|
1438
|
-
const
|
|
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
|
-
|
|
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
|
-
|
|
1819
|
+
throw new CommandStopped("The destructive push was declined.");
|
|
1461
1820
|
}
|
|
1462
|
-
} else outWarn(chalk.yellow(
|
|
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, {
|
|
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._,
|
|
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(`
|
|
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._,
|
|
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-
|
|
1553
|
-
const { legacyForeignKeyRenames } = await import("./push-legacy-renames-
|
|
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-
|
|
1573
|
-
const { keptEnumLabels, formatKeptEnumLabelsNotice } = await import("./enum-labels-kept-
|
|
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-
|
|
1583
|
-
const { missingForeignKeyIndexes } = await import("./push-index-prebuild-
|
|
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-
|
|
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-
|
|
1638
|
-
const { loadCollections } = await import("./doctor-
|
|
1639
|
-
userRole = (await import("./rls-enforcement-
|
|
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-
|
|
1675
|
-
const { loadCollections } = await import("./doctor-
|
|
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-
|
|
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-
|
|
1756
|
-
const { planCollectionPolicies } = await import("./generate-postgres-ddl-logic-
|
|
1757
|
-
const { policyStampSql } = await import("./ensure-collection-policies-
|
|
1758
|
-
const { pinSearchPath } = await import("./connection-
|
|
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-
|
|
1783
|
-
const { loadCollections } = await import("./doctor-
|
|
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("
|
|
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-
|
|
1860
|
-
const { BranchService } = await import("./BranchService-
|
|
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
|
-
|
|
2118
|
-
outError(
|
|
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 =
|
|
2159
|
-
const atlasDevUrl =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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-
|
|
2316
|
-
const { findLegacyForeignKeyNames, compareGeneratedDeclarations, staleVerdict } = await import("./generated-schema-staleness-
|
|
2317
|
-
const { generateSchema } = await import("./generate-drizzle-schema-logic-
|
|
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 {
|