@rebasepro/cli 0.23.0 → 0.24.1-canary.g19596ef

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (186) hide show
  1. package/README.md +19 -3
  2. package/bin/rebase.js +4 -1
  3. package/dist/agent-setup-B31F00DC.js +324 -0
  4. package/dist/agent-setup-B31F00DC.js.map +1 -0
  5. package/dist/api-keys-Bl1I9q7M.js +525 -0
  6. package/dist/api-keys-Bl1I9q7M.js.map +1 -0
  7. package/dist/apps-CNgQJjg5.js +340 -0
  8. package/dist/apps-CNgQJjg5.js.map +1 -0
  9. package/dist/args-BnmsEXvu.js +109 -0
  10. package/dist/args-BnmsEXvu.js.map +1 -0
  11. package/dist/atlas-dev-database-D_LP_Woq.js +62 -0
  12. package/dist/atlas-dev-database-D_LP_Woq.js.map +1 -0
  13. package/dist/auth-aySHEF45.js +414 -0
  14. package/dist/auth-aySHEF45.js.map +1 -0
  15. package/dist/branch-pointer-DvSHBjjz.js +2 -0
  16. package/dist/build-DIHWIqO4.js +336 -0
  17. package/dist/build-DIHWIqO4.js.map +1 -0
  18. package/dist/bundle-CqURVN-K.js +1621 -0
  19. package/dist/bundle-CqURVN-K.js.map +1 -0
  20. package/dist/bundle.d.ts +12 -3
  21. package/dist/cli.d.ts +23 -2
  22. package/dist/cloud-DjByQlkW.js +9667 -0
  23. package/dist/cloud-DjByQlkW.js.map +1 -0
  24. package/dist/collection-drift-BKj_BoMq.js +106 -0
  25. package/dist/collection-drift-BKj_BoMq.js.map +1 -0
  26. package/dist/commands/__test_support__/parser-specs.d.ts +13 -0
  27. package/dist/commands/agent-setup.d.ts +116 -0
  28. package/dist/commands/api-keys.d.ts +86 -23
  29. package/dist/commands/apps.d.ts +17 -0
  30. package/dist/commands/auth.d.ts +7 -0
  31. package/dist/commands/build.d.ts +5 -1
  32. package/dist/commands/cloud/auth.d.ts +4 -3
  33. package/dist/commands/cloud/bundle-deploy.d.ts +45 -0
  34. package/dist/commands/cloud/context.d.ts +44 -5
  35. package/dist/commands/cloud/cron.d.ts +53 -0
  36. package/dist/commands/cloud/debug.d.ts +134 -0
  37. package/dist/commands/cloud/index.d.ts +89 -12
  38. package/dist/commands/cloud/token-capabilities.d.ts +76 -0
  39. package/dist/commands/cloud/tokens.d.ts +23 -0
  40. package/dist/commands/console.d.ts +12 -0
  41. package/dist/commands/db.d.ts +79 -25
  42. package/dist/commands/dev.d.ts +181 -13
  43. package/dist/commands/doctor.d.ts +5 -0
  44. package/dist/commands/generate_sdk.d.ts +29 -2
  45. package/dist/commands/init.d.ts +115 -14
  46. package/dist/commands/schema.d.ts +6 -0
  47. package/dist/commands/skills.d.ts +126 -7
  48. package/dist/commands/start.d.ts +44 -0
  49. package/dist/commands/storage.d.ts +46 -0
  50. package/dist/commands/upgrade.d.ts +1 -0
  51. package/dist/consent-ClRHmcxf.js +76 -0
  52. package/dist/consent-ClRHmcxf.js.map +1 -0
  53. package/dist/console-D6SJf8k_.js +102 -0
  54. package/dist/console-D6SJf8k_.js.map +1 -0
  55. package/dist/context-BqhRydBg.js +1234 -0
  56. package/dist/context-BqhRydBg.js.map +1 -0
  57. package/dist/{daemon-entry-LTFKtpHy.js → daemon-entry-Da-D3rmH.js} +3 -4
  58. package/dist/daemon-entry-Da-D3rmH.js.map +1 -0
  59. package/dist/daemon-pzsQwDHa.js +2 -0
  60. package/dist/db-B7RuNuaQ.js +1312 -0
  61. package/dist/db-B7RuNuaQ.js.map +1 -0
  62. package/dist/derive-Dl0z2OWi.js +484 -0
  63. package/dist/derive-Dl0z2OWi.js.map +1 -0
  64. package/dist/dev-CYr_WXan.js +1599 -0
  65. package/dist/dev-CYr_WXan.js.map +1 -0
  66. package/dist/dev-db/atlas-dev-database.d.ts +32 -0
  67. package/dist/dev-db/daemon-entry.d.ts +13 -0
  68. package/dist/dev-db/daemon.d.ts +10 -0
  69. package/dist/dev-db/last-dev-database.d.ts +41 -0
  70. package/dist/dev-db/prepare.d.ts +29 -3
  71. package/dist/dev-db/pull.d.ts +90 -5
  72. package/dist/dev-db/resolve.d.ts +26 -2
  73. package/dist/dev-preflight-B-LYyBf1.js +2 -0
  74. package/dist/dev-server-DG8Tt-uf.js +96 -0
  75. package/dist/dev-server-DG8Tt-uf.js.map +1 -0
  76. package/dist/doctor-BP8cZ-7F.js +771 -0
  77. package/dist/doctor-BP8cZ-7F.js.map +1 -0
  78. package/dist/eject-DizdoecV.js +335 -0
  79. package/dist/eject-DizdoecV.js.map +1 -0
  80. package/dist/fold-static-BC9CYicw.js +201 -0
  81. package/dist/fold-static-BC9CYicw.js.map +1 -0
  82. package/dist/fold-static.d.ts +26 -3
  83. package/dist/function-portability-BIvlvR9q.js +265 -0
  84. package/dist/function-portability-BIvlvR9q.js.map +1 -0
  85. package/dist/generate_sdk-BPqdJc3Q.js +489 -0
  86. package/dist/generate_sdk-BPqdJc3Q.js.map +1 -0
  87. package/dist/index.es.js +199 -22095
  88. package/dist/index.es.js.map +1 -1
  89. package/dist/init-Utstvh2Q.js +1149 -0
  90. package/dist/init-Utstvh2Q.js.map +1 -0
  91. package/dist/last-dev-database-B58g89uA.js +2 -0
  92. package/dist/manifest-DRp0UVTj.js +880 -0
  93. package/dist/manifest-DRp0UVTj.js.map +1 -0
  94. package/dist/manifest.d.ts +56 -4
  95. package/dist/normalize-imports-X0Tgze9y.js +107 -0
  96. package/dist/normalize-imports-X0Tgze9y.js.map +1 -0
  97. package/dist/package-manager-DSEOwTqM.js +193 -0
  98. package/dist/package-manager-DSEOwTqM.js.map +1 -0
  99. package/dist/path-flags-BFgG9V6y.js +91 -0
  100. package/dist/path-flags-BFgG9V6y.js.map +1 -0
  101. package/dist/precompress.d.ts +17 -0
  102. package/dist/prepare-CDN6lSpz.js +1236 -0
  103. package/dist/prepare-CDN6lSpz.js.map +1 -0
  104. package/dist/{project-3WCbrrfW.js → project-Wp2zoyeN.js} +34 -184
  105. package/dist/project-Wp2zoyeN.js.map +1 -0
  106. package/dist/pull-CrGYMEsy.js +458 -0
  107. package/dist/pull-CrGYMEsy.js.map +1 -0
  108. package/dist/resolve-B2O-wKKk.js +2 -0
  109. package/dist/resources-FD7AfZx8.js +183 -0
  110. package/dist/resources-FD7AfZx8.js.map +1 -0
  111. package/dist/schema-DMdr431S.js +125 -0
  112. package/dist/schema-DMdr431S.js.map +1 -0
  113. package/dist/skills-DTFibT1m.js +642 -0
  114. package/dist/skills-DTFibT1m.js.map +1 -0
  115. package/dist/start-DEa-hOBh.js +267 -0
  116. package/dist/start-DEa-hOBh.js.map +1 -0
  117. package/dist/{state-C59Elrnt.js → state-Do7ZJYkF.js} +49 -4
  118. package/dist/state-Do7ZJYkF.js.map +1 -0
  119. package/dist/status-DxnJTekv.js +352 -0
  120. package/dist/status-DxnJTekv.js.map +1 -0
  121. package/dist/storage-DyVq-f_F.js +303 -0
  122. package/dist/storage-DyVq-f_F.js.map +1 -0
  123. package/dist/telemetry/identity.d.ts +10 -1
  124. package/dist/telemetry-C8Uq8phb.js +129 -0
  125. package/dist/telemetry-C8Uq8phb.js.map +1 -0
  126. package/dist/telemetry-EMFRVRQi.js +351 -0
  127. package/dist/telemetry-EMFRVRQi.js.map +1 -0
  128. package/dist/unknown-command-DNwDcX3w.js +98 -0
  129. package/dist/unknown-command-DNwDcX3w.js.map +1 -0
  130. package/dist/upgrade-CtZtRkTE.js +1332 -0
  131. package/dist/upgrade-CtZtRkTE.js.map +1 -0
  132. package/dist/upgrade.d.ts +38 -5
  133. package/dist/utils/dev-preflight.d.ts +47 -5
  134. package/dist/utils/dev-server.d.ts +30 -0
  135. package/dist/utils/path-flags.d.ts +31 -0
  136. package/dist/utils/project.d.ts +16 -0
  137. package/dist/utils/temp-archive.d.ts +9 -0
  138. package/dist/version-Buyyo1ut.js +58 -0
  139. package/dist/version-Buyyo1ut.js.map +1 -0
  140. package/package.json +7 -7
  141. package/templates/eject/Dockerfile +4 -2
  142. package/templates/eject/backend/src/env.ts +1 -1
  143. package/templates/eject/backend/src/index.ts +42 -4
  144. package/templates/eject/docker-compose.custom.yml +8 -4
  145. package/templates/generated-schemas/blank.ts +40 -0
  146. package/templates/generated-schemas/blog.ts +127 -0
  147. package/templates/generated-schemas/ecommerce.ts +93 -0
  148. package/templates/overlays/baas/ai-instructions.md +1 -1
  149. package/templates/overlays/baas/config/package.json +1 -0
  150. package/templates/overlays/baas/config/storage.ts +5 -3
  151. package/templates/overlays/baas/package.json +1 -0
  152. package/templates/template/.cursorrules +1 -1
  153. package/templates/template/.env.example +20 -10
  154. package/templates/template/.github/copilot-instructions.md +1 -1
  155. package/templates/template/.mcp.json +2 -2
  156. package/templates/template/.windsurfrules +1 -1
  157. package/templates/template/AGENTS.md +1 -1
  158. package/templates/template/CLAUDE.md +1 -1
  159. package/templates/template/README.md +21 -15
  160. package/templates/template/ai-instructions.md +5 -4
  161. package/templates/template/backend/functions/hello.ts +8 -0
  162. package/templates/template/backend/package.json +1 -1
  163. package/templates/template/config/resources.ts +7 -6
  164. package/templates/template/config/storage.ts +5 -3
  165. package/templates/template/docker-compose.yml +15 -9
  166. package/templates/template/frontend/vite.config.ts +13 -3
  167. package/templates/template/gitignore +6 -1
  168. package/templates/template/package.json +5 -3
  169. package/templates/template/scripts/example.ts +6 -4
  170. package/templates/template/tsconfig.json +25 -0
  171. package/dist/branch-pointer-dBiClJ0p.js +0 -111
  172. package/dist/branch-pointer-dBiClJ0p.js.map +0 -1
  173. package/dist/constraints-DKGbNfUW.js +0 -48
  174. package/dist/constraints-DKGbNfUW.js.map +0 -1
  175. package/dist/daemon-DR9Tk0La.js +0 -355
  176. package/dist/daemon-DR9Tk0La.js.map +0 -1
  177. package/dist/daemon-entry-LTFKtpHy.js.map +0 -1
  178. package/dist/dev-preflight-CNLl4rdo.js +0 -367
  179. package/dist/dev-preflight-CNLl4rdo.js.map +0 -1
  180. package/dist/project-3WCbrrfW.js.map +0 -1
  181. package/dist/pull-Dywm5FWP.js +0 -232
  182. package/dist/pull-Dywm5FWP.js.map +0 -1
  183. package/dist/resolve-BAeRUBhg.js +0 -74
  184. package/dist/resolve-BAeRUBhg.js.map +0 -1
  185. package/dist/state-C59Elrnt.js.map +0 -1
  186. package/templates/template/backend/src/schema.generated.ts +0 -13
