@rebasepro/cli 0.25.0-canary.ge06257e → 0.25.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (142) hide show
  1. package/bin/rebase.js +9 -5
  2. package/dist/{agent-setup-C83DXnlx.js → agent-setup-BYb2Tbdz.js} +2 -2
  3. package/dist/{agent-setup-C83DXnlx.js.map → agent-setup-BYb2Tbdz.js.map} +1 -1
  4. package/dist/{api-keys-Bc4FJKYp.js → api-keys-CSMbsmAn.js} +28 -22
  5. package/dist/api-keys-CSMbsmAn.js.map +1 -0
  6. package/dist/{apps-wG-ZTryA.js → apps-iBXNZhVN.js} +6 -7
  7. package/dist/{apps-wG-ZTryA.js.map → apps-iBXNZhVN.js.map} +1 -1
  8. package/dist/{atlas-dev-database-DeK3Sa4h.js → atlas-dev-database-BusbHP6n.js} +4 -4
  9. package/dist/{atlas-dev-database-DeK3Sa4h.js.map → atlas-dev-database-BusbHP6n.js.map} +1 -1
  10. package/dist/{auth-BL7bNQVu.js → auth-DRsTWPky.js} +77 -11
  11. package/dist/auth-DRsTWPky.js.map +1 -0
  12. package/dist/{branch-pointer-DTQNLFtb.js → branch-pointer-Bn_FjNld.js} +1 -1
  13. package/dist/{build-h_7Re-67.js → build-et7Rccxi.js} +6 -7
  14. package/dist/{build-h_7Re-67.js.map → build-et7Rccxi.js.map} +1 -1
  15. package/dist/{bundle-CqURVN-K.js → bundle-DlqkDYSe.js} +6 -21
  16. package/dist/bundle-DlqkDYSe.js.map +1 -0
  17. package/dist/{cloud-DVV_5GPG.js → cloud-B-ZIjHrA.js} +10 -11
  18. package/dist/{cloud-DVV_5GPG.js.map → cloud-B-ZIjHrA.js.map} +1 -1
  19. package/dist/commands/api-keys.d.ts +4 -1
  20. package/dist/commands/auth.d.ts +3 -2
  21. package/dist/commands/dev.d.ts +12 -0
  22. package/dist/commands/generate_sdk.d.ts +4 -2
  23. package/dist/commands/init.d.ts +20 -0
  24. package/dist/commands/start.d.ts +10 -0
  25. package/dist/commands/upgrade.d.ts +12 -0
  26. package/dist/connection-banner-DbLJzWf4.js +128 -0
  27. package/dist/connection-banner-DbLJzWf4.js.map +1 -0
  28. package/dist/{consent-ClRHmcxf.js → consent-BaB-eQKG.js} +2 -2
  29. package/dist/{consent-ClRHmcxf.js.map → consent-BaB-eQKG.js.map} +1 -1
  30. package/dist/{console-Dw3HLdUs.js → console-BlD9kjFW.js} +4 -4
  31. package/dist/{console-Dw3HLdUs.js.map → console-BlD9kjFW.js.map} +1 -1
  32. package/dist/{context-BM-oOMNX.js → context-DHiiiJ5C.js} +3 -4
  33. package/dist/context-DHiiiJ5C.js.map +1 -0
  34. package/dist/{daemon-CknkDjd6.js → daemon-DDMRbz4T.js} +160 -5
  35. package/dist/daemon-DDMRbz4T.js.map +1 -0
  36. package/dist/{daemon-entry-C6pxdily.js → daemon-entry-zOC1revO.js} +204 -5
  37. package/dist/daemon-entry-zOC1revO.js.map +1 -0
  38. package/dist/{db-Dkn9XhT-.js → db-9P6MnmdC.js} +237 -86
  39. package/dist/db-9P6MnmdC.js.map +1 -0
  40. package/dist/{derive-Dl0z2OWi.js → derive-MTdClnkc.js} +29 -14
  41. package/dist/{derive-Dl0z2OWi.js.map → derive-MTdClnkc.js.map} +1 -1
  42. package/dist/{dev-DAcZR4Pp.js → dev-BDN_OKXA.js} +183 -23
  43. package/dist/dev-BDN_OKXA.js.map +1 -0
  44. package/dist/dev-db/connection-banner.d.ts +47 -0
  45. package/dist/dev-db/daemon.d.ts +41 -0
  46. package/dist/dev-db/notification-proxy.d.ts +82 -1
  47. package/dist/dev-db/pull.d.ts +33 -5
  48. package/dist/dev-db/state.d.ts +9 -0
  49. package/dist/{dev-preflight-CorMg7Ub.js → dev-preflight-FT9GbJVj.js} +73 -4
  50. package/dist/dev-preflight-FT9GbJVj.js.map +1 -0
  51. package/dist/{dev-server-B1UYifZJ.js → dev-server-CIZwAfZa.js} +14 -3
  52. package/dist/dev-server-CIZwAfZa.js.map +1 -0
  53. package/dist/{doctor-Ds_lzc9p.js → doctor-D8YXz7df.js} +6 -6
  54. package/dist/doctor-D8YXz7df.js.map +1 -0
  55. package/dist/{eject-CJHkfnat.js → eject-izG2KjMo.js} +3 -4
  56. package/dist/{eject-CJHkfnat.js.map → eject-izG2KjMo.js.map} +1 -1
  57. package/dist/{fold-static-BC9CYicw.js → fold-static-CybSay_W.js} +3 -3
  58. package/dist/{fold-static-BC9CYicw.js.map → fold-static-CybSay_W.js.map} +1 -1
  59. package/dist/{generate_sdk-kfkZmG-d.js → generate_sdk-B4R65oUV.js} +22 -12
  60. package/dist/generate_sdk-B4R65oUV.js.map +1 -0
  61. package/dist/index.es.js +36 -37
  62. package/dist/index.es.js.map +1 -1
  63. package/dist/{init-CjoJoThg.js → init-DYaeppEN.js} +52 -63
  64. package/dist/init-DYaeppEN.js.map +1 -0
  65. package/dist/invoked-command-CG_rIa_o.js +55 -0
  66. package/dist/invoked-command-CG_rIa_o.js.map +1 -0
  67. package/dist/last-dev-database-B_75fxZC.js +2 -0
  68. package/dist/{manifest-DRp0UVTj.js → manifest-nvPdLlJY.js} +2 -2
  69. package/dist/{manifest-DRp0UVTj.js.map → manifest-nvPdLlJY.js.map} +1 -1
  70. package/dist/{normalize-imports-BHxL2d05.js → normalize-imports-BYw8ad87.js} +3 -3
  71. package/dist/{normalize-imports-BHxL2d05.js.map → normalize-imports-BYw8ad87.js.map} +1 -1
  72. package/dist/precompress.d.ts +0 -7
  73. package/dist/{prepare-C1iaiTC4.js → prepare-1rVNd2PF.js} +7 -6
  74. package/dist/prepare-1rVNd2PF.js.map +1 -0
  75. package/dist/project-g4K6kV1t.js +701 -0
  76. package/dist/project-g4K6kV1t.js.map +1 -0
  77. package/dist/{pull-COpNJFzY.js → pull-DDMpsZ5r.js} +143 -63
  78. package/dist/pull-DDMpsZ5r.js.map +1 -0
  79. package/dist/refusal-ReeeGP8a.js +26 -0
  80. package/dist/refusal-ReeeGP8a.js.map +1 -0
  81. package/dist/resolve-BQUAC3Wi.js +2 -0
  82. package/dist/resources/derive.d.ts +9 -0
  83. package/dist/{resources-CLm6g0br.js → resources-CovP8AZ-.js} +4 -5
  84. package/dist/{resources-CLm6g0br.js.map → resources-CovP8AZ-.js.map} +1 -1
  85. package/dist/{schema-BKdh6UoE.js → schema-nuxvvc50.js} +5 -6
  86. package/dist/{schema-BKdh6UoE.js.map → schema-nuxvvc50.js.map} +1 -1
  87. package/dist/{skills-DEhYiPeO.js → skills-DeUIsZcT.js} +3 -4
  88. package/dist/{skills-DEhYiPeO.js.map → skills-DeUIsZcT.js.map} +1 -1
  89. package/dist/{start-BsPJedPQ.js → start-RzZqDroW.js} +20 -7
  90. package/dist/start-RzZqDroW.js.map +1 -0
  91. package/dist/{state-CCB-FAQ9.js → state-CYdBvnwA.js} +3 -2
  92. package/dist/state-CYdBvnwA.js.map +1 -0
  93. package/dist/{status-D_B3tlDy.js → status-D9Zm88NZ.js} +11 -12
  94. package/dist/{status-D_B3tlDy.js.map → status-D9Zm88NZ.js.map} +1 -1
  95. package/dist/{storage-CtB8polS.js → storage-BB3umRfw.js} +3 -4
  96. package/dist/{storage-CtB8polS.js.map → storage-BB3umRfw.js.map} +1 -1
  97. package/dist/{telemetry-EMFRVRQi.js → telemetry-HfU5uAh1.js} +2 -2
  98. package/dist/{telemetry-EMFRVRQi.js.map → telemetry-HfU5uAh1.js.map} +1 -1
  99. package/dist/{telemetry-BYmPU-Ji.js → telemetry-TseNLk6X.js} +4 -4
  100. package/dist/{telemetry-BYmPU-Ji.js.map → telemetry-TseNLk6X.js.map} +1 -1
  101. package/dist/{upgrade-DbuKX9lv.js → upgrade-apiPKKE_.js} +29 -6
  102. package/dist/{upgrade-DbuKX9lv.js.map → upgrade-apiPKKE_.js.map} +1 -1
  103. package/dist/utils/args.d.ts +45 -3
  104. package/dist/utils/boot-retry.d.ts +41 -0
  105. package/dist/utils/dev-preflight.d.ts +34 -1
  106. package/dist/utils/dev-server.d.ts +11 -0
  107. package/dist/utils/project.d.ts +5 -1
  108. package/dist/utils/redact.d.ts +10 -0
  109. package/dist/utils/refusal.d.ts +18 -0
  110. package/dist/utils/unknown-command.d.ts +4 -0
  111. package/package.json +7 -7
  112. package/templates/overlays/baas/README.md +2 -1
  113. package/templates/resource-graphs/blank.json +25 -0
  114. package/templates/resource-graphs/blog.json +28 -0
  115. package/templates/resource-graphs/ecommerce.json +28 -0
  116. package/templates/resource-graphs/headless.json +22 -0
  117. package/templates/template/.env.example +11 -4
  118. package/templates/template/frontend/src/App.tsx +10 -9
  119. package/templates/template/frontend/vite.config.ts +7 -73
  120. package/dist/api-keys-Bc4FJKYp.js.map +0 -1
  121. package/dist/args-CcBphFdj.js +0 -237
  122. package/dist/args-CcBphFdj.js.map +0 -1
  123. package/dist/auth-BL7bNQVu.js.map +0 -1
  124. package/dist/bundle-CqURVN-K.js.map +0 -1
  125. package/dist/context-BM-oOMNX.js.map +0 -1
  126. package/dist/daemon-CknkDjd6.js.map +0 -1
  127. package/dist/daemon-entry-C6pxdily.js.map +0 -1
  128. package/dist/db-Dkn9XhT-.js.map +0 -1
  129. package/dist/dev-DAcZR4Pp.js.map +0 -1
  130. package/dist/dev-preflight-CorMg7Ub.js.map +0 -1
  131. package/dist/dev-server-B1UYifZJ.js.map +0 -1
  132. package/dist/doctor-Ds_lzc9p.js.map +0 -1
  133. package/dist/generate_sdk-kfkZmG-d.js.map +0 -1
  134. package/dist/init-CjoJoThg.js.map +0 -1
  135. package/dist/last-dev-database-BEBPfAMY.js +0 -2
  136. package/dist/prepare-C1iaiTC4.js.map +0 -1
  137. package/dist/project-Wp2zoyeN.js +0 -349
  138. package/dist/project-Wp2zoyeN.js.map +0 -1
  139. package/dist/pull-COpNJFzY.js.map +0 -1
  140. package/dist/resolve-b_p9bmdH.js +0 -2
  141. package/dist/start-BsPJedPQ.js.map +0 -1
  142. package/dist/state-CCB-FAQ9.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"manifest-DRp0UVTj.js","names":[],"sources":["../src/manifest.ts"],"sourcesContent":["/**\n * Loading, validating and synthesizing `rebase.json`.\n *\n * The manifest declares *topology*: which runtime major a project targets and\n * which apps this repository contributes. It is deliberately small — schema,\n * security rules, hooks and functions stay in TypeScript, where a type system\n * can check them.\n *\n * Two properties matter more than the file format itself:\n *\n * - **A missing manifest is never an error.** Every project that exists today\n * predates this file. One is synthesized from the conventions the template\n * already follows, so nothing breaks and nobody is forced to migrate.\n * - **Validation reports every problem at once**, with the path to each. A\n * config file that surfaces its mistakes one run at a time is a bad config\n * file.\n */\nimport fs from \"fs\";\nimport path from \"path\";\nimport {\n findStorageSuffixCollision,\n parseAppAddress,\n reservedPrefixFor,\n type AppAddress,\n type ManagedCompatibility,\n type RebaseAppConfig,\n type RebaseBackendAppConfig,\n type RebaseProjectManifest,\n type RebaseStaticAppConfig\n} from \"@rebasepro/types\";\nimport { MANIFEST_FILENAME } from \"./utils/project\";\nimport { detectPackageManager, getPMCommands } from \"./utils/package-manager\";\n\n/** Runtime range written into new manifests. */\nexport const CURRENT_RUNTIME_RANGE = \"^1\";\n\n/** Conventional locations, matching what `rebase init` scaffolds. */\nexport const DEFAULT_CONFIG_DIR = \"config\";\nexport const DEFAULT_FUNCTIONS_DIR = \"backend/functions\";\nexport const DEFAULT_CRONS_DIR = \"backend/crons\";\nexport const DEFAULT_SCHEMA_FILE = \"backend/src/schema.generated.ts\";\n\nexport interface ManifestValidationIssue {\n /** Dotted path to the offending value, e.g. `apps.web.output`. */\n path: string;\n message: string;\n}\n\nexport interface LoadedManifest {\n manifest: RebaseProjectManifest;\n /** Where it came from — a real file, or inferred from the directory layout. */\n source: \"file\" | \"synthesized\";\n /** Absolute path to `rebase.json`, when one exists. */\n filePath?: string;\n}\n\nexport class ManifestError extends Error {\n constructor(message: string, readonly issues: ManifestValidationIssue[] = []) {\n super(message);\n this.name = \"ManifestError\";\n }\n}\n\nconst APP_TYPES = [\"backend\", \"static\"] as const;\n\n/**\n * App types that used to exist, and what replaced each one.\n *\n * Kept so a manifest written against the old vocabulary fails with the fix in\n * hand rather than with `must be one of: backend, static`, which tells a reader\n * what is wrong but not what to write instead.\n */\nconst REMOVED_APP_TYPES: Record<string, string> = {\n admin:\n \"the admin panel is an ordinary static app now — declare it as \" +\n '{ \"type\": \"static\", \"root\": \"frontend\", \"output\": \"frontend/dist\", \"path\": \"/admin\" }',\n custom:\n \"who owns the server is a property of the backend now — declare \" +\n '{ \"type\": \"backend\", \"runtime\": \"custom\" } instead of a separate app',\n mobile:\n \"mobile apps are no longer declared in the manifest — nothing consumed this type. \" +\n \"Remove the entry\"\n};\n\n/** Reserved because they name things in URLs and CLI output. */\nconst RESERVED_APP_NAMES = new Set([\"api\", \"health\", \"metrics\", \"livez\", \"_rebase\"]);\n\n/**\n * Validate a URL path an app is served at: a static app's `path`, or the path\n * part of it when `path` is a URL, and `cms`.\n *\n * Normalizes to \"no trailing slash, except for the root\" so that mounting and\n * the `REBASE_APP_BASE` build variable have one shape to reason about.\n *\n * `origin` is `https://<host>` when the path came out of a URL, and is only\n * used to phrase the fix in the form the author wrote: telling someone who\n * wrote `\"https://admin.example.com/cms/\"` to write `\"/cms\"` would be telling\n * them to drop the hostname.\n */\nfunction checkAppPath(\n value: unknown,\n fieldPath: string,\n issues: ManifestValidationIssue[],\n origin = \"\"\n): string | undefined {\n if (value === undefined) return undefined;\n if (typeof value !== \"string\" || !value.startsWith(\"/\")) {\n issues.push({\n path: fieldPath,\n message: 'must be an absolute path like \"/admin\"'\n });\n return undefined;\n }\n // Before the `..` and empty-segment rules, which it would otherwise trip\n // with a message about paths. `//admin.example.com` is a URL with the\n // scheme left off, and mounting it as a path is not what was meant.\n if (!origin && value.startsWith(\"//\")) {\n issues.push({\n path: fieldPath,\n message: `looks like a URL without its scheme — write \"https:${value}\" to give this app its own hostname`\n });\n return undefined;\n }\n if (value.includes(\"..\") || value.includes(\"//\")) {\n issues.push({\n path: fieldPath,\n message: origin\n ? `must have a plain path after the hostname, with no \"..\" and no empty segment — like \"${origin}/admin\"`\n : 'must be an absolute path like \"/admin\", with no \"..\" and no empty segment'\n });\n return undefined;\n }\n if (value !== \"/\" && value.endsWith(\"/\")) {\n issues.push({\n path: fieldPath,\n message: `must not end with a slash — write \"${origin}${value.slice(0, -1)}\", not \"${origin}${value}\"`\n });\n return undefined;\n }\n // Mounting is longest-path-first, so an app at `/api` outranks the API and\n // every request to it is answered with that app's index.html — a 200 of HTML\n // where the caller wanted JSON, from a deployment that looks healthy. A\n // hostname does not change that: the backend's routes are registered before\n // any app's, on every hostname the project answers on.\n const reserved = reservedPrefixFor(value);\n if (reserved) {\n issues.push({\n path: fieldPath,\n message: `cannot be \"${reserved}\" or nest under it — the backend serves that path` +\n (origin ? \" on every hostname, this one included\" : \"\") +\n \", and an app mounted there would answer the API's requests with its own index.html\"\n });\n return undefined;\n }\n return value;\n}\n\n/**\n * Validate a static app's `path`, which is either a path or an https URL that\n * also gives the app a hostname of its own.\n *\n * The split is {@link parseAppAddress}, shared with the control plane and the\n * runtime so that all three read one value the same way. What it leaves to the\n * caller — the path rules — are {@link checkAppPath}'s, applied to the path\n * part of either form.\n */\nfunction checkAppAddress(\n value: unknown,\n fieldPath: string,\n issues: ManifestValidationIssue[]\n): AppAddress | undefined {\n if (value === undefined) return undefined;\n if (typeof value !== \"string\") {\n issues.push({\n path: fieldPath,\n message: 'must be an absolute path like \"/admin\", or a URL like \"https://admin.example.com\"'\n });\n return undefined;\n }\n const parsed = parseAppAddress(value);\n if (!parsed.ok) {\n issues.push({ path: fieldPath, message: parsed.reason });\n return undefined;\n }\n const { host } = parsed.address;\n const appPath = checkAppPath(parsed.address.path, fieldPath, issues, host ? `https://${host}` : \"\");\n if (appPath === undefined) return undefined;\n return host ? { host, path: appPath } : { path: appPath };\n}\n\n/**\n * Where a validated static app answers: its hostname, if its `path` names one,\n * and the path under it.\n *\n * The manifest keeps `path` as it was written, URL and all, because it is also\n * what `writeManifest` puts back: storing the split pieces instead would have\n * `rebase eject` rewrite `\"https://admin.example.com\"` to `\"/\"` and drop the\n * hostname from a file the developer never asked it to touch. Everything that\n * mounts, builds or prints an app reads it through here instead, so there is\n * one reading of the field.\n *\n * Throws on a value `validateManifest` would have refused. Every caller holds a\n * manifest that came through it, so reaching the throw means one did not — and\n * guessing an address for it would build or mount the app somewhere it was\n * never declared.\n */\nexport function staticAppAddress(name: string, app: { path?: string }): AppAddress {\n if (app.path === undefined) return { path: \"/\" };\n const parsed = parseAppAddress(app.path);\n if (!parsed.ok) {\n throw new ManifestError(`apps.${name}.path ${parsed.reason}`, [{ path: `apps.${name}.path`, message: parsed.reason }]);\n }\n return parsed.address;\n}\n\n/**\n * An address as a developer would type it: the path alone for an app on every\n * hostname, the https URL for one on its own. The root of a hostname prints as\n * the bare origin — `https://admin.example.com`, which is how it is declared.\n */\nexport function formatAppAddress(address: AppAddress): string {\n if (!address.host) return address.path;\n return `https://${address.host}${address.path === \"/\" ? \"\" : address.path}`;\n}\n\n/**\n * The order static apps are mounted in, and the order a bundle lists them.\n *\n * Longest path first, so an app at `/` is registered after the apps beneath it\n * and its catch-all cannot claim their URLs. At an equal path, the app bound to\n * a hostname goes first: on that hostname it is the one that answers, and the\n * app on every hostname answers the rest. That is the runtime's matching rule,\n * restated as a sort so a bundle reads in the order it is served.\n */\nexport function compareMountOrder(a: AppAddress, b: AppAddress): number {\n if (a.path.length !== b.path.length) return b.path.length - a.path.length;\n return (a.host ? 0 : 1) - (b.host ? 0 : 1);\n}\n\n/**\n * Is `candidate` the path `parent`, or something beneath it?\n *\n * Segment-aware for the same reason `isForbiddenStaticPath` is: a plain\n * `startsWith` reads `/admin` as living under `/adm`.\n */\nfunction isUnderPath(candidate: string, parent: string): boolean {\n if (parent === \"/\") return true;\n return candidate === parent || candidate.startsWith(`${parent}/`);\n}\n\n/**\n * Validate where an app says it mounts the Rebase CMS.\n *\n * The same shape as {@link checkAppPath} — it is a URL path and ends up in the\n * same places — plus one rule of its own: it has to be inside the app declaring\n * it. The app's SPA fallback is what answers that URL, so a `cms` outside its\n * `path` names an address this app will never serve. That is a link the console\n * would then offer to a 404, which is worse than the missing link it replaces.\n *\n * Always a path, even on an app whose `path` is a URL. The app's address\n * already says which hostname it answers on, and a second hostname here could\n * only disagree with it; `cms` says where on that hostname the CMS is.\n */\nfunction checkCmsPath(\n value: unknown,\n app: AppAddress,\n fieldPath: string,\n issues: ManifestValidationIssue[]\n): string | undefined {\n if (value === undefined) return undefined;\n if (typeof value === \"string\" && (/^[a-z][a-z0-9+.-]*:/i.test(value) || value.startsWith(\"//\"))) {\n const parsed = parseAppAddress(value.startsWith(\"//\") ? `https:${value}` : value);\n issues.push({\n path: fieldPath,\n message: \"must be a path, not a URL — the CMS is a route of this app, which is served where its \" +\n \"`path` says\" + (parsed.ok ? `. Write \"${parsed.address.path}\"` : \"\")\n });\n return undefined;\n }\n const cms = checkAppPath(value, fieldPath, issues);\n if (cms === undefined) return undefined;\n if (!isUnderPath(cms, app.path)) {\n issues.push({\n path: fieldPath,\n message: `must be inside this app's path — it is at \"${formatAppAddress(app)}\", so it cannot serve \"${cms}\". ` +\n \"The CMS is a route of this app, not a separate deployment; if it really lives elsewhere, \" +\n \"declare that app and put `cms` on it instead\"\n });\n return undefined;\n }\n return cms;\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\n/**\n * Reject paths that escape the repository.\n *\n * A manifest is committed and reviewed, so this is not a security boundary so\n * much as a guard against `../../` typos that would otherwise have `rebase build`\n * writing outside the project.\n *\n * `mayEscape` is for `context`, and only for `context`. Every other path here\n * names something Rebase *reads* — collections, functions, the generated\n * schema, the built assets to serve — and those have to be inside the project\n * or the bundle cannot carry them. A Docker build context is the one field that\n * names something Rebase never opens: it is handed to `docker build`, and in\n * any workspace repository the thing it has to name is the workspace root,\n * above the app. That is not exotic. It is what pnpm, turbo and nx layouts all\n * look like, and it is what this repository's own reference project needs — its\n * Dockerfile's first instruction copies `pnpm-lock.yaml`, which does not exist\n * beside `rebase.json` and never will.\n *\n * Refusing it did not prevent the escape; it only stopped anyone declaring it.\n * `app/rebase.json` built from the monorepo root the whole time — via\n * `infra/cloudbuild.yaml`, which says `-f app/backend/Dockerfile .` — while the\n * manifest said the context was `app/` and `rebase build` printed a command\n * that dies on its first `COPY`.\n */\nfunction checkRelativePath(\n value: unknown,\n fieldPath: string,\n issues: ManifestValidationIssue[],\n { required, mayEscape = false }: { required: boolean; mayEscape?: boolean }\n): string | undefined {\n if (value === undefined) {\n if (required) issues.push({ path: fieldPath,\nmessage: \"is required\" });\n return undefined;\n }\n if (typeof value !== \"string\" || value.trim() === \"\") {\n issues.push({ path: fieldPath,\nmessage: \"must be a non-empty string\" });\n return undefined;\n }\n if (path.isAbsolute(value)) {\n issues.push({ path: fieldPath,\nmessage: \"must be a relative path, not absolute\" });\n return undefined;\n }\n const normalized = path.normalize(value);\n if (!mayEscape && (normalized === \"..\" || normalized.startsWith(`..${path.sep}`))) {\n issues.push({ path: fieldPath,\nmessage: \"must stay inside the project directory\" });\n return undefined;\n }\n return value;\n}\n\n/** Fields each app type understands. Anything else is a typo or a newer CLI. */\nconst KNOWN_APP_FIELDS: Record<string, readonly string[]> = {\n backend: [\"type\", \"runtime\", \"config\", \"functions\", \"crons\", \"schema\",\n \"usersCollection\", \"registerDatabase\", \"dockerfile\", \"context\", \"port\"],\n static: [\"type\", \"root\", \"build\", \"output\", \"path\", \"spa\", \"cms\"]\n};\n\n/**\n * Fields people reach for to give a static app a hostname, which is spelled as\n * a URL in `path` instead. Named so the warning says that, rather than \"your\n * CLI may be older than this manifest\" — which is false here and sends the\n * reader to upgrade a CLI that already does what they wanted.\n */\nconst HOSTNAME_FIELDS = new Set([\"host\", \"hostname\", \"domain\"]);\n\n/**\n * Report a field this CLI does not recognise.\n *\n * A warning rather than an error, and the distinction matters in both\n * directions. `\"pathh\": \"/admin\"` is a typo that would otherwise be silently\n * dropped — the app builds for `/`, mounts at `/`, and the only symptom is that\n * it is not where you put it. But an unknown field is also what an *older* CLI\n * sees when it opens a manifest written for a newer one, and refusing to build\n * over a field that is simply from the future would make every manifest addition\n * a breaking change.\n *\n * So: name it, suggest the near-miss, and carry on.\n */\nfunction warnUnknownFields(name: string, raw: Record<string, unknown>, type: string): void {\n const known = KNOWN_APP_FIELDS[type];\n if (!known) return;\n\n for (const field of Object.keys(raw)) {\n if (known.includes(field)) continue;\n if (type === \"static\" && HOSTNAME_FIELDS.has(field)) {\n const value = raw[field];\n const host = typeof value === \"string\" && value.trim() !== \"\"\n ? value.trim().replace(/^[a-z]+:\\/\\//i, \"\").replace(/\\/+$/, \"\")\n : \"admin.example.com\";\n const under = typeof raw.path === \"string\" && raw.path.startsWith(\"/\") && raw.path !== \"/\" ? raw.path : \"\";\n console.warn(\n `⚠ rebase.json: apps.${name}.${field} is not a field — it is ignored. To serve this app on its ` +\n `own hostname, write its path as a URL: \"path\": \"https://${host}${under}\"`\n );\n continue;\n }\n const suggestion = known.find(candidate => isNearMiss(candidate, field));\n console.warn(\n `⚠ rebase.json: apps.${name}.${field} is not a field this CLI knows.` +\n (suggestion ? ` Did you mean \"${suggestion}\"?` : \" It is ignored — your CLI may be older than this manifest.\")\n );\n }\n}\n\n/**\n * One edit apart, ignoring case: enough for a typo, tight enough to stay quiet.\n *\n * The same rule as `isNearMiss` in `@rebasepro/utils`, which the collection\n * validator uses for unknown `admin` keys. It is a copy rather than an import\n * because the CLI does not depend on `@rebasepro/utils`, and adding the\n * dependency is a lockfile change; collapsing the two is worth doing the next\n * time this package's dependencies are touched.\n */\nfunction isNearMiss(a: string, b: string): boolean {\n const x = a.toLowerCase();\n const y = b.toLowerCase();\n if (x === y) return true;\n if (Math.abs(x.length - y.length) > 1) return false;\n\n const [shorter, longer] = x.length <= y.length ? [x, y] : [y, x];\n let i = 0;\n let j = 0;\n let edits = 0;\n while (i < shorter.length && j < longer.length) {\n if (shorter[i] === longer[j]) { i++; j++; continue; }\n if (++edits > 1) return false;\n if (shorter.length === longer.length) i++;\n j++;\n }\n return edits + (longer.length - j) + (shorter.length - i) <= 1;\n}\n\nfunction validateApp(\n name: string,\n raw: unknown,\n issues: ManifestValidationIssue[]\n): RebaseAppConfig | undefined {\n const base = `apps.${name}`;\n\n if (!isRecord(raw)) {\n issues.push({ path: base,\nmessage: \"must be an object\" });\n return undefined;\n }\n\n const type = raw.type;\n if (typeof type === \"string\" && REMOVED_APP_TYPES[type]) {\n issues.push({ path: `${base}.type`,\nmessage: `\"${type}\" is no longer an app type — ${REMOVED_APP_TYPES[type]}` });\n return undefined;\n }\n if (typeof type !== \"string\" || !(APP_TYPES as readonly string[]).includes(type)) {\n issues.push({\n path: `${base}.type`,\n message: `must be one of: ${APP_TYPES.join(\", \")}`\n });\n return undefined;\n }\n\n warnUnknownFields(name, raw, type);\n\n switch (type) {\n case \"backend\": {\n checkRelativePath(raw.config, `${base}.config`, issues, { required: false });\n checkRelativePath(raw.functions, `${base}.functions`, issues, { required: false });\n checkRelativePath(raw.crons, `${base}.crons`, issues, { required: false });\n checkRelativePath(raw.schema, `${base}.schema`, issues, { required: false });\n checkRelativePath(raw.usersCollection, `${base}.usersCollection`, issues, { required: false });\n\n if (raw.mode !== undefined) {\n issues.push({\n path: `${base}.mode`,\n message:\n \"is no longer a field — collections come from the config directory when \" +\n \"it exists, and are introspected from the database when it does not\"\n });\n }\n\n const custom = raw.runtime === \"custom\";\n if (raw.runtime !== \"managed\" && !custom) {\n issues.push({\n path: `${base}.runtime`,\n message: 'is required, and must be \"managed\" or \"custom\"'\n });\n }\n\n // The image fields describe an image this repository builds. Under a\n // managed backend there is no such image, so accepting them silently\n // would be accepting a Dockerfile that never gets built.\n for (const field of [\"dockerfile\", \"context\", \"port\"] as const) {\n if (raw[field] !== undefined && !custom) {\n issues.push({\n path: `${base}.${field}`,\n message: 'only applies to a custom runtime — set \"runtime\": \"custom\" to build your own image'\n });\n }\n }\n checkRelativePath(raw.dockerfile, `${base}.dockerfile`, issues, { required: false });\n // `dockerfile` stays project-relative even when the context is\n // above the project: it names a file in THIS repository, and moving\n // the context should not rewrite it. `rebase build` re-expresses it\n // against the context when it prints the command.\n checkRelativePath(raw.context, `${base}.context`, issues, { required: false, mayEscape: true });\n if (raw.port !== undefined && (typeof raw.port !== \"number\" || !Number.isInteger(raw.port))) {\n issues.push({ path: `${base}.port`,\nmessage: \"must be an integer\" });\n }\n if (raw.registerDatabase !== undefined && typeof raw.registerDatabase !== \"boolean\") {\n issues.push({ path: `${base}.registerDatabase`,\nmessage: \"must be a boolean\" });\n }\n // Constructed, not asserted. Everything above pushes an issue for a\n // field it does not like and then leaves the field alone, so\n // `raw as unknown as RebaseAppConfig` handed the caller the *whole\n // parsed object* under a type describing only these fields — an\n // unknown key that `warnUnknownFields` merely warned about travelled\n // on as if it were part of the config, and a field that failed its\n // check above travelled on with the type saying it had passed.\n // Naming them is also what keeps this list and the interface in step:\n // a field added to `RebaseBackendAppConfig` and not validated here\n // now fails to compile instead of silently arriving unchecked.\n return {\n type: \"backend\",\n runtime: custom ? \"custom\" : \"managed\",\n config: raw.config as string | undefined,\n functions: raw.functions as string | undefined,\n crons: raw.crons as string | undefined,\n schema: raw.schema as string | undefined,\n usersCollection: raw.usersCollection as string | undefined,\n registerDatabase: raw.registerDatabase as boolean | undefined,\n dockerfile: raw.dockerfile as string | undefined,\n context: raw.context as string | undefined,\n port: raw.port as number | undefined\n };\n }\n case \"static\": {\n checkRelativePath(raw.root, `${base}.root`, issues, { required: true });\n checkRelativePath(raw.output, `${base}.output`, issues, { required: true });\n if (raw.build !== undefined && typeof raw.build !== \"string\") {\n issues.push({ path: `${base}.build`,\nmessage: \"must be a string command\" });\n }\n if (raw.spa !== undefined && typeof raw.spa !== \"boolean\") {\n issues.push({ path: `${base}.spa`,\nmessage: \"must be a boolean\" });\n }\n const address = checkAppAddress(raw.path, `${base}.path`, issues);\n checkCmsPath(raw.cms, address ?? { path: \"/\" }, `${base}.cms`, issues);\n // See the backend branch: constructed for the same reasons. `path`\n // is kept as written — see `staticAppAddress` for why the split\n // pieces are not what the manifest stores.\n return {\n type: \"static\",\n root: raw.root as string,\n build: raw.build as string | undefined,\n output: raw.output as string,\n path: address ? raw.path as string : undefined,\n spa: raw.spa as boolean | undefined,\n cms: raw.cms as string | undefined\n };\n }\n default:\n return undefined;\n }\n}\n\n/**\n * Validate a parsed manifest, collecting every problem.\n */\nexport function validateManifest(raw: unknown): {\n manifest?: RebaseProjectManifest;\n issues: ManifestValidationIssue[];\n} {\n const issues: ManifestValidationIssue[] = [];\n\n if (!isRecord(raw)) {\n return { issues: [{ path: \"\",\nmessage: `${MANIFEST_FILENAME} must contain a JSON object` }] };\n }\n\n if (typeof raw.rebase !== \"string\" || raw.rebase.trim() === \"\") {\n // `runtime` used to be this key. It now means who owns the backend\n // process, so a manifest still using it at the top level is naming the\n // wrong thing rather than merely missing a field.\n const message = typeof raw.runtime === \"string\"\n ? `is required, e.g. \"${CURRENT_RUNTIME_RANGE}\" — this is the top-level \"runtime\" key renamed, ` +\n 'because \"runtime\" now means \"managed\" or \"custom\" on the backend app'\n : `is required, e.g. \"${CURRENT_RUNTIME_RANGE}\"`;\n issues.push({ path: \"rebase\",\nmessage });\n }\n\n if (!isRecord(raw.apps)) {\n issues.push({ path: \"apps\",\nmessage: \"is required and must be an object\" });\n return { issues };\n }\n\n const apps: Record<string, RebaseAppConfig> = {};\n let backendCount = 0;\n\n for (const [name, value] of Object.entries(raw.apps)) {\n if (!/^[a-z0-9][a-z0-9-]*$/.test(name)) {\n issues.push({\n path: `apps.${name}`,\n message: \"name must be lowercase alphanumeric with dashes (it appears in URLs)\"\n });\n continue;\n }\n if (RESERVED_APP_NAMES.has(name)) {\n issues.push({ path: `apps.${name}`,\nmessage: \"name is reserved\" });\n continue;\n }\n\n const app = validateApp(name, value, issues);\n if (!app) continue;\n if (app.type === \"backend\") backendCount++;\n apps[name] = app;\n }\n\n // One backend per *project*. A repository declaring two would have two sets\n // of collections claiming the same database and the same API surface.\n if (backendCount > 1) {\n issues.push({\n path: \"apps\",\n message: \"a project may declare at most one backend app\"\n });\n }\n\n // Two apps at one address is not a preference conflict — the first one\n // mounted swallows the other's URLs, and the loser looks like it deployed\n // fine. The address is the hostname and the path together: an app at \"/\" on\n // admin.example.com and one at \"/\" everywhere else are two different\n // addresses, and that pair is the point of giving an app a hostname. An app\n // with no hostname is its own value here, not a wildcard that collides with\n // every hostname — at an equal path the one naming the hostname wins there.\n const byAddress = new Map<string, string>();\n for (const [name, app] of Object.entries(apps)) {\n if (app.type !== \"static\") continue;\n const address = staticAppAddress(name, app);\n const key = `${address.host ?? \"\"} ${address.path}`;\n const owner = byAddress.get(key);\n if (owner) {\n issues.push({\n path: `apps.${name}.path`,\n message: `two apps cannot serve the same path — \"${owner}\" is already at \"${formatAppAddress(address)}\"`\n });\n continue;\n }\n byAddress.set(key, name);\n }\n\n // One CMS per project. Not a taste ruling: everything downstream — the\n // console's button, `rebase apps list`, the app row that records it — talks\n // about \"the project's CMS\", and two of them would make every one of those\n // pick arbitrarily. A repository that genuinely wants a second admin surface\n // has two apps and can say so by name.\n const withCms = Object.entries(apps).filter(\n ([, app]) => app.type === \"static\" && typeof app.cms === \"string\"\n );\n if (withCms.length > 1) {\n for (const [name] of withCms.slice(1)) {\n issues.push({\n path: `apps.${name}.cms`,\n message: `a project has one CMS — \"${withCms[0][0]}\" already declares one`\n });\n }\n }\n\n refuseStorageBlock(raw.storage, issues);\n\n // Carried rather than dropped. This key is the only repository-wide privacy\n // control the CLI honours, and a key that does not survive a parse does not\n // survive a rewrite either: `writeManifest` builds its output from what this\n // returned, so `rebase eject` deleted a committed `\"telemetry\": false` and\n // every developer who had ever opted in silently resumed sending events.\n //\n // Only a boolean is a value here — `true` is ignored elsewhere, loudly, but\n // it is still valid. A string `\"false\"` is the mistake that would leave\n // sharing on while looking like it was off, so it is named rather than\n // quietly discarded.\n let telemetry: boolean | undefined;\n if (raw.telemetry !== undefined) {\n if (typeof raw.telemetry === \"boolean\") {\n telemetry = raw.telemetry;\n } else {\n issues.push({\n path: \"telemetry\",\n message: \"must be a boolean — only `false` does anything, and it opts this repository out of usage sharing\"\n });\n }\n }\n\n if (issues.length > 0) return { issues };\n\n return {\n manifest: {\n $schema: typeof raw.$schema === \"string\" ? raw.$schema : undefined,\n rebase: raw.rebase as string,\n apps,\n ...(telemetry !== undefined ? { telemetry } : {})\n },\n issues\n };\n}\n\n/**\n * Refuse a hand-written `storage` block.\n *\n * Buckets are declared in config code now — `bucket(\"media\")` — and the graph\n * is generated from those declarations into `rebase.resources.json`. Two homes\n * for one concept is what this replaced, and the reason it had to go is that\n * the runtime *merged* them: a bucket declared in both had one engine kept and\n * the other silently discarded.\n *\n * Refused rather than ignored. A key that still parses and no longer does\n * anything is the same failure wearing a nicer hat — the author sees valid\n * JSON, the platform sees a bucket, and the runtime has neither.\n */\nfunction refuseStorageBlock(raw: unknown, issues: ManifestValidationIssue[]): undefined {\n if (raw === undefined) return undefined;\n issues.push({\n path: \"storage\",\n message:\n \"is no longer read. Declare each bucket in your config's resources.ts — \" +\n 'bucket(\"media\", { engine: \"s3\" }) — then run `rebase resources --write`, ' +\n \"which generates rebase.resources.json. Refused rather than ignored: this \" +\n \"block used to be merged with the code's declarations and silently win.\"\n });\n return undefined;\n}\n\n/**\n * Infer a manifest from a directory that does not have one.\n *\n * This mirrors what the template scaffolds, which is what makes adopting the\n * manifest a no-op for existing projects: the synthesized result is what they\n * would have written by hand. A field is written only when it says something\n * its default does not, as the scaffold's own `rebase.json` does — `spa`,\n * `functions` and `crons` restate their defaults, so they are left out.\n *\n * The static app's build command is the project's package manager, spelled the\n * way `rebase init` spells it, so a pnpm project is never built with npm.\n *\n * The static app is named `web`, where the scaffold says `admin`. The name is an\n * app's identity on a deployed project: Rebase Cloud registered `web` for every\n * project that deployed without a manifest, and its next deploy has to name the\n * same app. Nor can an inference know where an app mounts the CMS (`cms`), which\n * is a client-side route. `rebase apps init --force` therefore keeps the name,\n * `path` and `cms` a manifest already declares for the app it infers again.\n *\n * The backend's `runtime` is inferred from whether the repository declares a\n * **Dockerfile** — the thing that actually builds an image — and from nothing\n * else. It used to be inferred from the presence of `backend/src/index.ts`,\n * which every scaffolded project had whether or not it wanted its own server, so\n * projects predating the manifest silently landed on the custom runtime and paid\n * for it.\n */\nexport function synthesizeManifest(projectRoot: string): RebaseProjectManifest {\n const exists = (relative: string): boolean => fs.existsSync(path.join(projectRoot, relative));\n const apps: Record<string, RebaseAppConfig> = {};\n\n const hasConfig = exists(DEFAULT_CONFIG_DIR);\n const hasBackend = exists(\"backend\");\n const dockerfile = [\"Dockerfile\", \"backend/Dockerfile\"].find(exists);\n\n if (hasBackend || hasConfig) {\n const backend: RebaseBackendAppConfig = dockerfile\n ? { type: \"backend\",\nruntime: \"custom\",\ndockerfile,\ncontext: \".\" }\n : { type: \"backend\",\nruntime: \"managed\" };\n apps.backend = backend;\n\n // Say it out loud. The file looks exactly like the server and is never\n // loaded, which used to be discovered only after a deploy answered 404\n // on every route defined in it.\n if (!dockerfile && exists(\"backend/src/index.ts\")) {\n console.warn(\n \"⚠ backend/src/index.ts exists but this project's backend is managed — it is\\n\" +\n \" never loaded. Delete it, or move it aside and run `rebase eject`, which\\n\" +\n \" writes an entrypoint of its own and owns the image. Eject does not adopt\\n\" +\n \" this file: it refuses to replace it unless you pass --force.\"\n );\n }\n }\n\n if (exists(\"frontend\")) {\n apps.web = {\n type: \"static\",\n root: \"frontend\",\n build: getPMCommands(detectPackageManager(projectRoot)).runWorkspace(\"frontend\", \"build\").join(\" \"),\n output: \"frontend/dist\",\n path: \"/\"\n };\n }\n\n return { rebase: CURRENT_RUNTIME_RANGE,\napps };\n}\n\nexport function manifestPath(projectRoot: string): string {\n return path.join(projectRoot, MANIFEST_FILENAME);\n}\n\nexport function manifestExists(projectRoot: string): boolean {\n return fs.existsSync(manifestPath(projectRoot));\n}\n\n/**\n * Read the manifest, falling back to a synthesized one.\n *\n * A malformed manifest throws — unlike a missing one. Silently ignoring a file\n * the developer wrote, and building something else instead, is the worst\n * available behaviour.\n */\nexport function loadManifest(projectRoot: string): LoadedManifest {\n const filePath = manifestPath(projectRoot);\n\n if (!fs.existsSync(filePath)) {\n return { manifest: synthesizeManifest(projectRoot),\nsource: \"synthesized\" };\n }\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(fs.readFileSync(filePath, \"utf8\"));\n } catch (err) {\n throw new ManifestError(\n `${MANIFEST_FILENAME} is not valid JSON: ${err instanceof Error ? err.message : String(err)}`\n );\n }\n\n const { manifest, issues } = validateManifest(parsed);\n if (!manifest) {\n throw new ManifestError(`${MANIFEST_FILENAME} is invalid`, issues);\n }\n\n return { manifest,\nsource: \"file\",\nfilePath };\n}\n\n/** The keys `writeManifest` knows how to write. Everything else is carried through. */\nconst MODELLED_MANIFEST_KEYS = [\"$schema\", \"rebase\", \"apps\", \"storage\", \"telemetry\"];\n\n/**\n * What is on disk right now, or `{}` — this is a *rewrite*, so the file that is\n * about to be replaced is the only record of the keys the caller did not model.\n * Unparseable is treated as absent: `loadManifest` refuses malformed JSON long\n * before anything gets here, and a writer is not the place to fail on it.\n */\nfunction readManifestObject(filePath: string): Record<string, unknown> {\n try {\n const parsed: unknown = JSON.parse(fs.readFileSync(filePath, \"utf8\"));\n return isRecord(parsed) ? parsed : {};\n } catch {\n return {};\n }\n}\n\n/**\n * Write a manifest, with a trailing newline so it plays well with other tools.\n *\n * **Every key on disk survives.** This used to emit exactly `$schema`, `rebase`\n * and `apps`, so a rewrite deleted the rest of the file — and two commands with\n * no visible relationship to either key rewrite it: `rebase eject` and\n * `rebase apps init --force`. A repository that had committed\n * `\"telemetry\": false` lost its opt-out, and a multi-bucket project lost its\n * whole `storage` block, in a commit whose stated change was `runtime: custom`.\n *\n * So: the caller's manifest wins for what it models, the file supplies the rest.\n * `storage` and `telemetry` fall back to the file because the callers that\n * synthesize a manifest (`apps init --force`) cannot know them — they are\n * authored, not inferred — and unknown top-level keys are copied verbatim\n * rather than listed, since a hand-listed set loses the next key too.\n */\nexport function writeManifest(projectRoot: string, manifest: RebaseProjectManifest): string {\n const filePath = manifestPath(projectRoot);\n const existing = readManifestObject(filePath);\n\n const carried: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(existing)) {\n if (!MODELLED_MANIFEST_KEYS.includes(key)) carried[key] = value;\n }\n\n const schema = manifest.$schema\n ?? (typeof existing.$schema === \"string\" ? existing.$schema : undefined)\n ?? \"https://rebase.pro/schemas/rebase.json\";\n const telemetry = manifest.telemetry ?? (typeof existing.telemetry === \"boolean\" ? existing.telemetry : undefined);\n\n const ordered = {\n $schema: schema,\n rebase: manifest.rebase,\n apps: manifest.apps,\n ...(telemetry !== undefined ? { telemetry } : {}),\n ...carried\n };\n fs.writeFileSync(filePath, `${JSON.stringify(ordered, null, 4)}\\n`, \"utf8\");\n return filePath;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Queries\n// ─────────────────────────────────────────────────────────────────────────────\n\n/** Find the single backend app, if this repository declares one. */\nexport function findBackendApp(\n manifest: RebaseProjectManifest\n): { name: string; app: RebaseBackendAppConfig } | undefined {\n for (const [name, app] of Object.entries(manifest.apps)) {\n if (app.type === \"backend\") return { name,\napp: app as RebaseBackendAppConfig };\n }\n return undefined;\n}\n\n/**\n * Where this project mounts the Rebase CMS, if it says.\n *\n * The CMS is a component inside a developer's own app, so its address is a\n * client-side route: no build artifact, no running server and no control plane\n * can observe it. {@link RebaseStaticAppConfig.cms} is the only place that fact\n * is ever written down, and this is the one reader of it — so that \"the\n * project's CMS\" means the same thing to `rebase dev`, `rebase apps list`, the\n * bundle it builds and the console that shows it.\n *\n * Returns the *serving* app alongside the path, because a caller with a base URL\n * needs to know which app answers there — and because the path alone cannot say\n * whether the CMS is the whole of an app or one route of it.\n *\n * `path` is always a path. `host` is the serving app's hostname, when its `path`\n * gives it one: the CMS is then at `https://<host><path>` and nowhere else, and\n * a caller holding a local or project base URL must not put the path on that.\n */\nexport function cmsMountOf(\n manifest: RebaseProjectManifest\n): { appName: string; app: RebaseStaticAppConfig; path: string; host?: string } | undefined {\n for (const [name, app] of Object.entries(manifest.apps)) {\n if (app.type !== \"static\" || typeof app.cms !== \"string\") continue;\n const { host } = staticAppAddress(name, app);\n return { appName: name, app: app as RebaseStaticAppConfig, path: app.cms, ...(host ? { host } : {}) };\n }\n return undefined;\n}\n\n/**\n * The app a deploy targets: the one named, or the obvious one.\n *\n * A repository declares apps; a project owns them. So \"which app\" is a question\n * a multi-repo setup asks routinely and a single-app repository should never\n * have to answer — hence the fallbacks, in the order that makes each of them\n * unambiguous:\n *\n * - A name, when one is given. Wrong names fail loudly, listing what is there;\n * guessing past a typo would deploy the wrong app to a live project.\n * - The backend, when this repository declares one. It is the app whose deploy\n * people mean when they do not say, and it is what every existing invocation\n * already did.\n * - The only app, when there is exactly one. This is what makes a static-only\n * repository — an admin panel, a marketing site — deploy with no argument.\n * - Otherwise a refusal that lists the choices. A repository with two static\n * apps and no backend has no obvious default, and picking one would publish\n * somebody's admin panel at their marketing domain.\n */\nexport function selectDeployApp(\n manifest: RebaseProjectManifest,\n requested?: string\n): { name: string; app: RebaseAppConfig } {\n const entries = Object.entries(manifest.apps).map(([name, app]) => ({ name,\napp }));\n\n if (requested) {\n const match = entries.find(entry => entry.name === requested);\n if (!match) {\n const known = entries.map(e => e.name).join(\", \") || \"none\";\n throw new ManifestError(\n `This repository declares no app named \"${requested}\". It declares: ${known}.`\n );\n }\n return match;\n }\n\n const backend = findBackendApp(manifest);\n if (backend) return { name: backend.name,\napp: backend.app };\n\n if (entries.length === 1) return entries[0];\n\n if (entries.length === 0) {\n throw new ManifestError(\n `${MANIFEST_FILENAME} declares no apps, so there is nothing to deploy.`\n );\n }\n\n throw new ManifestError(\n \"This repository declares several apps and no backend, so there is no obvious one to deploy. \" +\n `Name one: ${entries.map(e => e.name).join(\", \")}.`\n );\n}\n\n/** Apps that produce build output, in the order they should be built. */\nexport function buildableApps(\n manifest: RebaseProjectManifest\n): { name: string; app: RebaseAppConfig }[] {\n // Backend first: a static app's build may consume an SDK generated from the\n // backend's collections, so building it second is the order that works.\n const entries = Object.entries(manifest.apps).map(([name, app]) => ({ name,\napp }));\n const rank = (app: RebaseAppConfig): number => (app.type === \"backend\" ? 0 : 1);\n return entries.sort((a, b) => rank(a.app) - rank(b.app));\n}\n\n/**\n * Decide whether a project can run on the managed runtime, and say why not.\n *\n * \"Not eligible\" is never a dead end — it selects the custom-runtime path, which\n * still deploys. The reasons exist so the answer is actionable rather than a\n * verdict.\n */\nexport function assessManagedCompatibility(\n manifest: RebaseProjectManifest\n): ManagedCompatibility {\n const backend = findBackendApp(manifest);\n\n if (!backend) {\n return {\n eligible: false,\n reasons: [\n \"No backend app is declared in this repository. Only the repository that \" +\n \"declares the backend selects the runtime.\"\n ]\n };\n }\n\n // Declared, not deduced. A project on the custom runtime is there because\n // somebody wrote it down, which is the whole point of the field.\n if (backend.app.runtime === \"custom\") {\n return {\n eligible: false,\n reasons: [\n `App \"${backend.name}\" declares runtime: \"custom\", so it builds and runs its ` +\n \"own image. The managed runtime boots the platform image with your bundle; \" +\n \"the custom runtime deploys exactly the same way, from your Dockerfile.\"\n ]\n };\n }\n\n return { eligible: true,\nreasons: [] };\n}\n\n/**\n * Resolve a backend app's directories against the conventions it omits.\n *\n * `hasCollections` replaces the old `mode: \"cms\" | \"baas\"` field. Where the\n * collections come from was never an independent choice: either they are\n * declared in code and the bundle ships them, or they are not and the runtime\n * introspects the live database at boot. Declaring it separately only created\n * the contradictory state — code-first declared, no collections anywhere.\n *\n * `hasConfig` is deliberately a **separate** question. A headless project has no\n * `config/collections`, but it may still ship a config package — that is where\n * the `storageAuthorize` hook lives, and storage is not under row-level\n * security, so without one the server refuses to boot with storage enabled.\n * Collapsing the two would leave a headless project nowhere to put it.\n */\nexport function resolveBackendPaths(\n app: RebaseBackendAppConfig,\n projectRoot: string\n): {\n config: string;\n functions: string;\n crons: string;\n schema: string;\n usersCollection: string;\n /** Whether a config package exists — hooks, storage authorization. */\n hasConfig: boolean;\n /** Whether collections are declared in code, under `<config>/collections`. */\n hasCollections: boolean;\n} {\n const config = app.config ?? DEFAULT_CONFIG_DIR;\n return {\n config,\n functions: app.functions ?? DEFAULT_FUNCTIONS_DIR,\n crons: app.crons ?? DEFAULT_CRONS_DIR,\n schema: app.schema ?? DEFAULT_SCHEMA_FILE,\n usersCollection: app.usersCollection ?? \"collections/users\",\n hasConfig: fs.existsSync(path.join(projectRoot, config)),\n hasCollections: fs.existsSync(path.join(projectRoot, config, \"collections\"))\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"manifest-nvPdLlJY.js","names":[],"sources":["../src/manifest.ts"],"sourcesContent":["/**\n * Loading, validating and synthesizing `rebase.json`.\n *\n * The manifest declares *topology*: which runtime major a project targets and\n * which apps this repository contributes. It is deliberately small — schema,\n * security rules, hooks and functions stay in TypeScript, where a type system\n * can check them.\n *\n * Two properties matter more than the file format itself:\n *\n * - **A missing manifest is never an error.** Every project that exists today\n * predates this file. One is synthesized from the conventions the template\n * already follows, so nothing breaks and nobody is forced to migrate.\n * - **Validation reports every problem at once**, with the path to each. A\n * config file that surfaces its mistakes one run at a time is a bad config\n * file.\n */\nimport fs from \"fs\";\nimport path from \"path\";\nimport {\n findStorageSuffixCollision,\n parseAppAddress,\n reservedPrefixFor,\n type AppAddress,\n type ManagedCompatibility,\n type RebaseAppConfig,\n type RebaseBackendAppConfig,\n type RebaseProjectManifest,\n type RebaseStaticAppConfig\n} from \"@rebasepro/types\";\nimport { MANIFEST_FILENAME } from \"./utils/project\";\nimport { detectPackageManager, getPMCommands } from \"./utils/package-manager\";\n\n/** Runtime range written into new manifests. */\nexport const CURRENT_RUNTIME_RANGE = \"^1\";\n\n/** Conventional locations, matching what `rebase init` scaffolds. */\nexport const DEFAULT_CONFIG_DIR = \"config\";\nexport const DEFAULT_FUNCTIONS_DIR = \"backend/functions\";\nexport const DEFAULT_CRONS_DIR = \"backend/crons\";\nexport const DEFAULT_SCHEMA_FILE = \"backend/src/schema.generated.ts\";\n\nexport interface ManifestValidationIssue {\n /** Dotted path to the offending value, e.g. `apps.web.output`. */\n path: string;\n message: string;\n}\n\nexport interface LoadedManifest {\n manifest: RebaseProjectManifest;\n /** Where it came from — a real file, or inferred from the directory layout. */\n source: \"file\" | \"synthesized\";\n /** Absolute path to `rebase.json`, when one exists. */\n filePath?: string;\n}\n\nexport class ManifestError extends Error {\n constructor(message: string, readonly issues: ManifestValidationIssue[] = []) {\n super(message);\n this.name = \"ManifestError\";\n }\n}\n\nconst APP_TYPES = [\"backend\", \"static\"] as const;\n\n/**\n * App types that used to exist, and what replaced each one.\n *\n * Kept so a manifest written against the old vocabulary fails with the fix in\n * hand rather than with `must be one of: backend, static`, which tells a reader\n * what is wrong but not what to write instead.\n */\nconst REMOVED_APP_TYPES: Record<string, string> = {\n admin:\n \"the admin panel is an ordinary static app now — declare it as \" +\n '{ \"type\": \"static\", \"root\": \"frontend\", \"output\": \"frontend/dist\", \"path\": \"/admin\" }',\n custom:\n \"who owns the server is a property of the backend now — declare \" +\n '{ \"type\": \"backend\", \"runtime\": \"custom\" } instead of a separate app',\n mobile:\n \"mobile apps are no longer declared in the manifest — nothing consumed this type. \" +\n \"Remove the entry\"\n};\n\n/** Reserved because they name things in URLs and CLI output. */\nconst RESERVED_APP_NAMES = new Set([\"api\", \"health\", \"metrics\", \"livez\", \"_rebase\"]);\n\n/**\n * Validate a URL path an app is served at: a static app's `path`, or the path\n * part of it when `path` is a URL, and `cms`.\n *\n * Normalizes to \"no trailing slash, except for the root\" so that mounting and\n * the `REBASE_APP_BASE` build variable have one shape to reason about.\n *\n * `origin` is `https://<host>` when the path came out of a URL, and is only\n * used to phrase the fix in the form the author wrote: telling someone who\n * wrote `\"https://admin.example.com/cms/\"` to write `\"/cms\"` would be telling\n * them to drop the hostname.\n */\nfunction checkAppPath(\n value: unknown,\n fieldPath: string,\n issues: ManifestValidationIssue[],\n origin = \"\"\n): string | undefined {\n if (value === undefined) return undefined;\n if (typeof value !== \"string\" || !value.startsWith(\"/\")) {\n issues.push({\n path: fieldPath,\n message: 'must be an absolute path like \"/admin\"'\n });\n return undefined;\n }\n // Before the `..` and empty-segment rules, which it would otherwise trip\n // with a message about paths. `//admin.example.com` is a URL with the\n // scheme left off, and mounting it as a path is not what was meant.\n if (!origin && value.startsWith(\"//\")) {\n issues.push({\n path: fieldPath,\n message: `looks like a URL without its scheme — write \"https:${value}\" to give this app its own hostname`\n });\n return undefined;\n }\n if (value.includes(\"..\") || value.includes(\"//\")) {\n issues.push({\n path: fieldPath,\n message: origin\n ? `must have a plain path after the hostname, with no \"..\" and no empty segment — like \"${origin}/admin\"`\n : 'must be an absolute path like \"/admin\", with no \"..\" and no empty segment'\n });\n return undefined;\n }\n if (value !== \"/\" && value.endsWith(\"/\")) {\n issues.push({\n path: fieldPath,\n message: `must not end with a slash — write \"${origin}${value.slice(0, -1)}\", not \"${origin}${value}\"`\n });\n return undefined;\n }\n // Mounting is longest-path-first, so an app at `/api` outranks the API and\n // every request to it is answered with that app's index.html — a 200 of HTML\n // where the caller wanted JSON, from a deployment that looks healthy. A\n // hostname does not change that: the backend's routes are registered before\n // any app's, on every hostname the project answers on.\n const reserved = reservedPrefixFor(value);\n if (reserved) {\n issues.push({\n path: fieldPath,\n message: `cannot be \"${reserved}\" or nest under it — the backend serves that path` +\n (origin ? \" on every hostname, this one included\" : \"\") +\n \", and an app mounted there would answer the API's requests with its own index.html\"\n });\n return undefined;\n }\n return value;\n}\n\n/**\n * Validate a static app's `path`, which is either a path or an https URL that\n * also gives the app a hostname of its own.\n *\n * The split is {@link parseAppAddress}, shared with the control plane and the\n * runtime so that all three read one value the same way. What it leaves to the\n * caller — the path rules — are {@link checkAppPath}'s, applied to the path\n * part of either form.\n */\nfunction checkAppAddress(\n value: unknown,\n fieldPath: string,\n issues: ManifestValidationIssue[]\n): AppAddress | undefined {\n if (value === undefined) return undefined;\n if (typeof value !== \"string\") {\n issues.push({\n path: fieldPath,\n message: 'must be an absolute path like \"/admin\", or a URL like \"https://admin.example.com\"'\n });\n return undefined;\n }\n const parsed = parseAppAddress(value);\n if (!parsed.ok) {\n issues.push({ path: fieldPath, message: parsed.reason });\n return undefined;\n }\n const { host } = parsed.address;\n const appPath = checkAppPath(parsed.address.path, fieldPath, issues, host ? `https://${host}` : \"\");\n if (appPath === undefined) return undefined;\n return host ? { host, path: appPath } : { path: appPath };\n}\n\n/**\n * Where a validated static app answers: its hostname, if its `path` names one,\n * and the path under it.\n *\n * The manifest keeps `path` as it was written, URL and all, because it is also\n * what `writeManifest` puts back: storing the split pieces instead would have\n * `rebase eject` rewrite `\"https://admin.example.com\"` to `\"/\"` and drop the\n * hostname from a file the developer never asked it to touch. Everything that\n * mounts, builds or prints an app reads it through here instead, so there is\n * one reading of the field.\n *\n * Throws on a value `validateManifest` would have refused. Every caller holds a\n * manifest that came through it, so reaching the throw means one did not — and\n * guessing an address for it would build or mount the app somewhere it was\n * never declared.\n */\nexport function staticAppAddress(name: string, app: { path?: string }): AppAddress {\n if (app.path === undefined) return { path: \"/\" };\n const parsed = parseAppAddress(app.path);\n if (!parsed.ok) {\n throw new ManifestError(`apps.${name}.path ${parsed.reason}`, [{ path: `apps.${name}.path`, message: parsed.reason }]);\n }\n return parsed.address;\n}\n\n/**\n * An address as a developer would type it: the path alone for an app on every\n * hostname, the https URL for one on its own. The root of a hostname prints as\n * the bare origin — `https://admin.example.com`, which is how it is declared.\n */\nexport function formatAppAddress(address: AppAddress): string {\n if (!address.host) return address.path;\n return `https://${address.host}${address.path === \"/\" ? \"\" : address.path}`;\n}\n\n/**\n * The order static apps are mounted in, and the order a bundle lists them.\n *\n * Longest path first, so an app at `/` is registered after the apps beneath it\n * and its catch-all cannot claim their URLs. At an equal path, the app bound to\n * a hostname goes first: on that hostname it is the one that answers, and the\n * app on every hostname answers the rest. That is the runtime's matching rule,\n * restated as a sort so a bundle reads in the order it is served.\n */\nexport function compareMountOrder(a: AppAddress, b: AppAddress): number {\n if (a.path.length !== b.path.length) return b.path.length - a.path.length;\n return (a.host ? 0 : 1) - (b.host ? 0 : 1);\n}\n\n/**\n * Is `candidate` the path `parent`, or something beneath it?\n *\n * Segment-aware for the same reason `isForbiddenStaticPath` is: a plain\n * `startsWith` reads `/admin` as living under `/adm`.\n */\nfunction isUnderPath(candidate: string, parent: string): boolean {\n if (parent === \"/\") return true;\n return candidate === parent || candidate.startsWith(`${parent}/`);\n}\n\n/**\n * Validate where an app says it mounts the Rebase CMS.\n *\n * The same shape as {@link checkAppPath} — it is a URL path and ends up in the\n * same places — plus one rule of its own: it has to be inside the app declaring\n * it. The app's SPA fallback is what answers that URL, so a `cms` outside its\n * `path` names an address this app will never serve. That is a link the console\n * would then offer to a 404, which is worse than the missing link it replaces.\n *\n * Always a path, even on an app whose `path` is a URL. The app's address\n * already says which hostname it answers on, and a second hostname here could\n * only disagree with it; `cms` says where on that hostname the CMS is.\n */\nfunction checkCmsPath(\n value: unknown,\n app: AppAddress,\n fieldPath: string,\n issues: ManifestValidationIssue[]\n): string | undefined {\n if (value === undefined) return undefined;\n if (typeof value === \"string\" && (/^[a-z][a-z0-9+.-]*:/i.test(value) || value.startsWith(\"//\"))) {\n const parsed = parseAppAddress(value.startsWith(\"//\") ? `https:${value}` : value);\n issues.push({\n path: fieldPath,\n message: \"must be a path, not a URL — the CMS is a route of this app, which is served where its \" +\n \"`path` says\" + (parsed.ok ? `. Write \"${parsed.address.path}\"` : \"\")\n });\n return undefined;\n }\n const cms = checkAppPath(value, fieldPath, issues);\n if (cms === undefined) return undefined;\n if (!isUnderPath(cms, app.path)) {\n issues.push({\n path: fieldPath,\n message: `must be inside this app's path — it is at \"${formatAppAddress(app)}\", so it cannot serve \"${cms}\". ` +\n \"The CMS is a route of this app, not a separate deployment; if it really lives elsewhere, \" +\n \"declare that app and put `cms` on it instead\"\n });\n return undefined;\n }\n return cms;\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\n/**\n * Reject paths that escape the repository.\n *\n * A manifest is committed and reviewed, so this is not a security boundary so\n * much as a guard against `../../` typos that would otherwise have `rebase build`\n * writing outside the project.\n *\n * `mayEscape` is for `context`, and only for `context`. Every other path here\n * names something Rebase *reads* — collections, functions, the generated\n * schema, the built assets to serve — and those have to be inside the project\n * or the bundle cannot carry them. A Docker build context is the one field that\n * names something Rebase never opens: it is handed to `docker build`, and in\n * any workspace repository the thing it has to name is the workspace root,\n * above the app. That is not exotic. It is what pnpm, turbo and nx layouts all\n * look like, and it is what this repository's own reference project needs — its\n * Dockerfile's first instruction copies `pnpm-lock.yaml`, which does not exist\n * beside `rebase.json` and never will.\n *\n * Refusing it did not prevent the escape; it only stopped anyone declaring it.\n * `app/rebase.json` built from the monorepo root the whole time — via\n * `infra/cloudbuild.yaml`, which says `-f app/backend/Dockerfile .` — while the\n * manifest said the context was `app/` and `rebase build` printed a command\n * that dies on its first `COPY`.\n */\nfunction checkRelativePath(\n value: unknown,\n fieldPath: string,\n issues: ManifestValidationIssue[],\n { required, mayEscape = false }: { required: boolean; mayEscape?: boolean }\n): string | undefined {\n if (value === undefined) {\n if (required) issues.push({ path: fieldPath,\nmessage: \"is required\" });\n return undefined;\n }\n if (typeof value !== \"string\" || value.trim() === \"\") {\n issues.push({ path: fieldPath,\nmessage: \"must be a non-empty string\" });\n return undefined;\n }\n if (path.isAbsolute(value)) {\n issues.push({ path: fieldPath,\nmessage: \"must be a relative path, not absolute\" });\n return undefined;\n }\n const normalized = path.normalize(value);\n if (!mayEscape && (normalized === \"..\" || normalized.startsWith(`..${path.sep}`))) {\n issues.push({ path: fieldPath,\nmessage: \"must stay inside the project directory\" });\n return undefined;\n }\n return value;\n}\n\n/** Fields each app type understands. Anything else is a typo or a newer CLI. */\nconst KNOWN_APP_FIELDS: Record<string, readonly string[]> = {\n backend: [\"type\", \"runtime\", \"config\", \"functions\", \"crons\", \"schema\",\n \"usersCollection\", \"registerDatabase\", \"dockerfile\", \"context\", \"port\"],\n static: [\"type\", \"root\", \"build\", \"output\", \"path\", \"spa\", \"cms\"]\n};\n\n/**\n * Fields people reach for to give a static app a hostname, which is spelled as\n * a URL in `path` instead. Named so the warning says that, rather than \"your\n * CLI may be older than this manifest\" — which is false here and sends the\n * reader to upgrade a CLI that already does what they wanted.\n */\nconst HOSTNAME_FIELDS = new Set([\"host\", \"hostname\", \"domain\"]);\n\n/**\n * Report a field this CLI does not recognise.\n *\n * A warning rather than an error, and the distinction matters in both\n * directions. `\"pathh\": \"/admin\"` is a typo that would otherwise be silently\n * dropped — the app builds for `/`, mounts at `/`, and the only symptom is that\n * it is not where you put it. But an unknown field is also what an *older* CLI\n * sees when it opens a manifest written for a newer one, and refusing to build\n * over a field that is simply from the future would make every manifest addition\n * a breaking change.\n *\n * So: name it, suggest the near-miss, and carry on.\n */\nfunction warnUnknownFields(name: string, raw: Record<string, unknown>, type: string): void {\n const known = KNOWN_APP_FIELDS[type];\n if (!known) return;\n\n for (const field of Object.keys(raw)) {\n if (known.includes(field)) continue;\n if (type === \"static\" && HOSTNAME_FIELDS.has(field)) {\n const value = raw[field];\n const host = typeof value === \"string\" && value.trim() !== \"\"\n ? value.trim().replace(/^[a-z]+:\\/\\//i, \"\").replace(/\\/+$/, \"\")\n : \"admin.example.com\";\n const under = typeof raw.path === \"string\" && raw.path.startsWith(\"/\") && raw.path !== \"/\" ? raw.path : \"\";\n console.warn(\n `⚠ rebase.json: apps.${name}.${field} is not a field — it is ignored. To serve this app on its ` +\n `own hostname, write its path as a URL: \"path\": \"https://${host}${under}\"`\n );\n continue;\n }\n const suggestion = known.find(candidate => isNearMiss(candidate, field));\n console.warn(\n `⚠ rebase.json: apps.${name}.${field} is not a field this CLI knows.` +\n (suggestion ? ` Did you mean \"${suggestion}\"?` : \" It is ignored — your CLI may be older than this manifest.\")\n );\n }\n}\n\n/**\n * One edit apart, ignoring case: enough for a typo, tight enough to stay quiet.\n *\n * The same rule as `isNearMiss` in `@rebasepro/utils`, which the collection\n * validator uses for unknown `admin` keys. It is a copy rather than an import\n * because the CLI does not depend on `@rebasepro/utils`, and adding the\n * dependency is a lockfile change; collapsing the two is worth doing the next\n * time this package's dependencies are touched.\n */\nfunction isNearMiss(a: string, b: string): boolean {\n const x = a.toLowerCase();\n const y = b.toLowerCase();\n if (x === y) return true;\n if (Math.abs(x.length - y.length) > 1) return false;\n\n const [shorter, longer] = x.length <= y.length ? [x, y] : [y, x];\n let i = 0;\n let j = 0;\n let edits = 0;\n while (i < shorter.length && j < longer.length) {\n if (shorter[i] === longer[j]) { i++; j++; continue; }\n if (++edits > 1) return false;\n if (shorter.length === longer.length) i++;\n j++;\n }\n return edits + (longer.length - j) + (shorter.length - i) <= 1;\n}\n\nfunction validateApp(\n name: string,\n raw: unknown,\n issues: ManifestValidationIssue[]\n): RebaseAppConfig | undefined {\n const base = `apps.${name}`;\n\n if (!isRecord(raw)) {\n issues.push({ path: base,\nmessage: \"must be an object\" });\n return undefined;\n }\n\n const type = raw.type;\n if (typeof type === \"string\" && REMOVED_APP_TYPES[type]) {\n issues.push({ path: `${base}.type`,\nmessage: `\"${type}\" is no longer an app type — ${REMOVED_APP_TYPES[type]}` });\n return undefined;\n }\n if (typeof type !== \"string\" || !(APP_TYPES as readonly string[]).includes(type)) {\n issues.push({\n path: `${base}.type`,\n message: `must be one of: ${APP_TYPES.join(\", \")}`\n });\n return undefined;\n }\n\n warnUnknownFields(name, raw, type);\n\n switch (type) {\n case \"backend\": {\n checkRelativePath(raw.config, `${base}.config`, issues, { required: false });\n checkRelativePath(raw.functions, `${base}.functions`, issues, { required: false });\n checkRelativePath(raw.crons, `${base}.crons`, issues, { required: false });\n checkRelativePath(raw.schema, `${base}.schema`, issues, { required: false });\n checkRelativePath(raw.usersCollection, `${base}.usersCollection`, issues, { required: false });\n\n if (raw.mode !== undefined) {\n issues.push({\n path: `${base}.mode`,\n message:\n \"is no longer a field — collections come from the config directory when \" +\n \"it exists, and are introspected from the database when it does not\"\n });\n }\n\n const custom = raw.runtime === \"custom\";\n if (raw.runtime !== \"managed\" && !custom) {\n issues.push({\n path: `${base}.runtime`,\n message: 'is required, and must be \"managed\" or \"custom\"'\n });\n }\n\n // The image fields describe an image this repository builds. Under a\n // managed backend there is no such image, so accepting them silently\n // would be accepting a Dockerfile that never gets built.\n for (const field of [\"dockerfile\", \"context\", \"port\"] as const) {\n if (raw[field] !== undefined && !custom) {\n issues.push({\n path: `${base}.${field}`,\n message: 'only applies to a custom runtime — set \"runtime\": \"custom\" to build your own image'\n });\n }\n }\n checkRelativePath(raw.dockerfile, `${base}.dockerfile`, issues, { required: false });\n // `dockerfile` stays project-relative even when the context is\n // above the project: it names a file in THIS repository, and moving\n // the context should not rewrite it. `rebase build` re-expresses it\n // against the context when it prints the command.\n checkRelativePath(raw.context, `${base}.context`, issues, { required: false, mayEscape: true });\n if (raw.port !== undefined && (typeof raw.port !== \"number\" || !Number.isInteger(raw.port))) {\n issues.push({ path: `${base}.port`,\nmessage: \"must be an integer\" });\n }\n if (raw.registerDatabase !== undefined && typeof raw.registerDatabase !== \"boolean\") {\n issues.push({ path: `${base}.registerDatabase`,\nmessage: \"must be a boolean\" });\n }\n // Constructed, not asserted. Everything above pushes an issue for a\n // field it does not like and then leaves the field alone, so\n // `raw as unknown as RebaseAppConfig` handed the caller the *whole\n // parsed object* under a type describing only these fields — an\n // unknown key that `warnUnknownFields` merely warned about travelled\n // on as if it were part of the config, and a field that failed its\n // check above travelled on with the type saying it had passed.\n // Naming them is also what keeps this list and the interface in step:\n // a field added to `RebaseBackendAppConfig` and not validated here\n // now fails to compile instead of silently arriving unchecked.\n return {\n type: \"backend\",\n runtime: custom ? \"custom\" : \"managed\",\n config: raw.config as string | undefined,\n functions: raw.functions as string | undefined,\n crons: raw.crons as string | undefined,\n schema: raw.schema as string | undefined,\n usersCollection: raw.usersCollection as string | undefined,\n registerDatabase: raw.registerDatabase as boolean | undefined,\n dockerfile: raw.dockerfile as string | undefined,\n context: raw.context as string | undefined,\n port: raw.port as number | undefined\n };\n }\n case \"static\": {\n checkRelativePath(raw.root, `${base}.root`, issues, { required: true });\n checkRelativePath(raw.output, `${base}.output`, issues, { required: true });\n if (raw.build !== undefined && typeof raw.build !== \"string\") {\n issues.push({ path: `${base}.build`,\nmessage: \"must be a string command\" });\n }\n if (raw.spa !== undefined && typeof raw.spa !== \"boolean\") {\n issues.push({ path: `${base}.spa`,\nmessage: \"must be a boolean\" });\n }\n const address = checkAppAddress(raw.path, `${base}.path`, issues);\n checkCmsPath(raw.cms, address ?? { path: \"/\" }, `${base}.cms`, issues);\n // See the backend branch: constructed for the same reasons. `path`\n // is kept as written — see `staticAppAddress` for why the split\n // pieces are not what the manifest stores.\n return {\n type: \"static\",\n root: raw.root as string,\n build: raw.build as string | undefined,\n output: raw.output as string,\n path: address ? raw.path as string : undefined,\n spa: raw.spa as boolean | undefined,\n cms: raw.cms as string | undefined\n };\n }\n default:\n return undefined;\n }\n}\n\n/**\n * Validate a parsed manifest, collecting every problem.\n */\nexport function validateManifest(raw: unknown): {\n manifest?: RebaseProjectManifest;\n issues: ManifestValidationIssue[];\n} {\n const issues: ManifestValidationIssue[] = [];\n\n if (!isRecord(raw)) {\n return { issues: [{ path: \"\",\nmessage: `${MANIFEST_FILENAME} must contain a JSON object` }] };\n }\n\n if (typeof raw.rebase !== \"string\" || raw.rebase.trim() === \"\") {\n // `runtime` used to be this key. It now means who owns the backend\n // process, so a manifest still using it at the top level is naming the\n // wrong thing rather than merely missing a field.\n const message = typeof raw.runtime === \"string\"\n ? `is required, e.g. \"${CURRENT_RUNTIME_RANGE}\" — this is the top-level \"runtime\" key renamed, ` +\n 'because \"runtime\" now means \"managed\" or \"custom\" on the backend app'\n : `is required, e.g. \"${CURRENT_RUNTIME_RANGE}\"`;\n issues.push({ path: \"rebase\",\nmessage });\n }\n\n if (!isRecord(raw.apps)) {\n issues.push({ path: \"apps\",\nmessage: \"is required and must be an object\" });\n return { issues };\n }\n\n const apps: Record<string, RebaseAppConfig> = {};\n let backendCount = 0;\n\n for (const [name, value] of Object.entries(raw.apps)) {\n if (!/^[a-z0-9][a-z0-9-]*$/.test(name)) {\n issues.push({\n path: `apps.${name}`,\n message: \"name must be lowercase alphanumeric with dashes (it appears in URLs)\"\n });\n continue;\n }\n if (RESERVED_APP_NAMES.has(name)) {\n issues.push({ path: `apps.${name}`,\nmessage: \"name is reserved\" });\n continue;\n }\n\n const app = validateApp(name, value, issues);\n if (!app) continue;\n if (app.type === \"backend\") backendCount++;\n apps[name] = app;\n }\n\n // One backend per *project*. A repository declaring two would have two sets\n // of collections claiming the same database and the same API surface.\n if (backendCount > 1) {\n issues.push({\n path: \"apps\",\n message: \"a project may declare at most one backend app\"\n });\n }\n\n // Two apps at one address is not a preference conflict — the first one\n // mounted swallows the other's URLs, and the loser looks like it deployed\n // fine. The address is the hostname and the path together: an app at \"/\" on\n // admin.example.com and one at \"/\" everywhere else are two different\n // addresses, and that pair is the point of giving an app a hostname. An app\n // with no hostname is its own value here, not a wildcard that collides with\n // every hostname — at an equal path the one naming the hostname wins there.\n const byAddress = new Map<string, string>();\n for (const [name, app] of Object.entries(apps)) {\n if (app.type !== \"static\") continue;\n const address = staticAppAddress(name, app);\n const key = `${address.host ?? \"\"} ${address.path}`;\n const owner = byAddress.get(key);\n if (owner) {\n issues.push({\n path: `apps.${name}.path`,\n message: `two apps cannot serve the same path — \"${owner}\" is already at \"${formatAppAddress(address)}\"`\n });\n continue;\n }\n byAddress.set(key, name);\n }\n\n // One CMS per project. Not a taste ruling: everything downstream — the\n // console's button, `rebase apps list`, the app row that records it — talks\n // about \"the project's CMS\", and two of them would make every one of those\n // pick arbitrarily. A repository that genuinely wants a second admin surface\n // has two apps and can say so by name.\n const withCms = Object.entries(apps).filter(\n ([, app]) => app.type === \"static\" && typeof app.cms === \"string\"\n );\n if (withCms.length > 1) {\n for (const [name] of withCms.slice(1)) {\n issues.push({\n path: `apps.${name}.cms`,\n message: `a project has one CMS — \"${withCms[0][0]}\" already declares one`\n });\n }\n }\n\n refuseStorageBlock(raw.storage, issues);\n\n // Carried rather than dropped. This key is the only repository-wide privacy\n // control the CLI honours, and a key that does not survive a parse does not\n // survive a rewrite either: `writeManifest` builds its output from what this\n // returned, so `rebase eject` deleted a committed `\"telemetry\": false` and\n // every developer who had ever opted in silently resumed sending events.\n //\n // Only a boolean is a value here — `true` is ignored elsewhere, loudly, but\n // it is still valid. A string `\"false\"` is the mistake that would leave\n // sharing on while looking like it was off, so it is named rather than\n // quietly discarded.\n let telemetry: boolean | undefined;\n if (raw.telemetry !== undefined) {\n if (typeof raw.telemetry === \"boolean\") {\n telemetry = raw.telemetry;\n } else {\n issues.push({\n path: \"telemetry\",\n message: \"must be a boolean — only `false` does anything, and it opts this repository out of usage sharing\"\n });\n }\n }\n\n if (issues.length > 0) return { issues };\n\n return {\n manifest: {\n $schema: typeof raw.$schema === \"string\" ? raw.$schema : undefined,\n rebase: raw.rebase as string,\n apps,\n ...(telemetry !== undefined ? { telemetry } : {})\n },\n issues\n };\n}\n\n/**\n * Refuse a hand-written `storage` block.\n *\n * Buckets are declared in config code now — `bucket(\"media\")` — and the graph\n * is generated from those declarations into `rebase.resources.json`. Two homes\n * for one concept is what this replaced, and the reason it had to go is that\n * the runtime *merged* them: a bucket declared in both had one engine kept and\n * the other silently discarded.\n *\n * Refused rather than ignored. A key that still parses and no longer does\n * anything is the same failure wearing a nicer hat — the author sees valid\n * JSON, the platform sees a bucket, and the runtime has neither.\n */\nfunction refuseStorageBlock(raw: unknown, issues: ManifestValidationIssue[]): undefined {\n if (raw === undefined) return undefined;\n issues.push({\n path: \"storage\",\n message:\n \"is no longer read. Declare each bucket in your config's resources.ts — \" +\n 'bucket(\"media\", { engine: \"s3\" }) — then run `rebase resources --write`, ' +\n \"which generates rebase.resources.json. Refused rather than ignored: this \" +\n \"block used to be merged with the code's declarations and silently win.\"\n });\n return undefined;\n}\n\n/**\n * Infer a manifest from a directory that does not have one.\n *\n * This mirrors what the template scaffolds, which is what makes adopting the\n * manifest a no-op for existing projects: the synthesized result is what they\n * would have written by hand. A field is written only when it says something\n * its default does not, as the scaffold's own `rebase.json` does — `spa`,\n * `functions` and `crons` restate their defaults, so they are left out.\n *\n * The static app's build command is the project's package manager, spelled the\n * way `rebase init` spells it, so a pnpm project is never built with npm.\n *\n * The static app is named `web`, where the scaffold says `admin`. The name is an\n * app's identity on a deployed project: Rebase Cloud registered `web` for every\n * project that deployed without a manifest, and its next deploy has to name the\n * same app. Nor can an inference know where an app mounts the CMS (`cms`), which\n * is a client-side route. `rebase apps init --force` therefore keeps the name,\n * `path` and `cms` a manifest already declares for the app it infers again.\n *\n * The backend's `runtime` is inferred from whether the repository declares a\n * **Dockerfile** — the thing that actually builds an image — and from nothing\n * else. It used to be inferred from the presence of `backend/src/index.ts`,\n * which every scaffolded project had whether or not it wanted its own server, so\n * projects predating the manifest silently landed on the custom runtime and paid\n * for it.\n */\nexport function synthesizeManifest(projectRoot: string): RebaseProjectManifest {\n const exists = (relative: string): boolean => fs.existsSync(path.join(projectRoot, relative));\n const apps: Record<string, RebaseAppConfig> = {};\n\n const hasConfig = exists(DEFAULT_CONFIG_DIR);\n const hasBackend = exists(\"backend\");\n const dockerfile = [\"Dockerfile\", \"backend/Dockerfile\"].find(exists);\n\n if (hasBackend || hasConfig) {\n const backend: RebaseBackendAppConfig = dockerfile\n ? { type: \"backend\",\nruntime: \"custom\",\ndockerfile,\ncontext: \".\" }\n : { type: \"backend\",\nruntime: \"managed\" };\n apps.backend = backend;\n\n // Say it out loud. The file looks exactly like the server and is never\n // loaded, which used to be discovered only after a deploy answered 404\n // on every route defined in it.\n if (!dockerfile && exists(\"backend/src/index.ts\")) {\n console.warn(\n \"⚠ backend/src/index.ts exists but this project's backend is managed — it is\\n\" +\n \" never loaded. Delete it, or move it aside and run `rebase eject`, which\\n\" +\n \" writes an entrypoint of its own and owns the image. Eject does not adopt\\n\" +\n \" this file: it refuses to replace it unless you pass --force.\"\n );\n }\n }\n\n if (exists(\"frontend\")) {\n apps.web = {\n type: \"static\",\n root: \"frontend\",\n build: getPMCommands(detectPackageManager(projectRoot)).runWorkspace(\"frontend\", \"build\").join(\" \"),\n output: \"frontend/dist\",\n path: \"/\"\n };\n }\n\n return { rebase: CURRENT_RUNTIME_RANGE,\napps };\n}\n\nexport function manifestPath(projectRoot: string): string {\n return path.join(projectRoot, MANIFEST_FILENAME);\n}\n\nexport function manifestExists(projectRoot: string): boolean {\n return fs.existsSync(manifestPath(projectRoot));\n}\n\n/**\n * Read the manifest, falling back to a synthesized one.\n *\n * A malformed manifest throws — unlike a missing one. Silently ignoring a file\n * the developer wrote, and building something else instead, is the worst\n * available behaviour.\n */\nexport function loadManifest(projectRoot: string): LoadedManifest {\n const filePath = manifestPath(projectRoot);\n\n if (!fs.existsSync(filePath)) {\n return { manifest: synthesizeManifest(projectRoot),\nsource: \"synthesized\" };\n }\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(fs.readFileSync(filePath, \"utf8\"));\n } catch (err) {\n throw new ManifestError(\n `${MANIFEST_FILENAME} is not valid JSON: ${err instanceof Error ? err.message : String(err)}`\n );\n }\n\n const { manifest, issues } = validateManifest(parsed);\n if (!manifest) {\n throw new ManifestError(`${MANIFEST_FILENAME} is invalid`, issues);\n }\n\n return { manifest,\nsource: \"file\",\nfilePath };\n}\n\n/** The keys `writeManifest` knows how to write. Everything else is carried through. */\nconst MODELLED_MANIFEST_KEYS = [\"$schema\", \"rebase\", \"apps\", \"storage\", \"telemetry\"];\n\n/**\n * What is on disk right now, or `{}` — this is a *rewrite*, so the file that is\n * about to be replaced is the only record of the keys the caller did not model.\n * Unparseable is treated as absent: `loadManifest` refuses malformed JSON long\n * before anything gets here, and a writer is not the place to fail on it.\n */\nfunction readManifestObject(filePath: string): Record<string, unknown> {\n try {\n const parsed: unknown = JSON.parse(fs.readFileSync(filePath, \"utf8\"));\n return isRecord(parsed) ? parsed : {};\n } catch {\n return {};\n }\n}\n\n/**\n * Write a manifest, with a trailing newline so it plays well with other tools.\n *\n * **Every key on disk survives.** This used to emit exactly `$schema`, `rebase`\n * and `apps`, so a rewrite deleted the rest of the file — and two commands with\n * no visible relationship to either key rewrite it: `rebase eject` and\n * `rebase apps init --force`. A repository that had committed\n * `\"telemetry\": false` lost its opt-out, and a multi-bucket project lost its\n * whole `storage` block, in a commit whose stated change was `runtime: custom`.\n *\n * So: the caller's manifest wins for what it models, the file supplies the rest.\n * `storage` and `telemetry` fall back to the file because the callers that\n * synthesize a manifest (`apps init --force`) cannot know them — they are\n * authored, not inferred — and unknown top-level keys are copied verbatim\n * rather than listed, since a hand-listed set loses the next key too.\n */\nexport function writeManifest(projectRoot: string, manifest: RebaseProjectManifest): string {\n const filePath = manifestPath(projectRoot);\n const existing = readManifestObject(filePath);\n\n const carried: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(existing)) {\n if (!MODELLED_MANIFEST_KEYS.includes(key)) carried[key] = value;\n }\n\n const schema = manifest.$schema\n ?? (typeof existing.$schema === \"string\" ? existing.$schema : undefined)\n ?? \"https://rebase.pro/schemas/rebase.json\";\n const telemetry = manifest.telemetry ?? (typeof existing.telemetry === \"boolean\" ? existing.telemetry : undefined);\n\n const ordered = {\n $schema: schema,\n rebase: manifest.rebase,\n apps: manifest.apps,\n ...(telemetry !== undefined ? { telemetry } : {}),\n ...carried\n };\n fs.writeFileSync(filePath, `${JSON.stringify(ordered, null, 4)}\\n`, \"utf8\");\n return filePath;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Queries\n// ─────────────────────────────────────────────────────────────────────────────\n\n/** Find the single backend app, if this repository declares one. */\nexport function findBackendApp(\n manifest: RebaseProjectManifest\n): { name: string; app: RebaseBackendAppConfig } | undefined {\n for (const [name, app] of Object.entries(manifest.apps)) {\n if (app.type === \"backend\") return { name,\napp: app as RebaseBackendAppConfig };\n }\n return undefined;\n}\n\n/**\n * Where this project mounts the Rebase CMS, if it says.\n *\n * The CMS is a component inside a developer's own app, so its address is a\n * client-side route: no build artifact, no running server and no control plane\n * can observe it. {@link RebaseStaticAppConfig.cms} is the only place that fact\n * is ever written down, and this is the one reader of it — so that \"the\n * project's CMS\" means the same thing to `rebase dev`, `rebase apps list`, the\n * bundle it builds and the console that shows it.\n *\n * Returns the *serving* app alongside the path, because a caller with a base URL\n * needs to know which app answers there — and because the path alone cannot say\n * whether the CMS is the whole of an app or one route of it.\n *\n * `path` is always a path. `host` is the serving app's hostname, when its `path`\n * gives it one: the CMS is then at `https://<host><path>` and nowhere else, and\n * a caller holding a local or project base URL must not put the path on that.\n */\nexport function cmsMountOf(\n manifest: RebaseProjectManifest\n): { appName: string; app: RebaseStaticAppConfig; path: string; host?: string } | undefined {\n for (const [name, app] of Object.entries(manifest.apps)) {\n if (app.type !== \"static\" || typeof app.cms !== \"string\") continue;\n const { host } = staticAppAddress(name, app);\n return { appName: name, app: app as RebaseStaticAppConfig, path: app.cms, ...(host ? { host } : {}) };\n }\n return undefined;\n}\n\n/**\n * The app a deploy targets: the one named, or the obvious one.\n *\n * A repository declares apps; a project owns them. So \"which app\" is a question\n * a multi-repo setup asks routinely and a single-app repository should never\n * have to answer — hence the fallbacks, in the order that makes each of them\n * unambiguous:\n *\n * - A name, when one is given. Wrong names fail loudly, listing what is there;\n * guessing past a typo would deploy the wrong app to a live project.\n * - The backend, when this repository declares one. It is the app whose deploy\n * people mean when they do not say, and it is what every existing invocation\n * already did.\n * - The only app, when there is exactly one. This is what makes a static-only\n * repository — an admin panel, a marketing site — deploy with no argument.\n * - Otherwise a refusal that lists the choices. A repository with two static\n * apps and no backend has no obvious default, and picking one would publish\n * somebody's admin panel at their marketing domain.\n */\nexport function selectDeployApp(\n manifest: RebaseProjectManifest,\n requested?: string\n): { name: string; app: RebaseAppConfig } {\n const entries = Object.entries(manifest.apps).map(([name, app]) => ({ name,\napp }));\n\n if (requested) {\n const match = entries.find(entry => entry.name === requested);\n if (!match) {\n const known = entries.map(e => e.name).join(\", \") || \"none\";\n throw new ManifestError(\n `This repository declares no app named \"${requested}\". It declares: ${known}.`\n );\n }\n return match;\n }\n\n const backend = findBackendApp(manifest);\n if (backend) return { name: backend.name,\napp: backend.app };\n\n if (entries.length === 1) return entries[0];\n\n if (entries.length === 0) {\n throw new ManifestError(\n `${MANIFEST_FILENAME} declares no apps, so there is nothing to deploy.`\n );\n }\n\n throw new ManifestError(\n \"This repository declares several apps and no backend, so there is no obvious one to deploy. \" +\n `Name one: ${entries.map(e => e.name).join(\", \")}.`\n );\n}\n\n/** Apps that produce build output, in the order they should be built. */\nexport function buildableApps(\n manifest: RebaseProjectManifest\n): { name: string; app: RebaseAppConfig }[] {\n // Backend first: a static app's build may consume an SDK generated from the\n // backend's collections, so building it second is the order that works.\n const entries = Object.entries(manifest.apps).map(([name, app]) => ({ name,\napp }));\n const rank = (app: RebaseAppConfig): number => (app.type === \"backend\" ? 0 : 1);\n return entries.sort((a, b) => rank(a.app) - rank(b.app));\n}\n\n/**\n * Decide whether a project can run on the managed runtime, and say why not.\n *\n * \"Not eligible\" is never a dead end — it selects the custom-runtime path, which\n * still deploys. The reasons exist so the answer is actionable rather than a\n * verdict.\n */\nexport function assessManagedCompatibility(\n manifest: RebaseProjectManifest\n): ManagedCompatibility {\n const backend = findBackendApp(manifest);\n\n if (!backend) {\n return {\n eligible: false,\n reasons: [\n \"No backend app is declared in this repository. Only the repository that \" +\n \"declares the backend selects the runtime.\"\n ]\n };\n }\n\n // Declared, not deduced. A project on the custom runtime is there because\n // somebody wrote it down, which is the whole point of the field.\n if (backend.app.runtime === \"custom\") {\n return {\n eligible: false,\n reasons: [\n `App \"${backend.name}\" declares runtime: \"custom\", so it builds and runs its ` +\n \"own image. The managed runtime boots the platform image with your bundle; \" +\n \"the custom runtime deploys exactly the same way, from your Dockerfile.\"\n ]\n };\n }\n\n return { eligible: true,\nreasons: [] };\n}\n\n/**\n * Resolve a backend app's directories against the conventions it omits.\n *\n * `hasCollections` replaces the old `mode: \"cms\" | \"baas\"` field. Where the\n * collections come from was never an independent choice: either they are\n * declared in code and the bundle ships them, or they are not and the runtime\n * introspects the live database at boot. Declaring it separately only created\n * the contradictory state — code-first declared, no collections anywhere.\n *\n * `hasConfig` is deliberately a **separate** question. A headless project has no\n * `config/collections`, but it may still ship a config package — that is where\n * the `storageAuthorize` hook lives, and storage is not under row-level\n * security, so without one the server refuses to boot with storage enabled.\n * Collapsing the two would leave a headless project nowhere to put it.\n */\nexport function resolveBackendPaths(\n app: RebaseBackendAppConfig,\n projectRoot: string\n): {\n config: string;\n functions: string;\n crons: string;\n schema: string;\n usersCollection: string;\n /** Whether a config package exists — hooks, storage authorization. */\n hasConfig: boolean;\n /** Whether collections are declared in code, under `<config>/collections`. */\n hasCollections: boolean;\n} {\n const config = app.config ?? DEFAULT_CONFIG_DIR;\n return {\n config,\n functions: app.functions ?? DEFAULT_FUNCTIONS_DIR,\n crons: app.crons ?? DEFAULT_CRONS_DIR,\n schema: app.schema ?? DEFAULT_SCHEMA_FILE,\n usersCollection: app.usersCollection ?? \"collections/users\",\n hasConfig: fs.existsSync(path.join(projectRoot, config)),\n hasCollections: fs.existsSync(path.join(projectRoot, config, \"collections\"))\n };\n}\n"],"mappings":"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"}
