@rebasepro/cli 0.23.0 → 0.24.1-canary.g56af4a4

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.
Files changed (158) hide show
  1. package/README.md +19 -3
  2. package/bin/rebase.js +4 -1
  3. package/dist/agent-setup-B31F00DC.js +324 -0
  4. package/dist/agent-setup-B31F00DC.js.map +1 -0
  5. package/dist/api-keys-BPBi5nOF.js +524 -0
  6. package/dist/api-keys-BPBi5nOF.js.map +1 -0
  7. package/dist/apps-CPXNElX4.js +256 -0
  8. package/dist/apps-CPXNElX4.js.map +1 -0
  9. package/dist/args-BnmsEXvu.js +109 -0
  10. package/dist/args-BnmsEXvu.js.map +1 -0
  11. package/dist/atlas-dev-database-DTMuEScS.js +62 -0
  12. package/dist/atlas-dev-database-DTMuEScS.js.map +1 -0
  13. package/dist/auth-DvY94ZuL.js +395 -0
  14. package/dist/auth-DvY94ZuL.js.map +1 -0
  15. package/dist/branch-pointer-DIhKbQLp.js +2 -0
  16. package/dist/build-CFwUx-Ff.js +336 -0
  17. package/dist/build-CFwUx-Ff.js.map +1 -0
  18. package/dist/bundle-BQhP_xiA.js +1617 -0
  19. package/dist/bundle-BQhP_xiA.js.map +1 -0
  20. package/dist/bundle.d.ts +6 -2
  21. package/dist/cli.d.ts +23 -2
  22. package/dist/cloud-CkTr-yt0.js +9640 -0
  23. package/dist/cloud-CkTr-yt0.js.map +1 -0
  24. package/dist/collection-drift-BKj_BoMq.js +106 -0
  25. package/dist/collection-drift-BKj_BoMq.js.map +1 -0
  26. package/dist/commands/__test_support__/parser-specs.d.ts +13 -0
  27. package/dist/commands/agent-setup.d.ts +116 -0
  28. package/dist/commands/api-keys.d.ts +86 -23
  29. package/dist/commands/apps.d.ts +17 -0
  30. package/dist/commands/auth.d.ts +7 -0
  31. package/dist/commands/build.d.ts +5 -1
  32. package/dist/commands/cloud/auth.d.ts +4 -3
  33. package/dist/commands/cloud/bundle-deploy.d.ts +45 -0
  34. package/dist/commands/cloud/context.d.ts +44 -5
  35. package/dist/commands/cloud/cron.d.ts +53 -0
  36. package/dist/commands/cloud/debug.d.ts +134 -0
  37. package/dist/commands/cloud/index.d.ts +89 -12
  38. package/dist/commands/cloud/token-capabilities.d.ts +76 -0
  39. package/dist/commands/cloud/tokens.d.ts +23 -0
  40. package/dist/commands/db.d.ts +52 -25
  41. package/dist/commands/dev.d.ts +181 -7
  42. package/dist/commands/doctor.d.ts +5 -0
  43. package/dist/commands/generate_sdk.d.ts +29 -2
  44. package/dist/commands/init.d.ts +57 -14
  45. package/dist/commands/schema.d.ts +6 -0
  46. package/dist/commands/skills.d.ts +126 -7
  47. package/dist/commands/start.d.ts +29 -0
  48. package/dist/commands/storage.d.ts +46 -0
  49. package/dist/consent-BWHaYx5p.js +76 -0
  50. package/dist/consent-BWHaYx5p.js.map +1 -0
  51. package/dist/context-BqhRydBg.js +1234 -0
  52. package/dist/context-BqhRydBg.js.map +1 -0
  53. package/dist/daemon-DBn-shxX.js +2 -0
  54. package/dist/{daemon-entry-LTFKtpHy.js → daemon-entry-Da-D3rmH.js} +3 -4
  55. package/dist/daemon-entry-Da-D3rmH.js.map +1 -0
  56. package/dist/db-CMpBK3sv.js +1252 -0
  57. package/dist/db-CMpBK3sv.js.map +1 -0
  58. package/dist/derive-RPIsAVO4.js +474 -0
  59. package/dist/derive-RPIsAVO4.js.map +1 -0
  60. package/dist/dev-C9P5Yp9k.js +1593 -0
  61. package/dist/dev-C9P5Yp9k.js.map +1 -0
  62. package/dist/dev-db/atlas-dev-database.d.ts +32 -0
  63. package/dist/dev-db/daemon-entry.d.ts +13 -0
  64. package/dist/dev-db/daemon.d.ts +10 -0
  65. package/dist/dev-db/prepare.d.ts +10 -0
  66. package/dist/dev-preflight-N1evJL3S.js +2 -0
  67. package/dist/doctor-DYs-NqFI.js +771 -0
  68. package/dist/doctor-DYs-NqFI.js.map +1 -0
  69. package/dist/eject-BT6t37D0.js +335 -0
  70. package/dist/eject-BT6t37D0.js.map +1 -0
  71. package/dist/fold-static-C1EtHlfV.js +201 -0
  72. package/dist/fold-static-C1EtHlfV.js.map +1 -0
  73. package/dist/fold-static.d.ts +26 -3
  74. package/dist/function-portability-BIvlvR9q.js +265 -0
  75. package/dist/function-portability-BIvlvR9q.js.map +1 -0
  76. package/dist/generate_sdk-UV635zSe.js +477 -0
  77. package/dist/generate_sdk-UV635zSe.js.map +1 -0
  78. package/dist/index.es.js +189 -22095
  79. package/dist/index.es.js.map +1 -1
  80. package/dist/init-fXeBcxuN.js +1042 -0
  81. package/dist/init-fXeBcxuN.js.map +1 -0
  82. package/dist/manifest-DzlC60WX.js +871 -0
  83. package/dist/manifest-DzlC60WX.js.map +1 -0
  84. package/dist/manifest.d.ts +41 -1
  85. package/dist/normalize-imports-CmVdE9iZ.js +100 -0
  86. package/dist/normalize-imports-CmVdE9iZ.js.map +1 -0
  87. package/dist/package-manager-DSEOwTqM.js +193 -0
  88. package/dist/package-manager-DSEOwTqM.js.map +1 -0
  89. package/dist/path-flags-DUBuI2i1.js +77 -0
  90. package/dist/path-flags-DUBuI2i1.js.map +1 -0
  91. package/dist/precompress.d.ts +17 -0
  92. package/dist/prepare-Btn9Ewbn.js +1052 -0
  93. package/dist/prepare-Btn9Ewbn.js.map +1 -0
  94. package/dist/{project-3WCbrrfW.js → project-Wp2zoyeN.js} +34 -184
  95. package/dist/project-Wp2zoyeN.js.map +1 -0
  96. package/dist/resolve-CJLkfFV0.js +2 -0
  97. package/dist/resources-DT1COn_6.js +161 -0
  98. package/dist/resources-DT1COn_6.js.map +1 -0
  99. package/dist/schema-BDBWvCHu.js +125 -0
  100. package/dist/schema-BDBWvCHu.js.map +1 -0
  101. package/dist/skills-DTFibT1m.js +642 -0
  102. package/dist/skills-DTFibT1m.js.map +1 -0
  103. package/dist/start-rU2e3Kp5.js +228 -0
  104. package/dist/start-rU2e3Kp5.js.map +1 -0
  105. package/dist/{state-C59Elrnt.js → state-Do7ZJYkF.js} +49 -4
  106. package/dist/state-Do7ZJYkF.js.map +1 -0
  107. package/dist/status-BR9oyi-y.js +351 -0
  108. package/dist/status-BR9oyi-y.js.map +1 -0
  109. package/dist/storage-DyvrSTH3.js +303 -0
  110. package/dist/storage-DyvrSTH3.js.map +1 -0
  111. package/dist/telemetry-CAWNCugs.js +338 -0
  112. package/dist/telemetry-CAWNCugs.js.map +1 -0
  113. package/dist/telemetry-DbLqGIBW.js +129 -0
  114. package/dist/telemetry-DbLqGIBW.js.map +1 -0
  115. package/dist/unknown-command-DNwDcX3w.js +98 -0
  116. package/dist/unknown-command-DNwDcX3w.js.map +1 -0
  117. package/dist/upgrade-D-5-Od96.js +1265 -0
  118. package/dist/upgrade-D-5-Od96.js.map +1 -0
  119. package/dist/upgrade.d.ts +14 -2
  120. package/dist/utils/path-flags.d.ts +18 -0
  121. package/dist/utils/project.d.ts +16 -0
  122. package/dist/version-Buyyo1ut.js +58 -0
  123. package/dist/version-Buyyo1ut.js.map +1 -0
  124. package/package.json +7 -7
  125. package/templates/eject/backend/src/env.ts +1 -1
  126. package/templates/eject/backend/src/index.ts +23 -2
  127. package/templates/overlays/baas/ai-instructions.md +1 -1
  128. package/templates/overlays/baas/config/storage.ts +5 -3
  129. package/templates/template/.cursorrules +1 -1
  130. package/templates/template/.env.example +10 -6
  131. package/templates/template/.github/copilot-instructions.md +1 -1
  132. package/templates/template/.mcp.json +2 -2
  133. package/templates/template/.windsurfrules +1 -1
  134. package/templates/template/AGENTS.md +1 -1
  135. package/templates/template/CLAUDE.md +1 -1
  136. package/templates/template/README.md +11 -12
  137. package/templates/template/ai-instructions.md +5 -4
  138. package/templates/template/backend/functions/hello.ts +8 -0
  139. package/templates/template/backend/package.json +1 -1
  140. package/templates/template/config/storage.ts +5 -3
  141. package/templates/template/docker-compose.yml +6 -6
  142. package/templates/template/frontend/vite.config.ts +11 -3
  143. package/templates/template/gitignore +6 -1
  144. package/templates/template/package.json +4 -3
  145. package/templates/template/scripts/example.ts +6 -4
  146. package/dist/branch-pointer-dBiClJ0p.js +0 -111
  147. package/dist/branch-pointer-dBiClJ0p.js.map +0 -1
  148. package/dist/constraints-DKGbNfUW.js +0 -48
  149. package/dist/constraints-DKGbNfUW.js.map +0 -1
  150. package/dist/daemon-DR9Tk0La.js +0 -355
  151. package/dist/daemon-DR9Tk0La.js.map +0 -1
  152. package/dist/daemon-entry-LTFKtpHy.js.map +0 -1
  153. package/dist/dev-preflight-CNLl4rdo.js +0 -367
  154. package/dist/dev-preflight-CNLl4rdo.js.map +0 -1
  155. package/dist/project-3WCbrrfW.js.map +0 -1
  156. package/dist/resolve-BAeRUBhg.js +0 -74
  157. package/dist/resolve-BAeRUBhg.js.map +0 -1
  158. package/dist/state-C59Elrnt.js.map +0 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prepare-Btn9Ewbn.js","names":[],"sources":["../src/utils/dev-preflight.ts","../src/dev-db/branch-pointer.ts","../src/dev-db/daemon.ts","../src/dev-db/resolve.ts","../src/dev-db/prepare.ts"],"sourcesContent":["/**\n * Make `rebase dev` the only command a new project needs.\n *\n * The scaffolded project prints three steps — start the database container,\n * push the schema, start the dev server — and a first-time reader has to\n * perform all three correctly before anything works. Two of them are\n * mechanical, and both fail in ways that do not name themselves: skipping the\n * container gives `ECONNREFUSED` from a stack the reader has never seen, and\n * skipping the push gives a server that starts cleanly and then 500s on the\n * first request because the tables are not there.\n *\n * So `dev` does them. Not always, and not silently — under four conditions that\n * together mean \"this is a throwaway development database on this laptop, and\n * it is not running\":\n *\n * 1. The DSN resolves to a loopback address. A remote database is somebody's\n * real data and is never touched here, whatever else is true.\n * 2. Nothing is listening on it. A database that answers is left completely\n * alone — no push, no inspection, no connection.\n * 3. The project has a `docker-compose.yml` declaring a `db` service, i.e.\n * the one `rebase init` wrote. Without it there is nothing to start and\n * the backend's own error is the better message.\n * 4. Docker is available. If it is not, the manual steps are printed and dev\n * continues — a preflight must never be the reason the command fails.\n *\n * The schema push happens only when this function actually started the\n * container, which is the one moment a local database is known to be new or\n * stale relative to the collections. A `dev` against an already-running\n * database never mutates its schema.\n */\nimport fs from \"fs\";\nimport net from \"net\";\nimport path from \"path\";\nimport chalk from \"chalk\";\nimport { execa } from \"execa\";\n\n/** Where the preflight stopped, and why. Returned so tests can assert it. */\nexport type PreflightOutcome =\n | { action: \"disabled\" }\n | { action: \"no-dsn\" }\n | { action: \"remote-dsn\"; host: string }\n | { action: \"already-running\"; host: string; port: number }\n | { action: \"no-compose\" }\n | { action: \"no-docker\"; hint: string }\n | { action: \"start-failed\"; hint: string }\n | { action: \"wrong-port\"; hint: string; port: number; composePort: number }\n | { action: \"started\"; port: number; pushed: boolean };\n\n/**\n * Hosts that mean \"this machine\", and so may be provisioned automatically.\n *\n * An exact-match set, never a prefix or substring test: `localhost.example.com`\n * is a perfectly resolvable name that somebody else controls.\n */\nconst LOOPBACK = new Set([\"localhost\", \"127.0.0.1\", \"::1\", \"0.0.0.0\"]);\n\n/**\n * The host and port of a Postgres DSN, but only when it points at this machine.\n *\n * Returns null for anything else — a remote host, an unparseable string, a\n * non-postgres scheme. Callers treat null as \"do nothing\", so every parse\n * failure fails closed onto the safe behaviour rather than onto a guess.\n */\nexport function parseLoopbackDsn(dsn: string | undefined): { host: string; port: number } | null {\n if (!dsn) return null;\n\n let url: URL;\n try {\n url = new URL(dsn);\n } catch {\n return null;\n }\n\n if (!/^postgres(ql)?:$/.test(url.protocol)) return null;\n\n // `url.hostname` KEEPS the brackets an IPv6 literal carries in a URL, and\n // `net.connect` rejects them. Normalising here rather than at each use is\n // what stops the two representations diverging: everything downstream gets\n // a host it can dial.\n const host = url.hostname.replace(/^\\[(.+)\\]$/, \"$1\");\n if (!LOOPBACK.has(host)) return null;\n\n const port = url.port ? Number(url.port) : 5432;\n if (!Number.isInteger(port) || port <= 0 || port > 65535) return null;\n\n return { host, port };\n}\n\n/**\n * Whether a compose file declares a `db` service.\n *\n * Deliberately a text scan and not a YAML parse: the CLI has no YAML\n * dependency, and the question is only ever asked of a file this tool wrote.\n * A false negative costs the automation and nothing else — the reader gets the\n * manual steps, which are correct.\n */\nexport function composeDeclaresDbService(yamlText: string): boolean {\n let inServices = false;\n for (const rawLine of yamlText.split(/\\r?\\n/)) {\n const line = rawLine.replace(/\\t/g, \" \");\n if (/^\\s*#/.test(line) || line.trim() === \"\") continue;\n\n if (/^services:\\s*$/.test(line)) {\n inServices = true;\n continue;\n }\n // Any other zero-indent key ends the services block.\n if (inServices && /^\\S/.test(line)) {\n inServices = false;\n continue;\n }\n if (inServices && /^\\s{2,}db:\\s*$/.test(line)) return true;\n }\n return false;\n}\n\n/**\n * The connection string the compose file's `db` service will answer on.\n *\n * `--docker` used to be a flag that changed a banner and nothing else: it\n * resolved to `kind: \"docker\"`, and every caller then had no URL to reach the\n * container with, so the backend booted with no `DATABASE_URL` at all and died\n * on the message a project with no database gets. The container it had just\n * been asked for was never started, because the preflight that starts it needs\n * a DSN to decide the database is local and not running.\n *\n * So the URL is derived rather than required: the compose file names the user,\n * the database and the published host port, and `.env` holds the password\n * compose itself interpolates. That is the same string `rebase init` writes as\n * the commented-out `DATABASE_URL`, which is what makes uncommenting that line\n * and passing `--docker` reach the same database.\n *\n * Returns null when the file declares no `db` service, or declares one this\n * scan cannot read — a `--docker` that cannot be honoured has to say so, and\n * a guess would point at somebody else's Postgres on 5432.\n *\n * Same text scan as {@link composeDeclaresDbService}, for the same reason.\n */\nexport function composeDatabaseUrl(\n yamlText: string,\n env: Record<string, string | undefined> = {}\n): string | null {\n const found = scanComposeDbService(yamlText);\n\n const user = expandComposeValue(found.POSTGRES_USER, env);\n const password = expandComposeValue(found.POSTGRES_PASSWORD, env);\n const database = expandComposeValue(found.POSTGRES_DB, env);\n if (!user || !password || !database || !found.hostPort) return null;\n\n return `postgresql://${encodeURIComponent(user)}:${encodeURIComponent(password)}` +\n `@127.0.0.1:${found.hostPort}/${database}?options=-c%20search_path%3Dpublic&sslmode=disable`;\n}\n\n/**\n * The host port the compose `db` service publishes, or null.\n *\n * Separate from {@link composeDatabaseUrl} because the port is answerable on\n * its own: the URL needs the user, the password and the database name too, and\n * a compose file missing any of them still says which port a container would\n * listen on. That is the question \"would starting this container answer the\n * DSN in `.env`?\" — see the `wrong-port` branch of {@link ensureDevDatabase},\n * where getting it wrong meant starting a container nobody asked for.\n */\nexport function composeHostPort(yamlText: string): number | null {\n const port = scanComposeDbService(yamlText).hostPort;\n\n return port ? Number(port) : null;\n}\n\n/** The raw values of the compose `db` service, before any expansion. */\nfunction scanComposeDbService(yamlText: string): Record<string, string> & { hostPort?: string } {\n let inServices = false;\n let inDb = false;\n let dbIndent = 0;\n let section: \"environment\" | \"ports\" | null = null;\n const found: Record<string, string> = {};\n let hostPort: string | null = null;\n\n for (const rawLine of yamlText.split(/\\r?\\n/)) {\n const line = rawLine.replace(/\\t/g, \" \");\n if (/^\\s*#/.test(line) || line.trim() === \"\") continue;\n\n if (/^services:\\s*$/.test(line)) { inServices = true; continue; }\n if (inServices && /^\\S/.test(line)) { inServices = false; inDb = false; continue; }\n if (!inServices) continue;\n\n const indent = line.length - line.trimStart().length;\n const dbHeader = line.match(/^(\\s{2,})db:\\s*$/);\n if (dbHeader) { inDb = true; dbIndent = dbHeader[1].length; section = null; continue; }\n // A sibling service at the same indent as `db:` ends it.\n if (inDb && indent <= dbIndent) { inDb = false; section = null; continue; }\n if (!inDb) continue;\n\n const key = line.match(/^\\s+(environment|ports):\\s*$/);\n if (key) { section = key[1] as \"environment\" | \"ports\"; continue; }\n // Any other key of the service ends the block we were reading.\n if (/^\\s+[A-Za-z_][\\w-]*:\\s*\\S/.test(line) && !/^\\s+POSTGRES_/.test(line)) {\n if (!/^\\s*-\\s/.test(line.trimStart())) section = null;\n }\n\n if (section === \"environment\") {\n const pair = line.match(/^\\s+(POSTGRES_(?:USER|PASSWORD|DB)):\\s*(.+?)\\s*$/);\n if (pair) found[pair[1]] = pair[2].replace(/^[\"']|[\"']$/g, \"\");\n continue;\n }\n\n if (section === \"ports\" && hostPort === null) {\n // `- \"5435:5432\"`. Only a mapping whose container side is Postgres's\n // own port is the one to dial; anything else is a different service.\n const mapping = line.match(/^\\s*-\\s*[\"']?(?:[\\d.]+:)?(\\d+):5432(?:\\/tcp)?[\"']?\\s*$/);\n if (mapping) hostPort = mapping[1];\n }\n }\n\n return hostPort === null ? found : { ...found, hostPort };\n}\n\n/**\n * `${VAR:-default}`, `${VAR}` and a literal, resolved the way compose resolves\n * them — against the project's `.env`, which is the file compose itself reads.\n */\nfunction expandComposeValue(\n raw: string | undefined,\n env: Record<string, string | undefined>\n): string | null {\n if (!raw) return null;\n const interpolated = raw.replace(/\\$\\{([A-Za-z_][\\w]*)(?::?-([^}]*))?\\}/g, (_all, name, fallback) => {\n const value = env[name];\n if (value !== undefined && value !== \"\") return value;\n return fallback ?? \"\";\n });\n const trimmed = interpolated.trim();\n return trimmed.length > 0 ? trimmed : null;\n}\n\n/** Is something accepting connections there right now? */\nexport function probeTcp(host: string, port: number, timeoutMs = 700): Promise<boolean> {\n return new Promise(resolve => {\n const socket = new net.Socket();\n let settled = false;\n\n const done = (open: boolean): void => {\n if (settled) return;\n settled = true;\n socket.destroy();\n resolve(open);\n };\n\n socket.setTimeout(timeoutMs);\n socket.once(\"connect\", () => done(true));\n socket.once(\"timeout\", () => done(false));\n socket.once(\"error\", () => done(false));\n socket.connect(port, host);\n });\n}\n\n/**\n * Poll until the port answers, or give up.\n *\n * Postgres publishes its port before it is ready to accept queries, so the\n * caller still has to tolerate a first connection that is refused at the\n * protocol level. This only answers \"has the container got as far as listening\",\n * which is the part that takes the seconds.\n */\nexport async function waitForPort(\n host: string,\n port: number,\n timeoutMs = 60_000,\n intervalMs = 500\n): Promise<boolean> {\n const deadline = Date.now() + timeoutMs;\n while (Date.now() < deadline) {\n if (await probeTcp(host, port, 700)) return true;\n await new Promise(resolve => setTimeout(resolve, intervalMs));\n }\n return false;\n}\n\nexport interface EnsureDevDatabaseOptions {\n projectRoot: string;\n /** The DSN from the project's env file, if it has one. */\n databaseUrl: string | undefined;\n /** Set by `--no-db` or REBASE_DEV_NO_DB. */\n disabled: boolean;\n /** Whether the project has collections worth pushing. */\n hasCollections: boolean;\n /** Runs `rebase db push` for this project. Injected so tests need no database. */\n pushSchema: () => Promise<void>;\n /**\n * Brings the compose `db` service up. Injected for the same reason\n * `pushSchema` is: what happens *after* a start — a push that fails without\n * taking the dev server with it — is the part worth asserting, and it was\n * untestable while the only way in was a real `docker compose up`.\n */\n startDatabase?: (projectRoot: string) => Promise<void>;\n log?: (message: string) => void;\n}\n\n/**\n * Start the project's development database if it is not running, and give it a\n * schema if this call is what started it.\n */\nexport async function ensureDevDatabase(options: EnsureDevDatabaseOptions): Promise<PreflightOutcome> {\n const { projectRoot, databaseUrl, disabled, hasCollections, pushSchema } = options;\n const log = options.log ?? ((message: string) => console.log(message));\n\n if (disabled) return { action: \"disabled\" };\n\n const target = parseLoopbackDsn(databaseUrl);\n if (!databaseUrl) return { action: \"no-dsn\" };\n if (!target) {\n // Not an error, and not worth a warning either: pointing DATABASE_URL at\n // a real database is a completely ordinary thing to do, and the only\n // correct response is to keep away from it.\n let host = \"the configured host\";\n try {\n host = new URL(databaseUrl).hostname || host;\n } catch { /* keep the placeholder */ }\n return { action: \"remote-dsn\", host };\n }\n\n if (await probeTcp(target.host, target.port)) {\n return { action: \"already-running\", host: target.host, port: target.port };\n }\n\n const composePath = path.join(projectRoot, \"docker-compose.yml\");\n if (!fs.existsSync(composePath)) return { action: \"no-compose\" };\n const composeText = fs.readFileSync(composePath, \"utf8\");\n if (!composeDeclaresDbService(composeText)) return { action: \"no-compose\" };\n\n const manualSteps =\n `${chalk.cyan(\"docker compose up -d db\")} then ${chalk.cyan(\"rebase db push\")}`;\n\n // Would starting that container answer *this* DSN? A `.env` naming a port\n // the compose file does not publish is the commonest way to get here, and\n // starting the container then told the reader the exact opposite of the\n // truth: `⚠ The database container did not begin listening on port 3139.\n // Run docker compose up -d db then rebase db push to see why.` The\n // container was up and healthy on 5436; the port in `.env` was what was\n // wrong. And a container nobody asked for was left running.\n const composePort = composeHostPort(composeText);\n if (composePort !== null && composePort !== target.port) {\n const hint =\n `DATABASE_URL points at ${target.host}:${target.port} and nothing listens there. `\n + `The compose file's db service publishes ${composePort}, so starting it would not `\n + `answer that URL — nothing was started. Either correct the port in .env, or point `\n + `DATABASE_URL at 127.0.0.1:${composePort}.`;\n log(\"\");\n log(` ${chalk.yellow(\"⚠\")} ${chalk.gray(hint)}`);\n return { action: \"wrong-port\", hint, port: target.port, composePort };\n }\n\n log(\"\");\n log(` ${chalk.gray(\"Database not running — starting it…\")}`);\n\n const startDatabase = options.startDatabase ?? (async (root: string) => {\n await execa(\"docker\", [\"compose\", \"up\", \"-d\", \"db\"], { cwd: root, stdio: \"pipe\" });\n });\n\n try {\n await startDatabase(projectRoot);\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n // Two very different failures land here and the reader needs to know\n // which: Docker not being installed at all, and Docker being installed\n // but unable to start this container.\n if (/ENOENT|not found|command not found/i.test(message)) {\n const hint = `Docker was not found. Start a Postgres yourself, then run ${manualSteps}.`;\n log(` ${chalk.yellow(\"⚠\")} ${chalk.gray(hint)}`);\n return { action: \"no-docker\", hint };\n }\n const hint = `Could not start the database container. Run ${manualSteps} to see why.`;\n log(` ${chalk.yellow(\"⚠\")} ${chalk.gray(hint)}`);\n return { action: \"start-failed\", hint };\n }\n\n if (!(await waitForPort(target.host, target.port))) {\n const hint = `The database container did not begin listening on port ${target.port}. Run ${manualSteps} to see why.`;\n log(` ${chalk.yellow(\"⚠\")} ${chalk.gray(hint)}`);\n return { action: \"start-failed\", hint };\n }\n\n log(` ${chalk.green(\"✓\")} ${chalk.gray(`Database ready on port ${target.port}`)}`);\n\n if (!hasCollections) return { action: \"started\", port: target.port, pushed: false };\n\n log(` ${chalk.gray(\"Pushing the schema…\")}`);\n try {\n await pushSchema();\n log(` ${chalk.green(\"✓\")} ${chalk.gray(\"Schema pushed\")}`);\n return { action: \"started\", port: target.port, pushed: true };\n } catch {\n // The push prints its own diagnostics through inherited stdio, so\n // repeating them here would only bury them. What is added is the one\n // thing the reader now needs: the server is still starting, and against\n // what.\n log(` ${chalk.yellow(\"⚠\")} ${chalk.gray(`Schema push failed. The database is running; fix the error above and run ${chalk.cyan(\"rebase db push\")}.`)}`);\n return { action: \"started\", port: target.port, pushed: false };\n }\n}\n","/**\n * Which database branch this checkout is working on.\n *\n * `rebase db branch create` made a full copy of the database in about a second\n * and then stopped one step short of being a feature: there was no `switch`, no\n * `--branch` on `rebase dev`, and nothing wrote the branch anywhere a command\n * would read it. The only way to *use* a branch was to hand-edit\n * `DATABASE_URL`, and the documentation claimed otherwise — \"the CLI updates\n * your local development configuration\" — which it did not; the `.env` was\n * byte-identical afterwards.\n *\n * This is that missing step, and the shape of it is chosen to avoid three\n * things:\n *\n * 1. **It does not touch `.env`.** That file holds secrets, comments and the\n * developer's own ordering, and a command that rewrites it in place will\n * eventually lose something that was not its business. The branch is\n * recorded beside the other per-checkout state in `.rebase/`, which is\n * already gitignored — a branch is a fact about one machine, never about\n * the project.\n *\n * 2. **It stores a name, not a connection string.** The credentials stay in\n * exactly one place; the URL is derived by swapping the database name on\n * the base one at resolution time. A stored URL would be a second copy of\n * the password, and would go stale the moment `.env` changed.\n *\n * 3. **It does not outrank an explicit instruction.** `--database-url` and a\n * `DATABASE_URL` in the shell are more immediate and more specific than a\n * switch made yesterday, so they still win. The pointer sits directly above\n * the project's `.env`: it is a deliberate, persistent choice, and `.env` is\n * the project default it is meant to override. Anything less and `switch`\n * would silently do nothing on every project that sets `DATABASE_URL` —\n * which is every project not using the managed database.\n */\n\nimport fs from \"fs\";\nimport path from \"path\";\n\n/** Where the pointer lives, next to the rest of the per-checkout state. */\nexport function branchPointerPath(projectRoot: string): string {\n return path.join(projectRoot, \".rebase\", \"branch.json\");\n}\n\nexport interface ActiveBranch {\n /** The name the developer typed. */\n name: string;\n /** The PostgreSQL database that name refers to. */\n database: string;\n}\n\n/**\n * The prefix `BranchService` puts on every branch database.\n *\n * Duplicated from the driver rather than imported because the CLI must answer\n * \"which database\" without loading a database driver — `rebase dev` reads this\n * before it knows which plugin the project uses. It is covered by a test that\n * fails if the driver's prefix moves.\n */\nexport const BRANCH_DB_PREFIX = \"rb_\";\n\nexport function branchDatabaseName(name: string): string {\n return `${BRANCH_DB_PREFIX}${name}`;\n}\n\n/** Read the active branch, or null when the checkout is on the main database. */\nexport function readActiveBranch(projectRoot: string): ActiveBranch | null {\n try {\n const raw = fs.readFileSync(branchPointerPath(projectRoot), \"utf-8\");\n const parsed = JSON.parse(raw) as Partial<ActiveBranch>;\n if (typeof parsed.name !== \"string\" || typeof parsed.database !== \"string\") return null;\n if (!parsed.name || !parsed.database) return null;\n\n return { name: parsed.name, database: parsed.database };\n } catch {\n // Absent is the common case and is not an error. A corrupt file is\n // treated the same way: the answer is \"no branch\", which is the state\n // the developer can always get back to with `branch switch --off`.\n return null;\n }\n}\n\nexport function writeActiveBranch(projectRoot: string, branch: ActiveBranch): void {\n const file = branchPointerPath(projectRoot);\n fs.mkdirSync(path.dirname(file), { recursive: true });\n fs.writeFileSync(file, `${JSON.stringify(branch, null, 2)}\\n`, \"utf-8\");\n}\n\n/** Return to the main database. Absent is success — this is idempotent. */\nexport function clearActiveBranch(projectRoot: string): void {\n fs.rmSync(branchPointerPath(projectRoot), { force: true });\n}\n\n/**\n * The base connection string with its database swapped for the branch's.\n *\n * Everything else is carried across untouched — credentials, host, port, and\n * the query string, which is load-bearing here: `?sslmode=disable` is what a\n * local Docker Postgres needs, and dropping it turns a switch into a TLS error\n * that says nothing about branches.\n *\n * Returns null for a connection string that cannot be parsed, so the caller can\n * say so plainly rather than emitting a mangled URL that fails later.\n */\nexport function branchUrl(baseUrl: string, database: string): string | null {\n try {\n const url = new URL(baseUrl);\n // `pathname` keeps its leading slash; a URL with no database at all\n // still gets one, which is what connecting to a named branch means.\n url.pathname = `/${database}`;\n\n return url.toString();\n } catch {\n return null;\n }\n}\n\n/** The database name in a connection string, for reporting what you are on. */\nexport function databaseNameOf(connectionString: string): string | null {\n try {\n const name = new URL(connectionString).pathname.replace(/^\\//, \"\");\n\n return name || null;\n } catch {\n return null;\n }\n}\n","/**\n * Starting, finding and stopping the managed database, from the caller's side.\n *\n * Every command that needs Postgres calls {@link ensureManagedDatabase} and\n * gets a connection string back. Whether that started a process or found one\n * already running is not the caller's business, which is the point: `rebase\n * db push`, `rebase dev` and `rebase studio` in three terminals must all reach\n * the same database without coordinating, because two processes opening one\n * PGlite data directory would corrupt it.\n *\n * The hard part is not starting the daemon; it is deciding whether the one the\n * state file describes is still there. A pid can be recycled after a reboot and\n * a port can be taken by a stranger, so believing either on its own would let\n * Rebase send a migration somewhere unintended. {@link isDaemonAlive} asks the\n * daemon to identify itself instead.\n */\n\nimport { randomBytes } from \"crypto\";\nimport { type ChildProcess, spawn } from \"child_process\";\nimport fs from \"fs\";\nimport net from \"net\";\nimport path from \"path\";\nimport { fileURLToPath } from \"url\";\n\nimport {\n acquireStartLock,\n clearState,\n type DaemonState,\n dataDir,\n devDbDir,\n findFreePort,\n pidRunning,\n readState,\n releaseStartLock,\n stateFile\n} from \"./state\";\n\n/** How long to wait for a first boot. PGlite runs initdb on an empty data dir. */\nconst START_TIMEOUT_MS = 60_000;\n/** Poll interval while waiting for the daemon to publish its state file. */\nconst POLL_INTERVAL_MS = 150;\n/** How long the identity handshake may take before we call it dead. */\nconst IDENTITY_TIMEOUT_MS = 1_500;\n\nexport interface ManagedDatabase {\n /** Connection string for this project's managed database. */\n url: string;\n /** Where the data lives, for diagnostics and `--reset`. */\n dataDir: string;\n port: number;\n pid: number;\n /** True when this call started the daemon rather than finding it. */\n started: boolean;\n /**\n * Connection strings for the additional databases the caller asked for,\n * by declared key — `database(\"analytics\")` → `analytics`. Each is its\n * own PGlite instance behind the same daemon.\n */\n additional: Record<string, string>;\n}\n\n/**\n * PGlite's built-in superuser and database.\n *\n * Fixed by PGlite rather than chosen here, and asserted by the socket spike:\n * `session_user` comes back as `postgres`.\n */\nconst MANAGED_USER = \"postgres\";\nconst MANAGED_DATABASE = \"postgres\";\n\n/**\n * The DSN for the managed development database.\n *\n * **`sslmode=disable` is not decoration.** PGlite's socket server speaks no TLS,\n * and libpq-family clients try SSL first — so Atlas, which `rebase db push`\n * shells out to, failed the whole push with\n * `pq: SSL is not enabled on the server`. Its remedy box then told the reader to\n * append `sslmode=disable` to `DATABASE_URL`, which on this path is not set at\n * all: `rebase init` leaves it commented out precisely so the managed database\n * is used. The advice was correct and unfollowable.\n *\n * Putting it in the URL fixes every consumer at once — Atlas, `pg`, and anything\n * a driver shells out to later — rather than teaching each one about PGlite.\n */\n\n/**\n * The end of `pglite.log`, for an error message that has to carry its own reason.\n *\n * The daemon writes its output to a file because it is detached — it has to\n * outlive the command that started it. That is right, and it means every\n * startup failure used to be reported as a path: \"see pglite.log for the\n * reason\". On a laptop that is a fair trade. In CI it is not a trade at all,\n * because the runner discards the workspace: the log named in the message no\n * longer exists by the time anybody reads the message, so a failed run says\n * only that something failed.\n *\n * So the tail comes with the error. Bounded, because a log that has been\n * appended to across many runs is not something to paste in full.\n */\nexport function pgliteLogTail(projectRoot: string, lines = 12): string {\n try {\n const contents = fs.readFileSync(path.join(devDbDir(projectRoot), \"pglite.log\"), \"utf8\").trimEnd();\n if (!contents) return \" (pglite.log is empty — the daemon wrote nothing before exiting)\";\n return contents.split(\"\\n\").slice(-lines).map(line => ` ${line}`).join(\"\\n\");\n } catch {\n return \" (pglite.log could not be read)\";\n }\n}\n\nexport function managedUrl(port: number): string {\n return `postgresql://${MANAGED_USER}@127.0.0.1:${port}/${MANAGED_DATABASE}?sslmode=disable`;\n}\n\n/**\n * Ask the process behind a state record to prove it is the one we wrote down.\n *\n * A pid check alone answers \"is *a* process running\", and a port check alone\n * answers \"is *something* listening\" — after a reboot both say yes about\n * strangers. The daemon answers with the token from its own state file, so a\n * match is the only evidence accepted.\n */\nexport function isDaemonAlive(state: DaemonState): Promise<boolean> {\n if (!pidRunning(state.pid)) return Promise.resolve(false);\n\n return new Promise((resolve) => {\n const socket = new net.Socket();\n let answer = \"\";\n const settle = (alive: boolean) => {\n socket.removeAllListeners();\n socket.destroy();\n resolve(alive);\n };\n socket.setTimeout(IDENTITY_TIMEOUT_MS);\n socket.once(\"timeout\", () => settle(false));\n socket.once(\"error\", () => settle(false));\n socket.on(\"data\", (chunk) => {\n answer += chunk.toString(\"utf8\");\n if (answer.includes(\"\\n\")) settle(answer.trim() === `rebase-dev-db ${state.token}`);\n });\n socket.once(\"close\", () => settle(answer.trim() === `rebase-dev-db ${state.token}`));\n socket.connect(state.identityPort, \"127.0.0.1\");\n });\n}\n\n/** The running daemon for this project, or `null`. Never starts anything. */\nexport async function findRunningDaemon(projectRoot: string): Promise<DaemonState | null> {\n const state = readState(projectRoot);\n if (!state) return null;\n if (await isDaemonAlive(state)) return state;\n\n // The record describes something that is not there. Clearing it is the\n // whole recovery: the next ensure call starts a fresh daemon, and leaving\n // it would make every subsequent check pay the identity timeout.\n clearState(projectRoot);\n\n return null;\n}\n\n/**\n * Resolve the CLI entry point, so the daemon can be spawned as ourselves.\n *\n * A hidden subcommand rather than a second build entry: under `tsx` this file\n * is `src/dev-db/daemon.ts` and in a published CLI it is bundled into\n * `dist/index.js`, and in both cases the executable to re-invoke is the one\n * already running. `rebase dev` re-runs itself the same way to regenerate the\n * SDK types.\n */\nexport function resolveCliEntry(): string {\n // `process.argv[1]` is `bin/rebase.js` for a real invocation, which is\n // exactly what should be re-run. Under a test runner it is the runner, so\n // fall back to this module's own directory.\n const argvEntry = process.argv[1];\n if (argvEntry && fs.existsSync(argvEntry)) return argvEntry;\n\n return fileURLToPath(new URL(\"../index.js\", import.meta.url));\n}\n\n/**\n * How to re-invoke ourselves, which differs between a published CLI and this\n * repository.\n *\n * A published `rebase` is `node bin/rebase.js`, and re-running that is trivial.\n * Inside the monorepo the entry is TypeScript, which plain `node` cannot load —\n * so the daemon has to be started through the same loader that is running now.\n * Getting this wrong fails as a spawn that exits instantly with a syntax error,\n * which is why the caller reads `pglite.log` on failure.\n */\nexport function resolveSpawn(entry: string): { execPath: string; prefixArgs: string[] } {\n if (!/\\.[cm]?ts$/.test(entry)) return { execPath: process.execPath, prefixArgs: [] };\n\n // Node can load TypeScript when tsx is registered as an import hook. tsx is\n // already a dependency of this package, because `rebase dev` runs the\n // backend through it.\n return { execPath: process.execPath, prefixArgs: [\"--import\", \"tsx\"] };\n}\n\nexport interface EnsureOptions {\n /** Silence the \"starting…\" progress line. */\n quiet?: boolean;\n /** Milliseconds of inactivity before the daemon exits. 0 disables. */\n idleTimeoutMs?: number;\n /** Where progress goes. Injected for tests. */\n onProgress?: (message: string) => void;\n /** Override how the daemon process is launched. For tests. */\n spawn?: { execPath: string; prefixArgs: string[] };\n /**\n * Override the CLI entry to re-invoke. For tests.\n *\n * Necessary because under a test runner `process.argv[1]` is the runner\n * itself, which exists and is therefore accepted by {@link resolveCliEntry}\n * — spawning vitest with `__dev-db-daemon` rather than the CLI.\n */\n entry?: string;\n /**\n * Keys of the additional databases the project declares, beyond the\n * default. Each is served by the daemon on request, so a project that\n * adds `database(\"analytics\")` while the daemon is running gets it\n * without a restart.\n */\n additionalKeys?: readonly string[];\n}\n\n/**\n * Ask the running daemon for one additional database, and get its port.\n *\n * The identity socket doubles as the command channel: the daemon answers\n * with its token first, which is how the caller knows it is talking to the\n * daemon the state file describes and not to a stranger on a recycled port.\n */\nfunction ensureAdditionalDatabase(state: DaemonState, key: string): Promise<number> {\n return new Promise((resolve, reject) => {\n const socket = new net.Socket();\n let answer = \"\";\n let identified = false;\n const fail = (message: string) => {\n socket.removeAllListeners();\n socket.destroy();\n reject(new Error(message));\n };\n socket.setTimeout(START_TIMEOUT_MS);\n socket.once(\"timeout\", () => fail(`The development database did not answer for \"${key}\" within ${Math.round(START_TIMEOUT_MS / 1000)}s.`));\n socket.once(\"error\", (err) => fail(`Could not reach the development database for \"${key}\": ${err.message}`));\n socket.on(\"data\", (chunk) => {\n answer += chunk.toString(\"utf8\");\n let newline = answer.indexOf(\"\\n\");\n while (newline !== -1) {\n const line = answer.slice(0, newline).trim();\n answer = answer.slice(newline + 1);\n if (!identified) {\n if (line !== `rebase-dev-db ${state.token}`) return fail(\"The process on the development database's port is not the daemon.\");\n identified = true;\n socket.write(`ensure ${key}\\n`);\n } else if (line.startsWith(\"ok \")) {\n const port = Number(line.slice(3));\n socket.removeAllListeners();\n socket.destroy();\n if (!Number.isInteger(port) || port <= 0) return reject(new Error(`The development database answered with an invalid port for \"${key}\": ${line}`));\n return resolve(port);\n } else {\n return fail(`The development database could not serve \"${key}\": ${line.replace(/^error\\s*/, \"\")}`);\n }\n newline = answer.indexOf(\"\\n\");\n }\n });\n socket.connect(state.identityPort, \"127.0.0.1\");\n });\n}\n\n/** Every additional database the caller asked for, as connection strings. */\nasync function adoptWithAdditional(state: DaemonState, started: boolean, keys: readonly string[]): Promise<ManagedDatabase> {\n const additional: Record<string, string> = {};\n for (const key of keys) {\n const known = state.databases?.[key];\n const port = known ? known.port : await ensureAdditionalDatabase(state, key);\n additional[key] = managedUrl(port);\n }\n return { ...adopt(state, started), additional };\n}\n\n/**\n * The project's managed database, started if it is not already running.\n *\n * Safe to call concurrently from several commands: the loser of the race finds\n * the winner's state file during its poll and adopts it rather than starting a\n * second daemon.\n */\nexport async function ensureManagedDatabase(\n projectRoot: string,\n options: EnsureOptions = {}\n): Promise<ManagedDatabase> {\n const keys = options.additionalKeys ?? [];\n const existing = await findRunningDaemon(projectRoot);\n if (existing) return adoptWithAdditional(existing, false, keys);\n\n fs.mkdirSync(devDbDir(projectRoot), { recursive: true });\n ensureGitignore(projectRoot);\n\n // Exactly one process may spawn a daemon. Without this, `rebase dev` and\n // `rebase db push` started in the same second both see no state file, both\n // spawn, and two processes open one PGlite data directory — the corruption\n // the single-daemon design exists to prevent.\n if (!acquireStartLock(projectRoot, START_TIMEOUT_MS)) {\n const adopted = await waitForDaemon(projectRoot, START_TIMEOUT_MS);\n if (adopted) return adoptWithAdditional(adopted, false, keys);\n\n // The holder gave up or died without publishing. Falling through to\n // start one ourselves is better than failing: the lock is stale by now,\n // so the next acquire will break it.\n if (!acquireStartLock(projectRoot, 0)) {\n throw new Error(\n \"Another process is starting the development database and did not finish.\\n\" +\n ` Remove ${path.join(devDbDir(projectRoot), \"starting.lock\")} if nothing is running.`\n );\n }\n }\n\n const port = await findFreePort();\n const token = randomBytes(16).toString(\"hex\");\n const entry = options.entry ?? resolveCliEntry();\n\n const spawnPlan = options.spawn ?? resolveSpawn(entry);\n const args = [\n ...spawnPlan.prefixArgs,\n entry,\n \"__dev-db-daemon\",\n \"--project\", projectRoot,\n \"--port\", String(port),\n \"--token\", token\n ];\n if (options.idleTimeoutMs !== undefined) {\n args.push(\"--idle-timeout\", String(options.idleTimeoutMs));\n }\n\n const log = fs.openSync(path.join(devDbDir(projectRoot), \"pglite.log\"), \"a\");\n const child: ChildProcess = spawn(spawnPlan.execPath, args, {\n // Detached with no stdin and its output on a file: the daemon must\n // survive the command that started it, and must not hold the terminal\n // open when that command exits.\n detached: true,\n stdio: [\"ignore\", log, log]\n });\n child.unref();\n\n try {\n const deadline = Date.now() + START_TIMEOUT_MS;\n let announced = false;\n while (Date.now() < deadline) {\n const state = readState(projectRoot);\n if (state && (await isDaemonAlive(state))) return adoptWithAdditional(state, true, keys);\n\n if (!announced && !options.quiet) {\n announced = true;\n options.onProgress?.(\"Starting the development database…\");\n }\n\n // A daemon that died on startup will never publish a state file, and\n // waiting the full minute for that is a bad way to learn it. The log\n // is the only place the reason exists.\n if (child.exitCode !== null && child.exitCode !== 0) {\n throw new Error(\n `The development database failed to start (exit ${child.exitCode}).\\n` +\n ` ${path.join(devDbDir(projectRoot), \"pglite.log\")} ends:\\n` +\n pgliteLogTail(projectRoot)\n );\n }\n\n await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS));\n }\n\n throw new Error(\n `The development database did not start within ${Math.round(START_TIMEOUT_MS / 1000)}s.\\n` +\n ` ${path.join(devDbDir(projectRoot), \"pglite.log\")} ends:\\n` +\n pgliteLogTail(projectRoot) + \"\\n\" +\n \" To use your own Postgres instead, set DATABASE_URL or pass --database-url.\"\n );\n } finally {\n // Released whether we succeeded, timed out or threw. A lock left behind\n // by a crash is broken by age, but only after a full timeout — which a\n // developer should never have to wait out.\n releaseStartLock(projectRoot);\n }\n}\n\n/** Shape a live state record as the caller's result. */\nfunction adopt(state: DaemonState, started: boolean): ManagedDatabase {\n return {\n url: managedUrl(state.port),\n dataDir: state.dataDir,\n port: state.port,\n pid: state.pid,\n started,\n additional: {}\n };\n}\n\n/** Poll until another process publishes a live daemon, or give up. */\nasync function waitForDaemon(projectRoot: string, timeoutMs: number): Promise<DaemonState | null> {\n const deadline = Date.now() + timeoutMs;\n while (Date.now() < deadline) {\n const state = readState(projectRoot);\n if (state && (await isDaemonAlive(state))) return state;\n await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS));\n }\n\n return null;\n}\n\n/** Stop the daemon. Returns false when there was nothing running. */\nexport async function stopManagedDatabase(projectRoot: string): Promise<boolean> {\n const state = await findRunningDaemon(projectRoot);\n if (!state) return false;\n\n try {\n process.kill(state.pid, \"SIGTERM\");\n } catch {\n clearState(projectRoot);\n\n return false;\n }\n\n // Wait for it to clear its own state file, which is how it says it closed\n // the data directory cleanly. A hard kill here would risk the WAL.\n const deadline = Date.now() + 10_000;\n while (Date.now() < deadline) {\n if (!fs.existsSync(stateFile(projectRoot))) return true;\n await new Promise((resolve) => setTimeout(resolve, 100));\n }\n\n clearState(projectRoot);\n\n return true;\n}\n\n/**\n * Stop the daemon and delete the data directory.\n *\n * Destructive and deliberately not clever: it removes the whole directory\n * rather than dropping schemas, because \"give me an empty database\" is the only\n * thing anyone means by it.\n */\nexport async function resetManagedDatabase(projectRoot: string): Promise<void> {\n await stopManagedDatabase(projectRoot);\n fs.rmSync(dataDir(projectRoot), { recursive: true, force: true });\n // Every additional database too: `pgdata__<key>` beside `pgdata`. \"Give\n // me an empty database\" means all of them — a reset that emptied the\n // default and kept the analytics rows would be a surprise on first read.\n const dir = devDbDir(projectRoot);\n if (fs.existsSync(dir)) {\n for (const entry of fs.readdirSync(dir)) {\n if (entry.startsWith(\"pgdata__\")) fs.rmSync(path.join(dir, entry), { recursive: true, force: true });\n }\n }\n clearState(projectRoot);\n}\n\n/**\n * Keep the generated directory out of git.\n *\n * `.rebase/` holds a Postgres data directory and a log. A project that commits\n * it will push hundreds of megabytes and a database that only makes sense on\n * one machine, so the ignore file is written next to the data rather than\n * relying on the project's root `.gitignore` having been updated.\n */\nfunction ensureGitignore(projectRoot: string): void {\n const target = path.join(devDbDir(projectRoot), \".gitignore\");\n if (fs.existsSync(target)) return;\n fs.writeFileSync(\n target,\n \"# Generated by `rebase dev` — a local Postgres data directory and its log.\\n\" +\n \"# Machine-specific and large; never commit it.\\n*\\n\",\n \"utf8\"\n );\n}\n","/**\n * Which database a command should talk to, decided in one place.\n *\n * Before this existed every command that needed Postgres read `DATABASE_URL`\n * for itself, which was fine while there was exactly one answer. Introducing a\n * managed database makes the question real: a project may have no\n * `DATABASE_URL` at all and still expect `rebase db push` to work, and a\n * project that *does* set one must never be quietly redirected somewhere else.\n *\n * So the rule is ordered and boring, and the order is the promise:\n *\n * 1. `--database-url <url>` — said on this command line, wins over everything\n * 2. `DATABASE_URL` in the shell environment\n * 3. the database branch this checkout is switched to\n * 4. `DATABASE_URL` in the project's `.env`\n * 5. `--docker`\n * 6. the managed PGlite database\n *\n * An explicit connection string always wins. That is the whole point of the\n * override: someone pointing Rebase at their own Postgres — a colleague's\n * staging box, a Neon branch, a container they manage — must get exactly that,\n * with no cleverness in between. The managed database is what fills the vacuum\n * when nobody has said anything, and it is the only case where the CLI picks.\n *\n * The branch at (3) is the one addition that is not an explicit connection\n * string, and it sits where it does deliberately. It has to outrank `.env` or\n * `rebase db branch switch` would silently do nothing on every project that\n * sets `DATABASE_URL` — which is every project not on the managed database.\n * It must not outrank (1) or (2), because a flag on this command line and a\n * variable in this shell are more immediate than a switch made yesterday.\n *\n * {@link resolveDevDatabase} is pure: inputs in, decision out, no filesystem\n * and no process. Reading `.env`, reading the branch pointer and starting a\n * daemon happen elsewhere, so the ordering above can be tested without any of\n * them.\n */\n\n/** Where the answer came from. Carried so diagnostics can name it. */\nexport type DevDatabaseSource =\n /** `--database-url` on the command line. */\n | \"flag\"\n /** `DATABASE_URL` in the shell environment. */\n | \"environment\"\n /** `DATABASE_URL` in the project's `.env`. */\n | \"env-file\"\n /** The branch this checkout is switched to, over the base connection. */\n | \"branch\"\n /** `--docker`. */\n | \"docker\"\n /** Nobody said anything, so the managed database fills in. */\n | \"managed\";\n\nexport type DevDatabase =\n | {\n kind: \"external\";\n /** The connection string, exactly as given. Never rewritten. */\n url: string;\n source: Extract<DevDatabaseSource, \"flag\" | \"environment\" | \"env-file\" | \"branch\">;\n /** The branch name, when this checkout is switched to one. */\n branch?: string;\n }\n | {\n kind: \"docker\";\n source: \"docker\";\n /**\n * The compose `db` service's connection string, derived by the caller.\n *\n * Null when the project has no compose file to derive one from, which\n * is a `--docker` that cannot be honoured rather than a fallback: the\n * caller says so instead of guessing at `localhost:5432`.\n */\n url: string | null;\n }\n | {\n kind: \"managed\";\n source: \"managed\";\n };\n\nexport interface ResolveDevDatabaseInput {\n /** `--database-url <url>`, if given. */\n flagUrl?: string | null;\n /** `--docker`, if given. */\n flagDocker?: boolean;\n /** The shell environment. Only `DATABASE_URL` is read. */\n env?: Record<string, string | undefined>;\n /** Parsed `.env` from the project root. Only `DATABASE_URL` is read. */\n envFile?: Record<string, string> | null;\n /**\n * The compose `db` service's connection string, when the project has one.\n *\n * Passed in rather than read here so this function stays pure. Only\n * consulted for the Docker case; an explicit `DATABASE_URL` still wins.\n */\n composeUrl?: string | null;\n /**\n * The branch this checkout is switched to, already resolved to a URL.\n *\n * Resolved by the caller rather than here so this stays pure: deriving it\n * needs the base connection string, which needs the `.env` this function is\n * handed rather than reads.\n */\n branch?: { name: string; url: string } | null;\n}\n\n/**\n * A value that is present but empty is treated as absent.\n *\n * `DATABASE_URL=` in a `.env` is what a developer writes when they mean \"not\n * this one\" — honouring it literally would hand an empty connection string to\n * libpq and produce an error about a missing host, which explains nothing.\n */\nfunction present(value: string | undefined | null): string | null {\n if (typeof value !== \"string\") return null;\n const trimmed = value.trim();\n\n return trimmed.length > 0 ? trimmed : null;\n}\n\nexport function resolveDevDatabase(input: ResolveDevDatabaseInput = {}): DevDatabase {\n const flagUrl = present(input.flagUrl);\n if (flagUrl) return { kind: \"external\", url: flagUrl, source: \"flag\" };\n\n const fromEnvironment = present(input.env?.DATABASE_URL);\n if (fromEnvironment) return { kind: \"external\", url: fromEnvironment, source: \"environment\" };\n\n // Above `.env`, below anything said explicitly — see the ordering note at\n // the top of this file.\n const fromBranch = present(input.branch?.url);\n if (fromBranch && input.branch) {\n return { kind: \"external\", url: fromBranch, source: \"branch\", branch: input.branch.name };\n }\n\n const fromEnvFile = present(input.envFile?.DATABASE_URL);\n if (fromEnvFile) return { kind: \"external\", url: fromEnvFile, source: \"env-file\" };\n\n // Only consulted once every explicit connection string is exhausted: asking\n // for Docker is a choice about *how to get* a database, not which one, so a\n // DATABASE_URL that already names one outranks it.\n if (input.flagDocker) {\n return { kind: \"docker\", source: \"docker\", url: present(input.composeUrl) };\n }\n\n return { kind: \"managed\", source: \"managed\" };\n}\n\n/** One line for the startup banner, naming both the database and why. */\nexport function describeDevDatabase(database: DevDatabase): string {\n switch (database.kind) {\n case \"external\":\n switch (database.source) {\n case \"flag\":\n return \"your database (--database-url)\";\n case \"environment\":\n return \"your database (DATABASE_URL in the environment)\";\n case \"env-file\":\n return \"your database (DATABASE_URL in .env)\";\n case \"branch\":\n return `branch \"${database.branch}\" (rebase db branch switch)`;\n }\n break;\n case \"docker\":\n return \"Postgres in Docker\";\n case \"managed\":\n return \"the managed development database (PGlite)\";\n }\n\n // Unreachable while the union is exhaustive; kept so a future variant fails\n // loudly in review rather than printing \"undefined\" to a user.\n throw new Error(`Unhandled dev database: ${JSON.stringify(database)}`);\n}\n","/**\n * The one place a command asks \"which database, and how do I reach it?\".\n *\n * Every command that touches Postgres — `dev`, and the whole `db` namespace\n * through the driver plugin — goes through {@link prepareDatabaseEnv}. It\n * resolves the ordering in `resolve.ts`, starts the managed database if that is\n * what the ordering chose, and hands back the environment additions the child\n * process needs.\n *\n * Two things it deliberately does *not* do:\n *\n * - **It never overwrites an existing `DATABASE_URL`.** When the developer has\n * named a database, the returned environment is empty and the child inherits\n * exactly what it would have inherited before this feature existed. A\n * migration must never be redirected away from the database its author meant.\n *\n * - **It never starts anything for a command that is not going to connect.**\n * `--help` and argument errors are handled by the caller before this is\n * reached, because booting a Postgres to print usage would be absurd.\n */\n\nimport fs from \"fs\";\nimport path from \"path\";\n\nimport { composeDatabaseUrl } from \"../utils/dev-preflight\";\nimport { findProjectRoot, readEnvFile } from \"../utils/project\";\nimport { resourceEnvSuffix } from \"@rebasepro/types\";\nimport { branchUrl, databaseNameOf, readActiveBranch } from \"./branch-pointer\";\nimport { MANAGED_LIMITATIONS, MANAGED_POOL_MAX } from \"./constraints\";\nimport { ensureManagedDatabase } from \"./daemon\";\nimport { type DevDatabase, describeDevDatabase, resolveDevDatabase } from \"./resolve\";\n\nexport interface PrepareOptions {\n /** `--database-url <url>`. */\n flagUrl?: string | null;\n /** `--docker`. */\n flagDocker?: boolean;\n /** Suppress the \"starting…\" progress line. */\n quiet?: boolean;\n /** Where human-facing lines go. Defaults to stdout via the caller. */\n onProgress?: (message: string) => void;\n /**\n * Keys of the additional databases the project declares —\n * `database(\"analytics\")` → `analytics`. On the managed path each gets\n * its own PGlite instance and a `DATABASE_URL__<KEY>` in the child's\n * environment, unless the developer set that variable themselves. On an\n * external database nothing is added: whoever chose the database binds\n * the rest of them.\n */\n additionalKeys?: readonly string[];\n}\n\nexport interface PreparedDatabase {\n /** What the resolver chose, for the banner and for tests. */\n database: DevDatabase;\n /**\n * Variables to add to a child process's environment.\n *\n * Empty for an external database: the child already has what it needs, and\n * adding to it could only do harm.\n */\n env: Record<string, string>;\n /** One line naming the database, suitable for a startup banner. */\n description: string;\n /** Absolute path of the managed data directory, when there is one. */\n dataDir?: string;\n /** True when this call started the managed database rather than finding it. */\n startedDaemon?: boolean;\n /** The additional managed databases, by declared key, when any were asked for. */\n additional?: Record<string, string>;\n}\n\n/**\n * The branch this checkout is switched to, as a connection string.\n *\n * Derived rather than stored: the pointer holds a name, and the credentials\n * stay in the one place the developer wrote them. The base is the project's\n * `.env` — a switch is a statement about *this project's* database, so a\n * `DATABASE_URL` that happens to be in the shell is not the thing being\n * branched, and it outranks the branch anyway.\n *\n * Returns null when there is no branch, and also when there is no base to swap\n * the database name on: a project with no `DATABASE_URL` is on the managed\n * database, where branching does not work at all.\n *\n * Exported for its tests. Asserting this through {@link prepareDatabaseEnv}\n * would start a real PGlite daemon to check a decision made before any daemon\n * is involved.\n */\nexport function resolveActiveBranch(\n projectRoot: string,\n envFile: Record<string, string>\n): { name: string; url: string } | null {\n const active = readActiveBranch(projectRoot);\n if (!active) return null;\n\n const base = envFile.DATABASE_URL?.trim();\n if (!base) return null;\n\n const url = branchUrl(base, active.database);\n\n return url ? { name: active.name, url } : null;\n}\n\n/**\n * The compose `db` service's connection string for this project, or null.\n *\n * Read here rather than in `resolve.ts` so the ordering there stays pure, and\n * exported so `rebase dev` can hand the same URL to the preflight that starts\n * the container — the preflight decides \"local, and not running\" from a DSN,\n * so without one `--docker` asked for a container nothing ever started.\n */\nexport function resolveComposeUrl(projectRoot: string, envFile: Record<string, string>): string | null {\n const composePath = path.join(projectRoot, \"docker-compose.yml\");\n if (!fs.existsSync(composePath)) return null;\n try {\n return composeDatabaseUrl(fs.readFileSync(composePath, \"utf8\"), envFile);\n } catch {\n return null;\n }\n}\n\n/**\n * Which database a project is on, decided without starting anything.\n *\n * `resolveDevDatabase` is pure, so the answer is available to help text and to\n * a driver child alike — and neither should pay for a daemon to learn it. The\n * driver cannot work it out for itself: on the managed path `DATABASE_URL` is a\n * perfectly ordinary connection string to a Postgres on loopback, so a child\n * handed only that has no way to know that `rebase db generate` will be refused.\n *\n * Null when there is no project here, which is legal — `rebase db --help` is\n * answered outside one — and when anything at all goes wrong reading the\n * project, because help text must not fail to print over an unreadable `.env`.\n */\nexport function devDatabaseKind(projectRoot?: string | null): DevDatabase[\"kind\"] | null {\n try {\n const root = projectRoot ?? findProjectRoot();\n if (!root) return null;\n const envFile = readEnvFile(root);\n\n return resolveDevDatabase({\n env: process.env,\n envFile,\n branch: resolveActiveBranch(root, envFile),\n composeUrl: resolveComposeUrl(root, envFile)\n }).kind;\n } catch {\n return null;\n }\n}\n\n/**\n * The variable a driver child reads {@link devDatabaseKind} from.\n *\n * One name, exported, because three commands build that child's environment and\n * a fourth reads it in another package.\n */\nexport const DEV_DATABASE_KIND_ENV = \"REBASE_DEV_DATABASE_KIND\";\n\n/**\n * The variable that tells a server started on a branch which database it was\n * branched from.\n *\n * The branch registry lives in that database, and a branch database holds\n * only a snapshot of it taken when it was copied — so a server on a branch\n * keeps its branches, and copies new ones from, the database named here. A\n * branch URL does not say which that is; only this checkout's `.env` does.\n */\nexport const BRANCH_PARENT_DATABASE_ENV = \"REBASE_BRANCH_PARENT_DATABASE\";\n\n/**\n * Resolve, start if needed, and describe the database for this command.\n *\n * `projectRoot` is where the managed database's data lives, so two projects on\n * one machine get two databases without either being told about the other.\n */\nexport async function prepareDatabaseEnv(\n projectRoot: string,\n options: PrepareOptions = {}\n): Promise<PreparedDatabase> {\n const envFile = readEnvFile(projectRoot);\n const database = resolveDevDatabase({\n flagUrl: options.flagUrl,\n flagDocker: options.flagDocker,\n env: process.env,\n envFile,\n branch: resolveActiveBranch(projectRoot, envFile),\n composeUrl: resolveComposeUrl(projectRoot, envFile)\n });\n\n const description = describeDevDatabase(database);\n\n if (database.kind === \"external\" && (database.source === \"branch\" || database.source === \"flag\")) {\n // The two external cases that MUST be exported. `environment` and\n // `env-file` are already somewhere the child will look — the shell it\n // inherits, or `.env` via DOTENV_CONFIG_PATH — but a branch URL is\n // derived here and exists nowhere else, and `--database-url` lives only\n // in this process's argv. Without this the pointer resolves correctly\n // and then changes nothing: `rebase db backup` on a switched checkout\n // still reported `Database: leadgen`, and `rebase dev --database-url …`\n // announced the database and then died on `DATABASE_URL: is required`.\n //\n // Safe to set: `dotenv` does not overwrite a variable that is already\n // in the environment, so the child's own `.env` load cannot undo it.\n // On a branch, the database it was branched from too: the one\n // `resolveActiveBranch` swapped the name on.\n const parent = database.source === \"branch\" ? databaseNameOf(envFile.DATABASE_URL?.trim() ?? \"\") : null;\n return {\n database,\n env: { DATABASE_URL: database.url, ...(parent ? { [BRANCH_PARENT_DATABASE_ENV]: parent } : {}) },\n description\n };\n }\n\n if (database.kind === \"docker\") {\n // Docker is the one case where nothing has named a connection string:\n // `.env` leaves DATABASE_URL commented out, so without this the child\n // booted with no database at all and failed on the message a project\n // that configured nothing gets — while the container it had just been\n // asked for was never started either.\n if (!database.url) {\n throw new Error(\n \"--docker needs a docker-compose.yml with a db service in this project, \" +\n \"and this one has none that names POSTGRES_USER, POSTGRES_DB and a published port. \" +\n \"Set DATABASE_URL in .env to point at the database you mean instead.\"\n );\n }\n return { database, env: { DATABASE_URL: database.url }, description };\n }\n\n if (database.kind !== \"managed\") {\n // `environment` or `env-file`: the connection string is already in a\n // place the child reads for itself, so this adds nothing to it.\n return { database, env: {}, description };\n }\n\n // Only the keys nobody bound by hand. A developer who set\n // DATABASE_URL__ANALYTICS to a warehouse of their own has said which\n // database that is, and the managed one fills a vacuum, never a choice.\n const unbound = (options.additionalKeys ?? []).filter((key) => {\n const name = `DATABASE_URL${resourceEnvSuffix(key)}`;\n return !process.env[name] && !envFile[name];\n });\n\n const managed = await ensureManagedDatabase(projectRoot, {\n quiet: options.quiet,\n onProgress: options.onProgress,\n additionalKeys: unbound\n });\n\n const additionalEnv: Record<string, string> = {};\n for (const [key, url] of Object.entries(managed.additional)) {\n additionalEnv[`DATABASE_URL${resourceEnvSuffix(key)}`] = url;\n }\n\n return {\n database,\n description,\n dataDir: managed.dataDir,\n startedDaemon: managed.started,\n additional: managed.additional,\n env: {\n DATABASE_URL: managed.url,\n // PGlite is one session behind a multiplexer: two pooled clients in\n // overlapping transactions deadlock there. This is the ceiling that\n // turns that into ordinary queueing — see `constraints.ts`.\n REBASE_DB_POOL_MAX: String(MANAGED_POOL_MAX),\n ...additionalEnv\n }\n };\n}\n\n/**\n * The lines to print about a managed database, in the order to print them.\n *\n * Returned rather than printed so the caller decides where they go — `dev` has\n * a banner, `db push` has a single line above its own output — and so a test can\n * assert on them without capturing a stream.\n */\nexport function managedNotices(prepared: PreparedDatabase): string[] {\n if (prepared.database.kind !== \"managed\") return [];\n\n const lines = [`Using ${prepared.description}.`];\n if (prepared.dataDir) lines.push(`Data: ${prepared.dataDir}`);\n for (const key of Object.keys(prepared.additional ?? {})) {\n lines.push(`Also serving database \"${key}\" as DATABASE_URL${resourceEnvSuffix(key)}`);\n }\n\n // Stated every time rather than discovered. A developer who does not know\n // requests are serialized here will read a concurrency difference as a bug\n // in their own code.\n for (const limitation of MANAGED_LIMITATIONS) {\n lines.push(`${limitation.summary} ${limitation.remedy}`);\n }\n lines.push(\"To use your own Postgres instead, set DATABASE_URL in .env.\");\n\n return lines;\n}\n"],"mappings":"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"}
