@rebasepro/cli 0.23.0 → 0.24.1-canary.g19596ef
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +19 -3
- package/bin/rebase.js +4 -1
- package/dist/agent-setup-B31F00DC.js +324 -0
- package/dist/agent-setup-B31F00DC.js.map +1 -0
- package/dist/api-keys-Bl1I9q7M.js +525 -0
- package/dist/api-keys-Bl1I9q7M.js.map +1 -0
- package/dist/apps-CNgQJjg5.js +340 -0
- package/dist/apps-CNgQJjg5.js.map +1 -0
- package/dist/args-BnmsEXvu.js +109 -0
- package/dist/args-BnmsEXvu.js.map +1 -0
- package/dist/atlas-dev-database-D_LP_Woq.js +62 -0
- package/dist/atlas-dev-database-D_LP_Woq.js.map +1 -0
- package/dist/auth-aySHEF45.js +414 -0
- package/dist/auth-aySHEF45.js.map +1 -0
- package/dist/branch-pointer-DvSHBjjz.js +2 -0
- package/dist/build-DIHWIqO4.js +336 -0
- package/dist/build-DIHWIqO4.js.map +1 -0
- package/dist/bundle-CqURVN-K.js +1621 -0
- package/dist/bundle-CqURVN-K.js.map +1 -0
- package/dist/bundle.d.ts +12 -3
- package/dist/cli.d.ts +23 -2
- package/dist/cloud-DjByQlkW.js +9667 -0
- package/dist/cloud-DjByQlkW.js.map +1 -0
- package/dist/collection-drift-BKj_BoMq.js +106 -0
- package/dist/collection-drift-BKj_BoMq.js.map +1 -0
- package/dist/commands/__test_support__/parser-specs.d.ts +13 -0
- package/dist/commands/agent-setup.d.ts +116 -0
- package/dist/commands/api-keys.d.ts +86 -23
- package/dist/commands/apps.d.ts +17 -0
- package/dist/commands/auth.d.ts +7 -0
- package/dist/commands/build.d.ts +5 -1
- package/dist/commands/cloud/auth.d.ts +4 -3
- package/dist/commands/cloud/bundle-deploy.d.ts +45 -0
- package/dist/commands/cloud/context.d.ts +44 -5
- package/dist/commands/cloud/cron.d.ts +53 -0
- package/dist/commands/cloud/debug.d.ts +134 -0
- package/dist/commands/cloud/index.d.ts +89 -12
- package/dist/commands/cloud/token-capabilities.d.ts +76 -0
- package/dist/commands/cloud/tokens.d.ts +23 -0
- package/dist/commands/console.d.ts +12 -0
- package/dist/commands/db.d.ts +79 -25
- package/dist/commands/dev.d.ts +181 -13
- package/dist/commands/doctor.d.ts +5 -0
- package/dist/commands/generate_sdk.d.ts +29 -2
- package/dist/commands/init.d.ts +115 -14
- package/dist/commands/schema.d.ts +6 -0
- package/dist/commands/skills.d.ts +126 -7
- package/dist/commands/start.d.ts +44 -0
- package/dist/commands/storage.d.ts +46 -0
- package/dist/commands/upgrade.d.ts +1 -0
- package/dist/consent-ClRHmcxf.js +76 -0
- package/dist/consent-ClRHmcxf.js.map +1 -0
- package/dist/console-D6SJf8k_.js +102 -0
- package/dist/console-D6SJf8k_.js.map +1 -0
- package/dist/context-BqhRydBg.js +1234 -0
- package/dist/context-BqhRydBg.js.map +1 -0
- package/dist/{daemon-entry-LTFKtpHy.js → daemon-entry-Da-D3rmH.js} +3 -4
- package/dist/daemon-entry-Da-D3rmH.js.map +1 -0
- package/dist/daemon-pzsQwDHa.js +2 -0
- package/dist/db-B7RuNuaQ.js +1312 -0
- package/dist/db-B7RuNuaQ.js.map +1 -0
- package/dist/derive-Dl0z2OWi.js +484 -0
- package/dist/derive-Dl0z2OWi.js.map +1 -0
- package/dist/dev-CYr_WXan.js +1599 -0
- package/dist/dev-CYr_WXan.js.map +1 -0
- package/dist/dev-db/atlas-dev-database.d.ts +32 -0
- package/dist/dev-db/daemon-entry.d.ts +13 -0
- package/dist/dev-db/daemon.d.ts +10 -0
- package/dist/dev-db/last-dev-database.d.ts +41 -0
- package/dist/dev-db/prepare.d.ts +29 -3
- package/dist/dev-db/pull.d.ts +90 -5
- package/dist/dev-db/resolve.d.ts +26 -2
- package/dist/dev-preflight-B-LYyBf1.js +2 -0
- package/dist/dev-server-DG8Tt-uf.js +96 -0
- package/dist/dev-server-DG8Tt-uf.js.map +1 -0
- package/dist/doctor-BP8cZ-7F.js +771 -0
- package/dist/doctor-BP8cZ-7F.js.map +1 -0
- package/dist/eject-DizdoecV.js +335 -0
- package/dist/eject-DizdoecV.js.map +1 -0
- package/dist/fold-static-BC9CYicw.js +201 -0
- package/dist/fold-static-BC9CYicw.js.map +1 -0
- package/dist/fold-static.d.ts +26 -3
- package/dist/function-portability-BIvlvR9q.js +265 -0
- package/dist/function-portability-BIvlvR9q.js.map +1 -0
- package/dist/generate_sdk-BPqdJc3Q.js +489 -0
- package/dist/generate_sdk-BPqdJc3Q.js.map +1 -0
- package/dist/index.es.js +199 -22095
- package/dist/index.es.js.map +1 -1
- package/dist/init-Utstvh2Q.js +1149 -0
- package/dist/init-Utstvh2Q.js.map +1 -0
- package/dist/last-dev-database-B58g89uA.js +2 -0
- package/dist/manifest-DRp0UVTj.js +880 -0
- package/dist/manifest-DRp0UVTj.js.map +1 -0
- package/dist/manifest.d.ts +56 -4
- package/dist/normalize-imports-X0Tgze9y.js +107 -0
- package/dist/normalize-imports-X0Tgze9y.js.map +1 -0
- package/dist/package-manager-DSEOwTqM.js +193 -0
- package/dist/package-manager-DSEOwTqM.js.map +1 -0
- package/dist/path-flags-BFgG9V6y.js +91 -0
- package/dist/path-flags-BFgG9V6y.js.map +1 -0
- package/dist/precompress.d.ts +17 -0
- package/dist/prepare-CDN6lSpz.js +1236 -0
- package/dist/prepare-CDN6lSpz.js.map +1 -0
- package/dist/{project-3WCbrrfW.js → project-Wp2zoyeN.js} +34 -184
- package/dist/project-Wp2zoyeN.js.map +1 -0
- package/dist/pull-CrGYMEsy.js +458 -0
- package/dist/pull-CrGYMEsy.js.map +1 -0
- package/dist/resolve-B2O-wKKk.js +2 -0
- package/dist/resources-FD7AfZx8.js +183 -0
- package/dist/resources-FD7AfZx8.js.map +1 -0
- package/dist/schema-DMdr431S.js +125 -0
- package/dist/schema-DMdr431S.js.map +1 -0
- package/dist/skills-DTFibT1m.js +642 -0
- package/dist/skills-DTFibT1m.js.map +1 -0
- package/dist/start-DEa-hOBh.js +267 -0
- package/dist/start-DEa-hOBh.js.map +1 -0
- package/dist/{state-C59Elrnt.js → state-Do7ZJYkF.js} +49 -4
- package/dist/state-Do7ZJYkF.js.map +1 -0
- package/dist/status-DxnJTekv.js +352 -0
- package/dist/status-DxnJTekv.js.map +1 -0
- package/dist/storage-DyVq-f_F.js +303 -0
- package/dist/storage-DyVq-f_F.js.map +1 -0
- package/dist/telemetry/identity.d.ts +10 -1
- package/dist/telemetry-C8Uq8phb.js +129 -0
- package/dist/telemetry-C8Uq8phb.js.map +1 -0
- package/dist/telemetry-EMFRVRQi.js +351 -0
- package/dist/telemetry-EMFRVRQi.js.map +1 -0
- package/dist/unknown-command-DNwDcX3w.js +98 -0
- package/dist/unknown-command-DNwDcX3w.js.map +1 -0
- package/dist/upgrade-CtZtRkTE.js +1332 -0
- package/dist/upgrade-CtZtRkTE.js.map +1 -0
- package/dist/upgrade.d.ts +38 -5
- package/dist/utils/dev-preflight.d.ts +47 -5
- package/dist/utils/dev-server.d.ts +30 -0
- package/dist/utils/path-flags.d.ts +31 -0
- package/dist/utils/project.d.ts +16 -0
- package/dist/utils/temp-archive.d.ts +9 -0
- package/dist/version-Buyyo1ut.js +58 -0
- package/dist/version-Buyyo1ut.js.map +1 -0
- package/package.json +7 -7
- package/templates/eject/Dockerfile +4 -2
- package/templates/eject/backend/src/env.ts +1 -1
- package/templates/eject/backend/src/index.ts +42 -4
- package/templates/eject/docker-compose.custom.yml +8 -4
- package/templates/generated-schemas/blank.ts +40 -0
- package/templates/generated-schemas/blog.ts +127 -0
- package/templates/generated-schemas/ecommerce.ts +93 -0
- package/templates/overlays/baas/ai-instructions.md +1 -1
- package/templates/overlays/baas/config/package.json +1 -0
- package/templates/overlays/baas/config/storage.ts +5 -3
- package/templates/overlays/baas/package.json +1 -0
- package/templates/template/.cursorrules +1 -1
- package/templates/template/.env.example +20 -10
- package/templates/template/.github/copilot-instructions.md +1 -1
- package/templates/template/.mcp.json +2 -2
- package/templates/template/.windsurfrules +1 -1
- package/templates/template/AGENTS.md +1 -1
- package/templates/template/CLAUDE.md +1 -1
- package/templates/template/README.md +21 -15
- package/templates/template/ai-instructions.md +5 -4
- package/templates/template/backend/functions/hello.ts +8 -0
- package/templates/template/backend/package.json +1 -1
- package/templates/template/config/resources.ts +7 -6
- package/templates/template/config/storage.ts +5 -3
- package/templates/template/docker-compose.yml +15 -9
- package/templates/template/frontend/vite.config.ts +13 -3
- package/templates/template/gitignore +6 -1
- package/templates/template/package.json +5 -3
- package/templates/template/scripts/example.ts +6 -4
- package/templates/template/tsconfig.json +25 -0
- package/dist/branch-pointer-dBiClJ0p.js +0 -111
- package/dist/branch-pointer-dBiClJ0p.js.map +0 -1
- package/dist/constraints-DKGbNfUW.js +0 -48
- package/dist/constraints-DKGbNfUW.js.map +0 -1
- package/dist/daemon-DR9Tk0La.js +0 -355
- package/dist/daemon-DR9Tk0La.js.map +0 -1
- package/dist/daemon-entry-LTFKtpHy.js.map +0 -1
- package/dist/dev-preflight-CNLl4rdo.js +0 -367
- package/dist/dev-preflight-CNLl4rdo.js.map +0 -1
- package/dist/project-3WCbrrfW.js.map +0 -1
- package/dist/pull-Dywm5FWP.js +0 -232
- package/dist/pull-Dywm5FWP.js.map +0 -1
- package/dist/resolve-BAeRUBhg.js +0 -74
- package/dist/resolve-BAeRUBhg.js.map +0 -1
- package/dist/state-C59Elrnt.js.map +0 -1
- package/templates/template/backend/src/schema.generated.ts +0 -13
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dev-CYr_WXan.js","names":[],"sources":["../src/commands/dev.ts"],"sourcesContent":["/**\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 { 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, UsageError, wantsHelp } from \"../utils/args\";\nimport { affectsSqlSchema } from \"../utils/collection-drift\";\nimport { ensureDevDatabase, probeTcp } from \"../utils/dev-preflight\";\nimport { 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 * 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): 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 args.push(\"--conditions\", \"development\", entryTarget);\n return args;\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 * 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 const saved = parsePort(fs.readFileSync(portFile, \"utf-8\"));\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 * 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 * 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 */\nasync function regenerateSdk(projectRoot: string): 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 });\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 (rawArgs.includes(\"--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 Error(`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 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 const flagUrl = typeof args[\"--database-url\"] === \"string\" ? args[\"--database-url\"] : null;\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), regenerateSdk(projectRoot)]))[0],\n preflight: () => runDatabasePreflight({\n projectRoot,\n disabled: noDb,\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 { previous } = recordLastDevDatabase(projectRoot, prepared.database.kind);\n const notice = lastDevDatabaseNotice(previous, prepared.database.kind);\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 // 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 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 watchArgs = devWatchArgs(projectRoot, runtimePaths, entryTarget, usableTrigger).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 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 if (fs.existsSync(collectionsDir)) {\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 fs.watch(collectionsDir, { recursive: true }, (_eventType, filename) => {\n if (!filename || filename.startsWith(\".\") || filename.endsWith(\".tmp\")) return;\n sqlSchemaAffected = sqlSchemaAffected || affectsSqlSchema(collectionsDir, filename);\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 regenerateSdk(projectRoot);\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), regenerateSdk(projectRoot)]);\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, or an ejected\n // entrypoint may not import it — dev restarts the\n // backend itself.\n if (!(usesStockRuntime && 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 }\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 /** 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 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 // 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, String(resolvedBackendPort), \"utf-8\");\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\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}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A second, throwaway PGlite for Atlas to plan against.
|
|
3
|
+
*
|
|
4
|
+
* Atlas computes a schema change by replaying the desired schema into an
|
|
5
|
+
* **empty** database and diffing. On a real server the driver makes one as
|
|
6
|
+
* `<db>_dev_diff`; PGlite serves exactly one database, so that `CREATE
|
|
7
|
+
* DATABASE` succeeds and creates nothing, and `rebase db push`, `generate` and
|
|
8
|
+
* `migrate` were refused on the managed database outright — which left it with
|
|
9
|
+
* no way to drop, rename or narrow a column: boot only ever adds.
|
|
10
|
+
*
|
|
11
|
+
* So the command that needs one starts one: in memory, in this process, for
|
|
12
|
+
* as long as the command runs. It shares nothing with the managed database's
|
|
13
|
+
* daemon (a separate instance, no data directory, a port of its own), so the
|
|
14
|
+
* project's data is never the thing Atlas fills and clears, and nothing is
|
|
15
|
+
* left behind — not a directory, not a process.
|
|
16
|
+
*
|
|
17
|
+
* Measured with Atlas 1.2.3 against PGlite 0.5.6: `schema apply` with this as
|
|
18
|
+
* `--dev-url` plans and applies a DROP COLUMN and a `varchar(200)` →
|
|
19
|
+
* `varchar(50)` narrowing on a PGlite target, and the dev database is empty
|
|
20
|
+
* again afterwards, so one instance serves every Atlas call of a command.
|
|
21
|
+
*
|
|
22
|
+
* It runs on this process's event loop. The driver runs as a child process,
|
|
23
|
+
* which is what keeps the loop free to serve it; a synchronous wait on that
|
|
24
|
+
* child would hang Atlas.
|
|
25
|
+
*/
|
|
26
|
+
export interface AtlasDevDatabase {
|
|
27
|
+
/** Connection string for Atlas's `--dev-url`. */
|
|
28
|
+
url: string;
|
|
29
|
+
/** Stop serving and drop the instance. Safe to call twice. */
|
|
30
|
+
stop(): Promise<void>;
|
|
31
|
+
}
|
|
32
|
+
export declare function startAtlasDevDatabase(): Promise<AtlasDevDatabase>;
|
|
@@ -32,4 +32,17 @@ export interface DaemonArgs {
|
|
|
32
32
|
* in the caller and guessing would hide it.
|
|
33
33
|
*/
|
|
34
34
|
export declare function parseDaemonArgs(argv: readonly string[]): DaemonArgs;
|
|
35
|
+
/**
|
|
36
|
+
* Load the extension bundles PGlite needs.
|
|
37
|
+
*
|
|
38
|
+
* `CREATE EXTENSION pg_trgm` cannot install anything on its own here — PGlite
|
|
39
|
+
* resolves extensions from bundles handed to the constructor, and a missing one
|
|
40
|
+
* fails at migration time with `extension "pg_trgm" is not available`, which
|
|
41
|
+
* reads like a broken database rather than a missing import.
|
|
42
|
+
*
|
|
43
|
+
* Every failure here is loud for that reason, including the one that is new:
|
|
44
|
+
* pgvector arrives from a package of its own, so unlike the contrib bundles it
|
|
45
|
+
* can be absent while PGlite itself is fine.
|
|
46
|
+
*/
|
|
47
|
+
export declare function loadExtensions(): Promise<Record<string, unknown>>;
|
|
35
48
|
export declare function runDaemon(args: DaemonArgs): Promise<void>;
|
package/dist/dev-db/daemon.d.ts
CHANGED
|
@@ -72,6 +72,16 @@ export declare function managedUrl(port: number): string;
|
|
|
72
72
|
export declare function isDaemonAlive(state: DaemonState): Promise<boolean>;
|
|
73
73
|
/** The running daemon for this project, or `null`. Never starts anything. */
|
|
74
74
|
export declare function findRunningDaemon(projectRoot: string): Promise<DaemonState | null>;
|
|
75
|
+
/**
|
|
76
|
+
* Resolve the CLI entry point, so the daemon can be spawned as ourselves.
|
|
77
|
+
*
|
|
78
|
+
* A hidden subcommand rather than a second build entry: under `tsx` this file
|
|
79
|
+
* is `src/dev-db/daemon.ts` and in a published CLI it is bundled into
|
|
80
|
+
* `dist/index.js`, and in both cases the executable to re-invoke is the one
|
|
81
|
+
* already running. `rebase dev` re-runs itself the same way to regenerate the
|
|
82
|
+
* SDK types.
|
|
83
|
+
*/
|
|
84
|
+
export declare function resolveCliEntry(): string;
|
|
75
85
|
/**
|
|
76
86
|
* How to re-invoke ourselves, which differs between a published CLI and this
|
|
77
87
|
* repository.
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which database the last `rebase dev` ran on, when nothing else records it.
|
|
3
|
+
*
|
|
4
|
+
* `rebase dev --docker` is a choice made on one command line, and every other
|
|
5
|
+
* command resolved without it: while dev served the project from its compose
|
|
6
|
+
* database, `rebase db url` printed a managed PGlite URL — and started that
|
|
7
|
+
* daemon to print it — and `db push`, `console` and the rest would have used
|
|
8
|
+
* the same empty database. Nothing said the two disagreed.
|
|
9
|
+
*
|
|
10
|
+
* So `rebase dev` records its choice here, and the other commands follow it
|
|
11
|
+
* (`resolve.ts`, rule 5). Only Docker is recorded. Every other database is
|
|
12
|
+
* already somewhere each command reads — `--database-url` aside, which is a
|
|
13
|
+
* choice for one command line — or is the default, so the record holds
|
|
14
|
+
* `docker` exactly when the last `rebase dev` ran on it, and any other start
|
|
15
|
+
* of `rebase dev` removes it. That is also the way back: `rebase dev` without
|
|
16
|
+
* `--docker`.
|
|
17
|
+
*
|
|
18
|
+
* Beside the branch pointer in `.rebase/`, which is gitignored: which database
|
|
19
|
+
* this checkout last ran on is a fact about one machine, never the project.
|
|
20
|
+
*/
|
|
21
|
+
import type { DevDatabase } from "./resolve.js";
|
|
22
|
+
export declare function lastDevDatabasePath(projectRoot: string): string;
|
|
23
|
+
/** Whether the last `rebase dev` ran on the compose database. */
|
|
24
|
+
export declare function readLastDevDocker(projectRoot: string): boolean;
|
|
25
|
+
/**
|
|
26
|
+
* Record what this `rebase dev` runs on, and say what the last one ran on.
|
|
27
|
+
*
|
|
28
|
+
* Best effort: a checkout whose `.rebase/` cannot be written still runs, and
|
|
29
|
+
* the other commands then resolve as they did before this existed.
|
|
30
|
+
*/
|
|
31
|
+
export declare function recordLastDevDatabase(projectRoot: string, kind: DevDatabase["kind"]): {
|
|
32
|
+
previous: "docker" | null;
|
|
33
|
+
};
|
|
34
|
+
/**
|
|
35
|
+
* What `rebase dev` says when it leaves the Docker database the last run used.
|
|
36
|
+
*
|
|
37
|
+
* Said rather than left to be noticed: the data a developer was working with
|
|
38
|
+
* is in the container, and the managed database this run is on is a different,
|
|
39
|
+
* possibly empty, one.
|
|
40
|
+
*/
|
|
41
|
+
export declare function lastDevDatabaseNotice(previous: "docker" | null, kind: DevDatabase["kind"]): string | null;
|
package/dist/dev-db/prepare.d.ts
CHANGED
|
@@ -24,6 +24,12 @@ export interface PrepareOptions {
|
|
|
24
24
|
flagUrl?: string | null;
|
|
25
25
|
/** `--docker`. */
|
|
26
26
|
flagDocker?: boolean;
|
|
27
|
+
/**
|
|
28
|
+
* Follow the database the last `rebase dev` ran on (`last-dev-database.ts`).
|
|
29
|
+
* On by default; `rebase dev` itself turns it off, because its own flags are
|
|
30
|
+
* the choice being recorded.
|
|
31
|
+
*/
|
|
32
|
+
followLastDev?: boolean;
|
|
27
33
|
/** Suppress the "starting…" progress line. */
|
|
28
34
|
quiet?: boolean;
|
|
29
35
|
/** Where human-facing lines go. Defaults to stdout via the caller. */
|
|
@@ -82,9 +88,10 @@ export declare function resolveActiveBranch(projectRoot: string, envFile: Record
|
|
|
82
88
|
* The compose `db` service's connection string for this project, or null.
|
|
83
89
|
*
|
|
84
90
|
* Read here rather than in `resolve.ts` so the ordering there stays pure, and
|
|
85
|
-
* exported
|
|
86
|
-
*
|
|
87
|
-
*
|
|
91
|
+
* exported for the commands that feed the resolver themselves (`rebase status`,
|
|
92
|
+
* `db branch switch`). The preflight that starts the container takes the URL
|
|
93
|
+
* from {@link resolveProjectDatabase}, so it starts one only when the order
|
|
94
|
+
* picks Docker.
|
|
88
95
|
*/
|
|
89
96
|
export declare function resolveComposeUrl(projectRoot: string, envFile: Record<string, string>): string | null;
|
|
90
97
|
/**
|
|
@@ -101,6 +108,15 @@ export declare function resolveComposeUrl(projectRoot: string, envFile: Record<s
|
|
|
101
108
|
* project, because help text must not fail to print over an unreadable `.env`.
|
|
102
109
|
*/
|
|
103
110
|
export declare function devDatabaseKind(projectRoot?: string | null): DevDatabase["kind"] | null;
|
|
111
|
+
/**
|
|
112
|
+
* The database this command resolves to in this project, decided without
|
|
113
|
+
* starting anything: the ordering in `resolve.ts`, fed from the project's
|
|
114
|
+
* `.env`, its branch pointer, its compose file and the last `rebase dev`.
|
|
115
|
+
*
|
|
116
|
+
* The one place those four are read for a resolution, so a command that asks
|
|
117
|
+
* "which database" and a command that then connects to it cannot disagree.
|
|
118
|
+
*/
|
|
119
|
+
export declare function resolveProjectDatabase(projectRoot: string, options?: Pick<PrepareOptions, "flagUrl" | "flagDocker" | "followLastDev">): DevDatabase;
|
|
104
120
|
/**
|
|
105
121
|
* The variable a driver child reads {@link devDatabaseKind} from.
|
|
106
122
|
*
|
|
@@ -108,6 +124,16 @@ export declare function devDatabaseKind(projectRoot?: string | null): DevDatabas
|
|
|
108
124
|
* a fourth reads it in another package.
|
|
109
125
|
*/
|
|
110
126
|
export declare const DEV_DATABASE_KIND_ENV = "REBASE_DEV_DATABASE_KIND";
|
|
127
|
+
/**
|
|
128
|
+
* The variable that tells a server started on a branch which database it was
|
|
129
|
+
* branched from.
|
|
130
|
+
*
|
|
131
|
+
* The branch registry lives in that database, and a branch database holds
|
|
132
|
+
* only a snapshot of it taken when it was copied — so a server on a branch
|
|
133
|
+
* keeps its branches, and copies new ones from, the database named here. A
|
|
134
|
+
* branch URL does not say which that is; only this checkout's `.env` does.
|
|
135
|
+
*/
|
|
136
|
+
export declare const BRANCH_PARENT_DATABASE_ENV = "REBASE_BRANCH_PARENT_DATABASE";
|
|
111
137
|
/**
|
|
112
138
|
* Resolve, start if needed, and describe the database for this command.
|
|
113
139
|
*
|