@rebasepro/server-postgres 0.24.0 → 0.24.1-canary.g19596ef
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/README.md +16 -0
- package/dist/{BranchService-BRt78gfa.js → BranchService-UgKa416x.js} +14 -3
- package/dist/BranchService-UgKa416x.js.map +1 -0
- package/dist/PostgresBackendDriver.d.ts +104 -2
- package/dist/PostgresBootstrapper.d.ts +101 -2
- package/dist/auth/ensure-tables.d.ts +12 -0
- package/dist/auth/services.d.ts +6 -0
- package/dist/backup/backup-cli.d.ts +75 -2
- package/dist/backup/backup-service.d.ts +2 -2
- package/dist/backup/pg-binary.d.ts +5 -0
- package/dist/{backup-cli-DW5p9_zv.js → backup-cli-yiKd1GsC.js} +168 -75
- package/dist/backup-cli-yiKd1GsC.js.map +1 -0
- package/dist/{backup-service-EwcDVG-8.js → backup-service-AZWc2wvI.js} +99 -28
- package/dist/backup-service-AZWc2wvI.js.map +1 -0
- package/dist/boot-ddl-B9ZmBJ75.js +105 -0
- package/dist/boot-ddl-B9ZmBJ75.js.map +1 -0
- package/dist/cli-console-DGee3Zl7.js +225 -0
- package/dist/cli-console-DGee3Zl7.js.map +1 -0
- package/dist/cli-console.d.ts +39 -0
- package/dist/cli-errors-oVUuD8k0.js +2180 -0
- package/dist/cli-errors-oVUuD8k0.js.map +1 -0
- package/dist/cli-errors.d.ts +91 -9
- package/dist/{cli-flags-BglvpjHv.js → cli-flags-DnPHXjRH.js} +56 -6
- package/dist/cli-flags-DnPHXjRH.js.map +1 -0
- package/dist/cli-flags.d.ts +15 -0
- package/dist/cli-helpers.d.ts +76 -22
- package/dist/cli-output.d.ts +16 -28
- package/dist/{arg-Dni7MzLB.js → cli-prompt-egtNKkZl.js} +49 -3
- package/dist/cli-prompt-egtNKkZl.js.map +1 -0
- package/dist/cli-prompt.d.ts +19 -0
- package/dist/cli-push.d.ts +87 -0
- package/dist/cli-scratch-database.d.ts +33 -8
- package/dist/cli.js +764 -222
- package/dist/cli.js.map +1 -1
- package/dist/{column-plan-helpers-1-LQD0yI.js → column-plan-helpers-D_V4oaZh.js} +11 -5
- package/dist/{column-plan-helpers-1-LQD0yI.js.map → column-plan-helpers-D_V4oaZh.js.map} +1 -1
- package/dist/{databasePoolManager-Bj5FbeAs.js → connection-C938nR3E.js} +25 -113
- package/dist/connection-C938nR3E.js.map +1 -0
- package/dist/connection.d.ts +13 -2
- package/dist/databasePoolManager-DZgnBOgj.js +119 -0
- package/dist/databasePoolManager-DZgnBOgj.js.map +1 -0
- package/dist/{ensure-collection-tables-kkkHk8oo.js → deferred-index-build-BqmEHGaD.js} +485 -347
- package/dist/deferred-index-build-BqmEHGaD.js.map +1 -0
- package/dist/{doctor-C-sYWbmt.js → doctor-CUAAOFZm.js} +65 -54
- package/dist/doctor-CUAAOFZm.js.map +1 -0
- package/dist/ensure-collection-policies-Bb51V2OV.js +541 -0
- package/dist/ensure-collection-policies-Bb51V2OV.js.map +1 -0
- package/dist/ensure-collection-tables-egjGBjhv.js +532 -0
- package/dist/ensure-collection-tables-egjGBjhv.js.map +1 -0
- package/dist/{ensure-tables-BmI_tRxc.js → ensure-tables-DMPWhJ6R.js} +174 -78
- package/dist/ensure-tables-DMPWhJ6R.js.map +1 -0
- package/dist/enum-labels-kept-Msq5gMcb.js +56 -0
- package/dist/enum-labels-kept-Msq5gMcb.js.map +1 -0
- package/dist/generate-drizzle-schema-DrA-YgXv.js +57 -0
- package/dist/generate-drizzle-schema-DrA-YgXv.js.map +1 -0
- package/dist/{generate-drizzle-schema-logic-sSFDp6LR.js → generate-drizzle-schema-logic-BaT0H7Gq.js} +6 -6
- package/dist/generate-drizzle-schema-logic-BaT0H7Gq.js.map +1 -0
- package/dist/generate-files-OoRJLEdk.js +199 -0
- package/dist/generate-files-OoRJLEdk.js.map +1 -0
- package/dist/{generate-postgres-ddl-logic-BJsLaVNX.js → generate-postgres-ddl-logic-B4k9djAV.js} +4 -4
- package/dist/{generate-postgres-ddl-logic-BJsLaVNX.js.map → generate-postgres-ddl-logic-B4k9djAV.js.map} +1 -1
- package/dist/{generated-schema-staleness-Di9c4ZxN.js → generated-schema-staleness-BWN_PsMi.js} +2 -2
- package/dist/{generated-schema-staleness-Di9c4ZxN.js.map → generated-schema-staleness-BWN_PsMi.js.map} +1 -1
- package/dist/generated-sql.d.ts +8 -2
- package/dist/history/HistoryService.d.ts +64 -5
- package/dist/history/ensure-history-table.d.ts +63 -4
- package/dist/index.d.ts +1 -0
- package/dist/index.es.js +139 -19236
- package/dist/index.es.js.map +1 -1
- package/dist/{introspect-db-logic-kCETE8TY.js → introspect-db-logic-BCc1Jym2.js} +136 -9
- package/dist/introspect-db-logic-BCc1Jym2.js.map +1 -0
- package/dist/{introspect-db-queries-C_Q5VgQw.js → introspect-db-queries-BhhJg0b8.js} +48 -1
- package/dist/introspect-db-queries-BhhJg0b8.js.map +1 -0
- package/dist/open-rebase-CAjWpPjs.js +20895 -0
- package/dist/open-rebase-CAjWpPjs.js.map +1 -0
- package/dist/open-rebase.d.ts +94 -0
- package/dist/pg-error-utils-Bs_Ue4wn.js +395 -0
- package/dist/pg-error-utils-Bs_Ue4wn.js.map +1 -0
- package/dist/{plan-schema-DU9exq6C.js → plan-schema-CsNgK0qi.js} +152 -42
- package/dist/plan-schema-CsNgK0qi.js.map +1 -0
- package/dist/{policy-drift-B-J2hhm0.js → policy-drift-BKC50y2p.js} +32 -8
- package/dist/policy-drift-BKC50y2p.js.map +1 -0
- package/dist/push-index-prebuild-C_qhyoJa.js +45 -0
- package/dist/push-index-prebuild-C_qhyoJa.js.map +1 -0
- package/dist/push-legacy-renames-5g9h3HOa.js +34 -0
- package/dist/push-legacy-renames-5g9h3HOa.js.map +1 -0
- package/dist/{render-ddl-Ds2t_d9V.js → render-ddl-CrfEe__B.js} +15 -10
- package/dist/render-ddl-CrfEe__B.js.map +1 -0
- package/dist/{rls-bootstrap-sql-_KNnjanK.js → rls-bootstrap-sql-Bd-hJk08.js} +125 -1325
- package/dist/rls-bootstrap-sql-Bd-hJk08.js.map +1 -0
- package/dist/{rls-enforcement-CfXOJJaW.js → rls-enforcement-D7P4-ZYX.js} +130 -18
- package/dist/rls-enforcement-D7P4-ZYX.js.map +1 -0
- package/dist/{__vite-browser-external-BnuHet1e.js → rolldown-runtime-V7rFbMRe.js} +1 -8
- package/dist/schema/atlas-exclude.d.ts +42 -0
- package/dist/schema/auth-schema.d.ts +2 -2
- package/dist/schema/boot-ddl.d.ts +47 -0
- package/dist/schema/carved-out-migration.d.ts +5 -4
- package/dist/schema/collection-index.d.ts +45 -0
- package/dist/schema/column-plan-helpers.d.ts +5 -0
- package/dist/schema/deferred-index-build.d.ts +131 -0
- package/dist/schema/destructive-sql.d.ts +30 -1
- package/dist/schema/doctor-cli.js +110 -6
- package/dist/schema/doctor-cli.js.map +1 -1
- package/dist/schema/doctor-search-scale.d.ts +45 -0
- package/dist/schema/doctor.d.ts +20 -1
- package/dist/schema/ensure-collection-policies.d.ts +73 -35
- package/dist/schema/ensure-collection-tables.d.ts +56 -3
- package/dist/schema/enum-labels-kept.d.ts +26 -0
- package/dist/schema/generate-drizzle-schema.js +1 -1
- package/dist/schema/generate-files.d.ts +44 -0
- package/dist/schema/generate-next-step.d.ts +0 -13
- package/dist/schema/generate-postgres-ddl.js +10 -81
- package/dist/schema/generate-postgres-ddl.js.map +1 -1
- package/dist/schema/generated-schema-diff.d.ts +7 -0
- package/dist/schema/introspect-db-logic.d.ts +32 -0
- package/dist/schema/introspect-db-queries.d.ts +20 -1
- package/dist/schema/introspect-db-structure.d.ts +10 -0
- package/dist/schema/introspect-db.js +16 -20
- package/dist/schema/introspect-db.js.map +1 -1
- package/dist/schema/migrate-baseline.d.ts +33 -0
- package/dist/schema/migration-post-process.d.ts +34 -0
- package/dist/schema/plan/diff-plan.d.ts +126 -0
- package/dist/schema/plan/render-ddl.d.ts +2 -1
- package/dist/schema/plan/types.d.ts +8 -0
- package/dist/schema/push-index-prebuild.d.ts +25 -0
- package/dist/schema/push-legacy-renames.d.ts +20 -0
- package/dist/schema/rls-bootstrap-sql.d.ts +15 -0
- package/dist/schema/undeclarable-constraints.d.ts +59 -0
- package/dist/security/rls-enforcement.d.ts +32 -4
- package/dist/services/BranchService.d.ts +7 -0
- package/dist/services/FetchService.d.ts +58 -8
- package/dist/services/PersistService.d.ts +15 -0
- package/dist/services/RelationService.d.ts +88 -3
- package/dist/services/cdc/CdcListener.d.ts +17 -6
- package/dist/services/cdc/trigger-cdc.d.ts +151 -27
- package/dist/services/channel-bus/index.d.ts +1 -1
- package/dist/services/channel-history.d.ts +15 -4
- package/dist/services/collection-helpers.d.ts +15 -0
- package/dist/services/field-viewer.d.ts +15 -1
- package/dist/services/pg-notify-listener.d.ts +7 -0
- package/dist/services/realtimeService.d.ts +314 -14
- package/dist/services/row-pipeline.d.ts +16 -9
- package/dist/services/socket-liveness.d.ts +42 -2
- package/dist/services/sql-script.d.ts +207 -3
- package/dist/services/vector-iterative-scan.d.ts +40 -0
- package/dist/services/write-depth.d.ts +6 -0
- package/dist/services/write-transaction-scope.d.ts +9 -0
- package/dist/socket-opt-ins.d.ts +64 -0
- package/dist/source-DB6xJv3p.js +873 -0
- package/dist/source-DB6xJv3p.js.map +1 -0
- package/dist/tsx-loader.d.ts +14 -0
- package/dist/utils/connection-string.d.ts +31 -0
- package/dist/utils/drizzle-conditions.d.ts +21 -1
- package/dist/utils/error-cause.d.ts +7 -0
- package/dist/utils/pg-error-utils.d.ts +2 -4
- package/dist/utils/result-rows.d.ts +10 -0
- package/dist/{vector-index-CMJKb3C9.js → vector-index-BFhyEaiz.js} +2 -2
- package/dist/{vector-index-CMJKb3C9.js.map → vector-index-BFhyEaiz.js.map} +1 -1
- package/dist/websocket-DUiiwQd2.js +1632 -0
- package/dist/websocket-DUiiwQd2.js.map +1 -0
- package/dist/websocket.d.ts +13 -0
- package/package.json +7 -7
- package/dist/BranchService-BRt78gfa.js.map +0 -1
- package/dist/__vite-browser-external-BnuHet1e.js.map +0 -1
- package/dist/arg-Dni7MzLB.js.map +0 -1
- package/dist/backup-cli-DW5p9_zv.js.map +0 -1
- package/dist/backup-service-EwcDVG-8.js.map +0 -1
- package/dist/cli-errors-DsA-K9uP.js +0 -893
- package/dist/cli-errors-DsA-K9uP.js.map +0 -1
- package/dist/cli-flags-BglvpjHv.js.map +0 -1
- package/dist/cli-output-CNdMql-L.js +0 -47
- package/dist/cli-output-CNdMql-L.js.map +0 -1
- package/dist/collection-index-DdnsxiJ_.js +0 -286
- package/dist/collection-index-DdnsxiJ_.js.map +0 -1
- package/dist/databasePoolManager-Bj5FbeAs.js.map +0 -1
- package/dist/doctor-C-sYWbmt.js.map +0 -1
- package/dist/ensure-collection-policies-B1ureSIV.js +0 -160
- package/dist/ensure-collection-policies-B1ureSIV.js.map +0 -1
- package/dist/ensure-collection-tables-kkkHk8oo.js.map +0 -1
- package/dist/ensure-tables-BmI_tRxc.js.map +0 -1
- package/dist/generate-drizzle-schema-93M0lUxK.js +0 -123
- package/dist/generate-drizzle-schema-93M0lUxK.js.map +0 -1
- package/dist/generate-drizzle-schema-logic-sSFDp6LR.js.map +0 -1
- package/dist/introspect-db-logic-kCETE8TY.js.map +0 -1
- package/dist/introspect-db-queries-C_Q5VgQw.js.map +0 -1
- package/dist/plan-schema-DU9exq6C.js.map +0 -1
- package/dist/policy-drift-B-J2hhm0.js.map +0 -1
- package/dist/render-ddl-Ds2t_d9V.js.map +0 -1
- package/dist/rls-bootstrap-sql-_KNnjanK.js.map +0 -1
- package/dist/rls-enforcement-CfXOJJaW.js.map +0 -1
- package/dist/source-Br7L7GOI.js +0 -410
- package/dist/source-Br7L7GOI.js.map +0 -1
- package/dist/utils/sql-redaction.d.ts +0 -21
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { createRequire as __createRequire } from "module";
|
|
2
2
|
__createRequire(import.meta.url);
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
3
|
+
import { i as __toESM } from "./rolldown-runtime-V7rFbMRe.js";
|
|
4
|
+
import { n as require_arg } from "./cli-prompt-egtNKkZl.js";
|
|
5
|
+
import { suggestNearMiss } from "@rebasepro/utils";
|
|
5
6
|
//#region src/cli-flags.ts
|
|
6
7
|
var import_arg = /* @__PURE__ */ __toESM(require_arg(), 1);
|
|
7
8
|
/**
|
|
@@ -54,7 +55,10 @@ var DRIVER_FLAG_SPECS = {
|
|
|
54
55
|
"--collections": String,
|
|
55
56
|
"-c": "--collections"
|
|
56
57
|
},
|
|
57
|
-
"db migrate": {
|
|
58
|
+
"db migrate": {
|
|
59
|
+
"--baseline": String,
|
|
60
|
+
"--refuse-destructive": Boolean
|
|
61
|
+
},
|
|
58
62
|
"schema generate": {
|
|
59
63
|
"--collections": String,
|
|
60
64
|
"-c": "--collections",
|
|
@@ -128,11 +132,57 @@ function assertKnownFlags(domain, subcommand, args) {
|
|
|
128
132
|
...spec
|
|
129
133
|
}, { argv: args.slice(2) });
|
|
130
134
|
} catch (err) {
|
|
131
|
-
|
|
135
|
+
const unknown = unknownFlagIn(err);
|
|
136
|
+
if (unknown !== null && err instanceof Error) {
|
|
137
|
+
const suggestion = closestFlag(unknown, {
|
|
138
|
+
...RELAYED_FLAGS,
|
|
139
|
+
...spec
|
|
140
|
+
});
|
|
141
|
+
throw new Error(`${err.message}${suggestion ? ` (did you mean ${suggestion}?)` : ""} — run \`rebase ${domain} --help\` for the options \`${domain} ${subcommand}\` takes.`);
|
|
142
|
+
}
|
|
132
143
|
throw err;
|
|
133
144
|
}
|
|
134
145
|
}
|
|
146
|
+
/** The flag `arg` refused as undeclared, or `null` for any other failure. */
|
|
147
|
+
function unknownFlagIn(err) {
|
|
148
|
+
if (!(err instanceof Error) || !("code" in err) || err.code !== "ARG_UNKNOWN_OPTION") return null;
|
|
149
|
+
return /option: (\S+)$/.exec(err.message)?.[1] ?? null;
|
|
150
|
+
}
|
|
151
|
+
/** The declared long flag `typed` is one edit away from, if there is one. */
|
|
152
|
+
function closestFlag(typed, spec) {
|
|
153
|
+
const name = typed.split("=")[0];
|
|
154
|
+
return suggestNearMiss(Object.keys(spec).filter((flag) => flag.startsWith("--")), name);
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Parse a command that owns its argument handling, refusing what it does not
|
|
158
|
+
* declare.
|
|
159
|
+
*
|
|
160
|
+
* `db backup`, `db restore` and `db backups list` parsed permissively, so an
|
|
161
|
+
* undeclared flag became a positional and was ignored: `db restore <dump>
|
|
162
|
+
* --targt-db x --create-db --yes` dropped the misspelled target and restored
|
|
163
|
+
* into the live database. A typo is refused here before anything runs, naming
|
|
164
|
+
* the declared flag it is one edit from, or every flag the command takes when
|
|
165
|
+
* none is that close.
|
|
166
|
+
*
|
|
167
|
+
* @param argv the words after the command, e.g. `rawArgs.slice(2)`.
|
|
168
|
+
* @param command the command as typed, for the message: `"db restore"`.
|
|
169
|
+
*/
|
|
170
|
+
function parseCommandFlags(spec, argv, command) {
|
|
171
|
+
const full = {
|
|
172
|
+
...RELAYED_FLAGS,
|
|
173
|
+
...spec
|
|
174
|
+
};
|
|
175
|
+
try {
|
|
176
|
+
return (0, import_arg.default)(full, { argv });
|
|
177
|
+
} catch (err) {
|
|
178
|
+
const unknown = unknownFlagIn(err);
|
|
179
|
+
if (unknown === null) throw err;
|
|
180
|
+
const suggestion = closestFlag(unknown, full);
|
|
181
|
+
const flags = Object.keys(spec).filter((flag) => flag.startsWith("--"));
|
|
182
|
+
throw new Error(`\`rebase ${command}\` has no option ${unknown}` + (suggestion ? ` — did you mean ${suggestion}?` : ` — it takes ${flags.join(", ")}.`) + ` Nothing was run. See \`rebase ${command} --help\`.`);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
135
185
|
//#endregion
|
|
136
|
-
export {
|
|
186
|
+
export { parseDriverLine as a, parseCommandFlags as i, assertKnownFlags as n, collectionsPathIn as r, RELAYED_FLAGS as t };
|
|
137
187
|
|
|
138
|
-
//# sourceMappingURL=cli-flags-
|
|
188
|
+
//# sourceMappingURL=cli-flags-DnPHXjRH.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli-flags-DnPHXjRH.js","names":[],"sources":["../src/cli-flags.ts"],"sourcesContent":["/**\n * The flags each driver command takes, in one place, and the check that\n * enforces them.\n *\n * Every parser in `cli.ts` runs `arg(..., { permissive: true })`, which does not\n * mean \"be lenient\" — it means **an undeclared flag becomes a positional**. So\n * `rebase db push --alow-destructive` did not fail: the typo landed in `_`, the\n * push ran with the destructive gate still closed, and the developer read the\n * refusal as Rebase ignoring the flag they had just typed. `rebase schema\n * generate --ouput src/schema.ts` was worse — it wrote the default path and\n * said nothing, so the next build compiled a file nobody had regenerated.\n *\n * The check has to live at the entry point rather than in those parsers, and\n * that is not a detail: `db push` and `db generate` re-enter `schemaCommand`\n * and `generatePostgresDdlCommand` with the *db* line, so a strict parser\n * inside either of them would reject `--allow-destructive` on a line where it\n * is correct. One validation, once, against the spec for the command the user\n * actually named; the inner parsers keep reading an already-validated line.\n *\n * Commands with their own argument handling — `db branch` (`unexpectedBranchArgs`),\n * `db backup`, `db restore`, `db backups` — are deliberately absent: they own\n * their spec, and a second list here would be one more thing to keep in step.\n * An absent entry is \"not checked here\", never \"takes nothing\".\n */\nimport arg from \"arg\";\nimport { suggestNearMiss } from \"@rebasepro/utils\";\n\n/**\n * Flags that reach this process on a line it did not compose.\n *\n * `rebase db …` hands the driver the user's whole line: the CLI *reads*\n * `--database-url` and `--docker` to resolve the database and then relays them\n * verbatim, and `--debug` is what `bin/rebase.js` prints after every failure as\n * the thing to re-run with. Rejecting any of the three would make the driver\n * refuse commands the CLI documents.\n */\nexport const RELAYED_FLAGS = {\n \"--database-url\": String,\n \"--docker\": Boolean,\n \"--debug\": Boolean,\n \"--help\": Boolean,\n \"-h\": \"--help\"\n} satisfies arg.Spec;\n\n/**\n * Parse a driver command's line — `[\"db\", \"<subcommand>\", …]` — with the\n * relayed flags declared alongside the command's own.\n *\n * Every driver parser is permissive, and an undeclared flag in a permissive\n * parse is a positional. The positionals are what `db migrate` hands\n * `atlas migrate apply`, what `db generate` names its migration after and\n * which backup `db restore` restores, so a relayed flag left undeclared was\n * read as one of those: `rebase db migrate --docker` died on Atlas's\n * `unknown flag: --docker` after it had already written the auth schema.\n */\nexport function parseDriverLine<S extends arg.Spec>(\n spec: S,\n args: string[]\n): arg.Result<typeof RELAYED_FLAGS & S> {\n return arg({ ...RELAYED_FLAGS, ...spec }, { argv: args.slice(2), permissive: true });\n}\n\n/** Keyed by `\"<domain> <subcommand>\"`, the way the user types it. */\nexport const DRIVER_FLAG_SPECS: Record<string, arg.Spec> = {\n \"db push\": {\n \"--collections\": String,\n \"-c\": \"--collections\",\n \"--dry-run\": Boolean,\n \"--allow-destructive\": Boolean,\n \"--yes\": Boolean,\n \"-y\": \"--yes\"\n },\n \"db generate\": {\n \"--collections\": String,\n \"-c\": \"--collections\"\n },\n // Bare positionals still pass through to `atlas migrate apply`, which takes\n // an optional amount; flags do not, apart from `--baseline`.\n //\n // `--baseline <version>` is the escape hatch for the normal case, not an\n // exotic one: every Rebase boot ensures the schema, so a production\n // database has the tables before its first migration ever runs, and\n // `migrate apply` then dies on `type \"…\" already exists (42710)`. It maps\n // onto Atlas's own `migrate apply --baseline`, which writes the revision\n // row Atlas reads — no ledger of ours.\n //\n // `--refuse-destructive` is opt-in: it dry-runs the pending files and exits\n // 1, applying nothing, when one drops or narrows data — the gate `db push`\n // has always had, for the path that had none.\n \"db migrate\": {\n \"--baseline\": String,\n \"--refuse-destructive\": Boolean\n },\n \"schema generate\": {\n \"--collections\": String,\n \"-c\": \"--collections\",\n \"--output\": String,\n \"-o\": \"--output\",\n \"--out\": \"--output\",\n \"--watch\": Boolean,\n \"-w\": \"--watch\"\n },\n \"schema introspect\": {\n \"--output\": String,\n \"-o\": \"--output\",\n \"--out\": \"--output\",\n \"--collections\": String,\n \"-c\": \"--collections\",\n \"--force\": Boolean,\n \"-f\": \"--force\",\n \"--schema\": String\n },\n \"schema stale\": {\n \"--collections\": String,\n \"-c\": \"--collections\",\n \"--output\": String,\n \"-o\": \"--output\",\n \"--out\": \"--output\",\n \"--fix\": Boolean\n }\n};\n\n/**\n * One destination flag, two spellings, everywhere.\n *\n * `--out` is the primary on `rebase build`, an alias on `generate-sdk`, `db\n * backup` and `cloud env pull`, and was refused outright by the three `schema`\n * commands — so the spelling a user learned on one command was an\n * \"unknown or unexpected option\" on the next. Neither name can be retired (both\n * are shipped), so both are accepted, and {@link assertOutputAliasesPaired}\n * makes that the rule rather than a habit.\n */\nexport const OUTPUT_FLAG_ALIASES = [\"--out\", \"--output\"] as const;\n\n/**\n * Every spec that names one of the pair names both.\n *\n * Exported so the CLI's own specs can be held to it too: the drift this fixes\n * ran across two packages, and a check that only reads this file would let the\n * next `--out`-only command through.\n */\nexport function assertOutputAliasesPaired(specs: Record<string, arg.Spec>): string[] {\n const problems: string[] = [];\n for (const [command, spec] of Object.entries(specs)) {\n const present = OUTPUT_FLAG_ALIASES.filter(flag => flag in spec);\n if (present.length === 0 || present.length === OUTPUT_FLAG_ALIASES.length) continue;\n const missing = OUTPUT_FLAG_ALIASES.filter(flag => !(flag in spec));\n problems.push(`\\`${command}\\` takes ${present.join(\", \")} but not ${missing.join(\", \")}`);\n }\n return problems;\n}\n\n/**\n * The `--collections` path the user typed, or `null` if they typed none.\n *\n * Read once, at the entry point, because every command that takes it re-enters\n * the generators with the same line and each of them resolved it again — which\n * is how \"Collections path not found\" came to be printed four times before the\n * real output, and how a path that does not exist got as far as *writing* an\n * empty schema.\n *\n * Permissive, and it has to be: {@link assertKnownFlags} has already judged the\n * line, and `db push` carries flags the `--collections` spec does not name.\n */\nexport function collectionsPathIn(args: string[]): string | null {\n try {\n const parsed = arg(\n { \"--collections\": String, \"-c\": \"--collections\" },\n { argv: args.slice(2), permissive: true }\n );\n\n return parsed[\"--collections\"] ?? null;\n } catch {\n return null;\n }\n}\n\n/**\n * The long flags a `--help` usage line documents.\n *\n * `\"rebase db push [--collections <dir>] [--dry-run] …\"` → `[\"--collections\",\n * \"--dry-run\"]`. Placeholders (`<dir>`) and the alternation inside a positional\n * (`<create|list|switch>`) are not flags and are not returned.\n */\nexport function flagsInUsage(usage: string): string[] {\n return [...new Set(usage.match(/--[a-z][a-z0-9-]*/g) ?? [])];\n}\n\n/**\n * Every flag the help documents is accepted, and every flag accepted is documented.\n *\n * The drift this catches shipped: `rebase db push --help` has printed\n * `[--dry-run]` since the flag was written, `DRIVER_FLAG_SPECS[\"db push\"]` never\n * listed it, and {@link assertKnownFlags} — added later to stop typos being\n * swallowed — turned the documented flag into `unknown or unexpected option`.\n * The only way to see a push's SQL was to trip the destructive gate, which is\n * the exact problem `--dry-run` was written to solve. Worse in a project on an\n * older driver, whose permissive parser *applied* the schema on that line.\n *\n * Two hand-maintained lists in two packages cannot be kept in step by care, so\n * they are held to each other instead: `usages` is keyed the way\n * {@link DRIVER_FLAG_SPECS} is (`\"db push\"`), and comes from the CLI's own help\n * pages. A key in only one of the two is not this function's business — the\n * help covers commands that parse their own lines (`db branch`, `db backup`),\n * and an absent spec entry means \"not checked here\".\n *\n * Aliases (`\"-c\": \"--collections\"`, `\"--out\": \"--output\"`) need no line of their\n * own: they are spellings of a documented flag. Neither do {@link RELAYED_FLAGS},\n * which every driver command accepts because the CLI relays them.\n */\nexport function assertSpecMatchesUsage(\n specs: Record<string, arg.Spec>,\n usages: Record<string, string>\n): string[] {\n const problems: string[] = [];\n for (const [command, spec] of Object.entries(specs)) {\n const usage = usages[command];\n if (usage === undefined) continue;\n\n const documented = flagsInUsage(usage);\n const accepted = Object.entries(spec);\n // An alias resolves to the flag it spells; only the target needs a line.\n const canonical = new Set(\n accepted\n .map(([flag, kind]) => (typeof kind === \"string\" ? kind : flag))\n .filter(flag => flag.startsWith(\"--\"))\n );\n\n for (const flag of documented) {\n if (flag in spec || flag in RELAYED_FLAGS) continue;\n problems.push(`\\`${command}\\` documents ${flag} in its usage line but the spec rejects it`);\n }\n for (const flag of canonical) {\n if (documented.includes(flag) || flag in RELAYED_FLAGS) continue;\n problems.push(`\\`${command}\\` accepts ${flag} but no usage line documents it`);\n }\n }\n return problems;\n}\n\n/**\n * Reject a flag the named command does not take.\n *\n * `args` is the driver's whole line — `[\"db\", \"push\", …]` — so the flags are\n * read from `args.slice(2)`, past the two command words.\n *\n * Throws rather than exiting: `runPluginCommand`'s caller already turns a\n * thrown error into one red line and exit 1, and a thrown error is what the\n * tests can see.\n */\nexport function assertKnownFlags(domain: string, subcommand: string | undefined, args: string[]): void {\n if (!subcommand) return;\n const spec = DRIVER_FLAG_SPECS[`${domain} ${subcommand}`];\n if (!spec) return;\n\n try {\n arg({ ...RELAYED_FLAGS, ...spec }, { argv: args.slice(2) });\n } catch (err) {\n const unknown = unknownFlagIn(err);\n if (unknown !== null && err instanceof Error) {\n const suggestion = closestFlag(unknown, { ...RELAYED_FLAGS, ...spec });\n throw new Error(\n `${err.message}${suggestion ? ` (did you mean ${suggestion}?)` : \"\"} — run ` +\n `\\`rebase ${domain} --help\\` for the options \\`${domain} ${subcommand}\\` takes.`\n );\n }\n throw err;\n }\n}\n\n/** The flag `arg` refused as undeclared, or `null` for any other failure. */\nfunction unknownFlagIn(err: unknown): string | null {\n if (!(err instanceof Error) || !(\"code\" in err) || err.code !== \"ARG_UNKNOWN_OPTION\") return null;\n // arg's wording: `unknown or unexpected option: --targt-db`.\n return /option: (\\S+)$/.exec(err.message)?.[1] ?? null;\n}\n\n/** The declared long flag `typed` is one edit away from, if there is one. */\nfunction closestFlag(typed: string, spec: arg.Spec): string | undefined {\n const name = typed.split(\"=\")[0];\n return suggestNearMiss(Object.keys(spec).filter(flag => flag.startsWith(\"--\")), name);\n}\n\n/**\n * Parse a command that owns its argument handling, refusing what it does not\n * declare.\n *\n * `db backup`, `db restore` and `db backups list` parsed permissively, so an\n * undeclared flag became a positional and was ignored: `db restore <dump>\n * --targt-db x --create-db --yes` dropped the misspelled target and restored\n * into the live database. A typo is refused here before anything runs, naming\n * the declared flag it is one edit from, or every flag the command takes when\n * none is that close.\n *\n * @param argv the words after the command, e.g. `rawArgs.slice(2)`.\n * @param command the command as typed, for the message: `\"db restore\"`.\n */\nexport function parseCommandFlags<S extends arg.Spec>(\n spec: S,\n argv: string[],\n command: string\n): arg.Result<typeof RELAYED_FLAGS & S> {\n const full = { ...RELAYED_FLAGS, ...spec };\n try {\n return arg(full, { argv });\n } catch (err) {\n const unknown = unknownFlagIn(err);\n if (unknown === null) throw err;\n const suggestion = closestFlag(unknown, full);\n const flags = Object.keys(spec).filter(flag => flag.startsWith(\"--\"));\n throw new Error(\n `\\`rebase ${command}\\` has no option ${unknown}` +\n (suggestion ? ` — did you mean ${suggestion}?` : ` — it takes ${flags.join(\", \")}.`) +\n ` Nothing was run. See \\`rebase ${command} --help\\`.`\n );\n }\n}\n"],"mappings":"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"}
|
package/dist/cli-flags.d.ts
CHANGED
|
@@ -127,3 +127,18 @@ export declare function assertSpecMatchesUsage(specs: Record<string, arg.Spec>,
|
|
|
127
127
|
* tests can see.
|
|
128
128
|
*/
|
|
129
129
|
export declare function assertKnownFlags(domain: string, subcommand: string | undefined, args: string[]): void;
|
|
130
|
+
/**
|
|
131
|
+
* Parse a command that owns its argument handling, refusing what it does not
|
|
132
|
+
* declare.
|
|
133
|
+
*
|
|
134
|
+
* `db backup`, `db restore` and `db backups list` parsed permissively, so an
|
|
135
|
+
* undeclared flag became a positional and was ignored: `db restore <dump>
|
|
136
|
+
* --targt-db x --create-db --yes` dropped the misspelled target and restored
|
|
137
|
+
* into the live database. A typo is refused here before anything runs, naming
|
|
138
|
+
* the declared flag it is one edit from, or every flag the command takes when
|
|
139
|
+
* none is that close.
|
|
140
|
+
*
|
|
141
|
+
* @param argv the words after the command, e.g. `rawArgs.slice(2)`.
|
|
142
|
+
* @param command the command as typed, for the message: `"db restore"`.
|
|
143
|
+
*/
|
|
144
|
+
export declare function parseCommandFlags<S extends arg.Spec>(spec: S, argv: string[], command: string): arg.Result<typeof RELAYED_FLAGS & S>;
|
package/dist/cli-helpers.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { GeneratedColumnDependency } from "./schema/generated-column-conflicts.js";
|
|
2
2
|
import { type ExistingColumnType } from "./schema/destructive-sql.js";
|
|
3
|
+
import { type ExistingConstraint } from "./schema/undeclarable-constraints.js";
|
|
3
4
|
import type { CollectionConfig } from "@rebasepro/types";
|
|
4
5
|
/**
|
|
5
6
|
* Why is a dependency's binary missing — never installed, or installed with its
|
|
@@ -84,6 +85,45 @@ export declare function resolveLocalBin(binName: string): string | null;
|
|
|
84
85
|
export declare function getTableIncludesFromCollections(allCollections: CollectionConfig[]): Promise<string[]>;
|
|
85
86
|
export declare function getTableIncludes(collectionsPath: string): Promise<string[]>;
|
|
86
87
|
export declare function loadCollectionsForCli(collectionsPath: string): Promise<CollectionConfig[]>;
|
|
88
|
+
type PgClient = import("pg").Client;
|
|
89
|
+
/**
|
|
90
|
+
* Run one step on this process's connection to the app database.
|
|
91
|
+
*
|
|
92
|
+
* Every step leaves the session as it found it, which is what a connection of
|
|
93
|
+
* its own used to guarantee by being closed. None of them sets anything for
|
|
94
|
+
* the session — `SET LOCAL` inside their own transaction, or `RESET` — and one
|
|
95
|
+
* that fails part-way through a transaction it opened is rolled back here, so
|
|
96
|
+
* the next step does not meet `current transaction is aborted`.
|
|
97
|
+
*
|
|
98
|
+
* @param options.connectionTimeoutMillis applies only when this call is the one
|
|
99
|
+
* that connects.
|
|
100
|
+
*/
|
|
101
|
+
export declare function withAppClient<T>(databaseUrl: string, step: (client: PgClient) => Promise<T>, options?: {
|
|
102
|
+
connectionTimeoutMillis?: number;
|
|
103
|
+
}): Promise<T>;
|
|
104
|
+
/** Close every connection {@link withAppClient} opened. Called once, as the command ends. */
|
|
105
|
+
export declare function closeAppClients(): Promise<void>;
|
|
106
|
+
/**
|
|
107
|
+
* Names the empty database Atlas plans against, instead of `<db>_dev_diff`.
|
|
108
|
+
*
|
|
109
|
+
* The derived one is made with `CREATE DATABASE` beside the real database,
|
|
110
|
+
* which assumes a server that holds more than one. PGlite — the managed
|
|
111
|
+
* development database — serves exactly one: the CREATE succeeds and creates
|
|
112
|
+
* nothing, so Atlas connected straight back to the database it was comparing
|
|
113
|
+
* and refused it as "not clean". `rebase db push` on the managed database now
|
|
114
|
+
* starts a second, throwaway PGlite and names it here. Anyone can name their
|
|
115
|
+
* own the same way; a named database is neither created nor dropped.
|
|
116
|
+
*/
|
|
117
|
+
export declare const ATLAS_DEV_URL_ENV = "REBASE_ATLAS_DEV_URL";
|
|
118
|
+
/**
|
|
119
|
+
* The database Atlas replays the desired schema into: the one
|
|
120
|
+
* {@link ATLAS_DEV_URL_ENV} names (`named: true` — not ours to create or
|
|
121
|
+
* drop), else `<db>_dev_diff`.
|
|
122
|
+
*/
|
|
123
|
+
export declare function atlasDevDatabaseUrl(databaseUrl: string, env: Readonly<Record<string, string | undefined>>): {
|
|
124
|
+
url: string;
|
|
125
|
+
named: boolean;
|
|
126
|
+
};
|
|
87
127
|
export declare function getDevDatabaseUrl(databaseUrl: string): string;
|
|
88
128
|
/**
|
|
89
129
|
* Create the scratch database Atlas plans against, and say so when we cannot.
|
|
@@ -238,15 +278,21 @@ export declare function getTriggerExcludes(collectionsPath: string): Promise<str
|
|
|
238
278
|
* catalogs. Separated from {@link getTableExcludes} so its failure mode can
|
|
239
279
|
* be handled explicitly (fail closed) and so tests can inject a stub.
|
|
240
280
|
*/
|
|
281
|
+
/** An index as the catalogue names it: its schema, its table and its own name. */
|
|
282
|
+
export interface ExistingIndex {
|
|
283
|
+
schema: string;
|
|
284
|
+
table: string;
|
|
285
|
+
index: string;
|
|
286
|
+
}
|
|
241
287
|
/**
|
|
242
|
-
* Every index Postgres holds on a table
|
|
243
|
-
*
|
|
288
|
+
* Every index Postgres holds on a table, by its parts — never joined with `.`,
|
|
289
|
+
* which a name may itself contain.
|
|
244
290
|
*
|
|
245
291
|
* Constraint-backed indexes are left out: a `PRIMARY KEY` or a `UNIQUE` is a
|
|
246
292
|
* *constraint* to Atlas, diffed from the constraint declaration, and naming its
|
|
247
293
|
* index in an exclude would shield the constraint itself.
|
|
248
294
|
*/
|
|
249
|
-
export declare function queryExistingIndexes(databaseUrl: string): Promise<
|
|
295
|
+
export declare function queryExistingIndexes(databaseUrl: string): Promise<ExistingIndex[]>;
|
|
250
296
|
/**
|
|
251
297
|
* Glob patterns keeping Atlas away from indexes that are not Rebase's.
|
|
252
298
|
*
|
|
@@ -273,13 +319,35 @@ export declare function queryExistingIndexes(databaseUrl: string): Promise<strin
|
|
|
273
319
|
*
|
|
274
320
|
* Three-part `schema.table.index`, never two: a two-part pattern reads as a
|
|
275
321
|
* *table* named `<index>` in a *schema* named `<table>`, matches nothing, and
|
|
276
|
-
* reports no error — the trap already recorded for the search excludes.
|
|
322
|
+
* reports no error — the trap already recorded for the search excludes. And
|
|
323
|
+
* spelled by `atlasExcludePattern`: a raw `orders_idx[v1]` is a glob that
|
|
324
|
+
* matches `orders_idxv`, so that index was dropped with no prompt.
|
|
277
325
|
*/
|
|
278
326
|
export declare function getForeignIndexExcludes(databaseUrl: string, collectionsPath: string, deps?: {
|
|
279
|
-
queryExistingIndexes?: (databaseUrl: string) => Promise<
|
|
327
|
+
queryExistingIndexes?: (databaseUrl: string) => Promise<ExistingIndex[]>;
|
|
280
328
|
getManagedIndexNames?: (collectionsPath: string) => Promise<Set<string>>;
|
|
281
329
|
}): Promise<string[]>;
|
|
282
|
-
|
|
330
|
+
/** Every CHECK and EXCLUDE constraint the database holds. @see UNDECLARABLE_CONSTRAINTS_SQL */
|
|
331
|
+
export declare function queryUndeclarableConstraints(databaseUrl: string): Promise<ExistingConstraint[]>;
|
|
332
|
+
/**
|
|
333
|
+
* Patterns keeping Atlas away from the CHECK and EXCLUDE constraints the
|
|
334
|
+
* collections do not declare — which, since a collection cannot declare
|
|
335
|
+
* either, is all of them. See `schema/undeclarable-constraints.ts`.
|
|
336
|
+
*
|
|
337
|
+
* Fails CLOSED, like {@link getTableExcludes}: without the catalogue nothing
|
|
338
|
+
* says which constraints are there, and a partial list lets the push drop a
|
|
339
|
+
* rule the database enforces. `desiredSql` is the `schema.sql` Atlas is about
|
|
340
|
+
* to diff against; a constraint it declares is Atlas's to manage.
|
|
341
|
+
*/
|
|
342
|
+
export declare function getUndeclaredConstraintExcludes(databaseUrl: string, desiredSql: string, deps?: {
|
|
343
|
+
queryConstraints?: (databaseUrl: string) => Promise<ExistingConstraint[]>;
|
|
344
|
+
}): Promise<string[]>;
|
|
345
|
+
/** A table or view as the catalogue names it. */
|
|
346
|
+
export interface ExistingTable {
|
|
347
|
+
schema: string;
|
|
348
|
+
table: string;
|
|
349
|
+
}
|
|
350
|
+
export declare function queryExistingTables(databaseUrl: string): Promise<ExistingTable[]>;
|
|
283
351
|
/**
|
|
284
352
|
* Raised when the exclude list could not be built. `db push` MUST abort on
|
|
285
353
|
* this rather than continue: the exclude list is the only thing shielding
|
|
@@ -304,21 +372,7 @@ export declare class ExcludeIntrospectionError extends Error {
|
|
|
304
372
|
* `deps` is injectable for tests; production uses the real pg-backed queries.
|
|
305
373
|
*/
|
|
306
374
|
export declare function getTableExcludes(databaseUrl: string, collectionsPath: string, deps?: {
|
|
307
|
-
queryExistingTables?: (databaseUrl: string) => Promise<
|
|
375
|
+
queryExistingTables?: (databaseUrl: string) => Promise<ExistingTable[]>;
|
|
308
376
|
getIncludes?: (collectionsPath: string) => Promise<string[]>;
|
|
309
377
|
}): Promise<string[]>;
|
|
310
|
-
|
|
311
|
-
* Ask a yes/no question on an interactive terminal.
|
|
312
|
-
*
|
|
313
|
-
* The `isTTY` guard is the contract, not an optimisation: non-interactive
|
|
314
|
-
* shells (CI, pipes, agents) can never answer, and `readline` on a
|
|
315
|
-
* non-TTY stdin resolves with whatever the pipe happens to contain — or never
|
|
316
|
-
* resolves at all. Returning false there is what makes an unattended
|
|
317
|
-
* `db push` abort instead of auto-confirming a destructive change. Callers
|
|
318
|
-
* should already have gone through {@link decidePushSafety}, which decides
|
|
319
|
-
* whether interactive confirmation is even possible; this is the backstop.
|
|
320
|
-
*
|
|
321
|
-
* Lives here rather than in cli.ts so it is reachable from a test — cli.ts uses
|
|
322
|
-
* `import.meta` and cannot be imported by the jest runner.
|
|
323
|
-
*/
|
|
324
|
-
export declare function promptConfirm(question: string): Promise<boolean>;
|
|
378
|
+
export {};
|
package/dist/cli-output.d.ts
CHANGED
|
@@ -1,34 +1,22 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Terminal output for the `rebase db|schema|doctor` commands.
|
|
3
|
-
*
|
|
4
|
-
* These commands used to write every line through `logger`, and that is a
|
|
5
|
-
* category error with three separate consequences:
|
|
6
|
-
*
|
|
7
|
-
* - `logger` prefixes each line with its own level, so a box-drawn report
|
|
8
|
-
* arrived as `ℹ️ [INFO] ┌─ ✗ Missing Column ───` and the frame no longer
|
|
9
|
-
* lined up with anything;
|
|
10
|
-
* - `logger` is gated by `LOG_LEVEL`, which ships in the scaffold's own
|
|
11
|
-
* `.env.example` — a developer who quietened their dev server with
|
|
12
|
-
* `LOG_LEVEL=warn` got a `rebase db push` that printed almost nothing and
|
|
13
|
-
* still exited non-zero, indistinguishable from a crash;
|
|
14
|
-
* - under `NODE_ENV=production` `logger` emits JSON, so the whole report
|
|
15
|
-
* became log records with the chalk escape codes embedded in them.
|
|
16
|
-
*
|
|
17
|
-
* A CLI's report *is* its return value. It goes to the terminal unconditionally
|
|
18
|
-
* and unadorned. `packages/cli` has always written its output this way; this is
|
|
19
|
-
* the same three functions for the plugin CLI that `rebase` delegates to.
|
|
20
|
-
*
|
|
21
|
-
* `logger` still belongs in this package's *runtime* — a request handler has no
|
|
22
|
-
* terminal and its lines want levels, timestamps and redaction. The rule is the
|
|
23
|
-
* caller, not the severity: anything a developer reads because they typed a
|
|
24
|
-
* command goes here, anything a server emits while running goes to `logger`.
|
|
25
|
-
*
|
|
26
|
-
* Errors and warnings go to stderr so `rebase db push > plan.txt` keeps the
|
|
27
|
-
* diagnosis on the terminal where it is readable.
|
|
28
|
-
*/
|
|
29
1
|
/** One line of human-facing output on stdout. */
|
|
30
2
|
export declare const out: (line?: string) => void;
|
|
31
3
|
/** One line of human-facing warning output on stderr. */
|
|
32
4
|
export declare const outWarn: (line?: string) => void;
|
|
33
5
|
/** One line of human-facing error output on stderr. */
|
|
34
6
|
export declare const outError: (line?: string) => void;
|
|
7
|
+
/**
|
|
8
|
+
* Whether a failure should be printed with its stack — `rebase --debug`, which
|
|
9
|
+
* the launcher passes down as `REBASE_DEBUG`, or `REBASE_DEBUG` set by hand.
|
|
10
|
+
*/
|
|
11
|
+
export declare const wantsStack: () => boolean;
|
|
12
|
+
/**
|
|
13
|
+
* An error as a person reads it: its message, and its stack only under
|
|
14
|
+
* `--debug`.
|
|
15
|
+
*
|
|
16
|
+
* The rule `rebase` itself follows (`bin/rebase.js`). These commands run in a
|
|
17
|
+
* child process the launcher's handler never sees, so a syntax error in a
|
|
18
|
+
* collection file reached the terminal as `Doctor failed CollectionConfigError:`
|
|
19
|
+
* followed by twenty frames of loader internals, under a sentence that already
|
|
20
|
+
* said which file and which line.
|
|
21
|
+
*/
|
|
22
|
+
export declare const describeFailure: (error: unknown) => string;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { createRequire as __createRequire } from "module";
|
|
2
2
|
__createRequire(import.meta.url);
|
|
3
|
-
import {
|
|
3
|
+
import { t as __commonJSMin } from "./rolldown-runtime-V7rFbMRe.js";
|
|
4
|
+
import readline from "readline";
|
|
4
5
|
//#region ../../node_modules/.pnpm/arg@5.0.2/node_modules/arg/index.js
|
|
5
6
|
var require_arg = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
6
7
|
var flagSymbol = Symbol("arg flag");
|
|
@@ -82,6 +83,51 @@ var require_arg = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
|
82
83
|
module.exports = arg;
|
|
83
84
|
}));
|
|
84
85
|
//#endregion
|
|
85
|
-
|
|
86
|
+
//#region src/cli-prompt.ts
|
|
87
|
+
/**
|
|
88
|
+
* The one yes/no question the driver CLI asks: before a destructive `db push`,
|
|
89
|
+
* before a `db restore` without `--yes`, before `db branch prune` removes
|
|
90
|
+
* anything, and before `schema introspect` samples rows.
|
|
91
|
+
*/
|
|
92
|
+
/**
|
|
93
|
+
* Ask a yes/no question on an interactive terminal; `true` only for "y" or
|
|
94
|
+
* "yes".
|
|
95
|
+
*
|
|
96
|
+
* The `isTTY` guard is the contract, not an optimisation: non-interactive
|
|
97
|
+
* shells (CI, pipes, agents) can never answer, and `readline` on a non-TTY
|
|
98
|
+
* stdin resolves with whatever the pipe happens to contain, or never resolves
|
|
99
|
+
* at all. Returning false there is what makes an unattended `db push` abort
|
|
100
|
+
* instead of auto-confirming a destructive change. Callers decide first
|
|
101
|
+
* whether interactive confirmation is possible (`decidePushSafety` for a
|
|
102
|
+
* push); this is the backstop.
|
|
103
|
+
*
|
|
104
|
+
* Ctrl-C and Ctrl-D answer no. On a terminal readline reads Ctrl-C as a key,
|
|
105
|
+
* not as a signal, and with nothing listening it only pauses the input: the
|
|
106
|
+
* question never resolves, and the open database connections behind it keep
|
|
107
|
+
* the process alive, so the command hung instead of stopping. Answering no
|
|
108
|
+
* sends it down the path that says "Aborted" and cleans up.
|
|
109
|
+
*/
|
|
110
|
+
async function promptConfirm(question) {
|
|
111
|
+
if (!process.stdin.isTTY) return false;
|
|
112
|
+
const rl = readline.createInterface({
|
|
113
|
+
input: process.stdin,
|
|
114
|
+
output: process.stdout
|
|
115
|
+
});
|
|
116
|
+
try {
|
|
117
|
+
const answer = await new Promise((resolve) => {
|
|
118
|
+
rl.once("SIGINT", () => {
|
|
119
|
+
process.stdout.write("\n");
|
|
120
|
+
resolve("");
|
|
121
|
+
});
|
|
122
|
+
rl.once("close", () => resolve(""));
|
|
123
|
+
rl.question(question, resolve);
|
|
124
|
+
});
|
|
125
|
+
return /^y(es)?$/i.test(answer.trim());
|
|
126
|
+
} finally {
|
|
127
|
+
rl.close();
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
//#endregion
|
|
131
|
+
export { require_arg as n, promptConfirm as t };
|
|
86
132
|
|
|
87
|
-
//# sourceMappingURL=
|
|
133
|
+
//# sourceMappingURL=cli-prompt-egtNKkZl.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli-prompt-egtNKkZl.js","names":[],"sources":["../../../node_modules/.pnpm/arg@5.0.2/node_modules/arg/index.js","../src/cli-prompt.ts"],"sourcesContent":["const flagSymbol = Symbol('arg flag');\n\nclass ArgError extends Error {\n\tconstructor(msg, code) {\n\t\tsuper(msg);\n\t\tthis.name = 'ArgError';\n\t\tthis.code = code;\n\n\t\tObject.setPrototypeOf(this, ArgError.prototype);\n\t}\n}\n\nfunction arg(\n\topts,\n\t{\n\t\targv = process.argv.slice(2),\n\t\tpermissive = false,\n\t\tstopAtPositional = false\n\t} = {}\n) {\n\tif (!opts) {\n\t\tthrow new ArgError(\n\t\t\t'argument specification object is required',\n\t\t\t'ARG_CONFIG_NO_SPEC'\n\t\t);\n\t}\n\n\tconst result = { _: [] };\n\n\tconst aliases = {};\n\tconst handlers = {};\n\n\tfor (const key of Object.keys(opts)) {\n\t\tif (!key) {\n\t\t\tthrow new ArgError(\n\t\t\t\t'argument key cannot be an empty string',\n\t\t\t\t'ARG_CONFIG_EMPTY_KEY'\n\t\t\t);\n\t\t}\n\n\t\tif (key[0] !== '-') {\n\t\t\tthrow new ArgError(\n\t\t\t\t`argument key must start with '-' but found: '${key}'`,\n\t\t\t\t'ARG_CONFIG_NONOPT_KEY'\n\t\t\t);\n\t\t}\n\n\t\tif (key.length === 1) {\n\t\t\tthrow new ArgError(\n\t\t\t\t`argument key must have a name; singular '-' keys are not allowed: ${key}`,\n\t\t\t\t'ARG_CONFIG_NONAME_KEY'\n\t\t\t);\n\t\t}\n\n\t\tif (typeof opts[key] === 'string') {\n\t\t\taliases[key] = opts[key];\n\t\t\tcontinue;\n\t\t}\n\n\t\tlet type = opts[key];\n\t\tlet isFlag = false;\n\n\t\tif (\n\t\t\tArray.isArray(type) &&\n\t\t\ttype.length === 1 &&\n\t\t\ttypeof type[0] === 'function'\n\t\t) {\n\t\t\tconst [fn] = type;\n\t\t\ttype = (value, name, prev = []) => {\n\t\t\t\tprev.push(fn(value, name, prev[prev.length - 1]));\n\t\t\t\treturn prev;\n\t\t\t};\n\t\t\tisFlag = fn === Boolean || fn[flagSymbol] === true;\n\t\t} else if (typeof type === 'function') {\n\t\t\tisFlag = type === Boolean || type[flagSymbol] === true;\n\t\t} else {\n\t\t\tthrow new ArgError(\n\t\t\t\t`type missing or not a function or valid array type: ${key}`,\n\t\t\t\t'ARG_CONFIG_VAD_TYPE'\n\t\t\t);\n\t\t}\n\n\t\tif (key[1] !== '-' && key.length > 2) {\n\t\t\tthrow new ArgError(\n\t\t\t\t`short argument keys (with a single hyphen) must have only one character: ${key}`,\n\t\t\t\t'ARG_CONFIG_SHORTOPT_TOOLONG'\n\t\t\t);\n\t\t}\n\n\t\thandlers[key] = [type, isFlag];\n\t}\n\n\tfor (let i = 0, len = argv.length; i < len; i++) {\n\t\tconst wholeArg = argv[i];\n\n\t\tif (stopAtPositional && result._.length > 0) {\n\t\t\tresult._ = result._.concat(argv.slice(i));\n\t\t\tbreak;\n\t\t}\n\n\t\tif (wholeArg === '--') {\n\t\t\tresult._ = result._.concat(argv.slice(i + 1));\n\t\t\tbreak;\n\t\t}\n\n\t\tif (wholeArg.length > 1 && wholeArg[0] === '-') {\n\t\t\t/* eslint-disable operator-linebreak */\n\t\t\tconst separatedArguments =\n\t\t\t\twholeArg[1] === '-' || wholeArg.length === 2\n\t\t\t\t\t? [wholeArg]\n\t\t\t\t\t: wholeArg\n\t\t\t\t\t\t\t.slice(1)\n\t\t\t\t\t\t\t.split('')\n\t\t\t\t\t\t\t.map((a) => `-${a}`);\n\t\t\t/* eslint-enable operator-linebreak */\n\n\t\t\tfor (let j = 0; j < separatedArguments.length; j++) {\n\t\t\t\tconst arg = separatedArguments[j];\n\t\t\t\tconst [originalArgName, argStr] =\n\t\t\t\t\targ[1] === '-' ? arg.split(/=(.*)/, 2) : [arg, undefined];\n\n\t\t\t\tlet argName = originalArgName;\n\t\t\t\twhile (argName in aliases) {\n\t\t\t\t\targName = aliases[argName];\n\t\t\t\t}\n\n\t\t\t\tif (!(argName in handlers)) {\n\t\t\t\t\tif (permissive) {\n\t\t\t\t\t\tresult._.push(arg);\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tthrow new ArgError(\n\t\t\t\t\t\t\t`unknown or unexpected option: ${originalArgName}`,\n\t\t\t\t\t\t\t'ARG_UNKNOWN_OPTION'\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tconst [type, isFlag] = handlers[argName];\n\n\t\t\t\tif (!isFlag && j + 1 < separatedArguments.length) {\n\t\t\t\t\tthrow new ArgError(\n\t\t\t\t\t\t`option requires argument (but was followed by another short argument): ${originalArgName}`,\n\t\t\t\t\t\t'ARG_MISSING_REQUIRED_SHORTARG'\n\t\t\t\t\t);\n\t\t\t\t}\n\n\t\t\t\tif (isFlag) {\n\t\t\t\t\tresult[argName] = type(true, argName, result[argName]);\n\t\t\t\t} else if (argStr === undefined) {\n\t\t\t\t\tif (\n\t\t\t\t\t\targv.length < i + 2 ||\n\t\t\t\t\t\t(argv[i + 1].length > 1 &&\n\t\t\t\t\t\t\targv[i + 1][0] === '-' &&\n\t\t\t\t\t\t\t!(\n\t\t\t\t\t\t\t\targv[i + 1].match(/^-?\\d*(\\.(?=\\d))?\\d*$/) &&\n\t\t\t\t\t\t\t\t(type === Number ||\n\t\t\t\t\t\t\t\t\t// eslint-disable-next-line no-undef\n\t\t\t\t\t\t\t\t\t(typeof BigInt !== 'undefined' && type === BigInt))\n\t\t\t\t\t\t\t))\n\t\t\t\t\t) {\n\t\t\t\t\t\tconst extended =\n\t\t\t\t\t\t\toriginalArgName === argName ? '' : ` (alias for ${argName})`;\n\t\t\t\t\t\tthrow new ArgError(\n\t\t\t\t\t\t\t`option requires argument: ${originalArgName}${extended}`,\n\t\t\t\t\t\t\t'ARG_MISSING_REQUIRED_LONGARG'\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\n\t\t\t\t\tresult[argName] = type(argv[i + 1], argName, result[argName]);\n\t\t\t\t\t++i;\n\t\t\t\t} else {\n\t\t\t\t\tresult[argName] = type(argStr, argName, result[argName]);\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\tresult._.push(wholeArg);\n\t\t}\n\t}\n\n\treturn result;\n}\n\narg.flag = (fn) => {\n\tfn[flagSymbol] = true;\n\treturn fn;\n};\n\n// Utility types\narg.COUNT = arg.flag((v, name, existingCount) => (existingCount || 0) + 1);\n\n// Expose error class\narg.ArgError = ArgError;\n\nmodule.exports = arg;\n","/**\n * The one yes/no question the driver CLI asks: before a destructive `db push`,\n * before a `db restore` without `--yes`, before `db branch prune` removes\n * anything, and before `schema introspect` samples rows.\n */\nimport readline from \"readline\";\n\n/**\n * Ask a yes/no question on an interactive terminal; `true` only for \"y\" or\n * \"yes\".\n *\n * The `isTTY` guard is the contract, not an optimisation: non-interactive\n * shells (CI, pipes, agents) can never answer, and `readline` on a non-TTY\n * stdin resolves with whatever the pipe happens to contain, or never resolves\n * at all. Returning false there is what makes an unattended `db push` abort\n * instead of auto-confirming a destructive change. Callers decide first\n * whether interactive confirmation is possible (`decidePushSafety` for a\n * push); this is the backstop.\n *\n * Ctrl-C and Ctrl-D answer no. On a terminal readline reads Ctrl-C as a key,\n * not as a signal, and with nothing listening it only pauses the input: the\n * question never resolves, and the open database connections behind it keep\n * the process alive, so the command hung instead of stopping. Answering no\n * sends it down the path that says \"Aborted\" and cleans up.\n */\nexport async function promptConfirm(question: string): Promise<boolean> {\n if (!process.stdin.isTTY) return false;\n const rl = readline.createInterface({ input: process.stdin, output: process.stdout });\n try {\n const answer = await new Promise<string>((resolve) => {\n rl.once(\"SIGINT\", () => {\n process.stdout.write(\"\\n\");\n resolve(\"\");\n });\n rl.once(\"close\", () => resolve(\"\"));\n rl.question(question, resolve);\n });\n return /^y(es)?$/i.test(answer.trim());\n } finally {\n rl.close();\n }\n}\n"],"x_google_ignoreList":[0],"mappings":"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"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ask a yes/no question on an interactive terminal; `true` only for "y" or
|
|
3
|
+
* "yes".
|
|
4
|
+
*
|
|
5
|
+
* The `isTTY` guard is the contract, not an optimisation: non-interactive
|
|
6
|
+
* shells (CI, pipes, agents) can never answer, and `readline` on a non-TTY
|
|
7
|
+
* stdin resolves with whatever the pipe happens to contain, or never resolves
|
|
8
|
+
* at all. Returning false there is what makes an unattended `db push` abort
|
|
9
|
+
* instead of auto-confirming a destructive change. Callers decide first
|
|
10
|
+
* whether interactive confirmation is possible (`decidePushSafety` for a
|
|
11
|
+
* push); this is the backstop.
|
|
12
|
+
*
|
|
13
|
+
* Ctrl-C and Ctrl-D answer no. On a terminal readline reads Ctrl-C as a key,
|
|
14
|
+
* not as a signal, and with nothing listening it only pauses the input: the
|
|
15
|
+
* question never resolves, and the open database connections behind it keep
|
|
16
|
+
* the process alive, so the command hung instead of stopping. Answering no
|
|
17
|
+
* sends it down the path that says "Aborted" and cleans up.
|
|
18
|
+
*/
|
|
19
|
+
export declare function promptConfirm(question: string): Promise<boolean>;
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The steps `rebase db push` takes once the dry-run has been read and gated.
|
|
3
|
+
*
|
|
4
|
+
* Callbacks rather than calls, so the order is testable without a database or
|
|
5
|
+
* an Atlas binary: `cli.ts` itself cannot be loaded by the jest suite.
|
|
6
|
+
*/
|
|
7
|
+
export interface PushApplySteps {
|
|
8
|
+
/** Drop the generated columns that read a column the plan retypes. */
|
|
9
|
+
dropRebuildable: () => Promise<void>;
|
|
10
|
+
/** `atlas schema apply --auto-approve`: Atlas plans again, then applies. */
|
|
11
|
+
atlasApply: () => Promise<void>;
|
|
12
|
+
/** Say what the skipped apply would have printed. */
|
|
13
|
+
reportSynced: () => void;
|
|
14
|
+
/** Put those generated columns back after a failed apply. */
|
|
15
|
+
rebuildAfterFailure: () => Promise<void>;
|
|
16
|
+
/**
|
|
17
|
+
* Everything Atlas cannot see: RLS locks, auth tables, vector, search,
|
|
18
|
+
* triggers, policies, the user role. Runs whether or not Atlas had work.
|
|
19
|
+
*/
|
|
20
|
+
afterApply: () => Promise<void>;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Apply a planned push.
|
|
24
|
+
*
|
|
25
|
+
* A dry-run that answered "Schema is synced" skips the second Atlas run: that
|
|
26
|
+
* run would plan the same diff again (≈0.9 s at 111 tables, 5.5 s at 411 —
|
|
27
|
+
* Atlas's cost grows faster than the table count) and then apply nothing. The
|
|
28
|
+
* steps after it still run, because Atlas cannot see what they own. A plan with
|
|
29
|
+
* anything in it keeps the old flow, including Atlas re-planning at apply time.
|
|
30
|
+
*
|
|
31
|
+
* @returns `"synced"` when Atlas was not run again, `"applied"` otherwise.
|
|
32
|
+
*/
|
|
33
|
+
export declare function applyPushPlan(plan: {
|
|
34
|
+
dryRun: string;
|
|
35
|
+
rebuildable: number;
|
|
36
|
+
}, steps: PushApplySteps): Promise<"applied" | "synced">;
|
|
37
|
+
/** The one call {@link applyPoliciesFile} needs from a connection. */
|
|
38
|
+
export interface PolicyConnection {
|
|
39
|
+
query(text: string): Promise<unknown>;
|
|
40
|
+
}
|
|
41
|
+
/** What {@link applyPoliciesFile} stamps, and how to read the definitions back. */
|
|
42
|
+
export interface PolicyStamping {
|
|
43
|
+
/**
|
|
44
|
+
* `policyStampSql` of every table whose policies `policies.sql` creates
|
|
45
|
+
* exactly as the boot would — see {@link stampableTables}.
|
|
46
|
+
*/
|
|
47
|
+
statements: string[];
|
|
48
|
+
/**
|
|
49
|
+
* The `search_path` a boot reads policies under, when the connection's own
|
|
50
|
+
* differs. A stamp hashes `pg_get_expr`, which spells a function on the
|
|
51
|
+
* path unqualified: read under `"$user", public` with a `rebase` role, the
|
|
52
|
+
* boot's `rebase.uid()` prints as `uid()` and no stamp would ever match.
|
|
53
|
+
*/
|
|
54
|
+
searchPath?: string;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* The tables whose stamp `db push` may write: those every one of whose planned
|
|
58
|
+
* statements `policies.sql` carries, verbatim.
|
|
59
|
+
*
|
|
60
|
+
* A stamp says "this stored definition is what the plan's statement made". It
|
|
61
|
+
* is only true of a policy the statement *did* make — and since both are
|
|
62
|
+
* rendered by the same function from the same collections, a table whose
|
|
63
|
+
* statements are all in the file is one whose policies the file just created.
|
|
64
|
+
* Anything else (a hand-edited file, a file generated elsewhere) is not
|
|
65
|
+
* stamped, and the next boot re-applies that table, as before.
|
|
66
|
+
*/
|
|
67
|
+
export declare function stampableTables<T extends {
|
|
68
|
+
policyStatements: string[];
|
|
69
|
+
}>(plans: readonly T[], policiesSql: string): T[];
|
|
70
|
+
/**
|
|
71
|
+
* Run `policies.sql`, then stamp the policies it created the way a boot does,
|
|
72
|
+
* in the same transaction.
|
|
73
|
+
*
|
|
74
|
+
* Before stamps the push's policies carried none, so the first boot after
|
|
75
|
+
* every push re-applied them all — a DROP and a CREATE per policy, an ACCESS
|
|
76
|
+
* EXCLUSIVE lock per table. Stamped here, that boot finds them current.
|
|
77
|
+
*
|
|
78
|
+
* The file runs in one transaction, as it did as one implicit one: a policy
|
|
79
|
+
* that fails leaves the previous ones in place. The stamps run after it, under
|
|
80
|
+
* a savepoint: a stamp that cannot be written costs the next boot a re-apply,
|
|
81
|
+
* which is what it did before, so it is reported and the policies are kept.
|
|
82
|
+
* Nothing is left set on the connection: `SET LOCAL` ends with the transaction.
|
|
83
|
+
*/
|
|
84
|
+
export declare function applyPoliciesFile(client: PolicyConnection, policiesSql: string, stamping: PolicyStamping): Promise<{
|
|
85
|
+
stamped: number;
|
|
86
|
+
stampError?: string;
|
|
87
|
+
}>;
|
|
@@ -8,11 +8,16 @@
|
|
|
8
8
|
* `db push`: a `--dry-run` that left one behind while saying nothing was
|
|
9
9
|
* applied, and `db generate`, left one per target on the server.
|
|
10
10
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
11
|
+
* It is dropped on every way out of the command: success, a destructive push
|
|
12
|
+
* the user declined or that had no terminal to ask, and a crash. A crash that
|
|
13
|
+
* kept it left it for good, because the next push finds it already there and
|
|
14
|
+
* takes it for one made by hand.
|
|
15
|
+
*
|
|
16
|
+
* Two cases keep it. An Atlas run that failed against it leaves the state Atlas
|
|
17
|
+
* was planning, which is the one thing worth inspecting. And one this process
|
|
18
|
+
* did not create (made by hand, the remedy for a role without `CREATEDB`) is
|
|
19
|
+
* the user's, and dropping it would put them back at the same refusal next
|
|
20
|
+
* time.
|
|
16
21
|
*/
|
|
17
22
|
export interface ScratchDatabaseOps {
|
|
18
23
|
/** Create it if it is missing; `true` only when this call created it. */
|
|
@@ -22,13 +27,33 @@ export interface ScratchDatabaseOps {
|
|
|
22
27
|
export declare class ScratchDatabase {
|
|
23
28
|
private readonly ops;
|
|
24
29
|
private created;
|
|
30
|
+
private keptAsEvidence;
|
|
25
31
|
constructor(ops: ScratchDatabaseOps);
|
|
26
32
|
/** Make sure it exists before an Atlas call that plans against it. */
|
|
27
33
|
ensure(databaseUrl: string, devDatabaseUrl: string): Promise<void>;
|
|
34
|
+
/** An Atlas run against it failed: keep it, whatever the command does next. */
|
|
35
|
+
keepAsEvidence(): void;
|
|
28
36
|
/**
|
|
29
|
-
* Run a command, then drop the scratch database if the command created it
|
|
30
|
-
*
|
|
31
|
-
*
|
|
37
|
+
* Run a command, then drop the scratch database if the command created it,
|
|
38
|
+
* whether the command returned or threw. A throw after
|
|
39
|
+
* {@link keepAsEvidence} keeps it. A command that wants to stop with exit 1
|
|
40
|
+
* throws {@link CommandStopped} rather than calling `process.exit`, which
|
|
41
|
+
* would skip this.
|
|
32
42
|
*/
|
|
33
43
|
around<T>(command: () => Promise<T>): Promise<T>;
|
|
44
|
+
/** Drop it now if this process created it. Safe to call twice. */
|
|
45
|
+
release(): Promise<void>;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* A db command stopping with exit 1 after it has said why: a destructive push
|
|
49
|
+
* the user declined, or one with no terminal to ask on.
|
|
50
|
+
*
|
|
51
|
+
* Thrown instead of `process.exit(1)`, so it unwinds through
|
|
52
|
+
* {@link ScratchDatabase.around} and the scratch database is dropped on the way
|
|
53
|
+
* out. `alreadyReported` tells `reportCommandFailure` the explanation is on the
|
|
54
|
+
* terminal already; the entry point sets the exit code.
|
|
55
|
+
*/
|
|
56
|
+
export declare class CommandStopped extends Error {
|
|
57
|
+
readonly alreadyReported = true;
|
|
58
|
+
constructor(reason: string);
|
|
34
59
|
}
|