@rebasepro/cli 0.25.0-canary.ge06257e → 0.25.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/rebase.js +9 -5
- package/dist/{agent-setup-C83DXnlx.js → agent-setup-B5AKHOgt.js} +2 -2
- package/dist/{agent-setup-C83DXnlx.js.map → agent-setup-B5AKHOgt.js.map} +1 -1
- package/dist/{api-keys-Bc4FJKYp.js → api-keys-Tm7zvAPe.js} +27 -20
- package/dist/api-keys-Tm7zvAPe.js.map +1 -0
- package/dist/{apps-wG-ZTryA.js → apps-BsU_ILbP.js} +4 -4
- package/dist/{apps-wG-ZTryA.js.map → apps-BsU_ILbP.js.map} +1 -1
- package/dist/{args-CcBphFdj.js → args-BX0YD14a.js} +45 -16
- package/dist/args-BX0YD14a.js.map +1 -0
- package/dist/{atlas-dev-database-DeK3Sa4h.js → atlas-dev-database-OWtqnG7W.js} +2 -2
- package/dist/{atlas-dev-database-DeK3Sa4h.js.map → atlas-dev-database-OWtqnG7W.js.map} +1 -1
- package/dist/{auth-BL7bNQVu.js → auth-Vl2hFKXP.js} +77 -10
- package/dist/auth-Vl2hFKXP.js.map +1 -0
- package/dist/{branch-pointer-DTQNLFtb.js → branch-pointer-9DnwkFJJ.js} +1 -1
- package/dist/{build-h_7Re-67.js → build-BBgT8EJZ.js} +5 -5
- package/dist/{build-h_7Re-67.js.map → build-BBgT8EJZ.js.map} +1 -1
- package/dist/{bundle-CqURVN-K.js → bundle-DtCCCr4w.js} +4 -19
- package/dist/bundle-DtCCCr4w.js.map +1 -0
- package/dist/{cloud-DVV_5GPG.js → cloud-BpH0_bmV.js} +7 -7
- package/dist/{cloud-DVV_5GPG.js.map → cloud-BpH0_bmV.js.map} +1 -1
- package/dist/commands/api-keys.d.ts +4 -1
- package/dist/commands/auth.d.ts +3 -2
- package/dist/connection-banner-DbLJzWf4.js +128 -0
- package/dist/connection-banner-DbLJzWf4.js.map +1 -0
- package/dist/{console-Dw3HLdUs.js → console-CEGm0XNc.js} +4 -4
- package/dist/{console-Dw3HLdUs.js.map → console-CEGm0XNc.js.map} +1 -1
- package/dist/{context-BM-oOMNX.js → context-CyflUrKg.js} +2 -2
- package/dist/{context-BM-oOMNX.js.map → context-CyflUrKg.js.map} +1 -1
- package/dist/{daemon-entry-C6pxdily.js → daemon-entry-CbLRrgzy.js} +199 -3
- package/dist/daemon-entry-CbLRrgzy.js.map +1 -0
- package/dist/{db-Dkn9XhT-.js → db-CRNpA5mH.js} +171 -60
- package/dist/db-CRNpA5mH.js.map +1 -0
- package/dist/{derive-Dl0z2OWi.js → derive-CsoFGAnK.js} +28 -13
- package/dist/{derive-Dl0z2OWi.js.map → derive-CsoFGAnK.js.map} +1 -1
- package/dist/{dev-DAcZR4Pp.js → dev-C0pbMmhS.js} +6 -6
- package/dist/{dev-DAcZR4Pp.js.map → dev-C0pbMmhS.js.map} +1 -1
- package/dist/dev-db/connection-banner.d.ts +47 -0
- package/dist/dev-db/notification-proxy.d.ts +82 -1
- package/dist/{dev-preflight-CorMg7Ub.js → dev-preflight-unHpiYO1.js} +26 -3
- package/dist/dev-preflight-unHpiYO1.js.map +1 -0
- package/dist/{doctor-Ds_lzc9p.js → doctor-CATRdWm6.js} +4 -4
- package/dist/{doctor-Ds_lzc9p.js.map → doctor-CATRdWm6.js.map} +1 -1
- package/dist/{eject-CJHkfnat.js → eject-DuasTr-F.js} +2 -2
- package/dist/{eject-CJHkfnat.js.map → eject-DuasTr-F.js.map} +1 -1
- package/dist/{fold-static-BC9CYicw.js → fold-static-hDHtDfTq.js} +2 -2
- package/dist/{fold-static-BC9CYicw.js.map → fold-static-hDHtDfTq.js.map} +1 -1
- package/dist/{generate_sdk-kfkZmG-d.js → generate_sdk-Cvg5jjJF.js} +3 -3
- package/dist/{generate_sdk-kfkZmG-d.js.map → generate_sdk-Cvg5jjJF.js.map} +1 -1
- package/dist/index.es.js +34 -34
- package/dist/{init-CjoJoThg.js → init-BWAwHPtz.js} +8 -7
- package/dist/init-BWAwHPtz.js.map +1 -0
- package/dist/last-dev-database-c_dKz1S3.js +2 -0
- package/dist/{normalize-imports-BHxL2d05.js → normalize-imports-BoBVh5tU.js} +3 -3
- package/dist/{normalize-imports-BHxL2d05.js.map → normalize-imports-BoBVh5tU.js.map} +1 -1
- package/dist/precompress.d.ts +0 -7
- package/dist/{prepare-C1iaiTC4.js → prepare-DdnU70z8.js} +4 -4
- package/dist/prepare-DdnU70z8.js.map +1 -0
- package/dist/{pull-COpNJFzY.js → pull-C3PXduzl.js} +13 -40
- package/dist/pull-C3PXduzl.js.map +1 -0
- package/dist/resolve-CNQjsatH.js +2 -0
- package/dist/resources/derive.d.ts +9 -0
- package/dist/{resources-CLm6g0br.js → resources-BdNRDEAy.js} +3 -3
- package/dist/{resources-CLm6g0br.js.map → resources-BdNRDEAy.js.map} +1 -1
- package/dist/{schema-BKdh6UoE.js → schema-B0-p6T1O.js} +4 -4
- package/dist/{schema-BKdh6UoE.js.map → schema-B0-p6T1O.js.map} +1 -1
- package/dist/{skills-DEhYiPeO.js → skills-DfnTeyRz.js} +3 -3
- package/dist/{skills-DEhYiPeO.js.map → skills-DfnTeyRz.js.map} +1 -1
- package/dist/{start-BsPJedPQ.js → start-BmEZkS0n.js} +4 -4
- package/dist/{start-BsPJedPQ.js.map → start-BmEZkS0n.js.map} +1 -1
- package/dist/{status-D_B3tlDy.js → status-BJ26yZOo.js} +9 -9
- package/dist/{status-D_B3tlDy.js.map → status-BJ26yZOo.js.map} +1 -1
- package/dist/{storage-CtB8polS.js → storage-DKPUBTWG.js} +3 -3
- package/dist/{storage-CtB8polS.js.map → storage-DKPUBTWG.js.map} +1 -1
- package/dist/{telemetry-BYmPU-Ji.js → telemetry-m9czTNGR.js} +2 -2
- package/dist/{telemetry-BYmPU-Ji.js.map → telemetry-m9czTNGR.js.map} +1 -1
- package/dist/{upgrade-DbuKX9lv.js → upgrade-DjihWuZL.js} +5 -4
- package/dist/{upgrade-DbuKX9lv.js.map → upgrade-DjihWuZL.js.map} +1 -1
- package/dist/utils/args.d.ts +13 -0
- package/dist/utils/redact.d.ts +10 -0
- package/dist/utils/refusal.d.ts +18 -0
- package/package.json +7 -7
- package/templates/overlays/baas/README.md +2 -1
- package/templates/template/.env.example +6 -0
- package/templates/template/frontend/vite.config.ts +7 -73
- package/dist/api-keys-Bc4FJKYp.js.map +0 -1
- package/dist/args-CcBphFdj.js.map +0 -1
- package/dist/auth-BL7bNQVu.js.map +0 -1
- package/dist/bundle-CqURVN-K.js.map +0 -1
- package/dist/daemon-entry-C6pxdily.js.map +0 -1
- package/dist/db-Dkn9XhT-.js.map +0 -1
- package/dist/dev-preflight-CorMg7Ub.js.map +0 -1
- package/dist/init-CjoJoThg.js.map +0 -1
- package/dist/last-dev-database-BEBPfAMY.js +0 -2
- package/dist/prepare-C1iaiTC4.js.map +0 -1
- package/dist/pull-COpNJFzY.js.map +0 -1
- package/dist/resolve-b_p9bmdH.js +0 -2
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"init-BWAwHPtz.js","names":[],"sources":["../src/utils/invoked-command.ts","../src/commands/init.ts"],"sourcesContent":["/**\n * `rebase`, spelled the way the reader started this process.\n *\n * A hint that tells someone to run the CLI again — `init`'s \"re-run it with\n * --yes\" above all — used to print a bare `rebase …`. The quickstart's first\n * command is `pnpm dlx @rebasepro/cli init my-app`, and a reader who typed that\n * has no `rebase` on PATH: the one command the refusal offered was \"command not\n * found\". So the hint repeats the runner the reader used, at this CLI's exact\n * version, which is the version that printed it.\n *\n * Read from what the runners leave in the environment, never from guesses:\n *\n * - **`pnpm dlx`** sets `npm_config_user_agent` to `pnpm/…` and puts its cache\n * entry's `node_modules/.bin` on PATH, under `<cache>/dlx/<key>/<dir>/`. It\n * does not set `npm_command`.\n * - **`npx`** (`npm exec`) sets `npm_config_user_agent` to `npm/…`,\n * `npm_command` to `exec`, and puts `~/.npm/_npx/<key>/node_modules/.bin` on\n * PATH.\n *\n * Anything else — a global install, `pnpm exec rebase` in a project, a shell\n * alias — is answered with `rebase`, which is what those readers type.\n */\nimport path from \"path\";\n\nimport { cliVersion } from \"./version\";\n\n/** A PATH entry that is a `pnpm dlx` cache entry's bin directory. */\nconst PNPM_DLX_BIN = /[\\\\/]dlx[\\\\/][^\\\\/]+[\\\\/][^\\\\/]+[\\\\/]node_modules[\\\\/]\\.bin[\\\\/]?$/;\n\n/** A PATH entry that is an `npx` cache entry's bin directory. */\nconst NPX_BIN = /[\\\\/]_npx[\\\\/][^\\\\/]+[\\\\/]node_modules[\\\\/]\\.bin[\\\\/]?$/;\n\n/**\n * The words to print in place of `rebase`: `[\"pnpm\", \"dlx\", \"@rebasepro/cli@0.25.0\"]`,\n * `[\"npx\", \"-y\", \"@rebasepro/cli@0.25.0\"]` or `[\"rebase\"]`.\n *\n * `env` and `version` are parameters for the tests; a caller passes neither.\n */\nexport function invokedRebaseCommand(\n env: Readonly<Record<string, string | undefined>> = process.env,\n version: string = cliVersion()\n): string[] {\n const agent = env.npm_config_user_agent ?? \"\";\n const binDirs = (env.PATH ?? \"\").split(path.delimiter);\n // The version that printed the hint, so re-running it runs the same CLI. A\n // version this cannot read names the package alone, which is still a\n // command that works.\n const spec = /^\\d+\\.\\d+\\.\\d+/.test(version) ? `@rebasepro/cli@${version}` : \"@rebasepro/cli\";\n\n if (agent.startsWith(\"pnpm/\") && binDirs.some(dir => PNPM_DLX_BIN.test(dir))) {\n return [\"pnpm\", \"dlx\", spec];\n }\n if (agent.startsWith(\"npm/\") && env.npm_command === \"exec\" && binDirs.some(dir => NPX_BIN.test(dir))) {\n return [\"npx\", \"-y\", spec];\n }\n return [\"rebase\"];\n}\n","import inquirer from \"inquirer\";\nimport chalk from \"chalk\";\nimport path from \"path\";\nimport fs from \"fs\";\nimport net from \"net\";\nimport { promisify } from \"util\";\nimport { execa } from \"execa\";\nimport { cp } from \"fs/promises\";\nimport { fileURLToPath } from \"url\";\nimport crypto from \"crypto\";\nimport { detectPackageManager, getPMCommands } from \"../utils/package-manager\";\nimport { cliVersion } from \"../utils/version\";\nimport { invokedRebaseCommand } from \"../utils/invoked-command\";\nimport { dockerClaimedPorts } from \"../utils/dev-preflight\";\nimport type arg from \"arg\";\nimport { parseCommandArgs, wantsHelp } from \"../utils/args\";\nimport { resolveCloudUrl, writeLink } from \"./cloud/context\";\nimport type { PackageManager, PMCommands } from \"../utils/package-manager\";\nimport { promptForConsent } from \"../telemetry/consent\";\nimport { durationBucket, recordEvent } from \"../telemetry\";\nimport { parseEnvBoolean } from \"@rebasepro/types\";\nimport { AGENT_KEYS, AGENTS, type AgentKey, resolveAgentNames } from \"./skills\";\nimport { configureAgents, printAgentSetup, promptForAgents, retargetScaffoldMcpConfig } from \"./agent-setup\";\n\nconst access = promisify(fs.access);\n\n\n// Resolve template path relative to this file\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = path.dirname(__filename);\n\nfunction findParentDir(currentDir: string, targetName: string): string | null {\n const root = path.parse(currentDir).root;\n while (currentDir && currentDir !== root) {\n if (path.basename(currentDir) === targetName) {\n return currentDir;\n }\n currentDir = path.dirname(currentDir);\n }\n return null;\n}\n\nconst cliRoot = findParentDir(__dirname, \"cli\");\n\nconst PROJECT_NAME_RE = /^[a-z0-9][a-z0-9._-]*$/;\n\n/**\n * Every scaffolded file that carries a `{{PLACEHOLDER}}`.\n *\n * Exported because it is the single source of truth: `init.test.ts` reads it and\n * asserts that every template file containing `{{` appears here. It used to be a\n * local, with the test harness keeping a *second* copy — so the two drifted, and\n * a test built on the copy could not observe the production list at all.\n *\n * The drift shipped: `docker-compose.yml` arrived with the self-host work\n * (26fd5259c) and was added to neither, so every scaffolded project got a literal\n * `name: {{PROJECT_NAME}}`. In YAML `{{...}}` is a map, not a string, so the\n * documented `docker compose up` path failed on the file before doing anything:\n *\n * yaml: unmarshal errors: line 28: cannot unmarshal !!map into string\n */\nexport const TEMPLATE_PLACEHOLDER_FILES = [\n \"package.json\",\n \"frontend/package.json\",\n \"backend/package.json\",\n \"config/package.json\",\n \"frontend/index.html\",\n \"pnpm-workspace.yaml\",\n \"docker-compose.yml\",\n \"README.md\"\n];\n\n/** Returns an error message, or null when the name is a valid package name. */\nexport function validateProjectName(name: string): string | null {\n if (!name.trim()) return \"Project name is required\";\n if (!PROJECT_NAME_RE.test(name)) {\n return \"Project name must start with a lowercase letter or number and contain only lowercase letters, numbers, hyphens, dots, or underscores\";\n }\n return null;\n}\n\nexport type TemplatePreset = \"blog\" | \"ecommerce\" | \"blank\";\n\n/**\n * Whether to scaffold the admin panel alongside the backend.\n *\n * A boolean, not a named pair. It was `--flavor cms|baas`, and neither word\n * survived what they described: \"cms\" is a product category rather than a thing\n * this tool builds, and \"baas\" was the same value that used to appear as\n * `backend.mode` — which is now derived from whether collections are declared.\n * What is left is one question: does this project get an admin UI?\n */\nconst HEADLESS_CHOICES: Array<{ name: string; value: boolean; short: string }> = [\n { name: \"Backend + admin — API plus an admin UI, driven by collections you define (like Payload/Directus)\",\nvalue: false,\nshort: \"Backend + admin\" },\n { name: \"Backend only — headless API over your database. No collections, no UI (like Supabase)\",\nvalue: true,\nshort: \"Backend only\" }\n];\n\nconst PRESET_CHOICES: Array<{ name: string; value: TemplatePreset; short: string }> = [\n { name: \"Blog — Posts, Authors, Tags (with markdown editor)\",\nvalue: \"blog\",\nshort: \"Blog\" },\n { name: \"E-commerce — Products, Categories, Orders\",\nvalue: \"ecommerce\",\nshort: \"E-commerce\" },\n { name: \"Blank — Empty project, just authentication\",\nvalue: \"blank\",\nshort: \"Blank\" }\n];\n\nexport interface InitOptions {\n projectName: string;\n git: boolean;\n installDeps: boolean;\n targetDirectory: string;\n templateDirectory: string;\n databaseUrl?: string;\n introspect?: boolean;\n /** Starter template preset. */\n preset: TemplatePreset;\n /** Whether `preset` came from an explicit --template rather than the default. */\n explicitPreset?: boolean;\n /** Scaffold the backend alone, with no admin panel and no collections. */\n headless: boolean;\n /** Detected package manager (pnpm or npm). */\n pm: PackageManager;\n /** Command helpers for the detected PM. */\n pmCommands: PMCommands;\n /** Cloud project slug (its subdomain) to link the scaffold to. */\n cloudProject?: string;\n /** One-time setup key that authenticates the cloud link. */\n setupKey?: string;\n /** Control-plane URL the setup key is redeemed against. */\n cloudUrl?: string;\n /** AI coding agents to install skills and the MCP server for. */\n agents: AgentKey[];\n /** Whoever reads the closing steps can answer a prompt (a TTY, not --yes). */\n interactive?: boolean;\n}\n\nexport interface BuildQuestionsParams {\n nameArg?: string;\n templateArg?: TemplatePreset;\n headlessArg?: boolean;\n hasGitFlag: boolean;\n hasInstallFlag: boolean;\n pm: PackageManager;\n}\n\n/**\n * Builds the interactive prompt questions for `rebase init`.\n * Exported for testability — all prompt `type` values must match\n * types registered by the installed version of inquirer.\n */\n/**\n * The questions this command asks, as inquirer types them.\n *\n * Declared, rather than left as `Record<string, unknown>` and asserted into\n * `Parameters<typeof inquirer.prompt>[0]` at the call. Inquirer infers the\n * answer shape from the array it is handed, so an array of open records matches\n * none of its overloads — which is the refusal that assertion was there to get\n * past, at the cost of every question in this file being unchecked: a\n * misspelled `mesage`, a `choices` on a `confirm`, a `default` of the wrong\n * type for its question kind all compiled.\n */\nexport type InitQuestion =\n | { type: \"input\"; name: string; message: string; default: string; validate?: (input: string) => string | true }\n | { type: \"confirm\"; name: string; message: string; default: boolean; when?: (answers: Record<string, unknown>) => boolean }\n | { type: \"select\"; name: string; message: string; choices: ReadonlyArray<{ name: string; value: unknown; short: string }>; default: unknown; when?: (answers: Record<string, unknown>) => boolean };\n\n/**\n * Why `input` cannot be this project's `DATABASE_URL`, or null when it can.\n *\n * The runtime refuses anything that is not a URL, and only a Postgres one can\n * work, so `init` refuses the same — before the scaffold is written around it.\n * The value itself is never repeated back: it may hold a password.\n */\nexport function databaseUrlProblem(input: string): string | null {\n const expected = \"(expected postgresql://user:password@host:5432/database)\";\n let parsed: URL;\n try {\n parsed = new URL(input.trim());\n } catch {\n return `not a PostgreSQL connection string ${expected}`;\n }\n if (parsed.protocol !== \"postgresql:\" && parsed.protocol !== \"postgres:\") {\n return `not a PostgreSQL connection string: it starts with ${parsed.protocol}// ${expected}`;\n }\n return null;\n}\n\n/** One line saying `dir` already holds files, or null when it may be scaffolded into. */\nfunction scaffoldConflictLine(dir: string): string | null {\n const conflicts = scaffoldDirectoryConflicts(dir);\n if (conflicts.length === 0) return null;\n const shown = conflicts.slice(0, 4).join(\", \") + (conflicts.length > 4 ? `, and ${conflicts.length - 4} more` : \"\");\n return `\"${path.basename(dir)}\" already holds files a new project would overwrite or mix with (${shown}). Choose another name.`;\n}\n\nexport function buildInitQuestions(params: BuildQuestionsParams): InitQuestion[] {\n const { nameArg, templateArg, headlessArg, hasGitFlag, hasInstallFlag, pm } = params;\n const questions: InitQuestion[] = [];\n\n if (!nameArg) {\n questions.push({\n type: \"input\",\n name: \"projectName\",\n message: \"Project name:\",\n default: \"my-app\",\n // The directory is checked here, while the name can still be\n // changed, rather than after every other question is answered.\n validate: (input: string) => validateProjectName(input)\n ?? scaffoldConflictLine(path.resolve(process.cwd(), input))\n ?? true\n });\n }\n\n if (headlessArg === undefined) {\n questions.push({\n type: \"select\",\n name: \"headless\",\n message: \"What do you want to build?\",\n choices: HEADLESS_CHOICES,\n default: false\n });\n }\n\n if (!templateArg) {\n questions.push({\n type: \"select\",\n name: \"preset\",\n message: \"Choose a starter template:\",\n choices: PRESET_CHOICES,\n default: \"blog\",\n // A headless project has no collection files, so a preset is moot.\n when: (answers: Record<string, unknown>) => !(headlessArg ?? answers.headless)\n });\n }\n\n if (!hasGitFlag) {\n questions.push({\n type: \"confirm\",\n name: \"git\",\n message: \"Initialize a git repository?\",\n default: true\n });\n }\n\n if (!hasInstallFlag) {\n questions.push({\n type: \"confirm\",\n name: \"installDeps\",\n message: `Install dependencies with ${pm}?`,\n default: true\n });\n }\n\n questions.push({\n type: \"input\",\n name: \"databaseUrl\",\n message: \"PostgreSQL connection string (blank = a managed database for this project, no setup):\",\n default: \"\",\n validate: (input: string) => {\n if (!input.trim()) return true;\n if (/[\\r\\n]/.test(input)) {\n return \"Database URL cannot contain newline characters.\";\n }\n // `n` here was an answer to the yes/no question the reader thought\n // this was, and it became DATABASE_URL=n.\n const problem = databaseUrlProblem(input);\n return problem ? `That is ${problem}. Leave it blank for the managed database.` : true;\n }\n });\n\n questions.push({\n type: \"confirm\",\n name: \"introspect\",\n message: \"Would you like to introspect this database to automatically generate collections?\",\n default: true,\n when: (answers: Record<string, unknown>) => !!(answers.databaseUrl as string)?.trim()\n });\n\n return questions;\n}\n\n/**\n * The `cd` a user must type to enter the new project.\n *\n * Not the project's basename: `init apps/my-app` has to say `cd apps/my-app`,\n * and `init .` returns \"\" because they are already in the project.\n */\nexport function formatCdTarget(cwd: string, targetDirectory: string): string {\n return path.relative(cwd, targetDirectory);\n}\n\n/** Help for `rebase init` — the flags were previously only discoverable by\n * triggering the non-TTY error. */\nexport function printInitHelp(): void {\n console.log(`\n${chalk.bold(\"rebase init\")} — Create a new Rebase project\n\n${chalk.bold(\"Usage\")}\n rebase init ${chalk.blue(\"[name]\")} [options]\n\n ${chalk.gray(\"The name may be a nested path (apps/my-app) or \\\".\\\" for the current directory.\")}\n ${chalk.gray(\"Asks when omitted; defaults to \\\"my-app\\\", and takes it without asking under --yes.\")}\n\n${chalk.bold(\"Options\")}\n ${chalk.blue(\"-t, --template\")} ${chalk.gray(\"<preset>\")} blog | ecommerce | blank ${chalk.gray(\"(default: blog)\")}\n ${chalk.blue(\"--headless\")} Backend only — no admin panel, no collections\n ${chalk.blue(\"-y, --yes\")} Never prompt ${chalk.gray(\"(required for CI / non-TTY)\")}: take every default,\n ${chalk.gray(\"except that git init and the install are skipped unless\")}\n ${chalk.gray(\"--git / --install is passed\")}\n ${chalk.blue(\"-i, --install\")} Install dependencies after scaffolding.\n ${chalk.gray(\"Without it, init asks (default yes); under --yes, no install\")}\n ${chalk.blue(\"--no-install\")} Install nothing, and do not ask\n ${chalk.blue(\"-g, --git\")} Initialize a git repository and make an initial commit.\n ${chalk.gray(\"Without it, init asks (default yes); under --yes, no repository\")}\n ${chalk.blue(\"--database-url\")} ${chalk.gray(\"<url>\")} Use an existing database instead of the generated one\n ${chalk.blue(\"--introspect\")} Generate collections from that database ${chalk.gray(\"(implies --template blank; needs --install)\")}\n ${chalk.blue(\"--project\")} ${chalk.gray(\"<slug>\")} Link the scaffold to a Rebase Cloud project\n ${chalk.blue(\"--setup-key\")} ${chalk.gray(\"<key>\")} One-time key authenticating the cloud link ${chalk.gray(\"(use with --project)\")}\n ${chalk.blue(\"-a, --agent\")} ${chalk.gray(\"<name>\")} Set up an AI coding agent: Rebase skills + the MCP server.\n ${chalk.gray(`Repeatable or comma-separated: ${AGENT_KEYS.join(\" | \")} | all`)}\n ${chalk.gray(\"Asks when omitted, pre-ticking the agents installed on this\")}\n ${chalk.gray(\"machine; under --yes, none unless named.\")}\n\n${chalk.bold(\"What gets scaffolded\")}\n ${chalk.gray(\"default\")} Backend + an admin UI, driven by collections you define ${chalk.gray(\"(like Payload/Directus)\")}\n ${chalk.blue(\"--headless\")} Backend only, over your existing database ${chalk.gray(\"(like Supabase)\")}\n ${chalk.gray(\"--template has no effect: there are no collections to seed.\")}\n\n${chalk.bold(\"Examples\")}\n ${chalk.gray(\"$\")} rebase init my-shop --template ecommerce --install\n ${chalk.gray(\"$\")} rebase init my-api --headless --yes\n ${chalk.gray(\"$\")} rebase init . --yes --git\n ${chalk.gray(\"$\")} rebase init my-app --yes --agent claude,cursor\n`);\n}\n\nexport async function createRebaseApp(rawArgs: string[]) {\n if (wantsHelp(rawArgs)) {\n printInitHelp();\n return;\n }\n\n console.log(`\n${chalk.bold(\"Rebase\")} — Create a new project 🚀\n`);\n\n const pm = detectPackageManager();\n const options = await promptForOptions(rawArgs, pm);\n await createProject(options);\n}\n\n/** The flags `rebase init` takes. */\nexport const INIT_FLAGS = {\n \"--git\": Boolean,\n \"--install\": Boolean,\n // The default, spelled out. `arg` has no negation, so without this entry a\n // CI script that writes what reads naturally — `rebase init app --yes\n // --no-install` — died on \"Unknown or unexpected option\", and both the\n // README and the Quickstart documented a flag the parser rejected. When\n // both are passed, the explicit \"no\" wins.\n \"--no-install\": Boolean,\n \"--database-url\": String,\n \"--introspect\": Boolean,\n \"--template\": String,\n \"--headless\": Boolean,\n \"--project\": String,\n \"--setup-key\": String,\n \"--agent\": [String],\n \"--yes\": Boolean,\n \"-a\": \"--agent\",\n \"-g\": \"--git\",\n \"-i\": \"--install\",\n \"-t\": \"--template\",\n \"-y\": \"--yes\"\n} satisfies arg.Spec;\n\nasync function promptForOptions(rawArgs: string[], pm: PackageManager): Promise<InitOptions> {\n const { flags: args, positionals } = parseCommandArgs({\n spec: INIT_FLAGS,\n rawArgs,\n commandWords: 1,\n command: \"init\",\n maxPositionals: 1\n });\n\n // The first positional arg after \"init\" is the project name. Under the old\n // permissive parse a mistyped flag took that slot — `rebase init --headles\n // shop` scaffolded a directory called `--headles` — and the misspelling that\n // caused it was accepted in silence.\n const nameArg = positionals[0];\n const isNonInteractive = args[\"--yes\"] || false;\n\n // The interactive prompt validates typed names; a name passed as an\n // argument must pass the same check or it becomes an invalid package.json\n // \"name\" that only fails later, at install time. Validate the basename so\n // nested paths (\"apps/my-app\") and \".\" still work.\n if (nameArg) {\n const resolvedName = path.basename(path.resolve(process.cwd(), nameArg));\n const nameError = validateProjectName(resolvedName);\n if (nameError) {\n console.error(chalk.red(`Invalid project name \"${resolvedName}\": ${nameError}`));\n process.exit(1);\n }\n // Before any question: the answer cannot change, so asking first only\n // wastes every answer the reader gives.\n const targetDirectory = path.resolve(process.cwd(), nameArg);\n const conflicts = scaffoldDirectoryConflicts(targetDirectory);\n if (conflicts.length > 0) {\n for (const line of scaffoldConflictMessage(resolvedName, formatCdTarget(process.cwd(), targetDirectory), conflicts)) {\n console.error(line);\n }\n process.exit(1);\n }\n }\n\n const databaseUrlArg = args[\"--database-url\"]?.trim();\n if (databaseUrlArg) {\n const problem = databaseUrlProblem(databaseUrlArg);\n if (problem) {\n console.error(chalk.red(`✗ --database-url is ${problem}.`));\n console.error(chalk.gray(\" Leave the flag out to use a managed database for this project, with no setup.\"));\n process.exit(1);\n }\n }\n\n const templateArg = args[\"--template\"] as TemplatePreset | undefined;\n if (templateArg && !PRESET_CHOICES.some(p => p.value === templateArg)) {\n console.error(chalk.red(`Unknown template \"${templateArg}\". Available: ${PRESET_CHOICES.map(p => p.value).join(\", \")}`));\n process.exit(1);\n }\n\n const headlessArg = args[\"--headless\"] === true ? true : undefined;\n\n // Resolved before any question, so a misspelt agent costs nothing.\n const namedAgents = resolveAgentNames(args[\"--agent\"] ?? []);\n\n // `--no-install` is an explicit \"no\", not merely the absence of a \"yes\": it\n // suppresses the interactive question too, so `rebase init app --no-install`\n // asks one fewer thing rather than asking and ignoring the answer.\n const noInstall = args[\"--no-install\"] === true;\n const installFlag = noInstall ? false : (args[\"--install\"] ?? false);\n const hasInstallFlag = noInstall || args[\"--install\"] === true;\n\n if (isNonInteractive) {\n // Non-interactive: nothing to ask, so the prompt's default stands in.\n const projectName = nameArg || \"my-app\";\n const targetDirectory = path.resolve(process.cwd(), projectName);\n const templateDirectory = path.resolve(cliRoot!, \"templates\", \"template\");\n const pmCommands = getPMCommands(pm);\n\n return {\n projectName: path.basename(targetDirectory),\n git: args[\"--git\"] ?? false,\n installDeps: installFlag,\n targetDirectory,\n templateDirectory,\n databaseUrl: args[\"--database-url\"] || undefined,\n introspect: args[\"--introspect\"] || false,\n preset: templateArg || \"blog\",\n explicitPreset: !!templateArg,\n headless: headlessArg ?? false,\n pm,\n pmCommands,\n cloudProject: args[\"--project\"] || undefined,\n setupKey: args[\"--setup-key\"] || undefined,\n cloudUrl: resolveCloudUrl(rawArgs),\n // Nothing is guessed without a person to confirm it: the machine's\n // agents are only ever a pre-ticked default in the prompt.\n agents: namedAgents ?? [],\n interactive: false\n };\n }\n\n // A non-interactive stdin (CI, a pipe, no TTY) can't answer prompts:\n // inquirer either blocks forever waiting for input or aborts with a raw\n // ExitPromptError stack trace. Fail fast with actionable guidance instead.\n if (!process.stdin.isTTY) {\n const rerun = initRerunCommand(rawArgs, invokedRebaseCommand());\n console.error(chalk.red(\"Cannot prompt: this is a non-interactive terminal (no TTY).\"));\n console.error(chalk.yellow(\" Re-run it with --yes, which takes the default for anything not passed as a flag:\"));\n console.error(chalk.yellow(` ${rerun.command}`));\n if (rerun.redacted.length > 0) {\n console.error(chalk.gray(` ${capitalize(joinWords(rerun.redacted))} ${rerun.redacted.length === 1 ? \"is\" : \"are\"} left out above; put yours back in.`));\n }\n console.error(chalk.gray(\" Options: --template <blog|ecommerce|blank> --headless --database-url <url> --install --git --agent <name>\"));\n process.exit(1);\n }\n\n const questions = buildInitQuestions({\n nameArg,\n templateArg,\n headlessArg,\n hasGitFlag: !!args[\"--git\"],\n hasInstallFlag,\n pm\n });\n\n\n const answers = await inquirer.prompt(questions);\n const agents = namedAgents ?? await promptForAgents();\n\n const targetDirectory = path.resolve(process.cwd(), nameArg || answers.projectName);\n const projectName = path.basename(targetDirectory);\n const templateDirectory = path.resolve(cliRoot!, \"templates\", \"template\");\n const pmCommands = getPMCommands(pm);\n\n return {\n projectName,\n git: args[\"--git\"] || answers.git || false,\n installDeps: noInstall ? false : (args[\"--install\"] || answers.installDeps || false),\n targetDirectory,\n templateDirectory,\n databaseUrl: (answers.databaseUrl as string)?.trim() || undefined,\n introspect: answers.introspect || false,\n preset: templateArg || (answers.preset as TemplatePreset) || \"blog\",\n // Only a flag is \"explicit\" here: the interactive path never asks for a\n // preset once baas is chosen, so it can't produce a conflicting answer.\n explicitPreset: !!templateArg,\n headless: headlessArg ?? Boolean(answers.headless),\n pm,\n pmCommands,\n cloudProject: args[\"--project\"] || undefined,\n setupKey: args[\"--setup-key\"] || undefined,\n cloudUrl: resolveCloudUrl(rawArgs),\n agents,\n interactive: true\n };\n}\n\n/**\n * The command a reader with no terminal is told to run instead: the one they\n * typed, as they typed it, with `--yes`.\n *\n * It used to be one fixed example, `rebase init <name> --yes --template blog`,\n * whatever had been passed — so `init intro-app --headless --database-url …\n * --introspect --install` was told to scaffold a blog. Every word after `init`\n * is repeated in its place, and `invocation` is `rebase` as the reader started\n * it (see `invokedRebaseCommand`).\n *\n * Two values are never repeated, because a hint is printed to logs: the\n * password in `--database-url` (`databaseUrlProblem` keeps the same rule) and\n * the `--setup-key`. Each is replaced with a placeholder and named in\n * `redacted`, so the caller can say the copy is not runnable as printed.\n *\n * Exported for its test.\n */\nexport function initRerunCommand(\n rawArgs: readonly string[],\n invocation: readonly string[]\n): { command: string; redacted: string[] } {\n const rest = rawArgs.slice(2);\n const commandWord = rest.indexOf(\"init\");\n const redacted = new Set<string>();\n\n const hide = (flag: string, value: string): string => {\n if (flag === \"--setup-key\") {\n redacted.add(\"the setup key\");\n return \"<setup-key>\";\n }\n if (flag === \"--database-url\") {\n return value.replace(/^([a-z][a-z0-9+.-]*:\\/\\/[^:@/?#]*):([^@/?#]+)@/i, (_all, before: string) => {\n redacted.add(\"the password in --database-url\");\n return `${before}:<password>@`;\n });\n }\n return value;\n };\n\n const words: string[] = [];\n for (let i = 0; i < rest.length; i++) {\n if (i === commandWord) continue;\n const word = rest[i];\n const inline = /^(--[a-z-]+)=([\\s\\S]*)$/.exec(word);\n if (inline) {\n words.push(`${inline[1]}=${shellWord(hide(inline[1], inline[2]))}`);\n continue;\n }\n const previous = i > 0 && i - 1 !== commandWord ? rest[i - 1] : \"\";\n words.push(shellWord(previous.startsWith(\"--\") ? hide(previous, word) : word));\n }\n\n return {\n command: [...invocation, \"init\", ...words, \"--yes\"].join(\" \"),\n redacted: [...redacted]\n };\n}\n\n/** One word for a POSIX shell: bare when nothing in it is special, else single-quoted. */\nfunction shellWord(word: string): string {\n return /^[\\w@%+=:,./-]+$/.test(word) ? word : `'${word.replace(/'/g, \"'\\\\''\")}'`;\n}\n\nfunction joinWords(items: readonly string[]): string {\n return items.length <= 1 ? items.join(\"\") : `${items.slice(0, -1).join(\", \")} and ${items[items.length - 1]}`;\n}\n\nfunction capitalize(text: string): string {\n return text.charAt(0).toUpperCase() + text.slice(1);\n}\n\n/**\n * Redeem the one-time setup key from the console's setup page and write the\n * `.rebase/cloud.json` link into the scaffold, so `rebase cloud deploy` etc.\n * work in the new directory with no further flags. `--project` carries the\n * project's slug (its subdomain, as shown in console URLs); the control plane\n * also accepts a raw id for old copies of the command.\n *\n * Best-effort by design: a failed link must never fail the scaffold, so every\n * exit path other than success is a warning plus instructions to link later.\n */\nasync function linkScaffoldToCloud(options: InitOptions): Promise<void> {\n if (!options.cloudProject && !options.setupKey) return;\n\n const linkLater = `Link it later with ${chalk.bold(\"rebase cloud login\")} then ${chalk.bold(\"rebase cloud link\")}.`;\n if (!options.cloudProject || !options.setupKey) {\n console.warn(chalk.yellow(\" --project and --setup-key go together; skipping the cloud link.\"));\n console.warn(chalk.yellow(` ${linkLater}`));\n return;\n }\n\n try {\n const res = await fetch(`${options.cloudUrl}/api/functions/setup-key/validate`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ projectId: options.cloudProject,\nsetupKey: options.setupKey })\n });\n const body = (await res.json().catch(() => ({}))) as {\n error?: { message?: string };\n project?: { id?: string | number; subdomain?: string; name?: string };\n };\n if (!res.ok || body.project?.id === undefined) {\n console.warn(chalk.yellow(` Could not verify the setup key: ${body.error?.message || res.statusText}`));\n console.warn(chalk.yellow(` ${linkLater}`));\n return;\n }\n writeLink(\n {\n url: String(options.cloudUrl),\n projectId: String(body.project.id),\n slug: body.project.subdomain,\n projectName: body.project.name\n },\n options.targetDirectory\n );\n console.log(\"\");\n console.log(` ${chalk.green(\"✓\")} Linked to cloud project ${chalk.bold(body.project.subdomain ?? options.cloudProject)}`);\n } catch (e) {\n console.warn(chalk.yellow(` Could not reach the control plane: ${e instanceof Error ? e.message : String(e)}`));\n console.warn(chalk.yellow(` ${linkLater}`));\n }\n}\n\n/**\n * Make the initial commit, after everything that writes into the project has run.\n *\n * It used to happen immediately after `git init`, which is before dependency\n * installation and before introspection — so `init --git --install` ended on a\n * dirty tree whose only untracked file was `pnpm-lock.yaml`. A lockfile is\n * precisely the thing that should be in a project's first commit, and a brand\n * new scaffold whose first `git status` is dirty invites the reader to conclude\n * the lockfile is deliberately ignored and never commit it at all.\n *\n * Introspection has the same shape: it generates `config/collections` and\n * `schema.generated.ts`, which belong in the commit describing the scaffold that\n * produced them.\n *\n * `git init` stays where it was. Creating the repository early costs nothing and\n * means a failed install still leaves the user a repository to commit into.\n */\nasync function commitScaffold(targetDirectory: string): Promise<void> {\n try {\n // Leaving the tree uncommitted makes the very first `git diff`\n // useless and hides the scaffold in a wall of untracked files.\n // .gitignore is already in place, so .env is never committed.\n await execa(\"git\", [\"add\", \"-A\"], { cwd: targetDirectory });\n // A machine with no user.email configured cannot commit at all.\n // Supply an identity only in that case, so a configured user still\n // authors their own initial commit.\n let identity: Record<string, string> = {};\n try {\n await execa(\"git\", [\"config\", \"user.email\"], { cwd: targetDirectory });\n } catch {\n identity = {\n GIT_AUTHOR_NAME: \"Rebase\",\n GIT_AUTHOR_EMAIL: \"noreply@rebase.pro\",\n GIT_COMMITTER_NAME: \"Rebase\",\n GIT_COMMITTER_EMAIL: \"noreply@rebase.pro\"\n };\n }\n await execa(\"git\", [\"commit\", \"-m\", \"Initial commit from Rebase\"], {\n cwd: targetDirectory,\n env: identity\n });\n } catch {\n console.warn(chalk.yellow(\" Warning: Failed to create the initial commit\"));\n }\n}\n\n/** What the closing \"Next steps\" depend on: the project's shape, and what `init` managed to do. */\nexport interface InitNextStepsInput {\n headless: boolean;\n /** `--database-url` was given, so `.env` names the user's own database. */\n ownDatabase: boolean;\n /** Introspection was asked for. */\n introspect: boolean;\n /** Introspection ran and produced collections. */\n introspected: boolean;\n /** Dependencies were installed. */\n installed: boolean;\n /** What the reader types to enter the project; \"\" when they are in it. */\n cdTarget: string;\n pm: PMCommands;\n}\n\n/**\n * The \"Next steps\" `init` closes on, every command one that works as printed.\n *\n * Each flavour has its own steps, and the branch for one flavour must not run\n * for another. The `--database-url` branch was checked before the headless one,\n * so `init --headless --database-url` — the documented BaaS path — opened on\n * `pnpm run db:push`: a script the headless scaffold does not have, for a\n * schema it does not declare (its tables are the developer's own), with an\n * Atlas it does not install. `init-next-steps.test.ts` holds every script\n * named here to the scaffold it is printed for.\n */\nexport function initNextSteps(input: InitNextStepsInput): string[] {\n const { headless, ownDatabase, introspect, introspected, installed, cdTarget, pm } = input;\n const cmd = (parts: string[]): string => ` ${chalk.cyan(parts.join(\" \"))}`;\n const note = (text: string): string => chalk.gray(` # ${text}`);\n const runDev = cmd(pm.run(\"dev\"));\n const lines: string[] = [];\n\n if (cdTarget) lines.push(` ${chalk.cyan(\"cd\")} ${cdTarget}`);\n if (!installed) lines.push(cmd(pm.install));\n lines.push(\"\");\n\n if (introspected) {\n lines.push(note(\"Database has been introspected & collections generated!\"));\n lines.push(note(headless\n ? \"Start the API over it:\"\n : \"Start the development server (frontend + backend):\"));\n lines.push(runDev);\n } else if (introspect) {\n // Introspection was requested but did not run. Point at the steps that\n // finish the job rather than claiming collections exist.\n lines.push(note(\"Introspection did not run — finish it with:\"));\n lines.push(cmd(pm.exec(\"rebase\", [\"schema\", \"introspect\", \"--force\"])));\n lines.push(cmd(pm.exec(\"rebase\", [\"schema\", \"generate\"])));\n lines.push(\"\");\n lines.push(note(\"Then start the development server:\"));\n lines.push(runDev);\n } else if (headless) {\n // One step, then the thing that is actually this mode's job. `dev`\n // starts the database itself when none is named; a headless project has\n // no collections, so there is no schema to push and the tables are the\n // developer's to create — on their own database or the managed one.\n lines.push(note(ownDatabase\n ? \"Your database is configured in .env (DATABASE_URL).\"\n : \"A local database configuration has been generated in .env.\"));\n lines.push(note(ownDatabase\n ? \"1. Start the API over it:\"\n : \"1. Start everything — the database comes up with it:\"));\n lines.push(runDev);\n lines.push(\"\");\n lines.push(note(ownDatabase\n ? \"2. Serve your tables.\"\n : \"2. Create your tables (migrations, SQL, any tool you like).\"));\n lines.push(note(\" A table is served once row-level security is enabled on it, and a\"));\n lines.push(note(\" policy is what opens its rows:\"));\n lines.push(cmd([\"ALTER TABLE your_table ENABLE ROW LEVEL SECURITY;\"]));\n // The column side is cast, as the policies Rebase generates cast it:\n // `rebase.uid()` is text, so `user_id = rebase.uid()` on a uuid column\n // failed with \"operator does not exist: uuid = text\".\n lines.push(cmd([\"-- rebase.uid() is text: the cast makes this work for a uuid or integer column too.\"]));\n lines.push(cmd([\"CREATE POLICY your_table_owner ON your_table FOR ALL USING (user_id::text = rebase.uid());\"]));\n lines.push(note(\" The API logs any table it skips, and why.\"));\n } else if (ownDatabase) {\n lines.push(note(\"Your custom database is configured in .env.\"));\n lines.push(note(\"If the database is empty, push the Rebase schema to initialize it:\"));\n lines.push(cmd(pm.run(\"db:push\")));\n lines.push(\"\");\n lines.push(note(\"Then start the development server:\"));\n lines.push(runDev);\n } else {\n // The whole first run, in one command.\n //\n // Deliberately does NOT say \"container\". This branch is the one a new\n // project takes, and it is precisely the branch with no container in it:\n // `.env` ships DATABASE_URL commented out, so `dev` starts the managed\n // PGlite database rather than Docker. Saying otherwise sent a reader who\n // had no Docker installed off to install it, to satisfy a step that was\n // never going to run — and made the whole quickstart look like it\n // required a daemon it does not.\n //\n // \"schema\" covers two things `dev` does before serving: generating the\n // Drizzle schema from the collections, and applying the collections to\n // the database. The push-and-Docker steps are documented under\n // `rebase dev --help`.\n lines.push(note(\"A local database configuration has been generated in .env.\"));\n lines.push(note(\"Start everything — the database and schema are set up for you,\"));\n lines.push(note(\"with no Docker and nothing else to install:\"));\n lines.push(runDev);\n lines.push(\"\");\n lines.push(note(\"To use a Postgres of your own instead, uncomment DATABASE_URL in .env.\"));\n }\n\n lines.push(\"\");\n // Both halves of the first-admin rule, once, here.\n //\n // `init` has just written REBASE_ADMIN_EMAIL and a generated password into\n // `.env`, and they belong to the *production* boot — the compose stack, where\n // the first-registration window is shut. `rebase dev` reads the same file, so\n // without this line the honest reading of that `.env` is \"these are my\n // credentials\", and the first documented step (register, become the admin)\n // reads as broken.\n lines.push(note(\"Sign in: the first account you register becomes the admin.\"));\n lines.push(note(\"In production (docker compose) that window is shut and the account named by\"));\n lines.push(note(\"REBASE_ADMIN_EMAIL in .env is created at boot instead.\"));\n\n lines.push(\"\");\n // `--headless --introspect` had these two claims fighting: the step above\n // announced \"collections generated!\" and this line then said there are none.\n // Both were reachable at once, and the reader has to believe one of them.\n lines.push(headless\n ? introspected\n ? chalk.gray(\"This starts a headless API (Hono + PostgreSQL) over the collections just \")\n + chalk.gray(\"generated from your database in config/collections. Edit them to change what the \")\n + chalk.gray(\"API exposes; docs are at /api/swagger.\")\n : chalk.gray(\"This starts a headless API (Hono + PostgreSQL). There are no collection files: \")\n + chalk.gray(\"the API is derived from your database schema. Once it serves a table, docs are at /api/swagger.\")\n : chalk.gray(\"This starts both the backend (Hono + PostgreSQL)\")\n + chalk.gray(\" and the frontend (Vite + React) concurrently.\"));\n return lines;\n}\n\n/**\n * The closing \"AI Agent Skills\" lines, each command one its reader can run.\n *\n * `rebase skills install` with no `--agent` prompts, and refuses without a\n * TTY — so printing it bare at the end of `rebase init --yes`, the agent and\n * CI path, told that reader to run a command that answers \"Cannot prompt\".\n * Named agents are spelled out (a refresh must not depend on detection, which\n * never finds Copilot); otherwise a TTY reader gets the prompt, anyone else\n * the `--agent` form.\n */\nexport function agentNextSteps(agents: readonly AgentKey[], interactive: boolean, pm: PMCommands): string[] {\n const both = (args: string[]): string =>\n ` ${chalk.cyan([\"rebase\", ...args].join(\" \"))} ${chalk.gray(\"or\")} ${chalk.cyan(pm.exec(\"rebase\", args).join(\" \"))}`;\n if (agents.length > 0) {\n return [\n chalk.gray(` Set up for ${agents.map(a => AGENTS[a].label).join(\", \")}. Commit the files to share them;`),\n chalk.gray(\" after a Rebase upgrade, refresh the skills with:\"),\n \"\",\n both([\"skills\", \"install\", \"--agent\", agents.join(\",\")])\n ];\n }\n if (interactive) {\n return [\n chalk.gray(\" Install Rebase agent skills, and the Rebase MCP server, for your AI coding assistant:\"),\n \"\",\n both([\"skills\", \"install\", \"--mcp\"])\n ];\n }\n return [\n chalk.gray(\" Install Rebase agent skills, and the Rebase MCP server, for your AI coding assistant —\"),\n chalk.gray(` name it: ${AGENT_KEYS.join(\", \")}, or all.`),\n \"\",\n both([\"skills\", \"install\", \"--agent\", \"<agent>\", \"--mcp\"])\n ];\n}\n\n/**\n * What a directory may already hold for `rebase init` to scaffold into it.\n *\n * `git clone` of an empty repository, or `git init`, leaves exactly `.git` —\n * and `rebase init . --yes --git` is the help's own example. Counting every\n * entry as \"not empty\" refused the one directory that example is most likely\n * to be typed in. The entries here are a repository's metadata, the files\n * operating systems drop into any folder, and a licence: none of them is\n * something the scaffold writes (`init.test.ts` holds the template to that),\n * so tolerating them can never overwrite anything. Everything else is a\n * conflict and is named, never merged into.\n */\nexport const SCAFFOLD_TOLERATED_ENTRIES: ReadonlySet<string> = new Set([\n \".git\", \".hg\", \".svn\", \".gitattributes\",\n \".DS_Store\", \"Thumbs.db\", \"desktop.ini\",\n \"LICENSE\", \"LICENSE.md\", \"LICENSE.txt\"\n]);\n\n/** Entries in `dir` the scaffold would overwrite or mix with — `[]` when it may proceed. */\nexport function scaffoldDirectoryConflicts(dir: string): string[] {\n if (!fs.existsSync(dir)) return [];\n return fs.readdirSync(dir, { withFileTypes: true })\n .filter(entry => !SCAFFOLD_TOLERATED_ENTRIES.has(entry.name))\n .map(entry => entry.isDirectory() ? `${entry.name}/` : entry.name)\n .sort();\n}\n\n/**\n * The refusal for a directory that already holds files, with the two ways forward.\n *\n * `invocation` is `rebase` as the reader started it, so the command offered is\n * one they can run.\n */\nexport function scaffoldConflictMessage(\n projectName: string,\n displayPath: string,\n conflicts: string[],\n invocation: readonly string[] = invokedRebaseCommand()\n): string[] {\n const shown = conflicts.slice(0, 8).join(\", \") + (conflicts.length > 8 ? `, and ${conflicts.length - 8} more` : \"\");\n return [\n `${chalk.red.bold(\"ERROR\")} Directory \"${projectName}\" already holds files a new project would overwrite or mix with: ${shown}`,\n chalk.yellow(` Scaffold somewhere new — ${chalk.cyan([...invocation, \"init\", \"<new-directory>\"].join(\" \"))} — or move those out of ${displayPath || \"this directory\"} and run this again.`),\n chalk.gray(` A repository's own metadata (.git) and a LICENSE are fine to leave in place.`)\n ];\n}\n\n/**\n * `git init`, or nothing when the directory is already a repository.\n *\n * A cloned repository keeps its branch, its remote and its history: the\n * scaffold is committed on top of what is there. Only a repository this\n * function created is pointed at `main`.\n */\nexport async function initGitRepository(targetDirectory: string): Promise<boolean> {\n if (fs.existsSync(path.join(targetDirectory, \".git\"))) {\n console.log(chalk.gray(\" Using the existing git repository...\"));\n return true;\n }\n console.log(chalk.gray(\" Initializing git repository...\"));\n try {\n await execa(\"git\", [\"init\"], { cwd: targetDirectory });\n // Name the branch `main` rather than inheriting whatever\n // `init.defaultBranch` is (often still `master`). `git init -b` would\n // be the obvious way, but it needs git >= 2.28; rewriting HEAD works\n // on every version and is safe before the first commit.\n try {\n await execa(\"git\", [\"symbolic-ref\", \"HEAD\", \"refs/heads/main\"], { cwd: targetDirectory });\n } catch {\n // Leave the default branch name; not worth failing the scaffold.\n }\n return true;\n } catch {\n console.warn(chalk.yellow(\" Warning: Failed to initialize git repository\"));\n return false;\n }\n}\n\nasync function createProject(options: InitOptions) {\n const startedAt = Date.now();\n const conflicts = scaffoldDirectoryConflicts(options.targetDirectory);\n if (conflicts.length > 0) {\n for (const line of scaffoldConflictMessage(options.projectName, formatCdTarget(process.cwd(), options.targetDirectory), conflicts)) {\n console.error(line);\n }\n process.exit(1);\n }\n fs.mkdirSync(options.targetDirectory, { recursive: true });\n\n // Verify template exists\n try {\n await access(options.templateDirectory, fs.constants.R_OK);\n } catch {\n console.error(`${chalk.red.bold(\"ERROR\")} Template not found at ${options.templateDirectory}`);\n process.exit(1);\n }\n\n // Copy template files\n console.log(chalk.gray(\" Copying project files...\"));\n try {\n await cp(options.templateDirectory, options.targetDirectory, {\n recursive: true,\n filter: (source: string) => {\n const basename = path.basename(source);\n // Skip node_modules and .DS_Store\n return basename !== \"node_modules\" && basename !== \".DS_Store\";\n }\n });\n } catch (err: unknown) {\n console.error(`${chalk.red.bold(\"ERROR\")} Failed to copy template files: ${err instanceof Error ? err.message : String(err)}`);\n process.exit(1);\n }\n\n // npm/pnpm always strip files named .gitignore and .npmrc from published\n // tarballs, so the template ships them un-dotted and we restore the real\n // names here.\n for (const [from, to] of [[\"gitignore\", \".gitignore\"], [\"npmrc\", \".npmrc\"]] as const) {\n const shipped = path.join(options.targetDirectory, from);\n if (fs.existsSync(shipped)) {\n fs.renameSync(shipped, path.join(options.targetDirectory, to));\n }\n }\n\n // Apply the selected template preset (swap collection files)\n if (options.headless && options.explicitPreset) {\n // baas has no collections, so there is nothing for a preset to swap.\n // Say so rather than accepting the flag and silently dropping it.\n console.log(chalk.yellow(` Ignoring --template ${options.preset}: a headless project declares no collections.`));\n }\n if (!options.headless) {\n // When introspecting, the database is the source of truth: start from\n // the blank preset so example collections never register on top of\n // tables the database doesn't have.\n if (options.introspect && options.preset !== \"blank\") {\n console.log(chalk.gray(\" Using the blank template: collections will come from your database.\"));\n }\n const applied = await applyPreset(options.targetDirectory, options.introspect ? \"blank\" : options.preset);\n writeShippedSchema(options.targetDirectory, applied);\n }\n\n // Reduce the project to the selected shape\n await applyHeadless(options.targetDirectory, options.headless);\n\n // Replace placeholder project name in package.json files\n await replacePlaceholders(options);\n\n // Rename .env.example to .env if it exists and randomize secrets\n await configureEnvFile(options.targetDirectory, options.databaseUrl);\n\n // The manifest's build command is the project's package manager, not npm.\n // The template has to spell one of them, and `npm run build --workspace\n // frontend` in a pnpm project is a second package manager reading a\n // lockfile it did not write.\n await writeProjectPackageManagerIntoManifest(options.targetDirectory, options.pmCommands);\n // The scaffold's `.mcp.json` runs the project's own `@rebasepro/mcp`\n // through the package manager, and it ships spelled for pnpm.\n retargetScaffoldMcpConfig(options.targetDirectory, options.pm);\n\n // Before git, so the skills and MCP configs are in the initial commit.\n if (options.agents.length > 0) {\n console.log(chalk.gray(\" Setting up AI coding agents...\"));\n printAgentSetup(configureAgents(options.agents, options.targetDirectory, undefined, options.pm), options.pm);\n }\n\n // Create the repository now, but commit at the very end — see\n // commitScaffold below for why the two halves are separated.\n const gitInitialized = options.git ? await initGitRepository(options.targetDirectory) : false;\n\n const { pm, pmCommands } = options;\n const installCmd = pmCommands.install;\n const execCmd = pmCommands.exec(\"rebase\", [\"schema\", \"introspect\", \"--force\"]);\n // No --collections: config/collections is the default, and a path written\n // here is resolved from where the reader runs it — the project root.\n const generateCmd = pmCommands.exec(\"rebase\", [\"schema\", \"generate\"]);\n\n // Whether the install succeeded, so a failed one is printed as a step still to take.\n let installed = false;\n if (options.installDeps) {\n console.log(\"\");\n console.log(chalk.gray(` Installing dependencies with ${pm}...`));\n console.log(\"\");\n try {\n await execa(installCmd[0], installCmd.slice(1), {\n cwd: options.targetDirectory,\n stdio: \"inherit\"\n });\n installed = true;\n } catch {\n console.warn(chalk.yellow(` Warning: Failed to install dependencies. You may need to run \\`${installCmd.join(\" \")}\\` manually.`));\n }\n }\n\n // Whether introspection actually ran and produced collections. The next\n // steps below report what really happened, so a skipped or failed\n // introspection is never announced as a success.\n let introspected = false;\n\n if (options.introspect) {\n console.log(\"\");\n if (options.installDeps) {\n console.log(chalk.gray(\" Introspecting database and generating collections...\"));\n console.log(\"\");\n try {\n // --force overwrites template example collections with real ones\n await execa(execCmd[0], execCmd.slice(1), {\n cwd: options.targetDirectory,\n stdio: \"inherit\"\n });\n // The scaffold holds the blank preset's schema.generated.ts;\n // regenerate it from the introspected collections or the\n // backend serves a schema that doesn't match the database.\n await execa(generateCmd[0], generateCmd.slice(1), {\n cwd: options.targetDirectory,\n stdio: \"inherit\"\n });\n console.log(chalk.green(\" Database successfully introspected!\"));\n introspected = true;\n } catch {\n console.warn(chalk.yellow(\" Warning: Failed to introspect database automatically.\"));\n console.warn(chalk.yellow(` You can run \\`${execCmd.join(\" \")}\\` then \\`${generateCmd.join(\" \")}\\` manually after setup.`));\n }\n } else {\n console.warn(chalk.yellow(\" Skipping introspection because dependencies were not installed.\"));\n console.warn(chalk.yellow(` Run \\`${installCmd.join(\" \")}\\` then \\`${execCmd.join(\" \")}\\` manually.`));\n }\n }\n\n if (gitInitialized) await commitScaffold(options.targetDirectory);\n\n await linkScaffoldToCloud(options);\n\n // Success message\n console.log(\"\");\n console.log(`${chalk.green.bold(\"✓\")} Project ${chalk.bold(options.projectName)} created successfully!`);\n console.log(\"\");\n console.log(chalk.bold(\"Next steps:\"));\n console.log(\"\");\n const nextSteps = initNextSteps({\n headless: options.headless,\n ownDatabase: Boolean(options.databaseUrl),\n introspect: Boolean(options.introspect),\n introspected,\n installed,\n // The path the user has to type, not the project's basename: `init\n // apps/my-app` must say `cd apps/my-app`, and `init .` needs no cd at all\n // because they are already standing in the project.\n cdTarget: formatCdTarget(process.cwd(), options.targetDirectory),\n pm: pmCommands\n });\n for (const line of nextSteps) console.log(line);\n console.log(\"\");\n console.log(chalk.gray(\"Docs: https://rebase.pro/docs\"));\n console.log(chalk.gray(\"GitHub: https://github.com/rebasepro/rebase\"));\n console.log(\"\");\n console.log(chalk.bold(\"🤖 AI Agent Skills\"));\n console.log(\"\");\n for (const line of agentNextSteps(options.agents, options.interactive === true, pmCommands)) console.log(line);\n console.log(\"\");\n\n // Asked here, at the very end, and never before: the project exists, it\n // worked, and the user has something to judge us on. Nothing has been\n // written or transmitted up to this line — declining leaves no id and no\n // file. See telemetry/consent.ts for why the order matters.\n //\n // `--yes` and non-TTY runs never reach the prompt, so CI behaviour is\n // unchanged.\n const initProperties = {\n preset: options.headless ? \"none\" : options.preset,\n headless: Boolean(options.headless),\n package_manager: options.pm,\n installed_deps: Boolean(options.installDeps),\n introspected,\n own_database: Boolean(options.databaseUrl),\n cloud_linked: Boolean(options.cloudProject),\n git: Boolean(options.git),\n duration: durationBucket(Date.now() - startedAt)\n };\n // `reAskDeclined`: scaffolding is the one moment where asking again is an\n // offer rather than nagging. See PromptOptions.\n if (await promptForConsent({ reAskDeclined: true })) {\n await recordEvent(\"cli.init\", initProperties, { projectRoot: options.targetDirectory });\n }\n}\n\n/**\n * Apply a template preset by replacing the default collection files.\n *\n * The template ships with blog collections at the top level and\n * preset alternatives under `config/collections/presets/<name>/`.\n * This function swaps the active collection files and removes the\n * presets directory so the final project is clean.\n */\n/**\n * Reduce the scaffolded project to the chosen shape.\n *\n * The base template is the full triad. `--headless` drops the frontend and the\n * declared collections — there is nothing to define, since the server derives\n * its API from the database — and overlays the files that differ.\n *\n * The config *package* stays, holding only `storageAuthorize`. Storage is not\n * under row-level security, so a deployment with file storage enabled and no\n * access model serves every user's files to every signed-in user; the server\n * refuses to boot in that state. Deleting the package outright would leave a\n * headless project with nowhere to put the hook, and the scaffold's own\n * docker-compose.yml enables storage — so the first `docker compose up` would\n * crash-loop.\n */\nasync function applyHeadless(targetDirectory: string, headless: boolean): Promise<void> {\n if (!headless) return;\n\n fs.rmSync(path.join(targetDirectory, \"frontend\"), { recursive: true,\nforce: true });\n // Collections only. `rebase build` reads the absence of this directory as\n // \"introspect from the database\", and the overlay replaces config/index.ts\n // with one that exports the storage hook alone.\n fs.rmSync(path.join(targetDirectory, \"config\", \"collections\"), { recursive: true,\nforce: true });\n for (const stray of [\"cms.d.ts\", \"frontend-assets.d.ts\"]) {\n fs.rmSync(path.join(targetDirectory, \"config\", stray), { force: true });\n }\n // Generated from collection files; a headless project reads the live schema.\n fs.rmSync(path.join(targetDirectory, \"backend\", \"src\", \"schema.generated.ts\"), { force: true });\n\n const overlayDir = path.resolve(cliRoot!, \"templates\", \"overlays\", \"baas\");\n if (!fs.existsSync(overlayDir)) {\n console.error(`${chalk.red.bold(\"ERROR\")} BaaS template overlay not found at ${overlayDir}`);\n process.exit(1);\n }\n\n await cp(overlayDir, targetDirectory, {\n recursive: true,\n force: true,\n filter: (source: string) => {\n const basename = path.basename(source);\n return basename !== \"node_modules\" && basename !== \".DS_Store\";\n }\n });\n}\n\n/**\n * Rewrite `rebase.json`'s static-app build command for this project's package manager.\n *\n * The template must spell one of them, and it spells npm. In a pnpm project\n * that is a second package manager invoked against a lockfile it did not\n * write — `rebase build` then either installs a second tree or fails on a\n * workspace flag npm and pnpm spell differently.\n */\nasync function writeProjectPackageManagerIntoManifest(\n targetDirectory: string,\n pmCommands: PMCommands\n): Promise<void> {\n const manifestPath = path.join(targetDirectory, \"rebase.json\");\n if (!fs.existsSync(manifestPath)) return;\n\n try {\n const manifest = JSON.parse(fs.readFileSync(manifestPath, \"utf8\")) as {\n apps?: Record<string, { type?: string; root?: string; build?: string }>;\n };\n let changed = false;\n for (const app of Object.values(manifest.apps ?? {})) {\n if (app.type !== \"static\" || !app.root || !app.build) continue;\n const rewritten = pmCommands.runWorkspace(app.root, \"build\").join(\" \");\n if (rewritten !== app.build) {\n app.build = rewritten;\n changed = true;\n }\n }\n if (changed) {\n fs.writeFileSync(manifestPath, `${JSON.stringify(manifest, null, 4)}\\n`, \"utf8\");\n }\n } catch {\n // A manifest that will not parse is `rebase build`'s problem to report,\n // and scaffolding must not fail on a cosmetic rewrite.\n }\n}\n\n/** Swap in the preset's collection files. Returns the preset actually applied. */\nasync function applyPreset(targetDirectory: string, preset: TemplatePreset): Promise<TemplatePreset> {\n const collectionsDir = path.join(targetDirectory, \"config\", \"collections\");\n const presetsDir = path.join(collectionsDir, \"presets\");\n\n if (preset !== \"blog\") {\n const presetDir = path.join(presetsDir, preset);\n if (!fs.existsSync(presetDir)) {\n console.warn(chalk.yellow(` Warning: Preset \"${preset}\" not found, falling back to blog template.`));\n cleanupPresets(presetsDir);\n return \"blog\";\n }\n\n // Remove the default blog collection files (keep users.ts — it's shared)\n const blogFiles = [\"posts.ts\", \"authors.ts\", \"tags.ts\", \"index.ts\"];\n for (const file of blogFiles) {\n const filePath = path.join(collectionsDir, file);\n if (fs.existsSync(filePath)) {\n fs.unlinkSync(filePath);\n }\n }\n\n // Copy preset files into the collections directory\n const presetFiles = fs.readdirSync(presetDir).filter(f => f.endsWith(\".ts\"));\n for (const file of presetFiles) {\n fs.copyFileSync(\n path.join(presetDir, file),\n path.join(collectionsDir, file)\n );\n }\n }\n\n // Always clean up the presets directory — it shouldn't ship with the final project\n cleanupPresets(presetsDir);\n return preset;\n}\n\n/**\n * Where the generated Drizzle schema for each preset ships, beside the template.\n *\n * `rebase dev` regenerates `backend/src/schema.generated.ts` on every start, and\n * the Studio's schema editor refuses to commit over a dirty copy of it — a\n * schema change must not sweep up somebody's work in progress. So whatever\n * `init` writes there, and commits under `--git`, has to be byte-for-byte what\n * that first regeneration produces. A stub (`export const tables = {}`) was\n * rewritten on the first `rebase dev`, and every new project's first Studio\n * edit was refused with `SCHEMA_EDIT_DIRTY_TREE` before its owner had changed\n * a thing.\n *\n * The file cannot be generated here: generation loads the collection files,\n * and they import `@rebasepro/cms-types`, which the project does not have\n * until it is installed — `init --git` without `--install` commits first. So\n * each preset's output is generated in this repository and shipped, and\n * `init-generated-schema.test.ts` fails the moment one of them differs from\n * what the generator writes for that preset's collections.\n *\n * Outside `templates/template` on purpose: a preset's schema must not be\n * copied into a project that chose another preset.\n */\nexport function shippedSchemaPath(preset: TemplatePreset): string {\n return path.resolve(cliRoot!, \"templates\", \"generated-schemas\", `${preset}.ts`);\n}\n\n/** Write the preset's generated schema to `backend/src/schema.generated.ts`. */\nfunction writeShippedSchema(targetDirectory: string, preset: TemplatePreset): void {\n const destination = path.join(targetDirectory, \"backend\", \"src\", \"schema.generated.ts\");\n fs.mkdirSync(path.dirname(destination), { recursive: true });\n fs.copyFileSync(shippedSchemaPath(preset), destination);\n}\n\nfunction cleanupPresets(presetsDir: string): void {\n if (fs.existsSync(presetsDir)) {\n fs.rmSync(presetsDir, { recursive: true,\nforce: true });\n }\n}\n\n/**\n * The one registry call `init` makes, and what it is for.\n *\n * Every `@rebasepro/*` package in this repository carries the same version —\n * `check:publishable-set` fails the build on the first PR after a bump if any\n * of them drifts — so the version to pin is known before the network is\n * touched: it is this CLI's own. Asking npm eleven times, once per package,\n * bought nothing but eleven chances to hang. On a slow connection that was the\n * slowest part of `init`; on no connection it was eleven timeouts before a\n * fallback to `\"latest\"`, which is the one answer that produces a project\n * mixing framework eras.\n *\n * One call remains, and it asks a different question. Lockstep is enforced in\n * the repository, not on the registry: a release can still ship the CLI and\n * leave a package behind, which is exactly what happened to\n * `@rebasepro/agent-skills` across three releases. So this probes a single\n * package — `@rebasepro/server`, the one a project cannot run without — to see\n * whether this version really made it out. A gap is a refusal, because the\n * alternative is scaffolding a project that cannot install.\n *\n * Unreachable is NOT a refusal. `init` offline is a legitimate thing to do,\n * and the pin it would write is the same pin the probe would have confirmed.\n * It says so and carries on.\n */\ntype ReleaseProbe =\n | { kind: \"published\" }\n | { kind: \"gap\"; found: string }\n | { kind: \"unreachable\"; reason: string };\n\n/** The package the probe asks about. Not the CLI: the CLI is what is running. */\nconst RELEASE_PROBE_PACKAGE = \"@rebasepro/server\";\n\nasync function probeRelease(version: string): Promise<ReleaseProbe> {\n try {\n const { stdout } = await execa(\"npm\", [\"view\", `${RELEASE_PROBE_PACKAGE}@${version}`, \"version\"]);\n if (stdout.trim() === version) return { kind: \"published\" };\n // npm answers an unpublished version with empty output rather than an\n // error on some registries. Empty is a gap, not a success.\n return { kind: \"gap\", found: stdout.trim() || \"nothing\" };\n } catch (error) {\n const text = `${(error as { stderr?: string }).stderr ?? \"\"}${(error as Error).message ?? \"\"}`;\n if (/E404|404 Not Found|No match(ing)? version/i.test(text)) {\n return { kind: \"gap\", found: \"nothing\" };\n }\n return { kind: \"unreachable\", reason: firstLine(text) || \"the registry could not be reached\" };\n }\n}\n\nfunction firstLine(text: string): string {\n return text.split(\"\\n\").map(l => l.trim()).filter(Boolean)[0] ?? \"\";\n}\n\nasync function replacePlaceholders(options: InitOptions) {\n const filesToProcess = TEMPLATE_PLACEHOLDER_FILES;\n\n // `unknown` only when the manifest could not be read at all, which is the\n // same \"nothing to pin, nothing to confirm\" case `latest` used to stand for.\n const version = cliVersion();\n\n const fileContents = new Map<string, string>();\n const allPackages = new Set<string>();\n\n for (const file of filesToProcess) {\n const fullPath = path.resolve(options.targetDirectory, file);\n if (!fs.existsSync(fullPath)) continue;\n const content = fs.readFileSync(fullPath, \"utf-8\");\n fileContents.set(fullPath, content);\n\n const matches = [...content.matchAll(/\"(@rebasepro\\/[^\"]+)\":\\s*\"workspace:\\*\"/g)];\n for (const match of matches) allPackages.add(match[1]);\n }\n\n // `latest` means this CLI could not read its own manifest — there is no\n // version to confirm, and nothing to gain from asking.\n const skipProbe = parseEnvBoolean(process.env.REBASE_E2E) === true || version === \"unknown\";\n const probe: ReleaseProbe = skipProbe ? { kind: \"published\" } : await probeRelease(version);\n\n if (probe.kind === \"gap\") {\n throw new Error(\n `Rebase ${version} is not fully published to npm.\\n\\n` +\n `${RELEASE_PROBE_PACKAGE} has no ${version} release — the registry has ${probe.found}.\\n` +\n `Every @rebasepro package ships at one version, so scaffolding would pin ${allPackages.size}\\n` +\n `dependencies at a version that is not there, and the install would fail.\\n\\n` +\n \"That is a release gap in Rebase itself — not a problem with your machine,\\n\" +\n \"your network, or your package manager.\\n\\n\" +\n \"Stopped before writing dependency versions or installing anything. The\\n\" +\n `project directory ${path.basename(options.targetDirectory)}/ was created and is safe to delete.\\n` +\n \"Please report this.\"\n );\n }\n\n if (probe.kind === \"published\") {\n console.log(chalk.gray(` Pinning ${allPackages.size} @rebasepro package(s) to ${version}...`));\n }\n\n if (probe.kind === \"unreachable\") {\n console.log(chalk.gray(` Pinning @rebasepro packages to ${version} (this CLI's version).`));\n console.log(chalk.yellow(` Could not reach npm to confirm the release: ${probe.reason}`));\n console.log(chalk.gray(\" The pin is right either way — install will say so if the version is missing.\"));\n }\n\n for (const [fullPath, originalContent] of fileContents.entries()) {\n let content = originalContent.replace(/\\{\\{PROJECT_NAME\\}\\}/g, options.projectName);\n for (const pkgName of allPackages) {\n content = content.replace(\n new RegExp(`\"${pkgName}\":\\\\s*\"workspace:\\\\*\"`, \"g\"),\n `\"${pkgName}\": \"${version}\"`\n );\n }\n fs.writeFileSync(fullPath, content, \"utf-8\");\n }\n}\n\n\n/** `undefined` binds the wildcard address, which is a different question — see isPortAvailable. */\nfunction canBind(port: number, host?: string): Promise<boolean> {\n return new Promise((resolve) => {\n const server = net.createServer();\n server.once(\"error\", (err: NodeJS.ErrnoException) => {\n // The host has no such address family at all — a v4-only machine\n // asked about `::1`. Nothing can be listening on an address that\n // cannot exist, so this is free rather than taken.\n resolve(err.code === \"EAFNOSUPPORT\" || err.code === \"EADDRNOTAVAIL\");\n });\n server.once(\"listening\", () => {\n server.close(() => resolve(true));\n });\n if (host === undefined) server.listen(port);\n else server.listen(port, host);\n });\n}\n\n/**\n * Whether `port` is free — on the wildcard address *and* on both loopback addresses.\n *\n * All three, because on macOS/BSD a successful bind does not mean the port is\n * unused. Sockets carry `SO_REUSEADDR` (Node sets it), under which a wildcard\n * bind and a specific-address bind on the same port do not conflict — in either\n * direction. So each probe alone has a blind spot, and they are different ones:\n *\n * - **Wildcard only** (what this used to do) misses a server bound to\n * `127.0.0.1` and `[::1]` — a Homebrew or Postgres.app install, i.e. most\n * developer machines. 5432 was reported free while it was already serving\n * another project's database. Docker then published `*:5432` for the same\n * reason and the container started cleanly, with no \"port already allocated\"\n * error anywhere to hint at the collision. `DATABASE_URL` pointed at\n * `localhost:5432`, `localhost` resolves to `::1` first, and every command\n * reported success while reading and writing the *pre-existing* database — a\n * `db push` would have created tables, roles and RLS policies inside it.\n *\n * - **Loopback only** misses the opposite case: Docker Desktop publishes a\n * container's port on `*`, and a specific-address bind succeeds right past it.\n * That port is free to probe and unusable to publish, so `docker compose up -d\n * db` fails on \"Bind for 0.0.0.0:PORT failed: port is already allocated\" —\n * loudly, but only after the project has been generated around the bad port.\n *\n * Requiring all three costs three sockets and leaves neither gap. The wildcard\n * bind is the one the container itself has to make; the loopback binds are the\n * addresses `DATABASE_URL` will actually name.\n */\nexport async function isPortAvailable(port: number): Promise<boolean> {\n return (await canBind(port)) && (await canBind(port, \"127.0.0.1\")) && (await canBind(port, \"::1\"));\n}\n\n/** The range a project's compose port search starts in: 5433–5632. */\nconst COMPOSE_DB_PORT_FIRST = 5433;\nconst COMPOSE_DB_PORT_SPAN = 200;\n\n/**\n * Where this project's compose port search starts: a port derived from its path.\n *\n * Every project used to start at 5432 and take the first free port. Nothing a\n * scaffold does claims that port — its container does not exist yet — so\n * projects scaffolded side by side all landed on the same one: blog, ecommerce\n * and blank all got 5433. `rebase dev --docker` in the second one then found\n * the first one's database answering there.\n *\n * Not 5432 at all: it is the port a Postgres installed on this machine later\n * will take, and on macOS a container publishing `*:5432` and a server bound to\n * loopback do not even collide loudly (see {@link isPortAvailable}).\n */\nexport function composeDbPortStart(targetDirectory: string): number {\n const digest = crypto.createHash(\"sha256\").update(path.resolve(targetDirectory)).digest();\n return COMPOSE_DB_PORT_FIRST + (digest.readUInt32BE(0) % COMPOSE_DB_PORT_SPAN);\n}\n\n/**\n * The host port this project's compose `db` service publishes.\n *\n * From {@link composeDbPortStart} upwards, the first port nothing listens on\n * ({@link isPortAvailable}) and no container on this machine is bound to,\n * running or stopped: a stopped project's `db` container takes its port back\n * the moment it starts, and a TCP probe cannot see it. Docker that cannot be\n * asked claims nothing. `probes` stands in for both in the tests.\n */\nexport async function chooseComposeDbPort(\n targetDirectory: string,\n probes: {\n isFree?: (port: number) => Promise<boolean>;\n claimed?: () => Promise<Set<number> | null>;\n } = {}\n): Promise<number> {\n const isFree = probes.isFree ?? isPortAvailable;\n const claimed = (await (probes.claimed ?? dockerClaimedPorts)()) ?? new Set<number>();\n let port = composeDbPortStart(targetDirectory);\n while (claimed.has(port) || !(await isFree(port))) {\n port++;\n }\n return port;\n}\n\n/**\n * The CLI's own version, which is the runtime image tag a scaffolded project\n * pins. They move together: the CLI, the packages and the runtime image are cut\n * from one release, so the version that installed the project is the version\n * whose runtime can boot its bundle.\n *\n * Falls back to `latest` only when the CLI cannot read its own manifest — a\n * floating tag is worse than a pinned one, but better than a compose file that\n * names a version that was never published.\n */\nfunction readCliVersion(): string {\n const version = cliVersion();\n return version === \"unknown\" ? \"latest\" : version;\n}\n\n/**\n * The runtime image tag to pin, given the version of the CLI doing the scaffolding.\n *\n * Only a stable release publishes `rebasepro/server` — a multi-arch build on\n * every push to main would cost minutes per commit for an image nobody pulls.\n * So pinning a prerelease CLI's own version writes a tag that cannot exist, and\n * `docker compose up` fails on `manifest unknown`, which is the same dead end\n * as the missing-repository bug this pinning was added to prevent.\n *\n * A prerelease therefore falls back to `latest`, which is correct rather than\n * merely available: a bundle's manifest declares the runtime range it needs\n * (`^1`), the image supplies only `@rebasepro/server`, and the framework a\n * bundle runs is installed from its own `deps.declared` at boot. The current\n * stable runtime boots a canary bundle by design.\n *\n * A floating tag is a real cost — it is what pinning exists to avoid — so say\n * so in the file rather than leaving a reader to discover it.\n */\nexport function resolveRuntimeImageTag(cliVersion: string): { tag: string; note?: string } {\n // Semver prerelease: everything after the first `-`. `0.13.1-canary.gcd6689e`.\n if (/^\\d+\\.\\d+\\.\\d+-/.test(cliVersion)) {\n return {\n tag: \"latest\",\n note: `# Scaffolded by a prerelease CLI (${cliVersion}), which publishes no runtime image,\\n` +\n \"# so this floats to the newest stable runtime. Pin an exact version once you\\n\" +\n \"# deploy: a moving tag changes what you are running with no version changing.\"\n };\n }\n return { tag: cliVersion };\n}\n\n/**\n * Set `KEY=value` in a .env body, whether the key ships set, commented out or\n * missing entirely.\n *\n * The missing case is not hypothetical: `configureEnvFile` runs against\n * whatever `.env.example` is in the target directory, which for a project\n * scaffolded from an older template — or from one somebody edited — may not\n * carry a key this version of `init` is expected to write. Silently skipping it\n * produces a `.env` that satisfies no `${VAR:?…}` in the compose file, which\n * surfaces as a stack that refuses to interpolate rather than as anything\n * pointing back here.\n */\nfunction setEnvValue(content: string, key: string, value: string, header?: string): string {\n const line = new RegExp(`^#?\\\\s*${key}=.*$`, \"m\");\n if (line.test(content)) return content.replace(line, `${key}=${value}`);\n return `${content.trimEnd()}\\n\\n${header ? `${header}\\n` : \"\"}${key}=${value}\\n`;\n}\n\n/**\n * `sslmode=disable` on a URL that names a database on this machine and says\n * nothing about TLS.\n *\n * A local Postgres — Homebrew, Postgres.app, a container — serves no TLS, and\n * Atlas, which `rebase db push`, `db generate` and `db migrate` run, requires it\n * unless the URL says otherwise: `pq: SSL is not enabled on the server`. The URL\n * `init` writes when it picks the database has always carried the parameter; a\n * URL passed with `--database-url` reached `.env` without it, so the app\n * connected and the first push failed.\n *\n * Only a loopback address, and only when the URL names no `sslmode` or `ssl` of\n * its own: another machine's TLS is not ours to turn off, and a choice the\n * developer wrote down is theirs. A `host` or `hostaddr` parameter is the real\n * address (a socket directory, say), so that URL is left as given too, and so\n * is anything this cannot parse.\n */\nexport function disableSslOnLoopback(connectionString: string): string {\n let url: URL;\n try {\n url = new URL(connectionString);\n } catch {\n return connectionString;\n }\n if (url.protocol !== \"postgres:\" && url.protocol !== \"postgresql:\") return connectionString;\n if ([\"sslmode\", \"ssl\", \"host\", \"hostaddr\"].some(param => url.searchParams.has(param))) return connectionString;\n\n // `hostname` keeps an IPv6 literal's brackets.\n const host = url.hostname.replace(/^\\[(.+)\\]$/, \"$1\");\n const loopback = host === \"localhost\" || host === \"::1\" || host === \"0.0.0.0\" || /^127(?:\\.\\d{1,3}){3}$/.test(host);\n if (!loopback) return connectionString;\n\n // Appended to the query as it stands, so the percent-encoding `pinSearchPath`\n // chose for `options` — the form libpq accepts — is kept byte for byte.\n url.search = url.search ? `${url.search}&sslmode=disable` : \"?sslmode=disable\";\n return url.toString();\n}\n\nexport async function configureEnvFile(targetDirectory: string, databaseUrl?: string) {\n const envExamplePath = path.join(targetDirectory, \".env.example\");\n const envPath = path.join(targetDirectory, \".env\");\n if (fs.existsSync(envExamplePath) && !fs.existsSync(envPath)) {\n // Copy .env.example → .env (keep .env.example as a reference in the repo)\n fs.copyFileSync(envExamplePath, envPath);\n // Owner-only from the first byte. `copyFileSync` copies the *source's*\n // permissions and the packaged `.env.example` is 0644, so the file that\n // is about to receive a generated JWT_SECRET, the database password and\n // REBASE_SERVICE_KEY was world-readable for the whole of `init` — on a\n // shared build box or a multi-user machine, any local account could read\n // another project's service key, which the API treats as a full admin\n // credential. `.rebase/state.json` (dev-port.ts) and `telemetry.json`\n // are both 0600 for the same reason; this file holds strictly more.\n fs.chmodSync(envPath, 0o600);\n\n // Generate secure random strings\n const jwtSecret = crypto.randomBytes(32).toString(\"hex\");\n const dbPassword = crypto.randomBytes(16).toString(\"hex\");\n const serviceKey = crypto.randomBytes(48).toString(\"base64\");\n // base64url rather than base64 or hex: 24 characters, comfortably past\n // the runtime's 12-character floor, and every character it can produce\n // is one dotenv and Compose interpolation read back unchanged. `+`, `/`\n // and `=` are fine in a .env value but not obviously so to a reader\n // about to paste one into a shell, and `#` — which hex cannot produce\n // but a passphrase generator might — would truncate the line.\n const adminPassword = crypto.randomBytes(18).toString(\"base64url\");\n\n let envContent = fs.readFileSync(envPath, \"utf-8\");\n\n // The banner is written for the file being read as a template. This\n // copy *is* the filled-in one — it already carries a generated\n // JWT_SECRET, service key and database password — and telling its\n // reader to \"copy this file to .env and fill in the values\" invites\n // exactly the overwrite the README warns against two paragraphs later.\n envContent = envContent.replace(\n /^(# ║ {2})(Copy this file to \\.env and fill in the values)( +║)$/m,\n (_match, open: string, old: string, close: string) => {\n const replacement = \"Generated by `rebase init` — the secrets below are already set\";\n // Keep the box drawn: pad or clip to the width the line had.\n const width = old.length + close.length - 1;\n return open + replacement.slice(0, width).padEnd(width, \" \") + \"║\";\n }\n );\n\n envContent = envContent.replace(\n /^JWT_SECRET=.*$/m,\n `JWT_SECRET=${jwtSecret}`\n );\n\n // Ships commented out in .env.example. Left unset, the server generates\n // one on every boot and silently invalidates the previous run's tokens,\n // so write a stable one now rather than making each restart a logout.\n envContent = envContent.replace(\n /^#\\s*REBASE_SERVICE_KEY=.*$/m,\n `REBASE_SERVICE_KEY=${serviceKey}`\n );\n\n // Read back rather than hardcoded, so this tracks whatever PORT the\n // generated .env ends up carrying. `docker-compose.yml` publishes the\n // api as `${PORT:-3001}:3001`.\n const composeApiPort = /^PORT=(\\d+)/m.exec(envContent)?.[1] ?? \"3001\";\n\n // Also ships commented out, and left that way the very first command\n // this project tells you to run does not work.\n //\n // `docker-compose.yml` declares `CORS_ORIGINS: ${CORS_ORIGINS:?…}` on\n // the `api` service — deliberately required, because an API that\n // guesses its allowed origins eventually allows the wrong one. But\n // Compose interpolates the WHOLE file before selecting services, so\n // `docker compose up -d db` — step 1 of the \"Next steps\" printed at the\n // end of `rebase init` — failed on a variable the `db` service does not\n // use. So did `docker compose config`: the file could not be read at all.\n //\n // The guard is right; a generated project should satisfy it. The compose\n // stack serves the bundled admin from the `api` container on ${PORT},\n // so that is the origin a browser loads it from. A deployment still has\n // to set its own, and gets a visible CORS failure rather than a silent\n // one if it forgets.\n envContent = envContent.replace(\n /^#\\s*CORS_ORIGINS=.*$/m,\n `CORS_ORIGINS=http://localhost:${composeApiPort}`\n );\n\n // The first admin, named rather than raced for.\n //\n // `docker-compose.yml` runs the api service with NODE_ENV=production,\n // and in production the first-registration-becomes-admin window is\n // shut — deliberately, because the compose stack is serving before its\n // operator has typed anything and whoever found the sign-up form first\n // would own it. A closed window needs a way in that is not a race, and\n // this is it: the account is named in the same file that already holds\n // the JWT secret and the database password.\n //\n // Written by `init` rather than left for the reader, because the\n // compose file declares both with `${VAR:?…}`: unset, `docker compose\n // up` does not produce a deployment without an admin, it produces a\n // file that refuses to interpolate — and it refuses for `docker compose\n // up -d db` too, which is step 1 of the \"Next steps\" printed below.\n //\n // The address is a placeholder the login route accepts. Not\n // `admin@localhost`: `POST /auth/login` parses its body with\n // `z.string().email()`, which rejects a domain with no dot, so that\n // address seeds an account and then 400s on every attempt to use it.\n // The runtime now refuses it at boot; this default is one it accepts.\n envContent = setEnvValue(\n envContent,\n \"REBASE_ADMIN_EMAIL\",\n \"admin@example.com\",\n \"# ── The first admin account, in production only ──────────────────────\\n\" +\n \"# `rebase dev` ignores both of these: on a laptop the first account to\\n\" +\n \"# register becomes the admin, and it says so at boot. In production —\\n\" +\n \"# `docker compose up`, or anything with NODE_ENV=production — that\\n\" +\n \"# window is shut, because the server answers on a hostname before you\\n\" +\n \"# have typed anything. So the operator names the account here instead.\\n\" +\n \"# Created once, while the user table is empty. Change the email to\\n\" +\n \"# yours, sign in, and change the password below.\"\n );\n envContent = setEnvValue(envContent, \"REBASE_ADMIN_PASSWORD\", adminPassword);\n\n // Blank the build-time API URL rather than shipping `http://localhost:3001`.\n //\n // This is the one environment-specific value `init` used to leave alone,\n // and the chain that made it dangerous is fully closed: `.env.example`\n // ships the literal, `frontend/vite.config.ts` sets `envDir: \"..\"` so\n // `vite build` reads *this* file, and the managed bundle is built on the\n // developer's own machine — so `.env` being gitignored does not save\n // anyone. A stock scaffold deployed with `rebase cloud deploy` served\n // HTML that passed every health check while every XHR from the live site\n // went to the developer's laptop. `rebase cloud env set VITE_API_URL=…`\n // refuses build-time variables, so there was no recovery from the\n // platform side either.\n //\n // Empty is not a missing value, it is the correct one: the client falls\n // back to `window.location.origin`, which is the right shape for a\n // single-origin bundle and the only one that keeps working when a custom\n // domain is added. `rebase dev` injects the port it actually bound, so\n // local development is unaffected.\n // The template ships it blank, so this is belt-and-braces for a project\n // whose .env.example was edited or predates that change.\n envContent = envContent.replace(/^#?\\s*VITE_API_URL=.*$/m, \"VITE_API_URL=\");\n\n // Pin the runtime image `docker-compose.yml` pulls.\n //\n // The compose file reads `rebasepro/server:${REBASE_VERSION:-latest}`,\n // and unset it resolves to `latest` — a tag that moves under a running\n // deployment. That is precisely the hazard `cloudbuild-runtime.yaml`\n // designed away for the managed fleet, which pins releases by digest\n // because \"re-pushing a tag silently changes what the fleet is running\n // with no version anywhere changing\". A self-hoster deserves the same\n // guarantee, and it costs one line here.\n //\n // It also makes the compose header's upgrade instruction true: \"To\n // upgrade Rebase, change REBASE_VERSION and restart\" is a no-op while\n // the value is unset and the tag floats.\n const { tag: runtimeVersion, note } = resolveRuntimeImageTag(readCliVersion());\n const pinned = `${note ? `${note}\\n` : \"\"}REBASE_VERSION=${runtimeVersion}`;\n envContent = /^#?\\s*REBASE_VERSION=.*$/m.test(envContent)\n ? envContent.replace(/^#?\\s*REBASE_VERSION=.*$/m, pinned)\n : `${envContent.trimEnd()}\\n\\n# The Rebase runtime image tag docker-compose.yml pulls.\\n# Change this and restart to upgrade; your project bundle is untouched.\\n${pinned}\\n`;\n\n if (databaseUrl) {\n if (/[\\r\\n]/.test(databaseUrl)) {\n throw new Error(\"Invalid DATABASE_URL: multiline values are not allowed.\");\n }\n // A supplied URL used to be written through verbatim, which quietly\n // dropped the `search_path=public` pin the generated URL below has\n // always carried. Postgres defaults `search_path` to `\"$user\",\n // public`, and this project creates a `rebase` schema — so on any\n // database whose role happens to be named `rebase` (the name every\n // template, compose file and deployment doc uses), unqualified SQL\n // resolves to `$user` and the project's tables get created in\n // `rebase` instead of `public`. Pinning here makes the two branches\n // agree; a URL that already pins something keeps it.\n // Imported lazily: this is the only line in `init` that needs the\n // Postgres package, and a static import would load pg + drizzle on\n // every CLI invocation.\n const { pinSearchPath } = await import(\"@rebasepro/server-postgres\");\n const pinnedUrl = disableSslOnLoopback(pinSearchPath(databaseUrl));\n // DATABASE_PASSWORD is still written even though the URL points\n // elsewhere: docker-compose.yml interpolates it into both\n // POSTGRES_PASSWORD and the backend's own DATABASE_URL, defaulting\n // to `${DATABASE_PASSWORD:-changeme}`. Omitting it here shipped a\n // compose stack whose database password was literally \"changeme\",\n // on a service that publishes a host port by default.\n //\n // A replacer function, not a replacement string: in a string, `$$`,\n // `$&` and `$'` are patterns, and a password is free to contain\n // them — `pa$$w0rd` reached .env as `pa$w0rd`.\n envContent = envContent.replace(\n /^DATABASE_URL=.*$/m,\n () => `DATABASE_URL=${pinnedUrl}\\nDATABASE_PASSWORD=${dbPassword}`\n );\n } else {\n const dbPort = await chooseComposeDbPort(targetDirectory);\n envContent = envContent.replace(\n /^DATABASE_URL=.*$/m,\n // sslmode=disable: the paired docker-compose Postgres has no TLS,\n // and Go-based tooling (atlas, via `rebase db push`) defaults to\n // requiring SSL when the URL doesn't say otherwise.\n //\n // 127.0.0.1 rather than `localhost`: the name resolves to `::1`\n // first on macOS, so on a machine that already runs Postgres on\n // loopback the two addresses can reach two different servers.\n // An address cannot be ambiguous the way a name can.\n //\n // `%3D`, not a literal `=`, inside the `options` value. libpq\n // splits a URI query parameter on the FIRST `=` and rejects any\n // further one outright:\n // extra key/value separator \"=\" in URI query parameter: \"options\"\n // That is every libpq caller — `pg_dump`/`pg_restore` behind\n // `rebase db backup|restore`, and a plain `psql \"$DATABASE_URL\"`\n // copied out of this very file. It is not a version quirk; 15\n // through 18 all refuse it. node-postgres parses URLs itself and\n // accepts the literal form, which is why `rebase dev` and\n // `rebase db push` worked and this shipped broken for so long.\n // `pinSearchPath` (the --database-url branch above) has always\n // emitted the encoded form; this line is what disagreed.\n // Commented out, not omitted. `rebase dev` starts a managed\n // PostgreSQL when nothing names a database, which is what makes\n // a fresh project one command rather than four. The compose URL\n // stays here, one `#` away, because the alternative — a project\n // that mentions Docker nowhere — makes switching to it a\n // documentation lookup instead of an edit.\n // Deliberately short: the header copied from `.env.example`\n // sits directly above and already says what unset means. Saying\n // it twice, in the first section of the first file a new\n // developer opens, is how a generated file starts reading like\n // boilerplate nobody wrote on purpose.\n [\n \"# Commented out on purpose — that is what selects the managed database.\",\n \"# To use the docker-compose Postgres this project ships instead:\",\n \"# docker compose up -d db, then uncomment the line below.\",\n `# DATABASE_URL=postgresql://rebase_app:${dbPassword}@127.0.0.1:${dbPort}/rebase?options=-c%20search_path%3Dpublic&sslmode=disable`,\n `DATABASE_PASSWORD=${dbPassword}`\n ].join(\"\\n\")\n );\n // The SSL note further down illustrates `sslmode=disable` with a\n // full URL, and this file was copied from `.env.example` before the\n // rewrite below gives that one this project's port.\n envContent = envContent.replaceAll(\"@localhost:5432/rebase\", `@127.0.0.1:${dbPort}/rebase`);\n\n // `.env.example` is copied to `.env` and then kept, as the reference\n // that gets committed — so it is the one file in the project that\n // still carries the template's defaults after everything else has\n // been rewritten. Two of them are actively dangerous once the port\n // is derived per project:\n //\n // - its `DATABASE_URL` is UNCOMMENTED, contradicting the rule the\n // rest of the scaffold now states (\"unset means the managed\n // database\"), and\n // - it names `localhost:5432`, while this project's compose\n // publishes ${dbPort}.\n //\n // Copying `.env.example` to `.env` is a near-universal habit, and on\n // a machine that already runs Postgres on 5432 — most developer\n // machines — the result is not a connection failure. It is a\n // successful connection to somebody else's database, which boot will\n // then provision. Comment it out and give it this project's port.\n const envExampleReference = path.join(targetDirectory, \".env.example\");\n if (fs.existsSync(envExampleReference)) {\n let exampleContent = fs.readFileSync(envExampleReference, \"utf-8\");\n exampleContent = exampleContent.replace(\n /^DATABASE_URL=postgresql:\\/\\/[^\\n]*$/m,\n [\n \"# Commented out on purpose — that is what selects the managed database.\",\n \"# To use the docker-compose Postgres this project ships instead:\",\n \"# docker compose up -d db, then uncomment the line below in .env\",\n \"# (with DATABASE_PASSWORD from that file, not the placeholder here).\",\n `# DATABASE_URL=postgresql://rebase_app:changeme@127.0.0.1:${dbPort}/rebase?options=-c%20search_path%3Dpublic&sslmode=disable`\n ].join(\"\\n\")\n );\n // The prose further down illustrates `sslmode=disable` with a\n // full DSN. It is commented and not meant to be copied whole,\n // but a file that names two different ports for one database\n // teaches the reader to distrust both.\n exampleContent = exampleContent.replaceAll(\n \"@localhost:5432/rebase\",\n `@127.0.0.1:${dbPort}/rebase`\n );\n fs.writeFileSync(envExampleReference, exampleContent, \"utf-8\");\n }\n\n // Also update docker-compose.yml with the dynamic host port if it has the default 5432 port mapping\n const dockerComposePath = path.join(targetDirectory, \"docker-compose.yml\");\n if (fs.existsSync(dockerComposePath)) {\n let dockerComposeContent = fs.readFileSync(dockerComposePath, \"utf-8\");\n dockerComposeContent = dockerComposeContent.replace(\n /-\\s*\"5432:5432\"/g,\n `- \"${dbPort}:5432\"`\n );\n fs.writeFileSync(dockerComposePath, dockerComposeContent, \"utf-8\");\n }\n }\n\n // `mode` only applies when the file is created, and this one exists\n // already — so chmod as well, exactly as `writeStateFile` does.\n fs.writeFileSync(envPath, envContent, {\n encoding: \"utf-8\",\n mode: 0o600\n });\n fs.chmodSync(envPath, 0o600);\n }\n}\n"],"mappings":"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"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { n as parseCommandArgs } from "./args-
|
|
2
|
-
import { v as normalizeEsmSpecifiers } from "./bundle-
|
|
1
|
+
import { n as parseCommandArgs } from "./args-BX0YD14a.js";
|
|
2
|
+
import { v as normalizeEsmSpecifiers } from "./bundle-DtCCCr4w.js";
|
|
3
3
|
import chalk from "chalk";
|
|
4
4
|
import fs from "fs";
|
|
5
5
|
import path from "path";
|
|
@@ -104,4 +104,4 @@ async function normalizeImportsCommand(rawArgs) {
|
|
|
104
104
|
//#endregion
|
|
105
105
|
export { normalizeImportsCommand };
|
|
106
106
|
|
|
107
|
-
//# sourceMappingURL=normalize-imports-
|
|
107
|
+
//# sourceMappingURL=normalize-imports-BoBVh5tU.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"normalize-imports-
|
|
1
|
+
{"version":3,"file":"normalize-imports-BoBVh5tU.js","names":[],"sources":["../src/commands/normalize-imports.ts"],"sourcesContent":["import path from \"path\";\nimport fs from \"fs\";\n\nimport chalk from \"chalk\";\n\nimport { normalizeEsmSpecifiers } from \"../bundle\";\nimport { parseCommandArgs } from \"../utils/args\";\n\n/**\n * Give compiled output the relative-import extensions Node's ESM loader needs.\n *\n * ## Why a project needs this at all\n *\n * TypeScript does not rewrite module specifiers, deliberately and by long-stated\n * policy: what you write is what it emits. Node's ESM loader, equally\n * deliberately, resolves no extensions — `./authors` is not a module, only\n * `./authors.js` is. Between those two positions sits every project that writes\n * extensionless relative imports, which is most of them, and which\n * `moduleResolution: \"bundler\"` — what this scaffold ships — is designed for.\n *\n * The usual escape is to write the *output* extension in the *source*:\n * `import x from \"./authors.js\"` in a file called `authors.ts`. It works, and it\n * is a tax on every import in the project to satisfy a step the toolchain could\n * take itself. This is that step.\n *\n * ## Why it is a command\n *\n * `rebase build` has always done this to its own bundle — the specifiers in\n * `dist-bundle` are complete because {@link normalizeEsmSpecifiers} completes\n * them. Nothing did it for the plain `tsc` output that `rebase eject` runs\n * (`node dist/backend/src/index.js`) or that a self-hoster runs after\n * `pnpm build`. So the templates wrote `.js` in source, which is the tax, and\n * removing it silently broke eject at `pnpm start` while `check:eject` — which\n * typechecks with `noEmit` — stayed green.\n *\n * One implementation, on both paths, callable from a package.json script.\n *\n * Mechanical and verifiable: only relative specifiers are touched, and only when\n * the target resolves to a file that exists. Idempotent, so a build that runs it\n * twice is a build that runs it once.\n */\nexport const NORMALIZE_IMPORTS_FLAGS = {\n /** Report what would change without writing anything. */\n \"--check\": Boolean\n} as const;\n\nconst HELP = `\n${chalk.bold(\"rebase normalize-imports\")} — complete the relative imports in compiled output\n\n${chalk.green.bold(\"Usage\")}\n rebase normalize-imports ${chalk.blue(\"<dir>\")} [--check]\n\n${chalk.green.bold(\"What it does\")}\n TypeScript emits your module specifiers verbatim, and Node's ESM loader does\n not guess extensions — so \\`./authors\\` compiles fine and fails at run time.\n This rewrites each relative specifier in the emitted JavaScript to the file it\n actually resolves to, so your source can stay extensionless.\n\n Only relative specifiers, only when the target exists on disk, and idempotent.\n\n${chalk.green.bold(\"Arguments\")}\n ${chalk.blue(\"<dir>\")} The directory tsc emitted into, e.g. ${chalk.cyan(\"dist\")}\n\n${chalk.green.bold(\"Options\")}\n ${chalk.blue(\"--check\")} Report what would change and exit 1 if anything would\n ${chalk.gray(\"(for CI — the build itself should just run the rewrite)\")}\n\n${chalk.green.bold(\"Where it runs\")}\n The scaffold's ${chalk.cyan(\"config\")} and ${chalk.cyan(\"backend\")} build scripts, after ${chalk.cyan(\"tsc\")}.\n ${chalk.cyan(\"rebase build\")} already does the same to the bundle it writes.\n`;\n\nexport async function normalizeImportsCommand(rawArgs: string[]): Promise<void> {\n const { flags, positionals, help } = parseCommandArgs({\n spec: NORMALIZE_IMPORTS_FLAGS,\n rawArgs,\n commandWords: 1,\n command: \"normalize-imports\",\n maxPositionals: 1\n });\n\n if (help) {\n console.log(HELP);\n return;\n }\n\n const target = positionals[0] ?? \"dist\";\n const outDir = path.resolve(process.cwd(), target);\n\n // A missing directory is the common shape of \"tsc did not run\", and saying\n // so beats a rewrite that reports zero changes and looks like success.\n if (!fs.existsSync(outDir)) {\n throw new Error(\n `${target} does not exist — run your build first. `\n + \"This step rewrites what the compiler emitted; it does not compile.\"\n );\n }\n\n // `--check` reports and writes nothing: it is what a CI job runs to ask\n // whether the build did this step, and a check that repaired the output\n // would answer \"no\" once and \"yes\" for ever after.\n const check = flags[\"--check\"] === true;\n const { rewritten, unresolved } = normalizeEsmSpecifiers(outDir, { write: !check });\n // A failed check is a verdict, on stderr with exit 1 — not a thrown error,\n // whose handler adds a stack-trace hint to an answer that has no stack.\n const say = check ? console.error : console.log;\n\n if (unresolved.length > 0) {\n say(chalk.yellow(` ⚠ ${unresolved.length} relative import(s) resolve to no file:`));\n for (const specifier of unresolved.slice(0, 10)) {\n say(chalk.dim(` ${specifier}`));\n }\n if (unresolved.length > 10) {\n say(chalk.dim(` …and ${unresolved.length - 10} more`));\n }\n say(chalk.dim(\" These will throw ERR_MODULE_NOT_FOUND at run time. This command cannot complete them:\"));\n say(chalk.dim(\" there is no file to point them at — fix the import, or the build that should emit it.\"));\n }\n\n if (check) {\n if (rewritten > 0) {\n console.error(chalk.red(\n `✗ ${rewritten} relative import(s) in ${target} are incomplete for Node ESM. `\n + `Run \\`rebase normalize-imports ${target}\\` as part of the build.`\n ));\n }\n if (rewritten > 0 || unresolved.length > 0) {\n process.exitCode = 1;\n return;\n }\n console.log(chalk.green(`✓ every relative import in ${target} is complete`));\n return;\n }\n\n console.log(\n rewritten > 0\n ? chalk.dim(` completed ${rewritten} relative import(s) in ${target}/ for Node ESM`)\n : chalk.dim(` ${target}/ — every relative import was already complete`)\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAyCA,IAAa,0BAA0B;;AAEnC,WAAW,QACf;AAEA,IAAM,OAAO;EACX,MAAM,KAAK,0BAA0B,EAAE;;EAEvC,MAAM,MAAM,KAAK,OAAO,EAAE;6BACC,MAAM,KAAK,OAAO,EAAE;;EAE/C,MAAM,MAAM,KAAK,cAAc,EAAE;;;;;;;;EAQjC,MAAM,MAAM,KAAK,WAAW,EAAE;IAC5B,MAAM,KAAK,OAAO,EAAE,0DAA0D,MAAM,KAAK,MAAM,EAAE;;EAEnG,MAAM,MAAM,KAAK,SAAS,EAAE;IAC1B,MAAM,KAAK,SAAS,EAAE;4BACE,MAAM,KAAK,yDAAyD,EAAE;;EAEhG,MAAM,MAAM,KAAK,eAAe,EAAE;mBACjB,MAAM,KAAK,QAAQ,EAAE,OAAO,MAAM,KAAK,SAAS,EAAE,wBAAwB,MAAM,KAAK,KAAK,EAAE;IAC3G,MAAM,KAAK,cAAc,EAAE;;AAG/B,eAAsB,wBAAwB,SAAkC;CAC5E,MAAM,EAAE,OAAO,aAAa,SAAS,iBAAiB;EAClD,MAAM;EACN;EACA,cAAc;EACd,SAAS;EACT,gBAAgB;CACpB,CAAC;CAED,IAAI,MAAM;EACN,QAAQ,IAAI,IAAI;EAChB;CACJ;CAEA,MAAM,SAAS,YAAY,MAAM;CACjC,MAAM,SAAS,KAAK,QAAQ,QAAQ,IAAI,GAAG,MAAM;CAIjD,IAAI,CAAC,GAAG,WAAW,MAAM,GACrB,MAAM,IAAI,MACN,GAAG,OAAO,2GAEd;CAMJ,MAAM,QAAQ,MAAM,eAAe;CACnC,MAAM,EAAE,WAAW,eAAe,uBAAuB,QAAQ,EAAE,OAAO,CAAC,MAAM,CAAC;CAGlF,MAAM,MAAM,QAAQ,QAAQ,QAAQ,QAAQ;CAE5C,IAAI,WAAW,SAAS,GAAG;EACvB,IAAI,MAAM,OAAO,OAAO,WAAW,OAAO,wCAAwC,CAAC;EACnF,KAAK,MAAM,aAAa,WAAW,MAAM,GAAG,EAAE,GAC1C,IAAI,MAAM,IAAI,SAAS,WAAW,CAAC;EAEvC,IAAI,WAAW,SAAS,IACpB,IAAI,MAAM,IAAI,cAAc,WAAW,SAAS,GAAG,MAAM,CAAC;EAE9D,IAAI,MAAM,IAAI,2FAA2F,CAAC;EAC1G,IAAI,MAAM,IAAI,2FAA2F,CAAC;CAC9G;CAEA,IAAI,OAAO;EACP,IAAI,YAAY,GACZ,QAAQ,MAAM,MAAM,IAChB,KAAK,UAAU,yBAAyB,OAAO,+DACX,OAAO,yBAC/C,CAAC;EAEL,IAAI,YAAY,KAAK,WAAW,SAAS,GAAG;GACxC,QAAQ,WAAW;GACnB;EACJ;EACA,QAAQ,IAAI,MAAM,MAAM,8BAA8B,OAAO,aAAa,CAAC;EAC3E;CACJ;CAEA,QAAQ,IACJ,YAAY,IACN,MAAM,IAAI,eAAe,UAAU,yBAAyB,OAAO,eAAe,IAClF,MAAM,IAAI,KAAK,OAAO,+CAA+C,CAC/E;AACJ"}
|
package/dist/precompress.d.ts
CHANGED
|
@@ -1,10 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* A content hash in the name, by the runtime's rule (`isContentHashed` in the
|
|
3
|
-
* server's `serve-spa.ts`): 8+ base64url characters after the last `-` with a
|
|
4
|
-
* digit or a capital in them. `index-vgugiqRO.js` is; `vendor-analytics.js` is
|
|
5
|
-
* not.
|
|
6
|
-
*/
|
|
7
|
-
export declare function isContentHashedName(fileName: string): boolean;
|
|
8
1
|
export interface PrecompressOutcome {
|
|
9
2
|
/** Files that got at least one sibling. */
|
|
10
3
|
files: number;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { c as findProjectRoot, d as readEnvFile } from "./project-Wp2zoyeN.js";
|
|
2
|
-
import { t as composeDatabaseUrl } from "./dev-preflight-
|
|
2
|
+
import { s as Refusal, t as composeDatabaseUrl } from "./dev-preflight-unHpiYO1.js";
|
|
3
3
|
import { n as MANAGED_LIMITATIONS } from "./constraints-CtiylsGf.js";
|
|
4
4
|
import { n as ensureManagedDatabase } from "./daemon-CknkDjd6.js";
|
|
5
5
|
import fs from "fs";
|
|
@@ -259,7 +259,7 @@ function describeDevDatabase(database) {
|
|
|
259
259
|
case "branch": return `branch "${database.branch}" (rebase db branch switch)`;
|
|
260
260
|
}
|
|
261
261
|
break;
|
|
262
|
-
case "docker": return database.source === "last-dev" ? "Postgres in Docker
|
|
262
|
+
case "docker": return database.source === "last-dev" ? "Postgres in Docker, as the last `rebase dev --docker` did" : "Postgres in Docker";
|
|
263
263
|
case "managed": return "the managed development database (PGlite)";
|
|
264
264
|
}
|
|
265
265
|
throw new Error(`Unhandled dev database: ${JSON.stringify(database)}`);
|
|
@@ -475,7 +475,7 @@ async function prepareDatabaseEnv(projectRoot, options = {}) {
|
|
|
475
475
|
description
|
|
476
476
|
};
|
|
477
477
|
if (database.kind === "docker") {
|
|
478
|
-
if (!database.url) throw new
|
|
478
|
+
if (!database.url) throw new Refusal("--docker needs a docker-compose.yml with a db service in this project, and this one has none that names POSTGRES_USER, POSTGRES_DB and a published port. Set DATABASE_URL in .env to point at the database you mean instead.");
|
|
479
479
|
if (database.source === "last-dev") options.onProgress?.(`Using ${branch ? `branch "${branch.name}" (${branch.database}) of ` : ""}Postgres in Docker, as the last \`rebase dev --docker\` did. \`rebase dev\` without --docker goes back to the managed database.`);
|
|
480
480
|
return {
|
|
481
481
|
database,
|
|
@@ -534,4 +534,4 @@ function managedNotices(prepared) {
|
|
|
534
534
|
//#endregion
|
|
535
535
|
export { DEV_DATABASE_KIND_ENV, lastDevDatabasePath as a, branchDatabaseName as c, clearActiveBranch as d, devDatabaseKind, databaseNameOf as f, lastDevDatabaseNotice as i, branchPointerPath as l, writeActiveBranch as m, managedNotices, outrankedDockerFlagNotice as n, readLastDevDocker as o, readActiveBranch as p, prepareDatabaseEnv, resolveDevDatabase as r, resolveComposeUrl, resolveProjectDatabase, resolveProjectDatabaseAndBranch, recordLastDevDatabase as s, describeDevDatabase as t, branchUrl as u };
|
|
536
536
|
|
|
537
|
-
//# sourceMappingURL=prepare-
|
|
537
|
+
//# sourceMappingURL=prepare-DdnU70z8.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prepare-DdnU70z8.js","names":[],"sources":["../src/dev-db/branch-pointer.ts","../src/dev-db/last-dev-database.ts","../src/dev-db/resolve.ts","../src/dev-db/prepare.ts"],"sourcesContent":["/**\n * Which database branch this checkout is working on.\n *\n * `rebase db branch create` made a full copy of the database in about a second\n * and then stopped one step short of being a feature: there was no `switch`, no\n * `--branch` on `rebase dev`, and nothing wrote the branch anywhere a command\n * would read it. The only way to *use* a branch was to hand-edit\n * `DATABASE_URL`, and the documentation claimed otherwise — \"the CLI updates\n * your local development configuration\" — which it did not; the `.env` was\n * byte-identical afterwards.\n *\n * This is that missing step, and the shape of it is chosen to avoid three\n * things:\n *\n * 1. **It does not touch `.env`.** That file holds secrets, comments and the\n * developer's own ordering, and a command that rewrites it in place will\n * eventually lose something that was not its business. The branch is\n * recorded beside the other per-checkout state in `.rebase/`, which is\n * already gitignored — a branch is a fact about one machine, never about\n * the project.\n *\n * 2. **It stores a name, not a connection string.** The credentials stay in\n * exactly one place; the URL is derived by swapping the database name on\n * the base one at resolution time. A stored URL would be a second copy of\n * the password, and would go stale the moment `.env` changed.\n *\n * 3. **It does not outrank an explicit instruction.** `--database-url` and a\n * `DATABASE_URL` in the shell are more immediate and more specific than a\n * switch made yesterday, so they still win. The pointer sits directly above\n * the project's `.env`: it is a deliberate, persistent choice, and `.env` is\n * the project default it is meant to override. Anything less and `switch`\n * would silently do nothing on every project that sets `DATABASE_URL` —\n * which is every project not using the managed database.\n */\n\nimport fs from \"fs\";\nimport path from \"path\";\n\n/** Where the pointer lives, next to the rest of the per-checkout state. */\nexport function branchPointerPath(projectRoot: string): string {\n return path.join(projectRoot, \".rebase\", \"branch.json\");\n}\n\nexport interface ActiveBranch {\n /** The name the developer typed. */\n name: string;\n /** The PostgreSQL database that name refers to. */\n database: string;\n}\n\n/**\n * The prefix `BranchService` puts on every branch database.\n *\n * Duplicated from the driver rather than imported because the CLI must answer\n * \"which database\" without loading a database driver — `rebase dev` reads this\n * before it knows which plugin the project uses. It is covered by a test that\n * fails if the driver's prefix moves.\n */\nexport const BRANCH_DB_PREFIX = \"rb_\";\n\nexport function branchDatabaseName(name: string): string {\n return `${BRANCH_DB_PREFIX}${name}`;\n}\n\n/** Read the active branch, or null when the checkout is on the main database. */\nexport function readActiveBranch(projectRoot: string): ActiveBranch | null {\n try {\n const raw = fs.readFileSync(branchPointerPath(projectRoot), \"utf-8\");\n const parsed = JSON.parse(raw) as Partial<ActiveBranch>;\n if (typeof parsed.name !== \"string\" || typeof parsed.database !== \"string\") return null;\n if (!parsed.name || !parsed.database) return null;\n\n return { name: parsed.name, database: parsed.database };\n } catch {\n // Absent is the common case and is not an error. A corrupt file is\n // treated the same way: the answer is \"no branch\", which is the state\n // the developer can always get back to with `branch switch --off`.\n return null;\n }\n}\n\nexport function writeActiveBranch(projectRoot: string, branch: ActiveBranch): void {\n const file = branchPointerPath(projectRoot);\n fs.mkdirSync(path.dirname(file), { recursive: true });\n fs.writeFileSync(file, `${JSON.stringify(branch, null, 2)}\\n`, \"utf-8\");\n}\n\n/** Return to the main database. Absent is success — this is idempotent. */\nexport function clearActiveBranch(projectRoot: string): void {\n fs.rmSync(branchPointerPath(projectRoot), { force: true });\n}\n\n/**\n * The base connection string with its database swapped for the branch's.\n *\n * Everything else is carried across untouched — credentials, host, port, and\n * the query string, which is load-bearing here: `?sslmode=disable` is what a\n * local Docker Postgres needs, and dropping it turns a switch into a TLS error\n * that says nothing about branches.\n *\n * Returns null for a connection string that cannot be parsed, so the caller can\n * say so plainly rather than emitting a mangled URL that fails later.\n */\nexport function branchUrl(baseUrl: string, database: string): string | null {\n try {\n const url = new URL(baseUrl);\n // `pathname` keeps its leading slash; a URL with no database at all\n // still gets one, which is what connecting to a named branch means.\n url.pathname = `/${database}`;\n\n return url.toString();\n } catch {\n return null;\n }\n}\n\n/** The database name in a connection string, for reporting what you are on. */\nexport function databaseNameOf(connectionString: string): string | null {\n try {\n const name = new URL(connectionString).pathname.replace(/^\\//, \"\");\n\n return name || null;\n } catch {\n return null;\n }\n}\n","/**\n * Which database the last `rebase dev` ran on, when nothing else records it.\n *\n * `rebase dev --docker` is a choice made on one command line, and every other\n * command resolved without it: while dev served the project from its compose\n * database, `rebase db url` printed a managed PGlite URL — and started that\n * daemon to print it — and `db push`, `console` and the rest would have used\n * the same empty database. Nothing said the two disagreed.\n *\n * So `rebase dev` records its choice here, and the other commands follow it\n * (`resolve.ts`, rule 5). Only Docker is recorded. Every other database is\n * already somewhere each command reads — `--database-url` aside, which is a\n * choice for one command line — or is the default, so the record holds\n * `docker` exactly when the last `rebase dev` ran on it, and any other start\n * of `rebase dev` removes it. That is also the way back: `rebase dev` without\n * `--docker`.\n *\n * Beside the branch pointer in `.rebase/`, which is gitignored: which database\n * this checkout last ran on is a fact about one machine, never the project.\n */\n\nimport fs from \"fs\";\nimport path from \"path\";\n\nimport type { DevDatabase } from \"./resolve\";\n\nexport function lastDevDatabasePath(projectRoot: string): string {\n return path.join(projectRoot, \".rebase\", \"dev-database.json\");\n}\n\n/** Whether the last `rebase dev` ran on the compose database. */\nexport function readLastDevDocker(projectRoot: string): boolean {\n try {\n const parsed: unknown = JSON.parse(fs.readFileSync(lastDevDatabasePath(projectRoot), \"utf8\"));\n return typeof parsed === \"object\" && parsed !== null && \"kind\" in parsed && parsed.kind === \"docker\";\n } catch {\n // Absent is the common case; unreadable has the same answer.\n return false;\n }\n}\n\n/** What this `rebase dev` was asked for, beside what it runs on. */\nexport interface LastDevRun {\n /** `--docker` was on this command line. */\n dockerFlag?: boolean;\n}\n\n/**\n * Record what this `rebase dev` runs on, and say what the last one ran on.\n *\n * A run that asked for Docker and was outranked by a connection string leaves\n * the record as it was. It is not on Docker, so it records nothing; but it is\n * not a run without `--docker` either, which is what forgets the record. The\n * reader was told to comment the `.env` line out to reach Docker, and once\n * they have, the other commands follow Docker as they did before.\n *\n * Best effort: a checkout whose `.rebase/` cannot be written still runs, and\n * the other commands then resolve as they did before this existed.\n */\nexport function recordLastDevDatabase(\n projectRoot: string,\n kind: DevDatabase[\"kind\"],\n run: LastDevRun = {}\n): { previous: \"docker\" | null } {\n const previous = readLastDevDocker(projectRoot) ? \"docker\" : null;\n if (run.dockerFlag && kind !== \"docker\") return { previous };\n const file = lastDevDatabasePath(projectRoot);\n try {\n if (kind === \"docker\") {\n fs.mkdirSync(path.dirname(file), { recursive: true });\n fs.writeFileSync(file, `${JSON.stringify({ kind: \"docker\", recordedAt: new Date().toISOString() }, null, 2)}\\n`);\n } else {\n fs.rmSync(file, { force: true });\n }\n } catch {\n // See above: not worth failing a dev start over.\n }\n\n return { previous };\n}\n\n/**\n * What `rebase dev` says when it leaves the Docker database the last run used.\n *\n * Said rather than left to be noticed: the data a developer was working with\n * is in the container, and the managed database this run is on is a different,\n * possibly empty, one. Not said to a run that asked for Docker: the line that\n * says `--docker` was ignored, and what outranked it, is the one that applies.\n */\nexport function lastDevDatabaseNotice(previous: \"docker\" | null, kind: DevDatabase[\"kind\"], run: LastDevRun = {}): string | null {\n if (previous !== \"docker\" || kind === \"docker\" || run.dockerFlag) return null;\n\n return \"The last `rebase dev` ran on Postgres in Docker; this one does not. \"\n + \"Pass --docker to stay on it — the other commands now follow this run.\";\n}\n","/**\n * Which database a command should talk to, decided in one place.\n *\n * Before this existed every command that needed Postgres read `DATABASE_URL`\n * for itself, which was fine while there was exactly one answer. Introducing a\n * managed database makes the question real: a project may have no\n * `DATABASE_URL` at all and still expect `rebase db push` to work, and a\n * project that *does* set one must never be quietly redirected somewhere else.\n *\n * So the rule is ordered and boring, and the order is the promise:\n *\n * 1. `--database-url <url>` — said on this command line, wins over everything\n * 2. `DATABASE_URL` in the shell environment\n * 3. the database branch this checkout is switched to\n * 4. `DATABASE_URL` in the project's `.env`\n * 5. `--docker`, or the last `rebase dev` having run with it\n * 6. the managed PGlite database\n *\n * An explicit connection string always wins. That is the whole point of the\n * override: someone pointing Rebase at their own Postgres — a colleague's\n * staging box, a Neon branch, a container they manage — must get exactly that,\n * with no cleverness in between. The managed database is what fills the vacuum\n * when nobody has said anything, and it is the only case where the CLI picks.\n *\n * The second half of (5) is `rebase dev --docker` remembered, so that `rebase\n * db url`, `db push` and the rest reach the database a dev server is on rather\n * than start an empty PGlite beside it (`last-dev-database.ts`). It is exactly\n * as strong as the flag it stands in for, and no stronger.\n *\n * The branch at (3) is the one addition that is not an explicit connection\n * string, and it sits where it does deliberately. It has to outrank `.env` or\n * `rebase db branch switch` would silently do nothing on every project that\n * sets `DATABASE_URL` — which is every project not on the managed database.\n * It must not outrank (1) or (2), because a flag on this command line and a\n * variable in this shell are more immediate than a switch made yesterday.\n *\n * {@link resolveDevDatabase} is pure: inputs in, decision out, no filesystem\n * and no process. Reading `.env`, reading the branch pointer and starting a\n * daemon happen elsewhere, so the ordering above can be tested without any of\n * them.\n */\n\n/** Where the answer came from. Carried so diagnostics can name it. */\nexport type DevDatabaseSource =\n /** `--database-url` on the command line. */\n | \"flag\"\n /** `DATABASE_URL` in the shell environment. */\n | \"environment\"\n /** `DATABASE_URL` in the project's `.env`. */\n | \"env-file\"\n /** The branch this checkout is switched to, over the base connection. */\n | \"branch\"\n /** `--docker`. */\n | \"docker\"\n /** `--docker`, on the last `rebase dev` rather than this command line. */\n | \"last-dev\"\n /** Nobody said anything, so the managed database fills in. */\n | \"managed\";\n\nexport type DevDatabase =\n | {\n kind: \"external\";\n /** The connection string, exactly as given. Never rewritten. */\n url: string;\n source: Extract<DevDatabaseSource, \"flag\" | \"environment\" | \"env-file\" | \"branch\">;\n /** The branch name, when this checkout is switched to one. */\n branch?: string;\n }\n | {\n kind: \"docker\";\n source: Extract<DevDatabaseSource, \"docker\" | \"last-dev\">;\n /**\n * The compose `db` service's connection string, derived by the caller.\n *\n * Null when the project has no compose file to derive one from, which\n * is a `--docker` that cannot be honoured rather than a fallback: the\n * caller says so instead of guessing at `localhost:5432`.\n */\n url: string | null;\n }\n | {\n kind: \"managed\";\n source: \"managed\";\n };\n\nexport interface ResolveDevDatabaseInput {\n /** `--database-url <url>`, if given. */\n flagUrl?: string | null;\n /** `--docker`, if given. */\n flagDocker?: boolean;\n /**\n * Whether the last `rebase dev` ran with `--docker`. Followed only while\n * the compose file still names a database, since there is none to go back\n * to otherwise.\n */\n lastDevDocker?: boolean;\n /** The shell environment. Only `DATABASE_URL` is read. */\n env?: Record<string, string | undefined>;\n /** Parsed `.env` from the project root. Only `DATABASE_URL` is read. */\n envFile?: Record<string, string> | null;\n /**\n * The compose `db` service's connection string, when the project has one.\n *\n * Passed in rather than read here so this function stays pure. Only\n * consulted for the Docker case; an explicit `DATABASE_URL` still wins.\n */\n composeUrl?: string | null;\n /**\n * The branch this checkout is switched to, already resolved to a URL.\n *\n * Resolved by the caller rather than here so this stays pure: deriving it\n * needs the base connection string, which needs the `.env` this function is\n * handed rather than reads.\n */\n branch?: { name: string; url: string } | null;\n}\n\n/**\n * A value that is present but empty is treated as absent.\n *\n * `DATABASE_URL=` in a `.env` is what a developer writes when they mean \"not\n * this one\" — honouring it literally would hand an empty connection string to\n * libpq and produce an error about a missing host, which explains nothing.\n */\nfunction present(value: string | undefined | null): string | null {\n if (typeof value !== \"string\") return null;\n const trimmed = value.trim();\n\n return trimmed.length > 0 ? trimmed : null;\n}\n\nexport function resolveDevDatabase(input: ResolveDevDatabaseInput = {}): DevDatabase {\n const flagUrl = present(input.flagUrl);\n if (flagUrl) return { kind: \"external\", url: flagUrl, source: \"flag\" };\n\n const fromEnvironment = present(input.env?.DATABASE_URL);\n if (fromEnvironment) return { kind: \"external\", url: fromEnvironment, source: \"environment\" };\n\n // Above `.env`, below anything said explicitly — see the ordering note at\n // the top of this file.\n const fromBranch = present(input.branch?.url);\n if (fromBranch && input.branch) {\n return { kind: \"external\", url: fromBranch, source: \"branch\", branch: input.branch.name };\n }\n\n const fromEnvFile = present(input.envFile?.DATABASE_URL);\n if (fromEnvFile) return { kind: \"external\", url: fromEnvFile, source: \"env-file\" };\n\n // Only consulted once every explicit connection string is exhausted: asking\n // for Docker is a choice about *how to get* a database, not which one, so a\n // DATABASE_URL that already names one outranks it.\n if (input.flagDocker) {\n return { kind: \"docker\", source: \"docker\", url: present(input.composeUrl) };\n }\n const composeUrl = present(input.composeUrl);\n if (input.lastDevDocker && composeUrl) {\n return { kind: \"docker\", source: \"last-dev\", url: composeUrl };\n }\n\n return { kind: \"managed\", source: \"managed\" };\n}\n\n/**\n * The line to print when `--docker` was typed and a connection string outranked\n * it, or null when it was not typed or was what this command used.\n *\n * The order stays as documented: asking for Docker says how to get a database,\n * and a `DATABASE_URL` that already names one wins. But a flag on this command\n * line that changes nothing without a word reads as a flag that worked. Only the\n * typed flag is reported — the last `rebase dev --docker` standing in for it is\n * a default, and losing to `.env` is what a default does.\n */\nexport function outrankedDockerFlagNotice(database: DevDatabase, flagDocker: boolean | undefined): string | null {\n if (!flagDocker || database.kind !== \"external\") return null;\n const ignored = \"--docker is ignored:\";\n switch (database.source) {\n case \"flag\":\n return `${ignored} --database-url outranks it.`;\n case \"environment\":\n return `${ignored} DATABASE_URL in the environment outranks it. Unset it to use Postgres in Docker.`;\n case \"branch\":\n return `${ignored} branch \"${database.branch}\" (rebase db branch switch) outranks it. ` +\n \"`rebase db branch switch --off` returns to the database in .env.\";\n case \"env-file\":\n return `${ignored} DATABASE_URL in .env outranks it. Comment that line out to use Postgres in Docker.`;\n }\n}\n\n/** One line for the startup banner, naming both the database and why. */\nexport function describeDevDatabase(database: DevDatabase): string {\n switch (database.kind) {\n case \"external\":\n switch (database.source) {\n case \"flag\":\n return \"your database (--database-url)\";\n case \"environment\":\n return \"your database (DATABASE_URL in the environment)\";\n case \"env-file\":\n return \"your database (DATABASE_URL in .env)\";\n case \"branch\":\n return `branch \"${database.branch}\" (rebase db branch switch)`;\n }\n break;\n case \"docker\":\n return database.source === \"last-dev\"\n ? \"Postgres in Docker, as the last `rebase dev --docker` did\"\n : \"Postgres in Docker\";\n case \"managed\":\n return \"the managed development database (PGlite)\";\n }\n\n // Unreachable while the union is exhaustive; kept so a future variant fails\n // loudly in review rather than printing \"undefined\" to a user.\n throw new Error(`Unhandled dev database: ${JSON.stringify(database)}`);\n}\n","/**\n * The one place a command asks \"which database, and how do I reach it?\".\n *\n * Every command that touches Postgres — `dev`, and the whole `db` namespace\n * through the driver plugin — goes through {@link prepareDatabaseEnv}. It\n * resolves the ordering in `resolve.ts`, starts the managed database if that is\n * what the ordering chose, and hands back the environment additions the child\n * process needs.\n *\n * Two things it deliberately does *not* do:\n *\n * - **It never overwrites an existing `DATABASE_URL`.** When the developer has\n * named a database, the returned environment is empty and the child inherits\n * exactly what it would have inherited before this feature existed. A\n * migration must never be redirected away from the database its author meant.\n *\n * - **It never starts anything for a command that is not going to connect.**\n * `--help` and argument errors are handled by the caller before this is\n * reached, because booting a Postgres to print usage would be absurd.\n */\n\nimport fs from \"fs\";\nimport path from \"path\";\n\nimport { composeDatabaseUrl } from \"../utils/dev-preflight\";\nimport { findProjectRoot, readEnvFile } from \"../utils/project\";\nimport { Refusal } from \"../utils/refusal\";\nimport { resourceEnvSuffix } from \"@rebasepro/types\";\nimport { branchUrl, databaseNameOf, readActiveBranch } from \"./branch-pointer\";\nimport { readLastDevDocker } from \"./last-dev-database\";\nimport { MANAGED_LIMITATIONS, MANAGED_POOL_MAX } from \"./constraints\";\nimport { ensureManagedDatabase } from \"./daemon\";\nimport { type DevDatabase, describeDevDatabase, outrankedDockerFlagNotice, resolveDevDatabase } from \"./resolve\";\n\nexport interface PrepareOptions {\n /** `--database-url <url>`. */\n flagUrl?: string | null;\n /** `--docker`. */\n flagDocker?: boolean;\n /**\n * Follow the database the last `rebase dev` ran on (`last-dev-database.ts`).\n * On by default; `rebase dev` itself turns it off, because its own flags are\n * the choice being recorded.\n */\n followLastDev?: boolean;\n /** Suppress the \"starting…\" progress line. */\n quiet?: boolean;\n /** Where human-facing lines go. Defaults to stdout via the caller. */\n onProgress?: (message: string) => void;\n /**\n * Keys of the additional databases the project declares —\n * `database(\"analytics\")` → `analytics`. On the managed path each gets\n * its own PGlite instance and a `DATABASE_URL__<KEY>` in the child's\n * environment, unless the developer set that variable themselves. On an\n * external database nothing is added: whoever chose the database binds\n * the rest of them.\n */\n additionalKeys?: readonly string[];\n}\n\nexport interface PreparedDatabase {\n /** What the resolver chose, for the banner and for tests. */\n database: DevDatabase;\n /**\n * Variables to add to a child process's environment.\n *\n * Empty for an external database: the child already has what it needs, and\n * adding to it could only do harm.\n */\n env: Record<string, string>;\n /** One line naming the database, suitable for a startup banner. */\n description: string;\n /** Absolute path of the managed data directory, when there is one. */\n dataDir?: string;\n /** True when this call started the managed database rather than finding it. */\n startedDaemon?: boolean;\n /** The additional managed databases, by declared key, when any were asked for. */\n additional?: Record<string, string>;\n}\n\n/**\n * The branch this checkout is switched to, over the `DATABASE_URL` in `.env`.\n *\n * Derived rather than stored: the pointer holds a name, and the credentials\n * stay in the one place the developer wrote them. The base here is the\n * project's `.env` — a switch is a statement about *this project's* database,\n * so a `DATABASE_URL` that happens to be in the shell is not the thing being\n * branched, and it outranks the branch anyway.\n *\n * Returns null when there is no branch, and also when `.env` names no base to\n * swap the database name on. That is not the end of it: a project on Postgres\n * in Docker has no `DATABASE_URL` either, and its branch is applied over the\n * compose URL once the order has picked Docker — see\n * {@link resolveProjectDatabaseAndBranch}.\n *\n * Exported for its tests. Asserting this through {@link prepareDatabaseEnv}\n * would start a real PGlite daemon to check a decision made before any daemon\n * is involved.\n */\nexport function resolveActiveBranch(\n projectRoot: string,\n envFile: Record<string, string>\n): { name: string; url: string } | null {\n const active = readActiveBranch(projectRoot);\n if (!active) return null;\n\n const base = envFile.DATABASE_URL?.trim();\n if (!base) return null;\n\n const url = branchUrl(base, active.database);\n\n return url ? { name: active.name, url } : null;\n}\n\n/**\n * The compose `db` service's connection string for this project, or null.\n *\n * Read here rather than in `resolve.ts` so the ordering there stays pure, and\n * exported for the commands that feed the resolver themselves (`rebase status`,\n * `db branch switch`). The preflight that starts the container takes the URL\n * from {@link resolveProjectDatabase}, so it starts one only when the order\n * picks Docker.\n */\nexport function resolveComposeUrl(projectRoot: string, envFile: Record<string, string>): string | null {\n const composePath = path.join(projectRoot, \"docker-compose.yml\");\n if (!fs.existsSync(composePath)) return null;\n try {\n return composeDatabaseUrl(fs.readFileSync(composePath, \"utf8\"), envFile);\n } catch {\n return null;\n }\n}\n\n/**\n * Which database a project is on, decided without starting anything.\n *\n * `resolveDevDatabase` is pure, so the answer is available to help text and to\n * a driver child alike — and neither should pay for a daemon to learn it. The\n * driver cannot work it out for itself: on the managed path `DATABASE_URL` is a\n * perfectly ordinary connection string to a Postgres on loopback, so a child\n * handed only that has no way to know that `rebase db generate` will be refused.\n *\n * Null when there is no project here, which is legal — `rebase db --help` is\n * answered outside one — and when anything at all goes wrong reading the\n * project, because help text must not fail to print over an unreadable `.env`.\n */\nexport function devDatabaseKind(projectRoot?: string | null): DevDatabase[\"kind\"] | null {\n try {\n const root = projectRoot ?? findProjectRoot();\n if (!root) return null;\n\n return resolveProjectDatabase(root).kind;\n } catch {\n return null;\n }\n}\n\n/**\n * The database this command resolves to in this project, decided without\n * starting anything: the ordering in `resolve.ts`, fed from the project's\n * `.env`, its branch pointer, its compose file and the last `rebase dev`.\n *\n * The one place those four are read for a resolution, so a command that asks\n * \"which database\" and a command that then connects to it cannot disagree.\n */\nexport function resolveProjectDatabase(\n projectRoot: string,\n options: Pick<PrepareOptions, \"flagUrl\" | \"flagDocker\" | \"followLastDev\"> = {}\n): DevDatabase {\n return resolveProjectDatabaseAndBranch(projectRoot, options).database;\n}\n\n/** The branch a resolution landed on. */\nexport interface ResolvedBranch {\n /** The name the developer switched to. */\n name: string;\n /** The PostgreSQL database that name refers to. */\n database: string;\n /**\n * The database the branch was taken from, where the branch registry lives:\n * the one the switch swapped the name on.\n */\n parent: string | null;\n}\n\nexport interface ProjectDatabase {\n database: DevDatabase;\n /** Set when the resolved database is this checkout's branch rather than the main one. */\n branch: ResolvedBranch | null;\n /**\n * The branch this checkout is switched to, when the database resolved to\n * is the managed one — which serves exactly one database, so the switch\n * cannot be honoured and the main database is used instead. Null otherwise.\n */\n ignoredBranch: string | null;\n}\n\n/**\n * {@link resolveProjectDatabase}, and which branch the answer is.\n *\n * The switch applies to whichever base the order picks, not to `.env` alone. A\n * project on Postgres in Docker — `rebase dev --docker`, remembered, with the\n * scaffold's `DATABASE_URL` left commented out, which is the setup the docs\n * point to for branching — resolved its branch to nothing, and every command\n * went on using the main database while `switch` said they had moved. So the\n * branch is applied over the compose URL whenever the order picks Docker, by a\n * typed `--docker` or the remembered one, and the answer stays Docker: that is\n * what has the container started and what `rebase dev` records.\n *\n * What outranks a switch still does. `--database-url` and a `DATABASE_URL` in\n * the shell name a database outright, and the managed database cannot hold a\n * branch at all — that last one is reported as `ignoredBranch`, so the caller\n * can say so instead of quietly using main.\n *\n * `env` is the shell environment to judge against, `process.env` by default;\n * `rebase status` passes `{}`, because it asks what *this project* is on.\n */\nexport function resolveProjectDatabaseAndBranch(\n projectRoot: string,\n options: Pick<PrepareOptions, \"flagUrl\" | \"flagDocker\" | \"followLastDev\"> & {\n env?: Record<string, string | undefined>;\n } = {}\n): ProjectDatabase {\n const envFile = readEnvFile(projectRoot);\n const active = readActiveBranch(projectRoot);\n\n const database = resolveDevDatabase({\n flagUrl: options.flagUrl,\n flagDocker: options.flagDocker,\n lastDevDocker: options.followLastDev !== false && readLastDevDocker(projectRoot),\n env: options.env ?? process.env,\n envFile,\n branch: resolveActiveBranch(projectRoot, envFile),\n composeUrl: resolveComposeUrl(projectRoot, envFile)\n });\n\n if (!active) return { database, branch: null, ignoredBranch: null };\n\n if (database.kind === \"external\" && database.source === \"branch\") {\n const parent = databaseNameOf(envFile.DATABASE_URL?.trim() ?? \"\");\n return { database, branch: { ...active, parent }, ignoredBranch: null };\n }\n\n if (database.kind === \"docker\" && database.url) {\n const url = branchUrl(database.url, active.database);\n if (url) {\n return {\n database: { ...database, url },\n branch: { ...active, parent: databaseNameOf(database.url) },\n ignoredBranch: null\n };\n }\n }\n\n return { database, branch: null, ignoredBranch: database.kind === \"managed\" ? active.name : null };\n}\n\n/**\n * The variable a driver child reads {@link devDatabaseKind} from.\n *\n * One name, exported, because three commands build that child's environment and\n * a fourth reads it in another package.\n */\nexport const DEV_DATABASE_KIND_ENV = \"REBASE_DEV_DATABASE_KIND\";\n\n/**\n * The variable that tells a server started on a branch which database it was\n * branched from.\n *\n * The branch registry lives in that database, and a branch database holds\n * only a snapshot of it taken when it was copied — so a server on a branch\n * keeps its branches, and copies new ones from, the database named here. A\n * branch URL does not say which that is; only this checkout's `.env` does.\n */\nexport const BRANCH_PARENT_DATABASE_ENV = \"REBASE_BRANCH_PARENT_DATABASE\";\n\n/**\n * Resolve, start if needed, and describe the database for this command.\n *\n * `projectRoot` is where the managed database's data lives, so two projects on\n * one machine get two databases without either being told about the other.\n */\nexport async function prepareDatabaseEnv(\n projectRoot: string,\n options: PrepareOptions = {}\n): Promise<PreparedDatabase> {\n const envFile = readEnvFile(projectRoot);\n const { database, branch, ignoredBranch } = resolveProjectDatabaseAndBranch(projectRoot, options);\n\n // A branch on Docker is still Docker to the resolver, so its own line does\n // not name the branch; this does, because that is the database in use.\n const description = database.kind === \"docker\" && branch\n ? `branch \"${branch.name}\" (rebase db branch switch) on ${describeDevDatabase(database)}`\n : describeDevDatabase(database);\n // Where a branch lives, for the server: the registry is kept in the\n // database the branch was taken from, and a branch URL does not say which.\n const parentEnv: Record<string, string> = branch?.parent ? { [BRANCH_PARENT_DATABASE_ENV]: branch.parent } : {};\n\n // Said even on a quiet command: `rebase db url --docker` is quiet, and its\n // progress goes to stderr, which is where a flag it did not follow belongs.\n const outranked = outrankedDockerFlagNotice(database, options.flagDocker);\n if (outranked) options.onProgress?.(outranked);\n\n if (ignoredBranch) {\n options.onProgress?.(\n `Branch \"${ignoredBranch}\" is switched to, but this uses the managed database, which has no branches, `\n + \"so the main database is used. `--docker` (or DATABASE_URL in .env) reaches the branch; \"\n + \"`rebase db branch switch --off` clears the switch.\"\n );\n }\n\n if (database.kind === \"external\" && (database.source === \"branch\" || database.source === \"flag\")) {\n // The two external cases that MUST be exported. `environment` and\n // `env-file` are already somewhere the child will look — the shell it\n // inherits, or `.env` via DOTENV_CONFIG_PATH — but a branch URL is\n // derived here and exists nowhere else, and `--database-url` lives only\n // in this process's argv. Without this the pointer resolves correctly\n // and then changes nothing: `rebase db backup` on a switched checkout\n // still reported `Database: leadgen`, and `rebase dev --database-url …`\n // announced the database and then died on `DATABASE_URL: is required`.\n //\n // Safe to set: `dotenv` does not overwrite a variable that is already\n // in the environment, so the child's own `.env` load cannot undo it.\n // On a branch, the database it was branched from too.\n return { database, env: { DATABASE_URL: database.url, ...parentEnv }, description };\n }\n\n if (database.kind === \"docker\") {\n // Docker is the one case where nothing has named a connection string:\n // `.env` leaves DATABASE_URL commented out, so without this the child\n // booted with no database at all and failed on the message a project\n // that configured nothing gets — while the container it had just been\n // asked for was never started either.\n if (!database.url) {\n throw new Refusal(\n \"--docker needs a docker-compose.yml with a db service in this project, \" +\n \"and this one has none that names POSTGRES_USER, POSTGRES_DB and a published port. \" +\n \"Set DATABASE_URL in .env to point at the database you mean instead.\"\n );\n }\n // Said every time it decides, because nothing on this command line\n // asked for Docker: the reader should not have to remember that an\n // earlier `rebase dev` did.\n if (database.source === \"last-dev\") {\n options.onProgress?.(\n `Using ${branch ? `branch \"${branch.name}\" (${branch.database}) of ` : \"\"}Postgres in Docker, `\n + \"as the last `rebase dev --docker` did. `rebase dev` without --docker goes back to the managed database.\"\n );\n }\n return { database, env: { DATABASE_URL: database.url, ...parentEnv }, description };\n }\n\n if (database.kind !== \"managed\") {\n // `environment` or `env-file`: the connection string is already in a\n // place the child reads for itself, so this adds nothing to it.\n return { database, env: {}, description };\n }\n\n // Only the keys nobody bound by hand. A developer who set\n // DATABASE_URL__ANALYTICS to a warehouse of their own has said which\n // database that is, and the managed one fills a vacuum, never a choice.\n const unbound = (options.additionalKeys ?? []).filter((key) => {\n const name = `DATABASE_URL${resourceEnvSuffix(key)}`;\n return !process.env[name] && !envFile[name];\n });\n\n const managed = await ensureManagedDatabase(projectRoot, {\n quiet: options.quiet,\n onProgress: options.onProgress,\n additionalKeys: unbound\n });\n\n const additionalEnv: Record<string, string> = {};\n for (const [key, url] of Object.entries(managed.additional)) {\n additionalEnv[`DATABASE_URL${resourceEnvSuffix(key)}`] = url;\n }\n\n return {\n database,\n description,\n dataDir: managed.dataDir,\n startedDaemon: managed.started,\n additional: managed.additional,\n env: {\n DATABASE_URL: managed.url,\n // PGlite is one session behind a multiplexer: two pooled clients in\n // overlapping transactions deadlock there. This is the ceiling that\n // turns that into ordinary queueing — see `constraints.ts`.\n REBASE_DB_POOL_MAX: String(MANAGED_POOL_MAX),\n ...additionalEnv\n }\n };\n}\n\n/**\n * The lines to print about a managed database, in the order to print them.\n *\n * Returned rather than printed so the caller decides where they go — `dev` has\n * a banner, `db push` has a single line above its own output — and so a test can\n * assert on them without capturing a stream.\n */\nexport function managedNotices(prepared: PreparedDatabase): string[] {\n if (prepared.database.kind !== \"managed\") return [];\n\n const lines = [`Using ${prepared.description}.`];\n if (prepared.dataDir) lines.push(`Data: ${prepared.dataDir}`);\n for (const key of Object.keys(prepared.additional ?? {})) {\n lines.push(`Also serving database \"${key}\" as DATABASE_URL${resourceEnvSuffix(key)}`);\n }\n\n // Stated every time rather than discovered. A developer who does not know\n // requests are serialized here will read a concurrency difference as a bug\n // in their own code.\n for (const limitation of MANAGED_LIMITATIONS) {\n lines.push(`${limitation.summary} ${limitation.remedy}`);\n }\n lines.push(\"To use your own Postgres instead, set DATABASE_URL in .env.\");\n\n return lines;\n}\n"],"mappings":"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"}
|