@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
|
@@ -1,18 +1,16 @@
|
|
|
1
1
|
import { createRequire as __createRequire } from "module";
|
|
2
2
|
__createRequire(import.meta.url);
|
|
3
|
-
import {
|
|
4
|
-
import { a as outWarn, i as outError, r as out, t as chalk } from "./source-
|
|
5
|
-
import { A as parseDbNameFromUrl,
|
|
6
|
-
import { t as
|
|
7
|
-
import {
|
|
3
|
+
import { $ as redactConnectionPasswords, t as checkDatabaseConnectivity } from "./cli-errors-mx4sSjB8.js";
|
|
4
|
+
import { a as outWarn, i as outError, r as out, t as chalk } from "./source-DB6xJv3p.js";
|
|
5
|
+
import { A as parseBackupDestination, F as resolveConnectionString, M as parseDbNameFromUrl, O as globalsFileForDump, R as withDatabaseName, c as findRestoreConflicts, d as preflight, g as validateDump, h as uploadBackup, i as createDump, m as restoreDump, n as applyGlobals, o as discardPartialDump, p as readDumpObjects, s as ensureDatabaseExists, t as BackupToolError, u as listBackups } from "./backup-service-D-UpT7DH.js";
|
|
6
|
+
import { t as promptConfirm } from "./cli-prompt-egtNKkZl.js";
|
|
7
|
+
import { a as parseCommandFlags, r as closestWord } from "./cli-flags-DUozBFyK.js";
|
|
8
8
|
import path from "path";
|
|
9
9
|
import fs from "fs";
|
|
10
10
|
import os from "os";
|
|
11
11
|
import { Readable } from "stream";
|
|
12
12
|
import { pipeline } from "stream/promises";
|
|
13
13
|
//#region src/backup/backup-cli.ts
|
|
14
|
-
var import_arg = /* @__PURE__ */ __toESM(require_arg(), 1);
|
|
15
|
-
var import___vite_browser_external = /* @__PURE__ */ __toESM(require___vite_browser_external(), 1);
|
|
16
14
|
function formatBytes(bytes) {
|
|
17
15
|
if (bytes < 1024) return `${bytes} B`;
|
|
18
16
|
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
|
@@ -52,6 +50,53 @@ async function resolveStorageForDestination(dest, env) {
|
|
|
52
50
|
throw new BackupToolError(error instanceof Error ? error.message : String(error), "Set the same S3_* variables your backend uses for storage.");
|
|
53
51
|
}
|
|
54
52
|
}
|
|
53
|
+
/**
|
|
54
|
+
* The directory `rebase` was run from, which `rebase` puts here for the driver.
|
|
55
|
+
*
|
|
56
|
+
* The driver runs in the project's `backend/`, so a relative path resolved
|
|
57
|
+
* against its own working directory named a different place from the one the
|
|
58
|
+
* user typed it in: `rebase db backup --out ./backups` wrote to `<root>/backups`
|
|
59
|
+
* (the CLI resolves explicit paths itself) while `rebase db backups list`
|
|
60
|
+
* looked in `<root>/backend/backups`. Spelled the same in
|
|
61
|
+
* `packages/cli/src/commands/db.ts`.
|
|
62
|
+
*/
|
|
63
|
+
var INVOCATION_CWD_ENV = "REBASE_INVOCATION_CWD";
|
|
64
|
+
/** Where a relative path the user typed is relative to: where they ran `rebase`. */
|
|
65
|
+
function invocationDir(env = process.env) {
|
|
66
|
+
const dir = env[INVOCATION_CWD_ENV]?.trim();
|
|
67
|
+
return dir && path.isAbsolute(dir) ? dir : process.cwd();
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Where `db backup` writes and `db backups list` reads, by one rule.
|
|
71
|
+
*
|
|
72
|
+
* - `--out` is relative to where `rebase` was run, like any path typed on a
|
|
73
|
+
* command line.
|
|
74
|
+
* - `BACKUP_DESTINATION` is relative to the backend directory, because the
|
|
75
|
+
* server reads the same variable there: the Backups panel and the scheduled
|
|
76
|
+
* backup have to find what the CLI wrote.
|
|
77
|
+
* - With neither, `./backups` where `rebase` was run, as the help says.
|
|
78
|
+
*
|
|
79
|
+
* An `s3://` or `gs://` destination is returned as given.
|
|
80
|
+
*/
|
|
81
|
+
function resolveBackupLocation(opts) {
|
|
82
|
+
const flag = opts.flag?.trim();
|
|
83
|
+
if (flag) return isRemoteDestination(flag) ? flag : path.resolve(opts.invocationDir, flag);
|
|
84
|
+
const configured = opts.configured?.trim();
|
|
85
|
+
if (configured) return isRemoteDestination(configured) ? configured : path.resolve(opts.backendDir, configured);
|
|
86
|
+
return path.join(opts.invocationDir, "backups");
|
|
87
|
+
}
|
|
88
|
+
function isRemoteDestination(value) {
|
|
89
|
+
return parseBackupDestination(value).kind !== "local";
|
|
90
|
+
}
|
|
91
|
+
/** {@link resolveBackupLocation} for this process. */
|
|
92
|
+
function backupLocation(flag) {
|
|
93
|
+
return resolveBackupLocation({
|
|
94
|
+
flag,
|
|
95
|
+
configured: process.env.BACKUP_DESTINATION,
|
|
96
|
+
invocationDir: invocationDir(),
|
|
97
|
+
backendDir: process.cwd()
|
|
98
|
+
});
|
|
99
|
+
}
|
|
55
100
|
function requireConnection() {
|
|
56
101
|
const conn = resolveConnectionString(process.env);
|
|
57
102
|
if (!conn) {
|
|
@@ -60,45 +105,43 @@ function requireConnection() {
|
|
|
60
105
|
}
|
|
61
106
|
return conn;
|
|
62
107
|
}
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
108
|
+
var BACKUP_FLAGS = {
|
|
109
|
+
"--out": String,
|
|
110
|
+
"--output": "--out",
|
|
111
|
+
"--exclude-schema": [String],
|
|
112
|
+
"--no-owner": Boolean,
|
|
113
|
+
"--enable-row-security": Boolean,
|
|
114
|
+
"--row-security-role": String,
|
|
115
|
+
"-o": "--out"
|
|
116
|
+
};
|
|
117
|
+
/**
|
|
118
|
+
* Parse a `rebase db backup …` line. An undeclared flag is refused (see
|
|
119
|
+
* `parseCommandFlags`), and so is a positional: `rebase db backup ./backups`
|
|
120
|
+
* means `--out ./backups`, and taken as nothing it wrote to the default
|
|
121
|
+
* directory.
|
|
122
|
+
*/
|
|
123
|
+
function parseBackupLine(rawArgs) {
|
|
124
|
+
const args = parseCommandFlags(BACKUP_FLAGS, rawArgs.slice(2), "db backup");
|
|
125
|
+
const [stray] = args._;
|
|
126
|
+
if (stray !== void 0) throw new Error(`\`rebase db backup\` takes no argument "${stray}". Nothing was run. To choose where the backup goes, pass --out ${stray}.`);
|
|
127
|
+
return args;
|
|
75
128
|
}
|
|
76
129
|
async function backupCommand(rawArgs) {
|
|
77
|
-
|
|
78
|
-
"--out": String,
|
|
79
|
-
"--output": "--out",
|
|
80
|
-
"--exclude-schema": [String],
|
|
81
|
-
"--no-owner": Boolean,
|
|
82
|
-
"--enable-row-security": Boolean,
|
|
83
|
-
"--row-security-role": String,
|
|
84
|
-
"-o": "--out"
|
|
85
|
-
}, {
|
|
86
|
-
argv: rawArgs.slice(2),
|
|
87
|
-
permissive: true
|
|
88
|
-
});
|
|
89
|
-
if (args._.includes("--help") || rawArgs.includes("--help")) {
|
|
130
|
+
if (rawArgs.includes("--help")) {
|
|
90
131
|
printBackupHelp();
|
|
91
132
|
return;
|
|
92
133
|
}
|
|
134
|
+
const args = parseBackupLine(rawArgs);
|
|
93
135
|
const connectionString = requireConnection();
|
|
94
136
|
const dbName = parseDbNameFromUrl(connectionString) ?? "database";
|
|
95
|
-
const out$1 = args["--out"]
|
|
137
|
+
const out$1 = backupLocation(args["--out"]);
|
|
96
138
|
const dest = parseBackupDestination(out$1);
|
|
97
139
|
out("");
|
|
98
140
|
out(chalk.bold(" 💾 Rebase DB Backup"));
|
|
99
141
|
out(chalk.gray(` Database: ${dbName}`));
|
|
100
142
|
out(chalk.gray(` Destination: ${out$1}`));
|
|
101
143
|
out("");
|
|
144
|
+
await checkDatabaseConnectivity(connectionString);
|
|
102
145
|
const pf = await preflight("pg_dump", connectionString);
|
|
103
146
|
if (!pf.compatible) {
|
|
104
147
|
outError(chalk.red(` ✗ ${pf.reason}`));
|
|
@@ -175,17 +218,64 @@ var RESTORE_FLAGS = {
|
|
|
175
218
|
"--yes": Boolean,
|
|
176
219
|
"-y": "--yes"
|
|
177
220
|
};
|
|
178
|
-
/**
|
|
221
|
+
/**
|
|
222
|
+
* Parse a `rebase db restore …` line; the one positional is the backup.
|
|
223
|
+
*
|
|
224
|
+
* Strict, because this is the command where a typo costs most: parsed
|
|
225
|
+
* permissively, `--targt-db x` became a second positional, the target fell
|
|
226
|
+
* back to DATABASE_URL's own database, and the restore ran against the live
|
|
227
|
+
* one.
|
|
228
|
+
*/
|
|
179
229
|
function parseRestoreLine(rawArgs) {
|
|
180
|
-
|
|
230
|
+
const args = parseCommandFlags(RESTORE_FLAGS, rawArgs.slice(2), "db restore");
|
|
231
|
+
if (args._.length > 1) throw new Error(`\`rebase db restore\` restores one backup, and was given ${args._.length}: ${args._.map((word) => `"${word}"`).join(", ")}. Nothing was run.`);
|
|
232
|
+
return args;
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* The restore's one question, asked before anything can change.
|
|
236
|
+
*
|
|
237
|
+
* `--yes` skips it. On a terminal it asks for a typed 'yes'. With no terminal
|
|
238
|
+
* nobody can answer, and the refusal says to pass `--yes`, as `db reset`,
|
|
239
|
+
* `db pull` and `db branch prune` do: "Aborted. No changes were made." alone
|
|
240
|
+
* read as though someone had answered no.
|
|
241
|
+
*
|
|
242
|
+
* @returns whether to go on. When not, it has said why.
|
|
243
|
+
*/
|
|
244
|
+
async function confirmRestore(opts) {
|
|
245
|
+
if (opts.yes) return true;
|
|
246
|
+
outWarn(chalk.yellow(` ⚠️ This will restore into "${opts.target}" and may overwrite existing data.`));
|
|
247
|
+
if (!opts.interactive) {
|
|
248
|
+
outError(chalk.red(" ✗ Nothing was restored: there is no terminal to confirm on."));
|
|
249
|
+
outError(chalk.gray(` Re-run with --yes to restore into "${opts.target}".`));
|
|
250
|
+
return false;
|
|
251
|
+
}
|
|
252
|
+
const confirmed = await opts.ask(chalk.yellow(" Type 'yes' to continue: "));
|
|
253
|
+
if (!confirmed) out(chalk.gray(" Aborted. No changes were made."));
|
|
254
|
+
return confirmed;
|
|
255
|
+
}
|
|
256
|
+
/** How many of the conflicting objects a refusal names before counting the rest. */
|
|
257
|
+
var CONFLICTS_NAMED = 5;
|
|
258
|
+
/**
|
|
259
|
+
* What `db restore` says when the target already holds what the backup
|
|
260
|
+
* creates, and the two ways round it: `--clean` replaces them with the
|
|
261
|
+
* backup's copy, a new database restores beside them.
|
|
262
|
+
*/
|
|
263
|
+
function describeRestoreConflict(target, backupArg, conflicts) {
|
|
264
|
+
return {
|
|
265
|
+
message: `"${target}" already holds ${conflicts.slice(0, CONFLICTS_NAMED).join(", ")}${conflicts.length > CONFLICTS_NAMED ? `, and ${conflicts.length - CONFLICTS_NAMED} more` : ""}, which this backup creates. Without --clean, pg_restore stops at the first of them. Nothing was restored or changed.`,
|
|
266
|
+
hint: [`To replace them with the backup's copy: rebase db restore ${backupArg} --clean`, `To restore beside them, in a new database: rebase db restore ${backupArg} --create-db --target-db <new name>`].join("\n ")
|
|
267
|
+
};
|
|
181
268
|
}
|
|
182
269
|
async function restoreCommand(rawArgs) {
|
|
270
|
+
if (rawArgs.includes("--help")) {
|
|
271
|
+
printRestoreHelp();
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
183
274
|
const args = parseRestoreLine(rawArgs);
|
|
184
275
|
const backupArg = args._[0];
|
|
185
|
-
if (!backupArg
|
|
276
|
+
if (!backupArg) {
|
|
186
277
|
printRestoreHelp();
|
|
187
|
-
|
|
188
|
-
return;
|
|
278
|
+
process.exit(1);
|
|
189
279
|
}
|
|
190
280
|
const baseConnection = requireConnection();
|
|
191
281
|
const targetDb = args["--target-db"] ?? parseDbNameFromUrl(baseConnection) ?? void 0;
|
|
@@ -195,6 +285,7 @@ async function restoreCommand(rawArgs) {
|
|
|
195
285
|
out(chalk.gray(` Source: ${backupArg}`));
|
|
196
286
|
out(chalk.gray(` Target: ${targetDb ?? "(from DATABASE_URL)"}`));
|
|
197
287
|
out("");
|
|
288
|
+
await checkDatabaseConnectivity(baseConnection);
|
|
198
289
|
let localFile;
|
|
199
290
|
let globalsSql = null;
|
|
200
291
|
let cleanupTemp = false;
|
|
@@ -213,57 +304,64 @@ async function restoreCommand(rawArgs) {
|
|
|
213
304
|
const globalsObj = await storage.getObject(globalsFileForDump(key), bucket);
|
|
214
305
|
if (globalsObj) globalsSql = Buffer.from(await globalsObj.arrayBuffer()).toString("utf-8");
|
|
215
306
|
} else {
|
|
216
|
-
localFile = path.resolve(backupArg);
|
|
217
|
-
if (!fs.existsSync(localFile)) {
|
|
218
|
-
outError(chalk.red(` ✗ Backup file not found: ${localFile}`));
|
|
219
|
-
process.exit(1);
|
|
220
|
-
}
|
|
307
|
+
localFile = path.resolve(invocationDir(), backupArg);
|
|
308
|
+
if (!fs.existsSync(localFile)) throw new BackupToolError(`Backup file not found: ${localFile}`);
|
|
221
309
|
const globalsPath = globalsFileForDump(localFile);
|
|
222
310
|
if (fs.existsSync(globalsPath)) globalsSql = fs.readFileSync(globalsPath, "utf-8");
|
|
223
311
|
}
|
|
224
312
|
const pf = await preflight("pg_restore", baseConnection);
|
|
225
|
-
if (!pf.compatible)
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
}
|
|
233
|
-
if (!args["--yes"]) {
|
|
234
|
-
outWarn(chalk.yellow(` ⚠️ This will restore into "${targetDb ?? "the target database"}" and may overwrite existing data.`));
|
|
235
|
-
if (!await promptConfirm(chalk.yellow(" Type 'yes' to continue: "))) {
|
|
236
|
-
out(chalk.gray(" Aborted. No changes were made."));
|
|
237
|
-
process.exit(1);
|
|
313
|
+
if (!pf.compatible) throw new BackupToolError(pf.reason ?? "pg_restore cannot restore into this server.");
|
|
314
|
+
if (args["--create-db"] && !targetDb) throw new BackupToolError("--create-db requires a resolvable target database name (use --target-db).");
|
|
315
|
+
if (!args["--clean"]) {
|
|
316
|
+
const conflicts = await findRestoreConflicts(targetConnection, await readDumpObjects(localFile));
|
|
317
|
+
if (conflicts.length > 0) {
|
|
318
|
+
const { message, hint } = describeRestoreConflict(targetDb ?? "the target database", backupArg, conflicts);
|
|
319
|
+
throw new BackupToolError(message, hint);
|
|
238
320
|
}
|
|
239
321
|
}
|
|
322
|
+
if (!await confirmRestore({
|
|
323
|
+
target: targetDb ?? "the target database",
|
|
324
|
+
yes: args["--yes"] === true,
|
|
325
|
+
interactive: process.stdin.isTTY === true,
|
|
326
|
+
ask: promptConfirm
|
|
327
|
+
})) {
|
|
328
|
+
process.exitCode = 1;
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
240
331
|
if (args["--create-db"]) {
|
|
241
332
|
const created = await ensureDatabaseExists(baseConnection, targetDb);
|
|
242
333
|
out(chalk.gray(created ? ` ✓ Created database "${targetDb}".` : ` • Database "${targetDb}" already exists.`));
|
|
243
334
|
}
|
|
335
|
+
let rolesApplied = 0;
|
|
244
336
|
if (globalsSql) {
|
|
245
337
|
out(chalk.gray(" Recreating cluster roles from the backup's roles sidecar…"));
|
|
246
338
|
const { applied, skipped } = await applyGlobals(targetConnection, globalsSql, (m) => out(chalk.gray(m)));
|
|
339
|
+
rolesApplied = applied;
|
|
247
340
|
out(chalk.gray(` ✓ Roles: ${applied} applied, ${skipped} skipped (already present or not permitted).`));
|
|
248
341
|
} else {
|
|
249
342
|
outWarn(chalk.yellow(" ⚠️ No roles sidecar (.globals.sql) accompanies this backup."));
|
|
250
343
|
outWarn(chalk.yellow(" If the dump grants to roles that don't exist (e.g. rebase_user), the restore"));
|
|
251
344
|
outWarn(chalk.yellow(" will fail — recreate those roles first, or use a backup that includes its globals."));
|
|
252
345
|
}
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
346
|
+
try {
|
|
347
|
+
await restoreDump({
|
|
348
|
+
connectionString: targetConnection,
|
|
349
|
+
inputFile: localFile,
|
|
350
|
+
clean: args["--clean"],
|
|
351
|
+
noOwner: args["--no-owner"],
|
|
352
|
+
exitOnError: !args["--continue-on-error"],
|
|
353
|
+
inheritStdio: true
|
|
354
|
+
});
|
|
355
|
+
} catch (err) {
|
|
356
|
+
if (err instanceof BackupToolError) throw err;
|
|
357
|
+
throw new BackupToolError(`pg_restore stopped on the error above. What it restored before that stays in "${targetDb ?? "the target database"}".`, rolesApplied > 0 ? `The ${rolesApplied} cluster role(s) recreated from the backup's sidecar before it stay too.` : void 0);
|
|
358
|
+
}
|
|
261
359
|
out("");
|
|
262
360
|
out(chalk.green(` ✓ Restore completed into "${targetDb ?? "the target database"}".`));
|
|
263
361
|
out("");
|
|
264
362
|
} catch (err) {
|
|
265
363
|
reportError(err);
|
|
266
|
-
process.
|
|
364
|
+
process.exitCode = 1;
|
|
267
365
|
} finally {
|
|
268
366
|
if (cleanupTemp && typeof localFile === "string" && fs.existsSync(localFile)) fs.rmSync(path.dirname(localFile), {
|
|
269
367
|
recursive: true,
|
|
@@ -271,6 +369,12 @@ async function restoreCommand(rawArgs) {
|
|
|
271
369
|
});
|
|
272
370
|
}
|
|
273
371
|
}
|
|
372
|
+
var BACKUPS_ACTIONS = ["list"];
|
|
373
|
+
/** What `db backups <action>` says of an action it does not have: the sentence every `db` command says. */
|
|
374
|
+
function unknownBackupsAction(action) {
|
|
375
|
+
const near = closestWord(action, BACKUPS_ACTIONS);
|
|
376
|
+
return near ? `Unknown db backups command "${action}" — did you mean \`${near}\`? Run \`rebase db backups --help\` for the rest.` : `Unknown db backups command "${action}". Run \`rebase db backups --help\` for the ones there are.`;
|
|
377
|
+
}
|
|
274
378
|
async function backupsCommand(rawArgs) {
|
|
275
379
|
const action = rawArgs[2];
|
|
276
380
|
if (!action || action === "--help") {
|
|
@@ -278,23 +382,23 @@ async function backupsCommand(rawArgs) {
|
|
|
278
382
|
return;
|
|
279
383
|
}
|
|
280
384
|
if (action !== "list") {
|
|
281
|
-
outError(chalk.red(
|
|
385
|
+
outError(chalk.red(`✗ ${unknownBackupsAction(action)}`));
|
|
282
386
|
process.exit(1);
|
|
283
387
|
}
|
|
284
|
-
const
|
|
388
|
+
const args = parseCommandFlags({
|
|
285
389
|
"--out": String,
|
|
286
390
|
"--output": "--out",
|
|
287
391
|
"-o": "--out"
|
|
288
|
-
},
|
|
289
|
-
|
|
290
|
-
permissive: true
|
|
291
|
-
})["--out"] || process.env.BACKUP_DESTINATION || path.join(process.cwd(), "backups");
|
|
392
|
+
}, rawArgs.slice(3), "db backups list");
|
|
393
|
+
const out$2 = backupLocation(args["--out"]);
|
|
292
394
|
const dest = parseBackupDestination(out$2);
|
|
293
395
|
try {
|
|
294
396
|
const backups = await listBackups(dest, await resolveStorageForDestination(dest, process.env) ?? void 0);
|
|
295
397
|
out("");
|
|
296
|
-
if (backups.length === 0)
|
|
297
|
-
|
|
398
|
+
if (backups.length === 0) {
|
|
399
|
+
out(chalk.gray(` No backups found at ${out$2}.`));
|
|
400
|
+
await mentionBackendBackups(out$2, args["--out"]);
|
|
401
|
+
} else {
|
|
298
402
|
out(chalk.bold(` 💾 ${backups.length} backup(s) at ${out$2}:`));
|
|
299
403
|
out("");
|
|
300
404
|
for (const b of backups) {
|
|
@@ -318,6 +422,26 @@ async function backupsCommand(rawArgs) {
|
|
|
318
422
|
}
|
|
319
423
|
}
|
|
320
424
|
/**
|
|
425
|
+
* Point at `<backend>/backups` when the default listing is empty and that
|
|
426
|
+
* directory holds dumps.
|
|
427
|
+
*
|
|
428
|
+
* Earlier releases resolved the default there, so after an upgrade the
|
|
429
|
+
* backups a project already has are one directory away from the listing.
|
|
430
|
+
*/
|
|
431
|
+
async function mentionBackendBackups(listed, flag) {
|
|
432
|
+
if (flag || process.env.BACKUP_DESTINATION?.trim()) return;
|
|
433
|
+
const backendBackups = path.join(process.cwd(), "backups");
|
|
434
|
+
if (path.resolve(listed) === backendBackups) return;
|
|
435
|
+
const found = await listBackups({
|
|
436
|
+
kind: "local",
|
|
437
|
+
path: backendBackups
|
|
438
|
+
}).catch(() => []);
|
|
439
|
+
if (found.length === 0) return;
|
|
440
|
+
out(chalk.gray(` ${found.length} backup(s) are in ${backendBackups}, where earlier releases of`));
|
|
441
|
+
out(chalk.gray(` \`rebase db backup\` wrote by default. List them with:`));
|
|
442
|
+
out(chalk.gray(` rebase db backups list --out ${path.relative(invocationDir(), backendBackups) || "."}`));
|
|
443
|
+
}
|
|
444
|
+
/**
|
|
321
445
|
* Verify a freshly written dump; abort the command if it looks corrupt.
|
|
322
446
|
*
|
|
323
447
|
* Discards the artifact on the way out. Refusing to *report* success was never
|
|
@@ -335,11 +459,20 @@ async function assertDumpValid(localFile) {
|
|
|
335
459
|
throw new BackupToolError(`The backup failed validation and was discarded: ${check.reason}`, "The dump was corrupt or truncated, so nothing was kept. Investigate before relying on this destination.");
|
|
336
460
|
}
|
|
337
461
|
}
|
|
462
|
+
/**
|
|
463
|
+
* Say why a backup command failed, with no password in it.
|
|
464
|
+
*
|
|
465
|
+
* What reaches here is often a client tool's failure, and execa's message
|
|
466
|
+
* repeats the tool's command line. The tools get the password through
|
|
467
|
+
* `PGPASSWORD` now, so it is not on that line, but a connection string can
|
|
468
|
+
* still arrive inside a message from elsewhere; this is the last place to
|
|
469
|
+
* stop it reaching a terminal or a CI log.
|
|
470
|
+
*/
|
|
338
471
|
function reportError(err) {
|
|
339
472
|
if (err instanceof BackupToolError) {
|
|
340
|
-
outError(chalk.red(` ✗ ${err.message}`));
|
|
341
|
-
if (err.hint) outError(chalk.gray(` ${err.hint}`));
|
|
342
|
-
} else outError(chalk.red(` ✗ ${err instanceof Error ? err.message : String(err)}`));
|
|
473
|
+
outError(chalk.red(` ✗ ${redactConnectionPasswords(err.message)}`));
|
|
474
|
+
if (err.hint) outError(chalk.gray(` ${redactConnectionPasswords(err.hint)}`));
|
|
475
|
+
} else outError(chalk.red(` ✗ ${redactConnectionPasswords(err instanceof Error ? err.message : String(err))}`));
|
|
343
476
|
}
|
|
344
477
|
function printBackupHelp() {
|
|
345
478
|
out(`
|
|
@@ -396,8 +529,11 @@ ${chalk.green.bold("Options")}
|
|
|
396
529
|
|
|
397
530
|
${chalk.red.bold("Warning")}
|
|
398
531
|
Restore is destructive and never runs automatically. Without --yes it
|
|
399
|
-
|
|
400
|
-
|
|
532
|
+
asks for a typed 'yes' on a terminal, and with no terminal to ask on it
|
|
533
|
+
refuses. Prefer --create-db/--target-db to restore into a fresh database
|
|
534
|
+
rather than overwriting a live one. Into a database that already holds
|
|
535
|
+
what the backup creates, it needs --clean, and refuses before changing
|
|
536
|
+
anything without it.
|
|
401
537
|
|
|
402
538
|
By default the restore aborts on the first error (--exit-on-error) so a
|
|
403
539
|
skipped GRANT never silently leaves RLS un-enforced. Roles are recreated
|
|
@@ -417,4 +553,4 @@ ${chalk.green.bold("Usage")}
|
|
|
417
553
|
//#endregion
|
|
418
554
|
export { backupCommand, backupsCommand, restoreCommand };
|
|
419
555
|
|
|
420
|
-
//# sourceMappingURL=backup-cli-
|
|
556
|
+
//# sourceMappingURL=backup-cli-HCEkPcQj.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"backup-cli-HCEkPcQj.js","names":[],"sources":["../src/backup/backup-cli.ts"],"sourcesContent":["/**\n * CLI handlers for `rebase db backup`, `rebase db restore`, and\n * `rebase db backups list`. Kept out of the main `cli.ts` dispatcher so the\n * backup surface stays self-contained.\n */\nimport type arg from \"arg\";\nimport path from \"path\";\nimport fs from \"fs\";\nimport os from \"os\";\nimport { Readable } from \"stream\";\nimport { pipeline } from \"stream/promises\";\nimport chalk from \"chalk\";\n// Aliased: `out` is already a local in two of the commands below (the `--out`\n// backup destination).\nimport { out as print, outWarn, outError } from \"../cli-output\";\nimport { closestWord, parseCommandFlags } from \"../cli-flags\";\nimport { redactConnectionPasswords } from \"../utils/connection-string\";\nimport { promptConfirm } from \"../cli-prompt\";\nimport { checkDatabaseConnectivity } from \"../cli-errors\";\nimport type { StorageController } from \"@rebasepro/server\";\nimport {\n BackupDestination,\n globalsFileForDump,\n parseBackupDestination,\n parseDbNameFromUrl,\n resolveConnectionString,\n withDatabaseName\n} from \"./pg-tools\";\nimport {\n applyGlobals,\n BackupToolError,\n createDump,\n discardPartialDump,\n ensureDatabaseExists,\n findRestoreConflicts,\n listBackups,\n preflight,\n readDumpObjects,\n restoreDump,\n uploadBackup,\n validateDump\n} from \"./backup-service\";\n\nfunction formatBytes(bytes: number): string {\n if (bytes < 1024) return `${bytes} B`;\n if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;\n if (bytes < 1024 * 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;\n return `${(bytes / (1024 * 1024 * 1024)).toFixed(2)} GB`;\n}\n\n/** A web stream's chunks, for piping into a file. */\nasync function* webChunks(stream: ReadableStream<Uint8Array>): AsyncGenerator<Uint8Array> {\n const reader = stream.getReader();\n try {\n for (;;) {\n const { done, value } = await reader.read();\n if (done) return;\n yield value;\n }\n } finally {\n reader.releaseLock();\n }\n}\n\n/**\n * The controller for an object-storage destination, or `null` for a local one.\n *\n * Built by `backupStorageFromEnv` from `@rebasepro/server` — the function the\n * Backups panel's routes use — so the CLI and the panel read a bucket the same\n * way. This file had its own copy, and the two had already drifted once\n * (`S3_FORCE_PATH_STYLE` read as `=== \"true\"`, so a MinIO backup addressed the\n * bucket as a host).\n *\n * Exported for its test.\n */\nexport async function resolveStorageForDestination(\n dest: BackupDestination,\n env: Record<string, string | undefined>\n): Promise<StorageController | null> {\n if (dest.kind === \"local\") return null;\n const { backupStorageFromEnv } = await import(\"@rebasepro/server\");\n try {\n return backupStorageFromEnv(dest, env);\n } catch (error) {\n throw new BackupToolError(\n error instanceof Error ? error.message : String(error),\n \"Set the same S3_* variables your backend uses for storage.\"\n );\n }\n}\n\n/**\n * The directory `rebase` was run from, which `rebase` puts here for the driver.\n *\n * The driver runs in the project's `backend/`, so a relative path resolved\n * against its own working directory named a different place from the one the\n * user typed it in: `rebase db backup --out ./backups` wrote to `<root>/backups`\n * (the CLI resolves explicit paths itself) while `rebase db backups list`\n * looked in `<root>/backend/backups`. Spelled the same in\n * `packages/cli/src/commands/db.ts`.\n */\nexport const INVOCATION_CWD_ENV = \"REBASE_INVOCATION_CWD\";\n\n/** Where a relative path the user typed is relative to: where they ran `rebase`. */\nexport function invocationDir(env: Readonly<Record<string, string | undefined>> = process.env): string {\n const dir = env[INVOCATION_CWD_ENV]?.trim();\n return dir && path.isAbsolute(dir) ? dir : process.cwd();\n}\n\n/**\n * Where `db backup` writes and `db backups list` reads, by one rule.\n *\n * - `--out` is relative to where `rebase` was run, like any path typed on a\n * command line.\n * - `BACKUP_DESTINATION` is relative to the backend directory, because the\n * server reads the same variable there: the Backups panel and the scheduled\n * backup have to find what the CLI wrote.\n * - With neither, `./backups` where `rebase` was run, as the help says.\n *\n * An `s3://` or `gs://` destination is returned as given.\n */\nexport function resolveBackupLocation(opts: {\n flag?: string;\n configured?: string;\n invocationDir: string;\n backendDir: string;\n}): string {\n const flag = opts.flag?.trim();\n if (flag) return isRemoteDestination(flag) ? flag : path.resolve(opts.invocationDir, flag);\n const configured = opts.configured?.trim();\n if (configured) return isRemoteDestination(configured) ? configured : path.resolve(opts.backendDir, configured);\n return path.join(opts.invocationDir, \"backups\");\n}\n\nfunction isRemoteDestination(value: string): boolean {\n return parseBackupDestination(value).kind !== \"local\";\n}\n\n/** {@link resolveBackupLocation} for this process. */\nfunction backupLocation(flag: string | undefined): string {\n return resolveBackupLocation({\n flag,\n configured: process.env.BACKUP_DESTINATION,\n invocationDir: invocationDir(),\n backendDir: process.cwd()\n });\n}\n\nfunction requireConnection(): string {\n const conn = resolveConnectionString(process.env);\n if (!conn) {\n outError(chalk.red(\"✗ DATABASE_URL is not set. Make sure your .env file is configured.\"));\n process.exit(1);\n }\n return conn;\n}\n\n// ─────────────────────────────────────────────────────────────────────────\n// rebase db backup\n// ─────────────────────────────────────────────────────────────────────────\nconst BACKUP_FLAGS = {\n \"--out\": String,\n // The same alias `build` and `cloud env pull` carry. `--output` is the\n // canonical spelling in this CLI, and `rebase db backup --output ./backups`\n // has to write there.\n \"--output\": \"--out\",\n \"--exclude-schema\": [String],\n \"--no-owner\": Boolean,\n \"--enable-row-security\": Boolean,\n \"--row-security-role\": String,\n \"-o\": \"--out\"\n} satisfies arg.Spec;\n\n/**\n * Parse a `rebase db backup …` line. An undeclared flag is refused (see\n * `parseCommandFlags`), and so is a positional: `rebase db backup ./backups`\n * means `--out ./backups`, and taken as nothing it wrote to the default\n * directory.\n */\nexport function parseBackupLine(rawArgs: string[]) {\n const args = parseCommandFlags(BACKUP_FLAGS, rawArgs.slice(2), \"db backup\");\n const [stray] = args._;\n if (stray !== undefined) {\n throw new Error(\n `\\`rebase db backup\\` takes no argument \"${stray}\". Nothing was run. ` +\n `To choose where the backup goes, pass --out ${stray}.`\n );\n }\n return args;\n}\n\nexport async function backupCommand(rawArgs: string[]): Promise<void> {\n if (rawArgs.includes(\"--help\")) {\n printBackupHelp();\n return;\n }\n const args = parseBackupLine(rawArgs);\n\n const connectionString = requireConnection();\n const dbName = parseDbNameFromUrl(connectionString) ?? \"database\";\n const out = backupLocation(args[\"--out\"]);\n const dest = parseBackupDestination(out);\n\n print(\"\");\n print(chalk.bold(\" 💾 Rebase DB Backup\"));\n print(chalk.gray(` Database: ${dbName}`));\n print(chalk.gray(` Destination: ${out}`));\n print(\"\");\n\n // A database that is not running is said the way `db push` says it — the\n // host, the port and what to start — before pg_dump's version probe meets\n // it as a bare `connect ECONNREFUSED`.\n await checkDatabaseConnectivity(connectionString);\n\n // Version pre-flight (doctor-style).\n const pf = await preflight(\"pg_dump\", connectionString);\n if (!pf.compatible) {\n outError(chalk.red(` ✗ ${pf.reason}`));\n process.exit(1);\n }\n print(chalk.gray(` Using pg_dump ${pf.toolMajor} against server ${pf.serverMajor}.`));\n\n // Opt-in, and loud. With row security on, pg_dump stops refusing to read\n // rows it cannot see and starts leaving them out — an exit-0 backup that\n // is quietly short. Anyone choosing that should know they chose it.\n const rowSecurity = args[\"--enable-row-security\"]\n ? { uid: \"rebase-db-backup\", roles: [args[\"--row-security-role\"] || \"admin\"] }\n : undefined;\n\n if (rowSecurity) {\n outWarn(\"\");\n outWarn(chalk.yellow(\" ⚠ Dumping with row-level security ON.\"));\n outWarn(chalk.gray(` Reading as roles [${rowSecurity.roles.join(\", \")}], which satisfies the generated`));\n outWarn(chalk.gray(\" `admin_full_access` policy. This backup contains exactly the rows those\"));\n outWarn(chalk.gray(\" policies admit — a table whose policies have no admin rule comes out short,\"));\n outWarn(chalk.gray(\" and pg_dump will not say so. Prefer granting the dumping role BYPASSRLS.\"));\n outWarn(\"\");\n }\n\n try {\n if (dest.kind === \"local\") {\n // Honour an explicit `…/name.dump` path; otherwise treat it as a\n // directory and auto-name the file.\n const explicitFile = dest.path.endsWith(\".dump\");\n const dump = await createDump({\n connectionString,\n dbName,\n outDir: explicitFile ? path.dirname(dest.path) : dest.path,\n fileName: explicitFile ? path.basename(dest.path) : undefined,\n excludeSchemas: args[\"--exclude-schema\"],\n noOwner: args[\"--no-owner\"],\n inheritStdio: true,\n rowSecurity\n });\n await assertDumpValid(dump.localFile);\n print(\"\");\n print(chalk.green(` ✓ Backup written to ${dump.localFile} (${formatBytes(dump.sizeBytes)})`));\n if (dump.globalsFile) {\n print(chalk.gray(` ✓ Roles captured to ${dump.globalsFile} (needed so RLS survives a restore).`));\n }\n } else {\n const storage = await resolveStorageForDestination(dest, process.env);\n const dump = await createDump({\n connectionString,\n dbName,\n excludeSchemas: args[\"--exclude-schema\"],\n noOwner: args[\"--no-owner\"],\n inheritStdio: true,\n rowSecurity\n });\n try {\n await assertDumpValid(dump.localFile);\n const uploaded = await uploadBackup(storage!, dump.localFile, dest);\n print(\"\");\n print(chalk.green(` ✓ Backup uploaded to ${uploaded.storageUrl} (${formatBytes(dump.sizeBytes)})`));\n // Upload the roles sidecar next to the dump so a restore can\n // recreate roles the dump's GRANT/RLS statements depend on.\n if (dump.globalsFile && fs.existsSync(dump.globalsFile)) {\n const globalsUpload = await uploadBackup(storage!, dump.globalsFile, dest);\n print(chalk.gray(` ✓ Roles uploaded to ${globalsUpload.storageUrl} (needed so RLS survives a restore).`));\n }\n print(chalk.gray(\" Ensure this bucket is private — backups may contain secrets and PII.\"));\n } finally {\n if (fs.existsSync(dump.localFile)) fs.unlinkSync(dump.localFile);\n if (dump.globalsFile && fs.existsSync(dump.globalsFile)) fs.unlinkSync(dump.globalsFile);\n }\n }\n print(\"\");\n } catch (err) {\n reportError(err);\n process.exit(1);\n }\n}\n\n// ─────────────────────────────────────────────────────────────────────────\n// rebase db restore <backup>\n// ─────────────────────────────────────────────────────────────────────────\nconst RESTORE_FLAGS = {\n \"--target-db\": String,\n \"--create-db\": Boolean,\n \"--clean\": Boolean,\n \"--no-owner\": Boolean,\n \"--continue-on-error\": Boolean,\n \"--yes\": Boolean,\n \"-y\": \"--yes\"\n} satisfies arg.Spec;\n\n/**\n * Parse a `rebase db restore …` line; the one positional is the backup.\n *\n * Strict, because this is the command where a typo costs most: parsed\n * permissively, `--targt-db x` became a second positional, the target fell\n * back to DATABASE_URL's own database, and the restore ran against the live\n * one.\n */\nexport function parseRestoreLine(rawArgs: string[]) {\n const args = parseCommandFlags(RESTORE_FLAGS, rawArgs.slice(2), \"db restore\");\n if (args._.length > 1) {\n throw new Error(\n `\\`rebase db restore\\` restores one backup, and was given ${args._.length}: ` +\n `${args._.map(word => `\"${word}\"`).join(\", \")}. Nothing was run.`\n );\n }\n return args;\n}\n\n/**\n * The restore's one question, asked before anything can change.\n *\n * `--yes` skips it. On a terminal it asks for a typed 'yes'. With no terminal\n * nobody can answer, and the refusal says to pass `--yes`, as `db reset`,\n * `db pull` and `db branch prune` do: \"Aborted. No changes were made.\" alone\n * read as though someone had answered no.\n *\n * @returns whether to go on. When not, it has said why.\n */\nexport async function confirmRestore(opts: {\n target: string;\n yes: boolean;\n interactive: boolean;\n ask: (question: string) => Promise<boolean>;\n}): Promise<boolean> {\n if (opts.yes) return true;\n outWarn(chalk.yellow(` ⚠️ This will restore into \"${opts.target}\" and may overwrite existing data.`));\n if (!opts.interactive) {\n outError(chalk.red(\" ✗ Nothing was restored: there is no terminal to confirm on.\"));\n outError(chalk.gray(` Re-run with --yes to restore into \"${opts.target}\".`));\n return false;\n }\n const confirmed = await opts.ask(chalk.yellow(\" Type 'yes' to continue: \"));\n if (!confirmed) print(chalk.gray(\" Aborted. No changes were made.\"));\n return confirmed;\n}\n\n/** How many of the conflicting objects a refusal names before counting the rest. */\nconst CONFLICTS_NAMED = 5;\n\n/**\n * What `db restore` says when the target already holds what the backup\n * creates, and the two ways round it: `--clean` replaces them with the\n * backup's copy, a new database restores beside them.\n */\nexport function describeRestoreConflict(\n target: string,\n backupArg: string,\n conflicts: readonly string[]\n): { message: string; hint: string } {\n const named = conflicts.slice(0, CONFLICTS_NAMED).join(\", \");\n const more = conflicts.length > CONFLICTS_NAMED ? `, and ${conflicts.length - CONFLICTS_NAMED} more` : \"\";\n return {\n message: `\"${target}\" already holds ${named}${more}, which this backup creates. ` +\n \"Without --clean, pg_restore stops at the first of them. Nothing was restored or changed.\",\n hint: [\n `To replace them with the backup's copy: rebase db restore ${backupArg} --clean`,\n `To restore beside them, in a new database: rebase db restore ${backupArg} --create-db --target-db <new name>`\n ].join(\"\\n \")\n };\n}\n\nexport async function restoreCommand(rawArgs: string[]): Promise<void> {\n if (rawArgs.includes(\"--help\")) {\n printRestoreHelp();\n return;\n }\n const args = parseRestoreLine(rawArgs);\n\n const backupArg = args._[0];\n if (!backupArg) {\n printRestoreHelp();\n process.exit(1);\n }\n\n const baseConnection = requireConnection();\n\n // Choose the target connection: an explicit --target-db swaps the database\n // name so the live one isn't clobbered. Without it the target is\n // DATABASE_URL's own database, and `--create-db` only creates it when it is\n // missing.\n const targetDb = args[\"--target-db\"] ?? parseDbNameFromUrl(baseConnection) ?? undefined;\n const targetConnection = args[\"--target-db\"]\n ? withDatabaseName(baseConnection, args[\"--target-db\"])\n : baseConnection;\n\n print(\"\");\n print(chalk.bold(\" ♻️ Rebase DB Restore\"));\n print(chalk.gray(` Source: ${backupArg}`));\n print(chalk.gray(` Target: ${targetDb ?? \"(from DATABASE_URL)\"}`));\n print(\"\");\n\n // Before anything is downloaded, and in `db push`'s words: see backupCommand.\n await checkDatabaseConnectivity(baseConnection);\n\n // Resolve the local file to restore from (download object-storage keys).\n // Also resolve the `.globals.sql` roles sidecar so cluster roles can be\n // recreated before the restore — without them the dump's GRANT/RLS\n // statements fail and RLS is silently lost.\n let localFile: string;\n let globalsSql: string | null = null;\n let cleanupTemp = false;\n try {\n if (/^(s3|gs):\\/\\//.test(backupArg)) {\n const dest = parseBackupDestination(backupArg.replace(/\\/[^/]+$/, \"\"));\n const storage = await resolveStorageForDestination(dest, process.env);\n if (!storage) throw new BackupToolError(\"Could not resolve storage for the given URL.\");\n const key = backupArg.replace(/^(s3|gs):\\/\\/[^/]+\\//, \"\");\n const bucket = backupArg.replace(/^(s3|gs):\\/\\/([^/]+)\\/.*$/, \"$2\");\n const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), \"rebase-restore-\"));\n localFile = path.join(tmpDir, path.basename(key));\n cleanupTemp = true;\n // Streamed to disk: buffered, a dump was held twice in memory and\n // one over 2 GiB could not be written in one call at all.\n const object = storage.getObjectStream\n ? await storage.getObjectStream(key, { bucket })\n : await storage.getObject(key, bucket).then((file) => file && { stream: file.stream() });\n if (!object) throw new BackupToolError(`Backup not found in storage: ${backupArg}`);\n await pipeline(Readable.from(webChunks(object.stream)), fs.createWriteStream(localFile));\n const globalsObj = await storage.getObject(globalsFileForDump(key), bucket);\n if (globalsObj) globalsSql = Buffer.from(await globalsObj.arrayBuffer()).toString(\"utf-8\");\n } else {\n localFile = path.resolve(invocationDir(), backupArg);\n if (!fs.existsSync(localFile)) throw new BackupToolError(`Backup file not found: ${localFile}`);\n const globalsPath = globalsFileForDump(localFile);\n if (fs.existsSync(globalsPath)) globalsSql = fs.readFileSync(globalsPath, \"utf-8\");\n }\n\n // Version pre-flight. Check against the base connection — the server\n // version is identical for every database, and the target may not\n // exist yet when --create-db is used.\n // Thrown, not exited, from here down: an exit skipped the `finally`\n // that removes a dump downloaded from object storage.\n const pf = await preflight(\"pg_restore\", baseConnection);\n if (!pf.compatible) throw new BackupToolError(pf.reason ?? \"pg_restore cannot restore into this server.\");\n\n // `--create-db` needs a name before anything else can be decided.\n if (args[\"--create-db\"] && !targetDb) {\n throw new BackupToolError(\"--create-db requires a resolvable target database name (use --target-db).\");\n }\n\n // Destructive-action gate. Restores overwrite data; never run without\n // an explicit yes (interactive confirmation or --yes).\n //\n // Ahead of `--create-db`, not after it. Creating the database first\n // meant an aborted run had already changed the cluster, while printing\n // \"No changes were made\" — and it left an empty database behind that a\n // second, confirmed run then reported as \"already exists\". The gate is\n // now the first thing that can stop the command, so its own message is\n // true whichever way the answer goes.\n // Before the question and before anything is applied, roles included:\n // without --clean, pg_restore stops at the first object the target\n // already has, and said so in its own words and execa's, after the\n // roles had run. A target that does not exist yet holds nothing.\n if (!args[\"--clean\"]) {\n const conflicts = await findRestoreConflicts(targetConnection, await readDumpObjects(localFile));\n if (conflicts.length > 0) {\n const { message, hint } = describeRestoreConflict(targetDb ?? \"the target database\", backupArg, conflicts);\n throw new BackupToolError(message, hint);\n }\n }\n\n const confirmed = await confirmRestore({\n target: targetDb ?? \"the target database\",\n yes: args[\"--yes\"] === true,\n interactive: process.stdin.isTTY === true,\n ask: promptConfirm\n });\n if (!confirmed) {\n // An exit code, so the `finally` still removes a downloaded dump.\n process.exitCode = 1;\n return;\n }\n\n // Create the target database when requested.\n if (args[\"--create-db\"]) {\n const created = await ensureDatabaseExists(baseConnection, targetDb!);\n print(chalk.gray(created ? ` ✓ Created database \"${targetDb}\".` : ` • Database \"${targetDb}\" already exists.`));\n }\n\n // Recreate cluster roles before restoring so GRANT/RLS statements in\n // the dump apply. Best-effort and idempotent (see applyGlobals).\n let rolesApplied = 0;\n if (globalsSql) {\n print(chalk.gray(\" Recreating cluster roles from the backup's roles sidecar…\"));\n const { applied, skipped } = await applyGlobals(\n targetConnection,\n globalsSql,\n (m) => print(chalk.gray(m))\n );\n rolesApplied = applied;\n print(chalk.gray(` ✓ Roles: ${applied} applied, ${skipped} skipped (already present or not permitted).`));\n } else {\n outWarn(chalk.yellow(\" ⚠️ No roles sidecar (.globals.sql) accompanies this backup.\"));\n outWarn(chalk.yellow(\" If the dump grants to roles that don't exist (e.g. rebase_user), the restore\"));\n outWarn(chalk.yellow(\" will fail — recreate those roles first, or use a backup that includes its globals.\"));\n }\n\n try {\n await restoreDump({\n connectionString: targetConnection,\n inputFile: localFile,\n clean: args[\"--clean\"],\n noOwner: args[\"--no-owner\"],\n // Fail loudly by default so a skipped GRANT never leaves RLS off.\n exitOnError: !args[\"--continue-on-error\"],\n inheritStdio: true\n });\n } catch (err) {\n if (err instanceof BackupToolError) throw err;\n // pg_restore has printed its own error above; execa's repeats the\n // command line, which says nothing the reader can act on.\n throw new BackupToolError(\n `pg_restore stopped on the error above. What it restored before that stays in \"${targetDb ?? \"the target database\"}\".`,\n rolesApplied > 0\n ? `The ${rolesApplied} cluster role(s) recreated from the backup's sidecar before it stay too.`\n : undefined\n );\n }\n\n print(\"\");\n print(chalk.green(` ✓ Restore completed into \"${targetDb ?? \"the target database\"}\".`));\n print(\"\");\n } catch (err) {\n reportError(err);\n // An exit code, not `process.exit`: exiting here skipped the `finally`\n // below, and a failed restore from object storage left its downloaded\n // dump, every row of the database, in the temp directory.\n process.exitCode = 1;\n } finally {\n if (cleanupTemp && typeof localFile! === \"string\" && fs.existsSync(localFile!)) {\n fs.rmSync(path.dirname(localFile!), { recursive: true, force: true });\n }\n }\n}\n\n// ─────────────────────────────────────────────────────────────────────────\n// rebase db backups list\n// ─────────────────────────────────────────────────────────────────────────\nconst BACKUPS_ACTIONS = [\"list\"] as const;\n\n/** What `db backups <action>` says of an action it does not have: the sentence every `db` command says. */\nexport function unknownBackupsAction(action: string): string {\n const near = closestWord(action, BACKUPS_ACTIONS);\n return near\n ? `Unknown db backups command \"${action}\" — did you mean \\`${near}\\`? Run \\`rebase db backups --help\\` for the rest.`\n : `Unknown db backups command \"${action}\". Run \\`rebase db backups --help\\` for the ones there are.`;\n}\n\nexport async function backupsCommand(rawArgs: string[]): Promise<void> {\n const action = rawArgs[2];\n if (!action || action === \"--help\") {\n printBackupsHelp();\n return;\n }\n if (action !== \"list\") {\n outError(chalk.red(`✗ ${unknownBackupsAction(action)}`));\n process.exit(1);\n }\n\n const args = parseCommandFlags(\n { \"--out\": String, \"--output\": \"--out\", \"-o\": \"--out\" },\n rawArgs.slice(3),\n \"db backups list\"\n );\n const out = backupLocation(args[\"--out\"]);\n const dest = parseBackupDestination(out);\n\n try {\n const storage = await resolveStorageForDestination(dest, process.env);\n const backups = await listBackups(dest, storage ?? undefined);\n print(\"\");\n if (backups.length === 0) {\n print(chalk.gray(` No backups found at ${out}.`));\n await mentionBackendBackups(out, args[\"--out\"]);\n } else {\n print(chalk.bold(` 💾 ${backups.length} backup(s) at ${out}:`));\n print(\"\");\n for (const b of backups) {\n const when = b.createdAt ? b.createdAt.toISOString() : \"unknown date\";\n const name = dest.kind === \"local\" ? path.basename(b.key) : b.key;\n // An empty file is called out rather than listed as a peer of\n // the real ones. Older failures could leave a 0-byte dump here\n // (fixed at the source now), and retention protects the newest\n // by date whatever they contain — so a corpse left in place can\n // hold a `keepMinimum` slot against a backup that matters.\n const empty = b.sizeBytes === 0;\n const size = b.sizeBytes === undefined ? \"\" : ` — ${formatBytes(b.sizeBytes)}`;\n if (empty) {\n print(` ${chalk.red(\"○\")} ${chalk.bold(name)} ${chalk.gray(`— ${when}`)}${chalk.red(\" — EMPTY, not restorable\")}`);\n } else {\n print(` ${chalk.green(\"●\")} ${chalk.bold(name)} ${chalk.gray(`— ${when}${size}`)}`);\n }\n }\n if (backups.some(b => b.sizeBytes === 0)) {\n print(\"\");\n print(chalk.yellow(\" ⚠ Empty files above are leftovers from a failed backup. Delete them:\"));\n print(chalk.gray(\" they count as recent backups for retention but restore nothing.\"));\n }\n }\n print(\"\");\n } catch (err) {\n reportError(err);\n process.exit(1);\n }\n}\n\n/**\n * Point at `<backend>/backups` when the default listing is empty and that\n * directory holds dumps.\n *\n * Earlier releases resolved the default there, so after an upgrade the\n * backups a project already has are one directory away from the listing.\n */\nasync function mentionBackendBackups(listed: string, flag: string | undefined): Promise<void> {\n if (flag || process.env.BACKUP_DESTINATION?.trim()) return;\n const backendBackups = path.join(process.cwd(), \"backups\");\n if (path.resolve(listed) === backendBackups) return;\n const found = await listBackups({ kind: \"local\", path: backendBackups }).catch(() => []);\n if (found.length === 0) return;\n print(chalk.gray(` ${found.length} backup(s) are in ${backendBackups}, where earlier releases of`));\n print(chalk.gray(` \\`rebase db backup\\` wrote by default. List them with:`));\n print(chalk.gray(` rebase db backups list --out ${path.relative(invocationDir(), backendBackups) || \".\"}`));\n}\n\n/**\n * Verify a freshly written dump; abort the command if it looks corrupt.\n *\n * Discards the artifact on the way out. Refusing to *report* success was never\n * enough on its own: the file stayed on disk, `rebase db backups list` showed\n * it as an ordinary entry, and retention — which ranks by timestamp and never\n * looks at size — would protect it as one of the `keepMinimum` newest while\n * pruning a real backup underneath it. The roles sidecar goes too; the two are\n * uploaded and pruned as a pair, so half a pair is not a backup either.\n */\nasync function assertDumpValid(localFile: string): Promise<void> {\n const check = await validateDump(localFile);\n if (!check.ok) {\n discardPartialDump(globalsFileForDump(localFile));\n discardPartialDump(localFile);\n throw new BackupToolError(\n `The backup failed validation and was discarded: ${check.reason}`,\n \"The dump was corrupt or truncated, so nothing was kept. Investigate before relying on this destination.\"\n );\n }\n}\n\n/**\n * Say why a backup command failed, with no password in it.\n *\n * What reaches here is often a client tool's failure, and execa's message\n * repeats the tool's command line. The tools get the password through\n * `PGPASSWORD` now, so it is not on that line, but a connection string can\n * still arrive inside a message from elsewhere; this is the last place to\n * stop it reaching a terminal or a CI log.\n */\nfunction reportError(err: unknown): void {\n if (err instanceof BackupToolError) {\n outError(chalk.red(` ✗ ${redactConnectionPasswords(err.message)}`));\n if (err.hint) outError(chalk.gray(` ${redactConnectionPasswords(err.hint)}`));\n } else {\n outError(chalk.red(` ✗ ${redactConnectionPasswords(err instanceof Error ? err.message : String(err))}`));\n }\n}\n\nfunction printBackupHelp(): void {\n print(`\n${chalk.bold(\"rebase db backup\")} — Create a database backup (pg_dump, custom format)\n\n${chalk.green.bold(\"Usage\")}\n rebase db backup [--out <path|s3://bucket/prefix>] [options]\n\n${chalk.green.bold(\"Options\")}\n ${chalk.blue(\"--out, -o\")} <dest> Local path or s3://…/gs://… URL (default: ./backups)\n ${chalk.blue(\"--exclude-schema\")} <s> Exclude a schema (repeatable)\n ${chalk.blue(\"--no-owner\")} Omit ownership commands from the dump\n ${chalk.blue(\"--enable-row-security\")} Dump as an admin subject instead of failing on RLS\n ${chalk.red(\"(may produce a partial dump — see below)\")}\n ${chalk.blue(\"--row-security-role\")} <r> Role to read as with the flag above (default: admin)\n\n${chalk.green.bold(\"Row-level security\")}\n On a managed Postgres the dumping role usually owns nothing and has no\n BYPASSRLS, so pg_dump refuses:\n\n ERROR: query would be affected by row-level security policy for table \"...\"\n\n That refusal is the safe behaviour. --enable-row-security replaces it by\n reading as an admin subject: Rebase sets app.uid/app.user_roles so the\n generated admin_full_access policy admits the dump. The dump then contains\n exactly what those policies admit ${chalk.red(\"and no error is raised for what they do not\")} —\n a table whose policies lack an admin rule comes out short, silently.\n\n Granting the dumping role BYPASSRLS is the option that keeps a backup\n meaning \"every row\".\n\n${chalk.green.bold(\"Notes\")}\n Backups may contain secrets and PII. Use private storage destinations and\n enable encryption-at-rest. See docs/backups.md.\n`);\n}\n\nfunction printRestoreHelp(): void {\n print(`\n${chalk.bold(\"rebase db restore\")} — Restore a database from a backup (pg_restore)\n\n${chalk.green.bold(\"Usage\")}\n rebase db restore <backup> [options]\n\n${chalk.green.bold(\"Arguments\")}\n <backup> Local .dump file, or s3://…/gs://… object key\n\n${chalk.green.bold(\"Options\")}\n ${chalk.blue(\"--target-db\")} <name> Restore into this database instead of DATABASE_URL's\n ${chalk.blue(\"--create-db\")} Create the target database first if it doesn't exist\n ${chalk.blue(\"--clean\")} Drop existing objects before recreating them\n ${chalk.blue(\"--no-owner\")} Ignore ownership from the dump\n ${chalk.blue(\"--continue-on-error\")} Log and continue past errors ${chalk.red(\"(may leave RLS un-enforced!)\")}\n ${chalk.blue(\"--yes, -y\")} Skip the interactive confirmation ${chalk.red(\"(destructive!)\")}\n\n${chalk.red.bold(\"Warning\")}\n Restore is destructive and never runs automatically. Without --yes it\n asks for a typed 'yes' on a terminal, and with no terminal to ask on it\n refuses. Prefer --create-db/--target-db to restore into a fresh database\n rather than overwriting a live one. Into a database that already holds\n what the backup creates, it needs --clean, and refuses before changing\n anything without it.\n\n By default the restore aborts on the first error (--exit-on-error) so a\n skipped GRANT never silently leaves RLS un-enforced. Roles are recreated\n from the backup's .globals.sql sidecar first; keep that file next to the\n dump. Use --continue-on-error only when you understand the consequences.\n`);\n}\n\nfunction printBackupsHelp(): void {\n print(`\n${chalk.bold(\"rebase db backups\")} — Manage stored backups\n\n${chalk.green.bold(\"Usage\")}\n rebase db backups list [--out <path|s3://bucket/prefix>]\n rebase db backup list [--out <path|s3://bucket/prefix>] ${chalk.gray(\"(the same thing)\")}\n`);\n}\n"],"mappings":"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"}
|