@rebasepro/cli 0.25.0 → 0.26.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -1
- package/bin/rebase.js +143 -4
- package/dist/{agent-setup-B5AKHOgt.js → agent-setup-D9mHRZ5R.js} +125 -3
- package/dist/agent-setup-D9mHRZ5R.js.map +1 -0
- package/dist/{api-keys-Tm7zvAPe.js → api-keys-CyJ26KEJ.js} +41 -10
- package/dist/api-keys-CyJ26KEJ.js.map +1 -0
- package/dist/{apps-BsU_ILbP.js → apps-CnLDV5b6.js} +6 -7
- package/dist/{apps-BsU_ILbP.js.map → apps-CnLDV5b6.js.map} +1 -1
- package/dist/{atlas-dev-database-OWtqnG7W.js → atlas-dev-database-BODqdCWt.js} +4 -4
- package/dist/{atlas-dev-database-OWtqnG7W.js.map → atlas-dev-database-BODqdCWt.js.map} +1 -1
- package/dist/{auth-Vl2hFKXP.js → auth-dII2jCO1.js} +10 -8
- package/dist/auth-dII2jCO1.js.map +1 -0
- package/dist/{branch-pointer-9DnwkFJJ.js → branch-pointer-BQUF6lYa.js} +1 -1
- package/dist/{build-BBgT8EJZ.js → build-Cza7ZQrp.js} +6 -7
- package/dist/{build-BBgT8EJZ.js.map → build-Cza7ZQrp.js.map} +1 -1
- package/dist/{bundle-DtCCCr4w.js → bundle-C6yEtMJ-.js} +53 -5
- package/dist/bundle-C6yEtMJ-.js.map +1 -0
- package/dist/bundle.d.ts +14 -0
- package/dist/{cloud-BpH0_bmV.js → cloud-BdnmqQaZ.js} +8528 -4342
- package/dist/cloud-BdnmqQaZ.js.map +1 -0
- package/dist/collections-dir-DlvDhime.js +69 -0
- package/dist/collections-dir-DlvDhime.js.map +1 -0
- package/dist/commands/agent-setup.d.ts +58 -0
- package/dist/commands/api-keys.d.ts +13 -0
- package/dist/commands/cloud/activity.d.ts +25 -0
- package/dist/commands/cloud/app-key.d.ts +18 -0
- package/dist/commands/cloud/auth.d.ts +1 -0
- package/dist/commands/cloud/billing-invoices.d.ts +39 -0
- package/dist/commands/cloud/claim.d.ts +7 -0
- package/dist/commands/cloud/context.d.ts +115 -6
- package/dist/commands/cloud/deploy-summary.d.ts +162 -0
- package/dist/commands/cloud/deploy.d.ts +64 -0
- package/dist/commands/cloud/env.d.ts +24 -0
- package/dist/commands/cloud/index.d.ts +13 -1
- package/dist/commands/cloud/init.d.ts +143 -0
- package/dist/commands/cloud/login-flow.d.ts +51 -0
- package/dist/commands/cloud/notifications.d.ts +28 -0
- package/dist/commands/cloud/orgs.d.ts +13 -0
- package/dist/commands/cloud/previews.d.ts +123 -0
- package/dist/commands/cloud/resources.d.ts +68 -14
- package/dist/commands/cloud/runtime-logs.d.ts +176 -0
- package/dist/commands/cloud/service-key.d.ts +15 -0
- package/dist/commands/cloud/token-capabilities.d.ts +61 -8
- package/dist/commands/cloud/tokens.d.ts +18 -3
- package/dist/commands/cloud/usage.d.ts +63 -0
- package/dist/commands/dev.d.ts +22 -0
- package/dist/commands/doctor.d.ts +1 -1
- package/dist/commands/generate-view.d.ts +94 -0
- package/dist/commands/generate_sdk.d.ts +19 -2
- package/dist/commands/init.d.ts +20 -0
- package/dist/commands/skills.d.ts +4 -3
- package/dist/commands/start.d.ts +10 -0
- package/dist/commands/storage.d.ts +4 -0
- package/dist/commands/upgrade.d.ts +12 -0
- package/dist/commands/verify.d.ts +96 -0
- package/dist/{consent-ClRHmcxf.js → consent-BD7F29T3.js} +2 -2
- package/dist/{consent-ClRHmcxf.js.map → consent-BD7F29T3.js.map} +1 -1
- package/dist/{console-CEGm0XNc.js → console-BXB6-8GT.js} +12 -6
- package/dist/console-BXB6-8GT.js.map +1 -0
- package/dist/{context-CyflUrKg.js → context-GUxkZm-S.js} +482 -26
- package/dist/context-GUxkZm-S.js.map +1 -0
- package/dist/{daemon-CknkDjd6.js → daemon-Cp7oNXth.js} +160 -5
- package/dist/daemon-Cp7oNXth.js.map +1 -0
- package/dist/{daemon-entry-CbLRrgzy.js → daemon-entry-zOC1revO.js} +6 -3
- package/dist/daemon-entry-zOC1revO.js.map +1 -0
- package/dist/{db-CRNpA5mH.js → db-KzlqddGD.js} +101 -54
- package/dist/db-KzlqddGD.js.map +1 -0
- package/dist/{derive-CsoFGAnK.js → derive-ae9UOoOB.js} +2 -2
- package/dist/{derive-CsoFGAnK.js.map → derive-ae9UOoOB.js.map} +1 -1
- package/dist/{dev-C0pbMmhS.js → dev-DTqVMgB3.js} +231 -44
- package/dist/dev-DTqVMgB3.js.map +1 -0
- package/dist/dev-db/daemon.d.ts +41 -0
- package/dist/dev-db/pull.d.ts +33 -5
- package/dist/dev-db/state.d.ts +9 -0
- package/dist/{dev-preflight-unHpiYO1.js → dev-preflight-Cxv3TiD1.js} +73 -27
- package/dist/dev-preflight-Cxv3TiD1.js.map +1 -0
- package/dist/{dev-server-B1UYifZJ.js → dev-server-OJsUGZEB.js} +14 -3
- package/dist/dev-server-OJsUGZEB.js.map +1 -0
- package/dist/{doctor-CATRdWm6.js → doctor-BRWOlqeG.js} +14 -12
- package/dist/doctor-BRWOlqeG.js.map +1 -0
- package/dist/{eject-DuasTr-F.js → eject-DABcDnPN.js} +3 -4
- package/dist/{eject-DuasTr-F.js.map → eject-DABcDnPN.js.map} +1 -1
- package/dist/{fold-static-hDHtDfTq.js → fold-static-BnAK-anQ.js} +3 -3
- package/dist/{fold-static-hDHtDfTq.js.map → fold-static-BnAK-anQ.js.map} +1 -1
- package/dist/generate-view-IFwCADvb.js +662 -0
- package/dist/generate-view-IFwCADvb.js.map +1 -0
- package/dist/{generate_sdk-Cvg5jjJF.js → generate_sdk-BvcWvoUh.js} +49 -13
- package/dist/generate_sdk-BvcWvoUh.js.map +1 -0
- package/dist/index.es.js +58 -40
- package/dist/index.es.js.map +1 -1
- package/dist/{init-BWAwHPtz.js → init-CaU6GhwI.js} +50 -62
- package/dist/init-CaU6GhwI.js.map +1 -0
- package/dist/invoked-command-CG_rIa_o.js +55 -0
- package/dist/invoked-command-CG_rIa_o.js.map +1 -0
- package/dist/last-dev-database-DpW5dfoX.js +2 -0
- package/dist/{manifest-DRp0UVTj.js → manifest-BtzDy7KQ.js} +36 -3
- package/dist/{manifest-DRp0UVTj.js.map → manifest-BtzDy7KQ.js.map} +1 -1
- package/dist/{normalize-imports-BoBVh5tU.js → normalize-imports-D0zXlM6g.js} +3 -3
- package/dist/{normalize-imports-BoBVh5tU.js.map → normalize-imports-D0zXlM6g.js.map} +1 -1
- package/dist/{path-flags-BFgG9V6y.js → path-flags-DNvtEi2-.js} +5 -4
- package/dist/path-flags-DNvtEi2-.js.map +1 -0
- package/dist/{prepare-DdnU70z8.js → prepare-CTw9NEkh.js} +5 -4
- package/dist/{prepare-DdnU70z8.js.map → prepare-CTw9NEkh.js.map} +1 -1
- package/dist/project-CasK56vN.js +701 -0
- package/dist/project-CasK56vN.js.map +1 -0
- package/dist/{pull-C3PXduzl.js → pull-DDMpsZ5r.js} +131 -24
- package/dist/pull-DDMpsZ5r.js.map +1 -0
- package/dist/refusal-ReeeGP8a.js +26 -0
- package/dist/refusal-ReeeGP8a.js.map +1 -0
- package/dist/resolve-D647F1LA.js +2 -0
- package/dist/{resources-BdNRDEAy.js → resources-CPSC5Kcu.js} +4 -5
- package/dist/{resources-BdNRDEAy.js.map → resources-CPSC5Kcu.js.map} +1 -1
- package/dist/{schema-B0-p6T1O.js → schema-BSXwZSiI.js} +9 -8
- package/dist/schema-BSXwZSiI.js.map +1 -0
- package/dist/{skills-DfnTeyRz.js → skills-BU2flMOy.js} +52 -8
- package/dist/skills-BU2flMOy.js.map +1 -0
- package/dist/{start-BmEZkS0n.js → start-mVb6upwY.js} +20 -7
- package/dist/start-mVb6upwY.js.map +1 -0
- package/dist/{state-CCB-FAQ9.js → state-CYdBvnwA.js} +3 -2
- package/dist/state-CYdBvnwA.js.map +1 -0
- package/dist/static-syntax-check.d.ts +64 -0
- package/dist/{status-BJ26yZOo.js → status-BREvtzJm.js} +8 -9
- package/dist/{status-BJ26yZOo.js.map → status-BREvtzJm.js.map} +1 -1
- package/dist/{storage-DKPUBTWG.js → storage-DtOXGflp.js} +159 -16
- package/dist/storage-DtOXGflp.js.map +1 -0
- package/dist/{telemetry-EMFRVRQi.js → telemetry-CgwJPVMO.js} +2 -2
- package/dist/{telemetry-EMFRVRQi.js.map → telemetry-CgwJPVMO.js.map} +1 -1
- package/dist/{telemetry-m9czTNGR.js → telemetry-DwK5RQJl.js} +4 -4
- package/dist/{telemetry-m9czTNGR.js.map → telemetry-DwK5RQJl.js.map} +1 -1
- package/dist/{upgrade-DjihWuZL.js → upgrade-B7sVwL8S.js} +26 -4
- package/dist/{upgrade-DjihWuZL.js.map → upgrade-B7sVwL8S.js.map} +1 -1
- package/dist/utils/args.d.ts +32 -3
- package/dist/utils/boot-retry.d.ts +41 -0
- package/dist/utils/collections-dir.d.ts +14 -0
- package/dist/utils/dev-preflight.d.ts +34 -1
- package/dist/utils/dev-server.d.ts +11 -0
- package/dist/utils/path-flags.d.ts +2 -1
- package/dist/utils/project.d.ts +5 -1
- package/dist/utils/tool-stdio.d.ts +1 -1
- package/dist/utils/unknown-command.d.ts +4 -0
- package/dist/verify-D7UsV_qe.js +730 -0
- package/dist/verify-D7UsV_qe.js.map +1 -0
- package/package.json +9 -7
- package/templates/eject/backend/src/index.ts +9 -1
- package/templates/generated-schemas/ecommerce.ts +3 -0
- package/templates/overlays/baas/ai-instructions.md +13 -0
- package/templates/resource-graphs/blank.json +25 -0
- package/templates/resource-graphs/blog.json +28 -0
- package/templates/resource-graphs/ecommerce.json +28 -0
- package/templates/resource-graphs/headless.json +22 -0
- package/templates/template/.claude/hooks/post-edit-collection.sh +99 -0
- package/templates/template/.claude/settings.json +17 -0
- package/templates/template/.env.example +15 -5
- package/templates/template/ai-instructions.md +21 -1
- package/templates/template/config/collections/presets/ecommerce/index.ts +23 -0
- package/templates/template/docker-compose.yml +4 -1
- package/templates/template/frontend/package.json +1 -0
- package/templates/template/frontend/src/App.tsx +10 -9
- package/templates/views/AppView.tsx.tmpl +138 -0
- package/templates/views/EntityView.tsx.tmpl +64 -0
- package/templates/views/index.ts.tmpl +8 -0
- package/dist/agent-setup-B5AKHOgt.js.map +0 -1
- package/dist/api-keys-Tm7zvAPe.js.map +0 -1
- package/dist/args-BX0YD14a.js +0 -266
- package/dist/args-BX0YD14a.js.map +0 -1
- package/dist/auth-Vl2hFKXP.js.map +0 -1
- package/dist/bundle-DtCCCr4w.js.map +0 -1
- package/dist/cloud-BpH0_bmV.js.map +0 -1
- package/dist/console-CEGm0XNc.js.map +0 -1
- package/dist/context-CyflUrKg.js.map +0 -1
- package/dist/daemon-CknkDjd6.js.map +0 -1
- package/dist/daemon-entry-CbLRrgzy.js.map +0 -1
- package/dist/db-CRNpA5mH.js.map +0 -1
- package/dist/dev-C0pbMmhS.js.map +0 -1
- package/dist/dev-preflight-unHpiYO1.js.map +0 -1
- package/dist/dev-server-B1UYifZJ.js.map +0 -1
- package/dist/doctor-CATRdWm6.js.map +0 -1
- package/dist/generate_sdk-Cvg5jjJF.js.map +0 -1
- package/dist/init-BWAwHPtz.js.map +0 -1
- package/dist/last-dev-database-c_dKz1S3.js +0 -2
- package/dist/path-flags-BFgG9V6y.js.map +0 -1
- package/dist/project-Wp2zoyeN.js +0 -349
- package/dist/project-Wp2zoyeN.js.map +0 -1
- package/dist/pull-C3PXduzl.js.map +0 -1
- package/dist/resolve-CNQjsatH.js +0 -2
- package/dist/schema-B0-p6T1O.js.map +0 -1
- package/dist/skills-DfnTeyRz.js.map +0 -1
- package/dist/start-BmEZkS0n.js.map +0 -1
- package/dist/state-CCB-FAQ9.js.map +0 -1
- package/dist/storage-DKPUBTWG.js.map +0 -1
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"skills-DfnTeyRz.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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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"start-BmEZkS0n.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, type PMCommands } from \"../utils/package-manager\";\nimport { DEFAULT_BUNDLE_DIR } from \"../bundle\";\nimport type { RebaseBundleStatic } from \"@rebasepro/types\";\nimport type { DevDatabase } from \"../dev-db/resolve\";\nimport type { DaemonState } from \"../dev-db/state\";\nimport { MANAGED_POOL_MAX } from \"../dev-db/constraints\";\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. `pm` spells the command: a scaffold has `rebase` in\n * `node_modules/.bin`, not on PATH, so it is `pnpm exec rebase …` or\n * `npx rebase …`, as in the CLI's other hints.\n */\nexport function missingDatabaseUrl(\n env: Record<string, string | undefined>,\n kind: DevDatabase[\"kind\"] | null,\n pm: PMCommands\n): string[] | null {\n if (env.DATABASE_URL?.trim()) return null;\n\n const rebase = (args: string[]): string => pm.exec(\"rebase\", args).join(\" \");\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\n/**\n * What the runtime needs when DATABASE_URL is this project's managed\n * development database, or nothing.\n *\n * The connection ceiling `rebase dev` sets for it (`MANAGED_POOL_MAX`). PGlite\n * is one session behind a socket server that accepts only a few connections,\n * and a running `rebase dev` holds some. With an ordinary pool, the boot's\n * concurrent probes opened more than were left, the ones over were refused,\n * and a probe that could not connect read as a table that was not there:\n * `❌ rebase.cron_claims is unavailable`, beside tables that existed.\n *\n * Recognised by the running daemon's ports, which is what `rebase db url`\n * prints from. A ceiling set by hand is kept.\n */\nexport function managedDatabaseRuntimeEnv(\n databaseUrl: string | undefined,\n daemon: DaemonState | null,\n env: Record<string, string | undefined>\n): Record<string, string> {\n if (!databaseUrl || !daemon || env.REBASE_DB_POOL_MAX !== undefined) return {};\n let url: URL;\n try {\n url = new URL(databaseUrl);\n } catch {\n return {};\n }\n if (![\"127.0.0.1\", \"localhost\", \"[::1]\"].includes(url.hostname)) return {};\n const ports = [daemon.port, ...Object.values(daemon.databases ?? {}).map(database => database.port)];\n if (!ports.includes(Number(url.port))) return {};\n return { REBASE_DB_POOL_MAX: String(MANAGED_POOL_MAX) };\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), getPMCommands(detectPackageManager(projectRoot)));\n if (missing) {\n for (const line of missing) console.error(line);\n process.exit(1);\n }\n }\n\n // See `managedDatabaseRuntimeEnv`.\n const { findRunningDaemon } = await import(\"../dev-db/daemon\");\n const managedEnv = managedDatabaseRuntimeEnv(process.env.DATABASE_URL, await findRunningDaemon(projectRoot), process.env);\n if (managedEnv.REBASE_DB_POOL_MAX) {\n Object.assign(process.env, managedEnv);\n console.log(chalk.dim(\n \"DATABASE_URL is this project's managed development database: the runtime uses one connection,\\n\" +\n \"as `rebase dev` does, so requests are served one at a time.\\n\"\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 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"state-CCB-FAQ9.js","names":[],"sources":["../src/dev-db/state.ts"],"sourcesContent":["/**\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 { createHash } from \"crypto\";\nimport fs from \"fs\";\nimport { DEFAULT_RESOURCE_KEY, 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/**\n * The port each managed database last listened on, by key.\n *\n * Kept apart from the state file because it has to outlive the daemon: the\n * state file is cleared on every stop, and it is what a stopped daemon's\n * successor reads to come back where the last one was. See\n * {@link chooseManagedPort}.\n */\nexport const PORTS_FILE_NAME = \"pglite-ports.json\";\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\n/** Where a project's derived database port falls: 25432–26431. */\nconst MANAGED_PORT_FIRST = 25432;\nconst MANAGED_PORT_SPAN = 1000;\n\n/**\n * The port a project's managed database tries first when nothing is recorded:\n * derived from the project path, and for an additional database from its key\n * too, so two projects — and a project's two databases — rarely ask for the\n * same one.\n *\n * Below every ephemeral range (Linux hands out 32768 and up, macOS 49152 and\n * up), so an outgoing connection's source port never sits on it, and clear of\n * the ports `rebase dev` derives for the backend (3001–3999), the admin panel\n * (5173–5372) and the compose database (5433–5632).\n */\nexport function derivedManagedPort(projectRoot: string, key: string = DEFAULT_RESOURCE_KEY): number {\n const seed = key === DEFAULT_RESOURCE_KEY ? path.resolve(projectRoot) : `${path.resolve(projectRoot)}\\0${key}`;\n const digest = createHash(\"sha256\").update(seed).digest();\n return MANAGED_PORT_FIRST + (digest.readUInt32BE(0) % MANAGED_PORT_SPAN);\n}\n\nfunction portsFile(projectRoot: string): string {\n return path.join(devDbDir(projectRoot), PORTS_FILE_NAME);\n}\n\n/** The port each database last listened on, by key; empty when nothing is recorded or the record is unreadable. */\nfunction readRecordedPorts(projectRoot: string): Record<string, number> {\n try {\n const parsed: unknown = JSON.parse(fs.readFileSync(portsFile(projectRoot), \"utf8\"));\n if (!parsed || typeof parsed !== \"object\" || Array.isArray(parsed)) return {};\n const ports: Record<string, number> = {};\n for (const [key, port] of Object.entries(parsed)) {\n if (typeof port === \"number\" && Number.isInteger(port) && port > 0 && port <= 65535) ports[key] = port;\n }\n return ports;\n } catch {\n return {};\n }\n}\n\n/**\n * Remember where these databases listen, merged into what is recorded.\n *\n * Best effort: a `.rebase/` that cannot be written costs the next start its\n * preference, not the start itself.\n */\nexport function recordManagedPorts(projectRoot: string, ports: Record<string, number>): void {\n try {\n fs.mkdirSync(devDbDir(projectRoot), { recursive: true });\n const merged = { ...readRecordedPorts(projectRoot), ...ports };\n const target = portsFile(projectRoot);\n const temporary = `${target}.${process.pid}.tmp`;\n fs.writeFileSync(temporary, `${JSON.stringify(merged, null, 2)}\\n`, \"utf8\");\n fs.renameSync(temporary, target);\n } catch {\n // See above.\n }\n}\n\n/** Can a server listen on this loopback port right now? */\nexport function portBindable(port: number): Promise<boolean> {\n return new Promise((resolve) => {\n const server = net.createServer();\n server.once(\"error\", () => resolve(false));\n server.listen(port, \"127.0.0.1\", () => server.close(() => resolve(true)));\n });\n}\n\n/**\n * The port a managed database should listen on: where it listened last time,\n * else the one derived from the project path, else any free one.\n *\n * Stable on purpose. A backend holds the URL it was started with, and a pool\n * reconnects to the same address on its own — so a database that comes back on\n * the port it had is one a running `rebase dev` recovers from without a\n * restart. It used to be whatever the operating system handed out, and a `rebase\n * db stop` followed by any command that starts it again left the backend\n * dialling a port nothing listened on, answering 503 until dev was restarted.\n *\n * @param canBind stands in for {@link portBindable} in the tests.\n */\nexport async function chooseManagedPort(\n projectRoot: string,\n key: string = DEFAULT_RESOURCE_KEY,\n canBind: (port: number) => Promise<boolean> = portBindable\n): Promise<number> {\n const candidates = [readRecordedPorts(projectRoot)[key], derivedManagedPort(projectRoot, key)];\n for (const port of new Set(candidates.filter((candidate): candidate is number => candidate !== undefined))) {\n if (await canBind(port)) return port;\n }\n return findFreePort();\n}\n"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"storage-DKPUBTWG.js","names":[],"sources":["../src/commands/storage.ts"],"sourcesContent":["/**\n * CLI command: rebase storage <action>\n *\n * Whole-store maintenance through the running backend's `/api/admin/storage`,\n * authenticated with the service key from `.env`.\n *\n * Subcommands:\n * orphans — Stored files no row names; `--delete` deletes them\n * move — Copy a folder of files to another storage source, same keys\n */\nimport chalk from \"chalk\";\nimport readline from \"node:readline/promises\";\nimport { RebaseApiError } from \"@rebasepro/client\";\nimport { requireProjectRoot, readEnvFile } from \"../utils/project\";\nimport { parseCommandArgs, UsageError, wantsHelp } from \"../utils/args\";\nimport { unknownCommand } from \"../utils/unknown-command\";\nimport { cliUserAgent } from \"../utils/version\";\nimport { describeFailure, requireBackendUrl } from \"./api-keys\";\n\n/** Everything the switch below dispatches, for the did-you-mean. */\nexport const STORAGE_SUBCOMMANDS = [\"orphans\", \"move\"] as const;\n\n/** The flags `rebase storage orphans` takes. */\nexport const ORPHANS_FLAGS = {\n \"--source\": String,\n \"--older-than\": Number,\n \"--delete\": Boolean,\n \"--yes\": Boolean,\n \"-y\": \"--yes\",\n \"--json\": Boolean\n} as const;\n\n/** The flags `rebase storage move` takes. */\nexport const MOVE_FLAGS = {\n \"--from\": String,\n \"--to\": String,\n \"--prefix\": String,\n \"--delete-source\": Boolean,\n \"--json\": Boolean\n} as const;\n\n/** One stored object, as the backend lists it. */\ninterface StoredObjectEntry {\n key: string;\n size?: number;\n updated?: string;\n}\n\n/** What `GET /admin/storage/orphans` answers. */\nexport interface OrphanReport {\n storageId: string;\n objects: number;\n orphans: StoredObjectEntry[];\n recent: number;\n scanned: string[];\n unscanned: { collection: string; reason: string }[];\n}\n\ninterface MovedObject {\n key: string;\n outcome: \"copied\" | \"exists\" | \"failed\";\n deleted?: boolean;\n error?: string;\n}\n\ninterface Backend {\n baseUrl: string;\n request<T>(method: string, path: string, body?: unknown): Promise<T>;\n}\n\n/** The project's backend, holding the service key — the `admin` role, every scope. */\nfunction connect(): Backend {\n const projectRoot = requireProjectRoot();\n const env = readEnvFile(projectRoot);\n const serviceKey = env.SERVICE_KEY || env.REBASE_SERVICE_KEY;\n if (!serviceKey) {\n console.error(chalk.red(\"✗ REBASE_SERVICE_KEY not found in .env — required for storage maintenance.\"));\n process.exit(1);\n }\n // Only an address this project recorded or the developer named: the\n // request carries the service key. See `resolveBackendAddress`.\n const baseUrl = requireBackendUrl(env, projectRoot).replace(/\\/+$/, \"\");\n return {\n baseUrl,\n async request<T>(method: string, path: string, body?: unknown): Promise<T> {\n let response: Response;\n try {\n response = await fetch(`${baseUrl}/api/admin/storage${path}`, {\n method,\n headers: {\n \"Authorization\": `Bearer ${serviceKey}`,\n \"Accept\": \"application/json\",\n \"User-Agent\": cliUserAgent(),\n ...(body === undefined ? {} : { \"Content-Type\": \"application/json\" })\n },\n ...(body === undefined ? {} : { body: JSON.stringify(body) })\n });\n } catch (error: unknown) {\n throw new RebaseApiError(error instanceof Error ? error.message : String(error), { status: 0, code: \"NETWORK_ERROR\" });\n }\n const answer: unknown = await response.json().catch(() => undefined);\n if (!response.ok) {\n const error = answer && typeof answer === \"object\" && \"error\" in answer && answer.error && typeof answer.error === \"object\"\n ? answer.error as { message?: unknown; code?: unknown; details?: unknown }\n : undefined;\n throw new RebaseApiError(\n typeof error?.message === \"string\" ? error.message : `The server answered ${response.status}.`,\n { status: response.status, code: typeof error?.code === \"string\" ? error.code : undefined, details: error?.details }\n );\n }\n return (answer as { data: T }).data;\n }\n };\n}\n\nfunction fail(action: string, error: unknown, baseUrl: string): never {\n for (const line of describeFailure(action, error, baseUrl)) {\n console.error(line.startsWith(\"✗\") ? chalk.red(line) : chalk.gray(line));\n }\n if (error instanceof RebaseApiError && error.status === 404 && error.code !== \"UNKNOWN_STORAGE_SOURCE\") {\n console.error(chalk.gray(\" This backend has no storage maintenance routes: they arrived in 0.25, and need storage configured.\"));\n }\n process.exit(1);\n}\n\n/** Bytes, as a person reads them. */\nexport function formatBytes(bytes: number): string {\n if (bytes < 1024) return `${bytes} B`;\n const units = [\"KB\", \"MB\", \"GB\", \"TB\"];\n let value = bytes / 1024;\n let unit = 0;\n while (value >= 1024 && unit < units.length - 1) {\n value /= 1024;\n unit++;\n }\n return `${value.toFixed(value < 10 ? 1 : 0)} ${units[unit]}`;\n}\n\n/**\n * The lines that describe an orphan scan. Exported so its tests can read them\n * without a server.\n */\nexport function describeOrphanReport(report: OrphanReport, olderThanHours: number): string[] {\n const lines: string[] = [];\n const bytes = report.orphans.reduce((sum, orphan) => sum + (orphan.size ?? 0), 0);\n lines.push(` ${report.objects} file(s) in source \"${report.storageId}\", ${report.scanned.length} collection(s) read.`);\n if (report.orphans.length === 0) {\n lines.push(\" No file older than \" + `${olderThanHours}h is unused.`);\n } else {\n lines.push(` ${report.orphans.length} file(s) no row names, ${formatBytes(bytes)}:`);\n for (const orphan of report.orphans) {\n lines.push(` ${orphan.key}${orphan.size !== undefined ? ` ${formatBytes(orphan.size)}` : \"\"}${orphan.updated ? ` ${orphan.updated}` : \"\"}`);\n }\n }\n if (report.recent > 0) {\n const one = report.recent === 1;\n const more = report.orphans.length > 0 ? \"more \" : \"\";\n lines.push(` ${report.recent} ${more}unused ${one ? \"file is\" : \"files are\"} left out: ` +\n `${one ? \"it is\" : \"they are\"} newer than ${olderThanHours}h, or ${one ? \"its\" : \"their\"} age is unknown, ` +\n \"and an upload can be saved before the row that names it.\");\n }\n for (const entry of report.unscanned) {\n lines.push(` ⚠ ${entry.collection} could not be read (${entry.reason}): a file only it names is listed above.`);\n }\n return lines;\n}\n\nasync function confirm(question: string): Promise<boolean> {\n const prompt = readline.createInterface({ input: process.stdin, output: process.stdout });\n try {\n return /^y(es)?$/i.test((await prompt.question(question)).trim());\n } finally {\n prompt.close();\n }\n}\n\nasync function orphans(rawArgs: string[]): Promise<void> {\n const { flags } = parseCommandArgs({ spec: ORPHANS_FLAGS, rawArgs, commandWords: 2, command: \"storage orphans\", maxPositionals: 0 });\n const olderThan = flags[\"--older-than\"] ?? 24;\n if (!Number.isFinite(olderThan) || olderThan < 0) {\n throw new UsageError(\"--older-than takes a number of hours, 0 or more.\");\n }\n const query = new URLSearchParams({ olderThanHours: String(olderThan) });\n if (flags[\"--source\"]) query.set(\"storageId\", flags[\"--source\"]);\n const backend = connect();\n\n let report: OrphanReport;\n try {\n report = await backend.request<OrphanReport>(\"GET\", `/orphans?${query}`);\n } catch (error: unknown) {\n fail(\"Failed to scan storage\", error, backend.baseUrl);\n }\n\n if (!flags[\"--delete\"]) {\n if (flags[\"--json\"]) {\n console.log(JSON.stringify(report, null, 2));\n return;\n }\n console.log(\"\");\n for (const line of describeOrphanReport(report, olderThan)) console.log(line);\n if (report.orphans.length > 0) {\n console.log(\"\");\n console.log(chalk.gray(\" Nothing was deleted. Run again with --delete to delete them.\"));\n }\n console.log(\"\");\n return;\n }\n\n if (!flags[\"--json\"]) {\n console.log(\"\");\n for (const line of describeOrphanReport(report, olderThan)) console.log(line);\n console.log(\"\");\n }\n if (report.orphans.length === 0) return;\n if (report.unscanned.length > 0) {\n console.error(chalk.red(\"✗ Not deleting: some collections could not be read, so a file they name would look unused.\"));\n process.exit(1);\n }\n if (!flags[\"--yes\"]) {\n if (!process.stdin.isTTY) {\n throw new UsageError(\"--delete deletes files for good; pass --yes to confirm when there is no terminal to ask.\");\n }\n if (!await confirm(` Delete these ${report.orphans.length} file(s) for good? [y/N] `)) {\n console.log(chalk.gray(\" Nothing was deleted.\"));\n return;\n }\n }\n\n let result: { deleted: string[]; failed: { key: string; error: string }[] };\n try {\n // The server finds them again before it deletes: a file a row has\n // named since the list was printed is not deleted.\n result = await backend.request(\"DELETE\", `/orphans?${query}`);\n } catch (error: unknown) {\n fail(\"Failed to delete unused files\", error, backend.baseUrl);\n }\n if (flags[\"--json\"]) {\n console.log(JSON.stringify(result, null, 2));\n } else {\n console.log(chalk.green(` ✓ Deleted ${result.deleted.length} file(s).`));\n for (const failure of result.failed) console.error(chalk.red(` ✗ ${failure.key}: ${failure.error}`));\n console.log(\"\");\n }\n if (result.failed.length > 0) process.exitCode = 1;\n}\n\nasync function move(rawArgs: string[]): Promise<void> {\n const { flags } = parseCommandArgs({ spec: MOVE_FLAGS, rawArgs, commandWords: 2, command: \"storage move\", maxPositionals: 0 });\n const from = flags[\"--from\"];\n const to = flags[\"--to\"];\n if (!from && !to) {\n throw new UsageError(\"Name the sources: --from <source> --to <source>. The default source is \\\"(default)\\\", and either may be left out for it.\");\n }\n const backend = connect();\n const counts = { copied: 0, exists: 0, failed: 0, deleted: 0 };\n const failures: MovedObject[] = [];\n let cursor: string | undefined;\n do {\n let page: { moved: MovedObject[]; cursor?: string };\n try {\n page = await backend.request(\"POST\", \"/move\", {\n from,\n to,\n prefix: flags[\"--prefix\"],\n cursor,\n deleteSource: flags[\"--delete-source\"] === true\n });\n } catch (error: unknown) {\n fail(\"Failed to move files\", error, backend.baseUrl);\n }\n for (const object of page.moved) {\n counts[object.outcome]++;\n if (object.deleted) counts.deleted++;\n if (object.outcome === \"failed\") failures.push(object);\n }\n if (!flags[\"--json\"] && process.stdout.isTTY) {\n process.stdout.write(`\\r ${counts.copied} copied, ${counts.exists} already there, ${counts.failed} failed…`);\n }\n cursor = page.cursor;\n } while (cursor);\n\n if (flags[\"--json\"]) {\n console.log(JSON.stringify({ ...counts, failures }, null, 2));\n } else {\n if (process.stdout.isTTY) process.stdout.write(\"\\n\");\n console.log(chalk.green(` ✓ ${counts.copied} copied, ${counts.exists} already there` +\n `${flags[\"--delete-source\"] ? `, ${counts.deleted} deleted from the source` : \"\"}.`));\n for (const failure of failures) console.error(chalk.red(` ✗ ${failure.key}: ${failure.error ?? \"failed\"}`));\n if (counts.copied + counts.exists > 0 && !flags[\"--delete-source\"]) {\n console.log(chalk.gray(\" The source keeps its copies. Point the property at the new source, then run again with --delete-source.\"));\n }\n }\n if (failures.length > 0) process.exitCode = 1;\n}\n\nexport async function storageCommand(subcommand: string | undefined, rawArgs: string[]): Promise<void> {\n if (!subcommand || subcommand === \"--help\" || wantsHelp(rawArgs)) {\n printStorageHelp();\n return;\n }\n switch (subcommand) {\n case \"orphans\":\n await orphans(rawArgs);\n break;\n case \"move\":\n await move(rawArgs);\n break;\n default:\n unknownCommand(subcommand, STORAGE_SUBCOMMANDS, \"storage\");\n }\n}\n\nfunction printStorageHelp(): void {\n console.log(`\n${chalk.bold(\"rebase storage\")} — Maintain stored files, through the running backend\n\n${chalk.green.bold(\"Usage\")}\n rebase storage ${chalk.blue(\"<command>\")} [options]\n\n${chalk.green.bold(\"Commands\")}\n ${chalk.blue.bold(\"orphans\")} List the stored files no row names\n ${chalk.blue.bold(\"move\")} Copy a folder of files to another storage source, same keys\n\n${chalk.green.bold(\"orphans Options\")}\n ${chalk.blue(\"--source\")} Storage source key ${chalk.gray(\"(default: the default source)\")}\n ${chalk.blue(\"--older-than\")} Hours a file must be old to count ${chalk.gray(\"(default: 24)\")}\n ${chalk.blue(\"--delete\")} Delete them, after asking\n ${chalk.blue(\"--yes, -y\")} Do not ask ${chalk.gray(\"(required with --delete when there is no terminal)\")}\n ${chalk.blue(\"--json\")} Print the report as JSON\n\n${chalk.green.bold(\"move Options\")}\n ${chalk.blue(\"--from\")} Source to copy from ${chalk.gray(\"(default: the default source)\")}\n ${chalk.blue(\"--to\")} Source to copy to ${chalk.gray(\"(default: the default source)\")}\n ${chalk.blue(\"--prefix\")} Folder to copy ${chalk.gray(\"(default: everything)\")}\n ${chalk.blue(\"--delete-source\")} Delete each file from the source once the target holds it\n ${chalk.blue(\"--json\")} Print the counts as JSON\n\n${chalk.green.bold(\"Notes\")}\n A file is unused when no string in any row of any collection names it —\n a key, a URL, a ${chalk.blue(\"rebase-storage:\")} reference in text — whatever property\n or source holds it. Files newer than ${chalk.blue(\"--older-than\")} are left out: the admin\n panel uploads a file before the row that names it is saved. Files your own\n tables name, outside collections, are not seen: check before deleting.\n\n Both read ${chalk.blue(\"REBASE_SERVICE_KEY\")} from .env and need the backend running.\n`);\n}\n"],"mappings":"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"}
|