@@ -1,5 +1,5 @@
1
- import { n as parseCommandArgs } from "./args-CcBphFdj.js";
2
- import { v as normalizeEsmSpecifiers } from "./bundle-CqURVN-K.js";
1
+ import { b as parseCommandArgs } from "./project-g4K6kV1t.js";
2
+ import { v as normalizeEsmSpecifiers } from "./bundle-DlqkDYSe.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-BHxL2d05.js.map
107
+ //# sourceMappingURL=normalize-imports-BYw8ad87.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"normalize-imports-BHxL2d05.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"}
1
+ {"version":3,"file":"normalize-imports-BYw8ad87.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"}
@@ -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,7 +1,8 @@
1
- import { c as findProjectRoot, d as readEnvFile } from "./project-Wp2zoyeN.js";
2
- import { t as composeDatabaseUrl } from "./dev-preflight-CorMg7Ub.js";
1
+ import { c as findProjectRoot, d as readEnvFile } from "./project-g4K6kV1t.js";
2
+ import { t as Refusal } from "./refusal-ReeeGP8a.js";
3
+ import { t as composeDatabaseUrl } from "./dev-preflight-FT9GbJVj.js";
3
4
  import { n as MANAGED_LIMITATIONS } from "./constraints-CtiylsGf.js";