@@ -0,0 +1 @@
1
+ {"version":3,"file":"skills-DTFibT1m.js","names":[],"sources":["../src/commands/skills.ts"],"sourcesContent":["import chalk from \"chalk\";\nimport { createHash } from \"crypto\";\nimport fs from \"fs\";\nimport path from \"path\";\nimport inquirer from \"inquirer\";\nimport { createRequire } from \"module\";\nimport os from \"os\";\nimport { findProjectRoot } from \"../utils/project\";\nimport { parseCommandArgs, UsageError, wantsHelp } from \"../utils/args\";\nimport { unknownCommand } from \"../utils/unknown-command\";\n\nconst require = createRequire(import.meta.url);\n\n/**\n * Supported agent environments and their target directories.\n *\n * Every agent `rebase init` writes a pointer file for has an entry here. That\n * was not true: `rebase init` writes `CLAUDE.md`, `AGENTS.md`, `.cursorrules`,\n * `.windsurfrules` and `.github/copilot-instructions.md`, and the installer\n * covered four of the five — so a Codex or Copilot user was told, by a file in\n * their own repository, to run an installer that had nothing to offer them.\n *\n * `flatLayout` marks the agents whose rules directory is loaded *whole*, into\n * every request. Writing 21 skills there put ~84,000 characters of always-on\n * context in front of every question a person asked Cursor — more than the task\n * itself, on every turn, most of it about parts of Rebase they were not\n * touching. Those agents get one short index rule instead, and the skill bodies\n * go in per-skill subdirectories the index names, which the agent reads when it\n * needs one. `indexFile` is the name of that rule.\n *\n * A subdirectory layout has no such problem: the rule file already sits beside\n * its own assets, so `references/x.md` resolves as written and nothing is\n * loaded until it is opened.\n *\n * `homeMarkers` are the directories under `~` that say the assistant is\n * installed on this machine. They answer a different question from\n * `detectDir`: a fresh scaffold has no `.claude/` of its own, so `rebase init`\n * asks the machine which assistants the developer has and pre-selects those.\n *\n * `mcp` is where the assistant reads a *project-level* MCP server list, and in\n * which shape — see `agent-setup.ts`. `projectDir` is what `REBASE_PROJECT_DIR`\n * is set to: `\".\"` where the server is spawned in the project, the editor's own\n * workspace variable where it may not be. `null` means there is no project\n * file to write: Windsurf reads only a user-level config, and one entry there\n * cannot name every project on the machine.\n */\nexport const AGENTS = {\n cursor: {\n label: \"Cursor\",\n detectDir: \".cursor\",\n homeMarkers: [\".cursor\"],\n targetDir: \".cursor/rules\",\n flatLayout: true,\n indexFile: \"rebase.mdc\",\n mcp: {\n file: \".cursor/mcp.json\",\n format: \"mcpServers\",\n projectDir: \"${workspaceFolder}\"\n },\n /** The body is not a rule file here — `indexFile` is. */\n transformFile: (skillName: string, content: string) => ({\n fileName: path.join(skillName, \"SKILL.md\"),\n content\n })\n },\n claude: {\n label: \"Claude Code\",\n detectDir: \".claude\",\n homeMarkers: [\".claude\"],\n targetDir: \".claude/skills\",\n flatLayout: false,\n // The scaffold already ships this file; writing it is then a no-op.\n mcp: {\n file: \".mcp.json\",\n format: \"mcpServers\",\n projectDir: \".\"\n },\n /** Claude Code uses the standard SKILL.md format in subdirectories. */\n transformFile: (skillName: string, content: string) => ({\n fileName: path.join(skillName, \"SKILL.md\"),\n content\n })\n },\n windsurf: {\n label: \"Windsurf\",\n detectDir: \".windsurf\",\n homeMarkers: [\".codeium/windsurf\", \".windsurf\"],\n targetDir: \".windsurf/rules\",\n flatLayout: true,\n indexFile: \"rebase.md\",\n mcp: null,\n /** The body is not a rule file here — `indexFile` is. */\n transformFile: (skillName: string, content: string) => ({\n fileName: path.join(skillName, \"SKILL.md\"),\n content\n })\n },\n gemini: {\n label: \"Gemini CLI / Antigravity\",\n detectDir: \".agents\",\n homeMarkers: [\".gemini\"],\n targetDir: \".agents/skills\",\n flatLayout: false,\n mcp: {\n file: \".gemini/settings.json\",\n format: \"mcpServers\",\n projectDir: \".\"\n },\n /** Gemini uses the standard SKILL.md format in subdirectories. */\n transformFile: (skillName: string, content: string) => ({\n fileName: path.join(skillName, \"SKILL.md\"),\n content\n })\n },\n codex: {\n label: \"Codex CLI\",\n detectDir: \".codex\",\n homeMarkers: [\".codex\"],\n // Codex reads repository skills from `.agents/skills` — every directory\n // from the working directory up to the repository root — and nothing\n // under `.codex/`. This entry used to write `.codex/skills`, so `--agent\n // codex` reported 21 skills installed into a directory Codex never\n // opens. It shares the directory with Gemini; the installer writes it\n // once.\n targetDir: \".agents/skills\",\n flatLayout: false,\n // Project-scoped, and read only once the developer trusts the project.\n mcp: {\n file: \".codex/config.toml\",\n format: \"codexToml\",\n projectDir: \".\"\n },\n /** Codex uses the standard SKILL.md format in subdirectories. */\n transformFile: (skillName: string, content: string) => ({\n fileName: path.join(skillName, \"SKILL.md\"),\n content\n })\n },\n kiro: {\n label: \"Kiro\",\n detectDir: \".kiro\",\n homeMarkers: [\".kiro\"],\n targetDir: \".kiro/steering\",\n flatLayout: true,\n indexFile: \"rebase.md\",\n mcp: {\n file: \".kiro/settings/mcp.json\",\n format: \"mcpServers\",\n projectDir: \".\"\n },\n /** The body is not a rule file here — `indexFile` is. */\n transformFile: (skillName: string, content: string) => ({\n fileName: path.join(skillName, \"SKILL.md\"),\n content\n })\n },\n copilot: {\n label: \"GitHub Copilot\",\n // Deliberately undetectable. `.github/` is the one directory in this\n // table that is not evidence of an assistant: `rebase init` writes\n // `.github/copilot-instructions.md` into every scaffold, and half the\n // repositories on the internet have a `.github/` for workflows and\n // issue templates. Listing it made bare `rebase skills install` resolve\n // to `[copilot]` in every project — so a Claude Code user who ran the\n // command `rebase init` printed got a success message and nothing in\n // `.claude/skills`. Copilot is `--agent copilot`.\n detectDir: null,\n // Same reasoning on the machine: Copilot lives inside VS Code or a\n // JetBrains IDE, and neither's directory says the extension is there.\n homeMarkers: [],\n targetDir: \".github/instructions\",\n flatLayout: true,\n indexFile: \"rebase.instructions.md\",\n mcp: {\n file: \".vscode/mcp.json\",\n format: \"vscode\",\n projectDir: \"${workspaceFolder}\"\n },\n /** The body is not a rule file here — `indexFile` is. */\n transformFile: (skillName: string, content: string) => ({\n fileName: path.join(skillName, \"SKILL.md\"),\n content\n })\n }\n} as const;\n\nexport type AgentKey = keyof typeof AGENTS;\n\n/** Narrows a string to an agent name, by the table's own keys. */\nexport function isAgentKey(value: string): value is AgentKey {\n return Object.hasOwn(AGENTS, value);\n}\n\n/** Every agent, in table order. */\nexport const AGENT_KEYS: AgentKey[] = Object.keys(AGENTS).filter(isAgentKey);\n\n/**\n * Resolve the path to the skills directory from @rebasepro/agent-skills.\n * Works in both workspace (symlink) and published (real files) layouts.\n */\nfunction getSkillsSourceDir(): string {\n const pkgJsonPath = require.resolve(\"@rebasepro/agent-skills/package.json\");\n const pkgRoot = path.dirname(pkgJsonPath);\n const skillsDir = path.join(pkgRoot, \"skills\");\n\n if (!fs.existsSync(skillsDir)) {\n throw new Error(\n `Skills directory not found at ${skillsDir}. ` +\n \"Make sure @rebasepro/agent-skills is installed.\"\n );\n }\n\n return skillsDir;\n}\n\nexport interface LoadedSkill {\n name: string;\n /** Absolute path of the skill's source directory. */\n dir: string;\n content: string;\n /** Every file the skill ships besides SKILL.md, relative to `dir`. */\n assets: string[];\n}\n\n/**\n * Everything a skill ships alongside its SKILL.md — the `references/` tree the\n * Agent Skills format uses for progressive disclosure.\n *\n * These used to be dropped on install, because the installer read exactly\n * `<skill>/SKILL.md` and nothing else. That left `rebase-design-language`\n * telling the agent three separate times to read `references/view-patterns.md`\n * — 379 lines of view skeletons — in a project where the file had never\n * landed, and the instruction it carries is \"extend an existing pattern; do not\n * invent a layout\".\n */\nfunction loadSkillAssets(skillDir: string): string[] {\n const found: string[] = [];\n\n const walk = (dir: string, prefix: string) => {\n for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {\n // Dotfiles are bookkeeping — `.gitkeep` is what holds the empty\n // `references/` directories in git — and install nothing.\n if (entry.name.startsWith(\".\")) continue;\n const rel = prefix ? path.join(prefix, entry.name) : entry.name;\n if (entry.isDirectory()) walk(path.join(dir, entry.name), rel);\n else if (rel !== \"SKILL.md\") found.push(rel);\n }\n };\n\n walk(skillDir, \"\");\n return found.sort();\n}\n\n/** Read all skill directories and return their names, content and assets. */\nexport function loadSkills(skillsDir: string): LoadedSkill[] {\n const entries = fs.readdirSync(skillsDir, { withFileTypes: true });\n const skills: LoadedSkill[] = [];\n\n for (const entry of entries) {\n if (!entry.isDirectory()) continue;\n const skillDir = path.join(skillsDir, entry.name);\n const skillMdPath = path.join(skillDir, \"SKILL.md\");\n if (!fs.existsSync(skillMdPath)) continue;\n skills.push({\n name: entry.name,\n dir: skillDir,\n content: fs.readFileSync(skillMdPath, \"utf-8\"),\n assets: loadSkillAssets(skillDir)\n });\n }\n\n return skills;\n}\n\n/**\n * The skills this CLI ships, from its own `@rebasepro/agent-skills`.\n *\n * Throws rather than exiting, so `rebase init` can report a missing bundle as a\n * warning and still finish the scaffold.\n */\nexport function loadBundledSkills(): LoadedSkill[] {\n const skillsDir = getSkillsSourceDir();\n const skills = loadSkills(skillsDir);\n if (skills.length === 0) throw new Error(`No skills found in ${skillsDir}`);\n return skills;\n}\n\n/**\n * The one always-on rule a flat-layout agent gets, in place of every skill.\n *\n * Cursor and Windsurf load their whole rules directory into each request. With\n * a rule file per skill that was ~84,000 characters of Rebase reference in\n * front of every question, whether or not the question was about Rebase — and\n * an instruction an assistant skims is an instruction it does not follow.\n *\n * So the always-on part is this: a table of what exists and where to read it.\n * `alwaysApply: true` because knowing the skills are there costs a few hundred\n * characters and is the whole point; the bodies stay on disk until the agent\n * opens one.\n */\nexport function renderSkillIndex(skills: LoadedSkill[], agentKey: AgentKey): string {\n const agent = AGENTS[agentKey];\n const bodyPath = (name: string) => agent.transformFile(name, \"\").fileName.split(path.sep).join(\"/\");\n const rows = skills\n .map((skill) => `| \\`${skill.name}\\` | ${describe(skill)} | \\`${bodyPath(skill.name)}\\` |`)\n .join(\"\\n\");\n\n return [\n \"---\",\n \"description: Rebase — the skills index. Read the file named for the task before writing Rebase code.\",\n \"alwaysApply: true\",\n \"---\",\n \"\",\n \"# Rebase skills\",\n \"\",\n `This project uses [Rebase](https://rebase.pro). ${skills.length} reference skills are`,\n \"installed beside this file. **Read the one that covers the task before writing\",\n \"code** — they are the difference between a collection that compiles and one that\",\n \"does not.\",\n \"\",\n \"| Skill | Covers | Read |\",\n \"|---|---|---|\",\n rows,\n \"\",\n \"Start with `rebase-basics` if you are not sure: it opens with the recipes for\",\n \"adding a collection, a function and an RLS rule, and names the commands.\",\n \"\"\n ].join(\"\\n\");\n}\n\n/**\n * The body of a YAML block scalar, given the offset of its `description:` line.\n *\n * Everything indented more than the key belongs to the value; the first line\n * that is not blank and not indented ends it.\n */\nfunction blockScalarBody(frontMatter: string, keyOffset: number): string {\n const lines = frontMatter.slice(keyOffset).split(\"\\n\").slice(1);\n const body: string[] = [];\n for (const line of lines) {\n if (line.trim() === \"\") { body.push(\"\"); continue; }\n if (!/^[ \\t]/.test(line)) break;\n body.push(line.trim());\n }\n return body.join(\" \");\n}\n\n/**\n * A skill's one-line summary, from its own front matter.\n *\n * Read rather than written here so a new skill needs no edit in this file, and\n * truncated because the index is only worth having while it stays short.\n */\nfunction describe(skill: LoadedSkill): string {\n const frontMatter = skill.content.split(\"---\")[1] ?? \"\";\n const described = /^description:[ \\t]*(.*)$/m.exec(frontMatter);\n // `description: |` (or `>`) is a YAML block scalar: the value is the\n // indented lines that follow, and capturing the line itself captured the\n // `|`. That rendered an empty \"Covers\" cell for `rebase-design-language`,\n // the one skill whose description says agents MUST read it.\n const raw = /^[|>][-+]?$/.test((described?.[1] ?? \"\").trim())\n ? blockScalarBody(frontMatter, described?.index ?? 0)\n : (described?.[1] ?? \"\");\n const text = raw.trim().replace(/\\s+/g, \" \").replace(/\\|/g, \"\\\\|\");\n const firstSentence = /^.*?\\.(?:\\s|$)/.exec(text)?.[0]?.trim() ?? text;\n const fallback = skill.name.replace(/^rebase-/, \"\").replace(/-/g, \" \");\n if (!firstSentence) return fallback;\n if (firstSentence.length <= 140) return firstSentence;\n // Cut at a word boundary. A description sliced mid-word (\"using the collect\")\n // reads as a corrupted file rather than as a summary.\n const cut = firstSentence.slice(0, 140);\n return `${cut.slice(0, cut.lastIndexOf(\" \")).replace(/[,;:]$/, \"\")}…`;\n}\n\n/**\n * Detect which agent environments already exist in the project.\n *\n * A `detectDir` has to be evidence *the user* created, not something a scaffold\n * ships. An agent whose only marker fails that test declares `detectDir: null`\n * and is reachable by `--agent` alone; guessing wrong here is worse than not\n * guessing, because the wrong guess installs 21 files and reports success.\n */\nexport function detectAgents(projectDir: string): AgentKey[] {\n return AGENT_KEYS.filter((key) => {\n const { detectDir } = AGENTS[key];\n return detectDir !== null && fs.existsSync(path.join(projectDir, detectDir));\n });\n}\n\n/** What `detectInstalledAgents` found for one assistant. */\nexport interface InstalledAgent {\n key: AgentKey;\n label: string;\n /** The home directory checked, as `~/…` — the one found, else the first. */\n marker: string | null;\n found: boolean;\n}\n\n/**\n * Which assistants are installed on this machine, from their home directories.\n *\n * The question `rebase init` needs answered, and one `detectAgents` cannot: a\n * project that does not exist yet has no `.claude/` in it. What the machine has\n * is only a default — the developer confirms it — so a false positive costs an\n * unticked box, not 21 files nobody asked for.\n */\nexport function detectInstalledAgents(home: string = os.homedir()): InstalledAgent[] {\n return AGENT_KEYS.map((key) => {\n const agent = AGENTS[key];\n const markers: readonly string[] = agent.homeMarkers;\n const hit = markers.find((m) => fs.existsSync(path.join(home, m)));\n const shown = hit ?? markers[0];\n return {\n key,\n label: agent.label,\n marker: shown === undefined ? null : `~/${shown}`,\n found: hit !== undefined\n };\n });\n}\n\n/** Install skills for a specific agent into the project directory. */\n/**\n * The file in each target directory that records what the installer wrote:\n * relative path → sha256 of the bytes written. A dotfile, so no agent loads it\n * as a rule.\n */\nexport const SKILLS_MANIFEST = \".rebase-skills.json\";\n\ninterface SkillsManifest {\n version: 1;\n files: Record<string, string>;\n}\n\nfunction sha256(bytes: Buffer | string): string {\n return createHash(\"sha256\").update(bytes).digest(\"hex\");\n}\n\nfunction readManifest(targetBase: string): SkillsManifest | null {\n try {\n const parsed: unknown = JSON.parse(fs.readFileSync(path.join(targetBase, SKILLS_MANIFEST), \"utf-8\"));\n if (parsed && typeof parsed === \"object\" && \"files\" in parsed) {\n const files = (parsed as { files: unknown }).files;\n if (files && typeof files === \"object\") {\n const entries = Object.entries(files).filter((e): e is [string, string] => typeof e[1] === \"string\");\n return { version: 1, files: Object.fromEntries(entries) };\n }\n }\n } catch {\n // No manifest, or one that is not ours: the first install, or one made\n // before manifests existed.\n }\n return null;\n}\n\n/** What one agent's install did. */\nexport interface AgentInstallResult {\n skills: number;\n assets: number;\n /** Files left as they are because they were edited since the last install (relative to the target dir). */\n kept: string[];\n /** Files of skills the bundle no longer ships, removed because nobody edited them. */\n removed: string[];\n}\n\nexport function installForAgent(\n agentKey: AgentKey,\n skills: LoadedSkill[],\n projectDir: string,\n options: { force?: boolean } = {}\n): AgentInstallResult {\n const agent = AGENTS[agentKey];\n const targetBase = path.join(projectDir, agent.targetDir);\n\n // Ensure the target directory exists\n fs.mkdirSync(targetBase, { recursive: true });\n\n // A re-run used to overwrite every file unconditionally and never remove\n // one: a note a team appended to an installed skill and committed — as\n // the command tells them to — vanished on the next `skills install`, and a\n // skill a release retired stayed installed beside its replacement. The\n // manifest records what was written, so an edit is told apart from an\n // older bundle's copy. A file with no record (an install from before\n // manifests) is overwritten, as before.\n const previous = readManifest(targetBase);\n const recorded: Record<string, string> = {};\n const kept: string[] = [];\n\n const place = (relPath: string, bytes: Buffer | string) => {\n const target = path.join(targetBase, relPath);\n const wanted = sha256(bytes);\n const last = previous?.files[relPath];\n if (fs.existsSync(target)) {\n const current = sha256(fs.readFileSync(target));\n if (current === wanted) {\n recorded[relPath] = wanted;\n return;\n }\n if (last !== undefined && current !== last && !options.force) {\n // Edited since it was installed: left alone, and still recorded\n // as what was installed, so the next run says so again.\n kept.push(relPath);\n recorded[relPath] = last;\n return;\n }\n }\n fs.mkdirSync(path.dirname(target), { recursive: true });\n fs.writeFileSync(target, bytes);\n recorded[relPath] = wanted;\n };\n\n let count = 0;\n let assetCount = 0;\n for (const skill of skills) {\n // Every layout now writes the body into a per-skill subdirectory, so a\n // link the skill spells `references/x.md` resolves as written and no\n // rewriting is needed. What differs is whether that file is also the\n // agent's *rule* file (subdirectory layouts) or something the index\n // points at (flat ones).\n const { fileName, content } = agent.transformFile(skill.name, skill.content);\n place(fileName, content);\n count++;\n\n // A skill's assets always land in a directory named after the skill,\n // whatever the rule file's layout, so two skills' `references/` trees\n // cannot collide in a flat target directory.\n for (const asset of skill.assets) {\n place(path.join(skill.name, asset), fs.readFileSync(path.join(skill.dir, asset)));\n assetCount++;\n }\n }\n\n if (agent.flatLayout && agent.indexFile) {\n place(agent.indexFile, renderSkillIndex(skills, agentKey));\n }\n\n // Retired files: removed when they are exactly what was installed, kept\n // (and reported) when someone edited them.\n const removed: string[] = [];\n for (const [relPath, last] of Object.entries(previous?.files ?? {})) {\n if (relPath in recorded) continue;\n const target = path.join(targetBase, relPath);\n if (!fs.existsSync(target)) continue;\n if (sha256(fs.readFileSync(target)) !== last && !options.force) {\n kept.push(relPath);\n continue;\n }\n fs.rmSync(target);\n removed.push(relPath);\n // Empty directories the removal leaves behind, up to the target dir.\n for (let dir = path.dirname(target); dir.startsWith(targetBase + path.sep); dir = path.dirname(dir)) {\n if (fs.readdirSync(dir).length > 0) break;\n fs.rmdirSync(dir);\n }\n }\n\n fs.writeFileSync(\n path.join(targetBase, SKILLS_MANIFEST),\n JSON.stringify({ version: 1, files: recorded } satisfies SkillsManifest, null, 2) + \"\\n\",\n \"utf-8\"\n );\n\n return { skills: count, assets: assetCount, kept, removed };\n}\n\n/** What `installSkills` did for one agent. */\nexport interface SkillsInstallResult extends AgentInstallResult {\n agent: AgentKey;\n /** An earlier agent in the same run that already wrote this directory. */\n sharedWith?: AgentKey;\n}\n\n/**\n * Install for several agents, writing each target directory once.\n *\n * Gemini and Codex both read `.agents/skills`; installing twice would write the\n * same 21 files twice and report two installs of one directory.\n */\nexport function installSkills(\n agents: readonly AgentKey[],\n skills: LoadedSkill[],\n projectDir: string,\n options: { force?: boolean } = {}\n): SkillsInstallResult[] {\n const written = new Map<string, SkillsInstallResult>();\n return agents.map((agent) => {\n const earlier = written.get(AGENTS[agent].targetDir);\n if (earlier) return { ...earlier, agent, sharedWith: earlier.agent };\n const result = { agent, ...installForAgent(agent, skills, projectDir, options) };\n written.set(AGENTS[agent].targetDir, result);\n return result;\n });\n}\n\n/** Everything the switch below dispatches, for the did-you-mean. */\nexport const SKILLS_SUBCOMMANDS = [\"install\"] as const;\n\nexport async function skillsCommand(subcommand: string | undefined, rawArgs: string[]) {\n // `--help` cannot reach `skillsInstall`. `cli.ts` only rewrites the\n // subcommand to `\"--help\"` when none was named, so `rebase skills install\n // --help` used to run the install: it detected the agents and overwrote\n // `.claude/skills/*/SKILL.md`, `.cursor/rules/*.mdc` and the rest. A flag\n // that prints text must not write files.\n if (wantsHelp(rawArgs)) {\n printSkillsHelp();\n return;\n }\n\n switch (subcommand) {\n case \"install\":\n await skillsInstall(rawArgs);\n break;\n case \"--help\":\n case undefined:\n printSkillsHelp();\n break;\n default:\n unknownCommand(subcommand, SKILLS_SUBCOMMANDS, \"skills\");\n }\n}\n\n/**\n * Agents named explicitly on the command line, e.g. `--agent claude --agent cursor`\n * (also accepts a comma-separated list, and `all`). Returns null when none were\n * given.\n */\nexport function parseAgentFlags(rawArgs: string[]): AgentKey[] | null {\n return parseSkillsInstallLine(rawArgs).agents;\n}\n\n/** `rebase skills install`'s whole line: the agents it names, `--mcp` and `--force`. */\nexport function parseSkillsInstallLine(rawArgs: string[]): { agents: AgentKey[] | null; mcp: boolean; force: boolean } {\n // Strict, and before anything is written. This used to scan `rawArgs` for\n // `--agent` and ignore every other token, so `rebase skills install\n // --frobnicate --agent claude` exited 0 after writing 21 files — the CLI's\n // other families reject an unknown flag, and an installer is the last place\n // to guess what the person meant.\n const { flags } = parseCommandArgs({\n spec: { \"--agent\": [String], \"-a\": \"--agent\", \"--mcp\": Boolean, \"--force\": Boolean },\n rawArgs,\n commandWords: 2,\n command: \"skills install\",\n maxPositionals: 0\n });\n\n return {\n agents: resolveAgentNames(flags[\"--agent\"] ?? []),\n mcp: flags[\"--mcp\"] === true,\n force: flags[\"--force\"] === true\n };\n}\n\n/**\n * The agents a list of `--agent` values names, or null when it names none.\n *\n * Shared by `rebase skills install` and `rebase init`, so both accept the same\n * spellings. Throws a UsageError on a name that is not an agent.\n */\nexport function resolveAgentNames(values: readonly string[]): AgentKey[] | null {\n const requested = values\n .flatMap(value => value.split(\",\").map(v => v.trim()))\n .filter(Boolean);\n if (requested.length === 0) return null;\n\n // `all` exists for the non-interactive case. Detection cannot help there: a\n // scaffolded project ships `.cursorrules`, `CLAUDE.md`, `.windsurfrules`,\n // `AGENTS.md` and `.github/copilot-instructions.md` all at once, so every\n // pointer file is present and none of them says which assistant the\n // developer actually uses. Guessing from them would install several agents'\n // skills unasked; refusing leaves a scripted setup with no way through.\n // Naming `all` is the developer saying it out loud.\n if (requested.includes(\"all\")) return [...AGENT_KEYS];\n\n const unknown = requested.filter(a => !isAgentKey(a));\n if (unknown.length > 0) {\n throw new UsageError(`Unknown agent(s): ${unknown.join(\", \")}. Available: ${AGENT_KEYS.join(\", \")}, all`);\n }\n // Deduplicated, in the order given: `--agent claude,claude` is one install.\n return [...new Set(requested.filter(isAgentKey))];\n}\n\nasync function skillsInstall(rawArgs: string[] = []) {\n // Parse the line before anything else touches the disk: a bad flag must\n // cost nothing, and everything below this writes or resolves paths.\n const { agents: namedAgents, mcp: withMcp, force } = parseSkillsInstallLine(rawArgs);\n\n // The project root, not the cwd. Agent skills belong beside the repository's\n // other agent configuration — `.claude/`, `.cursor/` — which is both what\n // `detectAgents` looks for and where an assistant reads them from. Resolving\n // against wherever the command happened to be typed put them in\n // `backend/.claude/skills` when run from `backend/`, detected no agent\n // there, and left the skills somewhere nothing would look. Outside a project\n // the cwd is still the honest answer: this command is useful in a repository\n // that is not a Rebase one.\n const projectDir = findProjectRoot() ?? process.cwd();\n\n // 1. Load skills from @rebasepro/agent-skills\n let skills: LoadedSkill[];\n try {\n skills = loadBundledSkills();\n } catch (err) {\n console.error(`${chalk.red.bold(\"ERROR\")} ${err instanceof Error ? err.message : String(err)}`);\n process.exit(1);\n }\n\n // 2. Explicit --agent wins; otherwise detect existing agent environments\n let agents = namedAgents ?? detectAgents(projectDir);\n\n // 3. If none detected, ask the user\n if (agents.length === 0) {\n // A scaffolded project ships `.cursorrules`, `CLAUDE.md`, `AGENTS.md`\n // and `.github/copilot-instructions.md`, but none of the *directories*\n // detectAgents looks for, so a fresh project always lands here — which\n // is correct: those files say what `rebase init` writes, not what the\n // developer uses. On a non-TTY that used to abort with a raw\n // ExitPromptError.\n if (!process.stdin.isTTY) {\n console.error(chalk.red(\"Cannot prompt: this is a non-interactive terminal (no TTY).\"));\n console.error(chalk.yellow(` Name the agents explicitly, e.g. rebase skills install --agent ${Object.keys(AGENTS)[0]}`));\n console.error(chalk.yellow(\" Or install for every supported agent: rebase skills install --agent all\"));\n console.error(chalk.gray(` Available: ${Object.keys(AGENTS).join(\", \")}`));\n process.exit(1);\n }\n\n // The project says nothing, but the machine may: pre-tick the agents\n // whose home directory exists, as `rebase init` does.\n const installed = detectInstalledAgents();\n const choices = installed.map(({ key, label, found }) => ({\n name: label,\n value: key,\n checked: found\n }));\n\n const { selectedAgents } = await inquirer.prompt([{\n type: \"checkbox\",\n name: \"selectedAgents\",\n message: \"No AI agent configuration in this project. Which agents do you use?\",\n choices,\n validate: (input: readonly unknown[]) => {\n if (input.length === 0) return \"Please select at least one agent.\";\n return true;\n }\n }]);\n\n agents = AGENT_KEYS.filter(key => selectedAgents.includes(key));\n }\n\n // 4. Install skills for each agent\n console.log(\"\");\n console.log(chalk.gray(` Found ${chalk.white(skills.length)} Rebase skills`));\n console.log(\"\");\n\n let anyKept = false;\n for (const result of installSkills(agents, skills, projectDir, { force })) {\n const agent = AGENTS[result.agent];\n // Relative to where the command was typed, now that the destination is\n // the project root rather than the cwd — otherwise `.claude/skills`\n // names a directory that is not the one it wrote to.\n const shown = path.relative(process.cwd(), path.join(projectDir, agent.targetDir)) || agent.targetDir;\n if (result.sharedWith) {\n console.log(` ${chalk.green(\"✓\")} ${chalk.bold(agent.label)} — reads the same ${chalk.gray(shown)} as ${AGENTS[result.sharedWith].label}`);\n continue;\n }\n const withAssets = result.assets > 0 ? ` (+ ${result.assets} reference file${result.assets === 1 ? \"\" : \"s\"})` : \"\";\n console.log(` ${chalk.green(\"✓\")} ${chalk.bold(agent.label)} — ${result.skills} skills installed${withAssets} to ${chalk.gray(shown)}`);\n for (const file of result.removed) {\n console.log(chalk.gray(` − removed ${path.join(shown, file)} (no longer in the bundle)`));\n }\n for (const file of result.kept) {\n anyKept = true;\n console.log(chalk.yellow(` ! kept ${path.join(shown, file)}: edited since it was installed`));\n }\n }\n if (anyKept) {\n console.log(chalk.yellow(\" Edited files were left as they are. To replace them with the bundle's copy:\"));\n console.log(` ${chalk.cyan(\"rebase skills install --force\")}`);\n }\n\n // The MCP server, as `rebase init --agent` registers it. Imported here\n // rather than at the top: agent-setup imports this module.\n const {\n MCP_SERVER_NAME, ensureMcpDependency, mcpDependencyNote, mcpServerRegistered, writeMcpConfig\n } = await import(\"./agent-setup\");\n const { detectPackageManager } = await import(\"../utils/package-manager\");\n const shownFile = (file: string) => path.relative(process.cwd(), path.join(projectDir, file)) || file;\n if (withMcp) {\n console.log(\"\");\n const pm = detectPackageManager(projectDir);\n let registered = false;\n for (const agentKey of agents) {\n const agent = AGENTS[agentKey];\n const result = writeMcpConfig(agentKey, projectDir, pm);\n if (result && result.status !== \"unreadable\") registered = true;\n if (!result) {\n console.log(chalk.gray(` · ${agent.label} has no project-level MCP config — add @rebasepro/mcp in its MCP settings.`));\n } else if (result.status === \"added\") {\n console.log(` ${chalk.green(\"✓\")} ${chalk.bold(agent.label)} — MCP server in ${chalk.gray(shownFile(result.file))}`);\n } else if (result.status === \"present\") {\n console.log(` ${chalk.green(\"✓\")} ${chalk.bold(agent.label)} — MCP server already in ${chalk.gray(shownFile(result.file))}`);\n } else {\n console.log(chalk.yellow(` ! ${agent.label}: ${shownFile(result.file)} is not plain JSON, so it was left alone — add the \"${MCP_SERVER_NAME}\" server by hand.`));\n }\n }\n // The configs run the project's own copy of the server.\n const note = registered ? mcpDependencyNote(ensureMcpDependency(projectDir), pm) : undefined;\n if (note) console.log(chalk.yellow(` ${note}`));\n }\n\n console.log(\"\");\n console.log(chalk.gray(\" Skills are project-local. Commit them to share with your team.\"));\n // The skills ship inside the CLI's own pinned `@rebasepro/agent-skills`,\n // so a re-run reinstalls that version; newer ones come with a newer CLI.\n console.log(chalk.gray(\" Re-run this command to restore them. Upgrade @rebasepro/cli for newer skills.\"));\n if (!withMcp) {\n // Only the agents whose config does not have the server yet: on a\n // project `init --agent` set up, a refresh says nothing about it.\n const missing = agents.filter(agentKey => mcpServerRegistered(agentKey, projectDir) === false);\n if (missing.length > 0) {\n console.log(chalk.gray(\" To register the Rebase MCP server for them too, as `rebase init --agent` does:\"));\n console.log(` ${chalk.cyan(`rebase skills install --agent ${missing.join(\",\")} --mcp`)}`);\n }\n }\n console.log(\"\");\n}\n\nfunction printSkillsHelp() {\n console.log(`\n${chalk.bold(\"rebase skills\")} — Manage AI agent skills\n\n${chalk.green.bold(\"Usage\")}\n rebase skills ${chalk.blue(\"<subcommand>\")}\n\n${chalk.green.bold(\"Subcommands\")}\n ${chalk.blue.bold(\"install\")} Install Rebase agent skills for your AI coding assistant\n Supports: ${Object.values(AGENTS).map(a => a.label).join(\", \")}\n\n${chalk.green.bold(\"Options\")}\n ${chalk.blue(\"--force\")} Replace installed skill files you edited, and remove edited files of\n skills the bundle no longer ships. Without it, edits are kept.\n ${chalk.blue(\"--mcp\")} Also register the Rebase MCP server in each agent's project config\n (.mcp.json, .cursor/mcp.json, …), as ${chalk.cyan(\"rebase init --agent\")} does.\n Servers already in the file are kept.\n ${chalk.blue(\"--agent, -a\")} Agent(s) to install for, skipping detection and the prompt.\n Repeat the flag or pass a comma-separated list, or ${chalk.bold(\"all\")}.\n Required without a TTY: detection looks for an agent's own\n directory (${Object.values(AGENTS).filter(a => a.detectDir).map(a => a.detectDir).join(\", \")}),\n and a fresh scaffold has none. GitHub Copilot is never detected\n — ${chalk.gray(\".github/\")} is not evidence anyone uses it — so name it.\n Available: ${Object.keys(AGENTS).join(\", \")}, all\n\n${chalk.green.bold(\"Examples\")}\n ${chalk.cyan(\"rebase skills install\")}\n ${chalk.cyan(\"rebase skills install --agent claude\")}\n ${chalk.cyan(\"rebase skills install --agent claude,cursor\")}\n ${chalk.cyan(\"rebase skills install --agent cursor --mcp\")} ${chalk.gray(\"# skills + the MCP server\")}\n ${chalk.cyan(\"rebase skills install --agent all\")} ${chalk.gray(\"# scripted / CI\")}\n`);\n}\n"],"mappings":"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"}
@@ -0,0 +1,267 @@
1
+ import { n as getPMCommands, t as detectPackageManager } from "./package-manager-DSEOwTqM.js";
2
+ import { d as readEnvFile, o as findEnvFile, p as requireProjectRoot } from "./project-Wp2zoyeN.js";
3
+ import { n as parseCommandArgs, r as wantsHelp } from "./args-BnmsEXvu.js";
4
+ import { t as DEFAULT_BUNDLE_DIR } from "./bundle-CqURVN-K.js";
5
+ import chalk from "chalk";
6
+ import fs from "fs";
7
+ import path from "path";
8
+ import { execa } from "execa";
9
+ //#region src/commands/start.ts
10
+ /**
11
+ * CLI command: rebase start
12
+ *
13
+ * Runs a built bundle through the Rebase runtime — the same path the official
14
+ * container image takes, so what you test locally is what a deployment runs.
15
+ *
16
+ * When there is no bundle (an ejected backend, or a project that has not adopted
17
+ * `rebase.json`) this falls back to the backend workspace's own `start` script,
18
+ * which is what such a project has always used.
19
+ */
20
+ function printHelp() {
21
+ console.log(`
22
+ ${chalk.bold("rebase start")} — run a built bundle
23
+
24
+ ${chalk.bold("Usage")}
25
+ rebase start [options]
26
+
27
+ ${chalk.bold("Options")}
28
+ --bundle <dir> Bundle directory (default: ${DEFAULT_BUNDLE_DIR})
29
+ --workspace Run the backend workspace's own start script
30
+ -h, --help Show this help
31
+
32
+ Runs the bundle the way a deployment does, reading PORT and the rest of .env,
33
+ in whatever NODE_ENV the shell or .env sets. A scaffolded .env says
34
+ development, which keeps development behaviour on — the first account to
35
+ register becomes the admin. For a production server, run it with
36
+ NODE_ENV=production; it says so at start when it is not one.
37
+
38
+ It needs a DATABASE_URL, and starts no database of its own. To run it on the
39
+ database ${chalk.cyan("rebase dev")} uses: ${chalk.cyan("DATABASE_URL=\"$(rebase db url)\" rebase start")}
40
+
41
+ Build first with ${chalk.cyan("rebase build")}.
42
+ `.trim());
43
+ }
44
+ /**
45
+ * What NODE_ENV the server will boot in. The shell wins — dotenv never
46
+ * overrides a variable that is already set — then `.env`.
47
+ */
48
+ function startNodeEnv(projectRoot) {
49
+ if (process.env.NODE_ENV !== void 0) return {
50
+ value: process.env.NODE_ENV,
51
+ source: "shell"
52
+ };
53
+ const fromFile = readEnvFile(projectRoot).NODE_ENV;
54
+ return fromFile !== void 0 ? {
55
+ value: fromFile,
56
+ source: ".env"
57
+ } : {
58
+ value: void 0,
59
+ source: "unset"
60
+ };
61
+ }
62
+ /**
63
+ * The banner for a `rebase start` that is not a production server, or `null`.
64
+ *
65
+ * Not forced to production: the scaffold's `.env` points at a loopback
66
+ * database, which a production boot refuses, so forcing it would break the
67
+ * documented local run. What must not happen is the silence — a command the
68
+ * help called "(production)" booting with the first-registration window open.
69
+ */
70
+ function nonProductionWarning(nodeEnv) {
71
+ if (nodeEnv.value === "production") return null;
72
+ const what = nodeEnv.value === void 0 ? "NODE_ENV is not set" : `NODE_ENV=${nodeEnv.value}${nodeEnv.source === ".env" ? " (from .env)" : ""}`;
73
+ return [
74
+ chalk.yellow.bold(`⚠ ${what} — this is not a production server.`),
75
+ chalk.yellow(" Development behaviour is on: the first account to register becomes the admin"),
76
+ chalk.yellow(" (REBASE_ADMIN_EMAIL is ignored), and with no SMTP configured, sign-in and"),
77
+ chalk.yellow(" password-reset links are printed to this log instead of sent."),
78
+ chalk.gray(" For a production server, run it with NODE_ENV=production (in the shell or .env).")
79
+ ];
80
+ }
81
+ /**
82
+ * What to say about the apps a bundle serves only on their own hostname.
83
+ *
84
+ * The runtime prints "Server running at http://localhost:<port>", and an app
85
+ * whose `path` names a hostname answers only requests carrying that `Host` —
86
+ * so opening the printed URL showed a 404 for an app that was running fine,
87
+ * and nothing said why. One line per such app, with a command that reaches it.
88
+ */
89
+ function hostRoutedNotice(staticApps, port) {
90
+ const routed = staticApps.filter((app) => app.host);
91
+ if (routed.length === 0) return [];
92
+ const origin = `http://localhost:${port || "<port>"}`;
93
+ return [
94
+ ...routed.map((app) => {
95
+ const name = app.name ?? path.posix.basename(app.dir);
96
+ return chalk.yellow(`⚠ ${name} answers only on ${app.host}, so ${origin}${app.path} will not show it here. Reach it with:`) + `\n ${chalk.cyan(`curl -H "Host: ${app.host}" ${origin}${app.path}`)}`;
97
+ }),
98
+ chalk.gray(` (or point ${routed.map((app) => app.host).join(", ")} at 127.0.0.1 in /etc/hosts)`),
99
+ ""
100
+ ];
101
+ }
102
+ /**
103
+ * What to say when there is no DATABASE_URL for the runtime, or `null`.
104
+ *
105
+ * The runtime requires one, and its refusal — `DATABASE_URL: is required`, and
106
+ * a link to the self-hosting docs — is right for a deployment and says nothing
107
+ * to the reader most likely to see it: someone running the scaffold's own
108
+ * `.env`, which leaves DATABASE_URL commented out because `rebase dev` brings a
109
+ * database of its own. That database is one command away, so this names it.
110
+ *
111
+ * `kind` is what `rebase dev` runs this project on, so the line can say which
112
+ * database `rebase db url` will print. A blank value is unset, as the runtime
113
+ * reads it.
114
+ */
115
+ function missingDatabaseUrl(env, kind) {
116
+ if (env.DATABASE_URL?.trim()) return null;
117
+ const which = kind === "docker" ? "the Docker database `rebase dev --docker` uses" : kind === "managed" ? "the managed development database (PGlite) `rebase dev` uses" : "this project's development database";
118
+ return [
119
+ chalk.red("✗ DATABASE_URL is not set, so the runtime has no database to start against."),
120
+ chalk.gray(" `rebase start` runs the bundle the way a deployment does, against the database you name;"),
121
+ chalk.gray(" `rebase dev` is the command that brings one of its own."),
122
+ "",
123
+ ` To run it on ${which}:`,
124
+ ` ${chalk.cyan("DATABASE_URL=\"$(rebase db url)\" rebase start")}`,
125
+ "",
126
+ chalk.gray(" For a deployment, set DATABASE_URL in the environment or in .env."),
127
+ chalk.gray(" See https://rebase.pro/docs/deployment/self-hosting/ for the variables a deployment needs.")
128
+ ];
129
+ }
130
+ function readBundleStatic(bundleDir) {
131
+ try {
132
+ return JSON.parse(fs.readFileSync(path.join(bundleDir, "manifest.json"), "utf8")).entry?.static ?? [];
133
+ } catch {
134
+ return [];
135
+ }
136
+ }
137
+ function printNonProductionWarning(projectRoot) {
138
+ const warning = nonProductionWarning(startNodeEnv(projectRoot));
139
+ if (!warning) return;
140
+ console.log(warning.join("\n") + "\n");
141
+ }
142
+ async function startCommand(rawArgs = []) {
143
+ if (wantsHelp(rawArgs)) {
144
+ printHelp();
145
+ return;
146
+ }
147
+ const { flags: args } = parseCommandArgs({
148
+ spec: {
149
+ "--bundle": String,
150
+ "--workspace": Boolean
151
+ },
152
+ rawArgs,
153
+ commandWords: 1,
154
+ command: "start",
155
+ maxPositionals: 0
156
+ });
157
+ const projectRoot = requireProjectRoot();
158
+ const envFile = findEnvFile(projectRoot);
159
+ const env = { ...process.env };
160
+ if (envFile) env.DOTENV_CONFIG_PATH = envFile;
161
+ const bundleDir = path.resolve(projectRoot, args["--bundle"] ?? "dist-bundle");
162
+ const hasBundle = fs.existsSync(path.join(bundleDir, "manifest.json"));
163
+ printNonProductionWarning(projectRoot);
164
+ if (args["--workspace"] || !hasBundle) {
165
+ if (!args["--workspace"] && !hasBundle) console.log(chalk.dim(`No bundle at ${path.relative(projectRoot, bundleDir)}/ — starting the backend workspace instead.
166
+ `));
167
+ await startWorkspaceBackend(projectRoot, env);
168
+ return;
169
+ }
170
+ ensureBundleDependencies(projectRoot, bundleDir);
171
+ console.log(`${chalk.bold("Rebase")} — starting runtime from ${chalk.cyan(path.relative(projectRoot, bundleDir))}/\n`);
172
+ if (envFile && fs.existsSync(envFile)) (await import("dotenv")).config({
173
+ path: envFile,
174
+ quiet: true
175
+ });
176
+ if (!process.env.DATABASE_URL?.trim()) {
177
+ const { devDatabaseKind } = await import("./prepare-CDN6lSpz.js");
178
+ const missing = missingDatabaseUrl(process.env, devDatabaseKind(projectRoot));
179
+ if (missing) {
180
+ for (const line of missing) console.error(line);
181
+ process.exit(1);
182
+ }
183
+ }
184
+ process.env.REBASE_BUNDLE = bundleDir;
185
+ for (const line of hostRoutedNotice(readBundleStatic(bundleDir), process.env.PORT)) console.log(line);
186
+ try {
187
+ const { runFromBundle } = await import("@rebasepro/server");
188
+ await runFromBundle({ bundleDir });
189
+ } catch (err) {
190
+ console.error(chalk.red("\n✗ Failed to start the runtime."));
191
+ console.error(err instanceof Error ? err.message : String(err));
192
+ process.exit(1);
193
+ }
194
+ }
195
+ /**
196
+ * Make a bundle's imports resolvable for a local run.
197
+ *
198
+ * Node resolves a module by walking up from the *importing file*, so compiled
199
+ * code sitting in `dist-bundle/` no longer sees the per-package `node_modules`
200
+ * its source could: pnpm and npm both install a workspace package's
201
+ * dependencies inside that package, and the bundle is not inside any of them.
202
+ *
203
+ * A deployment solves this by installing the bundle's own `package.json` beside
204
+ * it — that is what the generated `package.json` is for. Locally, doing a second
205
+ * install to run code whose dependencies are already on disk would be wasteful,
206
+ * so this links what is already there instead.
207
+ *
208
+ * Only ever created when absent, and only under the bundle directory, so a real
209
+ * install always wins and nothing here is ever uploaded (`rebase build` cleans
210
+ * the directory and never writes this).
211
+ */
212
+ function ensureBundleDependencies(projectRoot, bundleDir) {
213
+ const target = path.join(bundleDir, "node_modules");
214
+ if (fs.existsSync(target)) return;
215
+ const sources = [
216
+ "backend/node_modules",
217
+ "config/node_modules",
218
+ "node_modules"
219
+ ].map((relative) => path.join(projectRoot, relative)).filter((dir) => fs.existsSync(dir));
220
+ if (sources.length === 0) return;
221
+ let linked = 0;
222
+ fs.mkdirSync(target, { recursive: true });
223
+ const linkInto = (sourceDir, targetDir) => {
224
+ let entries;
225
+ try {
226
+ entries = fs.readdirSync(sourceDir, { withFileTypes: true });
227
+ } catch {
228
+ return;
229
+ }
230
+ for (const entry of entries) {
231
+ if (entry.name === ".bin" || entry.name.startsWith(".")) continue;
232
+ const from = path.join(sourceDir, entry.name);
233
+ const to = path.join(targetDir, entry.name);
234
+ if (entry.name.startsWith("@") && entry.isDirectory()) {
235
+ fs.mkdirSync(to, { recursive: true });
236
+ linkInto(from, to);
237
+ continue;
238
+ }
239
+ if (fs.existsSync(to)) continue;
240
+ try {
241
+ fs.symlinkSync(fs.realpathSync(from), to, "junction");
242
+ linked++;
243
+ } catch {}
244
+ }
245
+ };
246
+ for (const source of sources) linkInto(source, target);
247
+ if (linked > 0) console.log(chalk.dim(` linked ${linked} package(s) into the bundle for this local run\n (a deployment installs the bundle's package.json instead)
248
+ `));
249
+ }
250
+ async function startWorkspaceBackend(projectRoot, env) {
251
+ const startCmd = getPMCommands(detectPackageManager(projectRoot)).runWorkspace("backend", "start");
252
+ console.log(`${chalk.bold("Rebase")} — Starting backend server...\n`);
253
+ try {
254
+ await execa(startCmd[0], startCmd.slice(1), {
255
+ cwd: projectRoot,
256
+ stdio: "inherit",
257
+ env
258
+ });
259
+ } catch {
260
+ console.error(chalk.red("\n✗ Failed to start server."));
261
+ process.exit(1);
262
+ }
263
+ }
264
+ //#endregion
265
+ export { startCommand };
266
+
267
+ //# sourceMappingURL=start-DEa-hOBh.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"start-DEa-hOBh.js","names":[],"sources":["../src/commands/start.ts"],"sourcesContent":["/**\n * CLI command: rebase start\n *\n * Runs a built bundle through the Rebase runtime — the same path the official\n * container image takes, so what you test locally is what a deployment runs.\n *\n * When there is no bundle (an ejected backend, or a project that has not adopted\n * `rebase.json`) this falls back to the backend workspace's own `start` script,\n * which is what such a project has always used.\n */\nimport fs from \"fs\";\nimport path from \"path\";\nimport chalk from \"chalk\";\nimport { execa } from \"execa\";\nimport { requireProjectRoot, findEnvFile, readEnvFile } from \"../utils/project\";\nimport { parseCommandArgs, wantsHelp } from \"../utils/args\";\nimport { detectPackageManager, getPMCommands } from \"../utils/package-manager\";\nimport { DEFAULT_BUNDLE_DIR } from \"../bundle\";\nimport type { RebaseBundleStatic } from \"@rebasepro/types\";\nimport type { DevDatabase } from \"../dev-db/resolve\";\n\nfunction printHelp(): void {\n console.log(`\n${chalk.bold(\"rebase start\")} — run a built bundle\n\n${chalk.bold(\"Usage\")}\n rebase start [options]\n\n${chalk.bold(\"Options\")}\n --bundle <dir> Bundle directory (default: ${DEFAULT_BUNDLE_DIR})\n --workspace Run the backend workspace's own start script\n -h, --help Show this help\n\nRuns the bundle the way a deployment does, reading PORT and the rest of .env,\nin whatever NODE_ENV the shell or .env sets. A scaffolded .env says\ndevelopment, which keeps development behaviour on — the first account to\nregister becomes the admin. For a production server, run it with\nNODE_ENV=production; it says so at start when it is not one.\n\nIt needs a DATABASE_URL, and starts no database of its own. To run it on the\ndatabase ${chalk.cyan(\"rebase dev\")} uses: ${chalk.cyan('DATABASE_URL=\"$(rebase db url)\" rebase start')}\n\nBuild first with ${chalk.cyan(\"rebase build\")}.\n`.trim());\n}\n\n/** The NODE_ENV the runtime will see, and where it comes from. */\nexport interface StartNodeEnv {\n value: string | undefined;\n source: \"shell\" | \".env\" | \"unset\";\n}\n\n/**\n * What NODE_ENV the server will boot in. The shell wins — dotenv never\n * overrides a variable that is already set — then `.env`.\n */\nexport function startNodeEnv(projectRoot: string): StartNodeEnv {\n if (process.env.NODE_ENV !== undefined) return { value: process.env.NODE_ENV, source: \"shell\" };\n const fromFile = readEnvFile(projectRoot).NODE_ENV;\n return fromFile !== undefined ? { value: fromFile, source: \".env\" } : { value: undefined, source: \"unset\" };\n}\n\n/**\n * The banner for a `rebase start` that is not a production server, or `null`.\n *\n * Not forced to production: the scaffold's `.env` points at a loopback\n * database, which a production boot refuses, so forcing it would break the\n * documented local run. What must not happen is the silence — a command the\n * help called \"(production)\" booting with the first-registration window open.\n */\nexport function nonProductionWarning(nodeEnv: StartNodeEnv): string[] | null {\n if (nodeEnv.value === \"production\") return null;\n const what = nodeEnv.value === undefined\n ? \"NODE_ENV is not set\"\n : `NODE_ENV=${nodeEnv.value}${nodeEnv.source === \".env\" ? \" (from .env)\" : \"\"}`;\n return [\n chalk.yellow.bold(`⚠ ${what} — this is not a production server.`),\n chalk.yellow(\" Development behaviour is on: the first account to register becomes the admin\"),\n chalk.yellow(\" (REBASE_ADMIN_EMAIL is ignored), and with no SMTP configured, sign-in and\"),\n chalk.yellow(\" password-reset links are printed to this log instead of sent.\"),\n chalk.gray(\" For a production server, run it with NODE_ENV=production (in the shell or .env).\")\n ];\n}\n\n/**\n * What to say about the apps a bundle serves only on their own hostname.\n *\n * The runtime prints \"Server running at http://localhost:<port>\", and an app\n * whose `path` names a hostname answers only requests carrying that `Host` —\n * so opening the printed URL showed a 404 for an app that was running fine,\n * and nothing said why. One line per such app, with a command that reaches it.\n */\nexport function hostRoutedNotice(staticApps: readonly RebaseBundleStatic[], port: string | undefined): string[] {\n const routed = staticApps.filter(app => app.host);\n if (routed.length === 0) return [];\n const origin = `http://localhost:${port || \"<port>\"}`;\n return [\n ...routed.map(app => {\n const name = app.name ?? path.posix.basename(app.dir);\n return chalk.yellow(`⚠ ${name} answers only on ${app.host}, so ${origin}${app.path} will not show it here. Reach it with:`)\n + `\\n ${chalk.cyan(`curl -H \"Host: ${app.host}\" ${origin}${app.path}`)}`;\n }),\n chalk.gray(` (or point ${routed.map(app => app.host).join(\", \")} at 127.0.0.1 in /etc/hosts)`),\n \"\"\n ];\n}\n\n/**\n * What to say when there is no DATABASE_URL for the runtime, or `null`.\n *\n * The runtime requires one, and its refusal — `DATABASE_URL: is required`, and\n * a link to the self-hosting docs — is right for a deployment and says nothing\n * to the reader most likely to see it: someone running the scaffold's own\n * `.env`, which leaves DATABASE_URL commented out because `rebase dev` brings a\n * database of its own. That database is one command away, so this names it.\n *\n * `kind` is what `rebase dev` runs this project on, so the line can say which\n * database `rebase db url` will print. A blank value is unset, as the runtime\n * reads it.\n */\nexport function missingDatabaseUrl(\n env: Record<string, string | undefined>,\n kind: DevDatabase[\"kind\"] | null\n): string[] | null {\n if (env.DATABASE_URL?.trim()) return null;\n\n const which = kind === \"docker\"\n ? \"the Docker database `rebase dev --docker` uses\"\n : kind === \"managed\"\n ? \"the managed development database (PGlite) `rebase dev` uses\"\n : \"this project's development database\";\n return [\n chalk.red(\"✗ DATABASE_URL is not set, so the runtime has no database to start against.\"),\n chalk.gray(\" `rebase start` runs the bundle the way a deployment does, against the database you name;\"),\n chalk.gray(\" `rebase dev` is the command that brings one of its own.\"),\n \"\",\n ` To run it on ${which}:`,\n ` ${chalk.cyan('DATABASE_URL=\"$(rebase db url)\" rebase start')}`,\n \"\",\n chalk.gray(\" For a deployment, set DATABASE_URL in the environment or in .env.\"),\n chalk.gray(\" See https://rebase.pro/docs/deployment/self-hosting/ for the variables a deployment needs.\")\n ];\n}\n\nfunction readBundleStatic(bundleDir: string): RebaseBundleStatic[] {\n try {\n const manifest = JSON.parse(fs.readFileSync(path.join(bundleDir, \"manifest.json\"), \"utf8\")) as { entry?: { static?: RebaseBundleStatic[] } };\n return manifest.entry?.static ?? [];\n } catch {\n return [];\n }\n}\n\nfunction printNonProductionWarning(projectRoot: string): void {\n const warning = nonProductionWarning(startNodeEnv(projectRoot));\n if (!warning) return;\n console.log(warning.join(\"\\n\") + \"\\n\");\n}\n\nexport async function startCommand(rawArgs: string[] = []): Promise<void> {\n if (wantsHelp(rawArgs)) {\n printHelp();\n return;\n }\n\n const { flags: args } = parseCommandArgs({\n spec: {\n \"--bundle\": String,\n \"--workspace\": Boolean\n },\n rawArgs,\n commandWords: 1,\n command: \"start\",\n maxPositionals: 0\n });\n\n const projectRoot = requireProjectRoot();\n\n const envFile = findEnvFile(projectRoot);\n const env: Record<string, string> = { ...process.env as Record<string, string> };\n if (envFile) {\n env.DOTENV_CONFIG_PATH = envFile;\n }\n\n const bundleDir = path.resolve(projectRoot, args[\"--bundle\"] ?? DEFAULT_BUNDLE_DIR);\n const hasBundle = fs.existsSync(path.join(bundleDir, \"manifest.json\"));\n\n // Before either path, and before the runtime's own boot logs bury it.\n printNonProductionWarning(projectRoot);\n\n if (args[\"--workspace\"] || !hasBundle) {\n if (!args[\"--workspace\"] && !hasBundle) {\n console.log(chalk.dim(\n `No bundle at ${path.relative(projectRoot, bundleDir)}/ — ` +\n \"starting the backend workspace instead.\\n\"\n ));\n }\n await startWorkspaceBackend(projectRoot, env);\n return;\n }\n\n ensureBundleDependencies(projectRoot, bundleDir);\n\n console.log(`${chalk.bold(\"Rebase\")} — starting runtime from ${chalk.cyan(path.relative(projectRoot, bundleDir))}/\\n`);\n\n // Loaded in-process rather than spawned: the runtime is a library here, so\n // signals, exit codes and stdio need no forwarding, and there is one less\n // process between the developer and a stack trace.\n if (envFile && fs.existsSync(envFile)) {\n const dotenv = await import(\"dotenv\");\n dotenv.config({ path: envFile, quiet: true });\n }\n\n // Before the runtime, which would refuse with a message written for a\n // deployment. See `missingDatabaseUrl`.\n if (!process.env.DATABASE_URL?.trim()) {\n const { devDatabaseKind } = await import(\"../dev-db/prepare\");\n const missing = missingDatabaseUrl(process.env, devDatabaseKind(projectRoot));\n if (missing) {\n for (const line of missing) console.error(line);\n process.exit(1);\n }\n }\n\n process.env.REBASE_BUNDLE = bundleDir;\n\n for (const line of hostRoutedNotice(readBundleStatic(bundleDir), process.env.PORT)) console.log(line);\n\n try {\n const { runFromBundle } = await import(\"@rebasepro/server\");\n await runFromBundle({ bundleDir });\n } catch (err) {\n console.error(chalk.red(\"\\n✗ Failed to start the runtime.\"));\n console.error(err instanceof Error ? err.message : String(err));\n process.exit(1);\n }\n}\n\n/**\n * Make a bundle's imports resolvable for a local run.\n *\n * Node resolves a module by walking up from the *importing file*, so compiled\n * code sitting in `dist-bundle/` no longer sees the per-package `node_modules`\n * its source could: pnpm and npm both install a workspace package's\n * dependencies inside that package, and the bundle is not inside any of them.\n *\n * A deployment solves this by installing the bundle's own `package.json` beside\n * it — that is what the generated `package.json` is for. Locally, doing a second\n * install to run code whose dependencies are already on disk would be wasteful,\n * so this links what is already there instead.\n *\n * Only ever created when absent, and only under the bundle directory, so a real\n * install always wins and nothing here is ever uploaded (`rebase build` cleans\n * the directory and never writes this).\n */\nfunction ensureBundleDependencies(projectRoot: string, bundleDir: string): void {\n const target = path.join(bundleDir, \"node_modules\");\n if (fs.existsSync(target)) return;\n\n // Later sources fill gaps left by earlier ones; the backend's tree wins\n // because it holds the server and driver the runtime itself needs.\n const sources = [\"backend/node_modules\", \"config/node_modules\", \"node_modules\"]\n .map(relative => path.join(projectRoot, relative))\n .filter(dir => fs.existsSync(dir));\n\n if (sources.length === 0) return;\n\n let linked = 0;\n fs.mkdirSync(target, { recursive: true });\n\n const linkInto = (sourceDir: string, targetDir: string): void => {\n let entries: fs.Dirent[];\n try {\n entries = fs.readdirSync(sourceDir, { withFileTypes: true });\n } catch {\n return;\n }\n\n for (const entry of entries) {\n if (entry.name === \".bin\" || entry.name.startsWith(\".\")) continue;\n const from = path.join(sourceDir, entry.name);\n const to = path.join(targetDir, entry.name);\n\n // A scope is a directory of packages, not a package — merge into it\n // so `@a/one` from one tree and `@a/two` from another both resolve.\n if (entry.name.startsWith(\"@\") && entry.isDirectory()) {\n fs.mkdirSync(to, { recursive: true });\n linkInto(from, to);\n continue;\n }\n\n if (fs.existsSync(to)) continue;\n try {\n fs.symlinkSync(fs.realpathSync(from), to, \"junction\");\n linked++;\n } catch {\n // A package that cannot be linked simply stays unresolved, and\n // the runtime will say so by name if it actually needed it.\n }\n }\n };\n\n for (const source of sources) linkInto(source, target);\n\n if (linked > 0) {\n console.log(chalk.dim(\n ` linked ${linked} package(s) into the bundle for this local run\\n` +\n \" (a deployment installs the bundle's package.json instead)\\n\"\n ));\n }\n}\n\nasync function startWorkspaceBackend(projectRoot: string, env: Record<string, string>): Promise<void> {\n const pm = detectPackageManager(projectRoot);\n const cmds = getPMCommands(pm);\n const startCmd = cmds.runWorkspace(\"backend\", \"start\");\n\n console.log(`${chalk.bold(\"Rebase\")} — Starting backend server...\\n`);\n\n try {\n await execa(startCmd[0], startCmd.slice(1), {\n cwd: projectRoot,\n stdio: \"inherit\",\n env\n });\n } catch {\n console.error(chalk.red(\"\\n✗ Failed to start server.\"));\n process.exit(1);\n }\n}\n"],"mappings":"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"}
@@ -1,8 +1,53 @@
1
1
  import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.js";