@@ -1,189 +1,12 @@
1
1
  import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.js";
2
+ import { n as getPMCommands, t as detectPackageManager } from "./package-manager-DSEOwTqM.js";
2
3
  import chalk from "chalk";
3
- import path from "path";
4
4
  import fs from "fs";
5
- import { execSync, spawnSync } from "child_process";
5
+ import path from "path";
6
+ import { pathToFileURL } from "url";
7
+ import { createRequire } from "module";
8
+ import { execSync } from "child_process";
6
9
  import dotenv from "dotenv";
7
- //#region src/utils/package-manager.ts
8
- /**
9
- * Package manager detection and command abstraction.
10
- *
11
- * Detects whether the user is running pnpm or npm and provides
12
- * a unified interface for common package-manager operations so
13
- * the rest of the CLI never has to hardcode a specific PM.
14
- */
15
- /**
16
- * How long to wait for `pnpm --version` before giving up on the probe.
17
- *
18
- * `pnpm --version` is a cold Node start, and on a machine that is busy — a
19
- * parallel install, a full test run — it routinely takes seconds. Measured at
20
- * 630ms, 990ms and 4293ms on three consecutive runs of one developer laptop
21
- * under load, so the previous 3s budget was inside the normal spread rather
22
- * than safely outside it.
23
- */
24
- var PNPM_PROBE_TIMEOUT_MS = 5e3;
25
- /** Memoised result of the probe. pnpm cannot appear or vanish mid-process. */
26
- var cachedPnpmAvailable;
27
- /**
28
- * Decide availability from a `spawnSync` outcome.
29
- *
30
- * Split out from the spawn itself so the decision is testable without starting
31
- * a process — which is what made the old test load-sensitive and occasionally
32
- * red for reasons that had nothing to do with the code under test.
33
- *
34
- * The three outcomes are distinguishable, and the old code conflated two of
35
- * them by asking only `status === 0`:
36
- *
37
- * not installed status null, signal null, error.code ENOENT
38
- * timed out status null, signal SIGTERM, error.code ETIMEDOUT
39
- * broken install status non-zero, no error
40
- */
41
- function pnpmAvailabilityFromProbe(res) {
42
- const code = res.error?.code;
43
- if (code === "ENOENT") return false;
44
- if (code === "ETIMEDOUT" || res.signal) return true;
45
- if (res.error) return false;
46
- return res.status === 0;
47
- }
48
- /**
49
- * Whether pnpm is runnable on this machine.
50
- *
51
- * Used to decide whether a fresh project can be scaffolded with pnpm. Kept
52
- * cheap and non-interactive (bounded timeout, output discarded) so it never
53
- * hangs detection if a corepack shim misbehaves, and memoised so that repeated
54
- * detection in one CLI run costs one process rather than one per call.
55
- */
56
- function isPnpmAvailable() {
57
- if (cachedPnpmAvailable !== void 0) return cachedPnpmAvailable;
58
- try {
59
- cachedPnpmAvailable = pnpmAvailabilityFromProbe(spawnSync("pnpm", ["--version"], {
60
- stdio: "ignore",
61
- timeout: PNPM_PROBE_TIMEOUT_MS
62
- }));
63
- } catch {
64
- cachedPnpmAvailable = false;
65
- }
66
- return cachedPnpmAvailable;
67
- }
68
- /**
69
- * Detect the package manager for a Rebase project.
70
- *
71
- * Rebase recommends pnpm, so detection prefers it. Crucially, *how the CLI was
72
- * invoked* (`npx` vs `pnpm dlx`, i.e. `npm_config_user_agent`) is deliberately
73
- * ignored: running `npx @rebasepro/cli init` says nothing about how the user
74
- * wants to manage the project they're creating, and letting it pin the scaffold
75
- * to npm is what made every `npx`-invoked project an npm project.
76
- *
77
- * Detection order:
78
- * 1. An existing lock file — an explicit choice we always respect
79
- * (`pnpm-lock.yaml` wins over `package-lock.json` when both are present).
80
- * 2. pnpm, whenever it is installed.
81
- * 3. npm, only as a fallback when pnpm is genuinely unavailable.
82
- */
83
- function detectPackageManager(targetDir) {
84
- const dirs = [targetDir, process.cwd()].filter((d) => !!d);
85
- for (const dir of dirs) {
86
- if (fs.existsSync(path.join(dir, "pnpm-lock.yaml"))) return "pnpm";
87
- if (fs.existsSync(path.join(dir, "package-lock.json"))) return "npm";
88
- for (const [lock, name] of [
89
- ["yarn.lock", "yarn"],
90
- ["bun.lockb", "bun"],
91
- ["bun.lock", "bun"]
92
- ]) {
93
- if (!fs.existsSync(path.join(dir, lock))) continue;
94
- console.warn(`[rebase] Found ${lock}, but Rebase scaffolds pnpm or npm workspaces only — ${name} is not supported yet. Continuing with ${isPnpmAvailable() ? "pnpm" : "npm"}.`);
95
- return isPnpmAvailable() ? "pnpm" : "npm";
96
- }
97
- }
98
- if (isPnpmAvailable()) return "pnpm";
99
- return "npm";
100
- }
101
- /** Build the command helpers for a given package manager. */
102
- function getPMCommands(pm) {
103
- if (pm === "npm") return {
104
- name: "npm",
105
- install: ["npm", "install"],
106
- run: (script) => [
107
- "npm",
108
- "run",
109
- script
110
- ],
111
- exec: (bin, args) => [
112
- "npx",
113
- bin,
114
- ...args
115
- ],
116
- view: (pkg, field) => [
117
- "npm",
118
- "view",
119
- pkg,
120
- field
121
- ],
122
- runAll: (script) => [
123
- "npm",
124
- "run",
125
- script,
126
- "--workspaces",
127
- "--if-present"
128
- ],
129
- runWorkspace: (workspace, script) => [
130
- "npm",
131
- "run",
132
- script,
133
- "-w",
134
- workspace
135
- ],
136
- dlx: (pkg, args) => [
137
- "npx",
138
- "-y",
139
- pkg,
140
- ...args
141
- ],
142
- workspaceProtocol: "*"
143
- };
144
- return {
145
- name: "pnpm",
146
- install: ["pnpm", "install"],
147
- run: (script) => [
148
- "pnpm",
149
- "run",
150
- script
151
- ],
152
- exec: (bin, args) => [
153
- "pnpm",
154
- "exec",
155
- bin,
156
- ...args
157
- ],
158
- view: (pkg, field) => [
159
- "pnpm",
160
- "view",
161
- pkg,
162
- field
163
- ],
164
- runAll: (script) => [
165
- "pnpm",
166
- "-r",
167
- "run",
168
- script
169
- ],
170
- runWorkspace: (workspace, script) => [
171
- "pnpm",
172
- "--filter",
173
- `./${workspace}`,
174
- "run",
175
- script
176
- ],
177
- dlx: (pkg, args) => [
178
- "pnpm",
179
- "dlx",
180
- pkg,
181
- ...args
182
- ],
183
- workspaceProtocol: "workspace:*"
184
- };
185
- }
186
- //#endregion
187
10
  //#region src/utils/project.ts
