@rebasepro/cli 0.25.0-canary.ge06257e → 0.25.1
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/bin/rebase.js +9 -5
- package/dist/{agent-setup-C83DXnlx.js → agent-setup-BYb2Tbdz.js} +2 -2
- package/dist/{agent-setup-C83DXnlx.js.map → agent-setup-BYb2Tbdz.js.map} +1 -1
- package/dist/{api-keys-Bc4FJKYp.js → api-keys-CSMbsmAn.js} +28 -22
- package/dist/api-keys-CSMbsmAn.js.map +1 -0
- package/dist/{apps-wG-ZTryA.js → apps-iBXNZhVN.js} +6 -7
- package/dist/{apps-wG-ZTryA.js.map → apps-iBXNZhVN.js.map} +1 -1
- package/dist/{atlas-dev-database-DeK3Sa4h.js → atlas-dev-database-BusbHP6n.js} +4 -4
- package/dist/{atlas-dev-database-DeK3Sa4h.js.map → atlas-dev-database-BusbHP6n.js.map} +1 -1
- package/dist/{auth-BL7bNQVu.js → auth-DRsTWPky.js} +77 -11
- package/dist/auth-DRsTWPky.js.map +1 -0
- package/dist/{branch-pointer-DTQNLFtb.js → branch-pointer-Bn_FjNld.js} +1 -1
- package/dist/{build-h_7Re-67.js → build-et7Rccxi.js} +6 -7
- package/dist/{build-h_7Re-67.js.map → build-et7Rccxi.js.map} +1 -1
- package/dist/{bundle-CqURVN-K.js → bundle-DlqkDYSe.js} +6 -21
- package/dist/bundle-DlqkDYSe.js.map +1 -0
- package/dist/{cloud-DVV_5GPG.js → cloud-B-ZIjHrA.js} +10 -11
- package/dist/{cloud-DVV_5GPG.js.map → cloud-B-ZIjHrA.js.map} +1 -1
- package/dist/commands/api-keys.d.ts +4 -1
- package/dist/commands/auth.d.ts +3 -2
- package/dist/commands/dev.d.ts +12 -0
- package/dist/commands/generate_sdk.d.ts +4 -2
- package/dist/commands/init.d.ts +20 -0
- package/dist/commands/start.d.ts +10 -0
- package/dist/commands/upgrade.d.ts +12 -0
- package/dist/connection-banner-DbLJzWf4.js +128 -0
- package/dist/connection-banner-DbLJzWf4.js.map +1 -0
- package/dist/{consent-ClRHmcxf.js → consent-BaB-eQKG.js} +2 -2
- package/dist/{consent-ClRHmcxf.js.map → consent-BaB-eQKG.js.map} +1 -1
- package/dist/{console-Dw3HLdUs.js → console-BlD9kjFW.js} +4 -4
- package/dist/{console-Dw3HLdUs.js.map → console-BlD9kjFW.js.map} +1 -1
- package/dist/{context-BM-oOMNX.js → context-DHiiiJ5C.js} +3 -4
- package/dist/context-DHiiiJ5C.js.map +1 -0
- package/dist/{daemon-CknkDjd6.js → daemon-DDMRbz4T.js} +160 -5
- package/dist/daemon-DDMRbz4T.js.map +1 -0
- package/dist/{daemon-entry-C6pxdily.js → daemon-entry-zOC1revO.js} +204 -5
- package/dist/daemon-entry-zOC1revO.js.map +1 -0
- package/dist/{db-Dkn9XhT-.js → db-9P6MnmdC.js} +237 -86
- package/dist/db-9P6MnmdC.js.map +1 -0
- package/dist/{derive-Dl0z2OWi.js → derive-MTdClnkc.js} +29 -14
- package/dist/{derive-Dl0z2OWi.js.map → derive-MTdClnkc.js.map} +1 -1
- package/dist/{dev-DAcZR4Pp.js → dev-BDN_OKXA.js} +183 -23
- package/dist/dev-BDN_OKXA.js.map +1 -0
- package/dist/dev-db/connection-banner.d.ts +47 -0
- package/dist/dev-db/daemon.d.ts +41 -0
- package/dist/dev-db/notification-proxy.d.ts +82 -1
- package/dist/dev-db/pull.d.ts +33 -5
- package/dist/dev-db/state.d.ts +9 -0
- package/dist/{dev-preflight-CorMg7Ub.js → dev-preflight-FT9GbJVj.js} +73 -4
- package/dist/dev-preflight-FT9GbJVj.js.map +1 -0
- package/dist/{dev-server-B1UYifZJ.js → dev-server-CIZwAfZa.js} +14 -3
- package/dist/dev-server-CIZwAfZa.js.map +1 -0
- package/dist/{doctor-Ds_lzc9p.js → doctor-D8YXz7df.js} +6 -6
- package/dist/doctor-D8YXz7df.js.map +1 -0
- package/dist/{eject-CJHkfnat.js → eject-izG2KjMo.js} +3 -4
- package/dist/{eject-CJHkfnat.js.map → eject-izG2KjMo.js.map} +1 -1
- package/dist/{fold-static-BC9CYicw.js → fold-static-CybSay_W.js} +3 -3
- package/dist/{fold-static-BC9CYicw.js.map → fold-static-CybSay_W.js.map} +1 -1
- package/dist/{generate_sdk-kfkZmG-d.js → generate_sdk-B4R65oUV.js} +22 -12
- package/dist/generate_sdk-B4R65oUV.js.map +1 -0
- package/dist/index.es.js +36 -37
- package/dist/index.es.js.map +1 -1
- package/dist/{init-CjoJoThg.js → init-DYaeppEN.js} +52 -63
- package/dist/init-DYaeppEN.js.map +1 -0
- package/dist/invoked-command-CG_rIa_o.js +55 -0
- package/dist/invoked-command-CG_rIa_o.js.map +1 -0
- package/dist/last-dev-database-B_75fxZC.js +2 -0
- package/dist/{manifest-DRp0UVTj.js → manifest-nvPdLlJY.js} +2 -2
- package/dist/{manifest-DRp0UVTj.js.map → manifest-nvPdLlJY.js.map} +1 -1
- package/dist/{normalize-imports-BHxL2d05.js → normalize-imports-BYw8ad87.js} +3 -3
- package/dist/{normalize-imports-BHxL2d05.js.map → normalize-imports-BYw8ad87.js.map} +1 -1
- package/dist/precompress.d.ts +0 -7
- package/dist/{prepare-C1iaiTC4.js → prepare-1rVNd2PF.js} +7 -6
- package/dist/prepare-1rVNd2PF.js.map +1 -0
- package/dist/project-g4K6kV1t.js +701 -0
- package/dist/project-g4K6kV1t.js.map +1 -0
- package/dist/{pull-COpNJFzY.js → pull-DDMpsZ5r.js} +143 -63
- package/dist/pull-DDMpsZ5r.js.map +1 -0
- package/dist/refusal-ReeeGP8a.js +26 -0
- package/dist/refusal-ReeeGP8a.js.map +1 -0
- package/dist/resolve-BQUAC3Wi.js +2 -0
- package/dist/resources/derive.d.ts +9 -0
- package/dist/{resources-CLm6g0br.js → resources-CovP8AZ-.js} +4 -5
- package/dist/{resources-CLm6g0br.js.map → resources-CovP8AZ-.js.map} +1 -1
- package/dist/{schema-BKdh6UoE.js → schema-nuxvvc50.js} +5 -6
- package/dist/{schema-BKdh6UoE.js.map → schema-nuxvvc50.js.map} +1 -1
- package/dist/{skills-DEhYiPeO.js → skills-DeUIsZcT.js} +3 -4
- package/dist/{skills-DEhYiPeO.js.map → skills-DeUIsZcT.js.map} +1 -1
- package/dist/{start-BsPJedPQ.js → start-RzZqDroW.js} +20 -7
- package/dist/start-RzZqDroW.js.map +1 -0
- package/dist/{state-CCB-FAQ9.js → state-CYdBvnwA.js} +3 -2
- package/dist/state-CYdBvnwA.js.map +1 -0
- package/dist/{status-D_B3tlDy.js → status-D9Zm88NZ.js} +11 -12
- package/dist/{status-D_B3tlDy.js.map → status-D9Zm88NZ.js.map} +1 -1
- package/dist/{storage-CtB8polS.js → storage-BB3umRfw.js} +3 -4
- package/dist/{storage-CtB8polS.js.map → storage-BB3umRfw.js.map} +1 -1
- package/dist/{telemetry-EMFRVRQi.js → telemetry-HfU5uAh1.js} +2 -2
- package/dist/{telemetry-EMFRVRQi.js.map → telemetry-HfU5uAh1.js.map} +1 -1
- package/dist/{telemetry-BYmPU-Ji.js → telemetry-TseNLk6X.js} +4 -4
- package/dist/{telemetry-BYmPU-Ji.js.map → telemetry-TseNLk6X.js.map} +1 -1
- package/dist/{upgrade-DbuKX9lv.js → upgrade-apiPKKE_.js} +29 -6
- package/dist/{upgrade-DbuKX9lv.js.map → upgrade-apiPKKE_.js.map} +1 -1
- package/dist/utils/args.d.ts +45 -3
- package/dist/utils/boot-retry.d.ts +41 -0
- package/dist/utils/dev-preflight.d.ts +34 -1
- package/dist/utils/dev-server.d.ts +11 -0
- package/dist/utils/project.d.ts +5 -1
- package/dist/utils/redact.d.ts +10 -0
- package/dist/utils/refusal.d.ts +18 -0
- package/dist/utils/unknown-command.d.ts +4 -0
- package/package.json +7 -7
- package/templates/overlays/baas/README.md +2 -1
- package/templates/resource-graphs/blank.json +25 -0
- package/templates/resource-graphs/blog.json +28 -0
- package/templates/resource-graphs/ecommerce.json +28 -0
- package/templates/resource-graphs/headless.json +22 -0
- package/templates/template/.env.example +11 -4
- package/templates/template/frontend/src/App.tsx +10 -9
- package/templates/template/frontend/vite.config.ts +7 -73
- package/dist/api-keys-Bc4FJKYp.js.map +0 -1
- package/dist/args-CcBphFdj.js +0 -237
- package/dist/args-CcBphFdj.js.map +0 -1
- package/dist/auth-BL7bNQVu.js.map +0 -1
- package/dist/bundle-CqURVN-K.js.map +0 -1
- package/dist/context-BM-oOMNX.js.map +0 -1
- package/dist/daemon-CknkDjd6.js.map +0 -1
- package/dist/daemon-entry-C6pxdily.js.map +0 -1
- package/dist/db-Dkn9XhT-.js.map +0 -1
- package/dist/dev-DAcZR4Pp.js.map +0 -1
- package/dist/dev-preflight-CorMg7Ub.js.map +0 -1
- package/dist/dev-server-B1UYifZJ.js.map +0 -1
- package/dist/doctor-Ds_lzc9p.js.map +0 -1
- package/dist/generate_sdk-kfkZmG-d.js.map +0 -1
- package/dist/init-CjoJoThg.js.map +0 -1
- package/dist/last-dev-database-BEBPfAMY.js +0 -2
- package/dist/prepare-C1iaiTC4.js.map +0 -1
- package/dist/project-Wp2zoyeN.js +0 -349
- package/dist/project-Wp2zoyeN.js.map +0 -1
- package/dist/pull-COpNJFzY.js.map +0 -1
- package/dist/resolve-b_p9bmdH.js +0 -2
- package/dist/start-BsPJedPQ.js.map +0 -1
- package/dist/state-CCB-FAQ9.js.map +0 -1
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"pull-COpNJFzY.js","names":[],"sources":["../src/dev-db/pull.ts"],"sourcesContent":["/**\n * `rebase db pull` — copy a database's contents into local development.\n *\n * The common case is production into local, and the reason it exists is that\n * the alternative is worse: without it people hand-roll a `pg_dump | psql` and\n * get the flags wrong in ways that either fail loudly at 2am or, more often,\n * quietly restore half a schema.\n *\n * Three things this command insists on, because copying a production database\n * onto a laptop is a data-protection event whether or not anyone calls it one:\n *\n * 1. **It says what it is about to do, in full, before doing it** — which\n * database it will read, which one it will overwrite, and where the data will\n * come to rest on disk. The target path matters: people forget that\n * `.rebase/pgdata` is a directory their backup software may be indexing.\n *\n * 2. **It refuses to run unattended without being told to.** The target is\n * destroyed, so a mistyped `--from` with no confirmation would take the\n * developer's working database with it.\n *\n * 3. **It will not write to a remote database.** The target is always the local\n * development database; there is no flag that makes this push. A tool that\n * can copy in both directions eventually copies in the wrong one.\n *\n * Anonymization is opt-in (`--anonymize`), which is a deliberate choice and not\n * an obviously safe one — the flag nobody types is the flag nobody gets. It is\n * a best-effort pass over columns whose *names* look like personal data, and\n * {@link ANONYMIZE_PATTERNS} says exactly which. It cannot find personal data in\n * a column called `notes`, and this file says so rather than implying a\n * guarantee it cannot keep.\n *\n * What it does guarantee is that it never half-happens: {@link anonymizeCopy}\n * runs the pass in one transaction, and when the pass fails it empties the\n * tables it would have rewritten rather than leave their real values behind.\n */\n\nimport { execa } from \"execa\";\nimport fs from \"fs\";\nimport type { Client } from \"pg\";\n\nimport { PGLITE_EXTENSIONS } from \"./constraints\";\n\n/**\n * How one kind of personal-looking column is overwritten.\n *\n * Two forms, because a constant is the most readable replacement and is wrong\n * for exactly one kind of column: a unique one. Every `token_hash` becoming\n * `'REDACTED'` is what `UNIQUE(token_hash)` refused on the second row of\n * `rebase.refresh_tokens` — so on any project where someone had signed in twice.\n */\nexport interface AnonymizeRule {\n pattern: RegExp;\n /**\n * Only on tables whose name matches, when set. For a column name that is\n * personal on one table and not on another: a user's `name` is a person's,\n * a tag's is not.\n */\n tables?: RegExp;\n /** The value for a column no unique index covers, or null to always use the unique form. */\n value: string | null;\n /** The unique form is `prefix` + random hex + `suffix`, cut to fit the column. */\n prefix: string;\n suffix: string;\n}\n\n/**\n * Tables whose rows are people, by name: `rebase.users` and the usual spellings\n * of a project's own. Singular or plural.\n */\nconst PERSON_TABLES = /^(users?|user_profiles?|profiles?|people|persons?|members?|customers?|contacts?|employees?)$/i;\n\n/**\n * Column-name patterns the anonymizer overwrites.\n *\n * Names, not contents: inspecting values would be slower, and would still miss\n * the same things. This is a reasonable-effort measure for making a local copy\n * less dangerous, and it is not a compliance control.\n */\nexport const ANONYMIZE_PATTERNS: readonly AnonymizeRule[] = [\n // Emails are unique in practice even where nothing enforces it, so they\n // always get a value of their own.\n { pattern: /^(.*_)?e?mail(_.*)?$/i, value: null, prefix: \"user-\", suffix: \"@example.invalid\" },\n { pattern: /^(.*_)?(phone|mobile|tel|telephone)(_.*)?$/i, value: \"+10000000000\", prefix: \"+1-\", suffix: \"\" },\n // `display_name` is how `rebase.users` holds every user's name.\n {\n pattern: /^(.*_)?(display_name|first_name|last_name|full_name|middle_name|family_name|surname|given_name)(_.*)?$/i,\n value: \"Redacted\",\n prefix: \"Redacted-\",\n suffix: \"\"\n },\n // A bare `name` is a person's only on a table of people.\n { pattern: /^name$/i, tables: PERSON_TABLES, value: \"Redacted\", prefix: \"Redacted-\", suffix: \"\" },\n { pattern: /^(.*_)?(address|street|postcode|zip|zipcode)(_.*)?$/i, value: \"Redacted\", prefix: \"Redacted-\", suffix: \"\" },\n { pattern: /^(.*_)?(ssn|tax_id|national_id|passport)(_.*)?$/i, value: \"REDACTED\", prefix: \"REDACTED-\", suffix: \"\" },\n { pattern: /^(.*_)?(password|password_hash|secret|token|api_key|access_token|refresh_token)(_.*)?$/i, value: \"REDACTED\", prefix: \"REDACTED-\", suffix: \"\" },\n { pattern: /^(.*_)?(ip|ip_address|user_agent)(_.*)?$/i, value: \"REDACTED\", prefix: \"REDACTED-\", suffix: \"\" }\n];\n\n/** The rule that overwrites this column, if any. Without a table, a table-bound rule never applies. */\nfunction ruleFor(columnName: string, table?: string): AnonymizeRule | undefined {\n return ANONYMIZE_PATTERNS.find((rule) =>\n rule.pattern.test(columnName) && (!rule.tables || (table !== undefined && rule.tables.test(table))));\n}\n\nexport function shouldAnonymize(columnName: string, table?: string): boolean {\n return ruleFor(columnName, table) !== undefined;\n}\n\n/** A text-ish column the anonymizer can overwrite without a type error. */\nexport interface ColumnRef {\n schema: string;\n table: string;\n column: string;\n dataType: string;\n /** `character_maximum_length`: null or absent for an unbounded type. */\n maxLength?: number | null;\n /** Covered by a unique index — a primary key, a UNIQUE constraint, or a unique expression index. */\n unique?: boolean;\n}\n\n/** Random hex of this length, per row. `gen_random_uuid()` is core since PostgreSQL 13. */\nfunction randomHex(length: number): string {\n return `left(replace(gen_random_uuid()::text, '-', ''), ${length})`;\n}\n\nfunction sqlLiteral(value: string): string {\n return `'${value.replace(/'/g, \"''\")}'`;\n}\n\n/**\n * Fewest random hex digits a unique replacement keeps. Twelve is 16^12 values:\n * a collision needs millions of rows, where eight would need tens of thousands.\n */\nconst MIN_UNIQUE_HEX = 12;\n\n/**\n * The SQL expression that overwrites one column, or null when no rule names it.\n *\n * Unique per row when an index makes the column unique, and always cut to the\n * column's declared length — `zip char(5)` refused `'Redacted'` outright, and a\n * failed statement now empties the tables rather than skip one. To fit a short\n * unique column the prefix goes first, then the suffix, and the random part is\n * kept to at least {@link MIN_UNIQUE_HEX} digits while there is room for them.\n * A NULL stays NULL: an empty optional field is not personal data.\n */\nexport function replacementFor(column: ColumnRef): string | null {\n const rule = ruleFor(column.column, column.table);\n if (!rule) return null;\n const limit = typeof column.maxLength === \"number\" && column.maxLength > 0 ? column.maxLength : null;\n\n let value: string;\n if (rule.value !== null && !column.unique) {\n value = sqlLiteral(limit === null ? rule.value : rule.value.slice(0, limit));\n } else {\n const shapes = [[rule.prefix, rule.suffix], [\"\", rule.suffix], [\"\", \"\"]];\n const fitting = shapes.find(([prefix, suffix]) =>\n limit === null || limit - prefix.length - suffix.length >= MIN_UNIQUE_HEX);\n const [prefix, suffix] = fitting ?? [\"\", \"\"];\n const hex = Math.min(32, limit === null ? 32 : limit - prefix.length - suffix.length);\n value = `concat(${sqlLiteral(prefix)}, ${randomHex(Math.max(1, hex))}, ${sqlLiteral(suffix)})`;\n }\n\n const name = quoteIdentifier(column.column);\n return `CASE WHEN ${name} IS NULL THEN NULL ELSE ${value} END`;\n}\n\n/** A double-quoted identifier, with any `\"` in it doubled — a table called `order` or `a\"b` still works. */\nexport function quoteIdentifier(name: string): string {\n return `\"${name.replace(/\"/g, '\"\"')}\"`;\n}\n\n/**\n * Anonymizable columns: name looks personal, and the type can hold the\n * replacement.\n *\n * The type check is what stops this generating `UPDATE … SET user_id =\n * 'Redacted'` for an integer column called `user_id_email_seq` and failing the\n * whole pass on a technicality.\n */\nexport function anonymizableColumns(columns: readonly ColumnRef[]): ColumnRef[] {\n const textual = new Set([\"text\", \"character varying\", \"varchar\", \"character\", \"char\", \"citext\"]);\n\n return columns.filter((column) =>\n shouldAnonymize(column.column, column.table) && textual.has(column.dataType.toLowerCase()));\n}\n\n/** `\"schema\".\"table\"` for each table the pass rewrites, sorted. */\nexport function anonymizedTables(columns: readonly ColumnRef[]): string[] {\n return [...new Set(anonymizableColumns(columns).map((column) =>\n `${quoteIdentifier(column.schema)}.${quoteIdentifier(column.table)}`))].sort();\n}\n\n/** `UPDATE` statements for one anonymization pass, in a stable order. */\nexport function anonymizeStatements(columns: readonly ColumnRef[]): string[] {\n const byTable = new Map<string, ColumnRef[]>();\n for (const column of anonymizableColumns(columns)) {\n const key = `${quoteIdentifier(column.schema)}.${quoteIdentifier(column.table)}`;\n byTable.set(key, [...(byTable.get(key) ?? []), column]);\n }\n\n return [...byTable.entries()]\n .sort(([a], [b]) => a.localeCompare(b))\n .map(([table, cols]) => {\n const assignments = [...cols]\n .sort((a, b) => a.column.localeCompare(b.column))\n .map((column) => `${quoteIdentifier(column.column)} = ${replacementFor(column)}`)\n .join(\", \");\n\n return `UPDATE ${table} SET ${assignments};`;\n });\n}\n\n/**\n * Every column the pass could overwrite, with what decides how.\n *\n * Base tables only — a view has an `email` column too, and is not something to\n * UPDATE. Generated columns are skipped for the same reason: `email_domain\n * GENERATED ALWAYS AS (…)` matches by name and refuses any value but DEFAULT,\n * and it follows the column it is generated from anyway. An extension type such\n * as `citext` reports `USER-DEFINED`, so its own name is what is compared.\n *\n * `unique` comes from the indexes, through `pg_depend` as well as `indkey`: a\n * unique index on `lower(email)` names no column in `indkey`, and is exactly\n * what a constant replacement would violate.\n */\nexport const ANONYMIZE_COLUMNS_SQL = `\nWITH unique_columns AS (\n SELECT DISTINCT i.indrelid AS relid, a.attname AS column_name\n FROM pg_index i\n JOIN pg_attribute a ON a.attrelid = i.indrelid AND a.attnum > 0 AND NOT a.attisdropped\n WHERE i.indisunique\n AND (a.attnum = ANY (i.indkey)\n OR EXISTS (SELECT 1 FROM pg_depend d\n WHERE d.classid = 'pg_class'::regclass AND d.objid = i.indexrelid\n AND d.refclassid = 'pg_class'::regclass AND d.refobjid = i.indrelid\n AND d.refobjsubid = a.attnum))\n)\nSELECT c.table_schema AS \"schema\",\n c.table_name AS \"table\",\n c.column_name AS \"column\",\n CASE WHEN c.data_type = 'USER-DEFINED' THEN c.udt_name ELSE c.data_type END AS \"dataType\",\n c.character_maximum_length AS \"maxLength\",\n (u.column_name IS NOT NULL) AS \"unique\"\n FROM information_schema.columns c\n JOIN information_schema.tables t ON t.table_schema = c.table_schema AND t.table_name = c.table_name\n LEFT JOIN unique_columns u\n ON u.relid = format('%I.%I', c.table_schema, c.table_name)::regclass AND u.column_name = c.column_name\n WHERE t.table_type = 'BASE TABLE'\n AND c.is_generated = 'NEVER'\n AND c.table_schema NOT IN ('pg_catalog', 'information_schema')`;\n\n/** How {@link anonymizeCopy} ended. */\nexport type AnonymizeOutcome =\n | { ok: true; tables: string[] }\n | {\n ok: false;\n /** Why the pass failed. Nothing it wrote was kept. */\n error: string;\n /** The tables emptied instead, so none of their real values are left. */\n emptied: string[];\n /** Why they could not be emptied, when they could not: the copy then holds real data. */\n emptyError: string | null;\n };\n\nfunction errorText(error: unknown): string {\n return error instanceof Error && error.message ? error.message : String(error);\n}\n\n/**\n * Overwrite the personal-looking columns of a restored copy, all or nothing.\n *\n * One transaction, so a failure part-way leaves no table half done — the old\n * pass ran its UPDATEs one by one, so a failure on `rebase.refresh_tokens` left\n * `public.authors` anonymized and `rebase.users` holding every real email.\n *\n * A rollback alone would be worse than that, though: it puts back the real\n * value of every column, in a copy the developer asked to be anonymized. So on\n * failure the tables the pass would have rewritten are emptied (`TRUNCATE …\n * CASCADE`, which takes the rows that reference them too), and the outcome\n * says which — or, if even that failed, says the copy holds real data, so the\n * caller can say it louder.\n *\n * `row_security = off` makes a role that RLS would filter fail the pass instead\n * of rewriting only the rows its policies show it, which looked like success.\n */\nexport async function anonymizeCopy(client: Client): Promise<AnonymizeOutcome> {\n let listed = false;\n let tables: string[] = [];\n try {\n const { rows } = await client.query<ColumnRef>(ANONYMIZE_COLUMNS_SQL);\n tables = anonymizedTables(rows);\n listed = true;\n await client.query(\"BEGIN\");\n await client.query(\"SET LOCAL row_security = off\");\n for (const statement of anonymizeStatements(rows)) await client.query(statement);\n await client.query(\"COMMIT\");\n\n return { ok: true, tables };\n } catch (error) {\n const reason = errorText(error);\n await client.query(\"ROLLBACK\").catch(() => undefined);\n if (!listed) {\n return { ok: false, error: reason, emptied: [], emptyError: \"the tables to empty could not be listed\" };\n }\n if (tables.length === 0) return { ok: false, error: reason, emptied: [], emptyError: null };\n try {\n await client.query(`TRUNCATE TABLE ${tables.join(\", \")} CASCADE`);\n\n return { ok: false, error: reason, emptied: tables, emptyError: null };\n } catch (emptyError) {\n return { ok: false, error: reason, emptied: [], emptyError: errorText(emptyError) };\n }\n }\n}\n\n/**\n * Schemas the restored copy needs the app role provisioned on.\n *\n * `pg_dump --no-privileges` strips every GRANT, so a pulled database arrives\n * with its RLS policies and its `FORCE ROW LEVEL SECURITY` intact and no\n * privileges behind them. Measured on a 30-table project: 68 policies restored,\n * 14 tables with RLS on, and **0** grants to `rebase_user` — where the source\n * had 60. Reading one table as the role Rebase serves every request through:\n *\n * source: 6\n * copy: ERROR: permission denied for table leads\n *\n * The dump flags are right and stay: without `--no-owner`/`--no-privileges`\n * every `ALTER … OWNER TO` and `GRANT … TO <prod role>` in the dump fails\n * against roles that do not exist on a laptop, and buries the real output. The\n * repair belongs after the restore, and belongs to `ensureAppRole` — the same\n * routine boot and `rebase db push` call — rather than to a second list of\n * grants written here.\n *\n * Which schemas: every non-system schema the restored database actually has.\n * Boot knows its `managedSchemas` from the collections; a restore knows only\n * what arrived, and a source may carry schemas this project does not declare.\n * `pg_catalog`, `information_schema` and the `pg_*` internals are never ours to\n * grant on and PostgreSQL would refuse anyway.\n */\nexport function provisionableSchemas(rows: readonly { schema: string }[]): string[] {\n const skip = (name: string) => name === \"information_schema\" || name.startsWith(\"pg_\");\n\n return [...new Set(rows.map((row) => row.schema))].filter((name) => !skip(name)).sort();\n}\n\n/** Host and database of a connection string, with no credentials in it. */\nexport function describeTarget(connectionString: string): string {\n try {\n const url = new URL(connectionString);\n const database = url.pathname.replace(/^\\//, \"\") || \"(default)\";\n\n return `${url.hostname}${url.port ? `:${url.port}` : \"\"}/${database}`;\n } catch {\n // Never echo the raw string: it carries a password, and this line is\n // printed to a terminal people paste into issues.\n return \"(unparseable connection string)\";\n }\n}\n\n/**\n * What the local database runs on.\n *\n * `pglite` is the managed development database: one PGlite session behind a\n * socket server. Two things a restore relies on are not there. The server runs\n * each protocol message on its own, so the `CopyData` that follows a `COPY …\n * FROM stdin` never reaches the backend waiting for it: the COPY fails on\n * \"unexpected EOF\", and the session stops answering anyone until the daemon\n * is killed. And every connection shares that one session, so what pg_restore\n * `SET`s for itself stays set for everyone after it disconnects.\n */\nexport type PullTargetEngine = \"postgres\" | \"pglite\";\n\nexport interface PullPlan {\n /** Where the data comes from. */\n source: string;\n /** Where it lands. Always local. */\n target: string;\n anonymize: boolean;\n /** Schemas to copy. Empty means every non-system schema. */\n schemas: string[];\n /**\n * What the target runs on, which decides how the dump carries its rows.\n * Absent means `postgres`. {@link dumpSource} reads it off the target.\n */\n targetEngine?: PullTargetEngine;\n}\n\n/**\n * Rows per `INSERT` in a dump for the managed database.\n *\n * One per statement restored 100k rows in 8s; a hundred took 0.8s. A hundred\n * 512KB rows, a 100MB statement, still went in.\n */\nconst ROWS_PER_INSERT = 100;\n\n/**\n * `pg_dump` arguments for the source.\n *\n * `--no-owner` and `--no-acl` because the roles on a production server do not\n * exist locally, and without them every `ALTER … OWNER TO` in the dump fails and\n * buries the real output in noise. `--format=custom` so `pg_restore` can be told\n * to continue past errors selectively rather than all-or-nothing.\n *\n * For the managed database the rows travel as `INSERT`s, which PGlite takes\n * like any other statement, instead of `COPY` data, which it cannot take at\n * all — see {@link PullTargetEngine}. The copy is the same; only how the rows\n * are written in the dump differs.\n */\nexport function dumpArgs(plan: PullPlan): string[] {\n const args = [\"--format=custom\", \"--no-owner\", \"--no-acl\", \"--no-privileges\"];\n if (plan.targetEngine === \"pglite\") args.push(`--rows-per-insert=${ROWS_PER_INSERT}`);\n for (const schema of plan.schemas) args.push(\"--schema\", schema);\n args.push(\"--dbname\", splitPassword(plan.source).connectionString);\n\n return args;\n}\n\n/** The environment `pg_dump` runs with: the source's password, if it has one. */\nexport function dumpEnv(plan: PullPlan): Record<string, string> {\n return passwordEnv(plan.source);\n}\n\n/** The environment `pg_restore` runs with: the target's password, if it has one. */\nexport function restoreEnv(plan: PullPlan): Record<string, string> {\n return passwordEnv(plan.target);\n}\n\nfunction passwordEnv(connectionString: string): Record<string, string> {\n const { password } = splitPassword(connectionString);\n return password === null ? {} : { PGPASSWORD: password };\n}\n\n/**\n * A libpq connection string without its password, and the password.\n *\n * The password goes in `PGPASSWORD`, never on a command line, where every\n * process listing can read it and every failure prints it back (execa's error\n * repeats the command). The same rule `db backup` follows; this mirrors\n * `splitPassword` in `@rebasepro/server-postgres`'s connection-string utils,\n * which is not exported. Covers the user-info password and a `password=`\n * parameter; anything that does not parse as a postgres URL is left alone.\n */\nfunction splitPassword(connectionString: string): { connectionString: string; password: string | null } {\n let url: URL;\n try {\n url = new URL(connectionString);\n } catch {\n return { connectionString, password: null };\n }\n if (url.protocol !== \"postgres:\" && url.protocol !== \"postgresql:\") return { connectionString, password: null };\n\n const fromUserInfo = url.password ? decodePercent(url.password) : null;\n const fromParameter = url.searchParams.get(\"password\");\n if (fromUserInfo === null && fromParameter === null) return { connectionString, password: null };\n\n url.password = \"\";\n if (fromParameter !== null) {\n url.searchParams.delete(\"password\");\n // By hand: URLSearchParams writes a space as `+`, which libpq reads literally.\n url.search = Array.from(url.searchParams.entries())\n .map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`)\n .join(\"&\");\n }\n return { connectionString: url.toString(), password: fromUserInfo ?? fromParameter };\n}\n\nfunction decodePercent(value: string): string {\n try {\n return decodeURIComponent(value);\n } catch {\n return value;\n }\n}\n\n/**\n * `pg_restore` arguments for the target.\n *\n * It restores into schemas {@link restoreCopy} has already emptied, so\n * `--clean --if-exists` drops nothing that is there; it stays for whatever a\n * dump holds outside them — an extension, say — which would otherwise fail as\n * already existing. `--no-owner` for the same reason as the dump.\n */\nexport function restoreArgs(plan: PullPlan, dumpFile: string): string[] {\n return [\"--clean\", \"--if-exists\", \"--no-owner\", \"--no-privileges\", \"--dbname\", splitPassword(plan.target).connectionString, dumpFile];\n}\n\n/**\n * Is this PostgreSQL client tool on PATH, and what version? Both are checked\n * before anything destructive: discovering `pg_restore` is missing after the\n * dump has been taken — or after `--clean` has emptied the target — is the\n * worst possible ordering.\n */\nexport async function findPgTool(tool: \"pg_dump\" | \"pg_restore\"): Promise<string | null> {\n try {\n const { stdout } = await execa(tool, [\"--version\"]);\n\n return stdout.trim();\n } catch {\n return null;\n }\n}\n\n/** PGlite names itself in `version()`: `PostgreSQL 18.3 (PGlite 0.5.6) on wasm32-…`. */\nfunction engineOf(version: string): PullTargetEngine {\n return /\\bPGlite\\b/i.test(version) ? \"pglite\" : \"postgres\";\n}\n\n/** `host:port` of a connection string, for a message: never the string, which carries the password. */\nfunction hostOf(connectionString: string): string {\n try {\n const url = new URL(connectionString);\n\n return `${url.hostname || \"localhost\"}:${url.port || \"5432\"}`;\n } catch {\n return \"(unparseable connection string)\";\n }\n}\n\n/**\n * A connection error's own sentence. A refused connection on a dual-stack\n * host is an `AggregateError` with an empty message, and its sentence —\n * `connect ECONNREFUSED 127.0.0.1:5432` — is in the attempts it holds.\n */\nfunction connectionErrorText(error: unknown): string {\n if (error instanceof AggregateError && error.errors.length > 0 && !error.message) {\n return connectionErrorText(error.errors[0]);\n }\n\n return errorText(error);\n}\n\nconst RULE = \"━\".repeat(60);\n\n/**\n * The boxed banner the database commands print when a connection fails —\n * `Cannot connect to PostgreSQL at …` — as plain lines for the caller to print.\n */\nfunction banner(title: string, said: string | null, body: readonly string[]): string[] {\n return [\"\", RULE, ` ❌ ${title}`, RULE, \"\", ...(said ? [` ${said}`, \"\"] : []), ...body.map(line => ` ${line}`), \"\", RULE];\n}\n\nconst NOTHING_CHANGED = \"Nothing was changed: the local database is as it was.\";\n\n/** What the target database runs on, or why it could not be asked. */\nasync function readTargetEngine(target: string): Promise<{ engine: PullTargetEngine } | { error: unknown }> {\n const { Client } = await import(\"pg\");\n const client = new Client({ connectionString: target, connectionTimeoutMillis: 10_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\n return { engine: engineOf(rows[0]?.version ?? \"\") };\n } catch (error) {\n return { error };\n } finally {\n await client.end().catch(() => undefined);\n }\n}\n\n/** How {@link dumpSource} ended. */\nexport type DumpOutcome =\n | {\n ok: true;\n /** The plan the dump was taken for, with its target's engine read: what {@link restoreCopy} takes. */\n plan: PullPlan;\n }\n | {\n ok: false;\n /** What failed and what to do, as plain lines for the caller to print. Nothing was changed. */\n lines: string[];\n };\n\n/**\n * `pg_dump` the source into `dumpFile`, in the form the target can restore.\n *\n * It asks the target what it runs on first — the managed database cannot take\n * `COPY` data, see {@link PullTargetEngine} — which also finds a local\n * database that is not there before anything is read from the source.\n *\n * pg_dump's stderr is kept rather than shown as it arrives, so a failure is\n * reported once, in the reader's terms: an unreachable source, or credentials\n * it refused, get the banner every database command prints for a connection\n * that failed, and anything else is pg_dump's own words with a line saying\n * nothing was changed. A dump that succeeds with warnings still shows them.\n * A dump that fails leaves no file behind.\n */\nexport async function dumpSource(plan: PullPlan, dumpFile: string): Promise<DumpOutcome> {\n try {\n const outcome = await dumpInto(plan, dumpFile);\n if (!outcome.ok) fs.rmSync(dumpFile, { force: true });\n\n return outcome;\n } catch (error) {\n fs.rmSync(dumpFile, { force: true });\n throw error;\n }\n}\n\nasync function dumpInto(plan: PullPlan, dumpFile: string): Promise<DumpOutcome> {\n let targetEngine = plan.targetEngine;\n if (!targetEngine) {\n const read = await readTargetEngine(plan.target);\n if (\"error\" in read) {\n return {\n ok: false,\n lines: banner(`Cannot connect to PostgreSQL at ${hostOf(plan.target)}`,\n `The driver said: ${connectionErrorText(read.error)}`, [\n \"That is the local database the pull would replace, and nothing\",\n \"was read from the source.\",\n \"Start it — `rebase dev` starts the managed one, `rebase dev --docker`\",\n \"Postgres in Docker — or check DATABASE_URL in .env, then pull again.\"\n ])\n };\n }\n targetEngine = read.engine;\n }\n\n const planned: PullPlan = { ...plan, targetEngine };\n const run = await execa(\"pg_dump\", [...dumpArgs(planned), \"--file\", dumpFile], {\n env: dumpEnv(planned),\n stdin: \"ignore\",\n stdout: \"inherit\",\n stderr: \"pipe\",\n reject: false\n });\n if (run.exitCode === 0) {\n if (run.stderr.trim()) process.stderr.write(`${run.stderr.trimEnd()}\\n`);\n\n return { ok: true, plan: planned };\n }\n\n return { ok: false, lines: describeDumpFailure(plan.source, run) };\n}\n\n/**\n * Why `pg_dump` could not read the source, as plain lines for the caller to\n * print. The dump is taken before anything local is touched, so every one of\n * them ends the same way: nothing was changed.\n */\nexport function describeDumpFailure(source: string, run: RestoreRun): string[] {\n if (run.signal) return [`✗ pg_dump was stopped by ${run.signal} before it finished.`, NOTHING_CHANGED];\n if (run.exitCode === undefined) {\n return [\n `✗ pg_dump could not be started (${run.code ?? \"unknown error\"}).`,\n NOTHING_CHANGED,\n \"Check that the PostgreSQL client tools are installed (`pg_dump --version`).\"\n ];\n }\n\n const stderr = (run.stderr ?? \"\").trim();\n const host = hostOf(source);\n const error = /pg_dump: error: (.+)/.exec(stderr)?.[1]?.trim() ?? null;\n const said = error ? `pg_dump said: ${error}` : null;\n\n if (/could not translate host name|could not connect to server|connection to server .* failed: (?:Connection refused|timeout expired|No route to host|Network is unreachable|Operation timed out|Host is down)/i.test(stderr)) {\n return banner(`Cannot connect to PostgreSQL at ${host}`, said, [\n \"That is the database --from names, which the pull reads.\",\n NOTHING_CHANGED,\n \"Check the host and port in --from, and that this machine can reach\",\n \"that server: a VPN, an allow-list, or an SSH tunnel.\"\n ]);\n }\n if (/password authentication failed|no pg_hba\\.conf entry|role \"[^\"]+\" does not exist/i.test(stderr)) {\n let user = \"unknown\";\n try {\n user = decodePercent(new URL(source).username) || \"unknown\";\n } catch { /* named as unknown */ }\n return banner(`Authentication failed for user \"${user}\" at ${host}`, said, [\n \"PostgreSQL rejected the credentials --from carries.\",\n NOTHING_CHANGED,\n \"Check the user and password in --from.\"\n ]);\n }\n\n const versions = /server version mismatch/i.test(stderr)\n ? [\"pg_dump is older than the server: install client tools at least as new as it (`brew install libpq`).\"]\n : [];\n return [\n ...(stderr ? stderr.split(\"\\n\") : []),\n `✗ pg_dump could not dump ${describeTarget(source)} (exit code ${run.exitCode}).`,\n NOTHING_CHANGED,\n ...versions\n ];\n}\n\n/** The fields of a finished `pg_restore` (or `pg_dump`) run that decide {@link restoreOutcome}. */\nexport interface RestoreRun {\n exitCode?: number;\n signal?: string;\n /** A spawn failure's code — `ENOENT` when the binary vanished. */\n code?: string;\n stderr?: string;\n}\n\n/** What a `pg_restore` run says about the copy. */\nexport interface RestoreRunOutcome {\n /** Whether anything can have arrived — what decides whether an anonymization pass has data to redact. */\n started: boolean;\n /** Null when the copy is complete. */\n failure: string | null;\n /**\n * What to do about the failure. \"Pull again\" only where a second pull is\n * what finishes the job: everything else repeats until its cause is fixed.\n */\n advice: string | null;\n}\n\n/**\n * What a finished `pg_restore` says about the copy.\n *\n * The target was emptied before anything was written, so every way this can\n * end short of exit 0 leaves a partial or empty local database, and none of\n * them may be reported as a copy. A non-zero exit is pg_restore saying it\n * skipped statements — \"errors ignored on restore: N\" — and whatever those\n * statements created (a table, its rows, an RLS policy) is missing locally.\n *\n * Restoring into an emptied database, those errors come from what the source\n * holds that this Postgres cannot create — an extension it does not have, a\n * feature of a newer server — and a second pull meets them again. So the\n * advice names that, rather than \"pull again\", which sent people in a loop.\n * On the managed database an extension cannot be installed at all, so the\n * advice there is Postgres in Docker.\n */\nexport function restoreOutcome(run: RestoreRun, engine: PullTargetEngine = \"postgres\"): RestoreRunOutcome {\n if (run.signal) {\n return {\n started: true,\n failure: `pg_restore was stopped by ${run.signal} before it finished.`,\n advice: \"It was interrupted, not refused: pull again to finish the copy.\"\n };\n }\n if (run.exitCode === undefined) {\n return {\n started: false,\n failure: `pg_restore could not be started (${run.code ?? \"unknown error\"}).`,\n advice: \"Check that the PostgreSQL client tools are installed (`pg_restore --version`), then pull again.\"\n };\n }\n if (run.exitCode === 0) return { started: true, failure: null, advice: null };\n\n const ignored = /errors ignored on restore: (\\d+)/.exec(run.stderr ?? \"\");\n const repeats = engine === \"pglite\"\n ? \"Pulling again repeats these errors until what they name is fixed — most often an extension the \"\n + `source uses that the managed database does not have (it has ${PGLITE_EXTENSIONS.map(extension => extension.name).join(\", \")}). `\n + \"Pull into Postgres in Docker instead (`rebase db pull --docker`), or pull only the schemas that do \"\n + \"not need it (--schema).\"\n : \"Pulling again repeats these errors until what they name is fixed — most often an extension \"\n + \"the source uses that this Postgres does not have installed. Install it here, or pull only the schemas \"\n + \"that do not need it (--schema).\";\n\n return {\n started: true,\n failure: ignored\n ? `pg_restore could not restore ${ignored[1]} object(s) — the errors are above.`\n : `pg_restore exited with code ${run.exitCode} — the errors are above.`,\n advice: repeats\n };\n}\n\n/** How {@link restoreCopy} ended. */\nexport interface RestoreOutcome extends RestoreRunOutcome {\n /** Whether the local database was emptied first. False means it is exactly as it was. */\n cleared: boolean;\n}\n\n/**\n * The local schemas a pull empties before it restores: every one of the local\n * database's own for a whole-database pull, and those it copies for a\n * `--schema` pull. `pg_catalog`, `information_schema` and the `pg_*` internals\n * never — see {@link provisionableSchemas}.\n */\nexport function schemasToClear(present: readonly { schema: string }[], planned: readonly string[]): string[] {\n const local = provisionableSchemas(present);\n\n return planned.length === 0 ? local : local.filter((schema) => planned.includes(schema));\n}\n\n/**\n * The statements that empty those schemas, in one transaction.\n *\n * `CASCADE`, which `pg_restore --clean` never uses: a table the source lacks —\n * `rebase.atlas_schema_revisions` from `rebase db migrate`, a collection added\n * locally that references a pulled table — is exactly what blocked its plain\n * `DROP SCHEMA` and `DROP TABLE`. `public` is put back empty, with the access\n * every role has to it by default, because a dump does not create a default\n * `public` and every table in it would otherwise have nowhere to go.\n */\nexport function clearStatements(schemas: readonly string[]): string[] {\n const statements = schemas.map((schema) => `DROP SCHEMA IF EXISTS ${quoteIdentifier(schema)} CASCADE`);\n if (schemas.includes(\"public\")) statements.push(\"CREATE SCHEMA public\", \"GRANT USAGE ON SCHEMA public TO PUBLIC\");\n\n return statements;\n}\n\n/**\n * How long emptying the local database waits for a lock before giving up.\n *\n * A `rebase dev` serving from the database holds connections, and one inside\n * a transaction would otherwise make the pull hang without a word.\n */\nconst CLEAR_LOCK_TIMEOUT = \"15s\";\n\n/** Why emptying the local database failed, in the reader's terms, and what fixes it. */\nfunction clearFailure(error: unknown): RestoreOutcome {\n const code = typeof error === \"object\" && error !== null && \"code\" in error ? String(error.code) : \"\";\n const reason = errorText(error);\n const advice = code === \"55P03\"\n ? \"Something holds a lock on it — usually `rebase dev` serving from it. Stop that, then pull again.\"\n : code === \"42501\"\n ? \"This connection may not drop those schemas. Connect as the role that owns them — the one \"\n + \"that created them — and pull with that.\"\n : \"Fix what the error names; pulling again before that repeats it.\";\n\n return {\n cleared: false,\n started: false,\n failure: `Could not empty the local database before restoring: ${reason}`,\n advice\n };\n}\n\n/**\n * Replace the local database's contents with a dump: empty the schemas it\n * holds, then `pg_restore` into them.\n *\n * Only ever `plan.target`, the local development database the command\n * resolved — the pull's one destination, which no flag can change. Emptying it\n * is one transaction: if any schema cannot be dropped, none is, nothing is\n * restored, and the database is as it was.\n *\n * On the managed database the dump must be one {@link dumpSource} took for it,\n * with its rows as INSERTs: a COPY would leave the daemon answering nobody, so\n * one is refused before anything is emptied. And afterwards its one session —\n * every connection's — is given back its own settings: pg_restore leaves\n * `search_path` empty and `row_security` off behind it, which a running\n * `rebase dev` would otherwise query under.\n */\nexport async function restoreCopy(plan: PullPlan, dumpFile: string): Promise<RestoreOutcome> {\n const { Client } = await import(\"pg\");\n const client = new Client({ connectionString: plan.target });\n client.on(\"error\", () => undefined);\n let engine: PullTargetEngine;\n try {\n await client.connect();\n engine = engineOf((await client.query<{ version: string }>(\"SELECT version() AS version\")).rows[0]?.version ?? \"\");\n if (engine === \"pglite\" && plan.targetEngine !== \"pglite\") {\n return {\n cleared: false,\n started: false,\n failure: \"The managed development database cannot take this dump: its rows are COPY data, which PGlite's socket server cannot receive.\",\n advice: \"Pull into Postgres in Docker instead: `rebase db pull --docker`.\"\n };\n }\n const { rows } = await client.query<{ schema: string }>(\"SELECT nspname AS \\\"schema\\\" FROM pg_namespace\");\n await client.query(\"BEGIN\");\n await client.query(`SET LOCAL lock_timeout = '${CLEAR_LOCK_TIMEOUT}'`);\n for (const statement of clearStatements(schemasToClear(rows, plan.schemas))) await client.query(statement);\n await client.query(\"COMMIT\");\n } catch (error) {\n await client.query(\"ROLLBACK\").catch(() => undefined);\n return clearFailure(error);\n } finally {\n await client.end().catch(() => undefined);\n }\n\n // stderr is shown as it arrives and kept, for the count in its summary\n // line. Not rejected: every way this ends is judged by `restoreOutcome`.\n const run = await execa(\"pg_restore\", restoreArgs(plan, dumpFile), {\n env: restoreEnv(plan),\n stdin: \"ignore\",\n stdout: \"inherit\",\n stderr: [\"pipe\", \"inherit\"],\n reject: false\n });\n\n if (engine === \"pglite\") await resetSharedSession(plan.target);\n\n return { cleared: true, ...restoreOutcome(run, engine) };\n}\n\n/**\n * Put the managed database's one session back to its own settings.\n *\n * `RESET ALL`, not `DISCARD ALL`: the session is every connection's, and\n * `DISCARD` would also drop the `LISTEN` a running `rebase dev`'s realtime\n * holds on it. Best effort: a daemon that cannot take this cannot take the\n * re-grant that follows either, and that step reports it.\n */\nasync function resetSharedSession(target: string): Promise<void> {\n const { Client } = await import(\"pg\");\n const client = new Client({ connectionString: target, connectionTimeoutMillis: 10_000, query_timeout: 15_000 });\n client.on(\"error\", () => undefined);\n try {\n await client.connect();\n await client.query(\"RESET ALL\");\n } catch {\n // Reported by the next step that connects; see above.\n } finally {\n await client.end().catch(() => undefined);\n }\n}\n\n/**\n * The lines that report a failed restore: what failed, what state the local\n * database is in now, and what to do. Plain text, for the caller to colour.\n */\nexport function describeRestoreFailure(outcome: RestoreOutcome, source: string): string[] {\n const state = outcome.started\n ? `The local database holds an incomplete copy of ${source}.`\n : outcome.cleared\n ? \"The local database was emptied, and nothing was restored into it.\"\n : \"Nothing was restored, and the local database is as it was.\";\n\n return [outcome.failure ?? \"The restore did not complete.\", state, ...(outcome.advice ? [outcome.advice] : [])];\n}\n"],"mappings":"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"}
|
package/dist/resolve-b_p9bmdH.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"start-BsPJedPQ.js","names":[],"sources":["../src/commands/start.ts"],"sourcesContent":["/**\n * CLI command: rebase start\n *\n * Runs a built bundle through the Rebase runtime — the same path the official\n * container image takes, so what you test locally is what a deployment runs.\n *\n * When there is no bundle (an ejected backend, or a project that has not adopted\n * `rebase.json`) this falls back to the backend workspace's own `start` script,\n * which is what such a project has always used.\n */\nimport fs from \"fs\";\nimport path from \"path\";\nimport chalk from \"chalk\";\nimport { execa } from \"execa\";\nimport { requireProjectRoot, findEnvFile, readEnvFile } from \"../utils/project\";\nimport { parseCommandArgs, wantsHelp } from \"../utils/args\";\nimport { detectPackageManager, getPMCommands, type PMCommands } from \"../utils/package-manager\";\nimport { DEFAULT_BUNDLE_DIR } from \"../bundle\";\nimport type { RebaseBundleStatic } from \"@rebasepro/types\";\nimport type { DevDatabase } from \"../dev-db/resolve\";\nimport type { DaemonState } from \"../dev-db/state\";\nimport { MANAGED_POOL_MAX } from \"../dev-db/constraints\";\n\nfunction printHelp(): void {\n console.log(`\n${chalk.bold(\"rebase start\")} — run a built bundle\n\n${chalk.bold(\"Usage\")}\n rebase start [options]\n\n${chalk.bold(\"Options\")}\n --bundle <dir> Bundle directory (default: ${DEFAULT_BUNDLE_DIR})\n --workspace Run the backend workspace's own start script\n -h, --help Show this help\n\nRuns the bundle the way a deployment does, reading PORT and the rest of .env,\nin whatever NODE_ENV the shell or .env sets. A scaffolded .env says\ndevelopment, which keeps development behaviour on — the first account to\nregister becomes the admin. For a production server, run it with\nNODE_ENV=production; it says so at start when it is not one.\n\nIt needs a DATABASE_URL, and starts no database of its own. To run it on the\ndatabase ${chalk.cyan(\"rebase dev\")} uses: ${chalk.cyan('DATABASE_URL=\"$(rebase db url)\" rebase start')}\n\nBuild first with ${chalk.cyan(\"rebase build\")}.\n`.trim());\n}\n\n/** The NODE_ENV the runtime will see, and where it comes from. */\nexport interface StartNodeEnv {\n value: string | undefined;\n source: \"shell\" | \".env\" | \"unset\";\n}\n\n/**\n * What NODE_ENV the server will boot in. The shell wins — dotenv never\n * overrides a variable that is already set — then `.env`.\n */\nexport function startNodeEnv(projectRoot: string): StartNodeEnv {\n if (process.env.NODE_ENV !== undefined) return { value: process.env.NODE_ENV, source: \"shell\" };\n const fromFile = readEnvFile(projectRoot).NODE_ENV;\n return fromFile !== undefined ? { value: fromFile, source: \".env\" } : { value: undefined, source: \"unset\" };\n}\n\n/**\n * The banner for a `rebase start` that is not a production server, or `null`.\n *\n * Not forced to production: the scaffold's `.env` points at a loopback\n * database, which a production boot refuses, so forcing it would break the\n * documented local run. What must not happen is the silence — a command the\n * help called \"(production)\" booting with the first-registration window open.\n */\nexport function nonProductionWarning(nodeEnv: StartNodeEnv): string[] | null {\n if (nodeEnv.value === \"production\") return null;\n const what = nodeEnv.value === undefined\n ? \"NODE_ENV is not set\"\n : `NODE_ENV=${nodeEnv.value}${nodeEnv.source === \".env\" ? \" (from .env)\" : \"\"}`;\n return [\n chalk.yellow.bold(`⚠ ${what} — this is not a production server.`),\n chalk.yellow(\" Development behaviour is on: the first account to register becomes the admin\"),\n chalk.yellow(\" (REBASE_ADMIN_EMAIL is ignored), and with no SMTP configured, sign-in and\"),\n chalk.yellow(\" password-reset links are printed to this log instead of sent.\"),\n chalk.gray(\" For a production server, run it with NODE_ENV=production (in the shell or .env).\")\n ];\n}\n\n/**\n * What to say about the apps a bundle serves only on their own hostname.\n *\n * The runtime prints \"Server running at http://localhost:<port>\", and an app\n * whose `path` names a hostname answers only requests carrying that `Host` —\n * so opening the printed URL showed a 404 for an app that was running fine,\n * and nothing said why. One line per such app, with a command that reaches it.\n */\nexport function hostRoutedNotice(staticApps: readonly RebaseBundleStatic[], port: string | undefined): string[] {\n const routed = staticApps.filter(app => app.host);\n if (routed.length === 0) return [];\n const origin = `http://localhost:${port || \"<port>\"}`;\n return [\n ...routed.map(app => {\n const name = app.name ?? path.posix.basename(app.dir);\n return chalk.yellow(`⚠ ${name} answers only on ${app.host}, so ${origin}${app.path} will not show it here. Reach it with:`)\n + `\\n ${chalk.cyan(`curl -H \"Host: ${app.host}\" ${origin}${app.path}`)}`;\n }),\n chalk.gray(` (or point ${routed.map(app => app.host).join(\", \")} at 127.0.0.1 in /etc/hosts)`),\n \"\"\n ];\n}\n\n/**\n * What to say when there is no DATABASE_URL for the runtime, or `null`.\n *\n * The runtime requires one, and its refusal — `DATABASE_URL: is required`, and\n * a link to the self-hosting docs — is right for a deployment and says nothing\n * to the reader most likely to see it: someone running the scaffold's own\n * `.env`, which leaves DATABASE_URL commented out because `rebase dev` brings a\n * database of its own. That database is one command away, so this names it.\n *\n * `kind` is what `rebase dev` runs this project on, so the line can say which\n * database `rebase db url` will print. A blank value is unset, as the runtime\n * reads it. `pm` spells the command: a scaffold has `rebase` in\n * `node_modules/.bin`, not on PATH, so it is `pnpm exec rebase …` or\n * `npx rebase …`, as in the CLI's other hints.\n */\nexport function missingDatabaseUrl(\n env: Record<string, string | undefined>,\n kind: DevDatabase[\"kind\"] | null,\n pm: PMCommands\n): string[] | null {\n if (env.DATABASE_URL?.trim()) return null;\n\n const rebase = (args: string[]): string => pm.exec(\"rebase\", args).join(\" \");\n\n const which = kind === \"docker\"\n ? \"the Docker database `rebase dev --docker` uses\"\n : kind === \"managed\"\n ? \"the managed development database (PGlite) `rebase dev` uses\"\n : \"this project's development database\";\n return [\n chalk.red(\"✗ DATABASE_URL is not set, so the runtime has no database to start against.\"),\n chalk.gray(\" `rebase start` runs the bundle the way a deployment does, against the database you name;\"),\n chalk.gray(\" `rebase dev` is the command that brings one of its own.\"),\n \"\",\n ` To run it on ${which}:`,\n ` ${chalk.cyan(`DATABASE_URL=\"$(${rebase([\"db\", \"url\"])})\" ${rebase([\"start\"])}`)}`,\n \"\",\n chalk.gray(\" For a deployment, set DATABASE_URL in the environment or in .env.\"),\n chalk.gray(\" See https://rebase.pro/docs/deployment/self-hosting/ for the variables a deployment needs.\")\n ];\n}\n\n/**\n * What the runtime needs when DATABASE_URL is this project's managed\n * development database, or nothing.\n *\n * The connection ceiling `rebase dev` sets for it (`MANAGED_POOL_MAX`). PGlite\n * is one session behind a socket server that accepts only a few connections,\n * and a running `rebase dev` holds some. With an ordinary pool, the boot's\n * concurrent probes opened more than were left, the ones over were refused,\n * and a probe that could not connect read as a table that was not there:\n * `❌ rebase.cron_claims is unavailable`, beside tables that existed.\n *\n * Recognised by the running daemon's ports, which is what `rebase db url`\n * prints from. A ceiling set by hand is kept.\n */\nexport function managedDatabaseRuntimeEnv(\n databaseUrl: string | undefined,\n daemon: DaemonState | null,\n env: Record<string, string | undefined>\n): Record<string, string> {\n if (!databaseUrl || !daemon || env.REBASE_DB_POOL_MAX !== undefined) return {};\n let url: URL;\n try {\n url = new URL(databaseUrl);\n } catch {\n return {};\n }\n if (![\"127.0.0.1\", \"localhost\", \"[::1]\"].includes(url.hostname)) return {};\n const ports = [daemon.port, ...Object.values(daemon.databases ?? {}).map(database => database.port)];\n if (!ports.includes(Number(url.port))) return {};\n return { REBASE_DB_POOL_MAX: String(MANAGED_POOL_MAX) };\n}\n\nfunction readBundleStatic(bundleDir: string): RebaseBundleStatic[] {\n try {\n const manifest = JSON.parse(fs.readFileSync(path.join(bundleDir, \"manifest.json\"), \"utf8\")) as { entry?: { static?: RebaseBundleStatic[] } };\n return manifest.entry?.static ?? [];\n } catch {\n return [];\n }\n}\n\nfunction printNonProductionWarning(projectRoot: string): void {\n const warning = nonProductionWarning(startNodeEnv(projectRoot));\n if (!warning) return;\n console.log(warning.join(\"\\n\") + \"\\n\");\n}\n\nexport async function startCommand(rawArgs: string[] = []): Promise<void> {\n if (wantsHelp(rawArgs)) {\n printHelp();\n return;\n }\n\n const { flags: args } = parseCommandArgs({\n spec: {\n \"--bundle\": String,\n \"--workspace\": Boolean\n },\n rawArgs,\n commandWords: 1,\n command: \"start\",\n maxPositionals: 0\n });\n\n const projectRoot = requireProjectRoot();\n\n const envFile = findEnvFile(projectRoot);\n const env: Record<string, string> = { ...process.env as Record<string, string> };\n if (envFile) {\n env.DOTENV_CONFIG_PATH = envFile;\n }\n\n const bundleDir = path.resolve(projectRoot, args[\"--bundle\"] ?? DEFAULT_BUNDLE_DIR);\n const hasBundle = fs.existsSync(path.join(bundleDir, \"manifest.json\"));\n\n // Before either path, and before the runtime's own boot logs bury it.\n printNonProductionWarning(projectRoot);\n\n if (args[\"--workspace\"] || !hasBundle) {\n if (!args[\"--workspace\"] && !hasBundle) {\n console.log(chalk.dim(\n `No bundle at ${path.relative(projectRoot, bundleDir)}/ — ` +\n \"starting the backend workspace instead.\\n\"\n ));\n }\n await startWorkspaceBackend(projectRoot, env);\n return;\n }\n\n ensureBundleDependencies(projectRoot, bundleDir);\n\n console.log(`${chalk.bold(\"Rebase\")} — starting runtime from ${chalk.cyan(path.relative(projectRoot, bundleDir))}/\\n`);\n\n // Loaded in-process rather than spawned: the runtime is a library here, so\n // signals, exit codes and stdio need no forwarding, and there is one less\n // process between the developer and a stack trace.\n if (envFile && fs.existsSync(envFile)) {\n const dotenv = await import(\"dotenv\");\n dotenv.config({ path: envFile, quiet: true });\n }\n\n // Before the runtime, which would refuse with a message written for a\n // deployment. See `missingDatabaseUrl`.\n if (!process.env.DATABASE_URL?.trim()) {\n const { devDatabaseKind } = await import(\"../dev-db/prepare\");\n const missing = missingDatabaseUrl(process.env, devDatabaseKind(projectRoot), getPMCommands(detectPackageManager(projectRoot)));\n if (missing) {\n for (const line of missing) console.error(line);\n process.exit(1);\n }\n }\n\n // See `managedDatabaseRuntimeEnv`.\n const { findRunningDaemon } = await import(\"../dev-db/daemon\");\n const managedEnv = managedDatabaseRuntimeEnv(process.env.DATABASE_URL, await findRunningDaemon(projectRoot), process.env);\n if (managedEnv.REBASE_DB_POOL_MAX) {\n Object.assign(process.env, managedEnv);\n console.log(chalk.dim(\n \"DATABASE_URL is this project's managed development database: the runtime uses one connection,\\n\" +\n \"as `rebase dev` does, so requests are served one at a time.\\n\"\n ));\n }\n\n process.env.REBASE_BUNDLE = bundleDir;\n\n for (const line of hostRoutedNotice(readBundleStatic(bundleDir), process.env.PORT)) console.log(line);\n\n try {\n const { runFromBundle } = await import(\"@rebasepro/server\");\n await runFromBundle({ bundleDir });\n } catch (err) {\n console.error(chalk.red(\"\\n✗ Failed to start the runtime.\"));\n console.error(err instanceof Error ? err.message : String(err));\n process.exit(1);\n }\n}\n\n/**\n * Make a bundle's imports resolvable for a local run.\n *\n * Node resolves a module by walking up from the *importing file*, so compiled\n * code sitting in `dist-bundle/` no longer sees the per-package `node_modules`\n * its source could: pnpm and npm both install a workspace package's\n * dependencies inside that package, and the bundle is not inside any of them.\n *\n * A deployment solves this by installing the bundle's own `package.json` beside\n * it — that is what the generated `package.json` is for. Locally, doing a second\n * install to run code whose dependencies are already on disk would be wasteful,\n * so this links what is already there instead.\n *\n * Only ever created when absent, and only under the bundle directory, so a real\n * install always wins and nothing here is ever uploaded (`rebase build` cleans\n * the directory and never writes this).\n */\nfunction ensureBundleDependencies(projectRoot: string, bundleDir: string): void {\n const target = path.join(bundleDir, \"node_modules\");\n if (fs.existsSync(target)) return;\n\n // Later sources fill gaps left by earlier ones; the backend's tree wins\n // because it holds the server and driver the runtime itself needs.\n const sources = [\"backend/node_modules\", \"config/node_modules\", \"node_modules\"]\n .map(relative => path.join(projectRoot, relative))\n .filter(dir => fs.existsSync(dir));\n\n if (sources.length === 0) return;\n\n let linked = 0;\n fs.mkdirSync(target, { recursive: true });\n\n const linkInto = (sourceDir: string, targetDir: string): void => {\n let entries: fs.Dirent[];\n try {\n entries = fs.readdirSync(sourceDir, { withFileTypes: true });\n } catch {\n return;\n }\n\n for (const entry of entries) {\n if (entry.name === \".bin\" || entry.name.startsWith(\".\")) continue;\n const from = path.join(sourceDir, entry.name);\n const to = path.join(targetDir, entry.name);\n\n // A scope is a directory of packages, not a package — merge into it\n // so `@a/one` from one tree and `@a/two` from another both resolve.\n if (entry.name.startsWith(\"@\") && entry.isDirectory()) {\n fs.mkdirSync(to, { recursive: true });\n linkInto(from, to);\n continue;\n }\n\n if (fs.existsSync(to)) continue;\n try {\n fs.symlinkSync(fs.realpathSync(from), to, \"junction\");\n linked++;\n } catch {\n // A package that cannot be linked simply stays unresolved, and\n // the runtime will say so by name if it actually needed it.\n }\n }\n };\n\n for (const source of sources) linkInto(source, target);\n\n if (linked > 0) {\n console.log(chalk.dim(\n ` linked ${linked} package(s) into the bundle for this local run\\n` +\n \" (a deployment installs the bundle's package.json instead)\\n\"\n ));\n }\n}\n\nasync function startWorkspaceBackend(projectRoot: string, env: Record<string, string>): Promise<void> {\n const pm = detectPackageManager(projectRoot);\n const cmds = getPMCommands(pm);\n const startCmd = cmds.runWorkspace(\"backend\", \"start\");\n\n console.log(`${chalk.bold(\"Rebase\")} — Starting backend server...\\n`);\n\n try {\n await execa(startCmd[0], startCmd.slice(1), {\n cwd: projectRoot,\n stdio: \"inherit\",\n env\n });\n } catch {\n console.error(chalk.red(\"\\n✗ Failed to start server.\"));\n process.exit(1);\n }\n}\n"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"state-CCB-FAQ9.js","names":[],"sources":["../src/dev-db/state.ts"],"sourcesContent":["/**\n * The managed database's state file, and the rules for trusting it.\n *\n * The daemon outlives the command that started it — `rebase db push` in one\n * terminal and `rebase dev` in another have to reach the *same* PGlite, because\n * two processes opening one data directory would corrupt it. So the daemon\n * records where it is, and every command reads that record.\n *\n * A record on disk is a claim, not a fact. The process it names may have been\n * killed, the machine may have rebooted and handed the pid to something else,\n * and the port may now belong to a stranger. {@link readState} therefore only\n * parses; deciding whether a record is live is {@link isDaemonAlive}'s job, and\n * it asks the daemon rather than the operating system.\n */\n\nimport { createHash } from \"crypto\";\nimport fs from \"fs\";\nimport { DEFAULT_RESOURCE_KEY, resourceEnvSuffix } from \"@rebasepro/types\";\nimport net from \"net\";\nimport path from \"path\";\n\n/** Everything under here is generated and gitignored. */\nexport const DEV_DB_DIR = \".rebase\";\n/** PGlite's own data directory. Deleting it is what `--reset` means. */\nexport const DATA_DIR_NAME = \"pgdata\";\n/** The record the daemon writes once it is accepting connections. */\nexport const STATE_FILE_NAME = \"pglite.json\";\n/**\n * Held by whoever is currently starting a daemon.\n *\n * Without it, `rebase dev` and `rebase db push` started in the same second both\n * see no state file, both spawn, and two processes open one PGlite data\n * directory — the exact corruption the single-daemon design exists to prevent.\n * Observed as `ENOTEMPTY` during cleanup, which is the harmless way for it to\n * show up; the harmful way is a damaged database.\n */\nexport const START_LOCK_NAME = \"starting.lock\";\n/**\n * The port each managed database last listened on, by key.\n *\n * Kept apart from the state file because it has to outlive the daemon: the\n * state file is cleared on every stop, and it is what a stopped daemon's\n * successor reads to come back where the last one was. See\n * {@link chooseManagedPort}.\n */\nexport const PORTS_FILE_NAME = \"pglite-ports.json\";\n\nexport interface DaemonState {\n /** TCP port the socket server is listening on, chosen when it started. */\n port: number;\n /** The daemon process. Used only as a fast negative check. */\n pid: number;\n /** Absolute path of the PGlite data directory this daemon has open. */\n dataDir: string;\n /** ISO timestamp, for diagnostics. */\n startedAt: string;\n /**\n * A random token the daemon also answers with over the wire, on\n * {@link identityPort}.\n *\n * Without it, \"is the daemon alive?\" degrades to \"is something listening on\n * that port?\", which is a different question and answers yes for whatever\n * process happened to take the port after a reboot. Rebase would then send\n * migrations to a stranger.\n */\n token: string;\n /** Loopback port that answers the identity check. */\n identityPort: number;\n /**\n * The additional databases this daemon serves, by declared key.\n *\n * One PGlite instance each — PGlite is one database per instance, so\n * `database(\"analytics\")` is a second data directory and a second port,\n * not a `CREATE DATABASE`. Started on demand when a command asks for the\n * key over the identity socket, and recorded here so `rebase status` and\n * a reset can see them. The default database is the fields above.\n */\n databases?: Record<string, { port: number; dataDir: string }>;\n}\n\nexport function devDbDir(projectRoot: string): string {\n return path.join(projectRoot, DEV_DB_DIR);\n}\n\nexport function dataDir(projectRoot: string): string {\n return path.join(devDbDir(projectRoot), DATA_DIR_NAME);\n}\n\n/**\n * The data directory for an additional declared database.\n *\n * `pgdata__analytics`: the key spelled the way its variable is, so the\n * directory beside `pgdata` and `DATABASE_URL__ANALYTICS` are visibly the\n * same thing.\n */\nexport function additionalDataDir(projectRoot: string, key: string): string {\n return path.join(devDbDir(projectRoot), `${DATA_DIR_NAME}${resourceEnvSuffix(key).toLowerCase()}`);\n}\n\nexport function stateFile(projectRoot: string): string {\n return path.join(devDbDir(projectRoot), STATE_FILE_NAME);\n}\n\nexport function startLockFile(projectRoot: string): string {\n return path.join(devDbDir(projectRoot), START_LOCK_NAME);\n}\n\n/**\n * Take the start lock, or report that somebody else holds it.\n *\n * `wx` is the whole mechanism: create-if-absent is a single atomic syscall, so\n * exactly one of two racing processes can succeed no matter how close together\n * they arrive.\n *\n * A lock older than `staleAfterMs` is broken rather than waited on — the holder\n * may have been killed between creating it and starting anything, and a\n * developer should never have to know this file exists in order to unstick\n * their project.\n */\nexport function acquireStartLock(projectRoot: string, staleAfterMs: number): boolean {\n const target = startLockFile(projectRoot);\n fs.mkdirSync(devDbDir(projectRoot), { recursive: true });\n\n const attempt = (): boolean => {\n try {\n const handle = fs.openSync(target, \"wx\");\n fs.writeSync(handle, `${process.pid} ${new Date().toISOString()}\\n`);\n fs.closeSync(handle);\n\n return true;\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code !== \"EEXIST\") throw error;\n\n return false;\n }\n };\n\n if (attempt()) return true;\n\n try {\n // Clamped at zero: a filesystem whose timestamp granularity rounds the\n // mtime *up* reports a negative age for a lock created moments ago,\n // and a negative age is below every threshold — so `staleAfterMs: 0`,\n // which means \"break any lock\", would refuse to break one.\n const age = Math.max(0, Date.now() - fs.statSync(target).mtimeMs);\n if (age < staleAfterMs) return false;\n fs.unlinkSync(target);\n } catch {\n // Vanished under us, which means the holder finished. Either way the\n // next attempt is the answer.\n }\n\n return attempt();\n}\n\nexport function releaseStartLock(projectRoot: string): void {\n try {\n fs.unlinkSync(startLockFile(projectRoot));\n } catch {\n // Already released is the desired end state.\n }\n}\n\n/**\n * Parse the record, or `null` for anything that is not one.\n *\n * Every failure is the same answer — absent — because every failure has the\n * same remedy: start a daemon. A corrupt state file is not worth an error\n * message to a user who never wrote it.\n */\nexport function readState(projectRoot: string): DaemonState | null {\n let raw: string;\n try {\n raw = fs.readFileSync(stateFile(projectRoot), \"utf8\");\n } catch {\n return null;\n }\n\n try {\n const parsed = JSON.parse(raw) as Partial<DaemonState>;\n if (\n typeof parsed.port !== \"number\" ||\n !Number.isInteger(parsed.port) ||\n parsed.port <= 0 ||\n parsed.port > 65535 ||\n typeof parsed.pid !== \"number\" ||\n typeof parsed.dataDir !== \"string\" ||\n typeof parsed.token !== \"string\" ||\n parsed.token.length === 0 ||\n typeof parsed.identityPort !== \"number\" ||\n !Number.isInteger(parsed.identityPort) ||\n parsed.identityPort <= 0 ||\n parsed.identityPort > 65535\n ) {\n return null;\n }\n\n // Only well-formed entries survive: a malformed one is treated as\n // absent, and the next ensure asks the daemon for it again.\n const databases: Record<string, { port: number; dataDir: string }> = {};\n if (parsed.databases && typeof parsed.databases === \"object\") {\n for (const [key, entry] of Object.entries(parsed.databases)) {\n if (\n entry && typeof entry.port === \"number\" && Number.isInteger(entry.port)\n && entry.port > 0 && entry.port <= 65535 && typeof entry.dataDir === \"string\"\n ) {\n databases[key] = { port: entry.port, dataDir: entry.dataDir };\n }\n }\n }\n\n return {\n port: parsed.port,\n pid: parsed.pid,\n dataDir: parsed.dataDir,\n startedAt: typeof parsed.startedAt === \"string\" ? parsed.startedAt : \"\",\n token: parsed.token,\n identityPort: parsed.identityPort,\n ...(Object.keys(databases).length > 0 ? { databases } : {})\n };\n } catch {\n return null;\n }\n}\n\nexport function writeState(projectRoot: string, state: DaemonState): void {\n fs.mkdirSync(devDbDir(projectRoot), { recursive: true });\n // Written whole then moved, so a reader never sees half a record — commands\n // poll this file while the daemon is starting.\n const target = stateFile(projectRoot);\n const temporary = `${target}.${process.pid}.tmp`;\n fs.writeFileSync(temporary, `${JSON.stringify(state, null, 2)}\\n`, \"utf8\");\n fs.renameSync(temporary, target);\n}\n\nexport function clearState(projectRoot: string): void {\n try {\n fs.unlinkSync(stateFile(projectRoot));\n } catch {\n // Already gone is the desired end state.\n }\n}\n\n/** Is *some* process with this pid running? A fast, cheap negative check. */\nexport function pidRunning(pid: number): boolean {\n try {\n // Signal 0 performs the permission and existence checks without\n // delivering anything.\n process.kill(pid, 0);\n\n return true;\n } catch (error) {\n // EPERM means it exists and belongs to someone else, which for our\n // purposes is still \"running\".\n return (error as NodeJS.ErrnoException).code === \"EPERM\";\n }\n}\n\n/** Can a TCP connection be opened to this port on loopback? */\nexport function portAccepting(port: number, timeoutMs = 1000): Promise<boolean> {\n return new Promise((resolve) => {\n const socket = new net.Socket();\n const settle = (answer: boolean) => {\n socket.removeAllListeners();\n socket.destroy();\n resolve(answer);\n };\n socket.setTimeout(timeoutMs);\n socket.once(\"connect\", () => settle(true));\n socket.once(\"timeout\", () => settle(false));\n socket.once(\"error\", () => settle(false));\n socket.connect(port, \"127.0.0.1\");\n });\n}\n\n/**\n * Ask a port for a free one, then hand back the number.\n *\n * Deliberately not the probe `rebase init` uses: that one has a documented\n * failure where a port is free to probe and unusable to publish. This binds on\n * loopback only, which is also where the daemon listens, so a port that binds\n * here binds there.\n */\nexport function findFreePort(): Promise<number> {\n return new Promise((resolve, reject) => {\n const server = net.createServer();\n server.once(\"error\", reject);\n server.listen(0, \"127.0.0.1\", () => {\n const address = server.address();\n if (address === null || typeof address === \"string\") {\n server.close(() => reject(new Error(\"Could not determine a free port.\")));\n\n return;\n }\n const { port } = address;\n server.close(() => resolve(port));\n });\n });\n}\n\n/** Where a project's derived database port falls: 25432–26431. */\nconst MANAGED_PORT_FIRST = 25432;\nconst MANAGED_PORT_SPAN = 1000;\n\n/**\n * The port a project's managed database tries first when nothing is recorded:\n * derived from the project path, and for an additional database from its key\n * too, so two projects — and a project's two databases — rarely ask for the\n * same one.\n *\n * Below every ephemeral range (Linux hands out 32768 and up, macOS 49152 and\n * up), so an outgoing connection's source port never sits on it, and clear of\n * the ports `rebase dev` derives for the backend (3001–3999), the admin panel\n * (5173–5372) and the compose database (5433–5632).\n */\nexport function derivedManagedPort(projectRoot: string, key: string = DEFAULT_RESOURCE_KEY): number {\n const seed = key === DEFAULT_RESOURCE_KEY ? path.resolve(projectRoot) : `${path.resolve(projectRoot)}\\0${key}`;\n const digest = createHash(\"sha256\").update(seed).digest();\n return MANAGED_PORT_FIRST + (digest.readUInt32BE(0) % MANAGED_PORT_SPAN);\n}\n\nfunction portsFile(projectRoot: string): string {\n return path.join(devDbDir(projectRoot), PORTS_FILE_NAME);\n}\n\n/** The port each database last listened on, by key; empty when nothing is recorded or the record is unreadable. */\nfunction readRecordedPorts(projectRoot: string): Record<string, number> {\n try {\n const parsed: unknown = JSON.parse(fs.readFileSync(portsFile(projectRoot), \"utf8\"));\n if (!parsed || typeof parsed !== \"object\" || Array.isArray(parsed)) return {};\n const ports: Record<string, number> = {};\n for (const [key, port] of Object.entries(parsed)) {\n if (typeof port === \"number\" && Number.isInteger(port) && port > 0 && port <= 65535) ports[key] = port;\n }\n return ports;\n } catch {\n return {};\n }\n}\n\n/**\n * Remember where these databases listen, merged into what is recorded.\n *\n * Best effort: a `.rebase/` that cannot be written costs the next start its\n * preference, not the start itself.\n */\nexport function recordManagedPorts(projectRoot: string, ports: Record<string, number>): void {\n try {\n fs.mkdirSync(devDbDir(projectRoot), { recursive: true });\n const merged = { ...readRecordedPorts(projectRoot), ...ports };\n const target = portsFile(projectRoot);\n const temporary = `${target}.${process.pid}.tmp`;\n fs.writeFileSync(temporary, `${JSON.stringify(merged, null, 2)}\\n`, \"utf8\");\n fs.renameSync(temporary, target);\n } catch {\n // See above.\n }\n}\n\n/** Can a server listen on this loopback port right now? */\nexport function portBindable(port: number): Promise<boolean> {\n return new Promise((resolve) => {\n const server = net.createServer();\n server.once(\"error\", () => resolve(false));\n server.listen(port, \"127.0.0.1\", () => server.close(() => resolve(true)));\n });\n}\n\n/**\n * The port a managed database should listen on: where it listened last time,\n * else the one derived from the project path, else any free one.\n *\n * Stable on purpose. A backend holds the URL it was started with, and a pool\n * reconnects to the same address on its own — so a database that comes back on\n * the port it had is one a running `rebase dev` recovers from without a\n * restart. It used to be whatever the operating system handed out, and a `rebase\n * db stop` followed by any command that starts it again left the backend\n * dialling a port nothing listened on, answering 503 until dev was restarted.\n *\n * @param canBind stands in for {@link portBindable} in the tests.\n */\nexport async function chooseManagedPort(\n projectRoot: string,\n key: string = DEFAULT_RESOURCE_KEY,\n canBind: (port: number) => Promise<boolean> = portBindable\n): Promise<number> {\n const candidates = [readRecordedPorts(projectRoot)[key], derivedManagedPort(projectRoot, key)];\n for (const port of new Set(candidates.filter((candidate): candidate is number => candidate !== undefined))) {\n if (await canBind(port)) return port;\n }\n return findFreePort();\n}\n"],"mappings":"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"}
|