2
- import path from "path";
3
2
  import fs from "fs";
4
- import net from "net";
3
+ import path from "path";
5
4
  import { resourceEnvSuffix } from "@rebasepro/types";
5
+ import net from "net";
6
+ //#region src/dev-db/constraints.ts
7
+ /**
8
+ * Extensions to hand PGlite's constructor.
9
+ *
10
+ * `CREATE EXTENSION` alone cannot install these — PGlite resolves them from
11
+ * bundles supplied at construction time, so anything missing here is missing
12
+ * from the database no matter what the migration says.
13
+ *
14
+ * The module is spelled out per extension rather than derived from the name,
15
+ * because they do not all live in the same place: the two contrib ones are
16
+ * subpaths of PGlite itself, and pgvector is a package of its own. Deriving the
17
+ * path is what left `vector` off this list — `@electric-sql/pglite/contrib/vector`
18
+ * does not exist, so a project declaring a `{ type: "vector" }` property could
19
+ * not use the managed dev database at all.
20
+ */
21
+ var PGLITE_EXTENSIONS = [
22
+ {
23
+ name: "pg_trgm",
24
+ module: "@electric-sql/pglite/contrib/pg_trgm",
25
+ export: "pg_trgm"
26
+ },
27
+ {
28
+ name: "unaccent",
29
+ module: "@electric-sql/pglite/contrib/unaccent",
30
+ export: "unaccent"
31
+ },
32
+ {
33
+ name: "vector",
34
+ module: "@electric-sql/pglite-pgvector",
35
+ export: "vector"
36
+ }
37
+ ];
38
+ /**
39
+ * Announced at startup, every time, rather than discovered.
40
+ *
41
+ * A developer who does not know realtime is off will read the silence as a bug
42
+ * in their own code, which is a worse outcome than not offering the managed
43
+ * database at all.
44
+ */
45
+ var MANAGED_LIMITATIONS = [{
46
+ id: "concurrency",
47
+ summary: "Requests are served one at a time. Behaviour is correct but serialized, so lock contention and job-queue concurrency cannot be reproduced here.",
48
+ remedy: "Reproduce concurrency against a real Postgres: rebase dev --docker"
49
+ }];
50
+ //#endregion
6
51
  //#region src/dev-db/state.ts
