@rebasepro/cli 0.25.0-canary.ge06257e → 0.25.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/rebase.js +9 -5
- package/dist/{agent-setup-C83DXnlx.js → agent-setup-BYb2Tbdz.js} +2 -2
- package/dist/{agent-setup-C83DXnlx.js.map → agent-setup-BYb2Tbdz.js.map} +1 -1
- package/dist/{api-keys-Bc4FJKYp.js → api-keys-CSMbsmAn.js} +28 -22
- package/dist/api-keys-CSMbsmAn.js.map +1 -0
- package/dist/{apps-wG-ZTryA.js → apps-iBXNZhVN.js} +6 -7
- package/dist/{apps-wG-ZTryA.js.map → apps-iBXNZhVN.js.map} +1 -1
- package/dist/{atlas-dev-database-DeK3Sa4h.js → atlas-dev-database-BusbHP6n.js} +4 -4
- package/dist/{atlas-dev-database-DeK3Sa4h.js.map → atlas-dev-database-BusbHP6n.js.map} +1 -1
- package/dist/{auth-BL7bNQVu.js → auth-DRsTWPky.js} +77 -11
- package/dist/auth-DRsTWPky.js.map +1 -0
- package/dist/{branch-pointer-DTQNLFtb.js → branch-pointer-Bn_FjNld.js} +1 -1
- package/dist/{build-h_7Re-67.js → build-et7Rccxi.js} +6 -7
- package/dist/{build-h_7Re-67.js.map → build-et7Rccxi.js.map} +1 -1
- package/dist/{bundle-CqURVN-K.js → bundle-DlqkDYSe.js} +6 -21
- package/dist/bundle-DlqkDYSe.js.map +1 -0
- package/dist/{cloud-DVV_5GPG.js → cloud-B-ZIjHrA.js} +10 -11
- package/dist/{cloud-DVV_5GPG.js.map → cloud-B-ZIjHrA.js.map} +1 -1
- package/dist/commands/api-keys.d.ts +4 -1
- package/dist/commands/auth.d.ts +3 -2
- package/dist/commands/dev.d.ts +12 -0
- package/dist/commands/generate_sdk.d.ts +4 -2
- package/dist/commands/init.d.ts +20 -0
- package/dist/commands/start.d.ts +10 -0
- package/dist/commands/upgrade.d.ts +12 -0
- package/dist/connection-banner-DbLJzWf4.js +128 -0
- package/dist/connection-banner-DbLJzWf4.js.map +1 -0
- package/dist/{consent-ClRHmcxf.js → consent-BaB-eQKG.js} +2 -2
- package/dist/{consent-ClRHmcxf.js.map → consent-BaB-eQKG.js.map} +1 -1
- package/dist/{console-Dw3HLdUs.js → console-BlD9kjFW.js} +4 -4
- package/dist/{console-Dw3HLdUs.js.map → console-BlD9kjFW.js.map} +1 -1
- package/dist/{context-BM-oOMNX.js → context-DHiiiJ5C.js} +3 -4
- package/dist/context-DHiiiJ5C.js.map +1 -0
- package/dist/{daemon-CknkDjd6.js → daemon-DDMRbz4T.js} +160 -5
- package/dist/daemon-DDMRbz4T.js.map +1 -0
- package/dist/{daemon-entry-C6pxdily.js → daemon-entry-zOC1revO.js} +204 -5
- package/dist/daemon-entry-zOC1revO.js.map +1 -0
- package/dist/{db-Dkn9XhT-.js → db-9P6MnmdC.js} +237 -86
- package/dist/db-9P6MnmdC.js.map +1 -0
- package/dist/{derive-Dl0z2OWi.js → derive-MTdClnkc.js} +29 -14
- package/dist/{derive-Dl0z2OWi.js.map → derive-MTdClnkc.js.map} +1 -1
- package/dist/{dev-DAcZR4Pp.js → dev-BDN_OKXA.js} +183 -23
- package/dist/dev-BDN_OKXA.js.map +1 -0
- package/dist/dev-db/connection-banner.d.ts +47 -0
- package/dist/dev-db/daemon.d.ts +41 -0
- package/dist/dev-db/notification-proxy.d.ts +82 -1
- package/dist/dev-db/pull.d.ts +33 -5
- package/dist/dev-db/state.d.ts +9 -0
- package/dist/{dev-preflight-CorMg7Ub.js → dev-preflight-FT9GbJVj.js} +73 -4
- package/dist/dev-preflight-FT9GbJVj.js.map +1 -0
- package/dist/{dev-server-B1UYifZJ.js → dev-server-CIZwAfZa.js} +14 -3
- package/dist/dev-server-CIZwAfZa.js.map +1 -0
- package/dist/{doctor-Ds_lzc9p.js → doctor-D8YXz7df.js} +6 -6
- package/dist/doctor-D8YXz7df.js.map +1 -0
- package/dist/{eject-CJHkfnat.js → eject-izG2KjMo.js} +3 -4
- package/dist/{eject-CJHkfnat.js.map → eject-izG2KjMo.js.map} +1 -1
- package/dist/{fold-static-BC9CYicw.js → fold-static-CybSay_W.js} +3 -3
- package/dist/{fold-static-BC9CYicw.js.map → fold-static-CybSay_W.js.map} +1 -1
- package/dist/{generate_sdk-kfkZmG-d.js → generate_sdk-B4R65oUV.js} +22 -12
- package/dist/generate_sdk-B4R65oUV.js.map +1 -0
- package/dist/index.es.js +36 -37
- package/dist/index.es.js.map +1 -1
- package/dist/{init-CjoJoThg.js → init-DYaeppEN.js} +52 -63
- package/dist/init-DYaeppEN.js.map +1 -0
- package/dist/invoked-command-CG_rIa_o.js +55 -0
- package/dist/invoked-command-CG_rIa_o.js.map +1 -0
- package/dist/last-dev-database-B_75fxZC.js +2 -0
- package/dist/{manifest-DRp0UVTj.js → manifest-nvPdLlJY.js} +2 -2
- package/dist/{manifest-DRp0UVTj.js.map → manifest-nvPdLlJY.js.map} +1 -1
- package/dist/{normalize-imports-BHxL2d05.js → normalize-imports-BYw8ad87.js} +3 -3
- package/dist/{normalize-imports-BHxL2d05.js.map → normalize-imports-BYw8ad87.js.map} +1 -1
- package/dist/precompress.d.ts +0 -7
- package/dist/{prepare-C1iaiTC4.js → prepare-1rVNd2PF.js} +7 -6
- package/dist/prepare-1rVNd2PF.js.map +1 -0
- package/dist/project-g4K6kV1t.js +701 -0
- package/dist/project-g4K6kV1t.js.map +1 -0
- package/dist/{pull-COpNJFzY.js → pull-DDMpsZ5r.js} +143 -63
- package/dist/pull-DDMpsZ5r.js.map +1 -0
- package/dist/refusal-ReeeGP8a.js +26 -0
- package/dist/refusal-ReeeGP8a.js.map +1 -0
- package/dist/resolve-BQUAC3Wi.js +2 -0
- package/dist/resources/derive.d.ts +9 -0
- package/dist/{resources-CLm6g0br.js → resources-CovP8AZ-.js} +4 -5
- package/dist/{resources-CLm6g0br.js.map → resources-CovP8AZ-.js.map} +1 -1
- package/dist/{schema-BKdh6UoE.js → schema-nuxvvc50.js} +5 -6
- package/dist/{schema-BKdh6UoE.js.map → schema-nuxvvc50.js.map} +1 -1
- package/dist/{skills-DEhYiPeO.js → skills-DeUIsZcT.js} +3 -4
- package/dist/{skills-DEhYiPeO.js.map → skills-DeUIsZcT.js.map} +1 -1
- package/dist/{start-BsPJedPQ.js → start-RzZqDroW.js} +20 -7
- package/dist/start-RzZqDroW.js.map +1 -0
- package/dist/{state-CCB-FAQ9.js → state-CYdBvnwA.js} +3 -2
- package/dist/state-CYdBvnwA.js.map +1 -0
- package/dist/{status-D_B3tlDy.js → status-D9Zm88NZ.js} +11 -12
- package/dist/{status-D_B3tlDy.js.map → status-D9Zm88NZ.js.map} +1 -1
- package/dist/{storage-CtB8polS.js → storage-BB3umRfw.js} +3 -4
- package/dist/{storage-CtB8polS.js.map → storage-BB3umRfw.js.map} +1 -1
- package/dist/{telemetry-EMFRVRQi.js → telemetry-HfU5uAh1.js} +2 -2
- package/dist/{telemetry-EMFRVRQi.js.map → telemetry-HfU5uAh1.js.map} +1 -1
- package/dist/{telemetry-BYmPU-Ji.js → telemetry-TseNLk6X.js} +4 -4
- package/dist/{telemetry-BYmPU-Ji.js.map → telemetry-TseNLk6X.js.map} +1 -1
- package/dist/{upgrade-DbuKX9lv.js → upgrade-apiPKKE_.js} +29 -6
- package/dist/{upgrade-DbuKX9lv.js.map → upgrade-apiPKKE_.js.map} +1 -1
- package/dist/utils/args.d.ts +45 -3
- package/dist/utils/boot-retry.d.ts +41 -0
- package/dist/utils/dev-preflight.d.ts +34 -1
- package/dist/utils/dev-server.d.ts +11 -0
- package/dist/utils/project.d.ts +5 -1
- package/dist/utils/redact.d.ts +10 -0
- package/dist/utils/refusal.d.ts +18 -0
- package/dist/utils/unknown-command.d.ts +4 -0
- package/package.json +7 -7
- package/templates/overlays/baas/README.md +2 -1
- package/templates/resource-graphs/blank.json +25 -0
- package/templates/resource-graphs/blog.json +28 -0
- package/templates/resource-graphs/ecommerce.json +28 -0
- package/templates/resource-graphs/headless.json +22 -0
- package/templates/template/.env.example +11 -4
- package/templates/template/frontend/src/App.tsx +10 -9
- package/templates/template/frontend/vite.config.ts +7 -73
- package/dist/api-keys-Bc4FJKYp.js.map +0 -1
- package/dist/args-CcBphFdj.js +0 -237
- package/dist/args-CcBphFdj.js.map +0 -1
- package/dist/auth-BL7bNQVu.js.map +0 -1
- package/dist/bundle-CqURVN-K.js.map +0 -1
- package/dist/context-BM-oOMNX.js.map +0 -1
- package/dist/daemon-CknkDjd6.js.map +0 -1
- package/dist/daemon-entry-C6pxdily.js.map +0 -1
- package/dist/db-Dkn9XhT-.js.map +0 -1
- package/dist/dev-DAcZR4Pp.js.map +0 -1
- package/dist/dev-preflight-CorMg7Ub.js.map +0 -1
- package/dist/dev-server-B1UYifZJ.js.map +0 -1
- package/dist/doctor-Ds_lzc9p.js.map +0 -1
- package/dist/generate_sdk-kfkZmG-d.js.map +0 -1
- package/dist/init-CjoJoThg.js.map +0 -1
- package/dist/last-dev-database-BEBPfAMY.js +0 -2
- package/dist/prepare-C1iaiTC4.js.map +0 -1
- package/dist/project-Wp2zoyeN.js +0 -349
- package/dist/project-Wp2zoyeN.js.map +0 -1
- package/dist/pull-COpNJFzY.js.map +0 -1
- package/dist/resolve-b_p9bmdH.js +0 -2
- package/dist/start-BsPJedPQ.js.map +0 -1
- package/dist/state-CCB-FAQ9.js.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dev-BDN_OKXA.js","names":[],"sources":["../src/utils/boot-retry.ts","../src/commands/dev.ts"],"sourcesContent":["/**\n * A backend boot that lost its database part-way, which booting again fixes.\n *\n * `rebase db branch create --force` closes the database it copies: it ends\n * every connection to it and refuses new ones until the copy is done. A\n * backend booting at that moment failed with `Failed to start the Rebase\n * runtime … terminating connection due to administrator command (57P01)`, and\n * `rebase dev` then said \"Fix the error above; the watcher restarts it on the\n * next change\", about an error nobody could fix and that the next boot would\n * not have. The same holds for a database that is restarting.\n *\n * This reads the backend's output and says when a failed boot is worth another\n * try, and after how long: a few tries, each waiting twice as long as the one\n * before, and none once the backend has failed for a reason of its own.\n */\n\n/** The sentence that starts a boot failure, as `reportBootFailure` in `@rebasepro/server` writes it. */\nconst BOOT_FAILED = /Failed to start the Rebase runtime/;\n\n/**\n * Why a boot failed that a later boot does not, by what PostgreSQL said:\n * the server ended the connection (57P01 `pg_terminate_backend`, 57P02, 57P03\n * a server starting or stopping), or the database is closed to new\n * connections (55000, what a forced branch create does while it copies).\n * A connection the server ended while it sat idle is reported by `pg` on the\n * next query, in its own words and with no code: \"Client has encountered a\n * connection error and is not queryable\".\n */\nconst PASSING_CAUSES: readonly RegExp[] = [\n /terminating connection due to administrator command/i,\n /Client has encountered a connection error and is not queryable/i,\n /\\(57P0[123]\\)/,\n /is not currently accepting connections/i,\n /the database system is (?:starting up|shutting down|in recovery mode)/i\n];\n\n/** One more boot, and how long to wait for it. */\nexport interface BootRetryPlan {\n /** 1 for the first retry. */\n attempt: number;\n /** Of {@link BootRetry}'s `maxAttempts`. */\n of: number;\n delayMs: number;\n /** The line that said why, as the backend printed it. */\n reason: string;\n}\n\nexport class BootRetry {\n private failing = false;\n private planned = false;\n private attempts = 0;\n\n constructor(private readonly maxAttempts = 3, private readonly firstDelayMs = 1_000) {}\n\n /**\n * Read one line of the backend's output, colour removed. Returns a plan\n * the first time a failing boot names a passing cause, and null otherwise,\n * including once the tries are spent.\n */\n line(text: string): BootRetryPlan | null {\n if (BOOT_FAILED.test(text)) {\n this.failing = true;\n this.planned = false;\n }\n if (!this.failing || this.planned) return null;\n if (!PASSING_CAUSES.some(cause => cause.test(text))) return null;\n this.planned = true;\n if (this.attempts >= this.maxAttempts) return null;\n this.attempts += 1;\n return {\n attempt: this.attempts,\n of: this.maxAttempts,\n delayMs: this.firstDelayMs * 2 ** (this.attempts - 1),\n reason: text.trim()\n };\n }\n\n /** The backend is serving: a later failure is a new one, with every try left. */\n up(): void {\n this.failing = false;\n this.planned = false;\n this.attempts = 0;\n }\n}\n","/**\n * CLI command: rebase dev\n *\n * Starts the full development environment:\n * - Backend: tsx watch with auto-reload\n * - Frontend: vite dev server\n *\n * Both processes stream output with color-coded prefixes.\n *\n * When the backend uses port-retry (i.e. the configured port is busy and it\n * binds to the next free one), the CLI detects the actual port from stdout\n * and injects VITE_API_URL into the frontend so it connects automatically.\n *\n * Each project gets a deterministic default port derived from the project\n * root path, so multiple Rebase instances never collide.\n */\nimport chalk from \"chalk\";\nimport {\n DEFAULT_RESOURCE_KEY,\n parseEnvBoolean,\n type AppAddress,\n type RebaseProjectManifest,\n type RebaseStaticAppConfig\n} from \"@rebasepro/types\";\nimport { execa, execaCommandSync, type ResultPromise } from \"execa\";\n\nimport {\n DEV_DATABASE_KIND_ENV,\n managedNotices,\n prepareDatabaseEnv,\n resolveProjectDatabase,\n type PreparedDatabase\n} from \"../dev-db/prepare\";\nimport { MANAGED_IDLE_TIMEOUT_MS } from \"../dev-db/constraints\";\nimport { resolveCliEntry, resolveSpawn } from \"../dev-db/daemon\";\nimport { lastDevDatabaseNotice, recordLastDevDatabase } from \"../dev-db/last-dev-database\";\nimport path from \"path\";\nimport fs from \"fs\";\nimport os from \"os\";\nimport { createHash } from \"crypto\";\nimport { StringDecoder } from \"string_decoder\";\nimport { fileURLToPath } from \"url\";\nimport {\n cmsMountOf,\n findBackendApp,\n formatAppAddress,\n loadManifest,\n resolveBackendPaths,\n staticAppAddress\n} from \"../manifest\";\nimport {\n requireProjectRoot,\n findBackendDir,\n findFrontendDir,\n findEnvFile,\n resolveTsx,\n validateTsxInstallation,\n getActiveBackendPlugin,\n resolvePluginCliScript,\n dependenciesNotInstalled\n} from \"../utils/project\";\nimport { detectPackageManager, getPMCommands } from \"../utils/package-manager\";\nimport { parseCommandArgs, switchOn, UsageError, wantsHelp } from \"../utils/args\";\nimport { Refusal } from \"../utils/refusal\";\nimport { affectsSqlSchema } from \"../utils/collection-drift\";\nimport { BootRetry } from \"../utils/boot-retry\";\nimport { ensureDevDatabase, probeTcp } from \"../utils/dev-preflight\";\nimport { DEV_BACKEND_URL_ENV, getProjectPort } from \"../utils/dev-server\";\nimport { runDriverDbCommand, runDriverSchemaCommand } from \"./db\";\nimport dotenv from \"dotenv\";\nimport { recordEvent } from \"../telemetry\";\n\n/**\n * The line the watcher prints when it is about to run the entry again.\n *\n * `rebase dev` has no exit code to read: the watcher outlives the process it\n * started, so a backend that dies leaves it running and the CLI printing\n * \"Press Ctrl+C to stop all servers.\" under the stack trace, as if everything\n * were fine.\n *\n * What used to be here was `/Waiting for file changes before restarting/` —\n * **Node's** message, on the theory that `--watch=…` was passed through to\n * Node. It is not: the backend is spawned as `tsx watch …` (see `watchArgs`),\n * so tsx does the watching and Node's line is never printed. The marker\n * matched nothing at all — and the test guarding it ran `node --watch`\n * directly, so it kept proving that Node's wording was intact while the CLI\n * watched a stream that never contained it. That is precisely the silent death\n * the old comment warned about, reached by a different route.\n *\n * So this is tsx's, captured from tsx 4.23.1:\n *\n * 6:26:11 AM [tsx] change in ./src/index.ts Rerunning...\n * 6:26:11 AM [tsx] change in ./src/index.ts Restarting...\n *\n * \"Rerunning\" when the previous run had already exited, \"Restarting\" when it\n * was still up and had to be killed first. Both mean the same thing here: the\n * backend that was announced is gone, and whether another one arrives is an\n * open question again — so the readiness deadline is re-armed.\n *\n * Exported so `dev-crash-marker.test.ts` can hold the running tsx to it.\n */\nexport const WATCHER_RESTART_MARKER = /\\[tsx\\][^\\n]*\\b(?:Rerunning|Restarting)\\b/i;\n\n/**\n * How often to ask whether the announced backend is still listening.\n *\n * The watcher says nothing when its child is killed outright, so a poll is the\n * only signal there is. Five seconds is chosen against the thing it must not\n * do — report a healthy server as dead — rather than against detection speed:\n * two consecutive misses are needed, so it takes ten seconds of a genuinely\n * closed port to say anything.\n */\nconst LIVENESS_INTERVAL_MS = 5_000;\n\n/**\n * Strip ANSI codes before matching.\n *\n * At module scope so `portMovedNotice` below can use the same one the stream\n * handlers do: a second copy of this regex is a second thing to keep in step.\n */\n// eslint-disable-next-line no-control-regex\nconst stripAnsi = (str: string) => str.replace(/[\\u001b\\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g, \"\");\n\n/**\n * Calls `onLine` with each line a child process writes to `stream`.\n *\n * A pipe hands over whatever has been written so far, so one line can arrive\n * in two chunks. Split chunk by chunk, \"Server running at http://localhost:3001\"\n * could read as two fragments that neither matched, and `rebase dev` then\n * never announced the backend or started the admin panel. So the piece after\n * the last newline is held for the next chunk, decoded as UTF-8 across chunk\n * boundaries. A held piece is handed over at the end of the stream, or after\n * `idleFlushMs` with nothing more written, so a prompt or a progress line\n * still shows when it is written.\n *\n * Exported for its test.\n */\nexport function forEachOutputLine(\n stream: NodeJS.ReadableStream | null | undefined,\n onLine: (line: string) => void,\n idleFlushMs = 100\n): void {\n if (!stream) return;\n const decoder = new StringDecoder(\"utf8\");\n let pending = \"\";\n let idle: NodeJS.Timeout | undefined;\n\n const emit = (text: string) => {\n for (const line of text.split(\"\\n\")) if (line) onLine(line);\n };\n // At the end of the stream the decoder gives up what it holds too; after\n // an idle wait it keeps a character still missing its last bytes.\n const flush = (atEnd: boolean) => {\n clearTimeout(idle);\n idle = undefined;\n const rest = pending + (atEnd ? decoder.end() : \"\");\n pending = \"\";\n emit(rest);\n };\n\n stream.on(\"data\", (data: Buffer | string) => {\n clearTimeout(idle);\n const text = pending + (typeof data === \"string\" ? data : decoder.write(data));\n const lastNewline = text.lastIndexOf(\"\\n\");\n pending = text.slice(lastNewline + 1);\n if (lastNewline >= 0) emit(text.slice(0, lastNewline));\n if (pending) {\n idle = setTimeout(() => flush(false), idleFlushMs);\n idle.unref?.();\n }\n });\n stream.on(\"end\", () => flush(true));\n}\n\n/**\n * The corrected banner line, when the backend says it moved port.\n *\n * `↳ PORT = 3014` is printed before the server binds — the backend's\n * environment has to be fixed before it spawns — so when the port turns out to\n * be taken and the server moves, the banner two inches up is simply wrong. The\n * true number then appears in a `[backend]` line a hundred lines of DDL later,\n * which nobody scrolls back through, and the banner is where a reader looks for\n * the port.\n *\n * A correction rather than a rewrite: the terminal is a transcript, and a line\n * that changes after the fact is a line nobody can trust. So the old number is\n * named too.\n *\n * Exported for its test.\n */\nexport function portMovedNotice(line: string): { from: string; to: string } | null {\n const moved = stripAnsi(line).match(/Port (\\d+) is in use — trying (\\d+)/);\n\n return moved ? { from: moved[1], to: moved[2] } : null;\n}\n\n/**\n * Quote a path for the shell `execa` runs the backend through.\n *\n * The dev runtime's path is absolute and therefore contains whatever the\n * developer's directories are called. Double quotes do not neutralize `$`,\n * backticks or backslashes in a POSIX shell, so a checkout under a directory\n * named `$(...)` would execute it. Single quotes disable all expansion; on\n * Windows, `cmd.exe` performs no such expansion and wants double quotes.\n */\nfunction quoteForShell(value: string): string {\n if (process.platform === \"win32\") return `\"${value.replace(/\"/g, \"\\\\\\\"\")}\"`;\n return `'${value.replace(/'/g, \"'\\\\''\")}'`;\n}\n\n/**\n * Locate the dev runtime shim shipped with the CLI.\n *\n * Published under `runtime/` in the package rather than compiled into `dist/`,\n * because tsx executes it as a file and it must exist on disk at a stable path.\n */\nfunction resolveDevRuntimeEntry(): string {\n const here = path.dirname(fileURLToPath(import.meta.url));\n // Walk up from wherever this module ended up (src/ in development, dist/ in\n // a published install) until the package root with `runtime/` is found.\n let dir = here;\n for (let i = 0; i < 5; i++) {\n const candidate = path.join(dir, \"runtime\", \"dev-server.mjs\");\n if (fs.existsSync(candidate)) return candidate;\n const parent = path.dirname(dir);\n if (parent === dir) break;\n dir = parent;\n }\n throw new Error(\n \"Could not find the Rebase dev runtime (runtime/dev-server.mjs). \" +\n \"Reinstall @rebasepro/cli, or add a backend/src/index.ts to run your own entrypoint.\"\n );\n}\n\n/**\n * Tell the dev runtime where this project keeps its parts.\n *\n * Read from `rebase.json` when there is one, so a project that moved its config\n * directory is honoured; otherwise the conventional layout.\n */\nfunction devRuntimeEnv(projectRoot: string): Record<string, string> {\n const result: Record<string, string> = {\n REBASE_DEV_PROJECT_ROOT: projectRoot,\n REBASE_DEV_CONFIG: \"config\",\n REBASE_DEV_FUNCTIONS: \"backend/functions\",\n REBASE_DEV_CRONS: \"backend/crons\",\n REBASE_DEV_SCHEMA: \"backend/src/schema.generated.ts\"\n };\n\n try {\n const loaded = loadManifest(projectRoot);\n const backend = findBackendApp(loaded.manifest);\n if (backend) {\n const paths = resolveBackendPaths(backend.app, projectRoot);\n result.REBASE_DEV_CONFIG = paths.config;\n result.REBASE_DEV_FUNCTIONS = paths.functions;\n result.REBASE_DEV_CRONS = paths.crons;\n result.REBASE_DEV_SCHEMA = paths.schema;\n result.REBASE_DEV_APP = backend.name;\n }\n } catch {\n // An invalid manifest is reported by `rebase build`; dev falls back to\n // the conventional layout rather than refusing to start.\n }\n\n // Nothing here says whether collections are declared or introspected, and\n // nothing should: `createSourceBundle` drops a config directory that does\n // not exist, and the server derives the answer from that. A REBASE_DEV_MODE\n // env var said it a second time, and a second place to say it is a second\n // place for it to disagree.\n return result;\n}\n\n/**\n * Directories `tsx watch` must be told about explicitly, as absolute paths.\n *\n * tsx restarts on a change to anything the entrypoint *imports*. Functions and\n * crons are not imported: the runtime scans their directories at boot. So\n * adding `backend/functions/new.ts` while `rebase dev` ran did nothing —\n * `GET /api/functions/new` stayed 404, and a new cron never registered — until\n * somebody restarted by hand. The same is true of `config/`, which is read as\n * data.\n *\n * `--include` is tsx's own answer to this, it takes a directory rather than a\n * glob, and it tolerates a directory that does not exist yet: a `crons/`\n * created mid-session starts being watched when it appears.\n *\n * @param projectRoot Absolute path the relative entries are resolved against.\n * @param paths The `REBASE_DEV_*` map from {@link devRuntimeEnv}.\n */\nexport function devWatchIncludes(\n projectRoot: string,\n paths: { REBASE_DEV_FUNCTIONS?: string; REBASE_DEV_CRONS?: string }\n): string[] {\n const dirs = [paths.REBASE_DEV_FUNCTIONS, paths.REBASE_DEV_CRONS]\n .filter((entry): entry is string => Boolean(entry))\n .map(entry => path.resolve(projectRoot, entry));\n dirs.push(path.join(projectRoot, \"config\"));\n return dirs;\n}\n\n/** Where dev watches the collections it regenerates the schema and SDK from. */\nfunction devCollectionsDir(projectRoot: string): string {\n return path.join(projectRoot, \"config\", \"collections\");\n}\n\n/**\n * Call `onAppear` once, when `target` exists, and return what stops waiting.\n *\n * Watches the nearest directory above `target` that exists (no higher than\n * `root`), and follows the path down as each part of it appears — `fs.watch`\n * cannot watch a directory that is not there yet. A headless project has no\n * `config/collections` until `rebase schema introspect` writes one, and dev\n * watched it only when it was there at start: the backend kept serving the\n * API it derived from the database, and edits to the new files did nothing,\n * until dev was restarted by hand.\n */\nexport function whenDirectoryAppears(target: string, root: string, onAppear: () => void): () => void {\n let watcher: fs.FSWatcher | null = null;\n let watching: string | null = null;\n let done = false;\n const stop = (): void => {\n done = true;\n watcher?.close();\n watcher = null;\n };\n const arm = (): void => {\n if (done) return;\n if (fs.existsSync(target)) {\n stop();\n onAppear();\n return;\n }\n let dir = path.dirname(target);\n while (!fs.existsSync(dir) && dir !== root && path.dirname(dir) !== dir) dir = path.dirname(dir);\n if (dir === watching || !fs.existsSync(dir)) return;\n watcher?.close();\n watching = dir;\n try {\n watcher = fs.watch(dir, () => arm());\n } catch {\n // Unwatchable: the collections are picked up at the next start, as before.\n watcher = null;\n }\n };\n arm();\n return stop;\n}\n\n/**\n * The file dev writes to restart the backend, or `null` when tsx would not see it.\n *\n * Outside the project, so nothing appears in the developer's tree, and keyed by\n * the project so two `rebase dev`s never restart each other. tsx ignores every\n * path with a dot-named segment (its built-in `**\\/.*\\/**`), so a trigger under\n * `.rebase/` — or under a TMPDIR inside a dot directory — would never fire; in\n * that case there is no trigger and {@link devWatchArgs} leaves the\n * collections to tsx, as before.\n */\nexport function devRestartTrigger(projectRoot: string): string | null {\n let tmp = os.tmpdir();\n try {\n tmp = fs.realpathSync(tmp);\n } catch {\n // A tmpdir that cannot be resolved is used as given.\n }\n const key = createHash(\"sha256\").update(projectRoot).digest(\"hex\").slice(0, 16);\n // Named for what it means: tsx prints the name on every restart it causes.\n const trigger = path.join(tmp, \"rebase-dev\", key, \"collections-changed\");\n return trigger.split(path.sep).some(segment => segment.startsWith(\".\")) ? null : trigger;\n}\n\n/**\n * tsx's built-in watch ignores (tsx 4's `watch` command), which it applies\n * relative to its cwd only. {@link devWatchArgs} repeats them under every\n * directory it includes from outside that cwd.\n */\nconst TSX_DEFAULT_IGNORES = [\"**/.*/**\", \"**/.*\", \"**/{node_modules,bower_components,vendor}/**\"] as const;\n\n/**\n * The `tsx watch` argument list for the backend (unquoted).\n *\n * One save of a collection file must restart the backend once. The runtime\n * `import()`s the collections, so tsx tracks them as dependencies and used to\n * restart the moment a file was saved — before dev had regenerated\n * `schema.generated.ts`. That boot warned \"schema.generated.ts no longer\n * describes the database … Run `rebase schema generate`\", telling the reader to\n * run what dev was running at that moment, and the regeneration then rewrote\n * the file and restarted the backend a second time. So tsx is told to leave the\n * collections directory alone (`--exclude` wins over its dependency tracking),\n * and dev restarts the backend itself, once, after the regeneration — through\n * `restartTrigger`, which tsx watches.\n *\n * Every included directory also gets tsx's own default ignores, made absolute.\n * tsx resolves those defaults (`**\\/node_modules/**`, `**\\/.*`, …) against its\n * cwd, which is `backend/`, so they never reached `config/`, a sibling: chokidar\n * walked `config/node_modules` — following symlinks — before tsx spawned the\n * backend at all, and kept every file in it watched. In the monorepo's example\n * app that was 17 s before the first backend line and 1,400 open files, and any\n * install (or another checkout's Vite build writing `.vite-temp` deep inside\n * that tree) restarted the backend. A `.DS_Store` or an editor swap file in\n * `config/` restarted it too.\n */\nexport function devWatchArgs(\n projectRoot: string,\n paths: { REBASE_DEV_FUNCTIONS?: string; REBASE_DEV_CRONS?: string },\n entryTarget: string,\n restartTrigger: string | null,\n envFile: string | null = null\n): string[] {\n // tsx's own flags first: it stops reading flags at the first one it does\n // not know (`--conditions` is Node's) and hands the rest to Node, which\n // then refuses `--include` as a bad option.\n const args = [\"watch\"];\n // Globs, so forward slashes whatever the platform's separator is.\n const glob = (dir: string) => dir.split(path.sep).join(\"/\");\n for (const dir of devWatchIncludes(projectRoot, paths)) {\n args.push(\"--include\", dir);\n for (const ignored of TSX_DEFAULT_IGNORES) args.push(\"--exclude\", `${glob(dir)}/${ignored}`);\n }\n if (restartTrigger) {\n args.push(\"--include\", restartTrigger);\n args.push(\"--exclude\", `${glob(devCollectionsDir(projectRoot))}/**`);\n }\n // The server reads `.env` at boot, and the entry never imports it, so an\n // edit there changed nothing until some watched file did. tsx watches a\n // named file outside its cwd; one inside `backend/` falls under its own\n // dotfile ignore, and dev watches that one itself (see `envFileHiddenFromTsx`).\n if (envFile) args.push(\"--include\", envFile);\n args.push(\"--conditions\", \"development\", entryTarget);\n return args;\n}\n\n/**\n * Whether tsx, run in `backendDir`, ignores `envFile` whatever it is told.\n *\n * Its default ignores (`**\\/.*` among them) are resolved against its cwd, so a\n * `backend/.env` is beyond an `--include`, and a project-root `.env` is not.\n */\nexport function envFileHiddenFromTsx(envFile: string, backendDir: string): boolean {\n const relative = path.relative(backendDir, envFile);\n return !relative.startsWith(\"..\") && !path.isAbsolute(relative);\n}\n\n/**\n * The databases the project declares beyond the default, by key.\n *\n * Read from the graph, so `database(\"analytics\")` in `config/resources.ts`\n * is enough for `rebase dev` to serve a second database — the declaration is\n * the request. A config that does not evaluate is the backend's problem to\n * report, in the terms it already uses; here it means \"no additional\n * databases\", not a refusal to start.\n */\nasync function declaredAdditionalDatabases(projectRoot: string): Promise<string[]> {\n try {\n const loaded = loadManifest(projectRoot);\n const backend = findBackendApp(loaded.manifest);\n if (!backend) return [];\n const { deriveResourceGraph, deriveOptionsFor } = await import(\"../resources/derive\");\n const { graph } = await deriveResourceGraph({ ...deriveOptionsFor(projectRoot, backend.app), cronsDir: undefined, functionsDir: undefined });\n return graph.resources\n .filter(r => r.kind === \"database\" && r.transport === \"server\" && r.key !== DEFAULT_RESOURCE_KEY)\n .map(r => r.key);\n } catch {\n return [];\n }\n}\n\n/** Well-known filename the backend writes its actual port to. */\nexport const DEV_PORT_FILENAME = \".rebase-dev-port\";\n\n/**\n * What `.rebase-dev-port` holds: the port the backend bound, then the one it\n * was asked for — the server's own format (`listenWithPortRetry`).\n *\n * The requested half is what tells a later server whether the saved port is\n * meant for it, and `rebase dev` used to overwrite the server's file with the\n * bound port alone. Every server in the project then tried dev's port first:\n * `rebase start` beside dev logged `Port 3755 is in use — trying 3001`, and\n * after a dev that crashed, leaving the file behind, it bound 3755 rather than\n * the `PORT=3001` its environment named.\n */\nexport function devPortFileContents(bound: number, requested: number): string {\n return `${bound} ${requested}`;\n}\n\n/**\n * The `PORT` the scaffold's own `.env.example` ships.\n *\n * `rebase init` copies that file into `.env` verbatim, so every new project has\n * this value whether or not anyone meant it. `init.test.ts` asserts the\n * template still says this number, so the two cannot drift apart in silence.\n */\nexport const SCAFFOLD_DEFAULT_PORT = 3001;\n\n/**\n * Which rung of the ladder below decided the port.\n *\n * Reported because the ladder is invisible from the outside and the two middle\n * rungs look identical from where the developer stands: `PORT` in the shell\n * wins, `PORT` in `.env` does not — `rebase dev` never reads the file for it —\n * and a saved affinity port looks exactly like a derived one. A developer who\n * sets `PORT=3001` in `.env`, sees 3774, and has nothing to read but the number\n * concludes the setting is broken rather than out of scope.\n */\nexport type StartPortSource = \"--port\" | \"PORT\" | \"affinity\" | \"derived\";\n\n/** What the banner says after the port, naming the rung. */\nexport const START_PORT_SOURCE_LABELS: Record<StartPortSource, string> = {\n \"--port\": \"from --port\",\n PORT: \"from PORT\",\n affinity: \"kept from the last run\",\n derived: \"derived\"\n};\n\n/**\n * A TCP port, or `undefined` for anything that is not one.\n *\n * One predicate for both sources below. The port file was already checked for\n * range, and `PORT` — the source a human or a platform actually sets — was not,\n * so `PORT=oops` reached `parseInt` and was returned as `NaN`: the dev server\n * then bound to whatever the OS handed out and the CLI printed a URL for a port\n * nothing was listening on.\n */\nfunction parsePort(raw: string | undefined): number | undefined {\n if (raw === undefined) return undefined;\n const port = Number(raw.trim());\n if (!Number.isInteger(port) || port <= 0 || port >= 65536) return undefined;\n return port;\n}\n\n/**\n * The port this project's backend starts on, and the rung that decided it:\n * 1. `--port` (highest priority)\n * 2. `PORT` **in the shell environment** — a `PORT` in `.env` is not read here\n * 3. the port a previous run saved in `.rebase-dev-port` (affinity)\n * 4. a port derived from the project path, unique per project\n */\nexport function resolveStartPort(\n projectRoot: string,\n explicitPort?: number\n): { port: number; source: StartPortSource } {\n // 1. Explicit flag\n if (explicitPort) return { port: explicitPort, source: \"--port\" };\n\n // 2. PORT env var. `process.env` only: the CLI does not load `.env` before\n // resolving the port, so a `PORT=` line in that file never reaches here —\n // it is handed to the child, which this then overrides. Both facts are in\n // `getting-started/configuration.md` and in `rebase dev --help`.\n if (process.env.PORT) {\n const fromEnv = parsePort(process.env.PORT);\n if (fromEnv !== undefined) return { port: fromEnv, source: \"PORT\" };\n // Set deliberately and unusable: falling through silently would start a\n // server on a port nobody asked for and say nothing about why.\n console.warn(chalk.yellow(\n ` ⚠ Ignoring PORT=\"${process.env.PORT}\" — not a port between 1 and 65535.`\n ));\n }\n\n // 3. Port affinity — check if we wrote a port file from a previous run\n try {\n const portFile = path.join(projectRoot, DEV_PORT_FILENAME);\n if (fs.existsSync(portFile)) {\n // The bound port, first of `<bound> <requested>` — see `devPortFileContents`.\n const saved = parsePort(fs.readFileSync(portFile, \"utf-8\").trim().split(/\\s+/)[0]);\n if (saved !== undefined) return { port: saved, source: \"affinity\" };\n }\n } catch { /* ignore */ }\n\n // 4. Deterministic hash\n return { port: getProjectPort(projectRoot), source: \"derived\" };\n}\n\n/**\n * Where a connection string actually points, as `host:port/name`.\n *\n * The banner used to name the database only by *provenance* — \"your database\n * (DATABASE_URL in the environment)\" — and the boot that follows it writes\n * thirty DDL statements. Which host, which port, which database name appeared\n * nowhere on the success path; only a *failed* connection printed an address.\n * So the one question after pointing a project at staging by accident — did it\n * touch staging? — had no answer in a hundred and twenty lines of output.\n *\n * The password is never in it. `new URL` keeps the credentials in their own\n * fields, so building the string out of `hostname`, `port` and `pathname`\n * cannot carry them by accident, which string surgery on a DSN reliably does.\n *\n * A DSN with no host is a UNIX socket (`postgresql:///app?host=/tmp`), and the\n * socket directory is the address there.\n *\n * Returns null for anything that is not a parseable URL rather than guessing:\n * a banner line that is wrong about the database is worse than one that is\n * missing.\n *\n * Exported for its test.\n */\nexport function databaseEndpoint(url: string | null | undefined): string | null {\n if (!url) return null;\n\n let parsed: URL;\n try {\n parsed = new URL(url);\n } catch {\n return null;\n }\n\n const host = parsed.hostname || parsed.searchParams.get(\"host\");\n if (!host) return null;\n\n const name = parsed.pathname.replace(/^\\//, \"\");\n\n return `${host}${parsed.port ? `:${parsed.port}` : \"\"}${name ? `/${name}` : \"\"}`;\n}\n\n/**\n * What the start banner needs to know about a project's static apps.\n *\n * `rebase dev` runs one frontend: Vite in `frontend/`, at the root of its own\n * localhost port, whatever path or hostname the manifest declares for it. So an\n * app's local address and its deployed one are different things, and for an app\n * with a hostname of its own they share nothing at all — appending its address\n * to the local URL would print `http://localhost:5173https://admin.example.com`.\n */\nexport interface DevStaticShape {\n declaresStaticApp: boolean;\n /** The declared app whose `root` is the `frontend/` that `rebase dev` runs, if one is. */\n served?: { appName: string; address: AppAddress };\n /** Where the CMS is, when an app declares one. */\n cms?: {\n appName: string;\n /** Always a path. */\n path: string;\n /** The serving app's hostname, when its `path` names one. */\n host?: string;\n /** Whether the app serving it is the one `rebase dev` runs, so it has a local address. */\n servedLocally: boolean;\n };\n}\n\n/**\n * Read {@link DevStaticShape} out of a loaded manifest. Exported for its test.\n *\n * Which declared app is running is decided by its `root` being `frontend/`,\n * because that directory is what `startFrontend` launches and nothing else is.\n * When no declared app has that root, which app serves the CMS cannot be told,\n * and it is read as the frontend that does run — what the banner has always\n * assumed, and right for every project `rebase init` scaffolds.\n */\nexport function devStaticShape(\n manifest: RebaseProjectManifest,\n projectRoot: string,\n frontendDir: string | null\n): DevStaticShape {\n const statics: Array<[string, RebaseStaticAppConfig]> = [];\n for (const [name, app] of Object.entries(manifest.apps ?? {})) {\n if (app.type === \"static\") statics.push([name, app]);\n }\n\n let served: DevStaticShape[\"served\"];\n if (frontendDir) {\n const running = path.resolve(frontendDir);\n const match = statics.find(([, app]) => path.resolve(projectRoot, app.root) === running);\n if (match) served = { appName: match[0], address: staticAppAddress(match[0], match[1]) };\n }\n\n const mount = cmsMountOf(manifest);\n return {\n declaresStaticApp: statics.length > 0,\n ...(served ? { served } : {}),\n ...(mount\n ? {\n cms: {\n appName: mount.appName,\n path: mount.path,\n ...(mount.host ? { host: mount.host } : {}),\n servedLocally: served ? served.appName === mount.appName : true\n }\n }\n : {})\n };\n}\n\n/**\n * The banner's lines for a project with a frontend. Exported for its test.\n *\n * Every address is printed whole, once: the local one Vite answers on, and —\n * for an app with a hostname — the https URL it answers on once deployed, on a\n * line of its own beneath it. A CMS in an app `rebase dev` does not run gets its\n * deployed address and a note saying so, rather than a localhost URL that the\n * running frontend's SPA fallback would answer with the wrong app.\n */\nexport function appBannerLines(frontendUrl: string, api: string, shape: DevStaticShape): Array<[string, string]> {\n const deployedNote = (address: string): [string, string] => [\" \", `${address} when deployed`];\n const lines: Array<[string, string]> = [\n [\"\", \"\"],\n [\"✦ Rebase is ready!\", \"\"],\n [\"➜ Admin: \", frontendUrl]\n ];\n const servedDeployed = shape.served?.address.host ? formatAppAddress(shape.served.address) : undefined;\n if (servedDeployed) lines.push(deployedNote(servedDeployed));\n // Both, because both are needed and only one was printed. The admin URL is\n // where you log in; the API URL is what every SDK client, curl and Swagger\n // link needs — and it is not derivable from the other, since the two ports\n // are derived separately from this project's path.\n lines.push([\"➜ API: \", api]);\n\n // The CMS, when it is not simply the frontend's home page. Printed rather\n // than assumed: a project that mounts `<RebaseCMS>` at `/admin` inside its\n // product app has an admin URL that appears nowhere — not in the build, not\n // in the running server, not here — and finding it meant reading the\n // frontend's source.\n const cms = shape.cms;\n if (!cms) return lines;\n const cmsDeployed = cms.host ? formatAppAddress({ host: cms.host, path: cms.path }) : undefined;\n if (!cms.servedLocally) {\n lines.push([\"➜ CMS: \", cmsDeployed ?? cms.path]);\n lines.push([\" \", `(app \"${cms.appName}\" — rebase dev only starts frontend/)`]);\n return lines;\n }\n // The CMS at the root of the running app is the Admin line, deployed\n // address included, and printing it twice only makes the box longer.\n if (cms.path === \"/\" && cmsDeployed === servedDeployed) return lines;\n lines.push([\"➜ CMS: \", cms.path === \"/\" ? frontendUrl : `${frontendUrl.replace(/\\/$/, \"\")}${cms.path}`]);\n if (cmsDeployed) lines.push(deployedNote(cmsDeployed));\n return lines;\n}\n\n/**\n * The `↳ Database =` line: which database, why it was chosen, and where it is.\n *\n * Provenance alone was what this line said, and the boot underneath it applies\n * schema. \"your database (DATABASE_URL in the environment)\" and \"Applied 30\n * additive schema change(s) before boot\" together still do not answer the\n * question somebody asks after pointing a project at staging by accident.\n *\n * The managed database gets its address too — the daemon's port is what a\n * `psql` in another terminal needs, and it is nowhere else in the banner.\n *\n * Exported for its test.\n */\nexport function databaseBannerValue(prepared: PreparedDatabase | null): string {\n if (!prepared) return \"none (--no-db) — the backend needs DATABASE_URL\";\n\n const endpoint = databaseEndpoint(\n prepared.database.kind === \"managed\" ? prepared.env.DATABASE_URL : prepared.database.url\n );\n\n return endpoint ? `${prepared.description} · ${endpoint}` : prepared.description;\n}\n\n/**\n * Refuse a port the developer named and something else already answers on.\n *\n * `--port` used to be advice, not an instruction: `resolveStartPort` returned\n * the number, the banner printed it, and the server then walked past the\n * collision to 3141, 3142. Because dev ports are derived per project, the\n * number typed into `--port` is very often *another Rebase backend*, so the\n * curl that followed answered 200 from the wrong project rather than refusing\n * — the one failure mode a port collision is supposed to rule out.\n *\n * Asked here as well as in the server (`listenWithPortRetry`'s `explicit`\n * option, which `REBASE_DEV_PORT_EXPLICIT` below turns on) so the refusal\n * arrives before a database is started and a schema is pushed, rather than a\n * minute later underneath them.\n *\n * Both loopback addresses, because a listener on `::1` alone is the same\n * collision and costs one more connect to rule out. Returns the sentence to\n * print, or `null` when there is nothing to refuse.\n *\n * Exported for its test.\n */\nexport async function pinnedPortRefusal(port: number, pinned: boolean): Promise<string | null> {\n if (!pinned) return null;\n const taken = await Promise.all([probeTcp(\"127.0.0.1\", port), probeTcp(\"::1\", port)]);\n if (!taken.some(Boolean)) return null;\n return `Port ${port} is in use, and you asked for it with --port. ` +\n `Stop whatever is listening on ${port}, or pass a different port. ` +\n \"(Without --port, dev derives a port for this project and walks past a collision.)\";\n}\n\n/**\n * Why `rebase dev` will not start on the database `--database-url` names, or\n * null when something answers there.\n *\n * A TCP connect, not a login: whether the credentials are right is the\n * backend's to say, and it says so in terms of the URL it was given. A URL\n * that does not parse is the backend's to refuse too.\n *\n * Exported for its test.\n */\nexport async function unreachableDatabaseFlag(\n url: string,\n probe: (host: string, port: number, timeoutMs?: number) => Promise<boolean> = probeTcp\n): Promise<string | null> {\n let host: string;\n let port: number;\n try {\n const parsed = new URL(url);\n host = parsed.hostname.replace(/^\\[(.*)\\]$/, \"$1\") || \"localhost\";\n port = parsed.port ? Number(parsed.port) : 5432;\n } catch {\n return null;\n }\n if (await probe(host, port, 3_000)) return null;\n return `Nothing answers at ${host}:${port}, the database --database-url names. ` +\n \"Start it, or pass a --database-url that reaches one. Nothing was started.\";\n}\n\n/**\n * The frontend port for this project, derived the same way the backend's is.\n *\n * `rebase dev` used to leave this entirely to Vite, which takes 5173 and, when\n * that is busy, silently walks to 5174, 5175 and onward. The backend was then\n * started with whatever `FRONTEND_URL` the scaffold's `.env` happened to say —\n * `http://localhost:5173` — so the welcome email linked a port the app was not\n * on. Deriving it here means the value handed to the backend describes the\n * server we are about to start rather than a default nobody checked.\n *\n * Range 5173–5372, so the familiar port is still the common case for a single\n * project and two projects rarely collide.\n *\n * Note this is passed to Vite WITHOUT `--strictPort` unless the developer\n * pinned it explicitly. Vite keeps its fallback, so a busy port still starts —\n * it just starts somewhere the email link does not name, which is exactly\n * today's behaviour and no worse. Pinning REBASE_FRONTEND_PORT makes both\n * exact.\n */\nexport function getProjectFrontendPort(projectRoot: string): number {\n let hash = 0;\n for (let i = 0; i < projectRoot.length; i++) {\n hash = ((hash << 5) - hash + projectRoot.charCodeAt(i)) | 0;\n }\n return 5173 + (Math.abs(hash) % 200);\n}\n\n/**\n * What `rebase dev` says as it stops, about the database it leaves behind.\n *\n * Ctrl+C used to print nothing, and the managed database's daemon stayed up\n * unannounced. Staying up is the design: the database belongs to the project,\n * so the other commands keep reaching it after dev stops. What was missing is\n * saying so, and how to stop it. A database dev did not start is not its to\n * mention.\n */\nexport function devExitLines(kind: PreparedDatabase[\"database\"][\"kind\"] | null): string[] {\n if (kind === \"managed\") {\n const minutes = Math.round(MANAGED_IDLE_TIMEOUT_MS / 60_000);\n return [\n `The development database keeps running for the other commands, and stops itself after ${minutes} minutes with nothing connected.`,\n \"Stop it now with `rebase db stop`; the data is kept.\"\n ];\n }\n if (kind === \"docker\") {\n return [\"The Postgres container keeps running. Stop it with `docker compose stop db`; the data is kept.\"];\n }\n return [];\n}\n\n/**\n * The flags `rebase dev` takes.\n *\n * Exported so `dev.test.ts` can assert that every short alias the help\n * advertises is declared here: the help said `--port, -p` while the spec has\n * only ever declared `-P`, so `rebase dev -p 4000` typed straight off the help\n * page passed `4000` as a positional and started on the default port.\n */\nexport const DEV_FLAGS = {\n \"--backend-only\": Boolean,\n \"--frontend-only\": Boolean,\n \"--port\": Number,\n /**\n * Point this run at a database of your own, ahead of everything else.\n *\n * The managed development database fills a vacuum; it never redirects a\n * project that has said which Postgres it wants. This flag is the loudest\n * way to say it, and outranks DATABASE_URL in the environment and in .env.\n */\n \"--database-url\": String,\n /** Use Postgres in Docker rather than the managed database. */\n \"--docker\": Boolean,\n \"-b\": \"--backend-only\",\n \"-f\": \"--frontend-only\",\n // `-P` for port, not `-p`. `-p` is `--project` across all ~20 cloud\n // commands — by a wide margin the most-typed short flag in the CLI — and it\n // also means `--password` in `auth`. One letter with three meanings is a\n // flag you have to look up every time, which is the opposite of what a short\n // flag is for. `--project` keeps `-p`; port moves here.\n \"-P\": \"--port\",\n // Opts out of the database preflight in `ensureDevDatabase`. Named for what\n // it withholds rather than for the mechanism: a reader reaching for this\n // wants \"leave my database alone\", not \"skip step one of three\".\n \"--no-db\": Boolean\n} as const;\n\n/**\n * Read one variable out of the project's env file.\n *\n * `dotenv.parse` rather than `dotenv.config`, because this must not put the\n * project's secrets into the CLI process's own environment — the child\n * processes are given the env file by path and read it themselves.\n */\nfunction readEnvVar(projectRoot: string, name: string): string | undefined {\n const envFile = findEnvFile(projectRoot);\n if (!envFile || !fs.existsSync(envFile)) return undefined;\n try {\n return dotenv.parse(fs.readFileSync(envFile))[name] || undefined;\n } catch {\n return undefined;\n }\n}\n\n/**\n * Whether this project declares collections at all.\n *\n * A project with none has no schema to generate or push — the headless BaaS\n * mode serves whatever the database already has.\n */\nfunction projectHasCollections(projectRoot: string): boolean {\n const collectionsDir = path.join(projectRoot, \"config\", \"collections\");\n return fs.existsSync(collectionsDir)\n && fs.readdirSync(collectionsDir).some(name => /\\.(ts|js|mts|mjs)$/.test(name) && !name.startsWith(\"index.\"));\n}\n\n/**\n * Make the generated Drizzle schema match the collections before anything reads it.\n *\n * `rebase init` writes `backend/src/schema.generated.ts` from the preset's own\n * collections (`templates/generated-schemas/<preset>.ts`), so a new project\n * starts with the real schema. It is stale the moment a collection changes, and\n * on the managed PGlite path — the one a new project takes, because `rebase\n * init` leaves `DATABASE_URL` commented out — no `rebase db push` runs to\n * regenerate it. So `dev` does.\n *\n * The driver builds its tables from `information_schema`, so a stale file\n * cannot empty the API. This still runs, because the file is a real artifact\n * for everything around the server: `db push` diffs it, Atlas plans from it,\n * `eject` writes it out, and application code imports it.\n *\n * ## Why it runs unconditionally\n *\n * Generation reads collection files and writes one file. It needs no database,\n * it is idempotent, and it takes about two seconds. Making it a precondition of\n * starting the dev server — rather than a side effect of one database path —\n * is what makes \"the generated schema matches the collections\" true by\n * construction instead of true by remembering. The same staleness bites a\n * project whose database was already running, where the push never ran either.\n *\n * Failure is not fatal: the server still starts, and it will report the real\n * problem itself. A dev server that refuses to boot because codegen failed is\n * worse than one that boots and says so.\n */\nasync function ensureGeneratedSchema(projectRoot: string): Promise<SchemaGeneration> {\n if (!projectHasCollections(projectRoot)) return \"skipped\";\n // Where the driver writes it: `src/schema.generated.ts` under the backend.\n const schemaFile = path.join(findBackendDir(projectRoot) ?? path.join(projectRoot, \"backend\"), \"src\", \"schema.generated.ts\");\n const read = (): string | null => {\n try {\n return fs.readFileSync(schemaFile, \"utf8\");\n } catch {\n return null;\n }\n };\n const before = read();\n try {\n await runDriverSchemaCommand([\"node\", \"rebase\", \"schema\", \"generate\"], { quiet: true });\n // Said when it changed, because the file is committed: a stale one means\n // the collections moved without it, and the diff is now the reader's.\n const after = read();\n if (before !== null && after !== null && before !== after) {\n const shown = path.relative(projectRoot, schemaFile).split(path.sep).join(\"/\");\n console.log(` ${chalk.gray(`↻ ${shown} was out of date with the collections, so it was regenerated.`)}`);\n }\n return \"generated\";\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n console.log(` ${chalk.yellow(\"⚠\")} ${chalk.gray(`Could not regenerate the database schema: ${message}`)}`);\n console.log(` ${chalk.gray(`Run ${chalk.cyan(\"rebase schema generate\")} to see why. Collection reads will fail until it succeeds.`)}`);\n return \"failed\";\n }\n}\n\n/** What {@link ensureGeneratedSchema} did at startup. */\nexport type SchemaGeneration = \"generated\" | \"skipped\" | \"failed\";\n\n/**\n * Whether dev must still run the driver's `schema stale --fix` before the\n * backend boots.\n *\n * Not after a regeneration that succeeded: that wrote `schema.generated.ts`\n * from these collections with this driver, moments earlier, and `stale`\n * compares the file against the same generator — it can only answer \"nothing\n * stale\", and it cost a driver process (~0.6 s) on every start. The repair it\n * exists for, a library upgrade that changed what the generator derives, is\n * exactly what the regeneration already did. When generation was skipped or\n * failed, the preflight is the remaining safety net and still runs.\n */\nexport function needsStaleSchemaPreflight(generation: SchemaGeneration): boolean {\n return generation !== \"generated\";\n}\n\n/**\n * Make `generated/sdk` match the collections, the same way the schema is kept.\n *\n * This used to happen only under `--generate`, and `generated/` is gitignored,\n * so a fresh clone had no SDK types at all until someone ran\n * `rebase generate-sdk`, and every collection edit left the typed client one\n * step behind. A frontend compiling against a `Database` type that no longer\n * describes the backend is the drift this whole command exists to prevent.\n *\n * A child process rather than a call into `generateSdkCommand`: that loads the\n * collections through jiti, whose module cache would hand the second\n * regeneration the collections the first one loaded. And this CLI re-run\n * directly, not `pnpm exec rebase`: the package manager's pre-run dependency\n * check can start an install on every save, and its `rebase` is whichever one\n * it resolves, not necessarily the one running `dev`.\n *\n * The generator's own output is a usage guide meant for a person who ran it by\n * hand, so it is captured, and on failure only the lines that say what went\n * wrong are shown — not the stack under them. Like the schema, a failure never\n * stops `dev`.\n *\n * `backendPort` is where the backend is, or is about to be: the README's\n * example names it. The first run is before the backend binds, so it is told\n * rather than left to read `.rebase/state.json`.\n */\nasync function regenerateSdk(projectRoot: string, backendPort: number | null): Promise<void> {\n if (!projectHasCollections(projectRoot)) return;\n const entry = resolveCliEntry();\n const { execPath, prefixArgs } = resolveSpawn(entry);\n try {\n await execa(execPath, [...prefixArgs, entry, \"generate-sdk\"], {\n cwd: projectRoot,\n all: true,\n // The README's example address — see `DEV_BACKEND_URL_ENV`.\n ...(backendPort !== null ? { env: { [DEV_BACKEND_URL_ENV]: `http://localhost:${backendPort}` } } : {})\n });\n } catch (error) {\n const output = typeof error === \"object\" && error !== null && \"all\" in error && typeof error.all === \"string\"\n ? error.all\n : \"\";\n const cause = output.split(\"\\n\").filter(line => line.trim() && !/^\\s+at /.test(line)).slice(-6);\n console.log(` ${chalk.yellow(\"⚠\")} ${chalk.gray(\"Could not regenerate the SDK types in generated/sdk.\")}`);\n if (cause.length > 0) console.log(chalk.gray(cause.map(line => ` ${line.trim()}`).join(\"\\n\")));\n console.log(` ${chalk.gray(`Run ${chalk.cyan(\"rebase generate-sdk\")} to see why.`)}`);\n }\n}\n\n/**\n * One variable's value out of `.env` text, or undefined when it has none.\n *\n * The obvious `\\s*(.+?)\\s*$` does not do this, because `\\s` matches a newline\n * and `$` under /m matches at every line end: on `VITE_API_URL=` followed by\n * `# VITE_GOOGLE_CLIENT_ID=`, the leading `\\s*` ate the line break and the\n * capture returned the *next* line. The scaffold ships `VITE_API_URL=` empty,\n * so every first `rebase dev` read a comment as its value, decided it differed\n * from the derived URL, and warned that a variable the developer had not set\n * was being ignored.\n *\n * Horizontal whitespace only, so a value stops at its own line.\n */\nexport function readEnvValue(envText: string, key: string): string | undefined {\n const match = envText.match(\n new RegExp(`^[^\\\\S\\\\r\\\\n]*${key}[^\\\\S\\\\r\\\\n]*=[^\\\\S\\\\r\\\\n]*(.*?)[^\\\\S\\\\r\\\\n]*$`, \"m\")\n );\n if (!match) return undefined;\n\n const value = match[1].replace(/^[\"']|[\"']$/g, \"\");\n\n return value.length > 0 ? value : undefined;\n}\n\n/**\n * The argv the first-boot schema push runs with.\n *\n * The database is named on the command line rather than left to be re-resolved.\n * `runDriverDbCommand` runs the resolver again for whatever argv it is handed,\n * and the argv it used to be handed (`[\"node\",\"rebase\",\"db\",\"push\"]`) carried\n * nothing at all — so on the `--docker` path it read a `.env` whose\n * `DATABASE_URL` is commented out, concluded \"managed PGlite\", and refused the\n * push for a database that was not the one the preflight had just started.\n *\n * Exported for its test: the missing pair of arguments is the whole defect, and\n * it is invisible in any assertion about the push's *outcome*.\n */\nexport function schemaPushArgv(databaseUrl: string | undefined): string[] {\n const argv = [\"node\", \"rebase\", \"db\", \"push\"];\n if (databaseUrl) argv.push(\"--database-url\", databaseUrl);\n\n return argv;\n}\n\n/**\n * Start-up's independent halves, overlapped rather than run one after another.\n *\n * `rebase dev` used to regenerate the schema and the SDK, then derive the\n * resource graph (every collection evaluated again, in this process), then\n * start the managed database and wait for it — about a second when it was not\n * already running — and only then spawn the backend. None of the three reads\n * what another writes: the generators read `config/` and write\n * `backend/src/schema.generated.ts` and `generated/sdk/`; the derivation reads\n * `config/resources.ts` and the collections; the database needs neither, and\n * `schema generate` never connects to one. So `database` starts first and is\n * collected last.\n *\n * `preflight` is the exception, and the reason this is not one `Promise.all`:\n * on the `--docker` path it pushes the schema the generators have just written,\n * so it still runs after them.\n *\n * A database that fails to start is reported where it always was — after the\n * generators' and the preflight's output — rather than ahead of it, and it is\n * never an unhandled rejection while they run.\n *\n * Exported for its test.\n */\nexport async function overlapDevStartup<T>(steps: {\n generate: () => Promise<SchemaGeneration>;\n preflight: () => Promise<void>;\n /** Resolve and, for the managed database, start it. `null` when this run starts none. */\n database: (() => Promise<T>) | null;\n}): Promise<{ schemaGeneration: SchemaGeneration; database: T | null }> {\n const database = steps.database\n ? steps.database().then(\n (value) => ({ ok: true as const, value }),\n (error: unknown) => ({ ok: false as const, error })\n )\n : null;\n const schemaGeneration = await steps.generate();\n await steps.preflight();\n const settled = database ? await database : null;\n if (settled && !settled.ok) throw settled.error;\n return { schemaGeneration, database: settled ? settled.value : null };\n}\n\n/**\n * The database half of `rebase dev` starting up.\n *\n * Separated from `ensureDevDatabase` so that everything needing a project on\n * disk lives here and the decision logic stays testable without one.\n */\nasync function runDatabasePreflight(options: {\n projectRoot: string;\n disabled: boolean;\n /**\n * The DSN the preflight decides from, when the caller resolved one that is\n * not in `.env`. `--docker` is exactly that case: the compose URL is\n * derived, so reading `.env` here saw nothing and the container the flag\n * asked for was never started.\n */\n databaseUrl?: string;\n}): Promise<void> {\n const { projectRoot, disabled } = options;\n\n const hasCollections = projectHasCollections(projectRoot);\n\n // The one DSN this preflight is about. `ensureDevDatabase` decides from it,\n // and the push has to run against the same one — re-resolving inside the\n // push saw a `.env` with DATABASE_URL commented out, chose the managed\n // PGlite, and refused, on the first `rebase dev --docker` of every scaffold.\n const databaseUrl = options.databaseUrl ?? readEnvVar(projectRoot, \"DATABASE_URL\");\n\n const outcome = await ensureDevDatabase({\n projectRoot,\n databaseUrl,\n disabled,\n hasCollections,\n pushSchema: async () => {\n // The same driver entry point `rebase db push` uses, called with the\n // argv layout every command in this CLI receives — the full process\n // argument vector, which the callee slices. `--database-url` names\n // the database the preflight just started, so the push cannot\n // resolve its way to a different one. The throwing variant: a failed\n // push must not take the dev server down with it.\n // Quiet: the database was resolved and announced in the banner above.\n await runDriverDbCommand(schemaPushArgv(databaseUrl), { quiet: true });\n }\n });\n\n if (outcome.action === \"started\") console.log(\"\");\n}\n\n/**\n * Kill a child `rebase dev` started, with everything it started in turn.\n *\n * Both servers run detached, in a process group of their own, behind a shell\n * and a package manager — so the group is what has to go, not the pid.\n */\nfunction killChildTree(child: ResultPromise): void {\n if (child.killed) return;\n try {\n if (!child.pid) throw new Error(\"no pid\");\n if (process.platform === \"win32\") {\n execaCommandSync(`taskkill /pid ${child.pid} /T /F`);\n } else {\n process.kill(-child.pid, \"SIGKILL\");\n }\n } catch {\n try {\n child.kill(\"SIGKILL\");\n } catch {\n // Already gone.\n }\n }\n}\n\n/**\n * The port the last `rebase dev` backend landed on, from `.rebase-dev-port`.\n *\n * Read the way the server reads it (`listenWithPortRetry`): the first number,\n * whether the file is the server's `<bound> <requested>` or the CLI's bare\n * `<bound>`. `null` when there is no file or no number in it.\n */\nfunction rememberedDevPort(projectRoot: string): number | null {\n try {\n const raw = fs.readFileSync(path.join(projectRoot, DEV_PORT_FILENAME), \"utf-8\").trim().split(/\\s+/)[0];\n return parsePort(raw) ?? null;\n } catch {\n return null;\n }\n}\n\n/**\n * Whether the admin panel can start beside the backend instead of after it.\n *\n * Vite has to be told the backend's URL when it starts (`VITE_API_URL`), and\n * the backend's port is only certain once it has bound — so the admin panel\n * used to wait for \"Server running at\", and the time to it was the backend's\n * whole boot plus Vite's start. It can start beside the backend whenever the\n * port the backend will bind is known in advance:\n *\n * - **named with `--port`**: the server binds it or refuses to start, never\n * walks past it (`REBASE_DEV_PORT_EXPLICIT`), and `pinnedPortRefusal` has\n * already checked that nothing answers there;\n * - **free, and the port the backend tries first**: the server tries the port\n * its last run landed on before the one it is given (`.rebase-dev-port`), so\n * a remembered port must be this one — the \"kept from the last run\" rung —\n * or absent, which it is after every clean exit, and the derived port is\n * then the one tried. A port something already answers on would make the\n * backend move, so that case keeps the old order.\n *\n * Nothing here can rule out another process taking the port in the second the\n * backend boots, so `devCommand` still compares the announced port with the\n * one Vite was given and restarts the admin panel when they differ.\n *\n * Exported for its test.\n */\nexport async function frontendCanStartWithBackend(options: {\n projectRoot: string;\n port: number;\n pinned: boolean;\n probe?: (host: string, port: number) => Promise<boolean>;\n}): Promise<boolean> {\n if (options.pinned) return true;\n const remembered = rememberedDevPort(options.projectRoot);\n if (remembered !== null && remembered !== options.port) return false;\n const probe = options.probe ?? probeTcp;\n const taken = await Promise.all([probe(\"127.0.0.1\", options.port), probe(\"::1\", options.port)]);\n return !taken.some(Boolean);\n}\n\n/**\n * The tsx the backend is watched with, or exit saying why there is none.\n *\n * Checked before anything is started rather than just before the backend\n * spawns: start-up begins the database at once now, and a project whose\n * dependencies are missing should hear that first, not after two generators\n * have each failed on the same cause.\n */\nfunction requireUsableTsx(projectRoot: string): string {\n const tsxBin = resolveTsx(projectRoot);\n if (!tsxBin) {\n console.error(chalk.red(` ✗ ${dependenciesNotInstalled(projectRoot)}`));\n process.exit(1);\n }\n\n // Verify the tsx installation is intact (not just the symlink)\n const tsxValidationError = validateTsxInstallation(tsxBin);\n if (tsxValidationError) {\n const pmCmdsLocal = getPMCommands(detectPackageManager(projectRoot));\n const installCmd = pmCmdsLocal.install.join(\" \");\n console.error(chalk.red(\" ✗ tsx installation appears corrupted.\"));\n console.error(chalk.gray(` ${tsxValidationError}`));\n console.error(\"\");\n console.error(chalk.gray(\" To fix, run:\"));\n console.error(chalk.cyan(` rm -rf node_modules && ${installCmd}`));\n process.exit(1);\n }\n return tsxBin;\n}\n\nexport async function devCommand(rawArgs: string[]): Promise<void> {\n if (wantsHelp(rawArgs)) {\n printDevHelp();\n return;\n }\n\n // Removed rather than kept as a no-op: what it switched on is how `dev`\n // always behaves now. Named, so the answer is \"drop it\" rather than a\n // generic unknown-option error that reads like a typo.\n if (switchOn(rawArgs, \"--generate\") || rawArgs.includes(\"-g\")) {\n throw new UsageError(\n \"`rebase dev --generate` is gone: dev now regenerates the schema and the SDK types \" +\n \"on every start and every collection save. Drop the flag.\"\n );\n }\n\n const { flags: args } = parseCommandArgs({\n spec: DEV_FLAGS,\n rawArgs,\n commandWords: 1,\n command: \"dev\",\n maxPositionals: 0\n });\n\n const projectRoot = requireProjectRoot();\n\n // Fire-and-forget, and a no-op unless the developer opted in. Deliberately\n // not awaited: `rebase dev` starting is the thing the user is waiting for,\n // and a slow collector must never be in front of it.\n void recordEvent(\"cli.dev\", {\n backend_only: Boolean(args[\"--backend-only\"]),\n frontend_only: Boolean(args[\"--frontend-only\"])\n }, { projectRoot });\n const backendDir = findBackendDir(projectRoot);\n const frontendDir = findFrontendDir(projectRoot);\n const backendOnly = args[\"--backend-only\"] || false;\n const frontendOnly = args[\"--frontend-only\"] || false;\n /**\n * Start no database at all — not the compose container, and not the managed\n * one either. Read once, here, because both halves of `dev` have to agree:\n * gating only the preflight left the managed PGlite starting anyway, which\n * is the one database a scaffolded project would otherwise get.\n */\n const noDb = Boolean(args[\"--no-db\"]) || parseEnvBoolean(process.env.REBASE_DEV_NO_DB) === true;\n\n // Resolve the ports ONCE, before starting anything. Both, because the\n // backend is told where the frontend will be and cannot be told later: its\n // environment is fixed when it spawns.\n const { port: startPort, source: startPortSource } = resolveStartPort(projectRoot, args[\"--port\"]);\n // Named, not derived — see `pinnedPortRefusal`. Only the flag counts: the\n // .env's `PORT` is deliberately overridden by this command, and the shell's\n // is what a test harness sets when it wants a port that may still move.\n // `startPortSource === \"--port\"` says the same thing; kept as its own read\n // because this one is about the *flag*, not about which rung answered.\n const portIsPinned = args[\"--port\"] !== undefined;\n const pinnedFrontendPort = process.env.REBASE_FRONTEND_PORT;\n if (pinnedFrontendPort && !/^\\d+$/.test(pinnedFrontendPort)) {\n throw new Refusal(`REBASE_FRONTEND_PORT must be a number, got ${JSON.stringify(pinnedFrontendPort)}.`);\n }\n const frontendPort = pinnedFrontendPort\n ? Number(pinnedFrontendPort)\n : getProjectFrontendPort(projectRoot);\n\n // Before the banner, and before anything is started: a pinned port that is\n // already answering is a refusal, not a warning, and the developer should\n // get it in the first second rather than after a database and a schema push.\n if (!frontendOnly) {\n const refusal = await pinnedPortRefusal(startPort, portIsPinned);\n if (refusal) {\n console.error(chalk.red(` ✗ ${refusal}`));\n process.exit(1);\n }\n }\n\n const flagUrl = typeof args[\"--database-url\"] === \"string\" ? args[\"--database-url\"] : null;\n // A database named on this command line that nothing answers at is the\n // same kind of refusal, said in the flag's terms. The backend's own banner\n // said \"Verify DATABASE_URL in your .env file\", and dev ran on with the\n // backend dead.\n if (!frontendOnly && !noDb && flagUrl) {\n const refusal = await unreachableDatabaseFlag(flagUrl);\n if (refusal) {\n console.error(chalk.red(` ✗ ${refusal}`));\n process.exit(1);\n }\n }\n\n const startsBackend = !frontendOnly && Boolean(backendDir);\n\n // Before anything is started, because the database now starts at once: a\n // project that cannot run its backend should not be left with a database\n // daemon it never got to use, nor wait for two generators first.\n const tsxBin = startsBackend ? requireUsableTsx(projectRoot) : null;\n\n console.log(\"\");\n console.log(chalk.bold(\" 🚀 Rebase Dev Server\"));\n console.log(\"\");\n\n // Start the development database and give it a schema, when this project\n // has one that is plainly local and plainly not running. Everything about\n // when it declines is in `ensureDevDatabase`; the frontend-only case is\n // decided here because there is no backend to need a database at all.\n let schemaGeneration: SchemaGeneration = \"skipped\";\n /**\n * The database this run's backend gets. A project with a DATABASE_URL is\n * untouched; a project without one gets a managed Postgres, which is what\n * makes `rebase dev` the only command a new project needs.\n *\n * `--no-db` means start nothing, and that includes the managed database.\n * It used to gate only the preflight, so the one path that actually starts\n * a database on a scaffolded project — the managed PGlite — ran anyway:\n * `rebase dev --no-db` wrote `.rebase/pglite/`, booted a daemon and served\n * against it, which is the opposite of what a reader asking for no database\n * expects. The backend is left to fail on the DATABASE_URL it cannot find,\n * which is the failure the flag exists to produce.\n */\n let prepared: PreparedDatabase | null = null;\n /**\n * The backend port the SDK README was last generated for: the one the\n * backend is told at boot, then each one it announces it bound.\n */\n let sdkBackendPort: number | null = startsBackend ? startPort : null;\n const regenerateSdkFor = (port: number | null): Promise<void> => {\n sdkBackendPort = port;\n return regenerateSdk(projectRoot, port);\n };\n if (!frontendOnly) {\n // `--docker` names a container, not a connection string, and the\n // preflight is what starts it — but only when the order picks Docker. A\n // DATABASE_URL outranks the flag, and the preflight used to start the\n // container and push the schema into it while the backend ran elsewhere.\n // Pure, so deciding costs nothing; `prepareDatabaseEnv` reaches the same\n // answer and says the flag was ignored.\n const resolved = args[\"--docker\"]\n ? resolveProjectDatabase(projectRoot, { flagUrl, flagDocker: true, followLastDev: false })\n : null;\n const dockerUrl = resolved?.kind === \"docker\" ? resolved.url ?? undefined : undefined;\n ({ schemaGeneration, database: prepared } = await overlapDevStartup({\n // Side by side: they read the same collections and write different\n // files, so the SDK costs nothing on top of the schema.\n generate: async () => (await Promise.all([ensureGeneratedSchema(projectRoot), regenerateSdkFor(sdkBackendPort)]))[0],\n // Not under --database-url: the preflight decides from `.env`, and\n // may start the compose container it names, which is not the\n // database the flag points this run at.\n preflight: () => runDatabasePreflight({\n projectRoot,\n disabled: noDb || flagUrl !== null,\n databaseUrl: dockerUrl\n }),\n database: startsBackend && !noDb\n ? async () => prepareDatabaseEnv(projectRoot, {\n flagUrl,\n flagDocker: Boolean(args[\"--docker\"]),\n // This run's flags are the choice; the record below is\n // what the other commands follow.\n followLastDev: false,\n onProgress: (message) => console.log(chalk.gray(` ${message}`)),\n additionalKeys: await declaredAdditionalDatabases(projectRoot)\n })\n : null\n }));\n }\n\n // What the other commands follow — `rebase db url`, `db push`, `console` —\n // so they reach the database this server is on rather than resolve their\n // own. See `last-dev-database.ts`.\n if (prepared) {\n const run = { dockerFlag: Boolean(args[\"--docker\"]) };\n const { previous } = recordLastDevDatabase(projectRoot, prepared.database.kind, run);\n const notice = lastDevDatabaseNotice(previous, prepared.database.kind, run);\n if (notice) console.log(` ${chalk.yellow(\"⚠\")} ${chalk.gray(notice)}`);\n }\n\n const children: ResultPromise[] = [];\n\n /**\n * Whether this project has an admin panel to serve at all.\n *\n * A headless project declares one app, of type \"backend\", so there is no\n * `frontend/` and never will be. Warning that a directory is \"missing\" for\n * a shape that has no such directory reads as a broken scaffold on the\n * very first run of the very command the headless quickstart names.\n */\n const staticShape = ((): DevStaticShape => {\n try {\n // Where the banner's \"Admin\" link actually reaches the CMS, and\n // where each is once deployed. For the scaffolded project the CMS\n // is the frontend's root and the two are the same URL; for a\n // project that mounts the CMS as one route of a larger app, or in\n // an app of its own, they are not.\n return devStaticShape(loadManifest(projectRoot).manifest, projectRoot, frontendDir);\n } catch {\n // A manifest that will not load is a different problem, reported\n // elsewhere. Fall back to the directory, which is what this check\n // used to be.\n return { declaresStaticApp: Boolean(frontendDir) };\n }\n })();\n const declaresStaticApp = staticShape.declaresStaticApp;\n\n // --- State for printing the banner ---\n let frontendUrl = \"\";\n let backendUrl = \"\";\n let debounceSummary: NodeJS.Timeout | null = null;\n let bannerPrinted = false;\n\n /** Actual backend port, resolved once the server prints its URL. */\n let resolvedBackendPort: number | null = null;\n\n /**\n * The connection string of the database this run started, when it started\n * one. Filled in below; read by the headless banner, which is the only\n * place a headless project could learn it — there is no admin panel to\n * open, and `.env` deliberately does not name the managed database.\n */\n let managedConnectionString: string | null = null;\n\n /**\n * Where the backend mounted Swagger, if it mounted it at all.\n *\n * Read from the backend's own log rather than assumed: a headless project\n * with no tables yet serves no data API and no Swagger, and the banner must\n * not name a URL that answers 404.\n */\n let swaggerPath: string | null = null;\n\n function printSummary() {\n if (!backendUrl) return;\n // A headless project has no frontend URL to wait for, and waiting for\n // one meant it got no banner at all: the first run of `rebase dev`\n // ended in log lines, with the API URL, the Swagger path and the\n // database it had just created all unstated.\n if (declaresStaticApp && !frontendUrl) return;\n if (debounceSummary) clearTimeout(debounceSummary);\n debounceSummary = setTimeout(() => {\n if (bannerPrinted) return;\n\n const api = `http://localhost:${resolvedBackendPort}`;\n /** `[label, value]` per line; an empty pair is a blank line. */\n const lines: Array<[string, string]> = declaresStaticApp\n ? appBannerLines(stripAnsi(frontendUrl), api, staticShape)\n : [[\"\", \"\"], [\"✦ Rebase API is ready!\", \"\"], [\"➜ API: \", api]];\n\n if (!declaresStaticApp) {\n // Only when it is actually mounted. A project with no tables\n // yet mounts no data API and no Swagger, and printing a URL\n // that 404s is worse than printing nothing — the reader spends\n // the next ten minutes deciding whether their install is\n // broken. The backend says which; this reads its own child.\n if (swaggerPath) {\n lines.push([\"➜ Swagger: \", `${api}${swaggerPath}`]);\n } else {\n // Say why, in the box the reader is actually looking at.\n // \"No docs\" with no reason reads as a broken install.\n lines.push([\" \", \"(no tables served yet, so no data API and no docs)\"]);\n }\n if (managedConnectionString) {\n lines.push([\"\", \"\"]);\n lines.push([\"Database (managed, this project only):\", \"\"]);\n lines.push([\" \", managedConnectionString]);\n lines.push([\" \", \"also: rebase db url\"]);\n }\n }\n\n // Both halves of the first-admin rule, in the box the reader is\n // looking at when they go to sign in.\n //\n // `rebase init` writes REBASE_ADMIN_EMAIL and a generated password\n // into `.env` — for the compose stack, which runs in production,\n // where the first-registration window is shut. Nothing said so, and\n // `rebase dev` reads the same file, so the honest reading of that\n // `.env` was \"these are my credentials\" and the documented first\n // step — register, become the admin — looked broken.\n lines.push([\"\", \"\"]);\n lines.push([\"First login: \", \"the first account to register becomes admin\"]);\n lines.push([\" \", \"(a production boot seeds REBASE_ADMIN_EMAIL from .env instead)\"]);\n lines.push([\"\", \"\"]);\n\n // Sized to its contents. A fixed width silently pushed the right\n // border off the moment a value was longer than the cell, and a\n // managed connection string — port and all — always is.\n const inner = Math.max(...lines.map(([label, value]) => label.length + value.length)) + 4;\n\n console.log(\"\");\n console.log(chalk.cyan(\"┌\" + \"─\".repeat(inner) + \"┐\"));\n for (const [label, value] of lines) {\n console.log(\n chalk.cyan(\"│ \") + label + chalk.white(value) +\n \" \".repeat(inner - 2 - label.length - value.length) +\n chalk.cyan(\"│\")\n );\n }\n console.log(chalk.cyan(\"└\" + \"─\".repeat(inner) + \"┘\"));\n console.log(\"\");\n bannerPrinted = true;\n }, 500);\n }\n\n /**\n * Set once Ctrl+C has been seen, so a child dying on the way out is not\n * reported as a crash. Everything below distinguishes \"the backend fell\n * over\" from \"you asked me to stop\" by this flag alone — an exit code of\n * `null` with a signal is the shape of both.\n */\n let shuttingDown = false;\n\n // Handle graceful shutdown\n const cleanup = () => {\n shuttingDown = true;\n // Clean up dev port file\n try {\n const portFile = path.join(projectRoot, DEV_PORT_FILENAME);\n if (fs.existsSync(portFile)) fs.unlinkSync(portFile);\n\n const urlFile = path.join(projectRoot, \".rebase-dev-url\");\n if (fs.existsSync(urlFile)) fs.unlinkSync(urlFile);\n } catch { /* ignore */ }\n\n children.forEach(killChildTree);\n const exitLines = devExitLines(prepared?.database.kind ?? null);\n if (exitLines.length > 0) {\n console.log(\"\");\n for (const line of exitLines) console.log(chalk.gray(` ${line}`));\n }\n // Not a hard-coded 0. Ctrl+C is how `rebase dev` normally ends, so\n // exiting 0 unconditionally threw away the one thing `--backend-only`\n // has to report: whether the backend it was asked to run is up. A\n // healthy run still exits 0 — nothing sets the code in that case.\n process.exit(typeof process.exitCode === \"number\" ? process.exitCode : 0);\n };\n process.on(\"SIGINT\", cleanup);\n process.on(\"SIGTERM\", cleanup);\n\n /** The running admin panel, and the backend port it was told (`VITE_API_URL`). */\n let frontendChild: ResultPromise | null = null;\n let frontendApiPort: number | null = null;\n\n /**\n * Start the Vite frontend, optionally injecting the backend port.\n */\n function startFrontend(backendPort: number | null) {\n if (!frontendDir) return;\n\n console.log(` ${chalk.magenta(\"▶\")} Frontend: ${chalk.gray(frontendDir)}`);\n\n const frontendEnv: Record<string, string> = { ...process.env as Record<string, string> };\n\n frontendApiPort = backendPort;\n // Inject the resolved backend URL so Vite picks it up\n if (backendPort) {\n frontendEnv.VITE_API_URL = `http://localhost:${backendPort}`;\n console.log(` ${chalk.gray(\"↳ VITE_API_URL\")} = ${chalk.white(`http://localhost:${backendPort}`)}`);\n }\n\n const pm = detectPackageManager(projectRoot);\n const pmCmds = getPMCommands(pm);\n const runDevCmd = pmCmds.run(\"dev\");\n\n /**\n * Pin the frontend's port when asked.\n *\n * Vite takes 5173 and hands it to the first asker, so a machine already\n * running one dev server gives the second either a bind failure or —\n * worse — somebody else's app on the address you expected. The backend\n * port has been pinnable since it had the same problem; this is the\n * other half.\n *\n * npm needs `--` before script arguments and pnpm does not, which is\n * the kind of difference that silently passes `--port` to the package\n * manager instead of to Vite.\n */\n if (pm === \"npm\") runDevCmd.push(\"--\");\n runDevCmd.push(\"--port\", String(frontendPort));\n // `--strictPort` only when the developer pinned the port: they asked for\n // that exact one and a silent move defeats the point. Unpinned, Vite\n // keeps its own fallback, so a busy port still starts the frontend.\n if (pinnedFrontendPort) runDevCmd.push(\"--strictPort\");\n console.log(` ${chalk.gray(\"↳ frontend port\")} = ${chalk.white(String(frontendPort))}`);\n\n const child = execa(\n runDevCmd[0],\n runDevCmd.slice(1),\n {\n cwd: frontendDir,\n stdio: [\"inherit\", \"pipe\", \"pipe\"],\n env: frontendEnv,\n shell: true,\n detached: process.platform !== \"win32\"\n }\n );\n child.catch(() => {}); // prevent unhandled promise rejection on exit\n frontendChild = child;\n\n forEachOutputLine(child.stdout, (line) => {\n console.log(`${chalk.magenta.bold(\"[admin]\")} ${line}`);\n const cleanLine = stripAnsi(line);\n const urlMatch = cleanLine.match(/(http:\\/\\/(?:localhost|127\\.0\\.0\\.1):\\d+)/);\n // Only the current one: a replaced admin panel's last words are not its address.\n if (cleanLine.includes(\"Local:\") && urlMatch && child === frontendChild) {\n frontendUrl = urlMatch[1];\n printSummary();\n }\n });\n\n forEachOutputLine(child.stderr, (line) => {\n console.log(`${chalk.magenta.bold(\"[admin]\")} ${line}`);\n });\n\n children.push(child);\n }\n\n /**\n * Start the admin panel again, told the port the backend actually bound.\n *\n * Only reachable when it was started beside the backend\n * ({@link frontendCanStartWithBackend}) and something took that port in the\n * second the backend booted, so the backend moved. Left alone, the admin\n * panel would talk to whatever now answers on the old port — very often\n * another Rebase backend, since dev ports are derived per project.\n */\n async function restartFrontend(backendPort: number): Promise<void> {\n const stale = frontendChild;\n frontendUrl = \"\";\n console.log(` ${chalk.gray(`↳ The backend moved from ${frontendApiPort} to ${backendPort}; restarting the admin panel on it.`)}`);\n if (stale) {\n frontendChild = null;\n const index = children.indexOf(stale);\n if (index >= 0) children.splice(index, 1);\n killChildTree(stale);\n // Settled either way — it was killed, so it rejects — and never\n // through `finally`, whose promise would carry that rejection on\n // with nothing to catch it.\n await stale.then(() => undefined, () => undefined);\n // Its port is free once the process is gone, which can trail the\n // shell above it. Vite would otherwise take the next port — or,\n // pinned with --strictPort, refuse to start.\n for (let attempt = 0; attempt < 30 && await probeTcp(\"127.0.0.1\", frontendPort, 200); attempt++) {\n await new Promise((resolve) => setTimeout(resolve, 100));\n }\n }\n if (!shuttingDown) startFrontend(backendPort);\n }\n\n // Start backend\n if (startsBackend && backendDir && tsxBin) {\n const envFile = findEnvFile(projectRoot);\n const env: Record<string, string> = { ...process.env as Record<string, string> };\n if (envFile) {\n env.DOTENV_CONFIG_PATH = envFile;\n }\n\n // The database was resolved — and the managed one started — beside the\n // generators above; see `overlapDevStartup`.\n if (prepared) Object.assign(env, prepared.env);\n /**\n * Whether this run is on the managed database.\n *\n * `db push` runs on either one — on the managed database, which serves\n * exactly one database, it plans against a throwaway in-memory PGlite\n * instead of a second database beside it — but its children and the\n * backend still word their remedies for the database under this run.\n */\n const managed = prepared?.database.kind === \"managed\";\n if (managed) managedConnectionString = prepared?.env.DATABASE_URL ?? null;\n // …and the backend needs the same answer, for the same reason: its\n // schema-drift remedy is worded for the database it serves. `db`,\n // `schema` and `doctor` already pass this to their driver children;\n // the backend is the one that had to guess.\n if (prepared) env[DEV_DATABASE_KIND_ENV] = prepared.database.kind;\n\n // Always inject PORT so the backend uses our resolved port instead of\n // its hardcoded default (3001). This prevents cross-project collisions\n // when multiple Rebase instances run simultaneously.\n env.PORT = String(startPort);\n // …and whether that number was typed or derived, which decides whether\n // the server may walk past a collision. Always stated, never merely\n // set: `env` starts as a copy of this process's, so leaving the key\n // alone would let an inherited value pin a port nobody asked for.\n // The server also reads its presence as \"rebase dev started me\", and\n // takes over `.rebase/state.json` from a `rebase start` beside it.\n env.REBASE_DEV_PORT_EXPLICIT = String(portIsPinned);\n\n // And where the frontend will be, for the same reason. `FRONTEND_URL`\n // is the base of every emailed link and one of the two things CORS is\n // derived from, and the scaffold's `.env` states a fixed\n // `http://localhost:5173` that this command overrides — so the welcome\n // email linked a port the app was not running on. The frontend-only\n // case never reaches here, and there the .env value is still right.\n if (!backendOnly && frontendDir) {\n env.FRONTEND_URL = `http://localhost:${frontendPort}`;\n }\n\n console.log(` ${chalk.cyan(\"▶\")} Backend: ${chalk.gray(backendDir)}`);\n console.log(` ${chalk.gray(\"↳ PORT\")} = ${chalk.white(String(startPort))} ${chalk.gray(`(${START_PORT_SOURCE_LABELS[startPortSource]})`)}`);\n console.log(` ${chalk.gray(\"↳ Database\")} = ${chalk.white(databaseBannerValue(prepared))}`);\n // Stated on every start rather than left to be discovered. A developer\n // who does not know requests are serialized here will read the\n // difference as a bug in their own code.\n if (prepared) {\n for (const line of managedNotices(prepared).slice(1)) console.log(` ${chalk.gray(line)}`);\n }\n\n // The .env's PORT / VITE_API_URL look authoritative but are overridden in\n // dev: we derive a per-project port to avoid cross-project collisions and\n // point the frontend at it. Surface that so a mismatched .env doesn't turn\n // into a silent \"connecting to the wrong port\" debugging loop.\n if (envFile) {\n try {\n const envText = fs.readFileSync(envFile, \"utf-8\");\n const envPort = readEnvValue(envText, \"PORT\");\n const envApiUrl = readEnvValue(envText, \"VITE_API_URL\");\n // Only name the keys — never echo a raw `http://localhost:<port>`\n // value, so log scrapers don't mistake it for the dev server URL.\n const overridden: string[] = [];\n // A value equal to the scaffold's own default was written by\n // the scaffold, not chosen by anyone, so there is nothing to\n // warn about: this fired on the very first `rebase dev` of\n // every new project, about a line the developer had not read\n // yet, for a setting that applies to a command they had not\n // run. A warning that is always there is a warning nobody\n // reads when it matters.\n const portWasChosen = Boolean(envPort) && envPort !== String(SCAFFOLD_DEFAULT_PORT);\n if (portWasChosen && envPort !== String(startPort)) overridden.push(\"PORT\");\n if (envApiUrl && envApiUrl !== `http://localhost:${startPort}`) overridden.push(\"VITE_API_URL\");\n if (overridden.length > 0) {\n console.log(chalk.yellow(\n ` ⚠ dev uses a derived per-project port (${startPort}); your .env ${overridden.join(\" / \")} ` +\n `${overridden.length > 1 ? \"are\" : \"is\"} ignored here (avoids cross-project collisions). ` +\n `Pass ${chalk.white(\"--port\")} to pin a port.`\n ));\n }\n } catch { /* ignore — best-effort notice */ }\n }\n\n /** Whether the frontend has been launched (we only launch it once). */\n let frontendLaunched = false;\n /**\n * Whether the backend's first \"Server running at\" has been compared\n * with the port an admin panel started beside it was given. Once:\n * where a later restart lands is the watcher's business, as before.\n */\n let frontendPortChecked = false;\n\n // A generated schema that a *library upgrade* invalidated, repaired before\n // the backend sees it.\n //\n // Distinct from the regeneration further down, which watches\n // `config/collections` and so only fires when the developer edits\n // something. This case has no edit: 0.13 derives `category_id` where 0.12\n // derived `categorie_id`, from an unchanged collection. Boot-ensure renames\n // the database column, relation validation reads the stale generated module\n // and refuses to start — on this boot and every boot after, since the\n // rename does not run twice. Regenerating is the whole fix, and the release\n // note promises the rename is handled, so do it rather than announce it.\n //\n // Not the same thing as the regeneration at startup: this is not \"keep\n // my schema fresh\", it is \"do not hand the runtime a file that cannot\n // boot\".\n try {\n const activePlugin = getActiveBackendPlugin(backendDir);\n const pluginCli = activePlugin ? resolvePluginCliScript(backendDir, activePlugin) : null;\n if (pluginCli && needsStaleSchemaPreflight(schemaGeneration)) {\n await execa(tsxBin, [pluginCli, \"schema\", \"stale\", \"--fix\"], {\n cwd: backendDir,\n stdio: \"inherit\",\n env\n });\n }\n } catch {\n // Never block `dev` on this. A project with no generated schema, a\n // driver too old to know the subcommand, or a collections directory\n // that will not load all land here, and the boot itself reports each\n // of them better than a preflight can.\n }\n\n // A project with its own `backend/src/index.ts` runs it, exactly as\n // before. Without one, dev boots the stock runtime over the project's\n // TypeScript source — the same boot path a deployment takes, so what runs\n // locally is what will run deployed. This is what makes the hand-written\n // entrypoint optional instead of something every project must carry.\n const ejectedEntry = path.join(backendDir, \"src\", \"index.ts\");\n const usesStockRuntime = !fs.existsSync(ejectedEntry);\n const entryTarget = usesStockRuntime ? resolveDevRuntimeEntry() : \"src/index.ts\";\n\n // Computed even for an ejected backend: the paths are where the *project*\n // keeps its functions and crons, which is what dev has to watch, and an\n // ejected entrypoint reads the same directories.\n const runtimePaths = devRuntimeEnv(projectRoot);\n if (usesStockRuntime) {\n Object.assign(env, runtimePaths);\n }\n\n const restartTrigger = devRestartTrigger(projectRoot);\n if (restartTrigger) {\n try {\n fs.mkdirSync(path.dirname(restartTrigger), { recursive: true });\n fs.writeFileSync(restartTrigger, String(Date.now()));\n } catch {\n // Unwritable tmpdir: the trigger cannot fire, so let tsx keep the\n // collections — two restarts per save beats none.\n }\n }\n const usableTrigger = restartTrigger && fs.existsSync(restartTrigger) ? restartTrigger : null;\n const envFileForDev = envFile && envFileHiddenFromTsx(envFile, backendDir) ? envFile : null;\n const watchArgs = devWatchArgs(projectRoot, runtimePaths, entryTarget, usableTrigger, envFileForDev ? null : envFile).map(quoteForShell);\n /** Restart the backend once, after dev has finished reacting to a collection save. */\n const restartBackend = (): void => {\n if (!usableTrigger) return; // tsx restarted on the save itself\n try {\n fs.writeFileSync(usableTrigger, String(Date.now()));\n } catch {\n // Best effort: the next save tries again.\n }\n };\n // A `backend/.env` is under tsx's own dotfile ignore, so dev watches it\n // and restarts the backend through the trigger. The directory, not the\n // file: an editor that saves by renaming replaces the file a watch on it\n // would have followed.\n if (envFileForDev && usableTrigger) {\n let envDebounce: NodeJS.Timeout | null = null;\n fs.watch(path.dirname(envFileForDev), (_event, filename) => {\n if (filename !== path.basename(envFileForDev)) return;\n if (envDebounce) clearTimeout(envDebounce);\n envDebounce = setTimeout(() => {\n console.log(chalk.gray(` ↻ ${path.relative(projectRoot, envFileForDev)} changed: restarting the backend.`));\n restartBackend();\n }, 200);\n });\n }\n const generatedSchemaFile = path.resolve(projectRoot, runtimePaths.REBASE_DEV_SCHEMA);\n /** What tsx reacts to: a write, even of identical bytes, moves the mtime. */\n const generatedSchemaStamp = (): string | null => {\n try {\n const stat = fs.statSync(generatedSchemaFile);\n return `${stat.mtimeMs}:${stat.size}`;\n } catch {\n return null;\n }\n };\n\n // Watch the collections folder and regenerate what is derived from it.\n //\n // This used to print a box telling the reader to run `rebase schema\n // generate` themselves, which made the documented first edit fail in a\n // way nothing named. tsx restarts the backend on a config change, boot's\n // additive ensure adds the new column to the database — and then the\n // very first save of a row carrying it answered 400\n // VALIDATION_UNKNOWN_FIELDS, because the driver looked its columns up in\n // `backend/src/schema.generated.ts` and that file was still the one\n // generated before the edit.\n //\n // The driver reads the database now, so that particular 400 is gone at\n // the root. This stays because the file is still what `eject` and the\n // developer's own imports read, and the SDK types are what a frontend\n // compiles against: either one silently falling behind the collections\n // during a dev session is a surprise waiting at the next deploy. The\n // reader's single instruction is still \"save the file\".\n const collectionsDir = devCollectionsDir(projectRoot);\n {\n let debounce: NodeJS.Timeout | null = null;\n // The SQL schema and the SDK do not answer to the same edits. The\n // SDK is generated from the collections `index` module, which can\n // re-export anything the project puts under this directory, so every\n // change is a reason to rebuild it. The Drizzle schema covers only the\n // SQL-backed collections the loader reads — a Firestore collection\n // has nothing to generate, push or check for drift. Tracked across the\n // debounce window because one burst can touch both kinds of file.\n let sqlSchemaAffected = false;\n let changed = \"\";\n const collectionsChanged = (filename: string, affectsSchema: boolean): void => {\n sqlSchemaAffected = sqlSchemaAffected || affectsSchema;\n changed = filename;\n if (debounce) clearTimeout(debounce);\n debounce = setTimeout(() => {\n const regenerateSchema = sqlSchemaAffected;\n sqlSchemaAffected = false;\n void (async () => {\n if (!regenerateSchema) {\n await regenerateSdkFor(resolvedBackendPort ?? startPort);\n restartBackend();\n return;\n }\n // The box is drawn at a fixed width, so a name longer\n // than the cell would push the right border off.\n const shown = changed.length > 31 ? `…${changed.slice(-30)}` : changed.padEnd(31);\n console.log([\n \"\",\n chalk.yellow(\" ┌──────────────────────────────────────────────────────────────┐\"),\n chalk.yellow(\" │ 🔄 Collection file changed: \") + chalk.white(shown) + chalk.yellow(\"│\"),\n chalk.yellow(\" │ Regenerating the schema and the SDK types… │\"),\n chalk.yellow(\" └──────────────────────────────────────────────────────────────┘\")\n ].join(\"\\n\"));\n\n const schemaBefore = generatedSchemaStamp();\n await Promise.all([ensureGeneratedSchema(projectRoot), regenerateSdkFor(resolvedBackendPort ?? startPort)]);\n // The stock runtime imports the generated schema, so tsx\n // already restarts when it is written; a second restart\n // from the trigger is exactly what this avoids.\n // Otherwise — nothing was written, a file that did not\n // exist at boot (so nothing imported it, and tsx is not\n // watching it), or an ejected entrypoint that may not\n // import it — dev restarts the backend itself.\n if (!(usesStockRuntime && schemaBefore !== null && generatedSchemaStamp() !== schemaBefore)) restartBackend();\n\n console.log([\n chalk.green(\" ✓ Schema and SDK types regenerated. The backend restarts and boot\"),\n chalk.green(\" creates what is missing — a new collection, a new property.\"),\n // `db push` is the remedy for what boot leaves alone, on the\n // managed database as on your own PostgreSQL.\n chalk.gray(\" For what boot leaves alone — a renamed column, a narrowed type,\"),\n chalk.gray(` a removed field — run ${chalk.cyan(\"rebase db push\")}.`),\n \"\"\n ].join(\"\\n\"));\n })();\n }, 500);\n };\n const watchCollections = (): void => {\n fs.watch(collectionsDir, { recursive: true }, (_eventType, filename) => {\n if (!filename || filename.startsWith(\".\") || filename.endsWith(\".tmp\")) return;\n collectionsChanged(filename, affectsSqlSchema(collectionsDir, filename));\n });\n };\n if (fs.existsSync(collectionsDir)) {\n watchCollections();\n } else {\n // A headless project has none until `rebase schema introspect`\n // writes them. They are new to the backend as a whole, so they\n // count as a schema change.\n whenDirectoryAppears(collectionsDir, projectRoot, () => {\n watchCollections();\n collectionsChanged(`${path.basename(collectionsDir)}/`, true);\n });\n }\n }\n\n // The admin panel beside the backend, when the port it will bind is\n // already known — so the time to it is the slower of the two starts,\n // not their sum. Otherwise it waits for \"Server running at\" below.\n if (!backendOnly && frontendDir\n && await frontendCanStartWithBackend({ projectRoot, port: startPort, pinned: portIsPinned })) {\n frontendLaunched = true;\n startFrontend(startPort);\n }\n\n const backendChild = execa(\n tsxBin,\n watchArgs,\n {\n cwd: backendDir,\n stdio: [\"inherit\", \"pipe\", \"pipe\"],\n env,\n shell: true,\n detached: process.platform !== \"win32\"\n }\n );\n backendChild.catch(() => {}); // prevent unhandled promise rejection on exit\n\n /**\n * Whether the backend ever said \"Server running at\".\n *\n * The one fact this command has about whether the thing it started\n * works. Without it, a backend that threw on boot left `rebase dev`\n * printing \"Press Ctrl+C to stop all servers.\" under a stack trace, as\n * if everything were fine — and `tsx watch` keeps its own process alive\n * after the entry crashes, so nothing exits to contradict it.\n */\n let backendAnnounced = false;\n\n /**\n * A boot that lost its database part-way is booted again, a few\n * times, instead of waiting for a file change. See utils/boot-retry.ts:\n * a `rebase db branch create --force` that lands while the backend\n * boots ends its connections, and nothing in the project is wrong.\n */\n const bootRetry = new BootRetry();\n const retryPassingBootFailure = (line: string): void => {\n if (!usableTrigger || shuttingDown) return;\n const plan = bootRetry.line(stripAnsi(line));\n if (!plan) return;\n setTimeout(() => {\n if (shuttingDown || backendAnnounced) return;\n console.log(chalk.gray(` ↻ The backend lost its database while it was starting; starting it again (${plan.attempt} of ${plan.of}).`));\n restartBackend();\n }, plan.delayMs).unref?.();\n };\n\n /** The verdict, printed once per failure rather than once per line. */\n let backendDownReported = false;\n const reportBackendDown = (headline: string) => {\n if (shuttingDown || backendDownReported) return;\n backendDownReported = true;\n console.log(\"\");\n console.error(chalk.red(` ✗ ${headline}`));\n console.error(chalk.gray(\" Fix the error above; the watcher restarts it on the next change.\"));\n console.log(\"\");\n // With a frontend still running there is a server to keep serving,\n // so the run continues and only the message is owed. `--backend-only`\n // has nothing left to do, and a zero exit there tells a script the\n // backend ran fine. Cleared again by the next \"Server running at\".\n if (backendOnly) process.exitCode = 1;\n };\n\n /**\n * Say so when the backend is not up.\n *\n * A deadline rather than an exit code, because there is usually no exit\n * to watch: the watcher survives its child and waits for a file change,\n * so a crash is visible only as an absence.\n *\n * Armed once at spawn and **re-armed on every restart**, which is the\n * half that was missing. The old timer fired once, thirty seconds after\n * start-up, and its body began `if (backendAnnounced) return` — so a\n * backend that came up and then died on the next save was covered by\n * nothing at all. Breaking `config/resources.ts` under a running\n * `rebase dev` printed a stack trace, took `/health` to nothing, and\n * left the CLI saying \"Press Ctrl+C to stop all servers.\"\n */\n const readyTimeoutMs = Number(env.REBASE_DEV_READY_TIMEOUT_MS ?? process.env.REBASE_DEV_READY_TIMEOUT_MS ?? 30_000);\n let readyDeadline: NodeJS.Timeout | undefined;\n const disarmReadyDeadline = () => {\n if (readyDeadline) clearTimeout(readyDeadline);\n readyDeadline = undefined;\n };\n const armReadyDeadline = (headline: string) => {\n disarmReadyDeadline();\n if (readyTimeoutMs <= 0) return;\n readyDeadline = setTimeout(() => {\n if (backendAnnounced) return;\n reportBackendDown(headline);\n }, readyTimeoutMs);\n readyDeadline.unref?.();\n };\n const readySeconds = Math.round(readyTimeoutMs / 1000);\n armReadyDeadline(`The backend has not started after ${readySeconds}s.`);\n\n const noticePortMoved = (line: string): void => {\n const moved = portMovedNotice(line);\n if (!moved) return;\n console.log(` ${chalk.gray(\"↳ PORT\")} = ${chalk.white(moved.to)} ${chalk.gray(`(${moved.from} was in use)`)}`);\n };\n\n /**\n * The restart line, on either stream.\n *\n * A restart means the announced backend is gone: it either exited on\n * its own (tsx says \"Rerunning\") or is being killed (tsx says\n * \"Restarting\"). Either way the question \"is there a server?\" is open\n * again, so readiness is un-announced and the deadline re-armed. Without\n * this the flag stayed true forever after the first successful boot.\n *\n * Checked on both streams because which pipe it lands on is tsx's\n * business, and a marker watched on the wrong one never fires.\n */\n const noticeWatcherRestart = (line: string): void => {\n if (!WATCHER_RESTART_MARKER.test(stripAnsi(line))) return;\n backendAnnounced = false;\n backendDownReported = false;\n armReadyDeadline(`The backend did not come back up after ${readySeconds}s.`);\n };\n\n /**\n * And notice a backend that dies without tsx saying anything.\n *\n * `kill -9` of the server process is the plain case: tsx stays alive,\n * prints nothing, and waits for a file change that may never come.\n * Measured on tsx 4.23.1 — the watcher's transcript after the kill is\n * empty. So the only honest question left is whether anything is still\n * listening on the port the backend announced, and this asks it.\n *\n * Only runs between an announcement and the next restart, so an\n * ordinary restart (where the port is legitimately closed for a second)\n * cannot trip it: `noticeWatcherRestart` clears `backendAnnounced`\n * before the process is killed. Two misses rather than one, because a\n * single refused connect under load is not yet news.\n */\n let missedProbes = 0;\n const liveness = setInterval(() => {\n if (shuttingDown || !backendAnnounced || !resolvedBackendPort) {\n missedProbes = 0;\n return;\n }\n const port = resolvedBackendPort;\n void probeTcp(\"127.0.0.1\", port, 1_000).then(alive => {\n if (!backendAnnounced || resolvedBackendPort !== port) return;\n if (alive) {\n missedProbes = 0;\n return;\n }\n if (++missedProbes < 2) return;\n backendAnnounced = false;\n missedProbes = 0;\n reportBackendDown(`The backend stopped listening on port ${port}.`);\n });\n }, LIVENESS_INTERVAL_MS);\n liveness.unref?.();\n\n /**\n * And say so when tsx itself gives up.\n *\n * Distinct from the deadline above: this is the watcher exiting, not\n * the app under it. It happens when the entry cannot be resolved at\n * all, when node_modules is broken, or when something kills the\n * process — none of which tsx will retry, so the run is over and the\n * exit code should say so.\n */\n backendChild.on(\"exit\", (code: number | null, signal: NodeJS.Signals | null) => {\n disarmReadyDeadline();\n clearInterval(liveness);\n backendAnnounced = false;\n // The watcher itself is gone, so this is terminal in a way a failed\n // run is not — worth saying even if a crash was already reported.\n backendDownReported = false;\n reportBackendDown(signal\n ? `Backend was killed by ${signal}.`\n : `Backend exited with code ${code ?? 1}.`);\n if (backendOnly) process.exitCode = code ?? 1;\n });\n\n forEachOutputLine(backendChild.stdout, (line) => {\n console.log(`${chalk.cyan.bold(\"[backend]\")} ${line}`);\n noticeWatcherRestart(line);\n noticePortMoved(line);\n retryPassingBootFailure(line);\n const cleanLine = stripAnsi(line);\n const swaggerMatch = cleanLine.match(/Swagger UI available.*\"path\":\"([^\"]+)\"/);\n if (swaggerMatch) swaggerPath = swaggerMatch[1];\n\n const serverMatch = cleanLine.match(/Server running at http:\\/\\/(?:localhost|127\\.0\\.0\\.1):(\\d+)/);\n if (serverMatch) {\n backendAnnounced = true;\n backendDownReported = false;\n missedProbes = 0;\n bootRetry.up();\n // The backend is up, so an earlier failure is no longer the\n // answer this run gives.\n if (backendOnly) process.exitCode = 0;\n disarmReadyDeadline();\n resolvedBackendPort = parseInt(serverMatch[1], 10);\n backendUrl = \"started\";\n\n // Start frontend now that we know the real port — or, when it\n // started beside the backend, make sure it was told this one.\n // Before the banner: a restart clears the frontend URL, and the\n // banner then waits for the new one.\n if (!backendOnly && frontendDir) {\n if (!frontendLaunched) {\n frontendLaunched = true;\n startFrontend(resolvedBackendPort);\n } else if (!frontendPortChecked && frontendApiPort !== resolvedBackendPort) {\n void restartFrontend(resolvedBackendPort);\n }\n frontendPortChecked = true;\n }\n printSummary();\n\n // Save the url to a temp file for scripts to pick up\n const urlFile = path.join(projectRoot, \".rebase-dev-url\");\n fs.writeFileSync(urlFile, `http://localhost:${resolvedBackendPort}`, \"utf-8\");\n\n // Save the port to .rebase-dev-port for port affinity\n const portFile = path.join(projectRoot, DEV_PORT_FILENAME);\n fs.writeFileSync(portFile, devPortFileContents(resolvedBackendPort, startPort), \"utf-8\");\n\n // The SDK README names the port the backend was told; one that\n // moved past a collision gets the port it actually bound.\n if (sdkBackendPort !== null && sdkBackendPort !== resolvedBackendPort) {\n void regenerateSdkFor(resolvedBackendPort);\n }\n }\n });\n\n /** Whether we've already shown a corrupted-modules recovery hint. */\n let corruptedModulesWarned = false;\n\n forEachOutputLine(backendChild.stderr, (line) => {\n console.log(`${chalk.cyan.bold(\"[backend]\")} ${line}`);\n\n noticeWatcherRestart(line);\n noticePortMoved(line);\n retryPassingBootFailure(line);\n\n // Detect corrupted node_modules at runtime\n // (covers tsx and any other dependency whose pnpm store entry is broken)\n if (!corruptedModulesWarned) {\n const cleanLine = stripAnsi(line);\n if (\n cleanLine.includes(\"Cannot find module\") &&\n cleanLine.includes(\"node_modules/.pnpm/\")\n ) {\n corruptedModulesWarned = true;\n // Delay slightly so the full Node.js error stack prints first\n setTimeout(() => {\n const pm = detectPackageManager(projectRoot);\n const installCmd = getPMCommands(pm).install.join(\" \");\n console.error(\"\");\n console.error(chalk.red(\" ✗ node_modules appears corrupted — a required file is missing.\"));\n console.error(chalk.gray(\" This usually happens when a previous install was interrupted\"));\n console.error(chalk.gray(\" or the package manager store was cleaned.\"));\n console.error(\"\");\n console.error(chalk.gray(\" To fix, stop the dev server and run:\"));\n console.error(chalk.cyan(` rm -rf node_modules && ${installCmd}`));\n console.error(\"\");\n }, 200);\n }\n }\n });\n\n children.push(backendChild);\n } else if (!frontendOnly && !backendDir) {\n console.warn(chalk.yellow(\" ⚠ No backend/ directory found, skipping backend.\"));\n }\n\n // Start frontend immediately if backend-only mode or no backend\n if (!backendOnly && frontendDir && (frontendOnly || !backendDir)) {\n startFrontend(null);\n } else if (!backendOnly && !frontendDir && declaresStaticApp) {\n console.warn(chalk.yellow(\" ⚠ No frontend/ directory found, skipping frontend.\"));\n }\n\n if (children.length === 0) {\n console.error(chalk.red(\" ✗ Nothing to start. Check your project structure.\"));\n process.exit(1);\n }\n\n console.log(\"\");\n console.log(chalk.gray(\" Press Ctrl+C to stop all servers.\"));\n console.log(\"\");\n\n // Wait for all children to exit — including an admin panel restarted on\n // the backend's real port, which joins `children` after this wait began.\n const awaited = new Set<ResultPromise>();\n for (let pending = [...children]; pending.length > 0; pending = children.filter((child) => !awaited.has(child))) {\n for (const child of pending) awaited.add(child);\n // Settled either way: a child that is killed rejects, and `finally`\n // would hand that rejection on to a promise nothing catches.\n await Promise.all(pending.map((child) => child.then(() => undefined, () => undefined)));\n }\n}\n\nfunction printDevHelp() {\n console.log(`\n${chalk.bold(\"rebase dev\")} — Start the development server\n\n${chalk.green.bold(\"Usage\")}\n rebase dev [options]\n\n${chalk.green.bold(\"Options\")}\n ${chalk.blue(\"--backend-only, -b\")} Only start the backend server\n ${chalk.blue(\"--frontend-only, -f\")} Only start the frontend server\n ${chalk.blue(\"--port, -P\")} Backend port (default: auto-detected per project)\n ${chalk.blue(\"--database-url\")} ${chalk.gray(\"<url>\")} Use this Postgres, ahead of everything else\n ${chalk.blue(\"--docker\")} Use the project's docker-compose ${chalk.gray(\"db\")} service\n ${chalk.blue(\"--no-db\")} Start no database at all\n\n${chalk.green.bold(\"Which database\")}\n Ordered, and the order is the promise. The first of these that says\n something wins:\n\n 1. ${chalk.blue(\"--database-url <url>\")} on this command line\n 2. ${chalk.gray(\"DATABASE_URL\")} in the shell environment\n 3. the branch this checkout is switched to ${chalk.gray(\"(rebase db branch switch)\")}\n 4. ${chalk.gray(\"DATABASE_URL\")} in the project's .env\n 5. ${chalk.blue(\"--docker\")} on this command line\n 6. the managed development database ${chalk.gray(\"(PGlite, data in .rebase/)\")}\n\n A scaffolded project sets none of 1–5, so it lands on (6): no Docker,\n nothing to install, and the collections' tables are created at boot.\n\n ${chalk.blue(\"--docker\")} reaches the ${chalk.gray(\"db\")} service in this project's docker-compose.yml,\n starting the container if nothing is listening on its port, and pushing\n the schema when this command is what started it. Its connection string\n is derived from the compose file and ${chalk.gray(\"DATABASE_PASSWORD\")} in .env — the same\n string .env carries, commented out, as ${chalk.gray(\"DATABASE_URL\")}. Any of 1–4\n outranks it, and then nothing is started and a line says ${chalk.blue(\"--docker\")} was ignored.\n\n The choice is remembered: after ${chalk.blue(\"rebase dev --docker\")}, ${chalk.cyan(\"rebase db url\")}, ${chalk.cyan(\"db push\")},\n ${chalk.cyan(\"console\")} and the rest use the Docker database too, at rule 5. The next\n ${chalk.cyan(\"rebase dev\")} without ${chalk.blue(\"--docker\")} goes back to the managed database, and so do they.\n\n A database that is already running is never touched: no schema push,\n no connection. A DATABASE_URL pointing anywhere other than this machine\n is left alone entirely. Pass ${chalk.blue(\"--no-db\")}, or set REBASE_DEV_NO_DB=1, to\n start nothing — the backend then fails on the database it cannot reach,\n which is the point.\n\n${chalk.green.bold(\"Which port\")}\n Ordered like the database, and the order is the promise. The first of\n these that says something wins, and the start banner names which one did:\n\n 1. ${chalk.blue(\"--port <n>\")} on this command line\n 2. ${chalk.gray(\"PORT\")} in the shell environment\n 3. the port the last ${chalk.gray(\"rebase dev\")} used ${chalk.gray(\"(.rebase-dev-port)\")}\n 4. a port derived from this project's path\n\n ${chalk.gray(\"PORT\")} in the project's .env is ${chalk.bold(\"not\")} read here: this command resolves\n its port before .env is loaded and then sets ${chalk.gray(\"PORT\")} for the backend from\n what it resolved. Export it in your shell, or pass ${chalk.blue(\"--port\")}.\n\n${chalk.green.bold(\"Description\")}\n Starts both the backend (tsx watch + Hono) and frontend (Vite)\n dev servers concurrently with color-coded output prefixes.\n\n Each project automatically receives a unique default port derived\n from its directory path, preventing collisions when running multiple\n Rebase instances simultaneously.\n\n If the assigned port is already in use, the server will automatically\n try the next available port. The frontend starts beside the backend\n when the backend's port is known in advance (named with --port, or\n free), and after the backend is ready otherwise. Either way\n VITE_API_URL is injected with the port the backend actually bound —\n the frontend is restarted if the backend had to move.\n\n On startup and on every save under config/collections, the Drizzle\n schema (backend/src/schema.generated.ts) and the SDK types\n (generated/sdk) are regenerated from the collections.\n\n An edit to .env restarts the backend, which reads the file again. What\n this command sets for it is kept until ${chalk.cyan(\"rebase dev\")} restarts: ${chalk.gray(\"PORT\")}, and\n ${chalk.gray(\"DATABASE_URL\")} on the managed or the Docker database.\n`);\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The boxed banner the database commands print when a connection fails.
|
|
3
|
+
*
|
|
4
|
+
* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
5
|
+
* ❌ Cannot connect to PostgreSQL at 127.0.0.1:5508
|
|
6
|
+
* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
7
|
+
*
|
|
8
|
+
* The driver said: connect ECONNREFUSED 127.0.0.1:5508
|
|
9
|
+
* …
|
|
10
|
+
*
|
|
11
|
+
* The driver prints it for the commands it runs (`cli-errors.ts` in
|
|
12
|
+
* `@rebasepro/server-postgres`, which its entry does not export); this is the
|
|
13
|
+
* same box for the commands the CLI answers in its own process — `db pull`
|
|
14
|
+
* and `auth reset-password`'s database fallback — as plain lines for the
|
|
15
|
+
* caller to print. Never with the connection string in it: it carries the
|
|
16
|
+
* password.
|
|
17
|
+
*/
|
|
18
|
+
import type { DevDatabaseSource } from "./resolve.js";
|
|
19
|
+
/** The banner, as lines: a title, what the driver or tool said, then the body. */
|
|
20
|
+
export declare function banner(title: string, said: string | null, body: readonly string[]): string[];
|
|
21
|
+
/** `host:port` of a connection string, for a message: never the string, which carries the password. */
|
|
22
|
+
export declare function hostOf(connectionString: string): string;
|
|
23
|
+
/**
|
|
24
|
+
* A connection error's own sentence. A refused connection on a dual-stack
|
|
25
|
+
* host is an `AggregateError` with an empty message, and its sentence —
|
|
26
|
+
* `connect ECONNREFUSED 127.0.0.1:5432` — is in the attempts it holds.
|
|
27
|
+
*/
|
|
28
|
+
export declare function connectionErrorText(error: unknown): string;
|
|
29
|
+
/**
|
|
30
|
+
* What to print when opening `url` failed with `error`: the banner, in the
|
|
31
|
+
* case it is — the server not answering, credentials refused, a database
|
|
32
|
+
* closed to new connections, or a database of that name not there — or `null`
|
|
33
|
+
* when it is none of those four.
|
|
34
|
+
*
|
|
35
|
+
* `consequence` is the line saying what did not happen ("The password was not
|
|
36
|
+
* reset."); `fixes` is what to do about a server that does not answer, which
|
|
37
|
+
* depends on what named the database.
|
|
38
|
+
*/
|
|
39
|
+
export declare function connectionFailureBanner(url: string, error: unknown, options: {
|
|
40
|
+
consequence: string;
|
|
41
|
+
fixes: readonly string[];
|
|
42
|
+
}): string[] | null;
|
|
43
|
+
/**
|
|
44
|
+
* What to do about a database that does not answer, for what named it — the
|
|
45
|
+
* list the driver's banner gives for the same `REBASE_DATABASE_SOURCE`.
|
|
46
|
+
*/
|
|
47
|
+
export declare function connectionRefusedFixes(source: DevDatabaseSource): string[];
|
package/dist/dev-db/daemon.d.ts
CHANGED
|
@@ -70,6 +70,41 @@ export declare function managedUrl(port: number): string;
|
|
|
70
70
|
* match is the only evidence accepted.
|
|
71
71
|
*/
|
|
72
72
|
export declare function isDaemonAlive(state: DaemonState): Promise<boolean>;
|
|
73
|
+
/**
|
|
74
|
+
* Whether a daemon `started` by one CLI should give way to the CLI `running` now.
|
|
75
|
+
*
|
|
76
|
+
* The database proxy runs inside the daemon, and the daemon outlives every
|
|
77
|
+
* command — so the proxy a project gets is whichever CLI started the daemon
|
|
78
|
+
* last, not the one it upgraded to. True for a daemon from before daemons were
|
|
79
|
+
* stamped, for an older release, and for another build of the same release
|
|
80
|
+
* (two canaries, a canary and its release): canary hashes do not sort by date,
|
|
81
|
+
* and this CLI's proxy is the one it can vouch for. False for a newer release,
|
|
82
|
+
* which an older CLI leaves alone, and when this CLI cannot read its own
|
|
83
|
+
* version.
|
|
84
|
+
*/
|
|
85
|
+
export declare function daemonOutdated(started: string | undefined, running: string): boolean;
|
|
86
|
+
/**
|
|
87
|
+
* How many of `lsof -Fn`'s sockets are connections accepted on `ports`.
|
|
88
|
+
*
|
|
89
|
+
* Each socket is a line `n<local>-><remote>`. The daemon's accepted client
|
|
90
|
+
* connections have one of its public ports on the local side; its own
|
|
91
|
+
* connections to the socket server behind the proxy are on other ports.
|
|
92
|
+
*/
|
|
93
|
+
export declare function acceptedConnections(lsofOutput: string, ports: readonly number[]): number;
|
|
94
|
+
/**
|
|
95
|
+
* How many clients are connected to the daemon, over all its databases, or
|
|
96
|
+
* `null` when that cannot be known.
|
|
97
|
+
*
|
|
98
|
+
* Asked of the daemon first (`clients`). A daemon from before that command
|
|
99
|
+
* answers `error unknown command`, and then the operating system is asked.
|
|
100
|
+
*/
|
|
101
|
+
export declare function connectedClients(state: DaemonState): Promise<number | null>;
|
|
102
|
+
/**
|
|
103
|
+
* What to say about a daemon an older CLI started that cannot be restarted
|
|
104
|
+
* here, because something is connected to it — or nothing can say whether
|
|
105
|
+
* anything is.
|
|
106
|
+
*/
|
|
107
|
+
export declare function outdatedDaemonNotice(state: Pick<DaemonState, "cliVersion">, running: string, clients: number | null): string[];
|
|
73
108
|
/** The running daemon for this project, or `null`. Never starts anything. */
|
|
74
109
|
export declare function findRunningDaemon(projectRoot: string): Promise<DaemonState | null>;
|
|
75
110
|
/**
|
|
@@ -116,6 +151,12 @@ export interface EnsureOptions {
|
|
|
116
151
|
* — spawning vitest with `__dev-db-daemon` rather than the CLI.
|
|
117
152
|
*/
|
|
118
153
|
entry?: string;
|
|
154
|
+
/**
|
|
155
|
+
* Where the notice about a daemon an older CLI started goes, when it
|
|
156
|
+
* cannot be restarted. stderr by default, so `rebase db url` keeps stdout
|
|
157
|
+
* to the URL. Injected for tests.
|
|
158
|
+
*/
|
|
159
|
+
onNotice?: (message: string) => void;
|
|
119
160
|
/**
|
|
120
161
|
* Keys of the additional databases the project declares, beyond the
|
|
121
162
|
* default. Each is served by the daemon on request, so a project that
|
|
@@ -31,10 +31,17 @@
|
|
|
31
31
|
* has subscribed to, which costs nothing. A client that *did* ask now always
|
|
32
32
|
* gets it, which is the whole point.
|
|
33
33
|
*
|
|
34
|
+
* It corrects one more defect on the way: PGlite answers a failed
|
|
35
|
+
* extended-protocol message with a ReadyForQuery of its own and the Sync after
|
|
36
|
+
* it with another, and the proxy drops the extra one (`ReadyForQueryLedger`),
|
|
37
|
+
* because a client that takes it for the next query's answer reads that
|
|
38
|
+
* query's rows as empty and every answer after it one request late.
|
|
39
|
+
*
|
|
34
40
|
* Two properties make this safe rather than clever:
|
|
35
41
|
*
|
|
36
42
|
* - **It never parses SQL and never rewrites a byte.** Frames are forwarded
|
|
37
|
-
* verbatim; the only
|
|
43
|
+
* verbatim; the only edits are delivering a copy of one to more sockets and
|
|
44
|
+
* holding back that extra ReadyForQuery.
|
|
38
45
|
* - **Injection only happens on a message boundary.** The server→client stream
|
|
39
46
|
* is reassembled into whole protocol messages before anything is written on,
|
|
40
47
|
* so an injected frame can never land inside another message.
|
|
@@ -61,6 +68,61 @@ export declare class BackendMessageParser {
|
|
|
61
68
|
/** Bytes held back because they are not yet a whole message. */
|
|
62
69
|
get pending(): number;
|
|
63
70
|
}
|
|
71
|
+
/**
|
|
72
|
+
* Reassembles the client→server stream into whole messages, so the proxy
|
|
73
|
+
* knows what each answer is an answer to.
|
|
74
|
+
*
|
|
75
|
+
* The connection opens untyped — an `SSLRequest` or `GSSENCRequest` (each
|
|
76
|
+
* answered with one byte) and then the startup packet, each a bare `Int32
|
|
77
|
+
* length` and a code — and is `type + Int32 length` after it. Each message is
|
|
78
|
+
* returned as its type character, `"startup"` for the startup packet, and the
|
|
79
|
+
* negotiation requests not at all.
|
|
80
|
+
*/
|
|
81
|
+
export declare class FrontendMessageParser {
|
|
82
|
+
private buffered;
|
|
83
|
+
private started;
|
|
84
|
+
push(chunk: Buffer): string[];
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Keeps a connection's ReadyForQuery messages one to a request.
|
|
88
|
+
*
|
|
89
|
+
* PGlite (0.5.6) answers an extended-protocol message that fails with an
|
|
90
|
+
* ErrorResponse *and* a ReadyForQuery, then answers the Sync after it with a
|
|
91
|
+
* second ReadyForQuery. Real Postgres sends one: after an error it ignores
|
|
92
|
+
* everything up to the Sync and answers the Sync. `pg` absorbs the extra one
|
|
93
|
+
* when both arrive in one read. When they arrive apart — under load, a script
|
|
94
|
+
* beside a running `rebase dev` — it takes the second as the answer to the
|
|
95
|
+
* next query it sent:
|
|
96
|
+
*
|
|
97
|
+
* - a refused write's ROLLBACK "finished" on it, the pool saw the client
|
|
98
|
+
* released in a failed transaction (status 'E'), and the real ROLLBACK's
|
|
99
|
+
* answer arrived as "Received unexpected commandComplete message from
|
|
100
|
+
* backend" — two `[pg-pool]` ERROR lines on every refused write;
|
|
101
|
+
* - on a connection held across queries, the next query "finished" with no
|
|
102
|
+
* rows: a `SELECT` that answered `[]`.
|
|
103
|
+
*
|
|
104
|
+
* So the proxy follows each request and drops the ReadyForQuery that answers
|
|
105
|
+
* a failed message before its Sync. An error raised *by* the Sync — a deferred
|
|
106
|
+
* constraint failing as the implicit transaction commits — comes with the
|
|
107
|
+
* one ReadyForQuery, and that is kept, as is every simple Query's.
|
|
108
|
+
*/
|
|
109
|
+
export declare class ReadyForQueryLedger {
|
|
110
|
+
private readonly pending;
|
|
111
|
+
/** Note a message the client sent, before it is forwarded. */
|
|
112
|
+
sent(type: string): void;
|
|
113
|
+
/** Whether a message the backend sent goes on to the client. */
|
|
114
|
+
deliver(message: Buffer): boolean;
|
|
115
|
+
}
|
|
116
|
+
/** What a client over the managed database's limit is told, and what to do. */
|
|
117
|
+
export declare const NO_FREE_CONNECTION: {
|
|
118
|
+
message: string;
|
|
119
|
+
hint: string;
|
|
120
|
+
};
|
|
121
|
+
/**
|
|
122
|
+
* The refusal as Postgres sends one to a client over `max_connections`: a
|
|
123
|
+
* FATAL ErrorResponse with SQLSTATE 53300 (too_many_connections).
|
|
124
|
+
*/
|
|
125
|
+
export declare function noFreeConnectionResponse(): Buffer;
|
|
64
126
|
export declare function isNotificationFrame(message: Buffer): boolean;
|
|
65
127
|
/** Channel and payload of a NotificationResponse, for logging and tests. */
|
|
66
128
|
export declare function decodeNotification(message: Buffer): {
|
|
@@ -75,6 +137,12 @@ export interface NotificationProxyOptions {
|
|
|
75
137
|
host?: string;
|
|
76
138
|
/** Called for every notification broadcast. For diagnostics and tests. */
|
|
77
139
|
onNotification?: (channel: string, payload: string, copies: number) => void;
|
|
140
|
+
/**
|
|
141
|
+
* Connections the socket server behind accepts. One more is refused here,
|
|
142
|
+
* with a message, rather than dropped there without one. Default
|
|
143
|
+
* {@link MANAGED_SERVER_MAX_CONNECTIONS}, what the daemon gives it.
|
|
144
|
+
*/
|
|
145
|
+
maxConnections?: number;
|
|
78
146
|
}
|
|
79
147
|
/**
|
|
80
148
|
* The proxy itself.
|
|
@@ -90,6 +158,19 @@ export declare class NotificationProxy {
|
|
|
90
158
|
get connectionCount(): number;
|
|
91
159
|
start(): Promise<void>;
|
|
92
160
|
private accept;
|
|
161
|
+
/**
|
|
162
|
+
* Turn a client away, in words, when every connection the socket server
|
|
163
|
+
* accepts is taken.
|
|
164
|
+
*
|
|
165
|
+
* The socket server sets its limit on the listening socket, so Node closes
|
|
166
|
+
* a connection over it the moment it is accepted, before a byte is read:
|
|
167
|
+
* every client read that as "Connection terminated unexpectedly", which
|
|
168
|
+
* says the database is down. Each client has its own connection upstream,
|
|
169
|
+
* so the proxy's count is the server's, and it answers as Postgres answers
|
|
170
|
+
* a client over `max_connections`, once the client has said hello: "no TLS"
|
|
171
|
+
* to an SSLRequest, and the refusal to the startup packet.
|
|
172
|
+
*/
|
|
173
|
+
private refuse;
|
|
93
174
|
/**
|
|
94
175
|
* Copy a notification to every other client.
|
|
95
176
|
*
|