@rebasepro/server-postgres 0.25.0 → 0.26.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{BranchService-iXXZeVYD.js → BranchService-CWBnDhcv.js} +88 -12
- package/dist/BranchService-CWBnDhcv.js.map +1 -0
- package/dist/PostgresBackendDriver.d.ts +13 -0
- package/dist/PostgresBootstrapper.d.ts +7 -0
- package/dist/auth/services.d.ts +8 -1
- package/dist/auth/verify-auth-tables.d.ts +21 -0
- package/dist/backup/backup-cli.d.ts +40 -3
- package/dist/backup/backup-service.d.ts +11 -0
- package/dist/{backup-service-Bb39plam.js → backup-cli-PoRG-zEq.js} +777 -10
- package/dist/backup-cli-PoRG-zEq.js.map +1 -0
- package/dist/{boot-ddl-s_3Hkz94.js → boot-ddl-EsgqDkx0.js} +3 -3
- package/dist/{boot-ddl-s_3Hkz94.js.map → boot-ddl-EsgqDkx0.js.map} +1 -1
- package/dist/branch-argv.d.ts +15 -0
- package/dist/branch-diff-CxE4VlxT.js +1396 -0
- package/dist/branch-diff-CxE4VlxT.js.map +1 -0
- package/dist/branch-diff-report-DpvVf7hR.js +48 -0
- package/dist/branch-diff-report-DpvVf7hR.js.map +1 -0
- package/dist/branching/branch-diff-report.d.ts +7 -0
- package/dist/branching/branch-diff.d.ts +115 -0
- package/dist/{cli-console-DQ_K-sdM.js → cli-console-f0k6_TsJ.js} +41 -9
- package/dist/cli-console-f0k6_TsJ.js.map +1 -0
- package/dist/cli-console.d.ts +17 -1
- package/dist/{cli-errors-DwQnNATs.js → cli-errors-DsYyNYzd.js} +90 -11
- package/dist/cli-errors-DsYyNYzd.js.map +1 -0
- package/dist/cli-errors.d.ts +14 -0
- package/dist/cli-flags.d.ts +51 -0
- package/dist/{cli-prompt-egtNKkZl.js → cli-prompt-DXG0yk-h.js} +39 -4
- package/dist/{cli-prompt-egtNKkZl.js.map → cli-prompt-DXG0yk-h.js.map} +1 -1
- package/dist/cli.js +121 -271
- package/dist/cli.js.map +1 -1
- package/dist/{collections-schema-version-T6cGvrDi.js → collections-schema-version-D7bDI0PJ.js} +13 -9
- package/dist/collections-schema-version-D7bDI0PJ.js.map +1 -0
- package/dist/{column-plan-helpers-D_V4oaZh.js → column-plan-helpers-IG6uFb1s.js} +2 -2
- package/dist/{column-plan-helpers-D_V4oaZh.js.map → column-plan-helpers-IG6uFb1s.js.map} +1 -1
- package/dist/{connection-C938nR3E.js → connection-czvI7TZL.js} +57 -10
- package/dist/connection-czvI7TZL.js.map +1 -0
- package/dist/{databasePoolManager-Yqdw14wY.js → databasePoolManager-3stMNFL-.js} +4 -6
- package/dist/databasePoolManager-3stMNFL-.js.map +1 -0
- package/dist/db-command-line.d.ts +14 -0
- package/dist/db-subcommands.d.ts +11 -0
- package/dist/{deferred-index-build-Cis_HoGn.js → deferred-index-build-CqaOv1Xx.js} +8 -8
- package/dist/{deferred-index-build-Cis_HoGn.js.map → deferred-index-build-CqaOv1Xx.js.map} +1 -1
- package/dist/{doctor-DvJH-IQG.js → doctor-DW_mlTzz.js} +186 -42
- package/dist/doctor-DW_mlTzz.js.map +1 -0
- package/dist/{ensure-collection-policies-CCqayoUz.js → ensure-collection-policies-DLWgabQ2.js} +13 -10
- package/dist/ensure-collection-policies-DLWgabQ2.js.map +1 -0
- package/dist/{ensure-collection-tables-DvvImXDe.js → ensure-collection-tables-CFIRq8ni.js} +9 -9
- package/dist/{ensure-collection-tables-DvvImXDe.js.map → ensure-collection-tables-CFIRq8ni.js.map} +1 -1
- package/dist/{ensure-tables-Dpgwx5sy.js → ensure-tables-4nA-tfwi.js} +5 -5
- package/dist/{ensure-tables-Dpgwx5sy.js.map → ensure-tables-4nA-tfwi.js.map} +1 -1
- package/dist/{enum-labels-kept-CUlUlRkO.js → enum-labels-kept-PcPW65xy.js} +3 -3
- package/dist/{enum-labels-kept-CUlUlRkO.js.map → enum-labels-kept-PcPW65xy.js.map} +1 -1
- package/dist/{generate-drizzle-schema-C3EAP9HE.js → generate-drizzle-schema-DC0BOUpn.js} +3 -3
- package/dist/{generate-drizzle-schema-C3EAP9HE.js.map → generate-drizzle-schema-DC0BOUpn.js.map} +1 -1
- package/dist/{generate-drizzle-schema-logic-DA3zM8zc.js → generate-drizzle-schema-logic-CPzCRD-8.js} +4 -4
- package/dist/{generate-drizzle-schema-logic-DA3zM8zc.js.map → generate-drizzle-schema-logic-CPzCRD-8.js.map} +1 -1
- package/dist/{generate-files-ClmOUHXj.js → generate-files-BP-4W_gm.js} +6 -6
- package/dist/{generate-files-ClmOUHXj.js.map → generate-files-BP-4W_gm.js.map} +1 -1
- package/dist/{generate-next-step-DCeS4LrJ.js → generate-next-step-DgK5BlBn.js} +2 -2
- package/dist/{generate-next-step-DCeS4LrJ.js.map → generate-next-step-DgK5BlBn.js.map} +1 -1
- package/dist/{generate-postgres-ddl-logic-4eUP4t8w.js → generate-postgres-ddl-logic-DPl1oH_z.js} +4 -4
- package/dist/{generate-postgres-ddl-logic-4eUP4t8w.js.map → generate-postgres-ddl-logic-DPl1oH_z.js.map} +1 -1
- package/dist/generated-schema-diff-eZCklRZQ.js +1025 -0
- package/dist/generated-schema-diff-eZCklRZQ.js.map +1 -0
- package/dist/{generated-schema-staleness-FlxM2i4i.js → generated-schema-staleness-BvNF3i-s.js} +2 -2
- package/dist/{generated-schema-staleness-FlxM2i4i.js.map → generated-schema-staleness-BvNF3i-s.js.map} +1 -1
- package/dist/history/HistoryService.d.ts +7 -0
- package/dist/index.d.ts +21 -0
- package/dist/index.es.js +288 -11
- package/dist/index.es.js.map +1 -1
- package/dist/{introspect-db-logic-CI5CU_n0.js → introspect-db-logic-DgQVzccW.js} +2 -2
- package/dist/{introspect-db-logic-CI5CU_n0.js.map → introspect-db-logic-DgQVzccW.js.map} +1 -1
- package/dist/{rls-bootstrap-sql-Bd-hJk08.js → migrate-baseline-B-2Ef4Mv.js} +718 -41
- package/dist/migrate-baseline-B-2Ef4Mv.js.map +1 -0
- package/dist/migrate-policy-stamps-BQtFHApx.js +89 -0
- package/dist/migrate-policy-stamps-BQtFHApx.js.map +1 -0
- package/dist/{open-rebase-BKA7Nyoe.js → open-rebase-C8jQ6s5a.js} +324 -1047
- package/dist/open-rebase-C8jQ6s5a.js.map +1 -0
- package/dist/open-rebase.d.ts +9 -1
- package/dist/{pg-error-utils-Bv5n2PLn.js → pg-error-utils-DhiUTlVx.js} +2 -2
- package/dist/{pg-error-utils-Bv5n2PLn.js.map → pg-error-utils-DhiUTlVx.js.map} +1 -1
- package/dist/{plan-schema-CXOucCw-.js → plan-schema-Czf3q4-Q.js} +4 -4
- package/dist/{plan-schema-CXOucCw-.js.map → plan-schema-Czf3q4-Q.js.map} +1 -1
- package/dist/{policy-drift-vm0BcvcE.js → policy-drift-X4A-xAzv.js} +3 -3
- package/dist/{policy-drift-vm0BcvcE.js.map → policy-drift-X4A-xAzv.js.map} +1 -1
- package/dist/pool-disconnects.d.ts +28 -0
- package/dist/{push-index-prebuild-DIequ4bV.js → push-index-prebuild-UTvB5pqH.js} +4 -4
- package/dist/{push-index-prebuild-DIequ4bV.js.map → push-index-prebuild-UTvB5pqH.js.map} +1 -1
- package/dist/push-kept-defaults-BGOlOqRQ.js +137 -0
- package/dist/push-kept-defaults-BGOlOqRQ.js.map +1 -0
- package/dist/{push-legacy-renames-BqJut4F3.js → push-legacy-renames-BwaKi8xF.js} +4 -4
- package/dist/{push-legacy-renames-BqJut4F3.js.map → push-legacy-renames-BwaKi8xF.js.map} +1 -1
- package/dist/{push-policy-plan-CvX8hiPb.js → push-policy-plan-DQ2tWceD.js} +5 -5
- package/dist/{push-policy-plan-CvX8hiPb.js.map → push-policy-plan-DQ2tWceD.js.map} +1 -1
- package/dist/{render-ddl-CQNwZdse.js → render-ddl-4v88y-Gv.js} +4 -4
- package/dist/{render-ddl-CQNwZdse.js.map → render-ddl-4v88y-Gv.js.map} +1 -1
- package/dist/{rls-enforcement-D7P4-ZYX.js → rls-enforcement-BOHuHVlc.js} +43 -4
- package/dist/rls-enforcement-BOHuHVlc.js.map +1 -0
- package/dist/schema/destructive-sql.d.ts +16 -0
- package/dist/schema/doctor-cli.js +5 -5
- package/dist/schema/doctor.d.ts +20 -2
- package/dist/schema/ensure-collection-policies.d.ts +4 -1
- package/dist/schema/generate-drizzle-schema.js +1 -1
- package/dist/schema/generate-postgres-ddl.js +2 -2
- package/dist/schema/generated-schema-diff.d.ts +21 -0
- package/dist/schema/introspect-db.js +5 -6
- package/dist/schema/introspect-db.js.map +1 -1
- package/dist/schema/migrate-policy-stamps.d.ts +23 -0
- package/dist/schema/migration-freshness.d.ts +69 -0
- package/dist/schema/push-kept-defaults.d.ts +57 -0
- package/dist/schema/push-policy-plan.d.ts +5 -0
- package/dist/schema/rls-bootstrap-sql.d.ts +47 -10
- package/dist/security/rls-enforcement.d.ts +18 -0
- package/dist/services/BranchService.d.ts +20 -2
- package/dist/services/FetchService.d.ts +12 -0
- package/dist/services/channel-presence.d.ts +3 -1
- package/dist/services/nested-path.d.ts +16 -0
- package/dist/services/pg-notify-listener.d.ts +2 -0
- package/dist/services/realtimeService.d.ts +18 -1
- package/dist/{source-Bv5KPeIx.js → source-fZb92pN8.js} +2 -2
- package/dist/{source-Bv5KPeIx.js.map → source-fZb92pN8.js.map} +1 -1
- package/dist/{vector-index-BFhyEaiz.js → vector-index-DsQ3V2d2.js} +2 -2
- package/dist/{vector-index-BFhyEaiz.js.map → vector-index-DsQ3V2d2.js.map} +1 -1
- package/dist/{websocket-MC0zTBar.js → websocket-CKYc3V4N.js} +56 -14
- package/dist/websocket-CKYc3V4N.js.map +1 -0
- package/package.json +7 -7
- package/dist/BranchService-iXXZeVYD.js.map +0 -1
- package/dist/backup-cli-B2CAn9fR.js +0 -647
- package/dist/backup-cli-B2CAn9fR.js.map +0 -1
- package/dist/backup-service-Bb39plam.js.map +0 -1
- package/dist/cli-console-DQ_K-sdM.js.map +0 -1
- package/dist/cli-errors-DwQnNATs.js.map +0 -1
- package/dist/cli-flags-D-b0qL9T.js +0 -206
- package/dist/cli-flags-D-b0qL9T.js.map +0 -1
- package/dist/collections-schema-version-T6cGvrDi.js.map +0 -1
- package/dist/connection-C938nR3E.js.map +0 -1
- package/dist/databasePoolManager-Yqdw14wY.js.map +0 -1
- package/dist/doctor-DvJH-IQG.js.map +0 -1
- package/dist/ensure-collection-policies-CCqayoUz.js.map +0 -1
- package/dist/open-rebase-BKA7Nyoe.js.map +0 -1
- package/dist/rls-bootstrap-sql-Bd-hJk08.js.map +0 -1
- package/dist/rls-enforcement-D7P4-ZYX.js.map +0 -1
- package/dist/rolldown-runtime-V7rFbMRe.js +0 -39
- package/dist/websocket-MC0zTBar.js.map +0 -1
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { createRequire as __createRequire } from "module";
|
|
2
2
|
__createRequire(import.meta.url);
|
|
3
|
-
import { i as
|
|
3
|
+
import { a as __require, i as __exportAll, n as require_arg, o as __toESM, r as __commonJSMin } from "./cli-prompt-DXG0yk-h.js";
|
|
4
|
+
import { B as withAppClient, tt as redactConnectionPasswords } from "./cli-errors-DsYyNYzd.js";
|
|
4
5
|
import { LEGACY_RLS_SCHEMA, REBASE_SCHEMA } from "@rebasepro/types";
|
|
5
6
|
import { sqlRows } from "@rebasepro/common";
|
|
7
|
+
import { suggestNearMiss } from "@rebasepro/utils";
|
|
6
8
|
import process$1, { execArgv, execPath, hrtime, platform } from "node:process";
|
|
7
9
|
import { constants } from "node:os";
|
|
8
10
|
import tty from "node:tty";
|
|
@@ -6192,6 +6194,7 @@ var { sendMessage, getOneMessage, getEachMessage, getCancelSignal } = getIpcExpo
|
|
|
6192
6194
|
//#region src/schema/rls-bootstrap-sql.ts
|
|
6193
6195
|
var rls_bootstrap_sql_exports = /* @__PURE__ */ __exportAll({
|
|
6194
6196
|
DROP_LEGACY_AUTH_SCHEMA_SQL: () => DROP_LEGACY_AUTH_SCHEMA_SQL,
|
|
6197
|
+
LEGACY_AUTH_SCHEMA_CONTENTS_SQL: () => LEGACY_AUTH_SCHEMA_CONTENTS_SQL,
|
|
6195
6198
|
LEGACY_RLS_DEPENDENTS_SQL: () => LEGACY_RLS_DEPENDENTS_SQL,
|
|
6196
6199
|
LEGACY_RLS_FUNCTION_DEPENDENTS_SQL: () => LEGACY_RLS_FUNCTION_DEPENDENTS_SQL,
|
|
6197
6200
|
RLS_BOOTSTRAP_SQL: () => RLS_BOOTSTRAP_SQL,
|
|
@@ -6356,6 +6359,60 @@ async function rlsRuntimeIsCurrent(run) {
|
|
|
6356
6359
|
/** The same statements as one script, for migration files and raw clients. */
|
|
6357
6360
|
var RLS_BOOTSTRAP_SQL = "-- Rebase schema + RLS helper functions (required by the policies below)\n" + RLS_BOOTSTRAP_STATEMENTS.map((s) => `${s};`).join("\n\n") + "\n";
|
|
6358
6361
|
/**
|
|
6362
|
+
* The three functions every pre-1.0 release put in `auth`. (The auth tables
|
|
6363
|
+
* lived in `rebase`, or wherever the users collection was declared.)
|
|
6364
|
+
*
|
|
6365
|
+
* `uid()` is matched on either spelling of the principal because Rebase wrote
|
|
6366
|
+
* both. Until v0.10.0 its body was
|
|
6367
|
+
* `SELECT NULLIF(current_setting('app.user_id', true), '')`; v0.10.0 renamed
|
|
6368
|
+
* the GUC to `app.uid` and kept `app.user_id` as a fallback; v0.14.0 moved the
|
|
6369
|
+
* helpers to `rebase`. A database last booted before v0.10.0 therefore holds an
|
|
6370
|
+
* `auth.uid()` that reads `app.user_id` alone — as does anything that copied
|
|
6371
|
+
* that body, the Rebase control plane included — and matching `app.uid` only
|
|
6372
|
+
* left every one of them in place, with the schema around it.
|
|
6373
|
+
*
|
|
6374
|
+
* This is the one place either spelling is listed: the drop and the report of
|
|
6375
|
+
* what it left are both generated from it, so they cannot disagree about which
|
|
6376
|
+
* functions are Rebase's.
|
|
6377
|
+
*/
|
|
6378
|
+
var LEGACY_AUTH_HELPERS = [
|
|
6379
|
+
{
|
|
6380
|
+
name: "uid",
|
|
6381
|
+
returns: "text",
|
|
6382
|
+
reads: ["app.uid", "app.user_id"]
|
|
6383
|
+
},
|
|
6384
|
+
{
|
|
6385
|
+
name: "jwt",
|
|
6386
|
+
returns: "jsonb",
|
|
6387
|
+
reads: ["app.jwt"]
|
|
6388
|
+
},
|
|
6389
|
+
{
|
|
6390
|
+
name: "roles",
|
|
6391
|
+
returns: "text",
|
|
6392
|
+
reads: ["app.user_roles"]
|
|
6393
|
+
}
|
|
6394
|
+
];
|
|
6395
|
+
/**
|
|
6396
|
+
* `pg_proc p` has a Rebase helper's name, arity and result type: what
|
|
6397
|
+
* `CREATE OR REPLACE` would have left, whatever the body.
|
|
6398
|
+
*
|
|
6399
|
+
* Two things that share that shape are excluded outright: a body reading
|
|
6400
|
+
* `request.jwt` (Supabase's helpers and anything modelled on them) and a
|
|
6401
|
+
* function an extension installed (`pg_session_jwt` puts an `auth.jwt()
|
|
6402
|
+
* RETURNS jsonb` in the same schema).
|
|
6403
|
+
*/
|
|
6404
|
+
var isLegacyHelperShaped = (helper) => `(
|
|
6405
|
+
p.proname = '${helper.name}' AND p.pronargs = 0
|
|
6406
|
+
AND pg_get_function_result(p.oid) = '${helper.returns}'
|
|
6407
|
+
AND strpos(p.prosrc, 'request.jwt') = 0
|
|
6408
|
+
AND NOT EXISTS (
|
|
6409
|
+
SELECT 1 FROM pg_depend e
|
|
6410
|
+
WHERE e.classid = 'pg_proc'::regclass AND e.objid = p.oid AND e.deptype = 'e'
|
|
6411
|
+
))`;
|
|
6412
|
+
/** `pg_proc p` is a helper Rebase wrote: its shape, and a body reading one of its GUCs. */
|
|
6413
|
+
var isLegacyHelper = (helper) => `${isLegacyHelperShaped(helper)}
|
|
6414
|
+
AND (${helper.reads.map((guc) => `strpos(p.prosrc, '${guc}') > 0`).join(" OR ")})`;
|
|
6415
|
+
/**
|
|
6359
6416
|
* Removes the pre-1.0 `auth` schema, but only when Rebase is what put it there.
|
|
6360
6417
|
*
|
|
6361
6418
|
* ## Why this is safe against a Supabase database
|
|
@@ -6363,11 +6420,12 @@ var RLS_BOOTSTRAP_SQL = "-- Rebase schema + RLS helper functions (required by th
|
|
|
6363
6420
|
* Two independent guards, and both have to pass:
|
|
6364
6421
|
*
|
|
6365
6422
|
* 1. **Each function is identified before it is dropped.** Ours returns `text`
|
|
6366
|
-
* and reads the `app.uid` GUC; Supabase's returns `uuid`
|
|
6367
|
-
* `request.jwt.claims`. Nothing is dropped on a signature we did
|
|
6368
|
-
* so a Supabase database — where our `CREATE OR REPLACE` could
|
|
6369
|
-
* succeeded in the first place, Postgres refusing to change a
|
|
6370
|
-
* matches nothing and this is a no-op.
|
|
6423
|
+
* and reads the `app.uid` or `app.user_id` GUC; Supabase's returns `uuid`
|
|
6424
|
+
* and reads `request.jwt.claims`. Nothing is dropped on a signature we did
|
|
6425
|
+
* not write, so a Supabase database — where our `CREATE OR REPLACE` could
|
|
6426
|
+
* never have succeeded in the first place, Postgres refusing to change a
|
|
6427
|
+
* return type — matches nothing and this is a no-op. See
|
|
6428
|
+
* {@link LEGACY_AUTH_HELPERS} for exactly what is matched.
|
|
6371
6429
|
* 2. **`DROP SCHEMA … RESTRICT`**, never CASCADE. If anything else at all still
|
|
6372
6430
|
* lives in `auth` (Supabase's `users` table, its other helpers), the drop
|
|
6373
6431
|
* fails and the schema stays. CASCADE here would be unrecoverable.
|
|
@@ -6388,41 +6446,21 @@ DECLARE
|
|
|
6388
6446
|
BEGIN
|
|
6389
6447
|
-- Each function is matched on its own result type and body, so a schema
|
|
6390
6448
|
-- that merely shares the name keeps everything it has.
|
|
6391
|
-
IF EXISTS (
|
|
6392
|
-
SELECT 1 FROM pg_proc p JOIN pg_namespace n ON n.oid = p.pronamespace
|
|
6393
|
-
WHERE n.nspname = 'auth' AND p.proname = 'uid'
|
|
6394
|
-
AND pg_get_function_result(p.oid) = 'text'
|
|
6395
|
-
AND p.prosrc LIKE '%app.uid%'
|
|
6396
|
-
) THEN
|
|
6397
|
-
DROP FUNCTION auth.uid();
|
|
6398
|
-
dropped_any := true;
|
|
6399
|
-
END IF;
|
|
6400
|
-
|
|
6401
|
-
IF EXISTS (
|
|
6402
|
-
SELECT 1 FROM pg_proc p JOIN pg_namespace n ON n.oid = p.pronamespace
|
|
6403
|
-
WHERE n.nspname = 'auth' AND p.proname = 'jwt'
|
|
6404
|
-
AND pg_get_function_result(p.oid) = 'jsonb'
|
|
6405
|
-
AND p.prosrc LIKE '%app.jwt%'
|
|
6406
|
-
) THEN
|
|
6407
|
-
DROP FUNCTION auth.jwt();
|
|
6408
|
-
dropped_any := true;
|
|
6409
|
-
END IF;
|
|
6410
|
-
|
|
6411
|
-
IF EXISTS (
|
|
6449
|
+
${LEGACY_AUTH_HELPERS.map((helper) => ` IF EXISTS (
|
|
6412
6450
|
SELECT 1 FROM pg_proc p JOIN pg_namespace n ON n.oid = p.pronamespace
|
|
6413
|
-
WHERE n.nspname = '
|
|
6414
|
-
AND pg_get_function_result(p.oid) = 'text'
|
|
6415
|
-
AND p.prosrc LIKE '%app.user_roles%'
|
|
6451
|
+
WHERE n.nspname = '${LEGACY_RLS_SCHEMA}' AND ${isLegacyHelper(helper)}
|
|
6416
6452
|
) THEN
|
|
6417
|
-
DROP FUNCTION
|
|
6453
|
+
DROP FUNCTION ${LEGACY_RLS_SCHEMA}.${helper.name}();
|
|
6418
6454
|
dropped_any := true;
|
|
6419
|
-
END IF
|
|
6455
|
+
END IF;`).join("\n\n")}
|
|
6420
6456
|
|
|
6421
6457
|
-- RESTRICT: only an empty schema goes. Anything else in there — including a
|
|
6422
|
-
-- Supabase installation left untouched above — keeps it.
|
|
6458
|
+
-- Supabase installation left untouched above — keeps it. Swallowed so the
|
|
6459
|
+
-- helpers dropped above stay dropped; dropLegacyAuthSchema looks afterwards
|
|
6460
|
+
-- and says what is left.
|
|
6423
6461
|
IF dropped_any THEN
|
|
6424
6462
|
BEGIN
|
|
6425
|
-
EXECUTE 'DROP SCHEMA
|
|
6463
|
+
EXECUTE 'DROP SCHEMA ${LEGACY_RLS_SCHEMA} RESTRICT';
|
|
6426
6464
|
EXCEPTION WHEN OTHERS THEN
|
|
6427
6465
|
NULL;
|
|
6428
6466
|
END;
|
|
@@ -6431,6 +6469,40 @@ END
|
|
|
6431
6469
|
$rebase_drop_legacy$;
|
|
6432
6470
|
`;
|
|
6433
6471
|
/**
|
|
6472
|
+
* What the `auth` schema holds, one row per object keeping it alive.
|
|
6473
|
+
*
|
|
6474
|
+
* No rows: the schema does not exist. A single row with a `null` object: it
|
|
6475
|
+
* exists and is empty. Read from the namespace entries in `pg_depend`, which
|
|
6476
|
+
* are what `DROP SCHEMA … RESTRICT` itself consults — minus whatever goes with
|
|
6477
|
+
* its owner (a serial's sequence, an identity column's), which Postgres does not
|
|
6478
|
+
* name either.
|
|
6479
|
+
*/
|
|
6480
|
+
var LEGACY_AUTH_SCHEMA_CONTENTS_SQL = `
|
|
6481
|
+
SELECT pg_describe_object(d.classid, d.objid, d.objsubid) AS object,
|
|
6482
|
+
CASE
|
|
6483
|
+
WHEN d.classid <> 'pg_proc'::regclass THEN NULL
|
|
6484
|
+
WHEN EXISTS (
|
|
6485
|
+
SELECT 1 FROM pg_proc p
|
|
6486
|
+
WHERE p.oid = d.objid
|
|
6487
|
+
AND (${LEGACY_AUTH_HELPERS.map((helper) => `(${isLegacyHelper(helper)})`).join(" OR ")})
|
|
6488
|
+
) THEN 'ours'
|
|
6489
|
+
WHEN EXISTS (
|
|
6490
|
+
SELECT 1 FROM pg_proc p
|
|
6491
|
+
WHERE p.oid = d.objid
|
|
6492
|
+
AND (${LEGACY_AUTH_HELPERS.map(isLegacyHelperShaped).join(" OR ")})
|
|
6493
|
+
) THEN 'lookalike'
|
|
6494
|
+
END AS helper
|
|
6495
|
+
FROM pg_namespace n
|
|
6496
|
+
LEFT JOIN pg_depend d
|
|
6497
|
+
ON d.refclassid = 'pg_namespace'::regclass AND d.refobjid = n.oid AND d.deptype = 'n'
|
|
6498
|
+
AND NOT EXISTS (
|
|
6499
|
+
SELECT 1 FROM pg_depend o
|
|
6500
|
+
WHERE o.classid = d.classid AND o.objid = d.objid AND o.deptype IN ('a', 'i')
|
|
6501
|
+
)
|
|
6502
|
+
WHERE n.nspname = '${LEGACY_RLS_SCHEMA}'
|
|
6503
|
+
ORDER BY helper NULLS LAST, object
|
|
6504
|
+
`;
|
|
6505
|
+
/**
|
|
6434
6506
|
* Policies whose body still calls `auth.uid()` / `auth.roles()` / `auth.jwt()`.
|
|
6435
6507
|
*
|
|
6436
6508
|
* Postgres will not drop a function a policy depends on, so this is exactly the
|
|
@@ -6461,11 +6533,12 @@ var LEGACY_RLS_DEPENDENTS_SQL = `
|
|
|
6461
6533
|
* exists, which fails at *query* time rather than at boot.
|
|
6462
6534
|
*
|
|
6463
6535
|
* A downstream project building on these helpers is not hypothetical: the Rebase
|
|
6464
|
-
* control plane
|
|
6536
|
+
* control plane defined `auth.is_org_member(uuid)` and `auth.is_org_admin(uuid)`
|
|
6465
6537
|
* in this very schema, each calling `auth.uid()` in a string body, and eleven of
|
|
6466
|
-
* its row-level-security policies
|
|
6467
|
-
* started failing the first time a recompile left no policy referencing
|
|
6468
|
-
* `auth.uid()` directly — the drop's own precondition.
|
|
6538
|
+
* its row-level-security policies went through them. Every one of those would
|
|
6539
|
+
* have started failing the first time a recompile left no policy referencing
|
|
6540
|
+
* `auth.uid()` directly — the drop's own precondition. (Its migration 0065 has
|
|
6541
|
+
* since moved both to `public`, calling `rebase.uid()`.)
|
|
6469
6542
|
*
|
|
6470
6543
|
* Matching on the body text is the only option available, and it is deliberately
|
|
6471
6544
|
* broad: a false positive costs a schema that stays one release longer and says
|
|
@@ -6489,8 +6562,30 @@ var LEGACY_RLS_FUNCTION_DEPENDENTS_SQL = `
|
|
|
6489
6562
|
* table not recompiled *yet* — and wrong for the one that never resolves: a
|
|
6490
6563
|
* hand-written policy nothing will ever rewrite. Then the schema stays forever
|
|
6491
6564
|
* and nothing ever says why.
|
|
6565
|
+
*
|
|
6566
|
+
* The drop itself had the same blind spot. A helper it did not recognise as
|
|
6567
|
+
* Rebase's stayed, and so did anything else in the schema, behind a
|
|
6568
|
+
* `DROP SCHEMA … RESTRICT` whose failure is swallowed so the helpers it did
|
|
6569
|
+
* drop stay dropped. The control plane's `auth.uid()` was one such: it read
|
|
6570
|
+
* `app.user_id` alone, the drop looked for `app.uid`, and the schema stayed
|
|
6571
|
+
* with no word said. So the schema is read twice. Before, so that one with no
|
|
6572
|
+
* Rebase helper in it is left alone: one already gone, as on every boot after
|
|
6573
|
+
* the drop, or one that was never Rebase's, like Supabase's — whose own
|
|
6574
|
+
* policies the checks below would otherwise report as blockers. After, to name
|
|
6575
|
+
* whatever is still there.
|
|
6492
6576
|
*/
|
|
6493
6577
|
async function dropLegacyAuthSchema(run, report) {
|
|
6578
|
+
let before;
|
|
6579
|
+
try {
|
|
6580
|
+
before = sqlRows(await run(LEGACY_AUTH_SCHEMA_CONTENTS_SQL));
|
|
6581
|
+
} catch {
|
|
6582
|
+
return;
|
|
6583
|
+
}
|
|
6584
|
+
if (!before.some((o) => o.helper !== null)) return;
|
|
6585
|
+
if (!before.some((o) => o.helper === "ours")) {
|
|
6586
|
+
report.warn(describeLegacyAuthLeftovers(before, void 0));
|
|
6587
|
+
return;
|
|
6588
|
+
}
|
|
6494
6589
|
let blockers;
|
|
6495
6590
|
try {
|
|
6496
6591
|
blockers = sqlRows(await run(LEGACY_RLS_DEPENDENTS_SQL));
|
|
@@ -6511,13 +6606,595 @@ async function dropLegacyAuthSchema(run, report) {
|
|
|
6511
6606
|
report.warn(`The pre-1.0 \`${LEGACY_RLS_SCHEMA}\` schema cannot be removed yet: ${borrowers.length} ${borrowers.length === 1 ? "function calls" : "functions call"} \`${LEGACY_RLS_SCHEMA}.uid()\` and friends from its own body. Postgres records no dependency for that — a SQL body written as a string literal is never parsed — so dropping the helpers would leave these pointing at nothing, and they would fail when a query reached them rather than now. Repoint them at \`${REBASE_SCHEMA}.uid()\`, after which the schema goes on its own:\n` + borrowers.map((b) => ` • ${b.schema}.${b.function}()`).join("\n"));
|
|
6512
6607
|
return;
|
|
6513
6608
|
}
|
|
6609
|
+
let failure;
|
|
6514
6610
|
try {
|
|
6515
6611
|
await run(DROP_LEGACY_AUTH_SCHEMA_SQL);
|
|
6516
6612
|
} catch (err) {
|
|
6517
|
-
|
|
6613
|
+
const detail = err instanceof Error && "detail" in err && typeof err.detail === "string" ? err.detail : "";
|
|
6614
|
+
failure = (err instanceof Error ? err.message : String(err)) + (detail ? ` (${detail.replace(/\n/g, "; ")})` : "");
|
|
6615
|
+
}
|
|
6616
|
+
let left;
|
|
6617
|
+
try {
|
|
6618
|
+
left = sqlRows(await run(LEGACY_AUTH_SCHEMA_CONTENTS_SQL));
|
|
6619
|
+
} catch {
|
|
6620
|
+
if (failure) report.info(`Left the \`${LEGACY_RLS_SCHEMA}\` schema in place: ${failure}`);
|
|
6621
|
+
return;
|
|
6622
|
+
}
|
|
6623
|
+
if (left.length === 0) return;
|
|
6624
|
+
report.warn(describeLegacyAuthLeftovers(left, failure));
|
|
6625
|
+
}
|
|
6626
|
+
/** At most this many objects are named; a schema this full is plainly in use. */
|
|
6627
|
+
var MAX_LEFTOVERS_NAMED = 20;
|
|
6628
|
+
/**
|
|
6629
|
+
* The warning for an `auth` schema the drop left standing.
|
|
6630
|
+
*
|
|
6631
|
+
* Said once for objects that are not Rebase's: on the next boot the schema holds
|
|
6632
|
+
* no helper, so {@link dropLegacyAuthSchema} leaves it alone — it is somebody
|
|
6633
|
+
* else's schema by then. A helper that is still there, Rebase's or a lookalike,
|
|
6634
|
+
* keeps it said on every boot until it goes.
|
|
6635
|
+
*/
|
|
6636
|
+
function describeLegacyAuthLeftovers(left, failure) {
|
|
6637
|
+
const schema = LEGACY_RLS_SCHEMA;
|
|
6638
|
+
const objects = left.filter((o) => o.object !== null);
|
|
6639
|
+
if (objects.length === 0) return `The pre-1.0 \`${schema}\` schema is empty, but this connection could not drop it — most likely because it does not own the schema. Run \`DROP SCHEMA ${schema};\` as its owner.`;
|
|
6640
|
+
const why = failure ? `dropping Rebase's old helpers from it failed: ${failure}.` : "`DROP SCHEMA … RESTRICT` refuses a schema that is not empty, and Rebase drops only the helpers it can tell are its own.";
|
|
6641
|
+
const lines = objects.slice(0, MAX_LEFTOVERS_NAMED).map((o) => o.helper === "ours" ? ` • ${o.object} — Rebase's old helper, which could not be dropped` : o.helper === "lookalike" ? ` • ${o.object} — named and typed like Rebase's old helper, but no release wrote this body, so it was left alone; drop it by hand if nothing of yours calls it` : ` • ${o.object}`);
|
|
6642
|
+
if (objects.length > MAX_LEFTOVERS_NAMED) lines.push(` • …and ${objects.length - MAX_LEFTOVERS_NAMED} more`);
|
|
6643
|
+
return `The pre-1.0 \`${schema}\` schema is still here: ${why} Still in it:\n` + lines.join("\n") + `
|
|
6644
|
+
Anything of yours can stay where it is — Rebase no longer uses this schema — or move out with \`ALTER … SET SCHEMA\`. Once the schema is empty, \`DROP SCHEMA ${schema};\` removes it.`;
|
|
6645
|
+
}
|
|
6646
|
+
//#endregion
|
|
6647
|
+
//#region src/cli-flags.ts
|
|
6648
|
+
var import_arg = /* @__PURE__ */ __toESM(require_arg(), 1);
|
|
6649
|
+
/**
|
|
6650
|
+
* Flags that reach this process on a line it did not compose.
|
|
6651
|
+
*
|
|
6652
|
+
* `rebase db …` hands the driver the user's whole line: the CLI *reads*
|
|
6653
|
+
* `--database-url` and `--docker` to resolve the database and then relays them
|
|
6654
|
+
* verbatim, and `--debug` is what `bin/rebase.js` prints after every failure as
|
|
6655
|
+
* the thing to re-run with. Rejecting any of the three would make the driver
|
|
6656
|
+
* refuse commands the CLI documents.
|
|
6657
|
+
*/
|
|
6658
|
+
var RELAYED_FLAGS = {
|
|
6659
|
+
"--database-url": String,
|
|
6660
|
+
"--docker": Boolean,
|
|
6661
|
+
"--debug": Boolean,
|
|
6662
|
+
"--help": Boolean,
|
|
6663
|
+
"-h": "--help"
|
|
6664
|
+
};
|
|
6665
|
+
/**
|
|
6666
|
+
* Parse a driver command's line — `["db", "<subcommand>", …]` — with the
|
|
6667
|
+
* relayed flags declared alongside the command's own.
|
|
6668
|
+
*
|
|
6669
|
+
* Every driver parser is permissive, and an undeclared flag in a permissive
|
|
6670
|
+
* parse is a positional. The positionals are what `db migrate` hands
|
|
6671
|
+
* `atlas migrate apply`, what `db generate` names its migration after and
|
|
6672
|
+
* which backup `db restore` restores, so a relayed flag left undeclared was
|
|
6673
|
+
* read as one of those: `rebase db migrate --docker` died on Atlas's
|
|
6674
|
+
* `unknown flag: --docker` after it had already written the auth schema.
|
|
6675
|
+
*/
|
|
6676
|
+
function parseDriverLine(spec, args) {
|
|
6677
|
+
return (0, import_arg.default)({
|
|
6678
|
+
...RELAYED_FLAGS,
|
|
6679
|
+
...spec
|
|
6680
|
+
}, {
|
|
6681
|
+
argv: args.slice(2),
|
|
6682
|
+
permissive: true
|
|
6683
|
+
});
|
|
6684
|
+
}
|
|
6685
|
+
/** Keyed by `"<domain> <subcommand>"`, the way the user types it. */
|
|
6686
|
+
var DRIVER_FLAG_SPECS = {
|
|
6687
|
+
"db push": {
|
|
6688
|
+
"--collections": String,
|
|
6689
|
+
"-c": "--collections",
|
|
6690
|
+
"--dry-run": Boolean,
|
|
6691
|
+
"--allow-destructive": Boolean,
|
|
6692
|
+
"--yes": Boolean,
|
|
6693
|
+
"-y": "--yes"
|
|
6694
|
+
},
|
|
6695
|
+
"db generate": {
|
|
6696
|
+
"--collections": String,
|
|
6697
|
+
"-c": "--collections"
|
|
6698
|
+
},
|
|
6699
|
+
"db migrate": {
|
|
6700
|
+
"--baseline": String,
|
|
6701
|
+
"--refuse-destructive": Boolean
|
|
6702
|
+
},
|
|
6703
|
+
"schema generate": {
|
|
6704
|
+
"--collections": String,
|
|
6705
|
+
"-c": "--collections",
|
|
6706
|
+
"--output": String,
|
|
6707
|
+
"-o": "--output",
|
|
6708
|
+
"--out": "--output",
|
|
6709
|
+
"--watch": Boolean,
|
|
6710
|
+
"-w": "--watch"
|
|
6711
|
+
},
|
|
6712
|
+
"schema introspect": {
|
|
6713
|
+
"--output": String,
|
|
6714
|
+
"-o": "--output",
|
|
6715
|
+
"--out": "--output",
|
|
6716
|
+
"--collections": String,
|
|
6717
|
+
"-c": "--collections",
|
|
6718
|
+
"--force": Boolean,
|
|
6719
|
+
"-f": "--force",
|
|
6720
|
+
"--schema": String,
|
|
6721
|
+
"--data-inference": Boolean,
|
|
6722
|
+
"--no-data-inference": Boolean
|
|
6723
|
+
},
|
|
6724
|
+
"schema stale": {
|
|
6725
|
+
"--collections": String,
|
|
6726
|
+
"-c": "--collections",
|
|
6727
|
+
"--output": String,
|
|
6728
|
+
"-o": "--output",
|
|
6729
|
+
"--out": "--output",
|
|
6730
|
+
"--fix": Boolean
|
|
6731
|
+
}
|
|
6732
|
+
};
|
|
6733
|
+
/**
|
|
6734
|
+
* The `--collections` path the user typed, or `null` if they typed none.
|
|
6735
|
+
*
|
|
6736
|
+
* Read once, at the entry point, because every command that takes it re-enters
|
|
6737
|
+
* the generators with the same line and each of them resolved it again — which
|
|
6738
|
+
* is how "Collections path not found" came to be printed four times before the
|
|
6739
|
+
* real output, and how a path that does not exist got as far as *writing* an
|
|
6740
|
+
* empty schema.
|
|
6741
|
+
*
|
|
6742
|
+
* Permissive, and it has to be: {@link assertKnownFlags} has already judged the
|
|
6743
|
+
* line, and `db push` carries flags the `--collections` spec does not name.
|
|
6744
|
+
*/
|
|
6745
|
+
function collectionsPathIn(args) {
|
|
6746
|
+
try {
|
|
6747
|
+
return (0, import_arg.default)({
|
|
6748
|
+
"--collections": String,
|
|
6749
|
+
"-c": "--collections"
|
|
6750
|
+
}, {
|
|
6751
|
+
argv: args.slice(2),
|
|
6752
|
+
permissive: true
|
|
6753
|
+
})["--collections"] ?? null;
|
|
6754
|
+
} catch {
|
|
6755
|
+
return null;
|
|
6756
|
+
}
|
|
6757
|
+
}
|
|
6758
|
+
/**
|
|
6759
|
+
* Reject a flag the named command does not take.
|
|
6760
|
+
*
|
|
6761
|
+
* `args` is the driver's whole line — `["db", "push", …]` — so the flags are
|
|
6762
|
+
* read from `args.slice(2)`, past the two command words.
|
|
6763
|
+
*
|
|
6764
|
+
* Throws rather than exiting: `runPluginCommand`'s caller already turns a
|
|
6765
|
+
* thrown error into one red line and exit 1, and a thrown error is what the
|
|
6766
|
+
* tests can see.
|
|
6767
|
+
*/
|
|
6768
|
+
function assertKnownFlags(domain, subcommand, args) {
|
|
6769
|
+
if (!subcommand) return;
|
|
6770
|
+
const spec = DRIVER_FLAG_SPECS[`${domain} ${subcommand}`];
|
|
6771
|
+
if (!spec) return;
|
|
6772
|
+
const argv = resolveSwitchValues({
|
|
6773
|
+
...RELAYED_FLAGS,
|
|
6774
|
+
...spec
|
|
6775
|
+
}, args.slice(2), `${domain} ${subcommand}`, domain);
|
|
6776
|
+
try {
|
|
6777
|
+
(0, import_arg.default)({
|
|
6778
|
+
...RELAYED_FLAGS,
|
|
6779
|
+
...spec
|
|
6780
|
+
}, { argv });
|
|
6781
|
+
} catch (err) {
|
|
6782
|
+
const unknown = unknownFlagIn(err, args.slice(2));
|
|
6783
|
+
if (unknown === null) throw err;
|
|
6784
|
+
throw new Error(unknownOptionSentence(`${domain} ${subcommand}`, domain, unknown, spec));
|
|
6785
|
+
}
|
|
6786
|
+
}
|
|
6787
|
+
/**
|
|
6788
|
+
* The sentence an undeclared flag is refused with, in the words the CLI's own
|
|
6789
|
+
* parser uses: "`rebase db push` has no option --dry-rn — did you mean
|
|
6790
|
+
* --dry-run? Nothing was run."
|
|
6791
|
+
*
|
|
6792
|
+
* `token` is the whole argument `arg` refused, which is not always a flag: one
|
|
6793
|
+
* argument holding a flag and its value, quoted together
|
|
6794
|
+
* (`"--from postgres://u:pw@h/db"`), is refused whole. That one is named as
|
|
6795
|
+
* what it is, and what it quotes is password-masked; no other part of an
|
|
6796
|
+
* argument is quoted at all.
|
|
6797
|
+
*
|
|
6798
|
+
* @param command the command as typed: `"db push"`.
|
|
6799
|
+
* @param help the command whose `--help` the sentence names.
|
|
6800
|
+
* @param spec the command's own flags; the relayed ones are offered too.
|
|
6801
|
+
*/
|
|
6802
|
+
function unknownOptionSentence(command, help, token, spec) {
|
|
6803
|
+
const full = {
|
|
6804
|
+
...RELAYED_FLAGS,
|
|
6805
|
+
...spec
|
|
6806
|
+
};
|
|
6807
|
+
const name = token.split(/[=\s]/)[0];
|
|
6808
|
+
const suggestion = closestWord(name, Object.keys(full).filter((flag) => flag.startsWith("--")));
|
|
6809
|
+
const tail = ` Nothing was run. See \`rebase ${help} --help\`.`;
|
|
6810
|
+
if (/\s/.test(token.trim())) {
|
|
6811
|
+
const flag = name in full ? name : suggestion;
|
|
6812
|
+
const how = flag ? ` Write them as two arguments, ${flag} <value>, or as ${flag}=<value>.` : " Write a flag and its value as two arguments.";
|
|
6813
|
+
return `\`rebase ${command}\` has no option "${redactConnectionPasswords(token)}": that is a flag and a value in one argument, quoted together.${how}${tail}`;
|
|
6814
|
+
}
|
|
6815
|
+
const own = Object.keys(spec).filter((flag) => flag.startsWith("--"));
|
|
6816
|
+
const takes = own.length > 0 ? `it takes ${own.join(", ")}` : "it takes no options of its own";
|
|
6817
|
+
return `\`rebase ${command}\` has no option ${name}` + (suggestion ? ` — did you mean ${suggestion}?` : ` — ${takes}.`) + tail;
|
|
6818
|
+
}
|
|
6819
|
+
/** What a flag resolves to, through its alias (`"-y": "--yes"`). */
|
|
6820
|
+
function handlerOf(spec, flag) {
|
|
6821
|
+
const declared = spec[flag];
|
|
6822
|
+
return typeof declared === "string" ? spec[declared] : declared;
|
|
6823
|
+
}
|
|
6824
|
+
/** A flag that takes no value: on when given, off when left out. */
|
|
6825
|
+
function isSwitch(handler) {
|
|
6826
|
+
return handler === Boolean || Array.isArray(handler) && handler[0] === Boolean;
|
|
6827
|
+
}
|
|
6828
|
+
/** The values a switch reads as on, and as off, after its `=`. */
|
|
6829
|
+
var SWITCH_ON = /^(?:true|1)$/i;
|
|
6830
|
+
var SWITCH_OFF = /^(?:false|0)$/i;
|
|
6831
|
+
/**
|
|
6832
|
+
* A line with every `--switch=value` resolved, before `arg` reads it: `=true`
|
|
6833
|
+
* or `=1` is the switch alone, `=false` or `=0` drops it, and any other value
|
|
6834
|
+
* is refused.
|
|
6835
|
+
*
|
|
6836
|
+
* `arg` drops the value of a flag that takes none and turns the flag on, so
|
|
6837
|
+
* `db push --dry-run=false` was a dry run and `--allow-destructive=false`
|
|
6838
|
+
* allowed the destructive statements it was typed to forbid. `=true` kept
|
|
6839
|
+
* working by the same accident, and scripts write it, so both readings a value
|
|
6840
|
+
* can have are honoured and nothing else is guessed at. Past `--` every word
|
|
6841
|
+
* is a positional, so nothing there is read.
|
|
6842
|
+
*
|
|
6843
|
+
* @param command the command as typed, for the message: `"db push"`.
|
|
6844
|
+
* @param help the command whose `--help` the message names.
|
|
6845
|
+
*/
|
|
6846
|
+
function resolveSwitchValues(spec, argv, command, help = command) {
|
|
6847
|
+
const resolved = [];
|
|
6848
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
6849
|
+
const word = argv[index];
|
|
6850
|
+
if (word === "--") {
|
|
6851
|
+
resolved.push(...argv.slice(index));
|
|
6852
|
+
break;
|
|
6853
|
+
}
|
|
6854
|
+
const flag = word.startsWith("--") && word.includes("=") ? word.slice(0, word.indexOf("=")) : null;
|
|
6855
|
+
if (flag === null || !isSwitch(handlerOf(spec, flag))) {
|
|
6856
|
+
resolved.push(word);
|
|
6857
|
+
continue;
|
|
6858
|
+
}
|
|
6859
|
+
const value = word.slice(flag.length + 1);
|
|
6860
|
+
if (SWITCH_ON.test(value)) resolved.push(flag);
|
|
6861
|
+
else if (!SWITCH_OFF.test(value)) throw new Error(`\`rebase ${command}\` cannot read ${redactConnectionPasswords(word)}: ${flag} is a switch, and after its = it takes true or 1 to turn it on, false or 0 to turn it off. Nothing was run. See \`rebase ${help} --help\`.`);
|
|
6862
|
+
}
|
|
6863
|
+
return resolved;
|
|
6864
|
+
}
|
|
6865
|
+
/**
|
|
6866
|
+
* A driver command's line, `["<domain>", "<subcommand>", …]`, with its
|
|
6867
|
+
* switches' values resolved by {@link resolveSwitchValues} against the
|
|
6868
|
+
* command's spec: what `runPluginCommand` hands every parser after it, so a
|
|
6869
|
+
* permissive one never reads `--allow-destructive=false` as on. A command with
|
|
6870
|
+
* no spec here comes back as it was.
|
|
6871
|
+
*/
|
|
6872
|
+
function resolveDriverSwitches(domain, subcommand, args) {
|
|
6873
|
+
const spec = subcommand ? DRIVER_FLAG_SPECS[`${domain} ${subcommand}`] : void 0;
|
|
6874
|
+
if (!spec) return [...args];
|
|
6875
|
+
return [...args.slice(0, 2), ...resolveSwitchValues({
|
|
6876
|
+
...RELAYED_FLAGS,
|
|
6877
|
+
...spec
|
|
6878
|
+
}, args.slice(2), `${domain} ${subcommand}`, domain)];
|
|
6879
|
+
}
|
|
6880
|
+
/**
|
|
6881
|
+
* The argument `arg` refused as undeclared, whole, or `null` for any other
|
|
6882
|
+
* failure.
|
|
6883
|
+
*
|
|
6884
|
+
* arg's wording is `unknown or unexpected option: --targt-db`, naming the
|
|
6885
|
+
* argument up to its first `=`. An argument with a space in it is named past
|
|
6886
|
+
* the space too, so the match runs to the end of the message; and the word
|
|
6887
|
+
* in `argv` it came from is returned, `=` and all, so a refusal that quotes it
|
|
6888
|
+
* quotes what was typed.
|
|
6889
|
+
*/
|
|
6890
|
+
function unknownFlagIn(err, argv) {
|
|
6891
|
+
if (!(err instanceof Error) || !("code" in err) || err.code !== "ARG_UNKNOWN_OPTION") return null;
|
|
6892
|
+
const named = /option: ([\s\S]+)$/.exec(err.message)?.[1];
|
|
6893
|
+
if (named === void 0) return null;
|
|
6894
|
+
return argv.find((word) => word === named || word.startsWith(`${named}=`)) ?? named;
|
|
6895
|
+
}
|
|
6896
|
+
/**
|
|
6897
|
+
* The word in `known` that `typed` was probably meant to be: one edit away
|
|
6898
|
+
* (`--targt-db`), or two neighbouring letters swapped (`--clena`, `--froce`),
|
|
6899
|
+
* which `suggestNearMiss` counts as two edits and so never offers.
|
|
6900
|
+
*/
|
|
6901
|
+
function closestWord(typed, known) {
|
|
6902
|
+
return suggestNearMiss(known, typed) ?? transposedFrom(known, typed);
|
|
6903
|
+
}
|
|
6904
|
+
function transposedFrom(known, typed) {
|
|
6905
|
+
return known.find((candidate) => {
|
|
6906
|
+
if (candidate.length !== typed.length || candidate === typed) return false;
|
|
6907
|
+
const differ = [...candidate].flatMap((letter, index) => letter === typed[index] ? [] : [index]);
|
|
6908
|
+
return differ.length === 2 && differ[1] === differ[0] + 1 && candidate[differ[0]] === typed[differ[1]] && candidate[differ[1]] === typed[differ[0]];
|
|
6909
|
+
});
|
|
6910
|
+
}
|
|
6911
|
+
/**
|
|
6912
|
+
* Parse a command that owns its argument handling, refusing what it does not
|
|
6913
|
+
* declare.
|
|
6914
|
+
*
|
|
6915
|
+
* `db backup`, `db restore` and `db backups list` parsed permissively, so an
|
|
6916
|
+
* undeclared flag became a positional and was ignored: `db restore <dump>
|
|
6917
|
+
* --targt-db x --create-db --yes` dropped the misspelled target and restored
|
|
6918
|
+
* into the live database. A typo is refused here before anything runs, naming
|
|
6919
|
+
* the declared flag it is one edit from, or every flag the command takes when
|
|
6920
|
+
* none is that close.
|
|
6921
|
+
*
|
|
6922
|
+
* @param argv the words after the command, e.g. `rawArgs.slice(2)`.
|
|
6923
|
+
* @param command the command as typed, for the message: `"db restore"`.
|
|
6924
|
+
*/
|
|
6925
|
+
function parseCommandFlags(spec, argv, command) {
|
|
6926
|
+
const full = {
|
|
6927
|
+
...RELAYED_FLAGS,
|
|
6928
|
+
...spec
|
|
6929
|
+
};
|
|
6930
|
+
try {
|
|
6931
|
+
return (0, import_arg.default)(full, { argv: resolveSwitchValues(full, argv, command) });
|
|
6932
|
+
} catch (err) {
|
|
6933
|
+
const unknown = unknownFlagIn(err, argv);
|
|
6934
|
+
if (unknown === null) throw err;
|
|
6935
|
+
throw new Error(unknownOptionSentence(command, command, unknown, spec));
|
|
6936
|
+
}
|
|
6937
|
+
}
|
|
6938
|
+
//#endregion
|
|
6939
|
+
//#region src/branch-argv.ts
|
|
6940
|
+
/**
|
|
6941
|
+
* Argument shapes for `rebase db branch`, with no runtime dependencies.
|
|
6942
|
+
*
|
|
6943
|
+
* Its own module for the same reason `schema/atlas-argv.ts` is: `cli.ts` pulls
|
|
6944
|
+
* in chalk, execa and the driver runtime, so a unit test that imports it cannot
|
|
6945
|
+
* run. Argv parsing is the part most worth testing and the part least in need
|
|
6946
|
+
* of any of that.
|
|
6947
|
+
*/
|
|
6948
|
+
/**
|
|
6949
|
+
* The flags each driver-side `db branch` action takes, besides the ones the CLI
|
|
6950
|
+
* relays on every line (`--database-url`, `--docker`, `--debug`).
|
|
6951
|
+
*
|
|
6952
|
+
* `switch` is absent: the CLI answers it before the driver is ever spawned.
|
|
6953
|
+
* An action not listed here is not checked, so the dispatch can say it does
|
|
6954
|
+
* not exist in its own words.
|
|
6955
|
+
*/
|
|
6956
|
+
var BRANCH_ACTION_FLAGS = {
|
|
6957
|
+
create: {
|
|
6958
|
+
"--from": String,
|
|
6959
|
+
"--force": Boolean
|
|
6960
|
+
},
|
|
6961
|
+
list: {},
|
|
6962
|
+
info: {},
|
|
6963
|
+
diff: {
|
|
6964
|
+
"--json": Boolean,
|
|
6965
|
+
"--sample-limit": Number
|
|
6966
|
+
},
|
|
6967
|
+
delete: {
|
|
6968
|
+
"--force": Boolean,
|
|
6969
|
+
"--yes": Boolean,
|
|
6970
|
+
"-y": "--yes"
|
|
6971
|
+
},
|
|
6972
|
+
prune: {
|
|
6973
|
+
"--older-than": String,
|
|
6974
|
+
"--include-dev-diff": Boolean,
|
|
6975
|
+
"--yes": Boolean,
|
|
6976
|
+
"-y": "--yes"
|
|
6977
|
+
}
|
|
6978
|
+
};
|
|
6979
|
+
/**
|
|
6980
|
+
* Refuse a flag `action` does not take, naming the one it was probably meant
|
|
6981
|
+
* to be — the same sentence `db backup` and `db restore` say.
|
|
6982
|
+
*
|
|
6983
|
+
* Throws: the driver's entry turns a thrown error into one red line and exit 1,
|
|
6984
|
+
* and this runs before anything connects.
|
|
6985
|
+
*/
|
|
6986
|
+
function assertKnownBranchFlags(action, rest) {
|
|
6987
|
+
const spec = BRANCH_ACTION_FLAGS[action];
|
|
6988
|
+
if (!spec) return;
|
|
6989
|
+
const full = {
|
|
6990
|
+
...RELAYED_FLAGS,
|
|
6991
|
+
...spec
|
|
6992
|
+
};
|
|
6993
|
+
const argv = resolveSwitchValues(full, rest, `db branch ${action}`, "db branch");
|
|
6994
|
+
try {
|
|
6995
|
+
(0, import_arg.default)(full, { argv });
|
|
6996
|
+
} catch (error) {
|
|
6997
|
+
const unknown = unknownFlagIn(error, rest);
|
|
6998
|
+
if (unknown === null) throw error;
|
|
6999
|
+
throw new Error(unknownOptionSentence(`db branch ${action}`, "db branch", unknown, spec));
|
|
6518
7000
|
}
|
|
6519
7001
|
}
|
|
7002
|
+
/**
|
|
7003
|
+
* A `db branch` line, `["db", "branch", <action>, …]`, with its switches'
|
|
7004
|
+
* values resolved against the action's flags (see `resolveSwitchValues`):
|
|
7005
|
+
* what the branch dispatch reads, so `prune --yes=false` is not a yes. An
|
|
7006
|
+
* action the table does not list comes back as it was.
|
|
7007
|
+
*/
|
|
7008
|
+
function resolveBranchSwitches(rawArgs) {
|
|
7009
|
+
const action = rawArgs[2];
|
|
7010
|
+
const spec = action === void 0 ? void 0 : BRANCH_ACTION_FLAGS[action];
|
|
7011
|
+
if (!spec) return [...rawArgs];
|
|
7012
|
+
return [...rawArgs.slice(0, 3), ...resolveSwitchValues({
|
|
7013
|
+
...RELAYED_FLAGS,
|
|
7014
|
+
...spec
|
|
7015
|
+
}, rawArgs.slice(3), `db branch ${action}`, "db branch")];
|
|
7016
|
+
}
|
|
7017
|
+
/**
|
|
7018
|
+
* Words on a `db branch` command line that no argument accounts for.
|
|
7019
|
+
*
|
|
7020
|
+
* `rebase db branch create alpha beta` created a branch called `alpha` and
|
|
7021
|
+
* threw `beta` away without a word. The shapes that produces are all quiet and
|
|
7022
|
+
* all wrong: an unquoted name (`create my feature` → `my`), a flag written
|
|
7023
|
+
* without its dashes (`create feat from main` → `feat`), a shell that split
|
|
7024
|
+
* something you thought was one token. In each case the command succeeds and
|
|
7025
|
+
* the branch is not the one asked for — and branch names are the thing you
|
|
7026
|
+
* later type to switch, delete, or point a deploy at.
|
|
7027
|
+
*
|
|
7028
|
+
* A flag the action does not take is refused outright, by throwing — see
|
|
7029
|
+
* {@link BRANCH_ACTION_FLAGS}. It was let through on the reasoning that a
|
|
7030
|
+
* subcommand added later might take flags this file had never heard of; what
|
|
7031
|
+
* that bought was every typo running without the flag it meant:
|
|
7032
|
+
* `prune --older-then 1d --yes` pruned by no age, `create x --froce` ran
|
|
7033
|
+
* without --force. A new action gets its line in the table. The value after a
|
|
7034
|
+
* flag that takes one is skipped: it is that flag's argument, not a stray word.
|
|
7035
|
+
*
|
|
7036
|
+
* @param words The command's own arguments: action, name, then the rest.
|
|
7037
|
+
*/
|
|
7038
|
+
function unexpectedBranchArgs(words) {
|
|
7039
|
+
if (words[0]) assertKnownBranchFlags(words[0], words.slice(1));
|
|
7040
|
+
const extras = [];
|
|
7041
|
+
let seenName = false;
|
|
7042
|
+
for (let index = 1; index < words.length; index += 1) {
|
|
7043
|
+
const word = words[index];
|
|
7044
|
+
if (word.startsWith("-")) {
|
|
7045
|
+
if (TAKES_A_VALUE.has(word.split("=")[0])) index += 1;
|
|
7046
|
+
continue;
|
|
7047
|
+
}
|
|
7048
|
+
if (!seenName) {
|
|
7049
|
+
seenName = true;
|
|
7050
|
+
continue;
|
|
7051
|
+
}
|
|
7052
|
+
extras.push(word);
|
|
7053
|
+
}
|
|
7054
|
+
return extras;
|
|
7055
|
+
}
|
|
7056
|
+
/**
|
|
7057
|
+
* Branch-line flags whose next word belongs to them.
|
|
7058
|
+
*
|
|
7059
|
+
* `--from` was the only one listed, and `--database-url` is the one that bit:
|
|
7060
|
+
* `rebase db branch list --database-url postgres://…` — which the CLI itself
|
|
7061
|
+
* appends when the checkout is on a branch, so `rebase.branches` is read in the
|
|
7062
|
+
* parent where it lives — answered `✗ Unexpected argument: postgres://…`. The
|
|
7063
|
+
* `=` form carries its value in the same word and needs no skip.
|
|
7064
|
+
*/
|
|
7065
|
+
var TAKES_A_VALUE = /* @__PURE__ */ new Set([
|
|
7066
|
+
"--from",
|
|
7067
|
+
"--older-than",
|
|
7068
|
+
"--sample-limit",
|
|
7069
|
+
"--database-url"
|
|
7070
|
+
]);
|
|
7071
|
+
//#endregion
|
|
7072
|
+
//#region src/backup-argv.ts
|
|
7073
|
+
/**
|
|
7074
|
+
* @param rawArgs the driver's whole line, `["db", "backup", …]`.
|
|
7075
|
+
*/
|
|
7076
|
+
function backupActionOf(rawArgs) {
|
|
7077
|
+
return rawArgs[2] === "list" ? "list" : "create";
|
|
7078
|
+
}
|
|
7079
|
+
//#endregion
|
|
7080
|
+
//#region src/db-subcommands.ts
|
|
7081
|
+
/**
|
|
7082
|
+
* The `db` subcommands this driver dispatches, with no runtime dependencies.
|
|
7083
|
+
*
|
|
7084
|
+
* Its own module so the dispatch in `cli.ts` can name them without loading
|
|
7085
|
+
* what judges their lines (`db-command-line.ts` reaches the backup module,
|
|
7086
|
+
* which `cli.ts` imports only when a backup command runs).
|
|
7087
|
+
*/
|
|
7088
|
+
var DRIVER_DB_SUBCOMMANDS = [
|
|
7089
|
+
"push",
|
|
7090
|
+
"generate",
|
|
7091
|
+
"migrate",
|
|
7092
|
+
"branch",
|
|
7093
|
+
"backup",
|
|
7094
|
+
"restore",
|
|
7095
|
+
"backups"
|
|
7096
|
+
];
|
|
7097
|
+
/** Is `word` a `db` subcommand this driver answers? */
|
|
7098
|
+
function isDriverDbSubcommand(word) {
|
|
7099
|
+
return DRIVER_DB_SUBCOMMANDS.some((subcommand) => subcommand === word);
|
|
7100
|
+
}
|
|
7101
|
+
//#endregion
|
|
7102
|
+
//#region src/schema/migrate-baseline.ts
|
|
7103
|
+
/**
|
|
7104
|
+
* What `rebase db migrate --baseline <version>` does, given the history the
|
|
7105
|
+
* database already has.
|
|
7106
|
+
*
|
|
7107
|
+
* The flag promises to record `<version>` as applied and migrate from the next
|
|
7108
|
+
* one. Atlas's own `migrate apply --baseline` keeps that promise only on a
|
|
7109
|
+
* database with no revision history: once one row exists it ignores the flag
|
|
7110
|
+
* without a word, applies the migration anyway, and the CLI had already printed
|
|
7111
|
+
* "Recording … as already applied". That is the normal second time: a project
|
|
7112
|
+
* baselines once, then `rebase dev` adds a column at boot, `db generate` writes
|
|
7113
|
+
* the migration for it, and `db migrate` fails on `column … already exists
|
|
7114
|
+
* (42701)` however the baseline is spelled.
|
|
7115
|
+
*
|
|
7116
|
+
* So the history is read first. With none, Atlas's baseline does the work.
|
|
7117
|
+
* Behind `<version>`, `atlas migrate set` marks every migration up to it as
|
|
7118
|
+
* applied, which is what a baseline means. At or past it, there is nothing to
|
|
7119
|
+
* record, and the command says so instead of claiming it recorded something.
|
|
7120
|
+
*/
|
|
7121
|
+
/**
|
|
7122
|
+
* Atlas's version order for the versions Rebase writes: numeric timestamps,
|
|
7123
|
+
* compared as numbers so a shorter one is not read as later; anything else as
|
|
7124
|
+
* text.
|
|
7125
|
+
*/
|
|
7126
|
+
function compareMigrationVersions(a, b) {
|
|
7127
|
+
if (/^\d+$/.test(a) && /^\d+$/.test(b)) {
|
|
7128
|
+
const x = BigInt(a);
|
|
7129
|
+
const y = BigInt(b);
|
|
7130
|
+
return x === y ? 0 : x < y ? -1 : 1;
|
|
7131
|
+
}
|
|
7132
|
+
return a.localeCompare(b);
|
|
7133
|
+
}
|
|
7134
|
+
/**
|
|
7135
|
+
* The version Atlas records for a migration file: its numeric prefix
|
|
7136
|
+
* (`20260906101530_init.sql` → `20260906101530`), or the name without `.sql`.
|
|
7137
|
+
*/
|
|
7138
|
+
function migrationVersionOf(file) {
|
|
7139
|
+
return /^(\d+)/.exec(file)?.[1] ?? file.replace(/\.sql$/, "");
|
|
7140
|
+
}
|
|
7141
|
+
/** How many versions a refused baseline names, newest last. */
|
|
7142
|
+
var VERSIONS_NAMED = 5;
|
|
7143
|
+
/**
|
|
7144
|
+
* Why `--baseline <version>` cannot run, or `null` when it names a migration
|
|
7145
|
+
* file in `drizzle/migrations`.
|
|
7146
|
+
*
|
|
7147
|
+
* Asked before anything else, the database included. A version no file has
|
|
7148
|
+
* was refused only by Atlas, after the command had said "Marking every
|
|
7149
|
+
* migration up to abc as applied…"; and one older than every file read
|
|
7150
|
+
* "Nothing to record" and migrated on, though it named nothing to baseline.
|
|
7151
|
+
*
|
|
7152
|
+
* @param files the migration files, sorted oldest first.
|
|
7153
|
+
* @returns the refusal, its first line the error and the rest the detail.
|
|
7154
|
+
*/
|
|
7155
|
+
function baselineVersionProblem(version, files) {
|
|
7156
|
+
const versions = files.map(migrationVersionOf);
|
|
7157
|
+
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.`;
|
|
7158
|
+
if (versions.includes(version)) return null;
|
|
7159
|
+
const named = versions.slice(-5).join(", ");
|
|
7160
|
+
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.`;
|
|
7161
|
+
}
|
|
7162
|
+
/** How to keep `--baseline <version>`'s promise, given the newest recorded version. */
|
|
7163
|
+
function planBaseline(version, latestRecorded) {
|
|
7164
|
+
if (latestRecorded === null) return { kind: "atlas-baseline" };
|
|
7165
|
+
if (compareMigrationVersions(version, latestRecorded) > 0) return {
|
|
7166
|
+
kind: "set",
|
|
7167
|
+
latest: latestRecorded
|
|
7168
|
+
};
|
|
7169
|
+
return {
|
|
7170
|
+
kind: "already-recorded",
|
|
7171
|
+
latest: latestRecorded
|
|
7172
|
+
};
|
|
7173
|
+
}
|
|
7174
|
+
/** What the command says before it migrates, for each plan. */
|
|
7175
|
+
function describeBaselinePlan(version, plan) {
|
|
7176
|
+
switch (plan.kind) {
|
|
7177
|
+
case "atlas-baseline": return `Recording ${version} as already applied, then migrating from the next one.`;
|
|
7178
|
+
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.`;
|
|
7179
|
+
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.`;
|
|
7180
|
+
}
|
|
7181
|
+
}
|
|
7182
|
+
/**
|
|
7183
|
+
* The newest version Atlas has recorded on this database, or `null` when it
|
|
7184
|
+
* has recorded none (no revisions table yet included).
|
|
7185
|
+
*
|
|
7186
|
+
* The table is `rebase.atlas_schema_revisions`: the CLI passes
|
|
7187
|
+
* `--revisions-schema rebase` to every `migrate apply`.
|
|
7188
|
+
*/
|
|
7189
|
+
async function latestRecordedMigration(databaseUrl) {
|
|
7190
|
+
return withAppClient(databaseUrl, async (client) => {
|
|
7191
|
+
if (!(await client.query("SELECT to_regclass('rebase.atlas_schema_revisions') IS NOT NULL AS present")).rows[0]?.present) return null;
|
|
7192
|
+
const versions = (await client.query("SELECT version FROM rebase.atlas_schema_revisions WHERE version NOT LIKE '.%'")).rows.map((row) => row.version);
|
|
7193
|
+
if (versions.length === 0) return null;
|
|
7194
|
+
return versions.reduce((latest, version) => compareMigrationVersions(version, latest) > 0 ? version : latest);
|
|
7195
|
+
});
|
|
7196
|
+
}
|
|
6520
7197
|
//#endregion
|
|
6521
|
-
export {
|
|
7198
|
+
export { rlsRuntimeIsCurrent as C, dropLegacyAuthSchema as S, execa as T, parseCommandFlags as _, migrationVersionOf as a, RLS_BOOTSTRAP_SQL as b, isDriverDbSubcommand as c, resolveBranchSwitches as d, unexpectedBranchArgs as f, collectionsPathIn as g, closestWord as h, latestRecordedMigration as i, backupActionOf as l, assertKnownFlags as m, compareMigrationVersions as n, planBaseline as o, RELAYED_FLAGS as p, describeBaselinePlan as r, DRIVER_DB_SUBCOMMANDS as s, baselineVersionProblem as t, assertKnownBranchFlags as u, parseDriverLine as v, rls_bootstrap_sql_exports as w, RLS_BOOTSTRAP_STATEMENTS as x, resolveDriverSwitches as y };
|
|
6522
7199
|
|
|
6523
|
-
//# sourceMappingURL=
|
|
7200
|
+
//# sourceMappingURL=migrate-baseline-B-2Ef4Mv.js.map
|