7
52
  /**
8
53
  * The managed database's state file, and the rules for trusting it.
@@ -194,6 +239,6 @@ function findFreePort() {
194
239
  });
195
240
  }
196
241
  //#endregion
197
- export { devDbDir as a, readState as c, state_exports as d, writeState as f, dataDir as i, releaseStartLock as l, additionalDataDir as n, findFreePort as o, clearState as r, pidRunning as s, acquireStartLock as t, stateFile as u };
242
+ export { devDbDir as a, readState as c, state_exports as d, writeState as f, dataDir as i, releaseStartLock as l, PGLITE_EXTENSIONS as m, additionalDataDir as n, findFreePort as o, MANAGED_LIMITATIONS as p, clearState as r, pidRunning as s, acquireStartLock as t, stateFile as u };
198
243
 
199
- //# sourceMappingURL=state-C59Elrnt.js.map
244
+ //# sourceMappingURL=state-Do7ZJYkF.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"state-Do7ZJYkF.js","names":[],"sources":["../src/dev-db/constraints.ts","../src/dev-db/state.ts"],"sourcesContent":["/**\n * What PGlite can and cannot do as a development database, measured rather\n * than assumed.\n *\n * Everything in this directory is shaped by four facts, each established by\n * running it against `@electric-sql/pglite` 0.5.6 and\n * `@electric-sql/pglite-socket` 0.2.9 rather than by reading their docs. They\n * are recorded here because two of them are silent failures — the kind that\n * make a developer lose an evening to a feature that reports success and does\n * nothing.\n *\n * 1. **It is really PostgreSQL 18.3.** `select version()` over the socket\n * returns `PostgreSQL 18.3 (PGlite 0.5.6) on wasm32`, which is the same\n * major as the `postgres:18-alpine` the eject template ships. So a dev\n * database here and a compose database there are the same Postgres, and\n * schema behaviour does not diverge between them.\n *\n * 2. **`pg_trgm` and `unaccent` are available**, which is what search\n * collections need. They are not installed by a bare `CREATE EXTENSION`,\n * though — PGlite ships them as separate bundles that must be passed to the\n * constructor, and without that `CREATE EXTENSION pg_trgm` fails with\n * `extension \"pg_trgm\" is not available`. {@link PGLITE_EXTENSIONS} is that\n * list, and it has to stay in step with what the schema generator emits.\n *\n * 3. **RLS is enforced exactly as it is on a real server.** With\n * `SET LOCAL ROLE \"rebase_user\"` inside a transaction — which is how\n * `PostgresBackendDriver` isolates every request — `current_user` becomes\n * the restricted role, `session_user` stays the owner, and a policy using\n * `current_setting('app.tenant')` filters rows correctly, including under\n * `FORCE ROW LEVEL SECURITY`. Measured: an owner saw 3 rows and the\n * role-switched transaction saw 2, with the cross-tenant probe returning 0.\n * This is the one that mattered most: a dev database that quietly failed to\n * apply RLS would give false confidence about the product's central claim.\n *\n * 4. **Concurrency is the real limit, and it fails badly.** PGlite is a single\n * session, and `PGLiteSocketServer` multiplexes connections onto it. Two\n * pooled clients that hold *overlapping transactions* deadlock — not error,\n * hang — which is precisely what a request-per-transaction server does under\n * any concurrent load. {@link MANAGED_POOL_MAX} is the answer: one client\n * connection, so requests queue in the pool instead of deadlocking in the\n * multiplexer. Measured: with a pool of 1, four concurrent queries and a\n * role-switched RLS transaction all pass; with a pool of 5 the same script\n * hangs indefinitely.\n *\n * 5. **LISTEN/NOTIFY needed repairing, and now works.** A notification is an\n * asynchronous message with no request to answer, and the multiplexer hands\n * it to whichever socket is reading rather than to the one that issued\n * `LISTEN` — so a dedicated listener connection, which is exactly how the\n * realtime engine works, received nothing while the *writer* received\n * notifications it never asked for. `notification-proxy.ts` corrects that by\n * copying every `NotificationResponse` frame to every client, which for a\n * single-session database is simply the truth. Realtime therefore works\n * against the managed database, with no change to the server: it does\n * ordinary `LISTEN` over ordinary libpq.\n */\n\n/** One extension bundle, and where to import it from. */\nexport interface PgliteExtension {\n /** The name `CREATE EXTENSION` uses, and the key PGlite is handed. */\n name: string;\n /** Module the bundle is imported from. */\n module: string;\n /** The named export carrying the bundle. */\n export: string;\n}\n\n/**\n * Extensions to hand PGlite's constructor.\n *\n * `CREATE EXTENSION` alone cannot install these — PGlite resolves them from\n * bundles supplied at construction time, so anything missing here is missing\n * from the database no matter what the migration says.\n *\n * The module is spelled out per extension rather than derived from the name,\n * because they do not all live in the same place: the two contrib ones are\n * subpaths of PGlite itself, and pgvector is a package of its own. Deriving the\n * path is what left `vector` off this list — `@electric-sql/pglite/contrib/vector`\n * does not exist, so a project declaring a `{ type: \"vector\" }` property could\n * not use the managed dev database at all.\n */\nexport const PGLITE_EXTENSIONS: readonly PgliteExtension[] = [\n { name: \"pg_trgm\", module: \"@electric-sql/pglite/contrib/pg_trgm\", export: \"pg_trgm\" },\n { name: \"unaccent\", module: \"@electric-sql/pglite/contrib/unaccent\", export: \"unaccent\" },\n { name: \"vector\", module: \"@electric-sql/pglite-pgvector\", export: \"vector\" }\n];\n\n/**\n * Client connections the managed database tolerates: exactly one.\n *\n * Not a tuning choice. Two concurrent transactions over the socket\n * multiplexer deadlock, and a request-per-transaction server produces those\n * the moment two requests overlap. One connection converts that deadlock into\n * ordinary queueing, which is slower and correct.\n */\nexport const MANAGED_POOL_MAX = 1;\n\n/**\n * Connections the socket server will accept.\n *\n * Above {@link MANAGED_POOL_MAX} so that a second *non-transactional* client —\n * `rebase db push` in another terminal while `rebase dev` runs — is refused\n * with a connection error rather than corrupting the multiplexer. The pool\n * limit is what prevents overlapping transactions; this only stops a stampede.\n */\nexport const MANAGED_SERVER_MAX_CONNECTIONS = 4;\n\n/** What a managed PGlite database cannot do, in the words the user needs. */\nexport interface ManagedLimitation {\n /** Stable id, so a warning can be suppressed or tested for. */\n id: string;\n /** One line, naming the feature rather than the mechanism. */\n summary: string;\n /** What to do instead. Always a concrete command. */\n remedy: string;\n}\n\n/**\n * Announced at startup, every time, rather than discovered.\n *\n * A developer who does not know realtime is off will read the silence as a bug\n * in their own code, which is a worse outcome than not offering the managed\n * database at all.\n */\nexport const MANAGED_LIMITATIONS: readonly ManagedLimitation[] = [\n {\n id: \"concurrency\",\n summary:\n \"Requests are served one at a time. Behaviour is correct but serialized, so \" +\n \"lock contention and job-queue concurrency cannot be reproduced here.\",\n remedy: \"Reproduce concurrency against a real Postgres: rebase dev --docker\"\n }\n] as const;\n","/**\n * The managed database's state file, and the rules for trusting it.\n *\n * The daemon outlives the command that started it — `rebase db push` in one\n * terminal and `rebase dev` in another have to reach the *same* PGlite, because\n * two processes opening one data directory would corrupt it. So the daemon\n * records where it is, and every command reads that record.\n *\n * A record on disk is a claim, not a fact. The process it names may have been\n * killed, the machine may have rebooted and handed the pid to something else,\n * and the port may now belong to a stranger. {@link readState} therefore only\n * parses; deciding whether a record is live is {@link isDaemonAlive}'s job, and\n * it asks the daemon rather than the operating system.\n */\n\nimport fs from \"fs\";\nimport { resourceEnvSuffix } from \"@rebasepro/types\";\nimport net from \"net\";\nimport path from \"path\";\n\n/** Everything under here is generated and gitignored. */\nexport const DEV_DB_DIR = \".rebase\";\n/** PGlite's own data directory. Deleting it is what `--reset` means. */\nexport const DATA_DIR_NAME = \"pgdata\";\n/** The record the daemon writes once it is accepting connections. */\nexport const STATE_FILE_NAME = \"pglite.json\";\n/**\n * Held by whoever is currently starting a daemon.\n *\n * Without it, `rebase dev` and `rebase db push` started in the same second both\n * see no state file, both spawn, and two processes open one PGlite data\n * directory — the exact corruption the single-daemon design exists to prevent.\n * Observed as `ENOTEMPTY` during cleanup, which is the harmless way for it to\n * show up; the harmful way is a damaged database.\n */\nexport const START_LOCK_NAME = \"starting.lock\";\n\nexport interface DaemonState {\n /** TCP port the socket server is listening on, chosen when it started. */\n port: number;\n /** The daemon process. Used only as a fast negative check. */\n pid: number;\n /** Absolute path of the PGlite data directory this daemon has open. */\n dataDir: string;\n /** ISO timestamp, for diagnostics. */\n startedAt: string;\n /**\n * A random token the daemon also answers with over the wire, on\n * {@link identityPort}.\n *\n * Without it, \"is the daemon alive?\" degrades to \"is something listening on\n * that port?\", which is a different question and answers yes for whatever\n * process happened to take the port after a reboot. Rebase would then send\n * migrations to a stranger.\n */\n token: string;\n /** Loopback port that answers the identity check. */\n identityPort: number;\n /**\n * The additional databases this daemon serves, by declared key.\n *\n * One PGlite instance each — PGlite is one database per instance, so\n * `database(\"analytics\")` is a second data directory and a second port,\n * not a `CREATE DATABASE`. Started on demand when a command asks for the\n * key over the identity socket, and recorded here so `rebase status` and\n * a reset can see them. The default database is the fields above.\n */\n databases?: Record<string, { port: number; dataDir: string }>;\n}\n\nexport function devDbDir(projectRoot: string): string {\n return path.join(projectRoot, DEV_DB_DIR);\n}\n\nexport function dataDir(projectRoot: string): string {\n return path.join(devDbDir(projectRoot), DATA_DIR_NAME);\n}\n\n/**\n * The data directory for an additional declared database.\n *\n * `pgdata__analytics`: the key spelled the way its variable is, so the\n * directory beside `pgdata` and `DATABASE_URL__ANALYTICS` are visibly the\n * same thing.\n */\nexport function additionalDataDir(projectRoot: string, key: string): string {\n return path.join(devDbDir(projectRoot), `${DATA_DIR_NAME}${resourceEnvSuffix(key).toLowerCase()}`);\n}\n\nexport function stateFile(projectRoot: string): string {\n return path.join(devDbDir(projectRoot), STATE_FILE_NAME);\n}\n\nexport function startLockFile(projectRoot: string): string {\n return path.join(devDbDir(projectRoot), START_LOCK_NAME);\n}\n\n/**\n * Take the start lock, or report that somebody else holds it.\n *\n * `wx` is the whole mechanism: create-if-absent is a single atomic syscall, so\n * exactly one of two racing processes can succeed no matter how close together\n * they arrive.\n *\n * A lock older than `staleAfterMs` is broken rather than waited on — the holder\n * may have been killed between creating it and starting anything, and a\n * developer should never have to know this file exists in order to unstick\n * their project.\n */\nexport function acquireStartLock(projectRoot: string, staleAfterMs: number): boolean {\n const target = startLockFile(projectRoot);\n fs.mkdirSync(devDbDir(projectRoot), { recursive: true });\n\n const attempt = (): boolean => {\n try {\n const handle = fs.openSync(target, \"wx\");\n fs.writeSync(handle, `${process.pid} ${new Date().toISOString()}\\n`);\n fs.closeSync(handle);\n\n return true;\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code !== \"EEXIST\") throw error;\n\n return false;\n }\n };\n\n if (attempt()) return true;\n\n try {\n // Clamped at zero: a filesystem whose timestamp granularity rounds the\n // mtime *up* reports a negative age for a lock created moments ago,\n // and a negative age is below every threshold — so `staleAfterMs: 0`,\n // which means \"break any lock\", would refuse to break one.\n const age = Math.max(0, Date.now() - fs.statSync(target).mtimeMs);\n if (age < staleAfterMs) return false;\n fs.unlinkSync(target);\n } catch {\n // Vanished under us, which means the holder finished. Either way the\n // next attempt is the answer.\n }\n\n return attempt();\n}\n\nexport function releaseStartLock(projectRoot: string): void {\n try {\n fs.unlinkSync(startLockFile(projectRoot));\n } catch {\n // Already released is the desired end state.\n }\n}\n\n/**\n * Parse the record, or `null` for anything that is not one.\n *\n * Every failure is the same answer — absent — because every failure has the\n * same remedy: start a daemon. A corrupt state file is not worth an error\n * message to a user who never wrote it.\n */\nexport function readState(projectRoot: string): DaemonState | null {\n let raw: string;\n try {\n raw = fs.readFileSync(stateFile(projectRoot), \"utf8\");\n } catch {\n return null;\n }\n\n try {\n const parsed = JSON.parse(raw) as Partial<DaemonState>;\n if (\n typeof parsed.port !== \"number\" ||\n !Number.isInteger(parsed.port) ||\n parsed.port <= 0 ||\n parsed.port > 65535 ||\n typeof parsed.pid !== \"number\" ||\n typeof parsed.dataDir !== \"string\" ||\n typeof parsed.token !== \"string\" ||\n parsed.token.length === 0 ||\n typeof parsed.identityPort !== \"number\" ||\n !Number.isInteger(parsed.identityPort) ||\n parsed.identityPort <= 0 ||\n parsed.identityPort > 65535\n ) {\n return null;\n }\n\n // Only well-formed entries survive: a malformed one is treated as\n // absent, and the next ensure asks the daemon for it again.\n const databases: Record<string, { port: number; dataDir: string }> = {};\n if (parsed.databases && typeof parsed.databases === \"object\") {\n for (const [key, entry] of Object.entries(parsed.databases)) {\n if (\n entry && typeof entry.port === \"number\" && Number.isInteger(entry.port)\n && entry.port > 0 && entry.port <= 65535 && typeof entry.dataDir === \"string\"\n ) {\n databases[key] = { port: entry.port, dataDir: entry.dataDir };\n }\n }\n }\n\n return {\n port: parsed.port,\n pid: parsed.pid,\n dataDir: parsed.dataDir,\n startedAt: typeof parsed.startedAt === \"string\" ? parsed.startedAt : \"\",\n token: parsed.token,\n identityPort: parsed.identityPort,\n ...(Object.keys(databases).length > 0 ? { databases } : {})\n };\n } catch {\n return null;\n }\n}\n\nexport function writeState(projectRoot: string, state: DaemonState): void {\n fs.mkdirSync(devDbDir(projectRoot), { recursive: true });\n // Written whole then moved, so a reader never sees half a record — commands\n // poll this file while the daemon is starting.\n const target = stateFile(projectRoot);\n const temporary = `${target}.${process.pid}.tmp`;\n fs.writeFileSync(temporary, `${JSON.stringify(state, null, 2)}\\n`, \"utf8\");\n fs.renameSync(temporary, target);\n}\n\nexport function clearState(projectRoot: string): void {\n try {\n fs.unlinkSync(stateFile(projectRoot));\n } catch {\n // Already gone is the desired end state.\n }\n}\n\n/** Is *some* process with this pid running? A fast, cheap negative check. */\nexport function pidRunning(pid: number): boolean {\n try {\n // Signal 0 performs the permission and existence checks without\n // delivering anything.\n process.kill(pid, 0);\n\n return true;\n } catch (error) {\n // EPERM means it exists and belongs to someone else, which for our\n // purposes is still \"running\".\n return (error as NodeJS.ErrnoException).code === \"EPERM\";\n }\n}\n\n/** Can a TCP connection be opened to this port on loopback? */\nexport function portAccepting(port: number, timeoutMs = 1000): Promise<boolean> {\n return new Promise((resolve) => {\n const socket = new net.Socket();\n const settle = (answer: boolean) => {\n socket.removeAllListeners();\n socket.destroy();\n resolve(answer);\n };\n socket.setTimeout(timeoutMs);\n socket.once(\"connect\", () => settle(true));\n socket.once(\"timeout\", () => settle(false));\n socket.once(\"error\", () => settle(false));\n socket.connect(port, \"127.0.0.1\");\n });\n}\n\n/**\n * Ask a port for a free one, then hand back the number.\n *\n * Deliberately not the probe `rebase init` uses: that one has a documented\n * failure where a port is free to probe and unusable to publish. This binds on\n * loopback only, which is also where the daemon listens, so a port that binds\n * here binds there.\n */\nexport function findFreePort(): Promise<number> {\n return new Promise((resolve, reject) => {\n const server = net.createServer();\n server.once(\"error\", reject);\n server.listen(0, \"127.0.0.1\", () => {\n const address = server.address();\n if (address === null || typeof address === \"string\") {\n server.close(() => reject(new Error(\"Could not determine a free port.\")));\n\n return;\n }\n const { port } = address;\n server.close(() => resolve(port));\n });\n });\n}\n"],"mappings":"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"}