188
11
  /**
189
12
  * Project discovery utilities for the Rebase CLI.
@@ -207,6 +30,7 @@ var project_exports = /* @__PURE__ */ __exportAll({
207
30
  resolveLocalBin: () => resolveLocalBin,
208
31
  resolvePluginCliScript: () => resolvePluginCliScript,
209
32
  resolveTsx: () => resolveTsx,
33
+ resolveTsxLoader: () => resolveTsxLoader,
210
34
  validateTsxInstallation: () => validateTsxInstallation,
211
35
  wantsJsonOutput: () => wantsJsonOutput
212
36
  });
@@ -355,6 +179,32 @@ function resolveTsx(projectRoot) {
355
179
  return resolveLocalBin(projectRoot, "tsx");
356
180
  }
357
181
  /**
182
+ * tsx's loader, as the URL `node --import` takes, or null when none resolves.
183
+ *
184
+ * For running a TypeScript entry in one Node process. The `tsx` binary is a
185
+ * relay — pnpm's `.bin/tsx` shell shim, then tsx's CLI in one Node, which
186
+ * spawns a second Node with this same loader to run the script — so spawning
187
+ * through it costs a shell and a Node start (~50–100 ms) on every hop, and
188
+ * `rebase dev` pays a hop on every start and every collection save.
189
+ *
190
+ * Resolved the way Node resolves the package, from the project first — the
191
+ * backend, where a scaffold declares tsx, then the root and everything above it
192
+ * — so the tsx used is the one {@link resolveTsx} would have found; then from
193
+ * this package, as the repository's own tests resolve `tsx/cli`. A URL rather
194
+ * than a path: a Windows path is not a valid `--import` specifier.
195
+ */
196
+ function resolveTsxLoader(projectRoot) {
197
+ const bases = [
198
+ path.join(projectRoot, "backend", "noop.js"),
199
+ path.join(projectRoot, "noop.js"),
200
+ import.meta.url
201
+ ];
202
+ for (const base of bases) try {
203
+ return pathToFileURL(createRequire(base).resolve("tsx")).href;
204
+ } catch {}
205
+ return null;
206
+ }
207
+ /**
358
208
  * Validate that a resolved tsx binary actually has an intact installation.
359
209
  *
360
210
  * `resolveLocalBin` only checks whether `node_modules/.bin/tsx` (a symlink)
@@ -494,6 +344,6 @@ function requireBackendDir(projectRoot) {
494
344
  return backendDir;
495
345
  }
496
346
  //#endregion
497
- export { validateTsxInstallation as _, findBackendDir as a, findProjectRoot as c, readEnvFile as d, requireBackendDir as f, resolveTsx as g, resolvePluginCliScript as h, failAsJson as i, getActiveBackendPlugin as l, resolveLocalBin as m, dependenciesNotInstalled as n, findEnvFile as o, requireProjectRoot as p, exitDependenciesNotInstalled as r, findFrontendDir as s, MANIFEST_FILENAME as t, project_exports as u, detectPackageManager as v, getPMCommands as y };
347
+ export { resolveTsxLoader as _, findBackendDir as a, findProjectRoot as c, readEnvFile as d, requireBackendDir as f, resolveTsx as g, resolvePluginCliScript as h, failAsJson as i, getActiveBackendPlugin as l, resolveLocalBin as m, dependenciesNotInstalled as n, findEnvFile as o, requireProjectRoot as p, exitDependenciesNotInstalled as r, findFrontendDir as s, MANIFEST_FILENAME as t, project_exports as u, validateTsxInstallation as v };
498
348
 
499
- //# sourceMappingURL=project-3WCbrrfW.js.map
349
+ //# sourceMappingURL=project-Wp2zoyeN.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"project-Wp2zoyeN.js","names":[],"sources":["../src/utils/project.ts"],"sourcesContent":["/**\n * Project discovery utilities for the Rebase CLI.\n *\n * These helpers locate the project root, backend directory, .env file,\n * and local binaries — used by all CLI command modules.\n */\nimport fs from \"fs\";\nimport { createRequire } from \"module\";\nimport path from \"path\";\nimport { pathToFileURL } from \"url\";\nimport { execSync } from \"child_process\";\nimport dotenv from \"dotenv\";\nimport chalk from \"chalk\";\nimport { detectPackageManager, getPMCommands } from \"./package-manager\";\n\n/** The authored project manifest. Its presence alone marks a project root. */\nexport const MANIFEST_FILENAME = \"rebase.json\";\n\n/**\n * Walk up from `startDir` to find the Rebase project root.\n *\n * A directory is the root when it holds a `rebase.json`, or when it holds a\n * `package.json` that either lists `backend` as a workspace or sits beside both\n * `backend/` and `config/`.\n *\n * `rebase.json` is checked first and needs no `package.json` beside it, because\n * the conventions below all describe a repository that *contains the backend*.\n * A repository holding only a frontend — the normal shape once a project's apps\n * live in separate repositories — matches none of them, so without this the\n * tooling could not run there at all.\n */\nexport function findProjectRoot(startDir: string = process.cwd()): string | null {\n let dir = path.resolve(startDir);\n const root = path.parse(dir).root;\n\n while (dir !== root) {\n if (fs.existsSync(path.join(dir, MANIFEST_FILENAME))) {\n return dir;\n }\n\n const pkgPath = path.join(dir, \"package.json\");\n\n if (fs.existsSync(pkgPath)) {\n try {\n const pkg = JSON.parse(fs.readFileSync(pkgPath, \"utf-8\"));\n // Check for workspace-based project (monorepo root)\n if (pkg.workspaces && Array.isArray(pkg.workspaces)) {\n const hasBackend = pkg.workspaces.some((w: string) =>\n w === \"backend\"\n );\n if (hasBackend) return dir;\n }\n } catch {\n // ignore parse errors\n }\n\n // Check for sibling backend directory\n if (fs.existsSync(path.join(dir, \"backend\")) && fs.existsSync(path.join(dir, \"config\"))) {\n return dir;\n }\n }\n\n dir = path.dirname(dir);\n }\n\n return null;\n}\n\n/**\n * Locate the backend directory within the project root.\n */\nexport function findBackendDir(projectRoot: string): string | null {\n const backendDir = path.join(projectRoot, \"backend\");\n return fs.existsSync(backendDir) ? backendDir : null;\n}\n\n/**\n * Detect the active backend plugin (e.g. @rebasepro/server-postgres) from the backend's package.json.\n */\nexport function getActiveBackendPlugin(backendDir: string): string | null {\n const pkgPath = path.join(backendDir, \"package.json\");\n if (!fs.existsSync(pkgPath)) return null;\n\n try {\n const pkg = JSON.parse(fs.readFileSync(pkgPath, \"utf-8\"));\n const deps = { ...pkg.dependencies,\n...pkg.devDependencies };\n\n // Collect all @rebasepro/server-* driver plugins (exclude server itself)\n const candidates = Object.keys(deps).filter(\n dep => dep.startsWith(\"@rebasepro/server-\") && dep !== \"@rebasepro/server\"\n );\n\n if (candidates.length === 0) return null;\n\n // Prefer server-postgres — it's the primary supported driver\n if (candidates.includes(\"@rebasepro/server-postgres\")) {\n return \"@rebasepro/server-postgres\";\n }\n\n // Fallback: return the first candidate that actually has a CLI entry point\n for (const candidate of candidates) {\n if (resolvePluginCliScript(backendDir, candidate)) {\n return candidate;\n }\n }\n\n // Last resort: return whatever we found\n return candidates[0];\n } catch {\n // Ignore parse errors\n }\n return null;\n}\n\n/**\n * Resolve the active plugin's CLI script.\n */\nexport function resolvePluginCliScript(backendDir: string, pluginName: string): string | null {\n const candidates: string[] = [];\n\n // Walk up from the backend dir: pnpm links the plugin into\n // backend/node_modules, while npm workspaces hoist it to the project (or an\n // enclosing monorepo) root.\n let dir = path.resolve(backendDir);\n const fsRoot = path.parse(dir).root;\n while (dir !== fsRoot) {\n candidates.push(\n path.join(dir, \"node_modules\", pluginName, \"src\", \"cli.ts\"),\n path.join(dir, \"node_modules\", pluginName, \"dist\", \"cli.js\")\n );\n dir = path.dirname(dir);\n }\n\n candidates.push(\n // For monorepo dev mode:\n path.resolve(backendDir, \"..\", \"..\", \"..\", \"packages\", pluginName.replace(\"@rebasepro/\", \"\"), \"src\", \"cli.ts\"),\n path.resolve(backendDir, \"..\", \"..\", \"packages\", pluginName.replace(\"@rebasepro/\", \"\"), \"src\", \"cli.ts\"),\n path.resolve(backendDir, \"..\", \"packages\", pluginName.replace(\"@rebasepro/\", \"\"), \"src\", \"cli.ts\")\n );\n\n for (const candidate of candidates) {\n if (fs.existsSync(candidate)) return candidate;\n }\n return null;\n}\n\n/**\n * Locate the frontend directory within the project root.\n */\nexport function findFrontendDir(projectRoot: string): string | null {\n const frontendDir = path.join(projectRoot, \"frontend\");\n return fs.existsSync(frontendDir) ? frontendDir : null;\n}\n\n/**\n * Find the .env file. Checks the project root first, then backend.\n */\nexport function findEnvFile(projectRoot: string): string | null {\n const candidates = [\n path.join(projectRoot, \".env\"),\n path.join(projectRoot, \"backend\", \".env\")\n ];\n\n for (const candidate of candidates) {\n if (fs.existsSync(candidate)) return candidate;\n }\n\n return null;\n}\n\n/**\n * Read the project's `.env` into a plain object.\n *\n * One reader, because there were four: `dotenv` in `start`, a hand-rolled\n * `indexOf(\"=\")` loop in `api-keys`, a single-key regex in `auth`, and its own\n * splitting in `cloud env`. `dotenv` is a declared dependency of this package,\n * so the other three existed for no reason and disagreed with the correct one\n * on the two things people actually write in a `.env`:\n *\n * - `export KEY=value`, which the hand-rolled parser keyed as\n * `export KEY` — so the command reported the key as unset while it was\n * right there in the file;\n * - `KEY=value # comment`, whose comment travelled into the value and then\n * into an `Authorization` header, coming back as a 401 with nothing\n * pointing at the cause.\n *\n * Returns `{}` when the project has no `.env`, so callers can treat \"absent\"\n * and \"empty\" alike.\n */\nexport function readEnvFile(projectRoot: string): Record<string, string> {\n const envFile = findEnvFile(projectRoot);\n if (!envFile || !fs.existsSync(envFile)) return {};\n try {\n return dotenv.parse(fs.readFileSync(envFile, \"utf-8\"));\n } catch {\n // An unreadable or malformed file is not worth failing a command over;\n // the caller reports the missing value it was looking for.\n return {};\n }\n}\n\n/**\n * Resolve a binary from the project's node_modules/.bin.\n * Checks backend, root, parent monorepo root, then falls back to PATH.\n */\nexport function resolveLocalBin(projectRoot: string, binName: string): string | null {\n const candidates = [\n path.join(projectRoot, \"backend\", \"node_modules\", \".bin\", binName),\n path.join(projectRoot, \"node_modules\", \".bin\", binName)\n ];\n\n // Also check parent directories (for monorepo setups where app/ is nested)\n let parent = path.dirname(projectRoot);\n const rootDir = path.parse(parent).root;\n while (parent !== rootDir) {\n candidates.push(path.join(parent, \"node_modules\", \".bin\", binName));\n parent = path.dirname(parent);\n }\n\n for (const candidate of candidates) {\n if (fs.existsSync(candidate)) return candidate;\n }\n\n // Fall back to globally installed binary via which\n try {\n const globalPath = execSync(`which ${binName}`, { encoding: \"utf-8\" }).trim();\n if (globalPath && fs.existsSync(globalPath)) return globalPath;\n } catch {\n // not found globally\n }\n\n return null;\n}\n\n/**\n * Resolve the tsx binary. Checks backend node_modules first, then root.\n */\nexport function resolveTsx(projectRoot: string): string | null {\n return resolveLocalBin(projectRoot, \"tsx\");\n}\n\n/**\n * tsx's loader, as the URL `node --import` takes, or null when none resolves.\n *\n * For running a TypeScript entry in one Node process. The `tsx` binary is a\n * relay — pnpm's `.bin/tsx` shell shim, then tsx's CLI in one Node, which\n * spawns a second Node with this same loader to run the script — so spawning\n * through it costs a shell and a Node start (~50–100 ms) on every hop, and\n * `rebase dev` pays a hop on every start and every collection save.\n *\n * Resolved the way Node resolves the package, from the project first — the\n * backend, where a scaffold declares tsx, then the root and everything above it\n * — so the tsx used is the one {@link resolveTsx} would have found; then from\n * this package, as the repository's own tests resolve `tsx/cli`. A URL rather\n * than a path: a Windows path is not a valid `--import` specifier.\n */\nexport function resolveTsxLoader(projectRoot: string): string | null {\n const bases = [\n path.join(projectRoot, \"backend\", \"noop.js\"),\n path.join(projectRoot, \"noop.js\"),\n import.meta.url\n ];\n for (const base of bases) {\n try {\n return pathToFileURL(createRequire(base).resolve(\"tsx\")).href;\n } catch {\n // Not resolvable from here; try the next base.\n }\n }\n return null;\n}\n\n/**\n * Validate that a resolved tsx binary actually has an intact installation.\n *\n * `resolveLocalBin` only checks whether `node_modules/.bin/tsx` (a symlink)\n * exists. If the pnpm content-addressable store was cleaned or a previous\n * install was interrupted, the symlink can exist while critical files inside\n * the tsx package (e.g. `dist/preflight.cjs`) are missing — causing a\n * confusing MODULE_NOT_FOUND error at runtime.\n *\n * This function follows the symlink, walks up to find the tsx package root\n * (`package.json` with `name: \"tsx\"`), and verifies that `dist/preflight.cjs`\n * is present. Returns `null` when the installation looks healthy, or an\n * error description string when it appears corrupted.\n */\nexport function validateTsxInstallation(tsxBinPath: string): string | null {\n try {\n // Follow the symlink chain to the real tsx entry script\n const realPath = fs.realpathSync(tsxBinPath);\n\n // Walk up from the real binary to locate the tsx package root\n let dir = path.dirname(realPath);\n const fsRoot = path.parse(dir).root;\n for (let depth = 0; depth < 10 && dir !== fsRoot; depth++) {\n const pkgPath = path.join(dir, \"package.json\");\n if (fs.existsSync(pkgPath)) {\n try {\n const pkg = JSON.parse(fs.readFileSync(pkgPath, \"utf-8\"));\n if (pkg.name === \"tsx\") {\n // Found the tsx package root — verify critical preload file\n const preflightPath = path.join(dir, \"dist\", \"preflight.cjs\");\n if (!fs.existsSync(preflightPath)) {\n return `tsx package at ${dir} is missing dist/preflight.cjs`;\n }\n return null; // Installation looks healthy\n }\n } catch {\n // Malformed package.json — keep walking\n }\n }\n dir = path.dirname(dir);\n }\n\n // Could not determine tsx root — don't block, assume valid\n return null;\n } catch (err) {\n // realpathSync throws if the symlink target is completely gone\n return `tsx binary symlink is broken: ${err instanceof Error ? err.message : String(err)}`;\n }\n}\n\n/**\n * The one sentence a command says when the project's dependencies are missing.\n *\n * Six commands each had their own wording for the same state, and none of them\n * named the remedy:\n *\n * ✗ Could not find CLI entry point for @rebasepro/server-postgres.\n * ✗ Could not find tsx binary.\n * ✗ Could not find tsx binary for backend.\n *\n * All three mean \"you have not installed yet\" — `getActiveBackendPlugin` has\n * already read the driver out of `backend/package.json` by the time the first\n * one fires, so the package is declared and simply not on disk, and `tsx` is a\n * devDependency of every scaffold. But they read as *Rebase* being broken, and\n * they name an internal path or a binary the developer never asked for rather\n * than the command that fixes it. That is the whole of the failure someone sees\n * on a fresh clone, where `node_modules/` is the one thing a checkout does not\n * carry.\n *\n * So: one sentence, naming the package manager this project uses and the\n * directory to run it in — the working directory is usually neither.\n */\nexport function dependenciesNotInstalled(projectRoot: string): string {\n const install = getPMCommands(detectPackageManager(projectRoot)).install.join(\" \");\n return `Dependencies are not installed — run \\`${install}\\` in ${projectRoot}`;\n}\n\n/**\n * Print {@link dependenciesNotInstalled} and exit 1.\n *\n * For the five commands that report and stop. `db.ts` throws instead, because\n * its caller adds the `✗` and the exit itself.\n */\nexport function exitDependenciesNotInstalled(projectRoot: string): never {\n console.error(chalk.red(`✗ ${dependenciesNotInstalled(projectRoot)}`));\n process.exit(1);\n}\n\n/** One problem inside a refusal, when the command found several. */\nexport interface JsonFailureIssue {\n /** Where it is — a config path, a manifest key, a file. */\n path?: string;\n message: string;\n}\n\n/**\n * The refusal envelope, for a caller that asked for JSON.\n *\n * Same shape as the cloud family's `fail()` — `{ error: { message, code, hint } }`\n * — because there is one CLI and a caller should not have to know which half of\n * it answered. `code` is what a caller branches on and is never absent; the\n * cloud family's reasoning about that applies here unchanged. `issues` carries\n * the per-problem detail a human reader gets as a bullet list, because a\n * refusal that says \"2 problem(s)\" and does not say which two is not\n * machine-readable in any useful sense.\n *\n * On **stdout**, like every other `--json` result: the contract those commands\n * make is that stdout holds one JSON value, and a caller that pipes stdout to a\n * parser must get a parseable refusal rather than an empty stream and a\n * human sentence it never sees. That contract used to hold for exactly one\n * failure of each command — the one that goes through `requireProjectRoot` —\n * while every other exit of the *same* command wrote human text to stderr and\n * left stdout empty. So `rebase status --json` was parseable outside a project\n * and unparseable inside a broken one, which is the case a caller actually has\n * to handle.\n *\n * The flag is read off `process.argv` because the failure happens inside a\n * helper the command calls before it has parsed anything. Coarse — a literal\n * `--json` as some other flag's value would count — and worth it: the failure\n * mode of being coarse is a JSON error where a human one was wanted, and the\n * failure mode of not doing it is an unparseable stream.\n */\nexport function failAsJson(\n message: string,\n code: string,\n hint?: string,\n issues?: JsonFailureIssue[]\n): never {\n const error: Record<string, unknown> = { message, code };\n if (hint !== undefined) error.hint = hint;\n if (issues !== undefined) error.issues = issues;\n console.log(JSON.stringify({ error }, null, 2));\n process.exit(1);\n}\n\n/** Did the command line ask for machine-readable output? */\nexport function wantsJsonOutput(argv: readonly string[] = process.argv): boolean {\n return argv.includes(\"--json\");\n}\n\n/**\n * Require the project root or exit with a helpful error.\n */\nexport function requireProjectRoot(): string {\n const root = findProjectRoot();\n if (!root) {\n if (wantsJsonOutput()) {\n // `rebase status --json` outside a project wrote four grey lines to\n // stderr and nothing at all to stdout, so the agent or CI step that\n // asked for JSON got an empty parse and no reason.\n failAsJson(\n \"Could not find a Rebase project root.\",\n \"no_project_root\",\n `Looked in this directory and every parent for a ${MANIFEST_FILENAME}, `\n + \"a package.json with a \\\"backend\\\" workspace, or a backend/ next to a config/.\"\n );\n }\n // Name what is actually looked for, in the order `findProjectRoot`\n // looks for it. The old wording (\"backend/, frontend/, and config/\")\n // described neither the manifest — which is the primary marker and the\n // only one `rebase init` writes — nor a `--headless` project, which has\n // no frontend at all and would read this as \"you are in the wrong\n // place\" while standing in the right one.\n console.error(chalk.red(\"✗ Could not find a Rebase project root.\"));\n console.error(chalk.gray(` Looked in this directory and every parent for a ${MANIFEST_FILENAME},`));\n console.error(chalk.gray(\" a package.json with a \\\"backend\\\" workspace, or a backend/ next to a config/.\"));\n console.error(chalk.gray(\" Run this from inside a project, or create one with `rebase init`.\"));\n process.exit(1);\n }\n return root;\n}\n\n/**\n * Require the backend directory or exit with a helpful error.\n */\nexport function requireBackendDir(projectRoot: string): string {\n const backendDir = findBackendDir(projectRoot);\n if (!backendDir) {\n if (wantsJsonOutput()) {\n failAsJson(\n \"Could not find a backend/ directory.\",\n \"no_backend_dir\",\n `Expected at: ${path.join(projectRoot, \"backend\")}`\n );\n }\n console.error(chalk.red(\"✗ Could not find a backend/ directory.\"));\n console.error(chalk.gray(` Expected at: ${path.join(projectRoot, \"backend\")}`));\n process.exit(1);\n }\n return backendDir;\n}\n"],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ import { n as resolveDevDatabase } from "./prepare-Btn9Ewbn.js";
2
+ export { resolveDevDatabase };