4
- import { n as ensureManagedDatabase } from "./daemon-CknkDjd6.js";
5
+ import { n as ensureManagedDatabase } from "./daemon-DDMRbz4T.js";
5
6
  import fs from "fs";
6
7
  import path from "path";
7
8
  import { resourceEnvSuffix } from "@rebasepro/types";
@@ -259,7 +260,7 @@ function describeDevDatabase(database) {
259
260
  case "branch": return `branch "${database.branch}" (rebase db branch switch)`;
260
261
  }
261
262
  break;
262
- case "docker": return database.source === "last-dev" ? "Postgres in Docker (as the last `rebase dev --docker`)" : "Postgres in Docker";
263
+ case "docker": return database.source === "last-dev" ? "Postgres in Docker, as the last `rebase dev --docker` did" : "Postgres in Docker";
263
264
  case "managed": return "the managed development database (PGlite)";
264
265
  }
265
266
  throw new Error(`Unhandled dev database: ${JSON.stringify(database)}`);
@@ -475,7 +476,7 @@ async function prepareDatabaseEnv(projectRoot, options = {}) {
475
476
  description
476
477
  };
477
478
  if (database.kind === "docker") {
478
- if (!database.url) throw new Error("--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
+ 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
480
  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
481
  return {
481
482
  database,
@@ -534,4 +535,4 @@ function managedNotices(prepared) {
534
535
  //#endregion
535
536
  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
537
 
537
- //# sourceMappingURL=prepare-C1iaiTC4.js.map
538
+ //# sourceMappingURL=prepare-1rVNd2PF.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prepare-1rVNd2PF.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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"}