@rebasepro/server-postgres 0.25.0-canary.ge06257e → 0.25.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-DNg4txyf.js → BranchService-iXXZeVYD.js} +222 -19
- package/dist/BranchService-iXXZeVYD.js.map +1 -0
- package/dist/backup/backup-cli.d.ts +33 -1
- package/dist/backup/backup-service.d.ts +68 -0
- package/dist/backup/pg-binary.d.ts +2 -2
- package/dist/backup/pg-tools.d.ts +60 -1
- package/dist/{backup-cli-HCEkPcQj.js → backup-cli-B2CAn9fR.js} +105 -14
- package/dist/backup-cli-B2CAn9fR.js.map +1 -0
- package/dist/{backup-service-D-UpT7DH.js → backup-service-Bb39plam.js} +358 -21
- package/dist/backup-service-Bb39plam.js.map +1 -0
- package/dist/{boot-ddl-Dnvpmm_T.js → boot-ddl-s_3Hkz94.js} +2 -2
- package/dist/{boot-ddl-Dnvpmm_T.js.map → boot-ddl-s_3Hkz94.js.map} +1 -1
- package/dist/branch-prune.d.ts +18 -0
- package/dist/{cli-console-C_ISGwJo.js → cli-console-DQ_K-sdM.js} +17 -10
- package/dist/cli-console-DQ_K-sdM.js.map +1 -0
- package/dist/cli-console.d.ts +4 -3
- package/dist/{cli-errors-mx4sSjB8.js → cli-errors-DwQnNATs.js} +80 -12
- package/dist/{cli-errors-mx4sSjB8.js.map → cli-errors-DwQnNATs.js.map} +1 -1
- package/dist/cli-errors.d.ts +12 -1
- package/dist/{cli-flags-DUozBFyK.js → cli-flags-D-b0qL9T.js} +3 -2
- package/dist/{cli-flags-DUozBFyK.js.map → cli-flags-D-b0qL9T.js.map} +1 -1
- package/dist/cli.js +106 -40
- package/dist/cli.js.map +1 -1
- package/dist/collections/validate-relations.d.ts +8 -2
- package/dist/{deferred-index-build-Ca6OqKyR.js → deferred-index-build-Cis_HoGn.js} +6 -6
- package/dist/{deferred-index-build-Ca6OqKyR.js.map → deferred-index-build-Cis_HoGn.js.map} +1 -1
- package/dist/{doctor-CJyuhV1n.js → doctor-DvJH-IQG.js} +6 -6
- package/dist/doctor-DvJH-IQG.js.map +1 -0
- package/dist/{ensure-collection-policies-C1ux3V_z.js → ensure-collection-policies-CCqayoUz.js} +54 -8
- package/dist/ensure-collection-policies-CCqayoUz.js.map +1 -0
- package/dist/{ensure-collection-tables-Do-Bvb64.js → ensure-collection-tables-DvvImXDe.js} +7 -7
- package/dist/{ensure-collection-tables-Do-Bvb64.js.map → ensure-collection-tables-DvvImXDe.js.map} +1 -1
- package/dist/{ensure-tables-Dl_Eh9ow.js → ensure-tables-Dpgwx5sy.js} +2 -2
- package/dist/{ensure-tables-Dl_Eh9ow.js.map → ensure-tables-Dpgwx5sy.js.map} +1 -1
- package/dist/{enum-labels-kept-D9eTIWCz.js → enum-labels-kept-CUlUlRkO.js} +3 -3
- package/dist/{enum-labels-kept-D9eTIWCz.js.map → enum-labels-kept-CUlUlRkO.js.map} +1 -1
- package/dist/{generate-drizzle-schema-Bf8canaR.js → generate-drizzle-schema-C3EAP9HE.js} +3 -3
- package/dist/{generate-drizzle-schema-Bf8canaR.js.map → generate-drizzle-schema-C3EAP9HE.js.map} +1 -1
- package/dist/{generate-drizzle-schema-logic-BaT0H7Gq.js → generate-drizzle-schema-logic-DA3zM8zc.js} +3 -3
- package/dist/{generate-drizzle-schema-logic-BaT0H7Gq.js.map → generate-drizzle-schema-logic-DA3zM8zc.js.map} +1 -1
- package/dist/{generate-files-D50Pxw1G.js → generate-files-ClmOUHXj.js} +5 -5
- package/dist/{generate-files-D50Pxw1G.js.map → generate-files-ClmOUHXj.js.map} +1 -1
- package/dist/{generate-next-step-DCd7yiJ2.js → generate-next-step-DCeS4LrJ.js} +22 -14
- package/dist/{generate-next-step-DCd7yiJ2.js.map → generate-next-step-DCeS4LrJ.js.map} +1 -1
- package/dist/{generate-postgres-ddl-logic-B4k9djAV.js → generate-postgres-ddl-logic-4eUP4t8w.js} +3 -3
- package/dist/{generate-postgres-ddl-logic-B4k9djAV.js.map → generate-postgres-ddl-logic-4eUP4t8w.js.map} +1 -1
- package/dist/index.es.js +8 -8
- package/dist/{introspect-db-logic-j2vYhmdP.js → introspect-db-logic-CI5CU_n0.js} +2 -2
- package/dist/{introspect-db-logic-j2vYhmdP.js.map → introspect-db-logic-CI5CU_n0.js.map} +1 -1
- package/dist/{open-rebase-D8rytTP0.js → open-rebase-BKA7Nyoe.js} +47 -27
- package/dist/{open-rebase-D8rytTP0.js.map → open-rebase-BKA7Nyoe.js.map} +1 -1
- package/dist/{pg-error-utils-CdsGYMTm.js → pg-error-utils-Bv5n2PLn.js} +11 -4
- package/dist/pg-error-utils-Bv5n2PLn.js.map +1 -0
- package/dist/{plan-schema-CsNgK0qi.js → plan-schema-CXOucCw-.js} +2 -2
- package/dist/{plan-schema-CsNgK0qi.js.map → plan-schema-CXOucCw-.js.map} +1 -1
- package/dist/{policy-drift-CY5P1ZRu.js → policy-drift-vm0BcvcE.js} +2 -2
- package/dist/{policy-drift-CY5P1ZRu.js.map → policy-drift-vm0BcvcE.js.map} +1 -1
- package/dist/{push-index-prebuild-BDFcfRPo.js → push-index-prebuild-DIequ4bV.js} +4 -4
- package/dist/{push-index-prebuild-BDFcfRPo.js.map → push-index-prebuild-DIequ4bV.js.map} +1 -1
- package/dist/{push-legacy-renames-D4bvMWAb.js → push-legacy-renames-BqJut4F3.js} +4 -4
- package/dist/{push-legacy-renames-D4bvMWAb.js.map → push-legacy-renames-BqJut4F3.js.map} +1 -1
- package/dist/{push-policy-plan-DTXE6aVa.js → push-policy-plan-CvX8hiPb.js} +22 -6
- package/dist/push-policy-plan-CvX8hiPb.js.map +1 -0
- package/dist/{render-ddl-CrfEe__B.js → render-ddl-CQNwZdse.js} +3 -3
- package/dist/{render-ddl-CrfEe__B.js.map → render-ddl-CQNwZdse.js.map} +1 -1
- package/dist/schema/atlas-argv.d.ts +3 -1
- package/dist/schema/doctor-cli.js +4 -4
- package/dist/schema/ensure-collection-policies.d.ts +24 -0
- package/dist/schema/generate-drizzle-schema.js +1 -1
- package/dist/schema/generate-next-step.d.ts +20 -8
- package/dist/schema/generate-postgres-ddl.js +2 -2
- package/dist/schema/introspect-db.js +5 -4
- package/dist/schema/introspect-db.js.map +1 -1
- package/dist/schema/push-policy-plan.d.ts +5 -2
- package/dist/services/BranchService.d.ts +67 -0
- package/dist/{source-DB6xJv3p.js → source-Bv5KPeIx.js} +45 -2
- package/dist/source-Bv5KPeIx.js.map +1 -0
- package/dist/{websocket-DMeXOoyv.js → websocket-MC0zTBar.js} +3 -3
- package/dist/{websocket-DMeXOoyv.js.map → websocket-MC0zTBar.js.map} +1 -1
- package/package.json +7 -7
- package/dist/BranchService-DNg4txyf.js.map +0 -1
- package/dist/backup-cli-HCEkPcQj.js.map +0 -1
- package/dist/backup-service-D-UpT7DH.js.map +0 -1
- package/dist/cli-console-C_ISGwJo.js.map +0 -1
- package/dist/doctor-CJyuhV1n.js.map +0 -1
- package/dist/ensure-collection-policies-C1ux3V_z.js.map +0 -1
- package/dist/pg-error-utils-CdsGYMTm.js.map +0 -1
- package/dist/push-policy-plan-DTXE6aVa.js.map +0 -1
- package/dist/source-DB6xJv3p.js.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"backup-service-Bb39plam.js","names":[],"sources":["../src/backup/pg-tools.ts","../src/backup/retention.ts","../src/backup/pg-binary.ts","../src/backup/backup-logic.ts","../src/backup/backup-service.ts"],"sourcesContent":["/**\n * Pure helpers for the backup/restore commands.\n *\n * Everything in this file is side-effect free so it can be unit-tested\n * without a live Postgres server, matching the constraint that CI must\n * not require a database.\n */\n\nimport { forLibpq } from \"../utils/connection-string\";\n\n/**\n * A parsed backup destination. `--out` (and the scheduled-backup config)\n * accepts either a local filesystem path or an object-storage URL.\n */\nexport type BackupDestination =\n | { kind: \"local\"; path: string }\n | { kind: \"s3\"; bucket: string; prefix: string }\n | { kind: \"gcs\"; bucket: string; prefix: string };\n\n/**\n * Extract the database name from a Postgres connection string.\n * Returns `null` when the URL has no database path (e.g. bare host).\n */\nexport function parseDbNameFromUrl(connectionString: string): string | null {\n try {\n const parsed = new URL(connectionString);\n const name = parsed.pathname.replace(/^\\//, \"\").trim();\n return name.length > 0 ? name : null;\n } catch {\n // Fall back to a permissive regex for non-URL DSNs.\n const match = connectionString.match(/\\/([^/?]+)(\\?|$)/);\n return match && match[1] ? match[1] : null;\n }\n}\n\n/**\n * Swap the database name in a connection string, preserving credentials,\n * host, port and query params. Used by `--target-db` / `--create-db` so a\n * restore can target a fresh database instead of clobbering the live one.\n */\nexport function withDatabaseName(connectionString: string, dbName: string): string {\n try {\n const parsed = new URL(connectionString);\n parsed.pathname = `/${dbName}`;\n return parsed.toString();\n } catch {\n return connectionString.replace(/\\/([^/?]+)(\\?|$)/, `/${dbName}$2`);\n }\n}\n\n/**\n * Parse the major version out of `pg_dump --version` / `pg_restore --version`\n * output, e.g. `\"pg_dump (PostgreSQL) 16.2\"` → `16`. Handles the pre-10\n * `9.6.x` scheme (returns `9`) and beta strings like `\"17beta1\"`.\n */\nexport function parsePgToolMajor(versionOutput: string): number | null {\n const match = versionOutput.match(/(\\d+)(?:\\.(\\d+))?/);\n if (!match) return null;\n const major = Number(match[1]);\n if (!Number.isFinite(major)) return null;\n // Postgres 9.x used the first two numbers as the major (9.6, 9.4…).\n if (major === 9 && match[2] !== undefined) {\n return 9;\n }\n return major;\n}\n\n/**\n * Convert `SELECT current_setting('server_version_num')` (e.g. `160002`)\n * into a major version (`16`). Also accepts pre-10 encodings like `90603`\n * → `9`.\n */\nexport function serverVersionNumToMajor(versionNum: number | string): number | null {\n const num = typeof versionNum === \"string\" ? Number(versionNum) : versionNum;\n if (!Number.isFinite(num) || num <= 0) return null;\n if (num < 100000) {\n // 9.x scheme: 90603 = 9.6.3\n return Math.floor(num / 10000);\n }\n return Math.floor(num / 10000);\n}\n\nexport interface VersionCompatibility {\n compatible: boolean;\n reason?: string;\n}\n\n/**\n * pg_dump / pg_restore must be **at least** as new as the server they talk\n * to. A newer client against an older server is supported; an older client\n * against a newer server is not and produces corrupt or rejected output.\n */\nexport function checkToolServerCompatibility(\n toolMajor: number | null,\n serverMajor: number | null\n): VersionCompatibility {\n if (toolMajor === null) {\n return { compatible: false, reason: \"Could not determine the client tool version.\" };\n }\n if (serverMajor === null) {\n return { compatible: false, reason: \"Could not determine the Postgres server version.\" };\n }\n if (toolMajor < serverMajor) {\n return {\n compatible: false,\n reason:\n `Client tool is Postgres ${toolMajor} but the server is Postgres ${serverMajor}. ` +\n `pg_dump/pg_restore must be the same major version as the server or newer. ` +\n `Install Postgres ${serverMajor} client tools.`\n };\n }\n return { compatible: true };\n}\n\n/**\n * Build a deterministic, sortable backup file name:\n * `rebase-<db>-<YYYYMMDD>T<HHMMSS>Z.dump`\n *\n * The UTC timestamp is embedded so retention pruning can recover the\n * creation time from the object key alone, without extra metadata.\n */\nexport function buildBackupFilename(dbName: string, date: Date = new Date()): string {\n const iso = date.toISOString(); // 2026-07-14T09:12:03.123Z\n const stamp = iso.replace(/\\.\\d+Z$/, \"Z\").replace(/[-:]/g, \"\");\n const safeDb = dbName.replace(/[^a-zA-Z0-9_-]/g, \"_\");\n return `rebase-${safeDb}-${stamp}.dump`;\n}\n\n/**\n * Recover the creation timestamp encoded in a backup file name by\n * {@link buildBackupFilename}. Returns `null` for names that don't match,\n * so foreign objects in a shared prefix are never pruned.\n */\nexport function parseBackupTimestamp(fileName: string): Date | null {\n const base = fileName.split(\"/\").pop() ?? fileName;\n const match = base.match(/-(\\d{8})T(\\d{6})Z\\.dump$/);\n if (!match) return null;\n const [, ymd, hms] = match;\n const iso =\n `${ymd.slice(0, 4)}-${ymd.slice(4, 6)}-${ymd.slice(6, 8)}` +\n `T${hms.slice(0, 2)}:${hms.slice(2, 4)}:${hms.slice(4, 6)}Z`;\n const date = new Date(iso);\n return Number.isNaN(date.getTime()) ? null : date;\n}\n\n/**\n * Parse a destination string into a structured {@link BackupDestination}.\n * `s3://bucket/prefix` and `gs://bucket/prefix` map to object storage;\n * anything else is treated as a local path.\n */\nexport function parseBackupDestination(out: string): BackupDestination {\n const s3 = out.match(/^s3:\\/\\/([^/]+)\\/?(.*)$/);\n if (s3) {\n return { kind: \"s3\", bucket: s3[1], prefix: stripTrailingSlash(s3[2]) };\n }\n const gcs = out.match(/^gs:\\/\\/([^/]+)\\/?(.*)$/);\n if (gcs) {\n return { kind: \"gcs\", bucket: gcs[1], prefix: stripTrailingSlash(gcs[2]) };\n }\n return { kind: \"local\", path: out };\n}\n\nfunction stripTrailingSlash(s: string): string {\n return s.replace(/\\/+$/, \"\");\n}\n\n/**\n * Join a storage prefix and a file name without producing a leading or\n * doubled slash.\n */\nexport function joinStorageKey(prefix: string, fileName: string): string {\n const clean = prefix.replace(/^\\/+|\\/+$/g, \"\");\n return clean.length > 0 ? `${clean}/${fileName}` : fileName;\n}\n\n/**\n * The identity `pg_dump` reads rows as, when row security is left on.\n *\n * Not optional, and that is the whole design. `pg_dump --enable-row-security`\n * on its own is the dangerous command in this file: it turns the \"query would\n * be affected by row-level security policy\" *error* into a dump that exits 0\n * and is silently missing every row the dumping role's policies exclude. A\n * backup that looks fine and restores most of your data is worse than one that\n * refused to run.\n *\n * So the flag is unreachable without a subject to evaluate the policies\n * against. Rebase's generated policies read `app.uid` and `app.user_roles`;\n * supplying an admin role satisfies the `admin_full_access` rule and the dump\n * sees everything that rule sees.\n */\nexport interface RowSecurityIdentity {\n /** Written to `app.uid`. Any non-empty value — it is only an audit trail. */\n uid: string;\n /** Written to `app.user_roles`. Must include a role the policies admit. */\n roles: string[];\n}\n\n/**\n * `PGOPTIONS` carrying an identity, for a libpq tool that has no other way to\n * set a GUC.\n *\n * A backslash escape rather than quoting, which is what libpq's `-c` parsing\n * takes: a space inside a value ends the option otherwise, so a role list is\n * comma-joined and never spaced.\n */\nexport function buildRowSecurityPgOptions(identity: RowSecurityIdentity): string {\n const escape = (value: string) => value.replace(/([\\\\ ])/g, \"\\\\$1\");\n return [\n `-c app.uid=${escape(identity.uid)}`,\n `-c app.user_id=${escape(identity.uid)}`,\n `-c app.user_roles=${escape(identity.roles.join(\",\"))}`\n ].join(\" \");\n}\n\n/**\n * Assemble the `pg_dump` argument vector. Uses the custom format (`-Fc`),\n * which is compressed and restorable selectively via `pg_restore`.\n */\nexport function buildPgDumpArgs(opts: {\n connectionString: string;\n outFile: string;\n /** Extra schemas/tables to exclude, e.g. Atlas revision tables. */\n excludeSchemas?: string[];\n /** Number of parallel jobs (directory format only; ignored for -Fc). */\n noOwner?: boolean;\n /**\n * Dump with row security on, as this identity. Omit — which is the default\n * — and `pg_dump` errors rather than skipping rows it cannot see.\n */\n rowSecurity?: RowSecurityIdentity;\n}): string[] {\n const args = [\"--format=custom\", \"--no-password\", `--file=${opts.outFile}`];\n if (opts.noOwner) {\n args.push(\"--no-owner\");\n }\n if (opts.rowSecurity) {\n args.push(\"--enable-row-security\");\n }\n for (const schema of opts.excludeSchemas ?? []) {\n args.push(`--exclude-schema=${schema}`);\n }\n args.push(forLibpq(opts.connectionString));\n return args;\n}\n\n/**\n * Whether a `pg_dump` failure is the row-security one, and what to do about it.\n *\n * The error text names the table and nothing else, so the first read of it is\n * \"why would a backup be affected by RLS at all?\" — the answer being that the\n * dumping role is not the tables' owner and has no `BYPASSRLS`, which is the\n * normal state of the `postgres` user on Cloud SQL, RDS and every other managed\n * Postgres. Nothing about that is visible from the message.\n *\n * Returns `null` for any other failure, so the caller reports it unchanged.\n */\nexport function diagnoseRowSecurityDumpFailure(error: unknown): string | null {\n const text = [\n (error as { stderr?: unknown })?.stderr,\n (error as { message?: unknown })?.message\n ].map(part => (typeof part === \"string\" ? part : \"\")).join(\"\\n\");\n\n if (!/row-level security policy/i.test(text)) return null;\n\n const table = text.match(/for table \"([^\"]+)\"/)?.[1];\n\n return [\n `pg_dump cannot read ${table ? `\"${table}\"` : \"one of the tables\"} because row-level security applies to it.`,\n \"\",\n \" The dumping role is neither the table's owner nor `BYPASSRLS`, which is the normal\",\n \" state of the `postgres` user on Cloud SQL, RDS and other managed Postgres — there is\",\n \" no superuser to hand out.\",\n \"\",\n \" Two ways out:\",\n \"\",\n \" • Grant the dumping role BYPASSRLS, or make it the owner, and run this again. The\",\n \" dump then contains every row, which is what a backup should mean.\",\n \"\",\n \" • Re-run with --enable-row-security to dump as an admin subject instead. Rebase\",\n \" sets `app.uid`/`app.user_roles` so the generated `admin_full_access` policy\",\n \" admits the dump. Read the warning it prints: the result contains exactly the\",\n \" rows those policies admit, and any table whose policies do not include an\",\n \" admin rule comes out short — with no error.\",\n \"\",\n \" Do not reach for a bare `pg_dump --enable-row-security` by hand. Without the\",\n \" settings above it succeeds and silently omits rows.\"\n ].join(\"\\n\");\n}\n\n/**\n * Assemble the `pg_restore` argument vector for a custom-format dump.\n */\nexport function buildPgRestoreArgs(opts: {\n connectionString: string;\n inputFile: string;\n /** Drop objects before recreating them (destructive but idempotent). */\n clean?: boolean;\n /**\n * Abort on the first error instead of logging and continuing. Defaults\n * ON: a restore that silently skips failed GRANT/RLS statements (because\n * a role is missing) \"succeeds\" with RLS un-enforced — a security hole.\n * Fail loudly instead so the operator knows the restore is incomplete.\n */\n exitOnError?: boolean;\n noOwner?: boolean;\n}): string[] {\n const args = [\"--format=custom\", \"--no-password\", `--dbname=${forLibpq(opts.connectionString)}`];\n if (opts.clean) {\n args.push(\"--clean\", \"--if-exists\");\n }\n if (opts.noOwner) {\n args.push(\"--no-owner\");\n }\n // Default to --exit-on-error unless explicitly disabled.\n if (opts.exitOnError !== false) {\n args.push(\"--exit-on-error\");\n }\n args.push(opts.inputFile);\n return args;\n}\n\n/**\n * Assemble the `pg_restore --list` argument vector. Reading a dump's table\n * of contents parses the whole archive without touching a database, so it is\n * a cheap integrity check that the file isn't truncated or corrupt.\n */\nexport function buildPgRestoreListArgs(inputFile: string): string[] {\n return [\"--list\", inputFile];\n}\n\n/** The schemas and tables a dump creates, read off its table of contents. */\nexport interface DumpObjects {\n schemas: string[];\n tables: { schema: string; table: string }[];\n /**\n * Every relation it creates — tables, views, materialized views, foreign\n * tables and sequences — by schema and name: what `restore --clean` can\n * tell apart from what the target holds besides.\n */\n relations: { schema: string; name: string }[];\n /** Every function, procedure and aggregate it creates, by schema and name: an overloaded name once for each. */\n routines: { schema: string; name: string }[];\n /** Every type it creates — enum, composite, range — and every domain, by schema and name. */\n types: { schema: string; name: string }[];\n}\n\n/** `<id>; <catalog oid> <oid> <description> <namespace> <tag…> <owner>`, the shape of a TOC entry. */\nconst TOC_ENTRY = /^\\d+;\\s+\\d+\\s+\\d+\\s+(SCHEMA|TABLE)\\s+(?!DATA\\s)(\\S+)\\s+(.+)\\s+(\\S+)$/;\n\n/** A TOC entry for a relation of any kind. `TABLE DATA`, `SEQUENCE SET` and `SEQUENCE OWNED BY` are not relations. */\nconst TOC_RELATION = /^\\d+;\\s+\\d+\\s+\\d+\\s+(?:FOREIGN TABLE|MATERIALIZED VIEW|TABLE|VIEW|SEQUENCE)\\s+(?!DATA\\s|SET\\s|OWNED BY\\s)(\\S+)\\s+(.+)\\s+(\\S+)$/;\n\n/** A function's TOC entry: its tag is the name and the argument types, `two(integer, text)`. */\nconst TOC_ROUTINE = /^\\d+;\\s+\\d+\\s+\\d+\\s+(?:FUNCTION|PROCEDURE|AGGREGATE)\\s+(\\S+)\\s+(.+?)\\(.*\\)\\s+\\S+$/;\n\n/** A type's or a domain's TOC entry. */\nconst TOC_TYPE = /^\\d+;\\s+\\d+\\s+\\d+\\s+(?:TYPE|DOMAIN)\\s+(\\S+)\\s+(.+)\\s+(\\S+)$/;\n\n/**\n * The schemas and tables in `pg_restore --list` output: the objects a\n * restore without `--clean` fails on when the target already has them.\n *\n * A TOC line ends in the object's owner, after its namespace and its name;\n * a schema's namespace is `-`. The name is what lies between the two, so a\n * name with a space in it (`Odd Name`) reads whole. `TABLE DATA` and every\n * other kind of entry are not objects of their own here. Its relations,\n * functions and types are read too, for what `restore --clean` would drop\n * that the dump does not hold (`findCleanExtras`).\n */\nexport function parseDumpObjects(list: string): DumpObjects {\n const objects: DumpObjects = { schemas: [], tables: [], relations: [], routines: [], types: [] };\n for (const line of list.split(\"\\n\")) {\n const trimmed = line.trim();\n const relation = TOC_RELATION.exec(trimmed);\n if (relation) objects.relations.push({ schema: relation[1], name: relation[2] });\n const routine = TOC_ROUTINE.exec(trimmed);\n if (routine) objects.routines.push({ schema: routine[1], name: routine[2] });\n const type = TOC_TYPE.exec(trimmed);\n if (type) objects.types.push({ schema: type[1], name: type[2] });\n const entry = TOC_ENTRY.exec(trimmed);\n if (!entry) continue;\n const [, kind, namespace, name] = entry;\n if (kind === \"SCHEMA\") objects.schemas.push(name);\n else objects.tables.push({ schema: namespace, table: name });\n }\n return objects;\n}\n\n/** A Postgres identifier, double-quoted. */\nfunction quoteIdentifier(name: string): string {\n return `\"${name.replace(/\"/g, \"\\\"\\\"\")}\"`;\n}\n\n/**\n * How long `restore --clean` waits for a lock before it gives up.\n *\n * A `rebase dev` serving from the database holds connections, and one inside a\n * transaction would otherwise make the restore wait without a word. The dump's\n * own script sets `lock_timeout = 0` after this, for the objects it creates.\n */\nexport const CLEAN_LOCK_TIMEOUT = \"15s\";\n\n/**\n * What `restore --clean` runs first, in the transaction the restore runs in:\n * every schema the dump creates, dropped with `CASCADE`, and every table it\n * creates in a schema it does not (`public`), the same.\n *\n * `CASCADE`, which `pg_restore --clean` never uses: a table the backup lacks\n * in one of its schemas — `rebase.atlas_schema_revisions` from `rebase db\n * migrate`, a table a newer runtime added — is exactly what stopped its plain\n * `DROP SCHEMA rebase`, and a table that references one of the backup's is what\n * stops a plain `DROP TABLE`. The dump's script follows, with its own `DROP …\n * IF EXISTS` for everything else it creates.\n */\nexport function cleanRestorePrelude(objects: DumpObjects): string[] {\n const schemas = objects.schemas.filter((schema) => schema !== \"public\");\n const dropped = new Set(schemas);\n const tables = objects.tables.filter((table) => !dropped.has(table.schema));\n return [\n \"SET client_min_messages = warning\",\n `SET lock_timeout = '${CLEAN_LOCK_TIMEOUT}'`,\n ...schemas.map((schema) => `DROP SCHEMA IF EXISTS ${quoteIdentifier(schema)} CASCADE`),\n ...tables.map((table) => `DROP TABLE IF EXISTS ${quoteIdentifier(table.schema)}.${quoteIdentifier(table.table)} CASCADE`)\n ];\n}\n\n/**\n * `pg_restore` writing the dump as a SQL script on stdout, with the `DROP …\n * IF EXISTS` that `--clean` adds, for `psql` to run. See {@link buildPsqlScriptArgs}.\n */\nexport function buildPgRestoreScriptArgs(opts: { inputFile: string; noOwner?: boolean }): string[] {\n return [\n \"--format=custom\",\n \"--clean\",\n \"--if-exists\",\n ...(opts.noOwner ? [\"--no-owner\"] : []),\n \"--file=-\",\n opts.inputFile\n ];\n}\n\n/**\n * `psql` running a script read from stdin, stopping at its first error.\n *\n * `-X`, so no `.psqlrc` changes what runs; `-q`, so it prints errors and\n * warnings, not a command tag per statement. The transaction is the script's\n * own: `restore --clean` writes the `BEGIN`, and the `COMMIT` only once\n * `pg_restore` has written the whole dump.\n */\nexport function buildPsqlScriptArgs(connectionString: string): string[] {\n return [\"-X\", \"-q\", \"--no-password\", \"-v\", \"ON_ERROR_STOP=1\", `--dbname=${forLibpq(connectionString)}`, \"--file=-\"];\n}\n\n/**\n * Assemble the `pg_dumpall --globals-only` argument vector. Roles (and other\n * cluster-wide objects) live outside any single database, so a per-database\n * `pg_dump` omits them. Without the `rebase_user` role the RLS GRANT\n * statements in the main dump fail on restore and RLS is silently lost — so\n * every backup captures the globals into a sidecar `.globals.sql`.\n *\n * `--no-role-passwords` keeps role secrets out of the artifact (backups may\n * be shipped off-box); roles are recreated password-less and re-secured by\n * the operator.\n */\nexport function buildPgDumpallGlobalsArgs(opts: {\n connectionString: string;\n outFile: string;\n}): string[] {\n return [\n \"--globals-only\",\n \"--no-role-passwords\",\n \"--no-password\",\n `--file=${opts.outFile}`,\n `--dbname=${forLibpq(opts.connectionString)}`\n ];\n}\n\n/**\n * Derive the globals sidecar path/key for a given `.dump` file. Keeps the\n * two artifacts adjacent so listing, uploading and pruning can find one from\n * the other. A name that doesn't end in `.dump` is returned unchanged with a\n * `.globals.sql` suffix appended.\n */\nexport function globalsFileForDump(dumpPath: string): string {\n return dumpPath.endsWith(\".dump\")\n ? dumpPath.slice(0, -\".dump\".length) + \".globals.sql\"\n : dumpPath + \".globals.sql\";\n}\n\n/**\n * Split a `pg_dumpall --globals-only` script into individual statements.\n * Used when replaying globals on restore so each `CREATE ROLE` / `GRANT`\n * can run independently and a benign \"role already exists\" on one doesn't\n * abort the rest. Drops `--` comment lines and blank statements.\n */\nexport function splitGlobalsStatements(sql: string): string[] {\n const withoutComments = sql\n .split(\"\\n\")\n // `--` comments, and psql meta-commands. pg_dumpall 15+ wraps its output\n // in `\\restrict <token>` / `\\unrestrict <token>`, which are instructions\n // to psql, not SQL. This replay sends statements over a connection\n // instead, so they arrived at the server as `\\restrict …` and came back\n // as `syntax error at or near \"\\\"` — reported to the user as two skipped\n // globals per restore, which reads like roles failed to apply when\n // nothing did. They carry no state worth replaying: dropping them is\n // exactly what a SQL-level consumer should do.\n .filter((line) => {\n const trimmed = line.trim();\n return !trimmed.startsWith(\"--\") && !trimmed.startsWith(\"\\\\\");\n })\n .join(\"\\n\");\n return withoutComments\n .split(\";\")\n .map((s) => s.trim())\n .filter((s) => s.length > 0);\n}\n\n/**\n * Resolve the Postgres connection string the backup commands should use,\n * mirroring the precedence the branch command already relies on.\n */\nexport function resolveConnectionString(\n env: Record<string, string | undefined>\n): string | null {\n return env.DATABASE_URL || env.ADMIN_CONNECTION_STRING || null;\n}\n","/**\n * Retention pruning for scheduled backups. Pure logic — no I/O — so it can\n * be exhaustively unit-tested without touching a storage backend.\n */\nimport { parseBackupTimestamp } from \"./pg-tools\";\n\nexport interface BackupObject {\n /** Storage key / file name. */\n key: string;\n /**\n * Creation time. Optional — when absent it is recovered from the\n * timestamp encoded in the key by `buildBackupFilename`.\n */\n createdAt?: Date;\n /**\n * Size on disk, when the lister knows it (local destinations always do).\n *\n * Reported so an empty artifact cannot masquerade as a backup in\n * `rebase db backups list`. Deliberately NOT consulted by\n * {@link selectBackupsToPrune}: retention decides by age, and a rule that\n * silently skipped small files would make a genuinely tiny dump immortal.\n * Visibility is the fix here; the artifacts themselves are now removed at\n * the point of failure by `discardPartialDump`.\n */\n sizeBytes?: number;\n}\n\nexport interface RetentionOptions {\n /** Delete backups older than this many days. `0`/negative disables age pruning. */\n retentionDays?: number;\n /**\n * Always keep at least this many of the most recent backups, even if\n * they are older than `retentionDays`. Guards against wiping every\n * backup after a long outage. Default: 0 (no minimum).\n */\n keepMinimum?: number;\n /** Reference \"now\" — injectable for deterministic tests. */\n now?: Date;\n}\n\n/**\n * Decide which backups to delete under the given retention policy.\n *\n * Rules, applied in order:\n * 1. Objects whose key isn't a recognised rebase backup (no parseable\n * timestamp and no `createdAt`) are never selected — foreign files in a\n * shared bucket are left untouched.\n * 2. The `keepMinimum` newest backups are always retained.\n * 3. Of the remainder, any older than `retentionDays` are selected for\n * deletion.\n *\n * Returns the keys to delete, oldest first.\n */\nexport function selectBackupsToPrune(\n backups: BackupObject[],\n options: RetentionOptions\n): string[] {\n const retentionDays = options.retentionDays ?? 0;\n const keepMinimum = Math.max(0, options.keepMinimum ?? 0);\n const now = options.now ?? new Date();\n\n if (retentionDays <= 0) {\n return [];\n }\n\n // Resolve a timestamp for every object; drop the ones we can't date.\n const dated = backups\n .map((b) => {\n const createdAt = b.createdAt ?? parseBackupTimestamp(b.key);\n return createdAt ? { key: b.key, createdAt } : null;\n })\n .filter((b): b is { key: string; createdAt: Date } => b !== null);\n\n // Newest first so the first `keepMinimum` are the protected ones.\n dated.sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime());\n\n const cutoff = now.getTime() - retentionDays * 24 * 60 * 60 * 1000;\n\n const toDelete = dated\n .slice(keepMinimum)\n .filter((b) => b.createdAt.getTime() < cutoff);\n\n // Return oldest first for predictable, readable logs.\n toDelete.sort((a, b) => a.createdAt.getTime() - b.createdAt.getTime());\n return toDelete.map((b) => b.key);\n}\n","/**\n * Where the PostgreSQL client tools are, without spawning them.\n *\n * Kept apart from `backup-service.ts`, which pulls in `execa` (ESM-only), so\n * `createBackupCron` can check for the tools when it is registered.\n */\nimport fs from \"fs\";\nimport { resolveLocalBin } from \"../cli-helpers\";\n\nexport type PgTool = \"pg_dump\" | \"pg_restore\" | \"pg_dumpall\" | \"psql\";\n\nconst OVERRIDE_VARIABLE: Record<PgTool, string> = {\n pg_dump: \"PG_DUMP_PATH\",\n pg_restore: \"PG_RESTORE_PATH\",\n pg_dumpall: \"PG_DUMPALL_PATH\",\n psql: \"PSQL_PATH\"\n};\n\n/** The variable that points at `tool` when it is not on the PATH. */\nexport function pgToolVariable(tool: PgTool): string {\n return OVERRIDE_VARIABLE[tool];\n}\n\n/** Locate `pg_dump` / `pg_restore` / `pg_dumpall` / `psql`, honouring an env override. */\nexport function resolvePgBinary(\n tool: PgTool,\n env: Record<string, string | undefined> = process.env\n): string | null {\n const override = env[OVERRIDE_VARIABLE[tool]];\n if (override && fs.existsSync(override)) return override;\n return resolveLocalBin(tool);\n}\n","/**\n * Pure-ish orchestration for the backup/restore paths, kept free of `execa`\n * and `pg` value imports so it can be unit-tested under jest (the impure\n * edges — spawning processes, opening connections — live in\n * `backup-service.ts`, which is vitest/runtime only).\n *\n * The functions here take their side-effecting dependency as an argument\n * (a statement runner, an object deleter) so tests can inject fakes.\n */\nimport { globalsFileForDump, splitGlobalsStatements } from \"./pg-tools\";\n\n/**\n * Replay a `pg_dumpall --globals-only` script one statement at a time,\n * tolerating per-statement failures. On a same-cluster restore the roles\n * usually already exist (`CREATE ROLE` → \"already exists\") and on a managed\n * provider an `ALTER ROLE <superuser>` may be refused; neither should abort\n * recreation of the roles that *are* missing. Returns how many statements\n * applied vs were skipped.\n *\n * `runStatement` executes one SQL statement and rejects on error.\n */\nexport async function applyGlobalsWith(\n runStatement: (sql: string) => Promise<void>,\n globalsSql: string,\n log: (message: string) => void = () => {}\n): Promise<{ applied: number; skipped: number }> {\n let applied = 0;\n let skipped = 0;\n for (const statement of splitGlobalsStatements(globalsSql)) {\n try {\n await runStatement(statement);\n applied++;\n } catch (err) {\n skipped++;\n const firstLine = statement.split(\"\\n\")[0].slice(0, 80);\n log(` • Skipped global: ${firstLine} (${err instanceof Error ? err.message : String(err)})`);\n }\n }\n return { applied, skipped };\n}\n\n/**\n * Delete each pruned dump together with its `.globals.sql` roles sidecar, so\n * pruning never orphans the roles file. The sidecar is best-effort — older\n * backups predate it, so a missing-sidecar failure is swallowed while a\n * failure deleting the dump itself propagates.\n *\n * `deleteObject` removes one key and rejects if it cannot (e.g. not found).\n */\nexport async function pruneWith(\n keys: string[],\n deleteObject: (key: string) => Promise<void>\n): Promise<void> {\n for (const key of keys) {\n await deleteObject(key);\n try {\n await deleteObject(globalsFileForDump(key));\n } catch {\n // Sidecar may not exist for older backups — ignore.\n }\n }\n}\n\n/**\n * Remove a dump artifact abandoned by a failed tool run.\n *\n * `pg_dump` creates its `--file=` target before it finishes connecting, so any\n * failure — a URL libpq rejects, a dropped connection, a full disk — leaves a\n * 0-byte file behind. That corpse is not inert: `rebase db backups list` used\n * to show it as an ordinary entry, and `selectBackupsToPrune` ranks by\n * timestamp alone, so it occupies a protected `keepMinimum` slot and can push\n * a real backup out of retention.\n *\n * Best-effort on purpose: the caller is already throwing, and a failed unlink\n * must not replace the real diagnosis with an ENOENT/EPERM from the cleanup.\n *\n * Lives here rather than in `backup-service.ts` for the reason at the top of\n * this file — that module's top-level `execa` import makes it unloadable under\n * jest, so anything placed there cannot be unit-tested.\n *\n * `remove` deletes one file and throws if it cannot; `exists` reports presence.\n */\nexport function discardPartialDumpWith(\n exists: (file: string) => boolean,\n remove: (file: string) => void,\n file: string\n): void {\n try {\n if (exists(file)) remove(file);\n } catch {\n // Ignored: see above.\n }\n}\n","/**\n * Backup / restore orchestration: thin, well-typed wrappers around\n * `pg_dump` and `pg_restore`, plus binary detection, version pre-flight,\n * and upload/prune against a storage backend.\n *\n * The pure argument- and policy-logic lives in `pg-tools.ts` and\n * `retention.ts`; this module is the impure edge (spawns processes, talks\n * to Postgres and storage) and is intentionally kept small.\n */\nimport fs from \"fs\";\nimport os from \"os\";\nimport path from \"path\";\nimport { execa } from \"execa\";\nimport type { StorageController } from \"@rebasepro/server\";\nimport { pgToolVariable, resolvePgBinary } from \"./pg-binary\";\nimport {\n BackupDestination,\n buildBackupFilename,\n buildPgDumpArgs,\n buildPgDumpallGlobalsArgs,\n buildRowSecurityPgOptions,\n diagnoseRowSecurityDumpFailure,\n type RowSecurityIdentity,\n buildPgRestoreArgs,\n buildPgRestoreListArgs,\n buildPgRestoreScriptArgs,\n buildPsqlScriptArgs,\n cleanRestorePrelude,\n checkToolServerCompatibility,\n type DumpObjects,\n globalsFileForDump,\n joinStorageKey,\n parsePgToolMajor,\n parseBackupTimestamp,\n parseDumpObjects,\n serverVersionNumToMajor,\n VersionCompatibility\n} from \"./pg-tools\";\nimport { BackupObject, RetentionOptions, selectBackupsToPrune } from \"./retention\";\nimport { applyGlobalsWith, discardPartialDumpWith, pruneWith } from \"./backup-logic\";\nimport { splitPassword } from \"../utils/connection-string\";\n\n/**\n * How a libpq tool is invoked for a connection string: the URL its command\n * line carries, and the environment it runs in.\n *\n * The password goes in `PGPASSWORD`, never on the command line, where every\n * process listing could read it and every failure printed it back (execa's\n * error repeats the command). See {@link splitPassword}.\n */\nfunction libpqInvocation(\n connectionString: string,\n env: Record<string, string | undefined>\n): { connectionString: string; env: Record<string, string> } {\n const split = splitPassword(connectionString);\n const toolEnv: Record<string, string> = {};\n for (const [key, value] of Object.entries(env)) {\n if (value !== undefined) toolEnv[key] = value;\n }\n if (split.password !== null) toolEnv.PGPASSWORD = split.password;\n return { connectionString: split.connectionString, env: toolEnv };\n}\n\n/**\n * Remove a dump artifact abandoned by a failed tool run. See\n * {@link discardPartialDumpWith} for why this exists and why the logic lives\n * in the execa-free module.\n */\nexport function discardPartialDump(file: string): void {\n discardPartialDumpWith(\n (f) => fs.existsSync(f),\n (f) => fs.unlinkSync(f),\n file\n );\n}\n\nexport class BackupToolError extends Error {\n constructor(message: string, readonly hint?: string) {\n super(message);\n this.name = \"BackupToolError\";\n }\n}\n\nexport { resolvePgBinary };\n\n/** Run `<bin> --version` and extract the major version. */\nexport async function detectToolMajor(bin: string): Promise<number | null> {\n try {\n const { stdout } = await execa(bin, [\"--version\"]);\n return parsePgToolMajor(stdout);\n } catch {\n return null;\n }\n}\n\n/** Query the server for its major version via `server_version_num`. */\nexport async function getServerVersionMajor(connectionString: string): Promise<number | null> {\n const { Client } = await import(\"pg\");\n const client = new Client({ connectionString });\n await client.connect();\n try {\n const res = await client.query(\"SHOW server_version_num\");\n const raw = res.rows?.[0]?.server_version_num;\n return serverVersionNumToMajor(raw);\n } finally {\n await client.end();\n }\n}\n\n/** PGlite names itself in `version()`: `PostgreSQL 18.3 (PGlite 0.5.6) on wasm32-…`. */\nconst SHARED_SESSION_ENGINE = /\\(PGlite\\b/i;\n\n/**\n * Put back what a client tool left set on a database that is one session\n * every connection shares, and do nothing on any other.\n *\n * The managed development database is PGlite: one session behind a socket\n * server, so what `pg_dump` sets for itself — `search_path` emptied,\n * `row_security` off, `PREPARE dumpEnumType …` — stays set for every client\n * after it, a running `rebase dev` included. One `rebase db backup` there left\n * every API request answering 500 \"table does not exist\", a restart did not\n * help (the database outlives `rebase dev`), and the next backup failed on\n * `prepared statement \"dumpenumtype\" already exists`.\n *\n * `RESET ALL` and `DEALLOCATE ALL`, not `DISCARD ALL`: that would also drop\n * the `LISTEN` a running `rebase dev`'s realtime holds on the same session.\n * Best effort: it runs after the tool, whose own outcome is what the caller\n * reports.\n */\nexport async function resetSharedSession(connectionString: string): Promise<void> {\n const { Client } = await import(\"pg\");\n const client = new Client({ connectionString, connectionTimeoutMillis: 10_000, query_timeout: 15_000 });\n client.on(\"error\", () => undefined);\n try {\n await client.connect();\n const { rows } = await client.query<{ version: string }>(\"SELECT version() AS version\");\n if (!SHARED_SESSION_ENGINE.test(rows[0]?.version ?? \"\")) return;\n await client.query(\"RESET ALL\");\n await client.query(\"DEALLOCATE ALL\");\n } catch {\n // Nothing to add to the tool's own result; see above.\n } finally {\n await client.end().catch(() => undefined);\n }\n}\n\nexport interface PreflightResult extends VersionCompatibility {\n bin: string;\n toolMajor: number | null;\n serverMajor: number | null;\n}\n\n/**\n * Verify the requested client tool exists and its major version is\n * compatible with the live server. Throws {@link BackupToolError} — with a\n * doctor-style hint — when the binary is missing.\n */\nexport async function preflight(\n tool: \"pg_dump\" | \"pg_restore\",\n connectionString: string,\n env: Record<string, string | undefined> = process.env\n): Promise<PreflightResult> {\n const bin = resolvePgBinary(tool, env);\n if (!bin) {\n throw new BackupToolError(\n `Could not find the '${tool}' binary.`,\n `Install the PostgreSQL client tools (e.g. 'brew install libpq' or 'apt-get install postgresql-client'), ` +\n `or set ${tool === \"pg_dump\" ? \"PG_DUMP_PATH\" : \"PG_RESTORE_PATH\"} to its full path.`\n );\n }\n const [toolMajor, serverMajor] = await Promise.all([\n detectToolMajor(bin),\n getServerVersionMajor(connectionString)\n ]);\n const compat = checkToolServerCompatibility(toolMajor, serverMajor);\n return { bin, toolMajor, serverMajor, ...compat };\n}\n\nexport interface BackupResult {\n /** Absolute path of the produced dump file on local disk. */\n localFile: string;\n fileName: string;\n sizeBytes: number;\n /**\n * Absolute path of the `.globals.sql` sidecar holding cluster-wide roles\n * (present unless globals capture was disabled or unavailable).\n */\n globalsFile?: string;\n globalsSizeBytes?: number;\n}\n\n/**\n * Produce a custom-format dump on local disk. When `outDir` is omitted the\n * file is written to the OS temp directory (used by the upload path, which\n * cleans it up afterwards).\n *\n * Alongside the `-Fc` dump it writes a `<name>.globals.sql` sidecar via\n * `pg_dumpall --globals-only` so the roles the dump's GRANT/RLS statements\n * depend on can be recreated on restore. Set `includeGlobals: false` to skip\n * it (e.g. when the caller has no privilege to read cluster globals).\n */\nexport async function createDump(opts: {\n connectionString: string;\n dbName: string;\n outDir?: string;\n fileName?: string;\n excludeSchemas?: string[];\n noOwner?: boolean;\n inheritStdio?: boolean;\n includeGlobals?: boolean;\n env?: Record<string, string | undefined>;\n /**\n * Dump with row security left on, reading as this identity.\n *\n * The escape hatch for a managed Postgres, where the dumping role owns\n * nothing and has no `BYPASSRLS`. Off by default, and deliberately so: with\n * row security on, `pg_dump` stops erroring on rows it cannot see and\n * simply omits them. See {@link RowSecurityIdentity}.\n */\n rowSecurity?: RowSecurityIdentity;\n}): Promise<BackupResult> {\n const env = opts.env ?? process.env;\n const bin = resolvePgBinary(\"pg_dump\", env);\n if (!bin) {\n throw new BackupToolError(\n \"Could not find the 'pg_dump' binary.\",\n \"Install the PostgreSQL client tools or set PG_DUMP_PATH.\"\n );\n }\n\n const fileName = opts.fileName ?? buildBackupFilename(opts.dbName);\n const outDir = opts.outDir ?? fs.mkdtempSync(path.join(os.tmpdir(), \"rebase-backup-\"));\n fs.mkdirSync(outDir, { recursive: true });\n const localFile = path.join(outDir, fileName);\n createOwnerOnly(localFile);\n\n const tool = libpqInvocation(opts.connectionString, env);\n const args = buildPgDumpArgs({\n connectionString: tool.connectionString,\n outFile: localFile,\n excludeSchemas: opts.excludeSchemas,\n noOwner: opts.noOwner,\n rowSecurity: opts.rowSecurity\n });\n\n // `PGOPTIONS` is the only way to set a GUC on a tool that takes no SQL.\n // Built from the same object that added `--enable-row-security`, so the\n // flag cannot travel without the identity that makes it safe.\n const dumpEnv: Record<string, string> = { ...tool.env };\n if (opts.rowSecurity) {\n dumpEnv.PGOPTIONS = [env.PGOPTIONS, buildRowSecurityPgOptions(opts.rowSecurity)]\n .filter(Boolean).join(\" \");\n }\n\n try {\n try {\n await execa(bin, args, {\n stdio: opts.inheritStdio ? \"inherit\" : \"pipe\",\n env: dumpEnv\n });\n } finally {\n await resetSharedSession(opts.connectionString);\n }\n } catch (error) {\n // pg_dump creates its `--file=` target before it finishes connecting,\n // so any failure here — a URL libpq rejects, a dropped connection, a\n // full disk — leaves a 0-byte file behind that nothing else removed.\n // That corpse is not inert: `rebase db backups list` shows it as an\n // ordinary entry, and `selectBackupsToPrune` ranks by timestamp alone,\n // so it occupies a protected `keepMinimum` slot and can push a real\n // backup out of retention. A missing backup is honest; an empty file\n // that reads as a backup is not.\n discardPartialDump(localFile);\n // The RLS failure names a table and no cause. Replace it with the\n // cause and the two ways out; anything else is re-thrown untouched.\n const diagnosis = diagnoseRowSecurityDumpFailure(error);\n if (!diagnosis) throw error;\n throw new BackupToolError(\n diagnosis,\n \"Run `rebase db backup --help` for the flag, and read what it says about partial dumps.\"\n );\n }\n\n const sizeBytes = fs.existsSync(localFile) ? fs.statSync(localFile).size : 0;\n\n const result: BackupResult = { localFile, fileName, sizeBytes };\n\n if (opts.includeGlobals !== false) {\n const dumpallBin = resolvePgBinary(\"pg_dumpall\", env);\n if (!dumpallBin) {\n throw new BackupToolError(\n \"Could not find the 'pg_dumpall' binary needed to capture cluster roles.\",\n \"Install the PostgreSQL client tools or set PG_DUMPALL_PATH. \" +\n \"To take a role-incomplete backup anyway, pass includeGlobals: false.\"\n );\n }\n const globalsFile = globalsFileForDump(localFile);\n createOwnerOnly(globalsFile);\n try {\n try {\n await execa(\n dumpallBin,\n buildPgDumpallGlobalsArgs({ connectionString: tool.connectionString, outFile: globalsFile }),\n { stdio: opts.inheritStdio ? \"inherit\" : \"pipe\", env: tool.env }\n );\n } finally {\n await resetSharedSession(opts.connectionString);\n }\n } catch (error) {\n // Same reasoning as the pg_dump catch above, and the dump goes with\n // it: the pair is uploaded and pruned together, so a dump whose\n // roles sidecar is missing restores without the roles its GRANT and\n // RLS statements need — the exact failure the sidecar exists to\n // prevent. Leaving half a pair on disk would look like a backup.\n discardPartialDump(globalsFile);\n discardPartialDump(localFile);\n throw error;\n }\n result.globalsFile = globalsFile;\n result.globalsSizeBytes = fs.existsSync(globalsFile) ? fs.statSync(globalsFile).size : 0;\n }\n\n return result;\n}\n\n/**\n * Create `file` empty and readable by its owner only, before a tool writes it.\n *\n * A dump holds every row, and the `.globals.sql` sidecar every role's password\n * hash. pg_dump and pg_dumpall create their `--file=` target with the\n * process's umask — 0644 on most machines, readable by every user — and keep\n * the mode of a file that is already there. Created 0600 first, the file is\n * never readable by anyone else, not even while the dump runs; chmod-ing it\n * afterwards would leave it open for as long as that takes. A file that\n * already existed is narrowed too.\n */\nfunction createOwnerOnly(file: string): void {\n fs.closeSync(fs.openSync(file, \"w\", 0o600));\n fs.chmodSync(file, 0o600);\n}\n\n/**\n * Cheap integrity check on a freshly written dump: it must be non-empty and\n * `pg_restore --list` must parse its table of contents without error. Used\n * before pruning older backups so a corrupt-but-exit-0 dump never becomes\n * the reason the last good backup is deleted.\n */\nexport async function validateDump(\n localFile: string,\n env: Record<string, string | undefined> = process.env\n): Promise<{ ok: boolean; reason?: string }> {\n if (!fs.existsSync(localFile)) {\n return { ok: false, reason: `Dump file does not exist: ${localFile}` };\n }\n if (fs.statSync(localFile).size === 0) {\n return { ok: false, reason: \"Dump file is empty (0 bytes).\" };\n }\n const bin = resolvePgBinary(\"pg_restore\", env);\n if (!bin) {\n // Can't verify without pg_restore; treat as inconclusive-but-fail so\n // pruning doesn't proceed on an unverified dump.\n return { ok: false, reason: \"Could not find 'pg_restore' to verify the dump.\" };\n }\n try {\n await execa(bin, buildPgRestoreListArgs(localFile), {\n stdio: \"pipe\",\n env: { ...(env as Record<string, string>) }\n });\n return { ok: true };\n } catch (err) {\n return { ok: false, reason: `pg_restore --list failed: ${err instanceof Error ? err.message : String(err)}` };\n }\n}\n\n/**\n * Replay a `pg_dumpall --globals-only` script to recreate cluster roles\n * before a restore, so the dump's GRANT/RLS statements (which reference\n * `rebase_user` and any owner roles) actually apply. Runs statement by\n * statement and tolerates per-statement failures — on a same-cluster restore\n * the roles usually already exist (`CREATE ROLE` → \"already exists\"), and on\n * a managed provider an `ALTER ROLE <superuser>` may be refused; neither\n * should abort role recreation. Returns how many statements applied vs were\n * skipped.\n */\nexport async function applyGlobals(\n connectionString: string,\n globalsSql: string,\n log: (message: string) => void = () => {}\n): Promise<{ applied: number; skipped: number }> {\n const { Client } = await import(\"pg\");\n const client = new Client({ connectionString });\n await client.connect();\n try {\n return await applyGlobalsWith(\n async (sql) => { await client.query(sql); },\n globalsSql,\n log\n );\n } finally {\n await client.end();\n }\n}\n\n/**\n * Restore a custom-format dump into the database named by\n * `connectionString`. Destructive when `clean` is set (drops objects\n * first). Never called automatically — the CLI gates it behind explicit\n * confirmation.\n *\n * Runs with `--exit-on-error` by default: a restore that logs-and-continues\n * past a failed GRANT (because a role was missing) reports success with RLS\n * un-enforced. Callers should recreate roles first (see {@link applyGlobals})\n * and only set `exitOnError: false` deliberately.\n */\nexport async function restoreDump(opts: {\n connectionString: string;\n inputFile: string;\n clean?: boolean;\n noOwner?: boolean;\n exitOnError?: boolean;\n inheritStdio?: boolean;\n env?: Record<string, string | undefined>;\n}): Promise<void> {\n const env = opts.env ?? process.env;\n const bin = resolvePgBinary(\"pg_restore\", env);\n if (!bin) {\n throw new BackupToolError(\n \"Could not find the 'pg_restore' binary.\",\n \"Install the PostgreSQL client tools or set PG_RESTORE_PATH.\"\n );\n }\n const tool = libpqInvocation(opts.connectionString, env);\n const args = buildPgRestoreArgs({\n connectionString: tool.connectionString,\n inputFile: opts.inputFile,\n clean: opts.clean,\n noOwner: opts.noOwner,\n exitOnError: opts.exitOnError\n });\n try {\n await execa(bin, args, {\n stdio: opts.inheritStdio ? \"inherit\" : \"pipe\",\n env: tool.env\n });\n } finally {\n await resetSharedSession(opts.connectionString);\n }\n}\n\n/**\n * A `restore --clean` that stopped, and so was rolled back whole.\n *\n * `tool` is the one that stopped it: `pg_restore`, writing the dump's script,\n * or `psql`, running it. `lockTimeout` when what `psql` stopped on was the\n * wait for a lock — `CLEAN_LOCK_TIMEOUT` in `pg-tools.ts`.\n */\nexport class CleanRestoreFailed extends Error {\n constructor(\n readonly tool: \"pg_restore\" | \"psql\",\n readonly exitCode: number | undefined,\n readonly lockTimeout: boolean\n ) {\n super(`${tool} exited with code ${exitCode ?? \"unknown\"}`);\n this.name = \"CleanRestoreFailed\";\n }\n}\n\n/**\n * Restore a dump over what the database holds, as one transaction:\n * {@link cleanRestorePrelude} first, then the dump as `pg_restore` writes it\n * for `psql`, and the `COMMIT` only after the last of it.\n *\n * One unit, so it either replaces what the dump holds or changes nothing:\n * `pg_restore --clean` runs each statement on its own, so the drops it starts\n * with had all happened when a later one failed — and its plain `DROP SCHEMA\n * rebase` fails on any table the backup lacks, which left a database with\n * every table dropped and nothing restored. Here a failure anywhere —\n * `pg_restore` cannot read the archive, a statement fails, the connection\n * drops — ends the transaction without a `COMMIT`, and Postgres rolls it back.\n *\n * Throws {@link CleanRestoreFailed} when it did not commit.\n */\nexport async function restoreDumpReplacing(opts: {\n connectionString: string;\n inputFile: string;\n objects: DumpObjects;\n noOwner?: boolean;\n inheritStdio?: boolean;\n env?: Record<string, string | undefined>;\n}): Promise<void> {\n const env = opts.env ?? process.env;\n const restoreBin = resolvePgBinary(\"pg_restore\", env);\n const psqlBin = resolvePgBinary(\"psql\", env);\n if (!restoreBin || !psqlBin) {\n const tool = restoreBin ? \"psql\" : \"pg_restore\";\n throw new BackupToolError(\n `Could not find the '${tool}' binary${tool === \"psql\" ? \", which runs a --clean restore as one transaction\" : \"\"}.`,\n `Install the PostgreSQL client tools or set ${pgToolVariable(tool)}.`\n );\n }\n const tool = libpqInvocation(opts.connectionString, env);\n\n const script = execa(restoreBin, buildPgRestoreScriptArgs({ inputFile: opts.inputFile, noOwner: opts.noOwner }), {\n stdin: \"ignore\",\n stdout: \"pipe\",\n stderr: opts.inheritStdio ? \"inherit\" : \"ignore\",\n buffer: false,\n reject: false,\n env: tool.env\n });\n // stdout is the rows of the script's own `SELECT`s (`set_config`,\n // `setval`), which say nothing. stderr is shown as it arrives and kept, to\n // tell a lock timeout from anything else.\n const apply = execa(psqlBin, buildPsqlScriptArgs(tool.connectionString), {\n stdin: \"pipe\",\n stdout: \"ignore\",\n stderr: opts.inheritStdio ? [\"pipe\", \"inherit\"] : \"pipe\",\n reject: false,\n env: tool.env\n });\n\n const sink = apply.stdin;\n const source = script.stdout;\n if (!sink || !source) throw new BackupToolError(\"Could not connect pg_restore to psql.\");\n // psql stops reading at its first error, and the script behind it must not\n // wait on a pipe nobody drains.\n sink.on(\"error\", () => undefined);\n apply.once(\"exit\", () => {\n source.unpipe(sink);\n source.resume();\n script.kill();\n });\n\n sink.write([\"BEGIN\", ...cleanRestorePrelude(opts.objects)].map((statement) => `${statement};\\n`).join(\"\"));\n source.pipe(sink, { end: false });\n\n const written = await script;\n const committed = written.exitCode === 0 && sink.writable;\n sink.end(committed ? \"COMMIT;\\n\" : undefined);\n const applied = await apply;\n await resetSharedSession(opts.connectionString);\n\n if (applied.exitCode !== 0) {\n const stderr = typeof applied.stderr === \"string\" ? applied.stderr : \"\";\n throw new CleanRestoreFailed(\"psql\", applied.exitCode, /lock timeout/i.test(stderr));\n }\n if (!committed) throw new CleanRestoreFailed(\"pg_restore\", written.exitCode, false);\n}\n\n/**\n * What `restore --clean` drops that the backup does not hold, so it can say\n * so before it runs: what a schema the backup replaces whole holds that it has\n * no copy of — tables, views, sequences, functions, procedures, aggregates,\n * types and domains — and the views and foreign keys elsewhere that reference\n * one of the relations it replaces. As `<kind> <name>`.\n *\n * A function is matched by its name, so an overload the backup lacks beside\n * one it holds is not named. Sequences a column owns go with their table,\n * and what an extension owns with the extension, so neither is named.\n *\n * None for a database that does not exist yet.\n */\nexport async function findCleanExtras(connectionString: string, objects: DumpObjects): Promise<string[]> {\n const schemas = objects.schemas.filter((schema) => schema !== \"public\");\n if (schemas.length === 0 && objects.relations.length === 0) return [];\n const { routines, types } = objects;\n const { Client } = await import(\"pg\");\n const client = new Client({ connectionString });\n try {\n await client.connect();\n } catch (err) {\n // 3D000: the database does not exist yet, as `--create-db` finds it.\n if (err instanceof Error && \"code\" in err && err.code === \"3D000\") return [];\n throw err;\n }\n try {\n const { rows } = await client.query<{ kind: string; name: string }>(\n `WITH dumped AS (\n SELECT c.oid\n FROM pg_class c\n JOIN pg_namespace n ON n.oid = c.relnamespace\n JOIN unnest($2::text[], $3::text[]) AS d(schema_name, rel_name)\n ON d.schema_name = n.nspname AND d.rel_name = c.relname\n )\n SELECT kind, name FROM (\n SELECT CASE c.relkind WHEN 'v' THEN 'view' WHEN 'm' THEN 'materialized view'\n WHEN 'f' THEN 'foreign table' WHEN 'S' THEN 'sequence' ELSE 'table' END AS kind,\n n.nspname || '.' || c.relname AS name\n FROM pg_class c\n JOIN pg_namespace n ON n.oid = c.relnamespace\n WHERE n.nspname = ANY($1::text[])\n AND c.relkind IN ('r', 'p', 'v', 'm', 'f', 'S')\n AND c.oid NOT IN (SELECT oid FROM dumped)\n AND NOT (c.relkind = 'S' AND EXISTS (\n SELECT 1 FROM pg_depend owner\n WHERE owner.classid = 'pg_class'::regclass AND owner.objid = c.oid\n AND owner.deptype IN ('a', 'i')))\n UNION\n SELECT CASE p.prokind WHEN 'p' THEN 'procedure' WHEN 'a' THEN 'aggregate' ELSE 'function' END,\n n.nspname || '.' || p.proname\n FROM pg_proc p\n JOIN pg_namespace n ON n.oid = p.pronamespace\n WHERE n.nspname = ANY($1::text[])\n AND (n.nspname, p.proname) NOT IN (SELECT * FROM unnest($4::text[], $5::text[]))\n AND NOT EXISTS (\n SELECT 1 FROM pg_depend owner\n WHERE owner.classid = 'pg_proc'::regclass AND owner.objid = p.oid\n AND owner.deptype IN ('e', 'i'))\n UNION\n SELECT CASE t.typtype WHEN 'd' THEN 'domain' ELSE 'type' END,\n n.nspname || '.' || t.typname\n FROM pg_type t\n JOIN pg_namespace n ON n.oid = t.typnamespace\n WHERE n.nspname = ANY($1::text[])\n AND (t.typtype IN ('e', 'd', 'r')\n OR (t.typtype = 'c' AND EXISTS (\n SELECT 1 FROM pg_class tc WHERE tc.oid = t.typrelid AND tc.relkind = 'c')))\n AND (n.nspname, t.typname) NOT IN (SELECT * FROM unnest($6::text[], $7::text[]))\n AND NOT EXISTS (\n SELECT 1 FROM pg_depend owner\n WHERE owner.classid = 'pg_type'::regclass AND owner.objid = t.oid\n AND owner.deptype = 'e')\n UNION\n SELECT 'view', vn.nspname || '.' || v.relname\n FROM pg_depend dep\n JOIN pg_rewrite r ON r.oid = dep.objid AND dep.classid = 'pg_rewrite'::regclass\n JOIN pg_class v ON v.oid = r.ev_class\n JOIN pg_namespace vn ON vn.oid = v.relnamespace\n WHERE dep.refobjid IN (SELECT oid FROM dumped)\n AND v.oid NOT IN (SELECT oid FROM dumped)\n AND NOT vn.nspname = ANY($1::text[])\n UNION\n SELECT 'foreign key', con.conname || ' on ' || cn.nspname || '.' || cr.relname\n FROM pg_constraint con\n JOIN pg_class cr ON cr.oid = con.conrelid\n JOIN pg_namespace cn ON cn.oid = cr.relnamespace\n WHERE con.contype = 'f'\n AND con.confrelid IN (SELECT oid FROM dumped)\n AND con.conrelid NOT IN (SELECT oid FROM dumped)\n AND NOT cn.nspname = ANY($1::text[])\n ) extra\n ORDER BY name`,\n [\n schemas,\n objects.relations.map((r) => r.schema), objects.relations.map((r) => r.name),\n routines.map((r) => r.schema), routines.map((r) => r.name),\n types.map((t) => t.schema), types.map((t) => t.name)\n ]\n );\n return rows.map((row) => `${row.kind} ${row.name}`);\n } finally {\n await client.end();\n }\n}\n\n/** The schemas and tables a dump creates, from `pg_restore --list`. See {@link parseDumpObjects}. */\nexport async function readDumpObjects(\n inputFile: string,\n env: Record<string, string | undefined> = process.env\n): Promise<DumpObjects> {\n const bin = resolvePgBinary(\"pg_restore\", env);\n if (!bin) {\n throw new BackupToolError(\n \"Could not find the 'pg_restore' binary.\",\n \"Install the PostgreSQL client tools or set PG_RESTORE_PATH.\"\n );\n }\n const toolEnv = Object.fromEntries(\n Object.entries(env).filter((entry): entry is [string, string] => entry[1] !== undefined)\n );\n const { stdout } = await execa(bin, buildPgRestoreListArgs(inputFile), { stdio: \"pipe\", env: toolEnv });\n return parseDumpObjects(stdout);\n}\n\n/**\n * Which of a dump's schemas and tables the target database already holds,\n * as `schema <name>` / `table <schema>.<name>`: what a restore without\n * `--clean` would stop on. `public` is left out of the schemas — every\n * database has it, and pg_restore does not create it. None for a database\n * that does not exist yet.\n */\nexport async function findRestoreConflicts(connectionString: string, objects: DumpObjects): Promise<string[]> {\n const schemas = objects.schemas.filter((schema) => schema !== \"public\");\n if (schemas.length === 0 && objects.tables.length === 0) return [];\n const { Client } = await import(\"pg\");\n const client = new Client({ connectionString });\n try {\n await client.connect();\n } catch (err) {\n // 3D000: the database does not exist yet, as `--create-db` finds it.\n if (err instanceof Error && \"code\" in err && err.code === \"3D000\") return [];\n throw err;\n }\n try {\n const existingSchemas = await client.query<{ nspname: string }>(\n \"SELECT nspname FROM pg_namespace WHERE nspname = ANY($1::text[]) ORDER BY nspname\",\n [schemas]\n );\n const existingTables = await client.query<{ nspname: string; relname: string }>(\n `SELECT n.nspname, c.relname\n FROM pg_class c\n JOIN pg_namespace n ON n.oid = c.relnamespace\n JOIN unnest($1::text[], $2::text[]) AS d(schema_name, table_name)\n ON d.schema_name = n.nspname AND d.table_name = c.relname\n WHERE c.relkind IN ('r', 'p')\n ORDER BY n.nspname, c.relname`,\n [objects.tables.map((t) => t.schema), objects.tables.map((t) => t.table)]\n );\n return [\n ...existingSchemas.rows.map((row) => `schema ${row.nspname}`),\n ...existingTables.rows.map((row) => `table ${row.nspname}.${row.relname}`)\n ];\n } finally {\n await client.end();\n }\n}\n\n/**\n * Create a database (if absent) by connecting to the maintenance\n * `postgres` database. Used by `restore --create-db`.\n */\nexport async function ensureDatabaseExists(\n adminConnectionString: string,\n dbName: string\n): Promise<boolean> {\n const { Client } = await import(\"pg\");\n const parsed = new URL(adminConnectionString);\n parsed.pathname = \"/postgres\";\n const client = new Client({ connectionString: parsed.toString() });\n await client.connect();\n try {\n const existing = await client.query(\"SELECT 1 FROM pg_database WHERE datname = $1\", [dbName]);\n if (existing.rowCount && existing.rowCount > 0) {\n return false;\n }\n // Identifier can't be parameterised; guard against injection.\n if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(dbName)) {\n throw new BackupToolError(`Invalid database name: \"${dbName}\"`);\n }\n await client.query(`CREATE DATABASE \"${dbName}\"`);\n return true;\n } finally {\n await client.end();\n }\n}\n\n/**\n * Upload a local dump to object storage under the destination's prefix.\n * Backups may contain secrets/PII, so the object is written with a\n * private/octet-stream content type; bucket-level ACLs must stay private.\n */\nexport async function uploadBackup(\n storage: StorageController,\n localFile: string,\n dest: Extract<BackupDestination, { kind: \"s3\" | \"gcs\" }>\n): Promise<{ key: string; storageUrl: string }> {\n const fileName = path.basename(localFile);\n const key = joinStorageKey(dest.prefix, fileName);\n // Backed by the dump on disk, not read into memory: the built-in\n // controllers write it a chunk (S3: a multipart part) at a time. Read with\n // `readFileSync` and copied into a `File`, a dump was held three or four\n // times over in the process running the cron, and one over 2 GiB could\n // not be read at all (`ERR_FS_FILE_TOO_LARGE`), so it never uploaded.\n const file = new File([await fs.openAsBlob(localFile)], fileName, { type: \"application/octet-stream\" });\n // Not held to the controller's upload limits: `maxFileSize` (50 MB unless\n // set) and `allowedMimeTypes` say what a user may upload, and the CLI and\n // the documented cron build their controller without either. Held to\n // them, every dump over 50 MB failed with PAYLOAD_TOO_LARGE.\n const result = await storage.putObject({\n file,\n key,\n bucket: dest.bucket,\n metadata: { \"rebase-backup\": \"1\" }\n }, { uploadLimits: false });\n return { key, storageUrl: result.storageUrl };\n}\n\n/** Stats in flight at once while listing a local backup folder. */\nconst LOCAL_STAT_CONCURRENCY = 32;\n\n/** `fn` over `items`, at most `limit` at a time; results in input order. */\nasync function mapBounded<T, R>(items: readonly T[], limit: number, fn: (item: T) => Promise<R>): Promise<R[]> {\n const results = new Array<R>(items.length);\n let next = 0;\n const worker = async () => {\n while (next < items.length) {\n const i = next++;\n results[i] = await fn(items[i]);\n }\n };\n await Promise.all(Array.from({ length: Math.min(limit, items.length) }, worker));\n return results;\n}\n\n/**\n * List existing backups at a destination. For local destinations reads the\n * directory; for object storage lists the prefix via the controller.\n */\nexport async function listBackups(\n dest: BackupDestination,\n storage?: StorageController\n): Promise<BackupObject[]> {\n if (dest.kind === \"local\") {\n // Asynchronous throughout, a few stats at a time: retention lists the\n // folder after every scheduled backup, inside the server process, and\n // a year of hourly backups is 8,760 dumps stat'ed one by one.\n const stat = await fs.promises.stat(dest.path).catch(() => null);\n if (!stat) return [];\n const dir = stat.isDirectory() ? dest.path : path.dirname(dest.path);\n const dumps = (await fs.promises.readdir(dir)).filter((f) => f.endsWith(\".dump\"));\n const stats = await mapBounded(dumps, LOCAL_STAT_CONCURRENCY, (f) => fs.promises.stat(path.join(dir, f)));\n return dumps\n .map((f, i) => {\n const createdAt = parseBackupTimestamp(f) ?? stats[i].mtime;\n return { key: path.join(dir, f), createdAt, sizeBytes: stats[i].size };\n })\n .sort((a, b) => (b.createdAt?.getTime() ?? 0) - (a.createdAt?.getTime() ?? 0));\n }\n\n if (!storage) {\n throw new BackupToolError(\n `Listing ${dest.kind} backups requires a configured storage backend.`\n );\n }\n // The server's lister, not a second copy of it: a listing is paged, in\n // ascending key order, and this one read a single page of 1,000 — the\n // OLDEST backups — so `rebase db backups list` stopped showing new ones,\n // and retention ranked a page that held none of the newest. The two copies\n // had already drifted apart once (over the roles sidecar).\n const { listBackupObjects } = await import(\"@rebasepro/server\");\n const listed = await listBackupObjects(dest, storage);\n return listed.map((info) => ({\n key: info.key,\n createdAt: info.createdAt ? new Date(info.createdAt) : undefined\n }));\n}\n\n/**\n * Apply a retention policy to a destination, deleting the backups selected\n * by {@link selectBackupsToPrune}. Returns the keys that were removed.\n */\nexport async function pruneBackups(\n dest: BackupDestination,\n options: RetentionOptions,\n storage?: StorageController\n): Promise<string[]> {\n const backups = await listBackups(dest, storage);\n const toDelete = selectBackupsToPrune(backups, options);\n\n // Delete each dump together with its `.globals.sql` sidecar (pruneWith\n // handles the pairing + best-effort sidecar). The deleter rejects on a\n // missing key so pruneWith can swallow an absent sidecar.\n const deleteObject =\n dest.kind === \"local\"\n ? async (key: string) => {\n if (!fs.existsSync(key)) throw new Error(`not found: ${key}`);\n fs.unlinkSync(key);\n }\n : async (key: string) => {\n if (!storage) throw new BackupToolError(\"Storage backend required to prune object backups.\");\n await storage.deleteObject(key, dest.bucket);\n };\n\n await pruneWith(toDelete, deleteObject);\n return toDelete;\n}\n"],"mappings":"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"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { createRequire as __createRequire } from "module";
|
|
2
2
|
__createRequire(import.meta.url);
|
|
3
3
|
import { n as __exportAll } from "./rolldown-runtime-V7rFbMRe.js";
|
|
4
|
-
import { n as extractPgError } from "./pg-error-utils-
|
|
4
|
+
import { n as extractPgError } from "./pg-error-utils-Bv5n2PLn.js";
|
|
5
5
|
//#region src/schema/boot-ddl.ts
|
|
6
6
|
/**
|
|
7
7
|
* How boot DDL waits for a table lock, and how it applies several statements as
|
|
@@ -102,4 +102,4 @@ var sleep = (ms) => ms > 0 ? new Promise((resolve) => setTimeout(resolve, ms)) :
|
|
|
102
102
|
//#endregion
|
|
103
103
|
export { isLockTimeout as a, resolveBootLockOptions as c, boot_ddl_exports as i, sleep as l, BOOT_LOCK_TIMEOUT_MS as n, isMultiStatementRefusal as o, atomicBatch as r, joinStatements as s, BOOT_LOCK_RETRY_BUDGET_MS as t };
|
|
104
104
|
|
|
105
|
-
//# sourceMappingURL=boot-ddl-
|
|
105
|
+
//# sourceMappingURL=boot-ddl-s_3Hkz94.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"boot-ddl-
|
|
1
|
+
{"version":3,"file":"boot-ddl-s_3Hkz94.js","names":[],"sources":["../src/schema/boot-ddl.ts"],"sourcesContent":["/**\n * How boot DDL waits for a table lock, and how it applies several statements as\n * one.\n *\n * ## Why boot DDL has a lock budget at all\n *\n * Every statement that changes a table — `ALTER TABLE`, `CREATE`/`DROP POLICY`,\n * `DROP TRIGGER` — needs a lock that conflicts with readers. While it *waits*\n * for that lock it sits in the table's lock queue, and every query that arrives\n * after it queues behind it, however short. A boot that happened to start while\n * a report, a long transaction or a `pg_dump` held a table stalled every live\n * read of that table for as long as it waited — up to the pool's\n * `statement_timeout` (30 s), per statement, one table after another.\n *\n * `lock_timeout` turns that wait into a short, bounded attempt. Measured on\n * Postgres 18: a reader holding one table for 5 s held a live `SELECT\n * count(*)` behind the boot for 4.4 s; with the budget below it is held for at\n * most {@link BOOT_LOCK_TIMEOUT_MS}, and the queue drains between attempts.\n *\n * ## The budget\n *\n * 1 s per attempt. An uncontended `ACCESS EXCLUSIVE` on a healthy table is\n * granted in well under a millisecond — the wait is for the statements already\n * running on it to finish — so a second is three orders of magnitude of\n * headroom and never fires on a database that is merely busy. It fires only\n * when something holds the table for longer than any interactive query should,\n * and then the cost to live traffic is one second, which sits inside every\n * client timeout this framework ships with. Longer budgets buy nothing a retry\n * does not buy better: the queue behind a waiting lock grows for as long as it\n * waits, and drains as soon as it gives up.\n *\n * ## Applying several statements as one\n *\n * {@link atomicBatch} joins statements into one simple-protocol query string.\n * Postgres runs a multi-statement query as a single implicit transaction: every\n * statement commits or none does, the `SET LOCAL` applies to that transaction\n * only, and a failure rolls it back on its own — the connection goes back to the\n * pool clean, which an explicit `BEGIN` in the string would not guarantee. It is\n * one round trip and one commit instead of one of each per statement.\n *\n * A handle that speaks only the extended protocol (PGlite in-process, used by\n * tests) refuses the string before running any of it; {@link\n * isMultiStatementRefusal} recognises that so the caller can fall back to one\n * statement at a time.\n */\nimport { extractPgError } from \"../utils/pg-error-utils\";\n\n/** `lock_timeout` for each attempt at a boot DDL statement or batch. See the module comment. */\nexport const BOOT_LOCK_TIMEOUT_MS = 1_000;\n\n/** Attempts per table, including the first, before a contended table is given up on. */\nexport const BOOT_LOCK_ATTEMPTS = 3;\n\n/**\n * The most lock waiting one boot step spends on *retries*, in total.\n *\n * Without a cap, a holder that covers the whole database — a `pg_dump` takes\n * `ACCESS SHARE` on every table it dumps, for as long as it runs — costs\n * tables × attempts × timeout: four minutes at 240 tables. Retrying is for the\n * holder that is about to finish (a report, a transaction), and ten seconds is\n * plenty to wait that out; past it, waiting longer only delays the boot.\n */\nexport const BOOT_LOCK_RETRY_BUDGET_MS = 10_000;\n\nexport interface BootLockOptions {\n /** Per-attempt `lock_timeout`, ms. Default {@link BOOT_LOCK_TIMEOUT_MS}. */\n lockTimeoutMs?: number;\n /** Attempts per table, including the first. Default {@link BOOT_LOCK_ATTEMPTS}. */\n attempts?: number;\n /** Total retry waiting per step, ms. Default {@link BOOT_LOCK_RETRY_BUDGET_MS}. */\n retryBudgetMs?: number;\n /** Pause before retry round `round` (1-based), ms. Default: jittered, growing. */\n backoffMs?: (round: number) => number;\n}\n\nexport interface ResolvedBootLockOptions {\n lockTimeoutMs: number;\n attempts: number;\n retryBudgetMs: number;\n backoffMs: (round: number) => number;\n}\n\nexport const resolveBootLockOptions = (options: BootLockOptions = {}): ResolvedBootLockOptions => ({\n lockTimeoutMs: options.lockTimeoutMs ?? BOOT_LOCK_TIMEOUT_MS,\n attempts: Math.max(1, options.attempts ?? BOOT_LOCK_ATTEMPTS),\n retryBudgetMs: options.retryBudgetMs ?? BOOT_LOCK_RETRY_BUDGET_MS,\n // Jittered so two replicas booting together do not retry in lockstep.\n backoffMs: options.backoffMs ?? (round => 500 * round * (1 + Math.random()))\n});\n\n/** `55P03 lock_not_available` — what `lock_timeout` raises. */\nexport const isLockTimeout = (err: unknown): boolean => extractPgError(err)?.code === \"55P03\";\n\n/**\n * `42601` \"cannot insert multiple commands into a prepared statement\": the\n * handle runs every query over the extended protocol, which carries exactly one\n * statement. Raised before anything runs, so nothing needs undoing.\n */\nexport const isMultiStatementRefusal = (err: unknown): boolean => {\n const pg = extractPgError(err);\n return pg?.code === \"42601\" && /multiple commands/i.test(pg.message ?? \"\");\n};\n\n/**\n * `SET LOCAL lock_timeout`, then the statements: one string, one implicit\n * transaction. See the module comment for why this and not `BEGIN … COMMIT`.\n */\nexport const atomicBatch = (statements: readonly string[], lockTimeoutMs: number): string =>\n `SET LOCAL lock_timeout = '${Math.max(1, Math.round(lockTimeoutMs))}ms';\\n${joinStatements(statements)}`;\n\n/** Statements as one multi-statement string — one implicit transaction — each `;`-terminated. */\nexport const joinStatements = (statements: readonly string[]): string =>\n statements\n .map(statement => {\n const trimmed = statement.trim();\n return trimmed.endsWith(\";\") ? trimmed : `${trimmed};`;\n })\n .join(\"\\n\");\n\nexport const sleep = (ms: number): Promise<void> =>\n ms > 0 ? new Promise(resolve => setTimeout(resolve, ms)) : Promise.resolve();\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAgDA,IAAa,uBAAuB;;;;;;;;;;AAcpC,IAAa,4BAA4B;AAoBzC,IAAa,0BAA0B,UAA2B,CAAC,OAAgC;CAC/F,eAAe,QAAQ,iBAAA;CACvB,UAAU,KAAK,IAAI,GAAG,QAAQ,YAAA,CAA8B;CAC5D,eAAe,QAAQ,iBAAA;CAEvB,WAAW,QAAQ,eAAc,UAAS,MAAM,SAAS,IAAI,KAAK,OAAO;AAC7E;;AAGA,IAAa,iBAAiB,QAA0B,eAAe,GAAG,CAAC,EAAE,SAAS;;;;;;AAOtF,IAAa,2BAA2B,QAA0B;CAC9D,MAAM,KAAK,eAAe,GAAG;CAC7B,OAAO,IAAI,SAAS,WAAW,qBAAqB,KAAK,GAAG,WAAW,EAAE;AAC7E;;;;;AAMA,IAAa,eAAe,YAA+B,kBACvD,6BAA6B,KAAK,IAAI,GAAG,KAAK,MAAM,aAAa,CAAC,EAAE,QAAQ,eAAe,UAAU;;AAGzG,IAAa,kBAAkB,eAC3B,WACK,KAAI,cAAa;CACd,MAAM,UAAU,UAAU,KAAK;CAC/B,OAAO,QAAQ,SAAS,GAAG,IAAI,UAAU,GAAG,QAAQ;AACxD,CAAC,CAAC,CACD,KAAK,IAAI;AAElB,IAAa,SAAS,OAClB,KAAK,IAAI,IAAI,SAAQ,YAAW,WAAW,SAAS,EAAE,CAAC,IAAI,QAAQ,QAAQ"}
|
package/dist/branch-prune.d.ts
CHANGED
|
@@ -47,7 +47,23 @@ export interface PrunePlan {
|
|
|
47
47
|
}[];
|
|
48
48
|
/** Atlas scratch databases. Reported; removed only when asked. */
|
|
49
49
|
devDiff: string[];
|
|
50
|
+
/**
|
|
51
|
+
* The branch the checkout is switched to, when prune would otherwise have
|
|
52
|
+
* removed it: kept, as `delete` refuses it, and named so the plan says
|
|
53
|
+
* why. `null` when prune would not have touched it.
|
|
54
|
+
*/
|
|
55
|
+
active: {
|
|
56
|
+
name: string;
|
|
57
|
+
database: string;
|
|
58
|
+
} | null;
|
|
50
59
|
}
|
|
60
|
+
/**
|
|
61
|
+
* The variable the CLI hands the driver the database its checkout is switched
|
|
62
|
+
* to in. The pointer is the CLI's (`dev-db/branch-pointer.ts`); the driver is
|
|
63
|
+
* a child process that cannot read it. Spelled the same in
|
|
64
|
+
* `packages/cli/src/commands/db.ts`.
|
|
65
|
+
*/
|
|
66
|
+
export declare const ACTIVE_BRANCH_DATABASE_ENV = "REBASE_ACTIVE_BRANCH_DATABASE";
|
|
51
67
|
/** True when nothing at all needs doing. */
|
|
52
68
|
export declare function planIsEmpty(plan: PrunePlan, includeDevDiff: boolean): boolean;
|
|
53
69
|
/**
|
|
@@ -74,4 +90,6 @@ export declare function planPrune(input: {
|
|
|
74
90
|
}, options?: {
|
|
75
91
|
olderThanDays?: number | null;
|
|
76
92
|
now?: Date;
|
|
93
|
+
/** The database the checkout is switched to, which prune never drops. */
|
|
94
|
+
activeDatabase?: string | null;
|
|
77
95
|
}): PrunePlan;
|
|
@@ -2,11 +2,11 @@ import { createRequire as __createRequire } from "module";
|
|
|
2
2
|
__createRequire(import.meta.url);
|
|
3
3
|
import { i as __toESM } from "./rolldown-runtime-V7rFbMRe.js";
|
|
4
4
|
import { l as poolMaxCeiling } from "./connection-C938nR3E.js";
|
|
5
|
-
import { t as openRebase } from "./open-rebase-
|
|
6
|
-
import {
|
|
7
|
-
import { i as outError, n as describeFailure, r as out } from "./source-
|
|
5
|
+
import { t as openRebase } from "./open-rebase-BKA7Nyoe.js";
|
|
6
|
+
import { a as diagnoseDbError } from "./cli-errors-DwQnNATs.js";
|
|
7
|
+
import { i as outError, n as describeFailure, r as out } from "./source-Bv5KPeIx.js";
|
|
8
8
|
import { n as require_arg } from "./cli-prompt-egtNKkZl.js";
|
|
9
|
-
import { t as RELAYED_FLAGS } from "./cli-flags-
|
|
9
|
+
import { t as RELAYED_FLAGS } from "./cli-flags-D-b0qL9T.js";
|
|
10
10
|
import { setLogOutput } from "@rebasepro/server";
|
|
11
11
|
import path from "path";
|
|
12
12
|
import fs from "fs";
|
|
@@ -100,24 +100,31 @@ function connectionCutOff(error, depth = 0) {
|
|
|
100
100
|
if ("errors" in error && Array.isArray(error.errors) && error.errors.some((inner) => connectionCutOff(inner, depth + 1))) return true;
|
|
101
101
|
return "cause" in error && connectionCutOff(error.cause, depth + 1);
|
|
102
102
|
}
|
|
103
|
+
/** Whether Postgres refused the connection for want of one: SQLSTATE 53300, anywhere in the error's chain. */
|
|
104
|
+
function noFreeConnection(error, depth = 0) {
|
|
105
|
+
if (!error || typeof error !== "object" || depth > 4) return false;
|
|
106
|
+
if ("code" in error && error.code === "53300") return true;
|
|
107
|
+
return "cause" in error && noFreeConnection(error.cause, depth + 1);
|
|
108
|
+
}
|
|
103
109
|
/**
|
|
104
110
|
* What the console says when the project's database will not open: the banner
|
|
105
111
|
* every db command prints for a database it cannot reach, or one sentence —
|
|
106
112
|
* never the driver's error serialised onto one line, stack and all.
|
|
107
113
|
*
|
|
108
|
-
* The managed development database
|
|
109
|
-
*
|
|
110
|
-
*
|
|
114
|
+
* The managed development database accepts only a few connections at a time,
|
|
115
|
+
* and a running `rebase dev` holds some of them. Its proxy refuses one over the
|
|
116
|
+
* limit with SQLSTATE 53300; a database an earlier CLI started resets it
|
|
117
|
+
* instead, and `read ECONNRESET` says nothing about why.
|
|
111
118
|
*/
|
|
112
119
|
function describeOpenFailure(error, databaseUrl, managed) {
|
|
113
|
-
if (managed && connectionCutOff(error)) return "✗ The managed development database has no free connection: it accepts only a few at a time, and other clients hold them — a running `rebase dev` holds some.\n Close another `rebase console`, psql or database GUI and run this again, or stop `rebase dev` while you use the console.";
|
|
120
|
+
if (managed && (connectionCutOff(error) || noFreeConnection(error))) return "✗ The managed development database has no free connection: it accepts only a few at a time, and other clients hold them — a running `rebase dev` holds some.\n Close another `rebase console`, psql or database GUI and run this again, or stop `rebase dev` while you use the console.";
|
|
114
121
|
return diagnoseDbError(error, databaseUrl) ?? describeFailure(error);
|
|
115
122
|
}
|
|
116
123
|
/** Open the project, run `--eval` or the REPL, and close it again. Resolves with the exit code. */
|
|
117
124
|
async function openAndRun(options, streams) {
|
|
118
125
|
const { databaseUrl } = options;
|
|
119
126
|
const collectionsPath = path.resolve(options.collections || path.join("..", "config", "collections"));
|
|
120
|
-
const { loadCollections } = await import("./doctor-
|
|
127
|
+
const { loadCollections } = await import("./doctor-DvJH-IQG.js").then((n) => n.t);
|
|
121
128
|
const collections = fs.existsSync(collectionsPath) ? await loadCollections(collectionsPath) : [];
|
|
122
129
|
const ceiling = poolMaxCeiling();
|
|
123
130
|
let rebase;
|
|
@@ -252,4 +259,4 @@ async function feedLineByLine(source, server, feed, exited) {
|
|
|
252
259
|
//#endregion
|
|
253
260
|
export { consoleCommand };
|
|
254
261
|
|
|
255
|
-
//# sourceMappingURL=cli-console-
|
|
262
|
+
//# sourceMappingURL=cli-console-DQ_K-sdM.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli-console-DQ_K-sdM.js","names":[],"sources":["../src/cli-console.ts"],"sourcesContent":["/**\n * `rebase console` — the driver's half.\n *\n * Loads the project's collections the way `rebase db push` does, opens the\n * database with {@link openRebase}, and either evaluates one piece of code\n * (`--eval`) or starts a Node REPL with `rebase` and `collections` in scope.\n * The `rebase` CLI resolves the database first (the managed one included) and\n * hands this process `DATABASE_URL`, exactly as it does for `db` commands.\n *\n * This is the boot side of `openRebase`: it is where the environment is read\n * — `DATABASE_URL`, and the `REBASE_DB_POOL_MAX` ceiling the managed\n * development database needs — and turned into explicit options.\n */\nimport arg from \"arg\";\nimport fs from \"fs\";\nimport path from \"path\";\nimport readline from \"node:readline\";\nimport repl from \"node:repl\";\nimport { PassThrough } from \"node:stream\";\nimport tty from \"node:tty\";\nimport { inspect } from \"node:util\";\nimport { setLogOutput } from \"@rebasepro/server\";\nimport type { CollectionConfig } from \"@rebasepro/types\";\nimport { RELAYED_FLAGS } from \"./cli-flags\";\nimport { diagnoseDbError } from \"./cli-errors\";\nimport { out, outError, describeFailure } from \"./cli-output\";\nimport { poolMaxCeiling } from \"./connection\";\nimport { openRebase, type OpenedRebase } from \"./open-rebase\";\n\n/**\n * The flags `rebase console` takes. `--history` is the CLI's own: where it\n * keeps the REPL's history (`.rebase/console_history` in the project).\n */\nexport const CONSOLE_FLAGS = {\n \"--collections\": String,\n \"-c\": \"--collections\",\n \"--eval\": String,\n \"-e\": \"--eval\",\n \"--history\": String\n} satisfies arg.Spec;\n\n/** What the code the console runs can name. */\nexport interface ConsoleScope {\n rebase: OpenedRebase;\n collections: CollectionConfig[];\n}\n\ntype ScopedFunction = (rebase: OpenedRebase, collections: CollectionConfig[]) => Promise<unknown>;\ntype AsyncFunctionConstructor = new (...parameters: string[]) => ScopedFunction;\n\n/** The constructor of `async function` — what `new Function` is for a body that awaits. */\nconst AsyncFunction: AsyncFunctionConstructor = Object.getPrototypeOf(async () => undefined).constructor;\n\n/**\n * Run `code` with `rebase` and `collections` in scope and resolve with its\n * value.\n *\n * Read as an expression first, so `rebase.dataAsAdmin.posts.find()` is its own\n * result; code that is not one expression runs as a function body, where\n * `await` works everywhere and `return` gives the result.\n */\nexport async function evaluateInConsole(code: string, scope: ConsoleScope): Promise<unknown> {\n let run: ScopedFunction;\n try {\n run = new AsyncFunction(\"rebase\", \"collections\", `return (${code}\\n);`);\n } catch (error) {\n if (!(error instanceof SyntaxError)) throw error;\n run = new AsyncFunction(\"rebase\", \"collections\", code);\n }\n return run(scope.rebase, scope.collections);\n}\n\n/** A result as JSON, the way `--eval` prints it: dates as ISO strings, bigints as strings. */\nexport function consoleJson(value: unknown): string {\n return JSON.stringify(value, (_key, item: unknown) => typeof item === \"bigint\" ? item.toString() : item, 2) ?? \"undefined\";\n}\n\n/** The host and database a URL names, for the banner — never its credentials. */\nfunction describeDatabase(databaseUrl: string): string {\n try {\n const url = new URL(databaseUrl);\n return `${url.hostname}${url.port ? `:${url.port}` : \"\"}${url.pathname}`;\n } catch {\n return \"the configured database\";\n }\n}\n\n/** Where the REPL reads and writes; the terminal unless a caller hands it streams. */\nexport interface ConsoleStreams {\n input?: NodeJS.ReadableStream;\n output?: NodeJS.WritableStream;\n}\n\n/**\n * `rebase console [--eval <code>] [--collections <dir>]`, from the driver\n * line `[\"console\", …flags]`. Resolves with the exit code.\n */\nexport async function consoleCommand(args: string[], streams: ConsoleStreams = {}): Promise<number> {\n const flags = arg({ ...RELAYED_FLAGS, ...CONSOLE_FLAGS }, { argv: args.slice(1) });\n if (flags[\"--help\"]) {\n out(\"Run `rebase console --help` for the options — this is the driver's own entry point.\");\n return 0;\n }\n\n const databaseUrl = flags[\"--database-url\"] || process.env.DATABASE_URL;\n if (!databaseUrl) {\n outError(\"✗ No database to open: DATABASE_URL is not set. Set it in .env, or pass --database-url.\");\n return 1;\n }\n\n // The console's own lines are what the reader is here for; the driver's\n // info-level boot notes are not, unless they asked for them.\n if (process.env.LOG_LEVEL === undefined) process.env.LOG_LEVEL = \"warn\";\n\n // And whatever level they asked for, a log line is never part of the\n // answer: stdout holds the `--eval` result a pipe reads (`| jq .`) and the\n // REPL's values. The scaffold's `.env` sets LOG_LEVEL=info, and under\n // NODE_ENV=production warnings go to stdout too.\n setLogOutput(\"stderr\");\n try {\n return await openAndRun({\n databaseUrl,\n collections: flags[\"--collections\"],\n eval: flags[\"--eval\"],\n history: flags[\"--history\"]\n }, streams);\n } finally {\n setLogOutput();\n }\n}\n\n/** Whether a connection was reset or cut off before it answered, anywhere in the error's chain. */\nfunction connectionCutOff(error: unknown, depth = 0): boolean {\n if (!error || typeof error !== \"object\" || depth > 4) return false;\n if (\"code\" in error && error.code === \"ECONNRESET\") return true;\n if (error instanceof Error && /Connection terminated unexpectedly/.test(error.message)) return true;\n if (\"errors\" in error && Array.isArray(error.errors) && error.errors.some(inner => connectionCutOff(inner, depth + 1))) return true;\n return \"cause\" in error && connectionCutOff(error.cause, depth + 1);\n}\n\n/** Whether Postgres refused the connection for want of one: SQLSTATE 53300, anywhere in the error's chain. */\nfunction noFreeConnection(error: unknown, depth = 0): boolean {\n if (!error || typeof error !== \"object\" || depth > 4) return false;\n if (\"code\" in error && error.code === \"53300\") return true;\n return \"cause\" in error && noFreeConnection(error.cause, depth + 1);\n}\n\n/**\n * What the console says when the project's database will not open: the banner\n * every db command prints for a database it cannot reach, or one sentence —\n * never the driver's error serialised onto one line, stack and all.\n *\n * The managed development database accepts only a few connections at a time,\n * and a running `rebase dev` holds some of them. Its proxy refuses one over the\n * limit with SQLSTATE 53300; a database an earlier CLI started resets it\n * instead, and `read ECONNRESET` says nothing about why.\n */\nexport function describeOpenFailure(error: unknown, databaseUrl: string, managed: boolean): string {\n if (managed && (connectionCutOff(error) || noFreeConnection(error))) {\n return \"✗ The managed development database has no free connection: it accepts only a few at a time, \" +\n \"and other clients hold them — a running `rebase dev` holds some.\\n\" +\n \" Close another `rebase console`, psql or database GUI and run this again, \" +\n \"or stop `rebase dev` while you use the console.\";\n }\n return diagnoseDbError(error, databaseUrl) ?? describeFailure(error);\n}\n\n/** Open the project, run `--eval` or the REPL, and close it again. Resolves with the exit code. */\nasync function openAndRun(\n options: { databaseUrl: string; collections?: string; eval?: string; history?: string },\n streams: ConsoleStreams\n): Promise<number> {\n const { databaseUrl } = options;\n const collectionsPath = path.resolve(options.collections || path.join(\"..\", \"config\", \"collections\"));\n const { loadCollections } = await import(\"./schema/doctor\");\n const collections = fs.existsSync(collectionsPath) ? await loadCollections(collectionsPath) : [];\n\n const ceiling = poolMaxCeiling();\n let rebase: OpenedRebase;\n try {\n rebase = await openRebase({\n databaseUrl,\n collections,\n ...(ceiling !== null ? { pool: { max: ceiling } } : {})\n });\n } catch (error) {\n outError(describeOpenFailure(error, databaseUrl, process.env.REBASE_DEV_DATABASE_KIND === \"managed\"));\n return 1;\n }\n\n try {\n const code = options.eval;\n if (code !== undefined) {\n try {\n const result = await evaluateInConsole(code, { rebase, collections: rebase.collections });\n if (result !== undefined) out(consoleJson(result));\n return 0;\n } catch (error) {\n outError(describeFailure(error));\n return 1;\n }\n }\n\n await startRepl({ rebase, collections: rebase.collections }, {\n history: options.history,\n database: describeDatabase(databaseUrl),\n ...streams\n });\n return 0;\n } finally {\n await rebase.close();\n }\n}\n\n/** The interactive half: a Node REPL until `.exit`, Ctrl-D or the end of piped input. */\nasync function startRepl(scope: ConsoleScope, options: { history?: string; database: string } & ConsoleStreams): Promise<void> {\n const input = options.input ?? process.stdin;\n const output = options.output ?? process.stdout;\n const typed = input instanceof tty.ReadStream && input.isTTY;\n\n // For a person at a terminal. Piped, stdout is the REPL's answers, which\n // whatever reads the pipe is after, so the banner goes to stderr with the\n // rest of what the console says about itself.\n const say = typed ? out : outError;\n say(\"\");\n say(` rebase console — ${options.database}, ${scope.collections.length} collection(s)`);\n say(\" In scope: rebase (dataAsAdmin, sql, withAuth, close) and collections. Top-level await works.\");\n say(\" Try: await rebase.dataAsAdmin.<collection>.find({ limit: 5 })\");\n say(\"\");\n\n // Typed at a terminal, the REPL reads it as one. Anything else — a pipe, a\n // file, a stream handed in — reaches it a line at a time through `feed`\n // (see `feedLineByLine`).\n const feed = typed ? undefined : new PassThrough();\n // Colours for a terminal only: piped to a file or another program, the\n // escape codes are noise in the text.\n const colors = output instanceof tty.WriteStream && output.isTTY;\n // Started without a prompt, and given one once the scope is in place: a\n // line that arrives with the first prompt must already see `rebase`.\n const server = repl.start({\n prompt: \"\",\n useGlobal: false,\n ...(feed ? { input: feed, output, terminal: false } : { input, output }),\n writer: (value: unknown) => inspect(value, { colors, depth: 6 })\n });\n server.context.rebase = scope.rebase;\n server.context.collections = scope.collections;\n server.setPrompt(\"rebase> \");\n\n const history = options.history;\n if (history) {\n fs.mkdirSync(path.dirname(history), { recursive: true });\n await new Promise<void>((resolve) => {\n server.setupHistory(history, (error) => {\n if (error) outError(` (history is off: ${error.message})`);\n resolve();\n });\n });\n }\n\n const exited = new Promise<void>((resolve) => server.once(\"exit\", () => resolve()));\n server.displayPrompt();\n if (feed) await feedLineByLine(input, server, feed, exited);\n await exited;\n}\n\n/**\n * Hand input that is not typed at a terminal to the REPL a line at a time,\n * each once the REPL has finished the one before.\n *\n * Typed, the REPL holds keystrokes back while a top-level `await` is pending.\n * Piped, every line arrives at once and each is evaluated as it arrives, so in\n * `const { data } = await …` followed by `data.map(…)` the second line read\n * `data` before the first had assigned it.\n *\n * The REPL is paused as each line reaches it, and it resumes its input when it\n * displays its prompt again: once the line's value has settled, its error has\n * been printed, or it needs the next line to complete it. That resumption is\n * when the next line goes in. Lines are written one per chunk, because a\n * chunk holding several is split and every line of it handed over at once.\n */\nasync function feedLineByLine(\n source: NodeJS.ReadableStream,\n server: repl.REPLServer,\n feed: PassThrough,\n exited: Promise<void>\n): Promise<void> {\n let evaluated: (() => void) | undefined;\n const ready = () => {\n const resolve = evaluated;\n evaluated = undefined;\n resolve?.();\n };\n // Before the REPL's own handler, so a line it evaluates at once resumes\n // after this pause rather than before it.\n server.prependListener(\"line\", () => {\n if (!server.editorMode) server.pause();\n });\n // `.editor` collects lines with no prompt until Ctrl-D: they go straight in.\n server.on(\"line\", () => {\n if (!server.editorMode) return;\n server.resume();\n ready();\n });\n server.on(\"resume\", ready);\n\n const closed = exited.then(() => \"closed\" as const);\n const lines = readline.createInterface({ input: source, crlfDelay: Infinity });\n for await (const line of lines) {\n const done = new Promise<\"done\">((resolve) => { evaluated = () => resolve(\"done\"); });\n feed.write(`${line}\\n`);\n // `.exit`: the rest of the input is not for this REPL.\n if (await Promise.race([done, closed]) === \"closed\") break;\n }\n feed.end();\n}\n"],"mappings":"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"}
|
package/dist/cli-console.d.ts
CHANGED
|
@@ -42,8 +42,9 @@ export declare function consoleCommand(args: string[], streams?: ConsoleStreams)
|
|
|
42
42
|
* every db command prints for a database it cannot reach, or one sentence —
|
|
43
43
|
* never the driver's error serialised onto one line, stack and all.
|
|
44
44
|
*
|
|
45
|
-
* The managed development database
|
|
46
|
-
*
|
|
47
|
-
*
|
|
45
|
+
* The managed development database accepts only a few connections at a time,
|
|
46
|
+
* and a running `rebase dev` holds some of them. Its proxy refuses one over the
|
|
47
|
+
* limit with SQLSTATE 53300; a database an earlier CLI started resets it
|
|
48
|
+
* instead, and `read ECONNRESET` says nothing about why.
|
|
48
49
|
*/
|
|
49
50
|
export declare function describeOpenFailure(error: unknown, databaseUrl: string, managed: boolean): string;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createRequire as __createRequire } from "module";
|
|
2
2
|
__createRequire(import.meta.url);
|
|
3
|
-
import { a as outWarn, i as outError, m as isRebaseIndexName, o as atlasExcludePattern, r as out, s as excludePatternParts, t as chalk } from "./source-
|
|
3
|
+
import { a as outWarn, i as outError, m as isRebaseIndexName, o as atlasExcludePattern, r as out, s as excludePatternParts, t as chalk } from "./source-Bv5KPeIx.js";
|
|
4
4
|
import path from "path";
|
|
5
5
|
import fs from "fs";
|
|
6
6
|
import { fileURLToPath, pathToFileURL } from "url";
|
|
@@ -1292,7 +1292,7 @@ async function applySearchDdl(databaseUrl, sqlDir = generatedSqlDir()) {
|
|
|
1292
1292
|
* `search` block, which is every project that has not opted in.
|
|
1293
1293
|
*/
|
|
1294
1294
|
async function getSearchExcludes(collectionsPath) {
|
|
1295
|
-
const { searchExcludePatterns } = await import("./generate-postgres-ddl-logic-
|
|
1295
|
+
const { searchExcludePatterns } = await import("./generate-postgres-ddl-logic-4eUP4t8w.js").then((n) => n.o);
|
|
1296
1296
|
return searchExcludePatterns(await loadCollectionsForCli(collectionsPath));
|
|
1297
1297
|
}
|
|
1298
1298
|
/**
|
|
@@ -1397,7 +1397,7 @@ async function applyVectorDdl(databaseUrl, sqlDir = generatedSqlDir()) {
|
|
|
1397
1397
|
* `vector` property, which is every project that has not opted in.
|
|
1398
1398
|
*/
|
|
1399
1399
|
async function getVectorExcludes(collectionsPath) {
|
|
1400
|
-
const { vectorExcludePatterns } = await import("./generate-postgres-ddl-logic-
|
|
1400
|
+
const { vectorExcludePatterns } = await import("./generate-postgres-ddl-logic-4eUP4t8w.js").then((n) => n.o);
|
|
1401
1401
|
return vectorExcludePatterns(await loadCollectionsForCli(collectionsPath));
|
|
1402
1402
|
}
|
|
1403
1403
|
/**
|
|
@@ -1434,7 +1434,7 @@ async function applyTriggersDdl(databaseUrl, sqlDir = generatedSqlDir()) {
|
|
|
1434
1434
|
}
|
|
1435
1435
|
/** @see getSearchExcludes */
|
|
1436
1436
|
async function getTriggerExcludes(collectionsPath) {
|
|
1437
|
-
const { triggerExcludePatterns } = await import("./generate-postgres-ddl-logic-
|
|
1437
|
+
const { triggerExcludePatterns } = await import("./generate-postgres-ddl-logic-4eUP4t8w.js").then((n) => n.o);
|
|
1438
1438
|
return triggerExcludePatterns(await loadCollectionsForCli(collectionsPath));
|
|
1439
1439
|
}
|
|
1440
1440
|
/**
|
|
@@ -1511,9 +1511,9 @@ async function getForeignIndexExcludes(databaseUrl, collectionsPath, deps = {})
|
|
|
1511
1511
|
*/
|
|
1512
1512
|
async function managedIndexNames(collectionsPath) {
|
|
1513
1513
|
const collections = await loadCollectionsForCli(collectionsPath);
|
|
1514
|
-
const { searchExcludePatterns, vectorExcludePatterns } = await import("./generate-postgres-ddl-logic-
|
|
1514
|
+
const { searchExcludePatterns, vectorExcludePatterns } = await import("./generate-postgres-ddl-logic-4eUP4t8w.js").then((n) => n.o);
|
|
1515
1515
|
const { resolveColumnName } = await import("./column-plan-helpers-D_V4oaZh.js").then((n) => n.t);
|
|
1516
|
-
const { buildCollectionIndexPlan } = await import("./source-
|
|
1516
|
+
const { buildCollectionIndexPlan } = await import("./source-Bv5KPeIx.js").then((n) => n.d);
|
|
1517
1517
|
const names = new Set(buildCollectionIndexPlan(collections, resolveColumnName).map((spec) => spec.indexName));
|
|
1518
1518
|
for (const pattern of [...searchExcludePatterns(collections), ...vectorExcludePatterns(collections)]) {
|
|
1519
1519
|
const object = excludePatternParts(pattern)?.[2];
|
|
@@ -1649,6 +1649,27 @@ function isSslNotEnabled(err) {
|
|
|
1649
1649
|
return false;
|
|
1650
1650
|
}
|
|
1651
1651
|
/**
|
|
1652
|
+
* The database a connection was refused by because it is closed to new
|
|
1653
|
+
* connections (`ALLOW_CONNECTIONS false`), or null.
|
|
1654
|
+
*
|
|
1655
|
+
* Postgres says it as `database "x" is not currently accepting connections`,
|
|
1656
|
+
* SQLSTATE 55000. That code also covers failures that have nothing to do with
|
|
1657
|
+
* connecting (`lastval is not yet defined in this session`), so the sentence is
|
|
1658
|
+
* what is matched, through Drizzle's `cause` and a dual-stack `AggregateError`.
|
|
1659
|
+
*/
|
|
1660
|
+
function closedDatabaseName(err) {
|
|
1661
|
+
if (!err || typeof err !== "object") return null;
|
|
1662
|
+
if ("message" in err && typeof err.message === "string") {
|
|
1663
|
+
const named = /database "(.+)" is not currently accepting connections/.exec(err.message);
|
|
1664
|
+
if (named) return named[1];
|
|
1665
|
+
}
|
|
1666
|
+
if (err instanceof AggregateError) for (const inner of err.errors) {
|
|
1667
|
+
const named = closedDatabaseName(inner);
|
|
1668
|
+
if (named) return named;
|
|
1669
|
+
}
|
|
1670
|
+
return "cause" in err ? closedDatabaseName(err.cause) : null;
|
|
1671
|
+
}
|
|
1672
|
+
/**
|
|
1652
1673
|
* Detect PostgreSQL "cannot drop ... because other objects depend on it"
|
|
1653
1674
|
* (error code 2BP01, dependent_objects_still_exist). This is the failure that
|
|
1654
1675
|
* strands a declarative `db push` half-applied when a collection is removed but
|
|
@@ -1781,17 +1802,43 @@ function formatSslNotEnabledBanner(databaseUrl) {
|
|
|
1781
1802
|
❌ SSL is not enabled on the PostgreSQL server at ${hostInfo}\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\n The client tried to connect over SSL, but the server does not\n support it. This is normal for a plain local dev database.\n\n Fix: append ${chalk.bold("sslmode=disable")} to DATABASE_URL, e.g.\n\n DATABASE_URL=...${suggestion}\n\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n`;
|
|
1782
1803
|
}
|
|
1783
1804
|
/**
|
|
1805
|
+
* Format a diagnostic banner for a database closed to new connections.
|
|
1806
|
+
*
|
|
1807
|
+
* Nothing in a development database closes one but `rebase db branch create
|
|
1808
|
+
* --force`, which does it for the length of the copy; one killed before it
|
|
1809
|
+
* opened it again leaves the project's database refusing everything. Every
|
|
1810
|
+
* `rebase db branch` command opens it again before it does anything else, so
|
|
1811
|
+
* that is the command named — with the statement itself, for a server the CLI
|
|
1812
|
+
* cannot reach that way.
|
|
1813
|
+
*/
|
|
1814
|
+
function formatClosedDatabaseBanner(databaseUrl, database, err) {
|
|
1815
|
+
const hostInfo = parseHostInfo(databaseUrl);
|
|
1816
|
+
const reopen = databaseSource() === "flag" ? "rebase db branch list --database-url <the same URL>" : "rebase db branch list";
|
|
1817
|
+
return `
|
|
1818
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
1819
|
+
❌ Database "${database}" at ${hostInfo} is closed to new connections\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\n` + driverReasonLine(err) + ` It is set to refuse every new connection (ALLOW_CONNECTIONS false).
|
|
1820
|
+
\`rebase db branch create --force\` closes its source for the copy, and
|
|
1821
|
+
one stopped before it finished (a closed terminal, a kill) leaves it
|
|
1822
|
+
closed. Nothing in it is lost. Open it again with:
|
|
1823
|
+
|
|
1824
|
+
${chalk.bold.green(reopen)}\n (every rebase db branch command opens it before anything else)\n\n or run this, connected to the server's postgres database:\n\n ALTER DATABASE "${database.replace(/"/g, "\"\"")}" ALLOW_CONNECTIONS true;\n\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n`;
|
|
1825
|
+
}
|
|
1826
|
+
/**
|
|
1784
1827
|
* Format a diagnostic banner for a dependency-drop failure during `db push`.
|
|
1785
1828
|
* Explains that the database may be left partially migrated and how to recover.
|
|
1829
|
+
*
|
|
1830
|
+
* The DROP reads the connection string from `rebase db url`, as the NOT NULL
|
|
1831
|
+
* box's backfill does: on the managed development database `.env` leaves
|
|
1832
|
+
* `DATABASE_URL` unset, and the shell has none.
|
|
1786
1833
|
*/
|
|
1787
|
-
function formatDependencyDropBanner() {
|
|
1834
|
+
function formatDependencyDropBanner(manager = detectProjectPackageManager()) {
|
|
1788
1835
|
return `
|
|
1789
1836
|
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
1790
1837
|
❌ Schema push failed: a type/table could not be dropped
|
|
1791
1838
|
because other objects still depend on it.
|
|
1792
1839
|
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
1793
1840
|
|
|
1794
|
-
${chalk.yellow("The database may now be partially migrated.")} Atlas applies\n statements individually, so earlier changes in this push may\n already be committed while later ones failed.\n\n This commonly happens when a collection is removed but an enum\n type it defined is still referenced. To recover:\n\n 1. Inspect the leftover object named in the error above.\n 2. Drop it with CASCADE, e.g.:\n psql "$
|
|
1841
|
+
${chalk.yellow("The database may now be partially migrated.")} Atlas applies\n statements individually, so earlier changes in this push may\n already be committed while later ones failed.\n\n This commonly happens when a collection is removed but an enum\n type it defined is still referenced. To recover:\n\n 1. Inspect the leftover object named in the error above.\n 2. Drop it with CASCADE, e.g.:\n psql "$(${projectRebaseCommand(manager)} db url)" -c 'DROP TYPE "<name>" CASCADE;'\n 3. Re-run: ${chalk.bold.green("rebase db push")}\n\n Prefer a safe, versioned workflow? Use ${chalk.bold("rebase db generate")}\n + ${chalk.bold("rebase db migrate")} instead of push for destructive changes.\n\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n`;
|
|
1795
1842
|
}
|
|
1796
1843
|
/**
|
|
1797
1844
|
* Pre-flight check: verify that the database is reachable before running
|
|
@@ -1816,6 +1863,11 @@ async function checkDatabaseConnectivity(databaseUrl) {
|
|
|
1816
1863
|
outError(formatSslNotEnabledBanner(databaseUrl));
|
|
1817
1864
|
process.exit(1);
|
|
1818
1865
|
}
|
|
1866
|
+
const closed = closedDatabaseName(err);
|
|
1867
|
+
if (closed) {
|
|
1868
|
+
outError(formatClosedDatabaseBanner(databaseUrl, closed, err));
|
|
1869
|
+
process.exit(1);
|
|
1870
|
+
}
|
|
1819
1871
|
outWarn(chalk.yellow(` ⚠ Could not verify database connectivity: ${err instanceof Error ? err.message : String(err)}`));
|
|
1820
1872
|
outWarn(chalk.gray(" Proceeding anyway — the command may fail if the database is unreachable."));
|
|
1821
1873
|
}
|
|
@@ -1829,6 +1881,8 @@ function diagnoseDbError(err, databaseUrl) {
|
|
|
1829
1881
|
if (isEconnrefused(err)) return formatConnectionRefusedBanner(databaseUrl || "", err);
|
|
1830
1882
|
if (isAuthFailure(err)) return formatAuthFailureBanner(databaseUrl || "", err);
|
|
1831
1883
|
if (isSslNotEnabled(err)) return formatSslNotEnabledBanner(databaseUrl || "");
|
|
1884
|
+
const closed = closedDatabaseName(err);
|
|
1885
|
+
if (closed) return formatClosedDatabaseBanner(databaseUrl || "", closed, err);
|
|
1832
1886
|
if (isDependencyDropError(err)) return formatDependencyDropBanner();
|
|
1833
1887
|
return null;
|
|
1834
1888
|
}
|
|
@@ -1984,6 +2038,20 @@ function notNullColumnIsNew(stderr, column) {
|
|
|
1984
2038
|
return new RegExp(`ADD\\s+COLUMN\\s+(?:IF\\s+NOT\\s+EXISTS\\s+)?"${quoted}"`, "i").test(statement);
|
|
1985
2039
|
}
|
|
1986
2040
|
/**
|
|
2041
|
+
* `rebase`, spelled the way a project runs it from its own shell: a scaffold
|
|
2042
|
+
* keeps the CLI in `node_modules/.bin`, not on PATH, so it is `pnpm exec
|
|
2043
|
+
* rebase`, as `rebase start`'s hint and `rebase init`'s next steps spell it.
|
|
2044
|
+
*/
|
|
2045
|
+
function projectRebaseCommand(manager) {
|
|
2046
|
+
switch (manager) {
|
|
2047
|
+
case "pnpm": return "pnpm exec rebase";
|
|
2048
|
+
case "npm": return "npx rebase";
|
|
2049
|
+
case "yarn": return "yarn rebase";
|
|
2050
|
+
case "bun": return "bunx rebase";
|
|
2051
|
+
default: return "rebase";
|
|
2052
|
+
}
|
|
2053
|
+
}
|
|
2054
|
+
/**
|
|
1987
2055
|
* The ways out, and only the ones that work for this column.
|
|
1988
2056
|
*
|
|
1989
2057
|
* A `defaultValue` backfills only a column the push is adding: PostgreSQL
|
|
@@ -1996,10 +2064,10 @@ function notNullColumnIsNew(stderr, column) {
|
|
|
1996
2064
|
* only when the table is empty — so a push that dies here is strictly worse
|
|
1997
2065
|
* than the boot that would have run instead.
|
|
1998
2066
|
*/
|
|
1999
|
-
function formatNotNullViolationBanner(violation, rowCount, isNew = null) {
|
|
2067
|
+
function formatNotNullViolationBanner(violation, rowCount, isNew = null, manager = detectProjectPackageManager()) {
|
|
2000
2068
|
const { table, column } = violation;
|
|
2001
2069
|
const rows = rowCount === null ? ` "${table}" already holds rows, and none of them has a value for "${column}".\n` : ` "${table}" already holds ${rowCount} row${rowCount === 1 ? "" : "s"}, and they have no value for "${column}".\n`;
|
|
2002
|
-
const backfill = `Backfill by hand first, then push again:\n ${chalk.gray(`psql "$
|
|
2070
|
+
const backfill = `Backfill by hand first, then push again:\n ${chalk.gray(`psql "$(${projectRebaseCommand(manager)} db url)" -c 'UPDATE "${table}" SET "${column}" = … WHERE "${column}" IS NULL;'`)}\n`;
|
|
2003
2071
|
const leaveOptional = `Leave it optional — take \`validation.required\` off the property.\n`;
|
|
2004
2072
|
const defaultLine = (caveat) => `Give the property a default, so the push can backfill${caveat}:\n ${chalk.gray(`{ type: "string", defaultValue: "…", validation: { required: true } }`)}\n`;
|
|
2005
2073
|
const remedies = isNew === false ? [backfill, leaveOptional] : isNew === true ? [
|
|
@@ -2288,6 +2356,6 @@ async function diagnoseAtlasFailure(context) {
|
|
|
2288
2356
|
return null;
|
|
2289
2357
|
}
|
|
2290
2358
|
//#endregion
|
|
2291
|
-
export {
|
|
2359
|
+
export { forLibpq as $, getUndeclaredConstraintExcludes as A, withAppClient as B, dropDevDatabase as C, getSearchExcludes as D, getForeignIndexExcludes as E, queryUndeclarableConstraints as F, prepareGeneratedSqlDir as G, DESIRED_STATE_URL as H, readSearchDdl as I, detectDestructiveStatements as J, decidePushSafety as K, readTriggersDdl as L, loadCollectionsForCli as M, queryColumnTypes as N, getTableExcludes as O, queryGeneratedColumnDependencies as P, forAtlas as Q, readVectorDdl as R, diagnoseMissingBin as S, ensureDevDatabaseExists as T, GENERATED_SQL_DIR as U, workspaceMemberDir as V, generatedSqlDir as W, fromAlterTable as X, extractPlanStatements as Y, isSyncedPlan as Z, closeAppClients as _, diagnoseDbError as a, describeReinstallCommand as b, isEconnrefused as c, reportCommandFailure as d, redactConnectionPasswords as et, ExcludeIntrospectionError as f, atlasDevDatabaseUrl as g, applyVectorDdl as h, diagnoseAtlasFailure as i, getVectorExcludes as j, getTriggerExcludes as k, isSslNotEnabled as l, applyTriggersDdl as m, closedDatabaseName as n, extractCauseMessage as nt, formatForeignGeneratedColumnBanner as o, applySearchDdl as p, destructiveOptInFlag as q, deepestErrorMessage as r, isAuthFailure as s, checkDatabaseConnectivity as t, splitPassword as tt, parseHostInfo as u, describeBuildScriptRemedy as v, dropGeneratedColumns as w, detectProjectPackageManager as x, describeDevAddCommand as y, resolveLocalBin as z };
|
|
2292
2360
|
|
|
2293
|
-
//# sourceMappingURL=cli-errors-
|
|
2361
|
+
//# sourceMappingURL=cli-errors-DwQnNATs.js.map
|