@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":"doctor-CATRdWm6.js","names":[],"sources":["../src/utils/libpq-url.ts","../src/doctor-environment.ts","../src/commands/doctor.ts"],"sourcesContent":["/**\n * Detect Postgres connection strings that libpq refuses to parse.\n *\n * libpq splits a URI query parameter on the FIRST `=` and rejects any further\n * one in the same parameter:\n *\n * extra key/value separator \"=\" in URI query parameter: \"options\"\n *\n * That makes a URL like\n *\n * postgresql://u:p@h:5432/db?options=-c%20search_path=public&sslmode=disable\n *\n * unusable by every libpq caller — `pg_dump`/`pg_restore` behind\n * `rebase db backup|restore`, and a plain `psql \"$DATABASE_URL\"`. It is not a\n * version quirk: Postgres 15 through 18 all refuse it.\n *\n * `rebase init` generated exactly that string until 2026-08-18, and the failure\n * is invisible day to day because node-postgres parses URLs itself and accepts\n * it — so `rebase dev` and `rebase db push` work while backups do not. Fixing\n * the generator does nothing for projects that already exist, which is what\n * these functions are for: `rebase doctor` reads them off disk and reports.\n */\n\n/** A query parameter whose value carries a literal `=`. */\nexport interface UnparseableParam {\n /** The parameter name, e.g. \"options\". */\n name: string;\n /** The raw `name=value` text as it appears in the URL. */\n raw: string;\n}\n\n/** Postgres URI schemes. A key/value DSN (\"host=… dbname=…\") is not one. */\nconst POSTGRES_URI_RE = /^postgres(ql)?:\\/\\//i;\n\n/**\n * Return the query parameters libpq would reject, or an empty array.\n *\n * Deliberately not built on `new URL()`: that parser is happy to accept the\n * broken form (it splits on the first `=` and keeps the rest as the value),\n * so it cannot see the defect at all. The rule being checked is libpq's, and\n * it is about the raw text.\n */\nexport function findUnparseableParams(connectionString: string): UnparseableParam[] {\n if (!POSTGRES_URI_RE.test(connectionString)) return [];\n\n const q = connectionString.indexOf(\"?\");\n if (q === -1) return [];\n\n // A fragment is not part of the query.\n const query = connectionString.slice(q + 1).split(\"#\")[0];\n if (!query) return [];\n\n const found: UnparseableParam[] = [];\n for (const part of query.split(\"&\")) {\n if (!part) continue;\n // One separator is the parameter's own; any further one is the defect.\n if (part.split(\"=\").length > 2) {\n found.push({ name: part.slice(0, part.indexOf(\"=\")), raw: part });\n }\n }\n return found;\n}\n\n/**\n * Percent-encode the offending `=` characters, leaving everything else byte for\n * byte as it was.\n *\n * Only the second and later `=` in a parameter are rewritten — the first is the\n * real separator. Nothing else is touched: re-serialising through `URL` would\n * also reorder parameters and turn spaces into `+`, which libpq does not decode,\n * so a \"tidy up\" would trade one unparseable string for another.\n */\nexport function encodeExtraEquals(connectionString: string): string {\n if (findUnparseableParams(connectionString).length === 0) return connectionString;\n\n const q = connectionString.indexOf(\"?\");\n const head = connectionString.slice(0, q + 1);\n const rest = connectionString.slice(q + 1);\n const hash = rest.indexOf(\"#\");\n const query = hash === -1 ? rest : rest.slice(0, hash);\n const fragment = hash === -1 ? \"\" : rest.slice(hash);\n\n const fixed = query\n .split(\"&\")\n .map((part) => {\n const first = part.indexOf(\"=\");\n if (first === -1) return part;\n const name = part.slice(0, first);\n const value = part.slice(first + 1).replace(/=/g, \"%3D\");\n return `${name}=${value}`;\n })\n .join(\"&\");\n\n return `${head}${fixed}${fragment}`;\n}\n\n/** One connection string, found somewhere on disk, that libpq cannot parse. */\nexport interface LibpqUrlFinding {\n /** File it came from, relative to the project root. */\n file: string;\n /** The variable holding it, e.g. \"DATABASE_URL\". */\n variable: string;\n /** Parameter names libpq objects to. */\n params: string[];\n /** The same string with the offending `=` encoded. */\n suggested: string;\n}\n\n/** Variables whose value is a Postgres connection string. */\nconst CONNECTION_VARIABLES = [\"DATABASE_URL\", \"ADMIN_CONNECTION_STRING\"];\n\n/**\n * Scan one file's text for connection strings libpq would reject.\n *\n * Handles both shapes a scaffolded project uses, because both shipped with the\n * defect and a deployed stack is broken by the compose one alone:\n *\n * .env DATABASE_URL=postgresql://…\n * docker-compose.yml DATABASE_URL: postgresql://…\n */\nexport function scanTextForLibpqUrls(file: string, text: string): LibpqUrlFinding[] {\n const findings: LibpqUrlFinding[] = [];\n\n for (const line of text.split(\"\\n\")) {\n const trimmed = line.trim();\n // Commented-out examples are not what the project runs on.\n if (trimmed.startsWith(\"#\")) continue;\n\n for (const variable of CONNECTION_VARIABLES) {\n // `NAME=value` or `NAME: value`, optionally exported/indented.\n const match = trimmed.match(\n new RegExp(`^(?:export\\\\s+)?${variable}\\\\s*[:=]\\\\s*(.+)$`)\n );\n if (!match) continue;\n\n // Strip surrounding quotes and any trailing comment-free whitespace.\n let value = match[1].trim();\n const quoted = value.match(/^(['\"])(.*)\\1$/);\n if (quoted) value = quoted[2];\n if (!value) continue;\n\n const params = findUnparseableParams(value);\n if (params.length === 0) continue;\n\n findings.push({\n file,\n variable,\n params: params.map((p) => p.name),\n suggested: encodeExtraEquals(value)\n });\n }\n }\n\n return findings;\n}\n","/**\n * The checks that need no database, and that catch what breaks a first run.\n *\n * `rebase doctor` compared three descriptions of a schema, which is the right\n * check for a project that is working and the wrong one for a project that has\n * never worked. Everything that stops a first run — the wrong Node, two\n * lockfiles disagreeing, two collections claiming one slug, a `JWT_SECRET`\n * short enough to be guessed, a half-installed workspace with three versions of\n * `@rebasepro/server` in it — happens before a single table is compared, and\n * doctor said nothing about any of it.\n *\n * Every function here is pure: it takes what it read and returns findings. The\n * reading and the printing are at the edges, in `commands/doctor.ts`, so each\n * check has a fixture that fails it without a filesystem, a project or a\n * database.\n */\n\n/** One thing that is wrong, and what to do about it. */\nexport interface EnvironmentFinding {\n /** Which check produced it, for grouping in the report. */\n check: \"node\" | \"package-manager\" | \"collections\" | \"env\" | \"versions\" | \"atlas\";\n severity: \"error\" | \"warning\";\n message: string;\n fix: string;\n}\n\n// ── Atlas ────────────────────────────────────────────────────────────────\n\n/** What is on disk for `@ariga/atlas`, read by `commands/doctor.ts`. */\nexport interface AtlasBinaryState {\n /** A `node_modules/.bin/atlas` the project's commands would find. */\n onPath: boolean;\n /** `@ariga/atlas/package.json` resolves from the project. */\n packageInstalled: boolean;\n /** The binary the package's `bin` names exists as a file. */\n binaryOnDisk: boolean;\n /** For the remedy: the reader's own package manager. */\n manager: string;\n /**\n * The workspace member the install goes into (`backend` in a scaffold),\n * or absent at a project that is no workspace. A scaffold's root refuses\n * `pnpm add` without `-w`, and atlas belongs to the backend anyway.\n */\n workspaceDir?: string | null;\n}\n\n/** `@ariga/atlas` added as a dev dependency where the project keeps it. */\nfunction atlasAddCommand(manager: string, workspaceDir: string | null | undefined): string {\n const pkg = \"@ariga/atlas\";\n if (!workspaceDir) return `${manager} add -D ${pkg}`;\n switch (manager) {\n case \"npm\": return `npm install -D ${pkg} -w ${workspaceDir}`;\n case \"yarn\": return `yarn --cwd ${workspaceDir} add -D ${pkg}`;\n case \"bun\": return `bun add -d ${pkg} --cwd ${workspaceDir}`;\n default: return `${manager} --dir ${workspaceDir} add -D ${pkg}`;\n }\n}\n\n/**\n * Whether `db push`, `db generate` and `db migrate` can run at all.\n *\n * They shell out to `atlas`, and its absence is invisible until one of them is\n * run: `@ariga/atlas` downloads its platform binary in `preinstall`, pnpm 10+\n * and npm 12+ refuse a dependency's lifecycle scripts unless allowlisted, and\n * the install exits 0 either way. The scaffold allowlists it, so this is a\n * problem for the person who added `@rebasepro/server-postgres` to a project\n * they already had — five lines of `ERR_PNPM_IGNORED_BUILDS` and\n * `Failed to create bin … ENOENT`, several screens up, with nothing saying that\n * a schema push will fail later.\n *\n * Three states, because two of them need opposite advice. The package absent is\n * an install; the package present with no binary is an allowlist; the binary\n * present with no `.bin` shim is a re-install, and telling that reader their\n * build scripts are blocked sends them to a command that does nothing.\n */\nexport function checkAtlasBinary(state: AtlasBinaryState | null): EnvironmentFinding[] {\n if (!state || state.onPath) return [];\n\n if (!state.packageInstalled) {\n return [{\n check: \"atlas\",\n severity: \"warning\",\n message: \"The atlas binary is not on PATH and @ariga/atlas is not installed — \"\n + \"`db push`, `db generate` and `db migrate` will fail.\",\n fix: `Install it: ${atlasAddCommand(state.manager, state.workspaceDir)}, and allow its install script.`\n }];\n }\n\n if (!state.binaryOnDisk) {\n return [{\n check: \"atlas\",\n severity: \"warning\",\n message: \"@ariga/atlas is installed and its binary is missing — its `preinstall` \"\n + \"script was blocked, so `db push` and `db migrate` will fail.\",\n fix: \"Allow the build script (pnpm: `allowBuilds` in pnpm-workspace.yaml or \"\n + \"`pnpm approve-builds`; npm: `allowScripts` in package.json), then re-install.\"\n }];\n }\n\n return [{\n check: \"atlas\",\n severity: \"warning\",\n message: \"The atlas binary is on disk but node_modules/.bin/atlas is missing, \"\n + \"so nothing can invoke it.\",\n fix: \"Re-resolve the tree so the link is recreated: `pnpm install --force`, or delete \"\n + \"node_modules and install again.\"\n }];\n}\n\n// ── Node ─────────────────────────────────────────────────────────────────\n\n/**\n * `20.11.0` → `[20, 11, 0]`, ignoring anything after the patch.\n *\n * A bare major is a version: `>=20` is how every `engines.node` in this repo is\n * written, and requiring a minor here made the whole check return nothing —\n * silently, which is the only way a check like this ever fails.\n */\nfunction parseVersion(raw: string): [number, number, number] | null {\n const match = raw.trim().replace(/^v/, \"\").match(/^(\\d+)(?:\\.(\\d+))?(?:\\.(\\d+))?/);\n if (!match) return null;\n return [Number(match[1]), Number(match[2] ?? 0), Number(match[3] ?? 0)];\n}\n\n/** The lowest version a `>=x.y.z` range admits, or null for anything else. */\nexport function minimumFromRange(range: string): [number, number, number] | null {\n const match = range.match(/>=\\s*v?(\\d+(?:\\.\\d+)?(?:\\.\\d+)?)/);\n return match ? parseVersion(match[1]) : null;\n}\n\nfunction isBelow(actual: [number, number, number], minimum: [number, number, number]): boolean {\n for (let i = 0; i < 3; i++) {\n if (actual[i] < minimum[i]) return true;\n if (actual[i] > minimum[i]) return false;\n }\n return false;\n}\n\n/**\n * Is the running Node old enough to break things that will not say so?\n *\n * The symptoms are not \"unsupported Node\". They are a syntax error inside a\n * dependency, a missing global, or — the one that costs the most time — a\n * `node:` builtin that resolves and then behaves differently. Naming the\n * version is the whole check.\n */\nexport function checkNodeVersion(running: string, required: string | undefined): EnvironmentFinding[] {\n if (!required) return [];\n const minimum = minimumFromRange(required);\n const actual = parseVersion(running);\n if (!minimum || !actual) return [];\n if (!isBelow(actual, minimum)) return [];\n\n return [{\n check: \"node\",\n severity: \"error\",\n message: `Node ${running} is running, and Rebase needs ${required}.`,\n fix: `Install Node ${minimum[0]} or newer (nvm install ${minimum[0]}), then reinstall dependencies.`\n }];\n}\n\n// ── Package manager ──────────────────────────────────────────────────────\n\n/** The lockfiles a project may have, and the manager each one belongs to. */\nexport const LOCKFILES: Record<string, string> = {\n \"pnpm-lock.yaml\": \"pnpm\",\n \"package-lock.json\": \"npm\",\n \"yarn.lock\": \"yarn\",\n \"bun.lockb\": \"bun\"\n};\n\n/**\n * Two package managers in one project.\n *\n * A Rebase project is a pnpm workspace, and running `npm install` inside one\n * rewrites `node_modules` into a hoisted layout that pnpm then disagrees with.\n * The result is not an install error: it is `Cannot find module` from inside a\n * dependency, hours later, on a machine where it worked yesterday. The\n * leftover `package-lock.json` is the only durable evidence, and nothing looked\n * at it.\n */\nexport function checkPackageManager(\n lockfilesPresent: string[],\n declared: string | undefined\n): EnvironmentFinding[] {\n const known = lockfilesPresent.filter(file => file in LOCKFILES);\n if (known.length < 2) {\n // One lockfile, or none. A single one that disagrees with\n // `packageManager` is worth saying too.\n const declaredName = declared?.split(\"@\")[0];\n if (known.length === 1 && declaredName && LOCKFILES[known[0]] !== declaredName) {\n return [{\n check: \"package-manager\",\n severity: \"warning\",\n message: `This project declares packageManager \"${declared}\" and has a ${LOCKFILES[known[0]]} lockfile (${known[0]}).`,\n fix: `Delete ${known[0]} and node_modules, then install with ${declaredName}.`\n }];\n }\n return [];\n }\n\n const managers = known.map(file => LOCKFILES[file]);\n return [{\n check: \"package-manager\",\n severity: \"error\",\n message: `Two package managers have installed here: ${known.join(\" and \")}.`,\n fix: `Keep one. Delete the others and node_modules, then reinstall with ${declared?.split(\"@\")[0] ?? managers[0]}.`\n }];\n}\n\n// ── Collections ──────────────────────────────────────────────────────────\n\n/**\n * Two collections claiming one slug.\n *\n * The slug is the route, the table lookup and the registry key, and the\n * registry keeps the last one registered. So the loser is not reported missing:\n * it is served as the winner, with the winner's properties, under its own name.\n * Every symptom of this points somewhere else.\n */\nexport function checkDuplicateSlugs(\n collections: Array<{ slug?: string; name?: string }>\n): EnvironmentFinding[] {\n const byslug = new Map<string, number>();\n for (const collection of collections) {\n if (!collection.slug) continue;\n byslug.set(collection.slug, (byslug.get(collection.slug) ?? 0) + 1);\n }\n\n return [...byslug]\n .filter(([, count]) => count > 1)\n .map(([slug, count]) => ({\n check: \"collections\" as const,\n severity: \"error\" as const,\n message: `${count} collections declare the slug \"${slug}\". Only the last one registered is served.`,\n fix: `Give each collection its own slug. The one that loses is not reported missing — it answers as the winner.`\n }));\n}\n\n// ── .env ─────────────────────────────────────────────────────────────────\n\n/** What `loadEnv` requires of a JWT secret. Mirrors `rebaseEnvSchema`. */\nexport const MIN_JWT_SECRET_LENGTH = 32;\n\n/**\n * The two `.env` facts that fail late and confusingly.\n *\n * A short `JWT_SECRET` boots fine and is refused by `loadEnv` only in\n * production, which is the worst place to find out. Missing CORS in production\n * is the opposite shape — it does not fail at all; the API simply accepts\n * requests from anywhere.\n *\n * Values are never echoed. This reads a file that holds every credential the\n * project has, and doctor's output goes wherever a terminal goes.\n */\nexport function checkEnvSanity(values: Record<string, string>): EnvironmentFinding[] {\n const findings: EnvironmentFinding[] = [];\n const production = values.NODE_ENV === \"production\";\n\n const secret = values.JWT_SECRET;\n if (secret !== undefined && secret.length > 0 && secret.length < MIN_JWT_SECRET_LENGTH) {\n findings.push({\n check: \"env\",\n severity: production ? \"error\" : \"warning\",\n message: `JWT_SECRET is ${secret.length} characters. The runtime requires at least ${MIN_JWT_SECRET_LENGTH}, and refuses to boot below it in production.`,\n fix: \"Generate one: openssl rand -hex 32\"\n });\n }\n\n if (production && !values.CORS_ORIGINS && !values.FRONTEND_URL) {\n findings.push({\n check: \"env\",\n severity: \"error\",\n message: \"NODE_ENV=production with neither CORS_ORIGINS nor FRONTEND_URL set — the API will accept requests from any origin.\",\n fix: \"Set CORS_ORIGINS to the origins that may call this API.\"\n });\n }\n\n return findings;\n}\n\n/**\n * Parse a `.env` into a plain object.\n *\n * Deliberately not `dotenv`: this must not touch `process.env`, and doctor may\n * be reading a file for a different environment than the one it is running in.\n */\nexport function parseEnvFile(text: string): Record<string, string> {\n const values: Record<string, string> = {};\n for (const line of text.split(\"\\n\")) {\n const trimmed = line.trim();\n if (!trimmed || trimmed.startsWith(\"#\")) continue;\n const eq = trimmed.indexOf(\"=\");\n if (eq <= 0) continue;\n const key = trimmed.slice(0, eq).replace(/^export\\s+/, \"\").trim();\n let value = trimmed.slice(eq + 1).trim();\n if ((value.startsWith('\"') && value.endsWith('\"')) || (value.startsWith(\"'\") && value.endsWith(\"'\"))) {\n value = value.slice(1, -1);\n }\n values[key] = value;\n }\n return values;\n}\n\n// ── Version skew ─────────────────────────────────────────────────────────\n\n/** One `@rebasepro/*` dependency, and where it was declared. */\nexport interface DeclaredDependency {\n /** Where it was declared, relative to the project root. */\n file: string;\n name: string;\n /** The range exactly as written. */\n range: string;\n}\n\n/**\n * The same `@rebasepro/*` package pinned to different versions in one project.\n *\n * A Rebase project declares them in several package.json files — the root, the\n * backend, the frontend, the config workspace — and nothing has ever compared\n * them. The failure that produces is not an install error: pnpm installs both,\n * `instanceof` stops working across the two copies, and the symptom is a\n * `CollectionConfig` that fails a type guard written against an identical type.\n *\n * `workspace:` ranges are excluded: inside this repo's own workspaces they all\n * resolve to the same build by construction.\n */\nexport function checkVersionSkew(declared: DeclaredDependency[]): EnvironmentFinding[] {\n const byPackage = new Map<string, Map<string, string[]>>();\n\n for (const dependency of declared) {\n if (!dependency.name.startsWith(\"@rebasepro/\")) continue;\n if (dependency.range.startsWith(\"workspace:\") || dependency.range.startsWith(\"link:\")) continue;\n if (dependency.range === \"*\" || dependency.range === \"latest\") continue;\n\n const ranges = byPackage.get(dependency.name) ?? new Map<string, string[]>();\n ranges.set(dependency.range, [...(ranges.get(dependency.range) ?? []), dependency.file]);\n byPackage.set(dependency.name, ranges);\n }\n\n const findings: EnvironmentFinding[] = [];\n for (const [name, ranges] of byPackage) {\n if (ranges.size < 2) continue;\n const described = [...ranges]\n .map(([range, files]) => `${range} (${files.join(\", \")})`)\n .join(\", \");\n findings.push({\n check: \"versions\",\n severity: \"error\",\n message: `${name} is pinned to different versions in this project: ${described}.`,\n fix: \"Run `rebase upgrade` to pin every @rebasepro package to one release and reinstall. Two copies of a Rebase package break `instanceof` between them, which fails as a type guard rejecting its own type.\"\n });\n }\n return findings;\n}\n","/**\n * CLI command: rebase doctor\n *\n * Detects three-way schema drift between collection definitions,\n * the generated Drizzle schema, and the live PostgreSQL database.\n */\nimport chalk from \"chalk\";\nimport fs from \"fs\";\nimport path from \"path\";\nimport { execa } from \"execa\";\nimport {\n requireProjectRoot,\n requireBackendDir,\n getActiveBackendPlugin,\n resolvePluginCliScript,\n resolveTsx,\n findEnvFile,\n exitDependenciesNotInstalled\n} from \"../utils/project\";\nimport { fileURLToPath, pathToFileURL } from \"url\";\nimport { createRequire } from \"module\";\nimport { scanTextForLibpqUrls, type LibpqUrlFinding } from \"../utils/libpq-url\";\nimport { analyseFunctionsDirectory, summarisePortability } from \"../function-portability\";\nimport { reportSpawnFailure } from \"../utils/spawn-error\";\nimport { argsFromCommand } from \"../utils/command-words\";\nimport { COLLECTIONS_FLAG_HELP, refuseMissingCollectionsPath } from \"../utils/path-flags\";\nimport { absolutizeLocalPathArgs } from \"./db\";\nimport { parseCommandArgs, wantsHelp } from \"../utils/args\";\nimport { loadManifest, findBackendApp, resolveBackendPaths } from \"../manifest\";\nimport { DEV_DATABASE_KIND_ENV, devDatabaseKind, managedNotices, prepareDatabaseEnv } from \"../dev-db/prepare\";\nimport {\n checkDuplicateSlugs,\n checkEnvSanity,\n checkAtlasBinary,\n checkNodeVersion,\n checkPackageManager,\n checkVersionSkew,\n LOCKFILES,\n parseEnvFile,\n type AtlasBinaryState,\n type DeclaredDependency,\n type EnvironmentFinding\n} from \"../doctor-environment\";\n\n/**\n * Files that can hold a connection string the project actually runs on.\n *\n * `.env` is what local commands read; the compose files are what a self-hosted\n * stack and its scheduled backup cron read. Both shipped with the defect, and a\n * deployed stack is broken by the compose one even when `.env` has been fixed.\n * `.env.example` is deliberately absent: nothing runs on it.\n */\nconst CONNECTION_FILES = [\n \".env\",\n \".env.local\",\n \"docker-compose.yml\",\n \"docker-compose.yaml\",\n \"docker-compose.custom.yml\"\n];\n\n/**\n * Find connection strings libpq cannot parse, anywhere in the project.\n *\n * Exported for the tests; `envFile` is passed separately because a project may\n * keep its `.env` outside the root (see `findEnvFile`).\n */\nexport function findLibpqUrlProblems(projectRoot: string, envFile?: string | null): LibpqUrlFinding[] {\n const candidates = CONNECTION_FILES.map((f) => path.join(projectRoot, f));\n if (envFile) candidates.push(envFile);\n\n const findings: LibpqUrlFinding[] = [];\n const seen = new Set<string>();\n\n for (const file of candidates) {\n if (seen.has(file)) continue;\n seen.add(file);\n let text: string;\n try {\n if (!fs.existsSync(file)) continue;\n text = fs.readFileSync(file, \"utf-8\");\n } catch {\n // Unreadable is not a finding — doctor reports what it can see.\n continue;\n }\n findings.push(...scanTextForLibpqUrls(path.relative(projectRoot, file) || path.basename(file), text));\n }\n\n return findings;\n}\n\n/**\n * Report unparseable connection strings, if any. Returns true when there were,\n * so the finding reaches the exit code like the environment errors do.\n *\n * Runs before the plugin's drift check and never blocks it. The plugin connects\n * through node-postgres, which parses these URLs happily — so it cannot see this\n * defect, and a project with it will otherwise get a clean bill of health while\n * `rebase db backup` fails.\n */\nfunction reportLibpqUrlProblems(findings: LibpqUrlFinding[]): boolean {\n if (findings.length === 0) return false;\n\n console.log(\"\");\n console.log(chalk.red.bold(\" ✗ Connection string that PostgreSQL's own tools cannot parse\"));\n console.log(\"\");\n for (const f of findings) {\n console.log(` ${chalk.bold(f.file)} → ${chalk.bold(f.variable)}`);\n console.log(chalk.gray(` the \"${f.params.join('\", \"')}\" parameter contains an unencoded \"=\".`));\n console.log(chalk.gray(\" Replace the value with:\"));\n console.log(` ${chalk.cyan(f.suggested)}`);\n console.log(\"\");\n }\n console.log(chalk.gray(\" libpq splits a query parameter on the first \\\"=\\\" and rejects any further\"));\n console.log(chalk.gray(\" one, so this fails:\"));\n console.log(chalk.gray(\" extra key/value separator \\\"=\\\" in URI query parameter\"));\n console.log(\"\");\n console.log(chalk.gray(\" Affects rebase db backup / restore, scheduled backups, and psql.\"));\n console.log(chalk.gray(\" NOT rebase dev or db push — those use a driver that accepts it, which\"));\n console.log(chalk.gray(\" is why a project can look healthy and still have no working backups.\"));\n console.log(chalk.gray(\" Projects scaffolded before 2026-08-18 all carry it.\"));\n console.log(\"\");\n return true;\n}\n\n/**\n * `--help` is answered before the project guard, not after.\n *\n * `doctor` declared no `--help` at all, so the flag fell through to the command\n * body and hit `requireProjectRoot()` — and `rebase doctor --help` outside a\n * project answered \"✗ Could not find a Rebase project root.\" Asking a command\n * what it does is the one question that cannot require being somewhere\n * particular to ask.\n */\n/**\n * What each custom function needs from its host.\n *\n * Prints nothing when there is nothing to say — which is the common case, and\n * the reason it can afford to run every time. When it does print, the ordering\n * is deliberate: the one finding that is a bug *today* comes first, and the\n * rest is a single descriptive line about where these functions could run.\n */\nfunction reportFunctionPortability(projectRoot: string): void {\n let results: ReturnType<typeof analyseFunctionsDirectory>;\n try {\n const backend = findBackendApp(loadManifest(projectRoot).manifest);\n if (!backend) return;\n const paths = resolveBackendPaths(backend.app, projectRoot);\n results = analyseFunctionsDirectory(path.join(projectRoot, paths.functions), projectRoot);\n } catch {\n // A project with no manifest, or one shaped differently — doctor has\n // plenty else to report, and this section is advisory.\n return;\n }\n\n const lines = summarisePortability(results);\n if (lines.length === 0) return;\n\n console.log(chalk.bold(\"\\nCustom functions\"));\n for (const line of lines) {\n console.log(line.trimStart().startsWith(\"⚠\") ? chalk.yellow(line) : chalk.gray(line));\n }\n console.log(chalk.gray(\" See https://rebase.pro/docs/backend/custom-functions#runtime-portability\"));\n}\n\n/**\n * Read everything the environment checks need, then run them.\n *\n * The reading is here and the deciding is in `doctor-environment.ts`, so every\n * check has a fixture that fails it without a project on disk.\n */\nexport function collectEnvironmentFindings(\n projectRoot: string,\n envFile: string | null | undefined\n): EnvironmentFinding[] {\n const findings: EnvironmentFinding[] = [];\n\n // Node, against the engines range the installed CLI declares.\n findings.push(...checkNodeVersion(process.versions.node, readCliEngines()));\n\n // Two package managers in one project.\n const rootPackage = readJson(path.join(projectRoot, \"package.json\"));\n findings.push(...checkPackageManager(\n Object.keys(LOCKFILES).filter(file => fs.existsSync(path.join(projectRoot, file))),\n typeof rootPackage?.packageManager === \"string\" ? rootPackage.packageManager : undefined\n ));\n\n // Two collections claiming one slug. Read as text rather than imported: a\n // collection file can import anything, and doctor must not execute a\n // project's code to tell it two slugs collide.\n findings.push(...checkDuplicateSlugs(readDeclaredSlugs(projectRoot)));\n\n // `.env`, values never echoed.\n if (envFile && fs.existsSync(envFile)) {\n try {\n findings.push(...checkEnvSanity(parseEnvFile(fs.readFileSync(envFile, \"utf-8\"))));\n } catch { /* unreadable is not a finding */ }\n }\n\n // One `@rebasepro/*` package, two versions.\n findings.push(...checkVersionSkew(readDeclaredRebaseDeps(projectRoot)));\n\n // The atlas binary, which every schema command shells out to and whose\n // absence is invisible until one of them is run.\n findings.push(...checkAtlasBinary(readAtlasBinaryState(projectRoot)));\n\n return findings;\n}\n\n/**\n * What is actually on disk for `@ariga/atlas`, from the project's point of view.\n *\n * Three separate questions, because the three states they distinguish need\n * three different remedies — see `checkAtlasBinary`. Directories are searched\n * from the project root upwards and through the workspaces a scaffold has,\n * because `db push` runs from `backend/` and the install may be hoisted to the\n * root.\n */\nexport function readAtlasBinaryState(projectRoot: string): AtlasBinaryState | null {\n const roots = [projectRoot, path.join(projectRoot, \"backend\"), path.join(projectRoot, \"config\")];\n\n const findUpwards = (starts: string[], relative: string): string | null => {\n for (const start of starts) {\n let dir = start;\n for (let i = 0; i < 8; i++) {\n const candidate = path.join(dir, \"node_modules\", relative);\n if (fs.existsSync(candidate)) return candidate;\n const parent = path.dirname(dir);\n if (parent === dir) break;\n dir = parent;\n }\n }\n return null;\n };\n\n // `@ariga/atlas` is a dependency of `@rebasepro/server-postgres`, not of the\n // project, so under pnpm's isolated layout — the scaffold default — it lives\n // in `.pnpm/` and a directory walk over `node_modules/` never sees it.\n // Resolve through the module graph the way the driver itself does.\n const resolveFrom = (from: string, request: string): string | null => {\n try {\n return createRequire(pathToFileURL(from)).resolve(request);\n } catch {\n return null;\n }\n };\n const bases = roots.map(dir => path.join(dir, \"package.json\")).filter(file => fs.existsSync(file));\n const driverManifests = bases\n .map(base => resolveFrom(base, \"@rebasepro/server-postgres/package.json\"))\n .filter((file): file is string => file !== null);\n\n let manifest: string | null = findUpwards(roots, path.join(\"@ariga\", \"atlas\", \"package.json\"));\n if (!manifest) {\n for (const base of [...bases, ...driverManifests]) {\n manifest = resolveFrom(base, \"@ariga/atlas/package.json\");\n if (manifest) break;\n }\n }\n\n // The `.bin` shim is written beside whichever package declared the\n // dependency, so the driver's own directory is a search root too.\n const onPath = findUpwards(\n [...roots, ...driverManifests.map(file => path.dirname(file))],\n path.join(\".bin\", \"atlas\")\n ) !== null;\n\n let binaryOnDisk = false;\n if (manifest) {\n try {\n const pkg = JSON.parse(fs.readFileSync(manifest, \"utf-8\")) as {\n bin?: string | Record<string, string>;\n };\n const dir = path.dirname(manifest);\n const targets = typeof pkg.bin === \"string\"\n ? [pkg.bin]\n : Object.values(pkg.bin ?? {}).filter((t): t is string => typeof t === \"string\");\n binaryOnDisk = targets.some(target => fs.existsSync(path.resolve(dir, target)));\n } catch { /* unreadable manifest: treat the binary as absent */ }\n }\n\n return {\n onPath,\n packageInstalled: manifest !== null,\n binaryOnDisk,\n manager: readPackageManagerName(projectRoot),\n workspaceDir: fs.existsSync(path.join(projectRoot, \"backend\", \"package.json\")) && isWorkspaceRoot(projectRoot)\n ? \"backend\"\n : null\n };\n}\n\n/** Whether `dir` is a workspace root, where `pnpm add` refuses without `-w`. */\nfunction isWorkspaceRoot(dir: string): boolean {\n if (fs.existsSync(path.join(dir, \"pnpm-workspace.yaml\"))) return true;\n try {\n const manifest: unknown = JSON.parse(fs.readFileSync(path.join(dir, \"package.json\"), \"utf8\"));\n return typeof manifest === \"object\" && manifest !== null && \"workspaces\" in manifest;\n } catch {\n return false;\n }\n}\n\n/** The reader's own package manager, from the lockfile beside their project. */\nfunction readPackageManagerName(projectRoot: string): string {\n for (const [file, manager] of Object.entries(LOCKFILES)) {\n if (fs.existsSync(path.join(projectRoot, file))) return manager;\n }\n return \"pnpm\";\n}\n\n/** The `engines.node` range of the CLI actually running. */\nfunction readCliEngines(): string | undefined {\n try {\n const here = path.dirname(fileURLToPath(import.meta.url));\n for (let dir = here, i = 0; i < 5; i++) {\n const candidate = path.join(dir, \"package.json\");\n if (fs.existsSync(candidate)) {\n const parsed = readJson(candidate);\n if (parsed?.name === \"@rebasepro/cli\") {\n const engines = parsed.engines as { node?: string } | undefined;\n return engines?.node;\n }\n }\n const parent = path.dirname(dir);\n if (parent === dir) break;\n dir = parent;\n }\n } catch { /* the check is advisory */ }\n return undefined;\n}\n\nfunction readJson(file: string): Record<string, unknown> | undefined {\n try {\n return JSON.parse(fs.readFileSync(file, \"utf-8\")) as Record<string, unknown>;\n } catch {\n return undefined;\n }\n}\n\n/** Where a project's package.json files live, relative to its root. */\nconst PACKAGE_JSON_LOCATIONS = [\"package.json\", \"backend/package.json\", \"frontend/package.json\", \"config/package.json\"];\n\nfunction readDeclaredRebaseDeps(projectRoot: string): DeclaredDependency[] {\n const declared: DeclaredDependency[] = [];\n for (const relative of PACKAGE_JSON_LOCATIONS) {\n const parsed = readJson(path.join(projectRoot, relative));\n if (!parsed) continue;\n for (const field of [\"dependencies\", \"devDependencies\", \"peerDependencies\"] as const) {\n const block = parsed[field] as Record<string, string> | undefined;\n if (!block) continue;\n for (const [name, range] of Object.entries(block)) {\n if (typeof range === \"string\") declared.push({ file: relative, name, range });\n }\n }\n }\n return declared;\n}\n\n/**\n * Does this project declare its collections in code?\n *\n * `resolveBackendPaths` answers it against the manifest's own `config` path\n * rather than the convention, because a project may move the directory. A\n * project with no manifest, or one shaped differently, is treated as declaring\n * them: the wrong answer there would silently skip the drift report, which is\n * the reason the command exists.\n */\nexport function hasDeclaredCollections(projectRoot: string): boolean {\n try {\n const backend = findBackendApp(loadManifest(projectRoot).manifest);\n if (!backend) return true;\n return resolveBackendPaths(backend.app, projectRoot).hasCollections;\n } catch {\n return true;\n }\n}\n\n/**\n * Every `slug:` a collection file declares, read as text.\n *\n * Deliberately not by importing them: a collection file may import anything the\n * project depends on, and doctor must not run a project's code to tell it two\n * slugs collide. A regex over `slug: \"...\"` finds the literal in every shape\n * the scaffold and the docs use; a computed slug is simply not seen, which is\n * the right failure for a check that only ever reports.\n */\nfunction readDeclaredSlugs(projectRoot: string): Array<{ slug: string }> {\n const dir = path.join(projectRoot, \"config\", \"collections\");\n if (!fs.existsSync(dir)) return [];\n const slugs: Array<{ slug: string }> = [];\n let entries: string[];\n try {\n entries = fs.readdirSync(dir);\n } catch {\n return [];\n }\n for (const entry of entries) {\n if (!/\\.(ts|js|mts|mjs)$/.test(entry) || entry.startsWith(\"index.\")) continue;\n try {\n const text = fs.readFileSync(path.join(dir, entry), \"utf-8\");\n for (const match of text.matchAll(/\\bslug:\\s*[\"'`]([^\"'`]+)[\"'`]/g)) {\n slugs.push({ slug: match[1] });\n }\n } catch { /* unreadable is not a finding */ }\n }\n return slugs;\n}\n\n/** Print what the environment checks found, grouped, or nothing at all. */\nfunction reportEnvironmentFindings(findings: EnvironmentFinding[]): boolean {\n if (findings.length === 0) return false;\n\n console.log(\"\");\n console.log(chalk.bold(\" Environment\"));\n for (const finding of findings) {\n const mark = finding.severity === \"error\" ? chalk.red(\" ✗\") : chalk.yellow(\" ⚠\");\n console.log(`${mark} ${finding.message}`);\n console.log(chalk.gray(` ${finding.fix}`));\n }\n console.log(\"\");\n return findings.some(finding => finding.severity === \"error\");\n}\n\n/**\n * The flags `rebase doctor` takes, and what each is for.\n *\n * One table, read by both the parser and the help page, because they were two\n * things that had to agree and did not: the page listed no flags at all, so\n * `--policies` — the form `cli/index.md` calls \"the form to use as a CI gate\" —\n * was invisible to anyone who ran `rebase doctor --help` to find it. And with\n * nothing parsing the line here, a typo travelled to the driver's permissive\n * parser and was silently dropped, so `rebase doctor --polices` ran the ordinary\n * doctor and reported success on a gate that never ran.\n *\n * The spec mirrors the driver's own (`doctorPluginCommand` in\n * `server-postgres/src/cli.ts`), because the line is relayed to it verbatim.\n */\nexport const DOCTOR_FLAGS = {\n \"--policies\": Boolean,\n \"--collections\": String,\n \"-c\": \"--collections\",\n \"--schema\": String,\n \"-s\": \"--schema\",\n \"--sdk\": String,\n \"-k\": \"--sdk\"\n} as const;\n\n/** What each long flag above does, for the Options block. */\nconst DOCTOR_FLAG_HELP: Record<string, string> = {\n \"--policies\": \"Also audit the row-level security policies. The form to use as a CI gate.\",\n \"--collections\": COLLECTIONS_FLAG_HELP,\n \"--schema\": \"Path to the generated Drizzle schema (default: backend/src/schema.generated.ts)\",\n \"--sdk\": \"Path to the generated SDK types (default: generated/sdk/database.types.ts)\"\n};\n\n/**\n * The line handed to the driver, every path on it resolved from where the\n * command was run — the driver runs in `backend/`. See `utils/path-flags.ts`.\n */\nexport function doctorDriverArgs(rawArgs: string[], cwd: string): string[] {\n return absolutizeLocalPathArgs(argsFromCommand(rawArgs, \"doctor\"), cwd, [\"--collections\", \"-c\", \"--schema\", \"-s\", \"--sdk\", \"-k\"]);\n}\n\n/** `--collections, -c` — the aliases, resolved from the spec rather than retyped. */\nfunction doctorFlagLabel(flag: string): string {\n const aliases = Object.entries(DOCTOR_FLAGS)\n .filter(([, target]) => target === flag)\n .map(([alias]) => alias);\n return [flag, ...aliases].join(\", \");\n}\n\nfunction printDoctorHelp(): void {\n console.log(`\n${chalk.bold(\"rebase doctor\")} — Check a project for drift and misconfiguration\n\n${chalk.green.bold(\"Usage\")}\n rebase doctor [options]\n\n${chalk.green.bold(\"Options\")}\n${Object.keys(DOCTOR_FLAG_HELP)\n .map(flag => ` ${chalk.blue(doctorFlagLabel(flag).padEnd(22))} ${DOCTOR_FLAG_HELP[flag]}`)\n .join(\"\\n\")}\n\nCompares the collections you declare, the generated Drizzle schema, and the\ntables that actually exist, then reports what disagrees and how to reconcile it.\n\nAlso checks the connection strings in .env and the compose files for the\nunencoded \"=\" that makes PostgreSQL's own tools refuse to parse them — which\nbreaks backups and psql while leaving the app itself working.\n\nRun from inside a Rebase project — it reads the project's collections and\nconnects to its database.\n`);\n}\n\nexport async function doctorCommand(rawArgs: string[]): Promise<void> {\n if (wantsHelp(rawArgs)) {\n printDoctorHelp();\n return;\n }\n\n // Strictly, before the line is relayed. The driver's own parser is\n // `permissive`, which turns an undeclared flag into a positional rather\n // than an error, so `rebase doctor --polices` ran the ordinary doctor and\n // exited 0 — a CI gate that reported success because the flag asking for it\n // was dropped on the way.\n parseCommandArgs({\n spec: DOCTOR_FLAGS,\n rawArgs,\n commandWords: 1,\n command: \"doctor\",\n maxPositionals: 0\n });\n\n const projectRoot = requireProjectRoot();\n const backendDir = requireBackendDir(projectRoot);\n refuseMissingCollectionsPath(rawArgs, process.cwd(), backendDir, \"doctor\");\n\n const activePlugin = getActiveBackendPlugin(backendDir);\n if (!activePlugin) {\n console.error(chalk.red(\"✗ Could not detect an active database plugin.\"));\n console.error(chalk.gray(\" Make sure a package like @rebasepro/server-postgres is installed in backend/package.json.\"));\n process.exit(1);\n }\n\n const pluginCli = resolvePluginCliScript(backendDir, activePlugin);\n if (!pluginCli) {\n exitDependenciesNotInstalled(projectRoot);\n }\n\n // Set up environment with DOTENV_CONFIG_PATH\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 env[DEV_DATABASE_KIND_ENV] = devDatabaseKind(projectRoot) ?? \"\";\n\n // Reported before the plugin runs: this one needs no database, and if the\n // URL is the problem then anything that tries to connect with it first will\n // fail with a worse message.\n const connectionFailed = reportLibpqUrlProblems(findLibpqUrlProblems(projectRoot, envFile));\n\n // Same reasoning: no database needed, and one of these findings — a\n // `process.env` read at module scope — is a live failure whose only symptom\n // today is a function quietly missing from `GET /api/functions`.\n reportFunctionPortability(projectRoot);\n\n // Everything that breaks a first run happens before a table is compared:\n // the wrong Node, two lockfiles disagreeing, two collections claiming one\n // slug, a JWT_SECRET short enough to be refused in production, three\n // versions of @rebasepro/server in one workspace. Doctor said nothing about\n // any of it. Reported before the plugin runs, and non-blocking — a project\n // with one of these still deserves its drift report.\n const environmentFailed = reportEnvironmentFindings(collectEnvironmentFindings(projectRoot, envFile));\n // Either is an error a CI gate must see: a connection string libpq cannot\n // parse is a project whose backups have never run.\n const failed = connectionFailed || environmentFailed;\n\n // Which database, resolved the way every other command resolves it.\n //\n // Two of doctor's three phases need one, and both used to key on\n // `DATABASE_URL` alone. On the documented first-run path that variable does\n // not exist — `rebase init` leaves it commented out and the managed\n // database fills the vacuum — so a stock scaffold always ended with\n // `⏭ Collections → Database: skipped (DATABASE_URL not set)` and\n // `⚠ No DATABASE_URL — RLS policies were NOT checked`, then told the reader\n // to set the one variable the quickstart tells them to leave alone. For the\n // command `ai-instructions.md` names as the thing to run before guessing.\n //\n // The managed daemon is started here, exactly as `rebase db url` starts it:\n // a doctor that will not start the database it is asked about can only ever\n // answer \"I did not look\".\n //\n // Last, and never fatal. Everything above needs no database, and a doctor\n // that cannot report what it *can* see because a database would not start\n // is the opposite of the job. A failure here is said out loud and the run\n // continues; the driver then reports its two database phases as skipped,\n // which is the truth.\n try {\n const prepared = await prepareDatabaseEnv(projectRoot, {\n onProgress: (message) => console.log(chalk.gray(` ${message}`))\n });\n Object.assign(env, prepared.env);\n for (const line of managedNotices(prepared)) console.log(chalk.gray(` ${line}`));\n } catch (error) {\n console.log(\"\");\n console.error(chalk.yellow(` ⚠ Could not resolve this project's database: ${\n error instanceof Error ? error.message : String(error)}`));\n console.error(chalk.gray(\" The checks that need one are reported below as not run.\"));\n }\n\n // A headless project has no `config/collections` by design — its API is\n // introspected from the live database at boot — and the driver's doctor\n // opens by loading them, finds none, and exits 1. So `rebase doctor` failed\n // on the documented headless path, in 0.17.3 and on main, while every check\n // above it had just passed. Its three phases all compare *against* declared\n // collections, so there is nothing for them to do here; the environment\n // findings are the whole report.\n if (!hasDeclaredCollections(projectRoot)) {\n console.log(\"\");\n console.log(chalk.gray(\n \" ○ Schema drift not checked: this project derives its API from the database — \"\n + \"run `rebase schema introspect` first.\"\n ));\n if (failed) process.exit(1);\n return;\n }\n\n try {\n const isTs = pluginCli.endsWith(\".ts\");\n if (isTs) {\n const tsxBin = resolveTsx(projectRoot);\n if (!tsxBin) {\n exitDependenciesNotInstalled(projectRoot);\n }\n await execa(tsxBin, [pluginCli, ...doctorDriverArgs(rawArgs, process.cwd())], {\n cwd: backendDir,\n stdio: \"inherit\",\n env\n });\n } else {\n await execa(\"node\", [pluginCli, ...doctorDriverArgs(rawArgs, process.cwd())], {\n cwd: backendDir,\n stdio: \"inherit\",\n env\n });\n }\n } catch (error) {\n // A child that ran and exited non-zero already printed its diagnostics\n // through inherited stdio. A child that never started did not — and\n // \"the tsx symlink is broken\" is exactly the state `doctor` exists to\n // report, so exiting 1 in silence was the worst possible answer.\n reportSpawnFailure(error);\n process.exit(1);\n }\n\n // The plugin exited clean, so the schema agrees with itself. That is not\n // the same as the project being able to run: an environment error is still\n // an error, and a doctor that exits 0 over one is a doctor nobody can gate\n // on.\n if (failed) process.exit(1);\n}\n"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"generate_sdk-Cvg5jjJF.js","names":[],"sources":["../src/commands/generate_sdk.ts"],"sourcesContent":["/**\n * CLI command: generate-sdk\n *\n * Reads every collection file in a directory (default: ./config/collections) —\n * the files the backend serves, not the barrel's list — generates a typed\n * TypeScript SDK, and writes it to the output directory (default: ./generated/sdk).\n *\n * Uses jiti for dynamic TypeScript import of collection files.\n */\n\nimport fs from \"fs\";\nimport path from \"path\";\nimport chalk from \"chalk\";\nimport { CollectionConfig, computeSchemaVersion, deserializeCollections } from \"@rebasepro/types\";\nimport { CodegenError, generateSDK, GeneratedFile, toCollectionAccessor } from \"@rebasepro/codegen\";\nimport { detectPackageManager, getPMCommands } from \"../utils/package-manager\";\nimport { findProjectRoot, readEnvFile } from \"../utils/project\";\nimport { findBackendApp, loadManifest, manifestExists, writeManifest } from \"../manifest\";\nimport { readLink } from \"./cloud/context\";\nimport { COLLECTIONS_FLAG_HELP, COLLECTIONS_PATH_RULE } from \"../utils/path-flags\";\nimport { isLoadedCollectionFile } from \"../utils/collection-drift\";\n\ninterface GenerateSDKArgs {\n collectionsDir: string;\n output: string;\n cwd: string;\n /**\n * Where to read the schema from instead of local source.\n *\n * `link` uses this checkout's linked project; anything else is treated as\n * the base URL of a Rebase backend. This is what lets a repository that\n * contains no collections — a separate frontend, a second web app — generate\n * a typed client from the project it talks to.\n */\n from?: string;\n /** Bearer token for the contract endpoint. Falls back to REBASE_SERVICE_KEY. */\n token?: string;\n /**\n * Also register the generated `Database` for server-side code: write the\n * types and the `Register` augmentation into `config/generated/`. See\n * {@link writeRegistration}.\n */\n register?: boolean;\n /**\n * The directory holding `rebase.json`, where `--register` is remembered.\n * Found from `cwd` when not given.\n */\n projectRoot?: string;\n help?: boolean;\n}\n\n/** What `--register` writes, and what marks a project as having opted in. */\nexport const REGISTER_FILE = \"register.ts\";\n\n/** The first line of {@link REGISTER_FILE}: a file without it is the developer's own. */\nconst REGISTER_MARKER = \"// Auto-generated by `rebase generate-sdk --register`. Do not edit.\";\n\nconst REGISTER_SOURCE = `${REGISTER_MARKER}\n//\n// Registers this project's generated \\`Database\\` on \\`@rebasepro/types\\`, so\n// \\`context.data\\` in collection callbacks, \\`rebase.dataAsAdmin\\`, and the\n// \\`rebase\\` a function or a cron receives are typed from it. Every later\n// \\`rebase generate-sdk\\` (and \\`rebase dev\\`) refreshes this directory; delete\n// it to go back to untyped server-side data.\nimport type { Database } from \"./database.types\";\n\ndeclare module \"@rebasepro/types\" {\n interface Register {\n database: Database;\n }\n}\n`;\n\n/**\n * Where `--register` writes: `generated/` beside the collections directory,\n * which is `config/generated/` in a scaffolded project.\n *\n * Inside `config/` because that is the one directory both compiles of a\n * collection file include: the backend's program (it includes\n * `../config/**`) and the `config` package's own `tsc`. A registration\n * there that imported `../generated/sdk/database.types` pulled a file from\n * outside `config/` into that package's program, which TypeScript refuses\n * (the file is not under its `rootDir`) — so the types are copied in beside\n * the registration rather than imported from the SDK.\n */\nexport function registrationDir(collectionsDir: string): string {\n return path.join(path.dirname(path.resolve(collectionsDir)), \"generated\");\n}\n\n/** Whether this project opted in: the registration is there, and it is ours. */\nfunction hasRegistration(dir: string): boolean {\n try {\n return fs.readFileSync(path.join(dir, REGISTER_FILE), \"utf-8\").startsWith(REGISTER_MARKER);\n } catch {\n return false;\n }\n}\n\n/** Write a file only when its content changed, so a watcher on the directory does not restart for nothing. */\nfunction writeIfChanged(filePath: string, content: string): void {\n try {\n if (fs.readFileSync(filePath, \"utf-8\") === content) return;\n } catch {\n // Not there yet.\n }\n fs.writeFileSync(filePath, content, \"utf-8\");\n}\n\n/**\n * Write the generated `Database` and its `Register` augmentation into\n * {@link registrationDir}. Refuses to overwrite a `register.ts` it did not\n * write.\n */\nfunction writeRegistration(dir: string, databaseTypes: string): void {\n const registerPath = path.join(dir, REGISTER_FILE);\n if (fs.existsSync(registerPath) && !hasRegistration(dir)) {\n throw new Error(\n `${registerPath} exists and was not written by \"rebase generate-sdk --register\". ` +\n \"Move it, or register the Database there yourself.\"\n );\n }\n fs.mkdirSync(dir, { recursive: true });\n writeIfChanged(path.join(dir, \"database.types.ts\"), databaseTypes);\n writeIfChanged(registerPath, REGISTER_SOURCE);\n}\n\n/**\n * Whether `rebase.json` asks for the registration (`registerDatabase: true` on\n * the backend app). That is what survives a clone: `generated/` is gitignored,\n * so a `register.ts` from an earlier run is not there to say it.\n *\n * An unreadable manifest answers no; `rebase build` is where it is reported.\n */\nfunction manifestRegisters(projectRoot: string | undefined): boolean {\n if (!projectRoot || !manifestExists(projectRoot)) return false;\n try {\n return findBackendApp(loadManifest(projectRoot).manifest)?.app.registerDatabase === true;\n } catch {\n return false;\n }\n}\n\n/**\n * Remember `--register` in `rebase.json`, so later runs, `rebase dev` and a\n * fresh clone keep registering. Returns where it was written, or why not.\n *\n * Never creates the file: a project without one is synthesized from its\n * layout, and writing one is `rebase apps init`'s decision, not this flag's.\n */\nfunction rememberRegistration(projectRoot: string | undefined): { written: string } | { skipped: string } {\n if (!projectRoot || !manifestExists(projectRoot)) {\n return { skipped: \"No rebase.json here, so the choice lasts as long as the generated files.\" };\n }\n let loaded: ReturnType<typeof loadManifest>;\n try {\n loaded = loadManifest(projectRoot);\n } catch (error) {\n return { skipped: `rebase.json could not be read (${error instanceof Error ? error.message : String(error)}).` };\n }\n const backend = findBackendApp(loaded.manifest);\n if (!backend) return { skipped: \"rebase.json declares no backend app to remember it on.\" };\n if (backend.app.registerDatabase === true) return { written: loaded.filePath ?? projectRoot };\n const manifest = {\n ...loaded.manifest,\n apps: { ...loaded.manifest.apps, [backend.name]: { ...backend.app, registerDatabase: true } }\n };\n return { written: writeManifest(projectRoot, manifest) };\n}\n\n/**\n * Load the collections the backend serves, the way it finds them.\n *\n * A directory is read as `loadCollectionsFromDirectory` in @rebasepro/server\n * reads it: every top-level collection file, each default-exporting one\n * collection. The barrel (`index.ts`) is not a registry. The runtime, the admin\n * panel, `rebase build` and `rebase schema generate` all scan the directory, and\n * the barrel only orders the panel's navigation and declares\n * `defaultSecurityRules`. This command used to read the barrel instead, so a\n * collection file left out of it was created, served at `/api/data/<slug>` and\n * listed in the panel, and missing from the typed SDK alone.\n *\n * A file that does not load is fatal, as it is at boot: `rebase dev` runs this\n * on every save, and skipping the file would overwrite good types with a\n * `Database` that is silently short a collection.\n *\n * A path to a single module is read as the runtime reads one: its\n * `backendCollections`, or its `collections`.\n */\nasync function loadCollections(collectionsDir: string): Promise<CollectionConfig[]> {\n const absDir = path.resolve(collectionsDir);\n\n if (!fs.existsSync(absDir)) {\n throw new Error(`Collections directory not found: ${absDir} (${COLLECTIONS_PATH_RULE})`);\n }\n\n let jiti: (id: string, userOptions?: Record<string, unknown>) => (modulePath: string) => Record<string, unknown>;\n try {\n const jitiModule = await import(\"jiti\");\n jiti = (jitiModule.default || jitiModule) as typeof jiti;\n } catch {\n const installCmd = [...getPMCommands(detectPackageManager()).install, \"-D\", \"jiti\"].join(\" \");\n throw new Error(\n `Could not load 'jiti'. Install it with: ${installCmd}\\n` +\n \"jiti is required to dynamically import TypeScript collection definitions.\"\n );\n }\n\n if (!fs.statSync(absDir).isDirectory()) {\n const mod = jiti(path.dirname(absDir), { interopDefault: true, esmResolve: true })(absDir);\n const listed = mod.backendCollections ?? mod.collections ?? mod.default;\n if (Array.isArray(listed)) return listed as CollectionConfig[];\n throw new Error(\n `Could not extract collections from ${absDir}.\\n` +\n \"A single collections module exports `backendCollections` or `collections`: an array of collections.\"\n );\n }\n\n const jitiInstance = jiti(absDir, {\n interopDefault: true,\n esmResolve: true\n });\n\n const collections: CollectionConfig[] = [];\n const failures: string[] = [];\n const files = fs.readdirSync(absDir).filter(isLoadedCollectionFile).sort();\n\n for (const file of files) {\n try {\n const mod = jitiInstance(path.join(absDir, file));\n const exported = mod.default ?? mod;\n if (exported && typeof exported === \"object\" && \"slug\" in exported) {\n collections.push(exported as CollectionConfig);\n } else {\n failures.push(`${file}: no default export of a collection`);\n }\n } catch (err) {\n failures.push(`${file}: ${(err as Error).message}`);\n }\n }\n\n if (failures.length > 0) {\n throw new Error(\n `Could not load ${failures.length} collection file(s) from ${absDir}:\\n` +\n failures.map(failure => ` • ${failure}`).join(\"\\n\") +\n \"\\n\\nEvery collection file must import cleanly and default-export a collection — \" +\n \"the backend refuses to boot on the same file.\"\n );\n }\n\n return collections;\n}\n\n/**\n * Write generated files to the output directory.\n */\nfunction writeFiles(outputDir: string, files: GeneratedFile[]): void {\n const absOutput = path.resolve(outputDir);\n\n // Create output directory\n fs.mkdirSync(absOutput, { recursive: true });\n\n for (const file of files) {\n const filePath = path.join(absOutput, file.path);\n const dir = path.dirname(filePath);\n if (!fs.existsSync(dir)) {\n fs.mkdirSync(dir, { recursive: true });\n }\n fs.writeFileSync(filePath, file.content, \"utf-8\");\n }\n}\n\nfunction printSdkHelp(): void {\n console.log(`\n${chalk.bold(\"rebase generate-sdk\")} — generate a typed client from a project's schema\n\n${chalk.bold(\"Usage\")}\n rebase generate-sdk [options]\n\n${chalk.bold(\"Options\")}\n -c, --collections <dir> ${COLLECTIONS_FLAG_HELP}\n (--collections-dir is accepted too)\n -o, --output <dir> Where to write the SDK (default: ./generated/sdk)\n (--out is accepted too, as on 'rebase build')\n --from <link|url> Fetch the schema from a running project instead of\n local source. \"link\" uses this checkout's linked project.\n --token <token> Bearer token for the contract endpoint\n (default: $REBASE_SERVICE_KEY)\n --register Also type server-side data: write the types and the\n Register declaration to config/generated/, and\n remember it in rebase.json so later runs keep it.\n -h, --help Show this help\n\n${chalk.bold(\"Examples\")}\n rebase generate-sdk From local collections\n rebase generate-sdk --from link From the linked project\n rebase generate-sdk --from https://api.acme.com From any Rebase backend\n rebase generate-sdk --register Type context.data and rebase.dataAsAdmin too\n`.trim());\n}\n\n/**\n * Fetch collections from a running project's contract endpoint.\n *\n * The payload replaces relation `target` functions with slug references, so it\n * has to be rehydrated before the generator sees it — the generator *calls*\n * `target()` to decide whether a foreign key is a string or a number, and a\n * missing target silently degrades that to a union rather than failing.\n */\n/** What `--from` sent to the contract endpoint, and which keys it left behind. */\ninterface ContractCredential {\n /** The bearer token sent: `--token`, the shell's key, or none. */\n sent: \"token\" | \"ambient\" | null;\n /** `REBASE_SERVICE_KEY` is in the shell environment. */\n shellKey: boolean;\n /** `REBASE_SERVICE_KEY` is in the project's `.env`. */\n envFileKey: boolean;\n}\n\n/**\n * What to say when the contract endpoint refuses, by what was and was not sent.\n *\n * \"Set REBASE_SERVICE_KEY\" is only right when no key exists. The shell's key is\n * withheld from every host but the linked project, and `.env`'s is never read\n * here — it is the local backend's key, and the linked project is usually a\n * deployed one — so telling a reader whose key is in `.env` to set it sends them\n * to fix something that is not broken.\n */\nfunction contractRefusalHint(credential: ContractCredential): string[] {\n if (credential.sent === \"token\") {\n return [\"The token passed with --token was refused: pass a service key, or an admin's token.\"];\n }\n if (credential.sent === \"ambient\") {\n return [\"REBASE_SERVICE_KEY was sent and refused: it is not this project's service key. Pass the right one with --token.\"];\n }\n if (credential.shellKey) {\n return [\n \"REBASE_SERVICE_KEY is set, but it is only sent to this checkout's linked project, and this host is not it.\",\n \"Pass the key with --token to send it here.\"\n ];\n }\n if (credential.envFileKey) {\n return [\n \"REBASE_SERVICE_KEY in .env is not sent: --from sends the shell's REBASE_SERVICE_KEY, and only to this checkout's linked project.\",\n \"Pass the key with --token.\"\n ];\n }\n return [\"Pass --token <key>, a service key or an admin's token. The shell's REBASE_SERVICE_KEY is sent only to this checkout's linked project.\"];\n}\n\nasync function fetchRemoteCollections(\n baseUrl: string,\n token: string | undefined,\n credential: ContractCredential\n): Promise<{ collections: CollectionConfig[]; schemaVersion: string }> {\n const url = `${baseUrl.replace(/\\/+$/, \"\")}/api/meta/contract`;\n\n const headers: Record<string, string> = { accept: \"application/json\" };\n if (token) headers.authorization = `Bearer ${token}`;\n\n let response: Response;\n try {\n response = await fetch(url, { headers });\n } catch (err) {\n console.log(chalk.red(` ✗ Could not reach ${url}`));\n console.log(chalk.gray(` ${err instanceof Error ? err.message : String(err)}`));\n process.exit(1);\n }\n\n if (response.status === 401 || response.status === 403) {\n console.log(chalk.red(` ✗ Not authorized to read the project contract (${response.status}).`));\n console.log(chalk.gray(\" The contract describes every table and relation, so it is admin-only.\"));\n for (const line of contractRefusalHint(credential)) console.log(chalk.gray(` ${line}`));\n process.exit(1);\n }\n\n if (response.status === 404) {\n console.log(chalk.red(\" ✗ This server has no contract endpoint.\"));\n console.log(chalk.gray(\" It needs to be running Rebase 0.11 or newer.\"));\n process.exit(1);\n }\n\n if (!response.ok) {\n console.log(chalk.red(` ✗ Contract request failed with ${response.status}.`));\n process.exit(1);\n }\n\n const contract = await response.json() as {\n collections?: unknown[];\n schemaVersion?: string;\n };\n\n if (!Array.isArray(contract.collections)) {\n console.log(chalk.red(\" ✗ The contract response did not contain collections.\"));\n process.exit(1);\n }\n\n return {\n collections: deserializeCollections(contract.collections),\n schemaVersion: contract.schemaVersion ?? \"unknown\"\n };\n}\n\n/**\n * Decide whether the ambient service key may be sent to this host.\n *\n * `REBASE_SERVICE_KEY` grants full admin bypass. Attaching it to whatever URL\n * happened to be passed — or, worse, to whatever a committed `.rebase/cloud.json`\n * points at — would hand the project's most powerful credential to a host nobody\n * vetted. An explicit `--token` is a decision the caller made; the ambient\n * variable is not, so it only travels to the project this checkout is linked to.\n */\nfunction mayUseAmbientKey(target: string, cwd: string): boolean {\n const link = readLink(findProjectRoot(cwd) ?? cwd);\n if (!link?.apiUrl) return false;\n try {\n // Origin, not host: with a link recorded as https, an `http://` target\n // for the same host would otherwise pass and send the key in cleartext.\n return new URL(link.apiUrl).origin === new URL(target).origin;\n } catch {\n return false;\n }\n}\n\n/**\n * The base URL to show in the printed usage example.\n *\n * `rebase dev` binds a port derived from the project path, not 3001, and writes\n * the one it actually got to `.rebase/state.json`. Printing a hardcoded\n * `localhost:3001` sent people to a port nothing was listening on — or, with\n * several projects on one machine, to a different project's backend. Prefer the\n * port this project last ran on; fall back to the literal only when the project\n * has never been started.\n */\nexport function resolveExampleBaseUrl(cwd: string): string {\n const projectRoot = findProjectRoot(cwd) ?? cwd;\n try {\n const state = JSON.parse(fs.readFileSync(path.join(projectRoot, \".rebase\", \"state.json\"), \"utf-8\"));\n if (typeof state.baseUrl === \"string\" && state.baseUrl) return state.baseUrl;\n if (typeof state.port === \"number\") return `http://localhost:${state.port}`;\n } catch {\n // Never started, or the file is unreadable — fall through.\n }\n return \"http://localhost:3001\";\n}\n\n/** Whether a slug can be written as `rebase.data.<slug>` rather than a lookup. */\nexport function isIdentifierLike(slug: string): boolean {\n return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(slug);\n}\n\n/** Resolve `--from` into a base URL, following the link file when asked. */\nfunction resolveSchemaSource(from: string, cwd: string): string {\n if (from !== \"link\") {\n // A bare hostname or a typo would otherwise be handed to `fetch` and fail\n // with something unhelpful; a non-http scheme has no business here at all.\n let parsed: URL;\n try {\n parsed = new URL(from);\n } catch {\n console.log(chalk.red(` ✗ \"${from}\" is not a valid URL.`));\n console.log(chalk.gray(\" Pass a full URL, e.g. https://api.example.com, or \\\"link\\\".\"));\n process.exit(1);\n }\n if (parsed.protocol !== \"http:\" && parsed.protocol !== \"https:\") {\n console.log(chalk.red(\" ✗ The project URL must be http or https.\"));\n process.exit(1);\n }\n return from;\n }\n\n const projectRoot = findProjectRoot(cwd) ?? cwd;\n const link = readLink(projectRoot);\n\n if (!link) {\n console.log(chalk.red(\" ✗ This checkout is not linked to a project.\"));\n console.log(chalk.gray(\" Run `rebase cloud link <url>`, or pass --from <url>.\"));\n process.exit(1);\n }\n\n const apiUrl = link.apiUrl;\n if (!apiUrl) {\n console.log(chalk.red(\" ✗ The project link has no API URL.\"));\n console.log(chalk.gray(\" Re-link with `rebase cloud link <url>` to record one.\"));\n process.exit(1);\n }\n\n return apiUrl;\n}\n\n/**\n * Main entry point for the generate-sdk command.\n */\nexport async function generateSdkCommand(args: GenerateSDKArgs): Promise<void> {\n const { collectionsDir, output, cwd } = args;\n\n if (args.help) {\n printSdkHelp();\n return;\n }\n\n const resolvedCollectionsDir = path.isAbsolute(collectionsDir)\n ? collectionsDir\n : path.join(cwd, collectionsDir);\n\n const resolvedOutput = path.isAbsolute(output)\n ? output\n : path.join(cwd, output);\n\n console.log(\"\");\n console.log(chalk.bold(\" 🔧 Rebase SDK Generator\"));\n console.log(\"\");\n\n let collections: CollectionConfig[];\n let remoteSchemaVersion: string | undefined;\n\n if (args.from) {\n const baseUrl = resolveSchemaSource(args.from, cwd);\n console.log(` ${chalk.gray(\"Project:\")} ${baseUrl}`);\n console.log(` ${chalk.gray(\"Output:\")} ${resolvedOutput}`);\n console.log(\"\");\n console.log(chalk.cyan(\" → Fetching the project contract...\"));\n\n const ambient = mayUseAmbientKey(baseUrl, cwd)\n ? process.env.REBASE_SERVICE_KEY\n : undefined;\n\n if (!args.token && !ambient && process.env.REBASE_SERVICE_KEY) {\n console.log(chalk.dim(\n \" (not sending REBASE_SERVICE_KEY — this host is not the linked project; pass --token to override)\"\n ));\n }\n\n const remote = await fetchRemoteCollections(baseUrl, args.token || ambient, {\n sent: args.token ? \"token\" : ambient ? \"ambient\" : null,\n shellKey: Boolean(process.env.REBASE_SERVICE_KEY),\n envFileKey: Boolean(readEnvFile(findProjectRoot(cwd) ?? cwd).REBASE_SERVICE_KEY)\n });\n collections = remote.collections;\n remoteSchemaVersion = remote.schemaVersion;\n } else {\n // A headless project has no collections directory by design: its API is\n // introspected from the live database at boot, and there is nothing here\n // to generate a client from. The scaffold ships `pnpm generate:sdk`\n // anyway, so the documented headless path had a script that could only\n // ever fail — and it failed with a path, which reads as a broken install\n // rather than a question with an answer.\n if (!fs.existsSync(resolvedCollectionsDir)) {\n console.log(chalk.gray(\n \" ○ This project derives its API from the database — \"\n + \"run `rebase schema introspect` first.\"\n ));\n console.log(chalk.gray(\n ` Nothing to generate from: ${resolvedCollectionsDir} does not exist.`\n ));\n console.log(\"\");\n return;\n }\n console.log(` ${chalk.gray(\"Collections:\")} ${resolvedCollectionsDir}`);\n console.log(` ${chalk.gray(\"Output:\")} ${resolvedOutput}`);\n console.log(\"\");\n console.log(chalk.cyan(\" → Loading collection definitions...\"));\n collections = await loadCollections(resolvedCollectionsDir);\n }\n\n // Sort collections alphabetically by slug to ensure deterministic SDK generation\n collections.sort((a, b) => a.slug.localeCompare(b.slug));\n\n if (collections.length === 0) {\n console.log(chalk.red(\" ✗ No collections found. Nothing to generate.\"));\n process.exit(1);\n }\n\n console.log(chalk.green(` ✓ Found ${collections.length} collection(s): ${collections.map(c => c.slug).join(\", \")}`));\n console.log(\"\");\n\n // 2. Generate SDK files\n console.log(chalk.cyan(\" → Generating SDK files...\"));\n let files: GeneratedFile[];\n try {\n files = generateSDK(collections, { baseUrl: resolveExampleBaseUrl(cwd) });\n } catch (err) {\n // A schema that cannot be expressed as valid TypeScript — two slugs\n // that collide on one accessor, a slug with no usable name. The\n // generator refuses rather than emitting a file that does not compile,\n // or one that silently routes a collection's reads to another's table.\n if (err instanceof CodegenError) {\n console.log(\"\");\n console.log(chalk.red(` ✗ ${err.message}`));\n process.exit(1);\n }\n throw err;\n }\n\n // Stamp the schema this SDK was built from.\n //\n // Without it, an app in a separate repository has no way to know its client\n // is stale: the backend moves on, the frontend keeps compiling against types\n // captured weeks ago, and the mismatch only surfaces as a runtime error. With\n // it, CI can compare against `/api/meta/schema-version` and say so.\n //\n // No generation timestamp. Collections are sorted above precisely so that\n // the same schema produces the same bytes; a timestamp put that back,\n // turning every regeneration into a diff and making the obvious staleness\n // gate — `rebase generate-sdk && git diff --exit-code` — impossible to pass.\n // `SCHEMA_VERSION` already identifies what this was built from, and does it\n // in a way that can be compared against a running project.\n const schemaVersion = remoteSchemaVersion ?? computeSchemaVersion(collections);\n files.push({\n path: \"schema.meta.ts\",\n content: `// Auto-generated by \\`rebase generate-sdk\\`. Do not edit.\n//\n// The schema version this SDK was generated from. Compare it against the\n// project's current version to detect drift:\n//\n// curl -s <api-url>/api/meta/schema-version\n//\n// Pass it to the client as \\`schemaVersion\\` and every request carries it in\n// the \\`x-rebase-schema\\` header, so the server can name the drift instead of\n// answering a renamed field with a bare 400.\nexport const SCHEMA_VERSION = ${JSON.stringify(schemaVersion)};\n`\n });\n\n console.log(chalk.green(` ✓ Generated ${files.length} file(s)`));\n console.log(chalk.gray(` schema ${schemaVersion}`));\n\n // 3. Write to disk\n console.log(chalk.cyan(` → Writing to ${resolvedOutput}...`));\n writeFiles(resolvedOutput, files);\n\n // 4. The server-side registration: on request, and kept current once a\n // project has it, so `rebase dev` regenerating the SDK never leaves the\n // server typed against an older schema than the client.\n const registerDir = registrationDir(resolvedCollectionsDir);\n const projectRoot = args.projectRoot ?? findProjectRoot(cwd) ?? undefined;\n if (args.register || hasRegistration(registerDir) || manifestRegisters(projectRoot)) {\n if (!fs.existsSync(path.dirname(registerDir))) {\n console.log(chalk.red(` ✗ No ${path.dirname(registerDir)} to register the Database in.`));\n console.log(chalk.gray(\" --register writes beside the collections directory; pass --collections.\"));\n process.exit(1);\n return;\n }\n const databaseTypes = files.find((file) => file.path === \"database.types.ts\")?.content;\n if (databaseTypes !== undefined) {\n try {\n writeRegistration(registerDir, databaseTypes);\n } catch (error) {\n console.log(chalk.red(` ✗ ${error instanceof Error ? error.message : String(error)}`));\n process.exit(1);\n return;\n }\n console.log(chalk.green(` ✓ Registered the Database for server-side code in ${registerDir}`));\n if (args.register) {\n const remembered = rememberRegistration(projectRoot);\n if (\"written\" in remembered) {\n console.log(chalk.gray(` Remembered in ${path.relative(cwd, remembered.written) || \"rebase.json\"} (registerDatabase).`));\n } else {\n console.log(chalk.yellow(` ${remembered.skipped}`));\n }\n }\n }\n }\n\n console.log(\"\");\n console.log(chalk.green.bold(\" ✓ SDK generated successfully!\"));\n console.log(\"\");\n const typesImport = `./${path.relative(cwd, path.join(resolvedOutput, \"database.types\"))}`;\n const metaImport = `./${path.relative(cwd, path.join(resolvedOutput, \"schema.meta\"))}`;\n const exampleSlug = collections[0]?.slug || \"my_collection\";\n\n console.log(chalk.gray(\" Usage:\"));\n console.log(chalk.gray(\" import { createRebaseClient } from '@rebasepro/client';\"));\n console.log(chalk.gray(` import { collectionsDictionary, type Database } from '${typesImport}';`));\n console.log(chalk.gray(` import { SCHEMA_VERSION } from '${metaImport}';`));\n console.log(\"\");\n console.log(chalk.gray(\" const rebase = createRebaseClient<Database>({\"));\n console.log(chalk.gray(` baseUrl: '${resolveExampleBaseUrl(cwd)}',`));\n // Sent as `x-rebase-schema` on every request. Costs one line here and is\n // the difference between a server that can say \"this client is three\n // deploys behind\" and one that can only reject a field it does not know.\n console.log(chalk.gray(\" schemaVersion: SCHEMA_VERSION,\"));\n // Without the dictionary a hyphenated slug is not resolvable from the\n // property name alone, and the request 404s at runtime.\n console.log(chalk.gray(\" collections: collectionsDictionary,\"));\n console.log(chalk.gray(\" // token: 'your-jwt-token',\"));\n console.log(chalk.gray(\" });\"));\n console.log(\"\");\n // `rebase.data.<accessor>` is the typed surface, so it is what gets printed.\n // `rebase.data.collection(slug)` exists too, but it is generic over\n // `Record<string, unknown>` and has no link to `Database` — it was the\n // headline example here for a while, which advertised the one call shape\n // that throws away the types this command just generated.\n const exampleAccessor = toCollectionAccessor(exampleSlug);\n const exampleAccess = isIdentifierLike(exampleAccessor)\n ? `rebase.data.${exampleAccessor}`\n : `rebase.data[${JSON.stringify(exampleAccessor)}]`;\n console.log(chalk.gray(` const { data } = await ${exampleAccess}.find();`));\n console.log(\"\");\n}\n"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"init-BWAwHPtz.js","names":[],"sources":["../src/utils/invoked-command.ts","../src/commands/init.ts"],"sourcesContent":["/**\n * `rebase`, spelled the way the reader started this process.\n *\n * A hint that tells someone to run the CLI again — `init`'s \"re-run it with\n * --yes\" above all — used to print a bare `rebase …`. The quickstart's first\n * command is `pnpm dlx @rebasepro/cli init my-app`, and a reader who typed that\n * has no `rebase` on PATH: the one command the refusal offered was \"command not\n * found\". So the hint repeats the runner the reader used, at this CLI's exact\n * version, which is the version that printed it.\n *\n * Read from what the runners leave in the environment, never from guesses:\n *\n * - **`pnpm dlx`** sets `npm_config_user_agent` to `pnpm/…` and puts its cache\n * entry's `node_modules/.bin` on PATH, under `<cache>/dlx/<key>/<dir>/`. It\n * does not set `npm_command`.\n * - **`npx`** (`npm exec`) sets `npm_config_user_agent` to `npm/…`,\n * `npm_command` to `exec`, and puts `~/.npm/_npx/<key>/node_modules/.bin` on\n * PATH.\n *\n * Anything else — a global install, `pnpm exec rebase` in a project, a shell\n * alias — is answered with `rebase`, which is what those readers type.\n */\nimport path from \"path\";\n\nimport { cliVersion } from \"./version\";\n\n/** A PATH entry that is a `pnpm dlx` cache entry's bin directory. */\nconst PNPM_DLX_BIN = /[\\\\/]dlx[\\\\/][^\\\\/]+[\\\\/][^\\\\/]+[\\\\/]node_modules[\\\\/]\\.bin[\\\\/]?$/;\n\n/** A PATH entry that is an `npx` cache entry's bin directory. */\nconst NPX_BIN = /[\\\\/]_npx[\\\\/][^\\\\/]+[\\\\/]node_modules[\\\\/]\\.bin[\\\\/]?$/;\n\n/**\n * The words to print in place of `rebase`: `[\"pnpm\", \"dlx\", \"@rebasepro/cli@0.25.0\"]`,\n * `[\"npx\", \"-y\", \"@rebasepro/cli@0.25.0\"]` or `[\"rebase\"]`.\n *\n * `env` and `version` are parameters for the tests; a caller passes neither.\n */\nexport function invokedRebaseCommand(\n env: Readonly<Record<string, string | undefined>> = process.env,\n version: string = cliVersion()\n): string[] {\n const agent = env.npm_config_user_agent ?? \"\";\n const binDirs = (env.PATH ?? \"\").split(path.delimiter);\n // The version that printed the hint, so re-running it runs the same CLI. A\n // version this cannot read names the package alone, which is still a\n // command that works.\n const spec = /^\\d+\\.\\d+\\.\\d+/.test(version) ? `@rebasepro/cli@${version}` : \"@rebasepro/cli\";\n\n if (agent.startsWith(\"pnpm/\") && binDirs.some(dir => PNPM_DLX_BIN.test(dir))) {\n return [\"pnpm\", \"dlx\", spec];\n }\n if (agent.startsWith(\"npm/\") && env.npm_command === \"exec\" && binDirs.some(dir => NPX_BIN.test(dir))) {\n return [\"npx\", \"-y\", spec];\n }\n return [\"rebase\"];\n}\n","import inquirer from \"inquirer\";\nimport chalk from \"chalk\";\nimport path from \"path\";\nimport fs from \"fs\";\nimport net from \"net\";\nimport { promisify } from \"util\";\nimport { execa } from \"execa\";\nimport { cp } from \"fs/promises\";\nimport { fileURLToPath } from \"url\";\nimport crypto from \"crypto\";\nimport { detectPackageManager, getPMCommands } from \"../utils/package-manager\";\nimport { cliVersion } from \"../utils/version\";\nimport { invokedRebaseCommand } from \"../utils/invoked-command\";\nimport { dockerClaimedPorts } from \"../utils/dev-preflight\";\nimport type arg from \"arg\";\nimport { parseCommandArgs, wantsHelp } from \"../utils/args\";\nimport { resolveCloudUrl, writeLink } from \"./cloud/context\";\nimport type { PackageManager, PMCommands } from \"../utils/package-manager\";\nimport { promptForConsent } from \"../telemetry/consent\";\nimport { durationBucket, recordEvent } from \"../telemetry\";\nimport { parseEnvBoolean } from \"@rebasepro/types\";\nimport { AGENT_KEYS, AGENTS, type AgentKey, resolveAgentNames } from \"./skills\";\nimport { configureAgents, printAgentSetup, promptForAgents, retargetScaffoldMcpConfig } from \"./agent-setup\";\n\nconst access = promisify(fs.access);\n\n\n// Resolve template path relative to this file\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = path.dirname(__filename);\n\nfunction findParentDir(currentDir: string, targetName: string): string | null {\n const root = path.parse(currentDir).root;\n while (currentDir && currentDir !== root) {\n if (path.basename(currentDir) === targetName) {\n return currentDir;\n }\n currentDir = path.dirname(currentDir);\n }\n return null;\n}\n\nconst cliRoot = findParentDir(__dirname, \"cli\");\n\nconst PROJECT_NAME_RE = /^[a-z0-9][a-z0-9._-]*$/;\n\n/**\n * Every scaffolded file that carries a `{{PLACEHOLDER}}`.\n *\n * Exported because it is the single source of truth: `init.test.ts` reads it and\n * asserts that every template file containing `{{` appears here. It used to be a\n * local, with the test harness keeping a *second* copy — so the two drifted, and\n * a test built on the copy could not observe the production list at all.\n *\n * The drift shipped: `docker-compose.yml` arrived with the self-host work\n * (26fd5259c) and was added to neither, so every scaffolded project got a literal\n * `name: {{PROJECT_NAME}}`. In YAML `{{...}}` is a map, not a string, so the\n * documented `docker compose up` path failed on the file before doing anything:\n *\n * yaml: unmarshal errors: line 28: cannot unmarshal !!map into string\n */\nexport const TEMPLATE_PLACEHOLDER_FILES = [\n \"package.json\",\n \"frontend/package.json\",\n \"backend/package.json\",\n \"config/package.json\",\n \"frontend/index.html\",\n \"pnpm-workspace.yaml\",\n \"docker-compose.yml\",\n \"README.md\"\n];\n\n/** Returns an error message, or null when the name is a valid package name. */\nexport function validateProjectName(name: string): string | null {\n if (!name.trim()) return \"Project name is required\";\n if (!PROJECT_NAME_RE.test(name)) {\n return \"Project name must start with a lowercase letter or number and contain only lowercase letters, numbers, hyphens, dots, or underscores\";\n }\n return null;\n}\n\nexport type TemplatePreset = \"blog\" | \"ecommerce\" | \"blank\";\n\n/**\n * Whether to scaffold the admin panel alongside the backend.\n *\n * A boolean, not a named pair. It was `--flavor cms|baas`, and neither word\n * survived what they described: \"cms\" is a product category rather than a thing\n * this tool builds, and \"baas\" was the same value that used to appear as\n * `backend.mode` — which is now derived from whether collections are declared.\n * What is left is one question: does this project get an admin UI?\n */\nconst HEADLESS_CHOICES: Array<{ name: string; value: boolean; short: string }> = [\n { name: \"Backend + admin — API plus an admin UI, driven by collections you define (like Payload/Directus)\",\nvalue: false,\nshort: \"Backend + admin\" },\n { name: \"Backend only — headless API over your database. No collections, no UI (like Supabase)\",\nvalue: true,\nshort: \"Backend only\" }\n];\n\nconst PRESET_CHOICES: Array<{ name: string; value: TemplatePreset; short: string }> = [\n { name: \"Blog — Posts, Authors, Tags (with markdown editor)\",\nvalue: \"blog\",\nshort: \"Blog\" },\n { name: \"E-commerce — Products, Categories, Orders\",\nvalue: \"ecommerce\",\nshort: \"E-commerce\" },\n { name: \"Blank — Empty project, just authentication\",\nvalue: \"blank\",\nshort: \"Blank\" }\n];\n\nexport interface InitOptions {\n projectName: string;\n git: boolean;\n installDeps: boolean;\n targetDirectory: string;\n templateDirectory: string;\n databaseUrl?: string;\n introspect?: boolean;\n /** Starter template preset. */\n preset: TemplatePreset;\n /** Whether `preset` came from an explicit --template rather than the default. */\n explicitPreset?: boolean;\n /** Scaffold the backend alone, with no admin panel and no collections. */\n headless: boolean;\n /** Detected package manager (pnpm or npm). */\n pm: PackageManager;\n /** Command helpers for the detected PM. */\n pmCommands: PMCommands;\n /** Cloud project slug (its subdomain) to link the scaffold to. */\n cloudProject?: string;\n /** One-time setup key that authenticates the cloud link. */\n setupKey?: string;\n /** Control-plane URL the setup key is redeemed against. */\n cloudUrl?: string;\n /** AI coding agents to install skills and the MCP server for. */\n agents: AgentKey[];\n /** Whoever reads the closing steps can answer a prompt (a TTY, not --yes). */\n interactive?: boolean;\n}\n\nexport interface BuildQuestionsParams {\n nameArg?: string;\n templateArg?: TemplatePreset;\n headlessArg?: boolean;\n hasGitFlag: boolean;\n hasInstallFlag: boolean;\n pm: PackageManager;\n}\n\n/**\n * Builds the interactive prompt questions for `rebase init`.\n * Exported for testability — all prompt `type` values must match\n * types registered by the installed version of inquirer.\n */\n/**\n * The questions this command asks, as inquirer types them.\n *\n * Declared, rather than left as `Record<string, unknown>` and asserted into\n * `Parameters<typeof inquirer.prompt>[0]` at the call. Inquirer infers the\n * answer shape from the array it is handed, so an array of open records matches\n * none of its overloads — which is the refusal that assertion was there to get\n * past, at the cost of every question in this file being unchecked: a\n * misspelled `mesage`, a `choices` on a `confirm`, a `default` of the wrong\n * type for its question kind all compiled.\n */\nexport type InitQuestion =\n | { type: \"input\"; name: string; message: string; default: string; validate?: (input: string) => string | true }\n | { type: \"confirm\"; name: string; message: string; default: boolean; when?: (answers: Record<string, unknown>) => boolean }\n | { type: \"select\"; name: string; message: string; choices: ReadonlyArray<{ name: string; value: unknown; short: string }>; default: unknown; when?: (answers: Record<string, unknown>) => boolean };\n\n/**\n * Why `input` cannot be this project's `DATABASE_URL`, or null when it can.\n *\n * The runtime refuses anything that is not a URL, and only a Postgres one can\n * work, so `init` refuses the same — before the scaffold is written around it.\n * The value itself is never repeated back: it may hold a password.\n */\nexport function databaseUrlProblem(input: string): string | null {\n const expected = \"(expected postgresql://user:password@host:5432/database)\";\n let parsed: URL;\n try {\n parsed = new URL(input.trim());\n } catch {\n return `not a PostgreSQL connection string ${expected}`;\n }\n if (parsed.protocol !== \"postgresql:\" && parsed.protocol !== \"postgres:\") {\n return `not a PostgreSQL connection string: it starts with ${parsed.protocol}// ${expected}`;\n }\n return null;\n}\n\n/** One line saying `dir` already holds files, or null when it may be scaffolded into. */\nfunction scaffoldConflictLine(dir: string): string | null {\n const conflicts = scaffoldDirectoryConflicts(dir);\n if (conflicts.length === 0) return null;\n const shown = conflicts.slice(0, 4).join(\", \") + (conflicts.length > 4 ? `, and ${conflicts.length - 4} more` : \"\");\n return `\"${path.basename(dir)}\" already holds files a new project would overwrite or mix with (${shown}). Choose another name.`;\n}\n\nexport function buildInitQuestions(params: BuildQuestionsParams): InitQuestion[] {\n const { nameArg, templateArg, headlessArg, hasGitFlag, hasInstallFlag, pm } = params;\n const questions: InitQuestion[] = [];\n\n if (!nameArg) {\n questions.push({\n type: \"input\",\n name: \"projectName\",\n message: \"Project name:\",\n default: \"my-app\",\n // The directory is checked here, while the name can still be\n // changed, rather than after every other question is answered.\n validate: (input: string) => validateProjectName(input)\n ?? scaffoldConflictLine(path.resolve(process.cwd(), input))\n ?? true\n });\n }\n\n if (headlessArg === undefined) {\n questions.push({\n type: \"select\",\n name: \"headless\",\n message: \"What do you want to build?\",\n choices: HEADLESS_CHOICES,\n default: false\n });\n }\n\n if (!templateArg) {\n questions.push({\n type: \"select\",\n name: \"preset\",\n message: \"Choose a starter template:\",\n choices: PRESET_CHOICES,\n default: \"blog\",\n // A headless project has no collection files, so a preset is moot.\n when: (answers: Record<string, unknown>) => !(headlessArg ?? answers.headless)\n });\n }\n\n if (!hasGitFlag) {\n questions.push({\n type: \"confirm\",\n name: \"git\",\n message: \"Initialize a git repository?\",\n default: true\n });\n }\n\n if (!hasInstallFlag) {\n questions.push({\n type: \"confirm\",\n name: \"installDeps\",\n message: `Install dependencies with ${pm}?`,\n default: true\n });\n }\n\n questions.push({\n type: \"input\",\n name: \"databaseUrl\",\n message: \"PostgreSQL connection string (blank = a managed database for this project, no setup):\",\n default: \"\",\n validate: (input: string) => {\n if (!input.trim()) return true;\n if (/[\\r\\n]/.test(input)) {\n return \"Database URL cannot contain newline characters.\";\n }\n // `n` here was an answer to the yes/no question the reader thought\n // this was, and it became DATABASE_URL=n.\n const problem = databaseUrlProblem(input);\n return problem ? `That is ${problem}. Leave it blank for the managed database.` : true;\n }\n });\n\n questions.push({\n type: \"confirm\",\n name: \"introspect\",\n message: \"Would you like to introspect this database to automatically generate collections?\",\n default: true,\n when: (answers: Record<string, unknown>) => !!(answers.databaseUrl as string)?.trim()\n });\n\n return questions;\n}\n\n/**\n * The `cd` a user must type to enter the new project.\n *\n * Not the project's basename: `init apps/my-app` has to say `cd apps/my-app`,\n * and `init .` returns \"\" because they are already in the project.\n */\nexport function formatCdTarget(cwd: string, targetDirectory: string): string {\n return path.relative(cwd, targetDirectory);\n}\n\n/** Help for `rebase init` — the flags were previously only discoverable by\n * triggering the non-TTY error. */\nexport function printInitHelp(): void {\n console.log(`\n${chalk.bold(\"rebase init\")} — Create a new Rebase project\n\n${chalk.bold(\"Usage\")}\n rebase init ${chalk.blue(\"[name]\")} [options]\n\n ${chalk.gray(\"The name may be a nested path (apps/my-app) or \\\".\\\" for the current directory.\")}\n ${chalk.gray(\"Asks when omitted; defaults to \\\"my-app\\\", and takes it without asking under --yes.\")}\n\n${chalk.bold(\"Options\")}\n ${chalk.blue(\"-t, --template\")} ${chalk.gray(\"<preset>\")} blog | ecommerce | blank ${chalk.gray(\"(default: blog)\")}\n ${chalk.blue(\"--headless\")} Backend only — no admin panel, no collections\n ${chalk.blue(\"-y, --yes\")} Never prompt ${chalk.gray(\"(required for CI / non-TTY)\")}: take every default,\n ${chalk.gray(\"except that git init and the install are skipped unless\")}\n ${chalk.gray(\"--git / --install is passed\")}\n ${chalk.blue(\"-i, --install\")} Install dependencies after scaffolding.\n ${chalk.gray(\"Without it, init asks (default yes); under --yes, no install\")}\n ${chalk.blue(\"--no-install\")} Install nothing, and do not ask\n ${chalk.blue(\"-g, --git\")} Initialize a git repository and make an initial commit.\n ${chalk.gray(\"Without it, init asks (default yes); under --yes, no repository\")}\n ${chalk.blue(\"--database-url\")} ${chalk.gray(\"<url>\")} Use an existing database instead of the generated one\n ${chalk.blue(\"--introspect\")} Generate collections from that database ${chalk.gray(\"(implies --template blank; needs --install)\")}\n ${chalk.blue(\"--project\")} ${chalk.gray(\"<slug>\")} Link the scaffold to a Rebase Cloud project\n ${chalk.blue(\"--setup-key\")} ${chalk.gray(\"<key>\")} One-time key authenticating the cloud link ${chalk.gray(\"(use with --project)\")}\n ${chalk.blue(\"-a, --agent\")} ${chalk.gray(\"<name>\")} Set up an AI coding agent: Rebase skills + the MCP server.\n ${chalk.gray(`Repeatable or comma-separated: ${AGENT_KEYS.join(\" | \")} | all`)}\n ${chalk.gray(\"Asks when omitted, pre-ticking the agents installed on this\")}\n ${chalk.gray(\"machine; under --yes, none unless named.\")}\n\n${chalk.bold(\"What gets scaffolded\")}\n ${chalk.gray(\"default\")} Backend + an admin UI, driven by collections you define ${chalk.gray(\"(like Payload/Directus)\")}\n ${chalk.blue(\"--headless\")} Backend only, over your existing database ${chalk.gray(\"(like Supabase)\")}\n ${chalk.gray(\"--template has no effect: there are no collections to seed.\")}\n\n${chalk.bold(\"Examples\")}\n ${chalk.gray(\"$\")} rebase init my-shop --template ecommerce --install\n ${chalk.gray(\"$\")} rebase init my-api --headless --yes\n ${chalk.gray(\"$\")} rebase init . --yes --git\n ${chalk.gray(\"$\")} rebase init my-app --yes --agent claude,cursor\n`);\n}\n\nexport async function createRebaseApp(rawArgs: string[]) {\n if (wantsHelp(rawArgs)) {\n printInitHelp();\n return;\n }\n\n console.log(`\n${chalk.bold(\"Rebase\")} — Create a new project 🚀\n`);\n\n const pm = detectPackageManager();\n const options = await promptForOptions(rawArgs, pm);\n await createProject(options);\n}\n\n/** The flags `rebase init` takes. */\nexport const INIT_FLAGS = {\n \"--git\": Boolean,\n \"--install\": Boolean,\n // The default, spelled out. `arg` has no negation, so without this entry a\n // CI script that writes what reads naturally — `rebase init app --yes\n // --no-install` — died on \"Unknown or unexpected option\", and both the\n // README and the Quickstart documented a flag the parser rejected. When\n // both are passed, the explicit \"no\" wins.\n \"--no-install\": Boolean,\n \"--database-url\": String,\n \"--introspect\": Boolean,\n \"--template\": String,\n \"--headless\": Boolean,\n \"--project\": String,\n \"--setup-key\": String,\n \"--agent\": [String],\n \"--yes\": Boolean,\n \"-a\": \"--agent\",\n \"-g\": \"--git\",\n \"-i\": \"--install\",\n \"-t\": \"--template\",\n \"-y\": \"--yes\"\n} satisfies arg.Spec;\n\nasync function promptForOptions(rawArgs: string[], pm: PackageManager): Promise<InitOptions> {\n const { flags: args, positionals } = parseCommandArgs({\n spec: INIT_FLAGS,\n rawArgs,\n commandWords: 1,\n command: \"init\",\n maxPositionals: 1\n });\n\n // The first positional arg after \"init\" is the project name. Under the old\n // permissive parse a mistyped flag took that slot — `rebase init --headles\n // shop` scaffolded a directory called `--headles` — and the misspelling that\n // caused it was accepted in silence.\n const nameArg = positionals[0];\n const isNonInteractive = args[\"--yes\"] || false;\n\n // The interactive prompt validates typed names; a name passed as an\n // argument must pass the same check or it becomes an invalid package.json\n // \"name\" that only fails later, at install time. Validate the basename so\n // nested paths (\"apps/my-app\") and \".\" still work.\n if (nameArg) {\n const resolvedName = path.basename(path.resolve(process.cwd(), nameArg));\n const nameError = validateProjectName(resolvedName);\n if (nameError) {\n console.error(chalk.red(`Invalid project name \"${resolvedName}\": ${nameError}`));\n process.exit(1);\n }\n // Before any question: the answer cannot change, so asking first only\n // wastes every answer the reader gives.\n const targetDirectory = path.resolve(process.cwd(), nameArg);\n const conflicts = scaffoldDirectoryConflicts(targetDirectory);\n if (conflicts.length > 0) {\n for (const line of scaffoldConflictMessage(resolvedName, formatCdTarget(process.cwd(), targetDirectory), conflicts)) {\n console.error(line);\n }\n process.exit(1);\n }\n }\n\n const databaseUrlArg = args[\"--database-url\"]?.trim();\n if (databaseUrlArg) {\n const problem = databaseUrlProblem(databaseUrlArg);\n if (problem) {\n console.error(chalk.red(`✗ --database-url is ${problem}.`));\n console.error(chalk.gray(\" Leave the flag out to use a managed database for this project, with no setup.\"));\n process.exit(1);\n }\n }\n\n const templateArg = args[\"--template\"] as TemplatePreset | undefined;\n if (templateArg && !PRESET_CHOICES.some(p => p.value === templateArg)) {\n console.error(chalk.red(`Unknown template \"${templateArg}\". Available: ${PRESET_CHOICES.map(p => p.value).join(\", \")}`));\n process.exit(1);\n }\n\n const headlessArg = args[\"--headless\"] === true ? true : undefined;\n\n // Resolved before any question, so a misspelt agent costs nothing.\n const namedAgents = resolveAgentNames(args[\"--agent\"] ?? []);\n\n // `--no-install` is an explicit \"no\", not merely the absence of a \"yes\": it\n // suppresses the interactive question too, so `rebase init app --no-install`\n // asks one fewer thing rather than asking and ignoring the answer.\n const noInstall = args[\"--no-install\"] === true;\n const installFlag = noInstall ? false : (args[\"--install\"] ?? false);\n const hasInstallFlag = noInstall || args[\"--install\"] === true;\n\n if (isNonInteractive) {\n // Non-interactive: nothing to ask, so the prompt's default stands in.\n const projectName = nameArg || \"my-app\";\n const targetDirectory = path.resolve(process.cwd(), projectName);\n const templateDirectory = path.resolve(cliRoot!, \"templates\", \"template\");\n const pmCommands = getPMCommands(pm);\n\n return {\n projectName: path.basename(targetDirectory),\n git: args[\"--git\"] ?? false,\n installDeps: installFlag,\n targetDirectory,\n templateDirectory,\n databaseUrl: args[\"--database-url\"] || undefined,\n introspect: args[\"--introspect\"] || false,\n preset: templateArg || \"blog\",\n explicitPreset: !!templateArg,\n headless: headlessArg ?? false,\n pm,\n pmCommands,\n cloudProject: args[\"--project\"] || undefined,\n setupKey: args[\"--setup-key\"] || undefined,\n cloudUrl: resolveCloudUrl(rawArgs),\n // Nothing is guessed without a person to confirm it: the machine's\n // agents are only ever a pre-ticked default in the prompt.\n agents: namedAgents ?? [],\n interactive: false\n };\n }\n\n // A non-interactive stdin (CI, a pipe, no TTY) can't answer prompts:\n // inquirer either blocks forever waiting for input or aborts with a raw\n // ExitPromptError stack trace. Fail fast with actionable guidance instead.\n if (!process.stdin.isTTY) {\n const rerun = initRerunCommand(rawArgs, invokedRebaseCommand());\n console.error(chalk.red(\"Cannot prompt: this is a non-interactive terminal (no TTY).\"));\n console.error(chalk.yellow(\" Re-run it with --yes, which takes the default for anything not passed as a flag:\"));\n console.error(chalk.yellow(` ${rerun.command}`));\n if (rerun.redacted.length > 0) {\n console.error(chalk.gray(` ${capitalize(joinWords(rerun.redacted))} ${rerun.redacted.length === 1 ? \"is\" : \"are\"} left out above; put yours back in.`));\n }\n console.error(chalk.gray(\" Options: --template <blog|ecommerce|blank> --headless --database-url <url> --install --git --agent <name>\"));\n process.exit(1);\n }\n\n const questions = buildInitQuestions({\n nameArg,\n templateArg,\n headlessArg,\n hasGitFlag: !!args[\"--git\"],\n hasInstallFlag,\n pm\n });\n\n\n const answers = await inquirer.prompt(questions);\n const agents = namedAgents ?? await promptForAgents();\n\n const targetDirectory = path.resolve(process.cwd(), nameArg || answers.projectName);\n const projectName = path.basename(targetDirectory);\n const templateDirectory = path.resolve(cliRoot!, \"templates\", \"template\");\n const pmCommands = getPMCommands(pm);\n\n return {\n projectName,\n git: args[\"--git\"] || answers.git || false,\n installDeps: noInstall ? false : (args[\"--install\"] || answers.installDeps || false),\n targetDirectory,\n templateDirectory,\n databaseUrl: (answers.databaseUrl as string)?.trim() || undefined,\n introspect: answers.introspect || false,\n preset: templateArg || (answers.preset as TemplatePreset) || \"blog\",\n // Only a flag is \"explicit\" here: the interactive path never asks for a\n // preset once baas is chosen, so it can't produce a conflicting answer.\n explicitPreset: !!templateArg,\n headless: headlessArg ?? Boolean(answers.headless),\n pm,\n pmCommands,\n cloudProject: args[\"--project\"] || undefined,\n setupKey: args[\"--setup-key\"] || undefined,\n cloudUrl: resolveCloudUrl(rawArgs),\n agents,\n interactive: true\n };\n}\n\n/**\n * The command a reader with no terminal is told to run instead: the one they\n * typed, as they typed it, with `--yes`.\n *\n * It used to be one fixed example, `rebase init <name> --yes --template blog`,\n * whatever had been passed — so `init intro-app --headless --database-url …\n * --introspect --install` was told to scaffold a blog. Every word after `init`\n * is repeated in its place, and `invocation` is `rebase` as the reader started\n * it (see `invokedRebaseCommand`).\n *\n * Two values are never repeated, because a hint is printed to logs: the\n * password in `--database-url` (`databaseUrlProblem` keeps the same rule) and\n * the `--setup-key`. Each is replaced with a placeholder and named in\n * `redacted`, so the caller can say the copy is not runnable as printed.\n *\n * Exported for its test.\n */\nexport function initRerunCommand(\n rawArgs: readonly string[],\n invocation: readonly string[]\n): { command: string; redacted: string[] } {\n const rest = rawArgs.slice(2);\n const commandWord = rest.indexOf(\"init\");\n const redacted = new Set<string>();\n\n const hide = (flag: string, value: string): string => {\n if (flag === \"--setup-key\") {\n redacted.add(\"the setup key\");\n return \"<setup-key>\";\n }\n if (flag === \"--database-url\") {\n return value.replace(/^([a-z][a-z0-9+.-]*:\\/\\/[^:@/?#]*):([^@/?#]+)@/i, (_all, before: string) => {\n redacted.add(\"the password in --database-url\");\n return `${before}:<password>@`;\n });\n }\n return value;\n };\n\n const words: string[] = [];\n for (let i = 0; i < rest.length; i++) {\n if (i === commandWord) continue;\n const word = rest[i];\n const inline = /^(--[a-z-]+)=([\\s\\S]*)$/.exec(word);\n if (inline) {\n words.push(`${inline[1]}=${shellWord(hide(inline[1], inline[2]))}`);\n continue;\n }\n const previous = i > 0 && i - 1 !== commandWord ? rest[i - 1] : \"\";\n words.push(shellWord(previous.startsWith(\"--\") ? hide(previous, word) : word));\n }\n\n return {\n command: [...invocation, \"init\", ...words, \"--yes\"].join(\" \"),\n redacted: [...redacted]\n };\n}\n\n/** One word for a POSIX shell: bare when nothing in it is special, else single-quoted. */\nfunction shellWord(word: string): string {\n return /^[\\w@%+=:,./-]+$/.test(word) ? word : `'${word.replace(/'/g, \"'\\\\''\")}'`;\n}\n\nfunction joinWords(items: readonly string[]): string {\n return items.length <= 1 ? items.join(\"\") : `${items.slice(0, -1).join(\", \")} and ${items[items.length - 1]}`;\n}\n\nfunction capitalize(text: string): string {\n return text.charAt(0).toUpperCase() + text.slice(1);\n}\n\n/**\n * Redeem the one-time setup key from the console's setup page and write the\n * `.rebase/cloud.json` link into the scaffold, so `rebase cloud deploy` etc.\n * work in the new directory with no further flags. `--project` carries the\n * project's slug (its subdomain, as shown in console URLs); the control plane\n * also accepts a raw id for old copies of the command.\n *\n * Best-effort by design: a failed link must never fail the scaffold, so every\n * exit path other than success is a warning plus instructions to link later.\n */\nasync function linkScaffoldToCloud(options: InitOptions): Promise<void> {\n if (!options.cloudProject && !options.setupKey) return;\n\n const linkLater = `Link it later with ${chalk.bold(\"rebase cloud login\")} then ${chalk.bold(\"rebase cloud link\")}.`;\n if (!options.cloudProject || !options.setupKey) {\n console.warn(chalk.yellow(\" --project and --setup-key go together; skipping the cloud link.\"));\n console.warn(chalk.yellow(` ${linkLater}`));\n return;\n }\n\n try {\n const res = await fetch(`${options.cloudUrl}/api/functions/setup-key/validate`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ projectId: options.cloudProject,\nsetupKey: options.setupKey })\n });\n const body = (await res.json().catch(() => ({}))) as {\n error?: { message?: string };\n project?: { id?: string | number; subdomain?: string; name?: string };\n };\n if (!res.ok || body.project?.id === undefined) {\n console.warn(chalk.yellow(` Could not verify the setup key: ${body.error?.message || res.statusText}`));\n console.warn(chalk.yellow(` ${linkLater}`));\n return;\n }\n writeLink(\n {\n url: String(options.cloudUrl),\n projectId: String(body.project.id),\n slug: body.project.subdomain,\n projectName: body.project.name\n },\n options.targetDirectory\n );\n console.log(\"\");\n console.log(` ${chalk.green(\"✓\")} Linked to cloud project ${chalk.bold(body.project.subdomain ?? options.cloudProject)}`);\n } catch (e) {\n console.warn(chalk.yellow(` Could not reach the control plane: ${e instanceof Error ? e.message : String(e)}`));\n console.warn(chalk.yellow(` ${linkLater}`));\n }\n}\n\n/**\n * Make the initial commit, after everything that writes into the project has run.\n *\n * It used to happen immediately after `git init`, which is before dependency\n * installation and before introspection — so `init --git --install` ended on a\n * dirty tree whose only untracked file was `pnpm-lock.yaml`. A lockfile is\n * precisely the thing that should be in a project's first commit, and a brand\n * new scaffold whose first `git status` is dirty invites the reader to conclude\n * the lockfile is deliberately ignored and never commit it at all.\n *\n * Introspection has the same shape: it generates `config/collections` and\n * `schema.generated.ts`, which belong in the commit describing the scaffold that\n * produced them.\n *\n * `git init` stays where it was. Creating the repository early costs nothing and\n * means a failed install still leaves the user a repository to commit into.\n */\nasync function commitScaffold(targetDirectory: string): Promise<void> {\n try {\n // Leaving the tree uncommitted makes the very first `git diff`\n // useless and hides the scaffold in a wall of untracked files.\n // .gitignore is already in place, so .env is never committed.\n await execa(\"git\", [\"add\", \"-A\"], { cwd: targetDirectory });\n // A machine with no user.email configured cannot commit at all.\n // Supply an identity only in that case, so a configured user still\n // authors their own initial commit.\n let identity: Record<string, string> = {};\n try {\n await execa(\"git\", [\"config\", \"user.email\"], { cwd: targetDirectory });\n } catch {\n identity = {\n GIT_AUTHOR_NAME: \"Rebase\",\n GIT_AUTHOR_EMAIL: \"noreply@rebase.pro\",\n GIT_COMMITTER_NAME: \"Rebase\",\n GIT_COMMITTER_EMAIL: \"noreply@rebase.pro\"\n };\n }\n await execa(\"git\", [\"commit\", \"-m\", \"Initial commit from Rebase\"], {\n cwd: targetDirectory,\n env: identity\n });\n } catch {\n console.warn(chalk.yellow(\" Warning: Failed to create the initial commit\"));\n }\n}\n\n/** What the closing \"Next steps\" depend on: the project's shape, and what `init` managed to do. */\nexport interface InitNextStepsInput {\n headless: boolean;\n /** `--database-url` was given, so `.env` names the user's own database. */\n ownDatabase: boolean;\n /** Introspection was asked for. */\n introspect: boolean;\n /** Introspection ran and produced collections. */\n introspected: boolean;\n /** Dependencies were installed. */\n installed: boolean;\n /** What the reader types to enter the project; \"\" when they are in it. */\n cdTarget: string;\n pm: PMCommands;\n}\n\n/**\n * The \"Next steps\" `init` closes on, every command one that works as printed.\n *\n * Each flavour has its own steps, and the branch for one flavour must not run\n * for another. The `--database-url` branch was checked before the headless one,\n * so `init --headless --database-url` — the documented BaaS path — opened on\n * `pnpm run db:push`: a script the headless scaffold does not have, for a\n * schema it does not declare (its tables are the developer's own), with an\n * Atlas it does not install. `init-next-steps.test.ts` holds every script\n * named here to the scaffold it is printed for.\n */\nexport function initNextSteps(input: InitNextStepsInput): string[] {\n const { headless, ownDatabase, introspect, introspected, installed, cdTarget, pm } = input;\n const cmd = (parts: string[]): string => ` ${chalk.cyan(parts.join(\" \"))}`;\n const note = (text: string): string => chalk.gray(` # ${text}`);\n const runDev = cmd(pm.run(\"dev\"));\n const lines: string[] = [];\n\n if (cdTarget) lines.push(` ${chalk.cyan(\"cd\")} ${cdTarget}`);\n if (!installed) lines.push(cmd(pm.install));\n lines.push(\"\");\n\n if (introspected) {\n lines.push(note(\"Database has been introspected & collections generated!\"));\n lines.push(note(headless\n ? \"Start the API over it:\"\n : \"Start the development server (frontend + backend):\"));\n lines.push(runDev);\n } else if (introspect) {\n // Introspection was requested but did not run. Point at the steps that\n // finish the job rather than claiming collections exist.\n lines.push(note(\"Introspection did not run — finish it with:\"));\n lines.push(cmd(pm.exec(\"rebase\", [\"schema\", \"introspect\", \"--force\"])));\n lines.push(cmd(pm.exec(\"rebase\", [\"schema\", \"generate\"])));\n lines.push(\"\");\n lines.push(note(\"Then start the development server:\"));\n lines.push(runDev);\n } else if (headless) {\n // One step, then the thing that is actually this mode's job. `dev`\n // starts the database itself when none is named; a headless project has\n // no collections, so there is no schema to push and the tables are the\n // developer's to create — on their own database or the managed one.\n lines.push(note(ownDatabase\n ? \"Your database is configured in .env (DATABASE_URL).\"\n : \"A local database configuration has been generated in .env.\"));\n lines.push(note(ownDatabase\n ? \"1. Start the API over it:\"\n : \"1. Start everything — the database comes up with it:\"));\n lines.push(runDev);\n lines.push(\"\");\n lines.push(note(ownDatabase\n ? \"2. Serve your tables.\"\n : \"2. Create your tables (migrations, SQL, any tool you like).\"));\n lines.push(note(\" A table is served once row-level security is enabled on it, and a\"));\n lines.push(note(\" policy is what opens its rows:\"));\n lines.push(cmd([\"ALTER TABLE your_table ENABLE ROW LEVEL SECURITY;\"]));\n // The column side is cast, as the policies Rebase generates cast it:\n // `rebase.uid()` is text, so `user_id = rebase.uid()` on a uuid column\n // failed with \"operator does not exist: uuid = text\".\n lines.push(cmd([\"-- rebase.uid() is text: the cast makes this work for a uuid or integer column too.\"]));\n lines.push(cmd([\"CREATE POLICY your_table_owner ON your_table FOR ALL USING (user_id::text = rebase.uid());\"]));\n lines.push(note(\" The API logs any table it skips, and why.\"));\n } else if (ownDatabase) {\n lines.push(note(\"Your custom database is configured in .env.\"));\n lines.push(note(\"If the database is empty, push the Rebase schema to initialize it:\"));\n lines.push(cmd(pm.run(\"db:push\")));\n lines.push(\"\");\n lines.push(note(\"Then start the development server:\"));\n lines.push(runDev);\n } else {\n // The whole first run, in one command.\n //\n // Deliberately does NOT say \"container\". This branch is the one a new\n // project takes, and it is precisely the branch with no container in it:\n // `.env` ships DATABASE_URL commented out, so `dev` starts the managed\n // PGlite database rather than Docker. Saying otherwise sent a reader who\n // had no Docker installed off to install it, to satisfy a step that was\n // never going to run — and made the whole quickstart look like it\n // required a daemon it does not.\n //\n // \"schema\" covers two things `dev` does before serving: generating the\n // Drizzle schema from the collections, and applying the collections to\n // the database. The push-and-Docker steps are documented under\n // `rebase dev --help`.\n lines.push(note(\"A local database configuration has been generated in .env.\"));\n lines.push(note(\"Start everything — the database and schema are set up for you,\"));\n lines.push(note(\"with no Docker and nothing else to install:\"));\n lines.push(runDev);\n lines.push(\"\");\n lines.push(note(\"To use a Postgres of your own instead, uncomment DATABASE_URL in .env.\"));\n }\n\n lines.push(\"\");\n // Both halves of the first-admin rule, once, here.\n //\n // `init` has just written REBASE_ADMIN_EMAIL and a generated password into\n // `.env`, and they belong to the *production* boot — the compose stack, where\n // the first-registration window is shut. `rebase dev` reads the same file, so\n // without this line the honest reading of that `.env` is \"these are my\n // credentials\", and the first documented step (register, become the admin)\n // reads as broken.\n lines.push(note(\"Sign in: the first account you register becomes the admin.\"));\n lines.push(note(\"In production (docker compose) that window is shut and the account named by\"));\n lines.push(note(\"REBASE_ADMIN_EMAIL in .env is created at boot instead.\"));\n\n lines.push(\"\");\n // `--headless --introspect` had these two claims fighting: the step above\n // announced \"collections generated!\" and this line then said there are none.\n // Both were reachable at once, and the reader has to believe one of them.\n lines.push(headless\n ? introspected\n ? chalk.gray(\"This starts a headless API (Hono + PostgreSQL) over the collections just \")\n + chalk.gray(\"generated from your database in config/collections. Edit them to change what the \")\n + chalk.gray(\"API exposes; docs are at /api/swagger.\")\n : chalk.gray(\"This starts a headless API (Hono + PostgreSQL). There are no collection files: \")\n + chalk.gray(\"the API is derived from your database schema. Once it serves a table, docs are at /api/swagger.\")\n : chalk.gray(\"This starts both the backend (Hono + PostgreSQL)\")\n + chalk.gray(\" and the frontend (Vite + React) concurrently.\"));\n return lines;\n}\n\n/**\n * The closing \"AI Agent Skills\" lines, each command one its reader can run.\n *\n * `rebase skills install` with no `--agent` prompts, and refuses without a\n * TTY — so printing it bare at the end of `rebase init --yes`, the agent and\n * CI path, told that reader to run a command that answers \"Cannot prompt\".\n * Named agents are spelled out (a refresh must not depend on detection, which\n * never finds Copilot); otherwise a TTY reader gets the prompt, anyone else\n * the `--agent` form.\n */\nexport function agentNextSteps(agents: readonly AgentKey[], interactive: boolean, pm: PMCommands): string[] {\n const both = (args: string[]): string =>\n ` ${chalk.cyan([\"rebase\", ...args].join(\" \"))} ${chalk.gray(\"or\")} ${chalk.cyan(pm.exec(\"rebase\", args).join(\" \"))}`;\n if (agents.length > 0) {\n return [\n chalk.gray(` Set up for ${agents.map(a => AGENTS[a].label).join(\", \")}. Commit the files to share them;`),\n chalk.gray(\" after a Rebase upgrade, refresh the skills with:\"),\n \"\",\n both([\"skills\", \"install\", \"--agent\", agents.join(\",\")])\n ];\n }\n if (interactive) {\n return [\n chalk.gray(\" Install Rebase agent skills, and the Rebase MCP server, for your AI coding assistant:\"),\n \"\",\n both([\"skills\", \"install\", \"--mcp\"])\n ];\n }\n return [\n chalk.gray(\" Install Rebase agent skills, and the Rebase MCP server, for your AI coding assistant —\"),\n chalk.gray(` name it: ${AGENT_KEYS.join(\", \")}, or all.`),\n \"\",\n both([\"skills\", \"install\", \"--agent\", \"<agent>\", \"--mcp\"])\n ];\n}\n\n/**\n * What a directory may already hold for `rebase init` to scaffold into it.\n *\n * `git clone` of an empty repository, or `git init`, leaves exactly `.git` —\n * and `rebase init . --yes --git` is the help's own example. Counting every\n * entry as \"not empty\" refused the one directory that example is most likely\n * to be typed in. The entries here are a repository's metadata, the files\n * operating systems drop into any folder, and a licence: none of them is\n * something the scaffold writes (`init.test.ts` holds the template to that),\n * so tolerating them can never overwrite anything. Everything else is a\n * conflict and is named, never merged into.\n */\nexport const SCAFFOLD_TOLERATED_ENTRIES: ReadonlySet<string> = new Set([\n \".git\", \".hg\", \".svn\", \".gitattributes\",\n \".DS_Store\", \"Thumbs.db\", \"desktop.ini\",\n \"LICENSE\", \"LICENSE.md\", \"LICENSE.txt\"\n]);\n\n/** Entries in `dir` the scaffold would overwrite or mix with — `[]` when it may proceed. */\nexport function scaffoldDirectoryConflicts(dir: string): string[] {\n if (!fs.existsSync(dir)) return [];\n return fs.readdirSync(dir, { withFileTypes: true })\n .filter(entry => !SCAFFOLD_TOLERATED_ENTRIES.has(entry.name))\n .map(entry => entry.isDirectory() ? `${entry.name}/` : entry.name)\n .sort();\n}\n\n/**\n * The refusal for a directory that already holds files, with the two ways forward.\n *\n * `invocation` is `rebase` as the reader started it, so the command offered is\n * one they can run.\n */\nexport function scaffoldConflictMessage(\n projectName: string,\n displayPath: string,\n conflicts: string[],\n invocation: readonly string[] = invokedRebaseCommand()\n): string[] {\n const shown = conflicts.slice(0, 8).join(\", \") + (conflicts.length > 8 ? `, and ${conflicts.length - 8} more` : \"\");\n return [\n `${chalk.red.bold(\"ERROR\")} Directory \"${projectName}\" already holds files a new project would overwrite or mix with: ${shown}`,\n chalk.yellow(` Scaffold somewhere new — ${chalk.cyan([...invocation, \"init\", \"<new-directory>\"].join(\" \"))} — or move those out of ${displayPath || \"this directory\"} and run this again.`),\n chalk.gray(` A repository's own metadata (.git) and a LICENSE are fine to leave in place.`)\n ];\n}\n\n/**\n * `git init`, or nothing when the directory is already a repository.\n *\n * A cloned repository keeps its branch, its remote and its history: the\n * scaffold is committed on top of what is there. Only a repository this\n * function created is pointed at `main`.\n */\nexport async function initGitRepository(targetDirectory: string): Promise<boolean> {\n if (fs.existsSync(path.join(targetDirectory, \".git\"))) {\n console.log(chalk.gray(\" Using the existing git repository...\"));\n return true;\n }\n console.log(chalk.gray(\" Initializing git repository...\"));\n try {\n await execa(\"git\", [\"init\"], { cwd: targetDirectory });\n // Name the branch `main` rather than inheriting whatever\n // `init.defaultBranch` is (often still `master`). `git init -b` would\n // be the obvious way, but it needs git >= 2.28; rewriting HEAD works\n // on every version and is safe before the first commit.\n try {\n await execa(\"git\", [\"symbolic-ref\", \"HEAD\", \"refs/heads/main\"], { cwd: targetDirectory });\n } catch {\n // Leave the default branch name; not worth failing the scaffold.\n }\n return true;\n } catch {\n console.warn(chalk.yellow(\" Warning: Failed to initialize git repository\"));\n return false;\n }\n}\n\nasync function createProject(options: InitOptions) {\n const startedAt = Date.now();\n const conflicts = scaffoldDirectoryConflicts(options.targetDirectory);\n if (conflicts.length > 0) {\n for (const line of scaffoldConflictMessage(options.projectName, formatCdTarget(process.cwd(), options.targetDirectory), conflicts)) {\n console.error(line);\n }\n process.exit(1);\n }\n fs.mkdirSync(options.targetDirectory, { recursive: true });\n\n // Verify template exists\n try {\n await access(options.templateDirectory, fs.constants.R_OK);\n } catch {\n console.error(`${chalk.red.bold(\"ERROR\")} Template not found at ${options.templateDirectory}`);\n process.exit(1);\n }\n\n // Copy template files\n console.log(chalk.gray(\" Copying project files...\"));\n try {\n await cp(options.templateDirectory, options.targetDirectory, {\n recursive: true,\n filter: (source: string) => {\n const basename = path.basename(source);\n // Skip node_modules and .DS_Store\n return basename !== \"node_modules\" && basename !== \".DS_Store\";\n }\n });\n } catch (err: unknown) {\n console.error(`${chalk.red.bold(\"ERROR\")} Failed to copy template files: ${err instanceof Error ? err.message : String(err)}`);\n process.exit(1);\n }\n\n // npm/pnpm always strip files named .gitignore and .npmrc from published\n // tarballs, so the template ships them un-dotted and we restore the real\n // names here.\n for (const [from, to] of [[\"gitignore\", \".gitignore\"], [\"npmrc\", \".npmrc\"]] as const) {\n const shipped = path.join(options.targetDirectory, from);\n if (fs.existsSync(shipped)) {\n fs.renameSync(shipped, path.join(options.targetDirectory, to));\n }\n }\n\n // Apply the selected template preset (swap collection files)\n if (options.headless && options.explicitPreset) {\n // baas has no collections, so there is nothing for a preset to swap.\n // Say so rather than accepting the flag and silently dropping it.\n console.log(chalk.yellow(` Ignoring --template ${options.preset}: a headless project declares no collections.`));\n }\n if (!options.headless) {\n // When introspecting, the database is the source of truth: start from\n // the blank preset so example collections never register on top of\n // tables the database doesn't have.\n if (options.introspect && options.preset !== \"blank\") {\n console.log(chalk.gray(\" Using the blank template: collections will come from your database.\"));\n }\n const applied = await applyPreset(options.targetDirectory, options.introspect ? \"blank\" : options.preset);\n writeShippedSchema(options.targetDirectory, applied);\n }\n\n // Reduce the project to the selected shape\n await applyHeadless(options.targetDirectory, options.headless);\n\n // Replace placeholder project name in package.json files\n await replacePlaceholders(options);\n\n // Rename .env.example to .env if it exists and randomize secrets\n await configureEnvFile(options.targetDirectory, options.databaseUrl);\n\n // The manifest's build command is the project's package manager, not npm.\n // The template has to spell one of them, and `npm run build --workspace\n // frontend` in a pnpm project is a second package manager reading a\n // lockfile it did not write.\n await writeProjectPackageManagerIntoManifest(options.targetDirectory, options.pmCommands);\n // The scaffold's `.mcp.json` runs the project's own `@rebasepro/mcp`\n // through the package manager, and it ships spelled for pnpm.\n retargetScaffoldMcpConfig(options.targetDirectory, options.pm);\n\n // Before git, so the skills and MCP configs are in the initial commit.\n if (options.agents.length > 0) {\n console.log(chalk.gray(\" Setting up AI coding agents...\"));\n printAgentSetup(configureAgents(options.agents, options.targetDirectory, undefined, options.pm), options.pm);\n }\n\n // Create the repository now, but commit at the very end — see\n // commitScaffold below for why the two halves are separated.\n const gitInitialized = options.git ? await initGitRepository(options.targetDirectory) : false;\n\n const { pm, pmCommands } = options;\n const installCmd = pmCommands.install;\n const execCmd = pmCommands.exec(\"rebase\", [\"schema\", \"introspect\", \"--force\"]);\n // No --collections: config/collections is the default, and a path written\n // here is resolved from where the reader runs it — the project root.\n const generateCmd = pmCommands.exec(\"rebase\", [\"schema\", \"generate\"]);\n\n // Whether the install succeeded, so a failed one is printed as a step still to take.\n let installed = false;\n if (options.installDeps) {\n console.log(\"\");\n console.log(chalk.gray(` Installing dependencies with ${pm}...`));\n console.log(\"\");\n try {\n await execa(installCmd[0], installCmd.slice(1), {\n cwd: options.targetDirectory,\n stdio: \"inherit\"\n });\n installed = true;\n } catch {\n console.warn(chalk.yellow(` Warning: Failed to install dependencies. You may need to run \\`${installCmd.join(\" \")}\\` manually.`));\n }\n }\n\n // Whether introspection actually ran and produced collections. The next\n // steps below report what really happened, so a skipped or failed\n // introspection is never announced as a success.\n let introspected = false;\n\n if (options.introspect) {\n console.log(\"\");\n if (options.installDeps) {\n console.log(chalk.gray(\" Introspecting database and generating collections...\"));\n console.log(\"\");\n try {\n // --force overwrites template example collections with real ones\n await execa(execCmd[0], execCmd.slice(1), {\n cwd: options.targetDirectory,\n stdio: \"inherit\"\n });\n // The scaffold holds the blank preset's schema.generated.ts;\n // regenerate it from the introspected collections or the\n // backend serves a schema that doesn't match the database.\n await execa(generateCmd[0], generateCmd.slice(1), {\n cwd: options.targetDirectory,\n stdio: \"inherit\"\n });\n console.log(chalk.green(\" Database successfully introspected!\"));\n introspected = true;\n } catch {\n console.warn(chalk.yellow(\" Warning: Failed to introspect database automatically.\"));\n console.warn(chalk.yellow(` You can run \\`${execCmd.join(\" \")}\\` then \\`${generateCmd.join(\" \")}\\` manually after setup.`));\n }\n } else {\n console.warn(chalk.yellow(\" Skipping introspection because dependencies were not installed.\"));\n console.warn(chalk.yellow(` Run \\`${installCmd.join(\" \")}\\` then \\`${execCmd.join(\" \")}\\` manually.`));\n }\n }\n\n if (gitInitialized) await commitScaffold(options.targetDirectory);\n\n await linkScaffoldToCloud(options);\n\n // Success message\n console.log(\"\");\n console.log(`${chalk.green.bold(\"✓\")} Project ${chalk.bold(options.projectName)} created successfully!`);\n console.log(\"\");\n console.log(chalk.bold(\"Next steps:\"));\n console.log(\"\");\n const nextSteps = initNextSteps({\n headless: options.headless,\n ownDatabase: Boolean(options.databaseUrl),\n introspect: Boolean(options.introspect),\n introspected,\n installed,\n // The path the user has to type, not the project's basename: `init\n // apps/my-app` must say `cd apps/my-app`, and `init .` needs no cd at all\n // because they are already standing in the project.\n cdTarget: formatCdTarget(process.cwd(), options.targetDirectory),\n pm: pmCommands\n });\n for (const line of nextSteps) console.log(line);\n console.log(\"\");\n console.log(chalk.gray(\"Docs: https://rebase.pro/docs\"));\n console.log(chalk.gray(\"GitHub: https://github.com/rebasepro/rebase\"));\n console.log(\"\");\n console.log(chalk.bold(\"🤖 AI Agent Skills\"));\n console.log(\"\");\n for (const line of agentNextSteps(options.agents, options.interactive === true, pmCommands)) console.log(line);\n console.log(\"\");\n\n // Asked here, at the very end, and never before: the project exists, it\n // worked, and the user has something to judge us on. Nothing has been\n // written or transmitted up to this line — declining leaves no id and no\n // file. See telemetry/consent.ts for why the order matters.\n //\n // `--yes` and non-TTY runs never reach the prompt, so CI behaviour is\n // unchanged.\n const initProperties = {\n preset: options.headless ? \"none\" : options.preset,\n headless: Boolean(options.headless),\n package_manager: options.pm,\n installed_deps: Boolean(options.installDeps),\n introspected,\n own_database: Boolean(options.databaseUrl),\n cloud_linked: Boolean(options.cloudProject),\n git: Boolean(options.git),\n duration: durationBucket(Date.now() - startedAt)\n };\n // `reAskDeclined`: scaffolding is the one moment where asking again is an\n // offer rather than nagging. See PromptOptions.\n if (await promptForConsent({ reAskDeclined: true })) {\n await recordEvent(\"cli.init\", initProperties, { projectRoot: options.targetDirectory });\n }\n}\n\n/**\n * Apply a template preset by replacing the default collection files.\n *\n * The template ships with blog collections at the top level and\n * preset alternatives under `config/collections/presets/<name>/`.\n * This function swaps the active collection files and removes the\n * presets directory so the final project is clean.\n */\n/**\n * Reduce the scaffolded project to the chosen shape.\n *\n * The base template is the full triad. `--headless` drops the frontend and the\n * declared collections — there is nothing to define, since the server derives\n * its API from the database — and overlays the files that differ.\n *\n * The config *package* stays, holding only `storageAuthorize`. Storage is not\n * under row-level security, so a deployment with file storage enabled and no\n * access model serves every user's files to every signed-in user; the server\n * refuses to boot in that state. Deleting the package outright would leave a\n * headless project with nowhere to put the hook, and the scaffold's own\n * docker-compose.yml enables storage — so the first `docker compose up` would\n * crash-loop.\n */\nasync function applyHeadless(targetDirectory: string, headless: boolean): Promise<void> {\n if (!headless) return;\n\n fs.rmSync(path.join(targetDirectory, \"frontend\"), { recursive: true,\nforce: true });\n // Collections only. `rebase build` reads the absence of this directory as\n // \"introspect from the database\", and the overlay replaces config/index.ts\n // with one that exports the storage hook alone.\n fs.rmSync(path.join(targetDirectory, \"config\", \"collections\"), { recursive: true,\nforce: true });\n for (const stray of [\"cms.d.ts\", \"frontend-assets.d.ts\"]) {\n fs.rmSync(path.join(targetDirectory, \"config\", stray), { force: true });\n }\n // Generated from collection files; a headless project reads the live schema.\n fs.rmSync(path.join(targetDirectory, \"backend\", \"src\", \"schema.generated.ts\"), { force: true });\n\n const overlayDir = path.resolve(cliRoot!, \"templates\", \"overlays\", \"baas\");\n if (!fs.existsSync(overlayDir)) {\n console.error(`${chalk.red.bold(\"ERROR\")} BaaS template overlay not found at ${overlayDir}`);\n process.exit(1);\n }\n\n await cp(overlayDir, targetDirectory, {\n recursive: true,\n force: true,\n filter: (source: string) => {\n const basename = path.basename(source);\n return basename !== \"node_modules\" && basename !== \".DS_Store\";\n }\n });\n}\n\n/**\n * Rewrite `rebase.json`'s static-app build command for this project's package manager.\n *\n * The template must spell one of them, and it spells npm. In a pnpm project\n * that is a second package manager invoked against a lockfile it did not\n * write — `rebase build` then either installs a second tree or fails on a\n * workspace flag npm and pnpm spell differently.\n */\nasync function writeProjectPackageManagerIntoManifest(\n targetDirectory: string,\n pmCommands: PMCommands\n): Promise<void> {\n const manifestPath = path.join(targetDirectory, \"rebase.json\");\n if (!fs.existsSync(manifestPath)) return;\n\n try {\n const manifest = JSON.parse(fs.readFileSync(manifestPath, \"utf8\")) as {\n apps?: Record<string, { type?: string; root?: string; build?: string }>;\n };\n let changed = false;\n for (const app of Object.values(manifest.apps ?? {})) {\n if (app.type !== \"static\" || !app.root || !app.build) continue;\n const rewritten = pmCommands.runWorkspace(app.root, \"build\").join(\" \");\n if (rewritten !== app.build) {\n app.build = rewritten;\n changed = true;\n }\n }\n if (changed) {\n fs.writeFileSync(manifestPath, `${JSON.stringify(manifest, null, 4)}\\n`, \"utf8\");\n }\n } catch {\n // A manifest that will not parse is `rebase build`'s problem to report,\n // and scaffolding must not fail on a cosmetic rewrite.\n }\n}\n\n/** Swap in the preset's collection files. Returns the preset actually applied. */\nasync function applyPreset(targetDirectory: string, preset: TemplatePreset): Promise<TemplatePreset> {\n const collectionsDir = path.join(targetDirectory, \"config\", \"collections\");\n const presetsDir = path.join(collectionsDir, \"presets\");\n\n if (preset !== \"blog\") {\n const presetDir = path.join(presetsDir, preset);\n if (!fs.existsSync(presetDir)) {\n console.warn(chalk.yellow(` Warning: Preset \"${preset}\" not found, falling back to blog template.`));\n cleanupPresets(presetsDir);\n return \"blog\";\n }\n\n // Remove the default blog collection files (keep users.ts — it's shared)\n const blogFiles = [\"posts.ts\", \"authors.ts\", \"tags.ts\", \"index.ts\"];\n for (const file of blogFiles) {\n const filePath = path.join(collectionsDir, file);\n if (fs.existsSync(filePath)) {\n fs.unlinkSync(filePath);\n }\n }\n\n // Copy preset files into the collections directory\n const presetFiles = fs.readdirSync(presetDir).filter(f => f.endsWith(\".ts\"));\n for (const file of presetFiles) {\n fs.copyFileSync(\n path.join(presetDir, file),\n path.join(collectionsDir, file)\n );\n }\n }\n\n // Always clean up the presets directory — it shouldn't ship with the final project\n cleanupPresets(presetsDir);\n return preset;\n}\n\n/**\n * Where the generated Drizzle schema for each preset ships, beside the template.\n *\n * `rebase dev` regenerates `backend/src/schema.generated.ts` on every start, and\n * the Studio's schema editor refuses to commit over a dirty copy of it — a\n * schema change must not sweep up somebody's work in progress. So whatever\n * `init` writes there, and commits under `--git`, has to be byte-for-byte what\n * that first regeneration produces. A stub (`export const tables = {}`) was\n * rewritten on the first `rebase dev`, and every new project's first Studio\n * edit was refused with `SCHEMA_EDIT_DIRTY_TREE` before its owner had changed\n * a thing.\n *\n * The file cannot be generated here: generation loads the collection files,\n * and they import `@rebasepro/cms-types`, which the project does not have\n * until it is installed — `init --git` without `--install` commits first. So\n * each preset's output is generated in this repository and shipped, and\n * `init-generated-schema.test.ts` fails the moment one of them differs from\n * what the generator writes for that preset's collections.\n *\n * Outside `templates/template` on purpose: a preset's schema must not be\n * copied into a project that chose another preset.\n */\nexport function shippedSchemaPath(preset: TemplatePreset): string {\n return path.resolve(cliRoot!, \"templates\", \"generated-schemas\", `${preset}.ts`);\n}\n\n/** Write the preset's generated schema to `backend/src/schema.generated.ts`. */\nfunction writeShippedSchema(targetDirectory: string, preset: TemplatePreset): void {\n const destination = path.join(targetDirectory, \"backend\", \"src\", \"schema.generated.ts\");\n fs.mkdirSync(path.dirname(destination), { recursive: true });\n fs.copyFileSync(shippedSchemaPath(preset), destination);\n}\n\nfunction cleanupPresets(presetsDir: string): void {\n if (fs.existsSync(presetsDir)) {\n fs.rmSync(presetsDir, { recursive: true,\nforce: true });\n }\n}\n\n/**\n * The one registry call `init` makes, and what it is for.\n *\n * Every `@rebasepro/*` package in this repository carries the same version —\n * `check:publishable-set` fails the build on the first PR after a bump if any\n * of them drifts — so the version to pin is known before the network is\n * touched: it is this CLI's own. Asking npm eleven times, once per package,\n * bought nothing but eleven chances to hang. On a slow connection that was the\n * slowest part of `init`; on no connection it was eleven timeouts before a\n * fallback to `\"latest\"`, which is the one answer that produces a project\n * mixing framework eras.\n *\n * One call remains, and it asks a different question. Lockstep is enforced in\n * the repository, not on the registry: a release can still ship the CLI and\n * leave a package behind, which is exactly what happened to\n * `@rebasepro/agent-skills` across three releases. So this probes a single\n * package — `@rebasepro/server`, the one a project cannot run without — to see\n * whether this version really made it out. A gap is a refusal, because the\n * alternative is scaffolding a project that cannot install.\n *\n * Unreachable is NOT a refusal. `init` offline is a legitimate thing to do,\n * and the pin it would write is the same pin the probe would have confirmed.\n * It says so and carries on.\n */\ntype ReleaseProbe =\n | { kind: \"published\" }\n | { kind: \"gap\"; found: string }\n | { kind: \"unreachable\"; reason: string };\n\n/** The package the probe asks about. Not the CLI: the CLI is what is running. */\nconst RELEASE_PROBE_PACKAGE = \"@rebasepro/server\";\n\nasync function probeRelease(version: string): Promise<ReleaseProbe> {\n try {\n const { stdout } = await execa(\"npm\", [\"view\", `${RELEASE_PROBE_PACKAGE}@${version}`, \"version\"]);\n if (stdout.trim() === version) return { kind: \"published\" };\n // npm answers an unpublished version with empty output rather than an\n // error on some registries. Empty is a gap, not a success.\n return { kind: \"gap\", found: stdout.trim() || \"nothing\" };\n } catch (error) {\n const text = `${(error as { stderr?: string }).stderr ?? \"\"}${(error as Error).message ?? \"\"}`;\n if (/E404|404 Not Found|No match(ing)? version/i.test(text)) {\n return { kind: \"gap\", found: \"nothing\" };\n }\n return { kind: \"unreachable\", reason: firstLine(text) || \"the registry could not be reached\" };\n }\n}\n\nfunction firstLine(text: string): string {\n return text.split(\"\\n\").map(l => l.trim()).filter(Boolean)[0] ?? \"\";\n}\n\nasync function replacePlaceholders(options: InitOptions) {\n const filesToProcess = TEMPLATE_PLACEHOLDER_FILES;\n\n // `unknown` only when the manifest could not be read at all, which is the\n // same \"nothing to pin, nothing to confirm\" case `latest` used to stand for.\n const version = cliVersion();\n\n const fileContents = new Map<string, string>();\n const allPackages = new Set<string>();\n\n for (const file of filesToProcess) {\n const fullPath = path.resolve(options.targetDirectory, file);\n if (!fs.existsSync(fullPath)) continue;\n const content = fs.readFileSync(fullPath, \"utf-8\");\n fileContents.set(fullPath, content);\n\n const matches = [...content.matchAll(/\"(@rebasepro\\/[^\"]+)\":\\s*\"workspace:\\*\"/g)];\n for (const match of matches) allPackages.add(match[1]);\n }\n\n // `latest` means this CLI could not read its own manifest — there is no\n // version to confirm, and nothing to gain from asking.\n const skipProbe = parseEnvBoolean(process.env.REBASE_E2E) === true || version === \"unknown\";\n const probe: ReleaseProbe = skipProbe ? { kind: \"published\" } : await probeRelease(version);\n\n if (probe.kind === \"gap\") {\n throw new Error(\n `Rebase ${version} is not fully published to npm.\\n\\n` +\n `${RELEASE_PROBE_PACKAGE} has no ${version} release — the registry has ${probe.found}.\\n` +\n `Every @rebasepro package ships at one version, so scaffolding would pin ${allPackages.size}\\n` +\n `dependencies at a version that is not there, and the install would fail.\\n\\n` +\n \"That is a release gap in Rebase itself — not a problem with your machine,\\n\" +\n \"your network, or your package manager.\\n\\n\" +\n \"Stopped before writing dependency versions or installing anything. The\\n\" +\n `project directory ${path.basename(options.targetDirectory)}/ was created and is safe to delete.\\n` +\n \"Please report this.\"\n );\n }\n\n if (probe.kind === \"published\") {\n console.log(chalk.gray(` Pinning ${allPackages.size} @rebasepro package(s) to ${version}...`));\n }\n\n if (probe.kind === \"unreachable\") {\n console.log(chalk.gray(` Pinning @rebasepro packages to ${version} (this CLI's version).`));\n console.log(chalk.yellow(` Could not reach npm to confirm the release: ${probe.reason}`));\n console.log(chalk.gray(\" The pin is right either way — install will say so if the version is missing.\"));\n }\n\n for (const [fullPath, originalContent] of fileContents.entries()) {\n let content = originalContent.replace(/\\{\\{PROJECT_NAME\\}\\}/g, options.projectName);\n for (const pkgName of allPackages) {\n content = content.replace(\n new RegExp(`\"${pkgName}\":\\\\s*\"workspace:\\\\*\"`, \"g\"),\n `\"${pkgName}\": \"${version}\"`\n );\n }\n fs.writeFileSync(fullPath, content, \"utf-8\");\n }\n}\n\n\n/** `undefined` binds the wildcard address, which is a different question — see isPortAvailable. */\nfunction canBind(port: number, host?: string): Promise<boolean> {\n return new Promise((resolve) => {\n const server = net.createServer();\n server.once(\"error\", (err: NodeJS.ErrnoException) => {\n // The host has no such address family at all — a v4-only machine\n // asked about `::1`. Nothing can be listening on an address that\n // cannot exist, so this is free rather than taken.\n resolve(err.code === \"EAFNOSUPPORT\" || err.code === \"EADDRNOTAVAIL\");\n });\n server.once(\"listening\", () => {\n server.close(() => resolve(true));\n });\n if (host === undefined) server.listen(port);\n else server.listen(port, host);\n });\n}\n\n/**\n * Whether `port` is free — on the wildcard address *and* on both loopback addresses.\n *\n * All three, because on macOS/BSD a successful bind does not mean the port is\n * unused. Sockets carry `SO_REUSEADDR` (Node sets it), under which a wildcard\n * bind and a specific-address bind on the same port do not conflict — in either\n * direction. So each probe alone has a blind spot, and they are different ones:\n *\n * - **Wildcard only** (what this used to do) misses a server bound to\n * `127.0.0.1` and `[::1]` — a Homebrew or Postgres.app install, i.e. most\n * developer machines. 5432 was reported free while it was already serving\n * another project's database. Docker then published `*:5432` for the same\n * reason and the container started cleanly, with no \"port already allocated\"\n * error anywhere to hint at the collision. `DATABASE_URL` pointed at\n * `localhost:5432`, `localhost` resolves to `::1` first, and every command\n * reported success while reading and writing the *pre-existing* database — a\n * `db push` would have created tables, roles and RLS policies inside it.\n *\n * - **Loopback only** misses the opposite case: Docker Desktop publishes a\n * container's port on `*`, and a specific-address bind succeeds right past it.\n * That port is free to probe and unusable to publish, so `docker compose up -d\n * db` fails on \"Bind for 0.0.0.0:PORT failed: port is already allocated\" —\n * loudly, but only after the project has been generated around the bad port.\n *\n * Requiring all three costs three sockets and leaves neither gap. The wildcard\n * bind is the one the container itself has to make; the loopback binds are the\n * addresses `DATABASE_URL` will actually name.\n */\nexport async function isPortAvailable(port: number): Promise<boolean> {\n return (await canBind(port)) && (await canBind(port, \"127.0.0.1\")) && (await canBind(port, \"::1\"));\n}\n\n/** The range a project's compose port search starts in: 5433–5632. */\nconst COMPOSE_DB_PORT_FIRST = 5433;\nconst COMPOSE_DB_PORT_SPAN = 200;\n\n/**\n * Where this project's compose port search starts: a port derived from its path.\n *\n * Every project used to start at 5432 and take the first free port. Nothing a\n * scaffold does claims that port — its container does not exist yet — so\n * projects scaffolded side by side all landed on the same one: blog, ecommerce\n * and blank all got 5433. `rebase dev --docker` in the second one then found\n * the first one's database answering there.\n *\n * Not 5432 at all: it is the port a Postgres installed on this machine later\n * will take, and on macOS a container publishing `*:5432` and a server bound to\n * loopback do not even collide loudly (see {@link isPortAvailable}).\n */\nexport function composeDbPortStart(targetDirectory: string): number {\n const digest = crypto.createHash(\"sha256\").update(path.resolve(targetDirectory)).digest();\n return COMPOSE_DB_PORT_FIRST + (digest.readUInt32BE(0) % COMPOSE_DB_PORT_SPAN);\n}\n\n/**\n * The host port this project's compose `db` service publishes.\n *\n * From {@link composeDbPortStart} upwards, the first port nothing listens on\n * ({@link isPortAvailable}) and no container on this machine is bound to,\n * running or stopped: a stopped project's `db` container takes its port back\n * the moment it starts, and a TCP probe cannot see it. Docker that cannot be\n * asked claims nothing. `probes` stands in for both in the tests.\n */\nexport async function chooseComposeDbPort(\n targetDirectory: string,\n probes: {\n isFree?: (port: number) => Promise<boolean>;\n claimed?: () => Promise<Set<number> | null>;\n } = {}\n): Promise<number> {\n const isFree = probes.isFree ?? isPortAvailable;\n const claimed = (await (probes.claimed ?? dockerClaimedPorts)()) ?? new Set<number>();\n let port = composeDbPortStart(targetDirectory);\n while (claimed.has(port) || !(await isFree(port))) {\n port++;\n }\n return port;\n}\n\n/**\n * The CLI's own version, which is the runtime image tag a scaffolded project\n * pins. They move together: the CLI, the packages and the runtime image are cut\n * from one release, so the version that installed the project is the version\n * whose runtime can boot its bundle.\n *\n * Falls back to `latest` only when the CLI cannot read its own manifest — a\n * floating tag is worse than a pinned one, but better than a compose file that\n * names a version that was never published.\n */\nfunction readCliVersion(): string {\n const version = cliVersion();\n return version === \"unknown\" ? \"latest\" : version;\n}\n\n/**\n * The runtime image tag to pin, given the version of the CLI doing the scaffolding.\n *\n * Only a stable release publishes `rebasepro/server` — a multi-arch build on\n * every push to main would cost minutes per commit for an image nobody pulls.\n * So pinning a prerelease CLI's own version writes a tag that cannot exist, and\n * `docker compose up` fails on `manifest unknown`, which is the same dead end\n * as the missing-repository bug this pinning was added to prevent.\n *\n * A prerelease therefore falls back to `latest`, which is correct rather than\n * merely available: a bundle's manifest declares the runtime range it needs\n * (`^1`), the image supplies only `@rebasepro/server`, and the framework a\n * bundle runs is installed from its own `deps.declared` at boot. The current\n * stable runtime boots a canary bundle by design.\n *\n * A floating tag is a real cost — it is what pinning exists to avoid — so say\n * so in the file rather than leaving a reader to discover it.\n */\nexport function resolveRuntimeImageTag(cliVersion: string): { tag: string; note?: string } {\n // Semver prerelease: everything after the first `-`. `0.13.1-canary.gcd6689e`.\n if (/^\\d+\\.\\d+\\.\\d+-/.test(cliVersion)) {\n return {\n tag: \"latest\",\n note: `# Scaffolded by a prerelease CLI (${cliVersion}), which publishes no runtime image,\\n` +\n \"# so this floats to the newest stable runtime. Pin an exact version once you\\n\" +\n \"# deploy: a moving tag changes what you are running with no version changing.\"\n };\n }\n return { tag: cliVersion };\n}\n\n/**\n * Set `KEY=value` in a .env body, whether the key ships set, commented out or\n * missing entirely.\n *\n * The missing case is not hypothetical: `configureEnvFile` runs against\n * whatever `.env.example` is in the target directory, which for a project\n * scaffolded from an older template — or from one somebody edited — may not\n * carry a key this version of `init` is expected to write. Silently skipping it\n * produces a `.env` that satisfies no `${VAR:?…}` in the compose file, which\n * surfaces as a stack that refuses to interpolate rather than as anything\n * pointing back here.\n */\nfunction setEnvValue(content: string, key: string, value: string, header?: string): string {\n const line = new RegExp(`^#?\\\\s*${key}=.*$`, \"m\");\n if (line.test(content)) return content.replace(line, `${key}=${value}`);\n return `${content.trimEnd()}\\n\\n${header ? `${header}\\n` : \"\"}${key}=${value}\\n`;\n}\n\n/**\n * `sslmode=disable` on a URL that names a database on this machine and says\n * nothing about TLS.\n *\n * A local Postgres — Homebrew, Postgres.app, a container — serves no TLS, and\n * Atlas, which `rebase db push`, `db generate` and `db migrate` run, requires it\n * unless the URL says otherwise: `pq: SSL is not enabled on the server`. The URL\n * `init` writes when it picks the database has always carried the parameter; a\n * URL passed with `--database-url` reached `.env` without it, so the app\n * connected and the first push failed.\n *\n * Only a loopback address, and only when the URL names no `sslmode` or `ssl` of\n * its own: another machine's TLS is not ours to turn off, and a choice the\n * developer wrote down is theirs. A `host` or `hostaddr` parameter is the real\n * address (a socket directory, say), so that URL is left as given too, and so\n * is anything this cannot parse.\n */\nexport function disableSslOnLoopback(connectionString: string): string {\n let url: URL;\n try {\n url = new URL(connectionString);\n } catch {\n return connectionString;\n }\n if (url.protocol !== \"postgres:\" && url.protocol !== \"postgresql:\") return connectionString;\n if ([\"sslmode\", \"ssl\", \"host\", \"hostaddr\"].some(param => url.searchParams.has(param))) return connectionString;\n\n // `hostname` keeps an IPv6 literal's brackets.\n const host = url.hostname.replace(/^\\[(.+)\\]$/, \"$1\");\n const loopback = host === \"localhost\" || host === \"::1\" || host === \"0.0.0.0\" || /^127(?:\\.\\d{1,3}){3}$/.test(host);\n if (!loopback) return connectionString;\n\n // Appended to the query as it stands, so the percent-encoding `pinSearchPath`\n // chose for `options` — the form libpq accepts — is kept byte for byte.\n url.search = url.search ? `${url.search}&sslmode=disable` : \"?sslmode=disable\";\n return url.toString();\n}\n\nexport async function configureEnvFile(targetDirectory: string, databaseUrl?: string) {\n const envExamplePath = path.join(targetDirectory, \".env.example\");\n const envPath = path.join(targetDirectory, \".env\");\n if (fs.existsSync(envExamplePath) && !fs.existsSync(envPath)) {\n // Copy .env.example → .env (keep .env.example as a reference in the repo)\n fs.copyFileSync(envExamplePath, envPath);\n // Owner-only from the first byte. `copyFileSync` copies the *source's*\n // permissions and the packaged `.env.example` is 0644, so the file that\n // is about to receive a generated JWT_SECRET, the database password and\n // REBASE_SERVICE_KEY was world-readable for the whole of `init` — on a\n // shared build box or a multi-user machine, any local account could read\n // another project's service key, which the API treats as a full admin\n // credential. `.rebase/state.json` (dev-port.ts) and `telemetry.json`\n // are both 0600 for the same reason; this file holds strictly more.\n fs.chmodSync(envPath, 0o600);\n\n // Generate secure random strings\n const jwtSecret = crypto.randomBytes(32).toString(\"hex\");\n const dbPassword = crypto.randomBytes(16).toString(\"hex\");\n const serviceKey = crypto.randomBytes(48).toString(\"base64\");\n // base64url rather than base64 or hex: 24 characters, comfortably past\n // the runtime's 12-character floor, and every character it can produce\n // is one dotenv and Compose interpolation read back unchanged. `+`, `/`\n // and `=` are fine in a .env value but not obviously so to a reader\n // about to paste one into a shell, and `#` — which hex cannot produce\n // but a passphrase generator might — would truncate the line.\n const adminPassword = crypto.randomBytes(18).toString(\"base64url\");\n\n let envContent = fs.readFileSync(envPath, \"utf-8\");\n\n // The banner is written for the file being read as a template. This\n // copy *is* the filled-in one — it already carries a generated\n // JWT_SECRET, service key and database password — and telling its\n // reader to \"copy this file to .env and fill in the values\" invites\n // exactly the overwrite the README warns against two paragraphs later.\n envContent = envContent.replace(\n /^(# ║ {2})(Copy this file to \\.env and fill in the values)( +║)$/m,\n (_match, open: string, old: string, close: string) => {\n const replacement = \"Generated by `rebase init` — the secrets below are already set\";\n // Keep the box drawn: pad or clip to the width the line had.\n const width = old.length + close.length - 1;\n return open + replacement.slice(0, width).padEnd(width, \" \") + \"║\";\n }\n );\n\n envContent = envContent.replace(\n /^JWT_SECRET=.*$/m,\n `JWT_SECRET=${jwtSecret}`\n );\n\n // Ships commented out in .env.example. Left unset, the server generates\n // one on every boot and silently invalidates the previous run's tokens,\n // so write a stable one now rather than making each restart a logout.\n envContent = envContent.replace(\n /^#\\s*REBASE_SERVICE_KEY=.*$/m,\n `REBASE_SERVICE_KEY=${serviceKey}`\n );\n\n // Read back rather than hardcoded, so this tracks whatever PORT the\n // generated .env ends up carrying. `docker-compose.yml` publishes the\n // api as `${PORT:-3001}:3001`.\n const composeApiPort = /^PORT=(\\d+)/m.exec(envContent)?.[1] ?? \"3001\";\n\n // Also ships commented out, and left that way the very first command\n // this project tells you to run does not work.\n //\n // `docker-compose.yml` declares `CORS_ORIGINS: ${CORS_ORIGINS:?…}` on\n // the `api` service — deliberately required, because an API that\n // guesses its allowed origins eventually allows the wrong one. But\n // Compose interpolates the WHOLE file before selecting services, so\n // `docker compose up -d db` — step 1 of the \"Next steps\" printed at the\n // end of `rebase init` — failed on a variable the `db` service does not\n // use. So did `docker compose config`: the file could not be read at all.\n //\n // The guard is right; a generated project should satisfy it. The compose\n // stack serves the bundled admin from the `api` container on ${PORT},\n // so that is the origin a browser loads it from. A deployment still has\n // to set its own, and gets a visible CORS failure rather than a silent\n // one if it forgets.\n envContent = envContent.replace(\n /^#\\s*CORS_ORIGINS=.*$/m,\n `CORS_ORIGINS=http://localhost:${composeApiPort}`\n );\n\n // The first admin, named rather than raced for.\n //\n // `docker-compose.yml` runs the api service with NODE_ENV=production,\n // and in production the first-registration-becomes-admin window is\n // shut — deliberately, because the compose stack is serving before its\n // operator has typed anything and whoever found the sign-up form first\n // would own it. A closed window needs a way in that is not a race, and\n // this is it: the account is named in the same file that already holds\n // the JWT secret and the database password.\n //\n // Written by `init` rather than left for the reader, because the\n // compose file declares both with `${VAR:?…}`: unset, `docker compose\n // up` does not produce a deployment without an admin, it produces a\n // file that refuses to interpolate — and it refuses for `docker compose\n // up -d db` too, which is step 1 of the \"Next steps\" printed below.\n //\n // The address is a placeholder the login route accepts. Not\n // `admin@localhost`: `POST /auth/login` parses its body with\n // `z.string().email()`, which rejects a domain with no dot, so that\n // address seeds an account and then 400s on every attempt to use it.\n // The runtime now refuses it at boot; this default is one it accepts.\n envContent = setEnvValue(\n envContent,\n \"REBASE_ADMIN_EMAIL\",\n \"admin@example.com\",\n \"# ── The first admin account, in production only ──────────────────────\\n\" +\n \"# `rebase dev` ignores both of these: on a laptop the first account to\\n\" +\n \"# register becomes the admin, and it says so at boot. In production —\\n\" +\n \"# `docker compose up`, or anything with NODE_ENV=production — that\\n\" +\n \"# window is shut, because the server answers on a hostname before you\\n\" +\n \"# have typed anything. So the operator names the account here instead.\\n\" +\n \"# Created once, while the user table is empty. Change the email to\\n\" +\n \"# yours, sign in, and change the password below.\"\n );\n envContent = setEnvValue(envContent, \"REBASE_ADMIN_PASSWORD\", adminPassword);\n\n // Blank the build-time API URL rather than shipping `http://localhost:3001`.\n //\n // This is the one environment-specific value `init` used to leave alone,\n // and the chain that made it dangerous is fully closed: `.env.example`\n // ships the literal, `frontend/vite.config.ts` sets `envDir: \"..\"` so\n // `vite build` reads *this* file, and the managed bundle is built on the\n // developer's own machine — so `.env` being gitignored does not save\n // anyone. A stock scaffold deployed with `rebase cloud deploy` served\n // HTML that passed every health check while every XHR from the live site\n // went to the developer's laptop. `rebase cloud env set VITE_API_URL=…`\n // refuses build-time variables, so there was no recovery from the\n // platform side either.\n //\n // Empty is not a missing value, it is the correct one: the client falls\n // back to `window.location.origin`, which is the right shape for a\n // single-origin bundle and the only one that keeps working when a custom\n // domain is added. `rebase dev` injects the port it actually bound, so\n // local development is unaffected.\n // The template ships it blank, so this is belt-and-braces for a project\n // whose .env.example was edited or predates that change.\n envContent = envContent.replace(/^#?\\s*VITE_API_URL=.*$/m, \"VITE_API_URL=\");\n\n // Pin the runtime image `docker-compose.yml` pulls.\n //\n // The compose file reads `rebasepro/server:${REBASE_VERSION:-latest}`,\n // and unset it resolves to `latest` — a tag that moves under a running\n // deployment. That is precisely the hazard `cloudbuild-runtime.yaml`\n // designed away for the managed fleet, which pins releases by digest\n // because \"re-pushing a tag silently changes what the fleet is running\n // with no version anywhere changing\". A self-hoster deserves the same\n // guarantee, and it costs one line here.\n //\n // It also makes the compose header's upgrade instruction true: \"To\n // upgrade Rebase, change REBASE_VERSION and restart\" is a no-op while\n // the value is unset and the tag floats.\n const { tag: runtimeVersion, note } = resolveRuntimeImageTag(readCliVersion());\n const pinned = `${note ? `${note}\\n` : \"\"}REBASE_VERSION=${runtimeVersion}`;\n envContent = /^#?\\s*REBASE_VERSION=.*$/m.test(envContent)\n ? envContent.replace(/^#?\\s*REBASE_VERSION=.*$/m, pinned)\n : `${envContent.trimEnd()}\\n\\n# The Rebase runtime image tag docker-compose.yml pulls.\\n# Change this and restart to upgrade; your project bundle is untouched.\\n${pinned}\\n`;\n\n if (databaseUrl) {\n if (/[\\r\\n]/.test(databaseUrl)) {\n throw new Error(\"Invalid DATABASE_URL: multiline values are not allowed.\");\n }\n // A supplied URL used to be written through verbatim, which quietly\n // dropped the `search_path=public` pin the generated URL below has\n // always carried. Postgres defaults `search_path` to `\"$user\",\n // public`, and this project creates a `rebase` schema — so on any\n // database whose role happens to be named `rebase` (the name every\n // template, compose file and deployment doc uses), unqualified SQL\n // resolves to `$user` and the project's tables get created in\n // `rebase` instead of `public`. Pinning here makes the two branches\n // agree; a URL that already pins something keeps it.\n // Imported lazily: this is the only line in `init` that needs the\n // Postgres package, and a static import would load pg + drizzle on\n // every CLI invocation.\n const { pinSearchPath } = await import(\"@rebasepro/server-postgres\");\n const pinnedUrl = disableSslOnLoopback(pinSearchPath(databaseUrl));\n // DATABASE_PASSWORD is still written even though the URL points\n // elsewhere: docker-compose.yml interpolates it into both\n // POSTGRES_PASSWORD and the backend's own DATABASE_URL, defaulting\n // to `${DATABASE_PASSWORD:-changeme}`. Omitting it here shipped a\n // compose stack whose database password was literally \"changeme\",\n // on a service that publishes a host port by default.\n //\n // A replacer function, not a replacement string: in a string, `$$`,\n // `$&` and `$'` are patterns, and a password is free to contain\n // them — `pa$$w0rd` reached .env as `pa$w0rd`.\n envContent = envContent.replace(\n /^DATABASE_URL=.*$/m,\n () => `DATABASE_URL=${pinnedUrl}\\nDATABASE_PASSWORD=${dbPassword}`\n );\n } else {\n const dbPort = await chooseComposeDbPort(targetDirectory);\n envContent = envContent.replace(\n /^DATABASE_URL=.*$/m,\n // sslmode=disable: the paired docker-compose Postgres has no TLS,\n // and Go-based tooling (atlas, via `rebase db push`) defaults to\n // requiring SSL when the URL doesn't say otherwise.\n //\n // 127.0.0.1 rather than `localhost`: the name resolves to `::1`\n // first on macOS, so on a machine that already runs Postgres on\n // loopback the two addresses can reach two different servers.\n // An address cannot be ambiguous the way a name can.\n //\n // `%3D`, not a literal `=`, inside the `options` value. libpq\n // splits a URI query parameter on the FIRST `=` and rejects any\n // further one outright:\n // extra key/value separator \"=\" in URI query parameter: \"options\"\n // That is every libpq caller — `pg_dump`/`pg_restore` behind\n // `rebase db backup|restore`, and a plain `psql \"$DATABASE_URL\"`\n // copied out of this very file. It is not a version quirk; 15\n // through 18 all refuse it. node-postgres parses URLs itself and\n // accepts the literal form, which is why `rebase dev` and\n // `rebase db push` worked and this shipped broken for so long.\n // `pinSearchPath` (the --database-url branch above) has always\n // emitted the encoded form; this line is what disagreed.\n // Commented out, not omitted. `rebase dev` starts a managed\n // PostgreSQL when nothing names a database, which is what makes\n // a fresh project one command rather than four. The compose URL\n // stays here, one `#` away, because the alternative — a project\n // that mentions Docker nowhere — makes switching to it a\n // documentation lookup instead of an edit.\n // Deliberately short: the header copied from `.env.example`\n // sits directly above and already says what unset means. Saying\n // it twice, in the first section of the first file a new\n // developer opens, is how a generated file starts reading like\n // boilerplate nobody wrote on purpose.\n [\n \"# Commented out on purpose — that is what selects the managed database.\",\n \"# To use the docker-compose Postgres this project ships instead:\",\n \"# docker compose up -d db, then uncomment the line below.\",\n `# DATABASE_URL=postgresql://rebase_app:${dbPassword}@127.0.0.1:${dbPort}/rebase?options=-c%20search_path%3Dpublic&sslmode=disable`,\n `DATABASE_PASSWORD=${dbPassword}`\n ].join(\"\\n\")\n );\n // The SSL note further down illustrates `sslmode=disable` with a\n // full URL, and this file was copied from `.env.example` before the\n // rewrite below gives that one this project's port.\n envContent = envContent.replaceAll(\"@localhost:5432/rebase\", `@127.0.0.1:${dbPort}/rebase`);\n\n // `.env.example` is copied to `.env` and then kept, as the reference\n // that gets committed — so it is the one file in the project that\n // still carries the template's defaults after everything else has\n // been rewritten. Two of them are actively dangerous once the port\n // is derived per project:\n //\n // - its `DATABASE_URL` is UNCOMMENTED, contradicting the rule the\n // rest of the scaffold now states (\"unset means the managed\n // database\"), and\n // - it names `localhost:5432`, while this project's compose\n // publishes ${dbPort}.\n //\n // Copying `.env.example` to `.env` is a near-universal habit, and on\n // a machine that already runs Postgres on 5432 — most developer\n // machines — the result is not a connection failure. It is a\n // successful connection to somebody else's database, which boot will\n // then provision. Comment it out and give it this project's port.\n const envExampleReference = path.join(targetDirectory, \".env.example\");\n if (fs.existsSync(envExampleReference)) {\n let exampleContent = fs.readFileSync(envExampleReference, \"utf-8\");\n exampleContent = exampleContent.replace(\n /^DATABASE_URL=postgresql:\\/\\/[^\\n]*$/m,\n [\n \"# Commented out on purpose — that is what selects the managed database.\",\n \"# To use the docker-compose Postgres this project ships instead:\",\n \"# docker compose up -d db, then uncomment the line below in .env\",\n \"# (with DATABASE_PASSWORD from that file, not the placeholder here).\",\n `# DATABASE_URL=postgresql://rebase_app:changeme@127.0.0.1:${dbPort}/rebase?options=-c%20search_path%3Dpublic&sslmode=disable`\n ].join(\"\\n\")\n );\n // The prose further down illustrates `sslmode=disable` with a\n // full DSN. It is commented and not meant to be copied whole,\n // but a file that names two different ports for one database\n // teaches the reader to distrust both.\n exampleContent = exampleContent.replaceAll(\n \"@localhost:5432/rebase\",\n `@127.0.0.1:${dbPort}/rebase`\n );\n fs.writeFileSync(envExampleReference, exampleContent, \"utf-8\");\n }\n\n // Also update docker-compose.yml with the dynamic host port if it has the default 5432 port mapping\n const dockerComposePath = path.join(targetDirectory, \"docker-compose.yml\");\n if (fs.existsSync(dockerComposePath)) {\n let dockerComposeContent = fs.readFileSync(dockerComposePath, \"utf-8\");\n dockerComposeContent = dockerComposeContent.replace(\n /-\\s*\"5432:5432\"/g,\n `- \"${dbPort}:5432\"`\n );\n fs.writeFileSync(dockerComposePath, dockerComposeContent, \"utf-8\");\n }\n }\n\n // `mode` only applies when the file is created, and this one exists\n // already — so chmod as well, exactly as `writeStateFile` does.\n fs.writeFileSync(envPath, envContent, {\n encoding: \"utf-8\",\n mode: 0o600\n });\n fs.chmodSync(envPath, 0o600);\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA2BA,IAAM,eAAe;;AAGrB,IAAM,UAAU;;;;;;;AAQhB,SAAgB,qBACZ,MAAoD,QAAQ,KAC5D,UAAkB,WAAW,GACrB;CACR,MAAM,QAAQ,IAAI,yBAAyB;CAC3C,MAAM,WAAW,IAAI,QAAQ,GAAA,CAAI,MAAM,KAAK,SAAS;CAIrD,MAAM,OAAO,iBAAiB,KAAK,OAAO,IAAI,kBAAkB,YAAY;CAE5E,IAAI,MAAM,WAAW,OAAO,KAAK,QAAQ,MAAK,QAAO,aAAa,KAAK,GAAG,CAAC,GACvE,OAAO;EAAC;EAAQ;EAAO;CAAI;CAE/B,IAAI,MAAM,WAAW,MAAM,KAAK,IAAI,gBAAgB,UAAU,QAAQ,MAAK,QAAO,QAAQ,KAAK,GAAG,CAAC,GAC/F,OAAO;EAAC;EAAO;EAAM;CAAI;CAE7B,OAAO,CAAC,QAAQ;AACpB;;;AChCA,IAAM,SAAS,UAAU,GAAG,MAAM;AAIlC,IAAM,aAAa,cAAc,OAAO,KAAK,GAAG;AAChD,IAAM,YAAY,KAAK,QAAQ,UAAU;AAEzC,SAAS,cAAc,YAAoB,YAAmC;CAC1E,MAAM,OAAO,KAAK,MAAM,UAAU,CAAC,CAAC;CACpC,OAAO,cAAc,eAAe,MAAM;EACtC,IAAI,KAAK,SAAS,UAAU,MAAM,YAC9B,OAAO;EAEX,aAAa,KAAK,QAAQ,UAAU;CACxC;CACA,OAAO;AACX;AAEA,IAAM,UAAU,cAAc,WAAW,KAAK;AAE9C,IAAM,kBAAkB;;;;;;;;;;;;;;;;AAiBxB,IAAa,6BAA6B;CACtC;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACJ;;AAGA,SAAgB,oBAAoB,MAA6B;CAC7D,IAAI,CAAC,KAAK,KAAK,GAAG,OAAO;CACzB,IAAI,CAAC,gBAAgB,KAAK,IAAI,GAC1B,OAAO;CAEX,OAAO;AACX;;;;;;;;;;AAaA,IAAM,mBAA2E,CAC7E;CAAE,MAAM;CACZ,OAAO;CACP,OAAO;AAAkB,GACrB;CAAE,MAAM;CACZ,OAAO;CACP,OAAO;AAAe,CACtB;AAEA,IAAM,iBAAgF;CAClF;EAAE,MAAM;EACZ,OAAO;EACP,OAAO;CAAO;CACV;EAAE,MAAM;EACZ,OAAO;EACP,OAAO;CAAa;CAChB;EAAE,MAAM;EACZ,OAAO;EACP,OAAO;CAAQ;AACf;;;;;;;;AAqEA,SAAgB,mBAAmB,OAA8B;CAC7D,MAAM,WAAW;CACjB,IAAI;CACJ,IAAI;EACA,SAAS,IAAI,IAAI,MAAM,KAAK,CAAC;CACjC,QAAQ;EACJ,OAAO,sCAAsC;CACjD;CACA,IAAI,OAAO,aAAa,iBAAiB,OAAO,aAAa,aACzD,OAAO,sDAAsD,OAAO,SAAS,KAAK;CAEtF,OAAO;AACX;;AAGA,SAAS,qBAAqB,KAA4B;CACtD,MAAM,YAAY,2BAA2B,GAAG;CAChD,IAAI,UAAU,WAAW,GAAG,OAAO;CACnC,MAAM,QAAQ,UAAU,MAAM,GAAG,CAAC,CAAC,CAAC,KAAK,IAAI,KAAK,UAAU,SAAS,IAAI,SAAS,UAAU,SAAS,EAAE,SAAS;CAChH,OAAO,IAAI,KAAK,SAAS,GAAG,EAAE,mEAAmE,MAAM;AAC3G;AAEA,SAAgB,mBAAmB,QAA8C;CAC7E,MAAM,EAAE,SAAS,aAAa,aAAa,YAAY,gBAAgB,OAAO;CAC9E,MAAM,YAA4B,CAAC;CAEnC,IAAI,CAAC,SACD,UAAU,KAAK;EACX,MAAM;EACN,MAAM;EACN,SAAS;EACT,SAAS;EAGT,WAAW,UAAkB,oBAAoB,KAAK,KAC/C,qBAAqB,KAAK,QAAQ,QAAQ,IAAI,GAAG,KAAK,CAAC,KACvD;CACX,CAAC;CAGL,IAAI,gBAAgB,KAAA,GAChB,UAAU,KAAK;EACX,MAAM;EACN,MAAM;EACN,SAAS;EACT,SAAS;EACT,SAAS;CACb,CAAC;CAGL,IAAI,CAAC,aACD,UAAU,KAAK;EACX,MAAM;EACN,MAAM;EACN,SAAS;EACT,SAAS;EACT,SAAS;EAET,OAAO,YAAqC,EAAE,eAAe,QAAQ;CACzE,CAAC;CAGL,IAAI,CAAC,YACD,UAAU,KAAK;EACX,MAAM;EACN,MAAM;EACN,SAAS;EACT,SAAS;CACb,CAAC;CAGL,IAAI,CAAC,gBACD,UAAU,KAAK;EACX,MAAM;EACN,MAAM;EACN,SAAS,6BAA6B,GAAG;EACzC,SAAS;CACb,CAAC;CAGL,UAAU,KAAK;EACX,MAAM;EACN,MAAM;EACN,SAAS;EACT,SAAS;EACT,WAAW,UAAkB;GACzB,IAAI,CAAC,MAAM,KAAK,GAAG,OAAO;GAC1B,IAAI,SAAS,KAAK,KAAK,GACnB,OAAO;GAIX,MAAM,UAAU,mBAAmB,KAAK;GACxC,OAAO,UAAU,WAAW,QAAQ,8CAA8C;EACtF;CACJ,CAAC;CAED,UAAU,KAAK;EACX,MAAM;EACN,MAAM;EACN,SAAS;EACT,SAAS;EACT,OAAO,YAAqC,CAAC,CAAE,QAAQ,aAAwB,KAAK;CACxF,CAAC;CAED,OAAO;AACX;;;;;;;AAQA,SAAgB,eAAe,KAAa,iBAAiC;CACzE,OAAO,KAAK,SAAS,KAAK,eAAe;AAC7C;;;AAIA,SAAgB,gBAAsB;CAClC,QAAQ,IAAI;EACd,MAAM,KAAK,aAAa,EAAE;;EAE1B,MAAM,KAAK,OAAO,EAAE;gBACN,MAAM,KAAK,QAAQ,EAAE;;IAEjC,MAAM,KAAK,iFAAiF,EAAE;IAC9F,MAAM,KAAK,qFAAqF,EAAE;;EAEpG,MAAM,KAAK,SAAS,EAAE;IACpB,MAAM,KAAK,gBAAgB,EAAE,GAAG,MAAM,KAAK,UAAU,EAAE,8BAA8B,MAAM,KAAK,iBAAiB,EAAE;IACnH,MAAM,KAAK,YAAY,EAAE;IACzB,MAAM,KAAK,WAAW,EAAE,gCAAgC,MAAM,KAAK,6BAA6B,EAAE;8BACxE,MAAM,KAAK,yDAAyD,EAAE;8BACtE,MAAM,KAAK,6BAA6B,EAAE;IACpE,MAAM,KAAK,eAAe,EAAE;8BACF,MAAM,KAAK,8DAA8D,EAAE;IACrG,MAAM,KAAK,cAAc,EAAE;IAC3B,MAAM,KAAK,WAAW,EAAE;8BACE,MAAM,KAAK,iEAAiE,EAAE;IACxG,MAAM,KAAK,gBAAgB,EAAE,GAAG,MAAM,KAAK,OAAO,EAAE;IACpD,MAAM,KAAK,cAAc,EAAE,wDAAwD,MAAM,KAAK,6CAA6C,EAAE;IAC7I,MAAM,KAAK,WAAW,EAAE,GAAG,MAAM,KAAK,QAAQ,EAAE;IAChD,MAAM,KAAK,aAAa,EAAE,GAAG,MAAM,KAAK,OAAO,EAAE,sDAAsD,MAAM,KAAK,sBAAsB,EAAE;IAC1I,MAAM,KAAK,aAAa,EAAE,GAAG,MAAM,KAAK,QAAQ,EAAE;8BACxB,MAAM,KAAK,kCAAkC,WAAW,KAAK,KAAK,EAAE,OAAO,EAAE;8BAC7E,MAAM,KAAK,6DAA6D,EAAE;8BAC1E,MAAM,KAAK,0CAA0C,EAAE;;EAEnF,MAAM,KAAK,sBAAsB,EAAE;IACjC,MAAM,KAAK,SAAS,EAAE,+DAA+D,MAAM,KAAK,yBAAyB,EAAE;IAC3H,MAAM,KAAK,YAAY,EAAE,8CAA8C,MAAM,KAAK,iBAAiB,EAAE;gBACzF,MAAM,KAAK,6DAA6D,EAAE;;EAExF,MAAM,KAAK,UAAU,EAAE;IACrB,MAAM,KAAK,GAAG,EAAE;IAChB,MAAM,KAAK,GAAG,EAAE;IAChB,MAAM,KAAK,GAAG,EAAE;IAChB,MAAM,KAAK,GAAG,EAAE;CACnB;AACD;AAEA,eAAsB,gBAAgB,SAAmB;CACrD,IAAI,UAAU,OAAO,GAAG;EACpB,cAAc;EACd;CACJ;CAEA,QAAQ,IAAI;EACd,MAAM,KAAK,QAAQ,EAAE;CACtB;CAIG,MAAM,cAAc,MADE,iBAAiB,SAD5B,qBACqC,CAAE,CACvB;AAC/B;;AAGA,IAAa,aAAa;CACtB,SAAS;CACT,aAAa;CAMb,gBAAgB;CAChB,kBAAkB;CAClB,gBAAgB;CAChB,cAAc;CACd,cAAc;CACd,aAAa;CACb,eAAe;CACf,WAAW,CAAC,MAAM;CAClB,SAAS;CACT,MAAM;CACN,MAAM;CACN,MAAM;CACN,MAAM;CACN,MAAM;AACV;AAEA,eAAe,iBAAiB,SAAmB,IAA0C;CACzF,MAAM,EAAE,OAAO,MAAM,gBAAgB,iBAAiB;EAClD,MAAM;EACN;EACA,cAAc;EACd,SAAS;EACT,gBAAgB;CACpB,CAAC;CAMD,MAAM,UAAU,YAAY;CAC5B,MAAM,mBAAmB,KAAK,YAAY;CAM1C,IAAI,SAAS;EACT,MAAM,eAAe,KAAK,SAAS,KAAK,QAAQ,QAAQ,IAAI,GAAG,OAAO,CAAC;EACvE,MAAM,YAAY,oBAAoB,YAAY;EAClD,IAAI,WAAW;GACX,QAAQ,MAAM,MAAM,IAAI,yBAAyB,aAAa,KAAK,WAAW,CAAC;GAC/E,QAAQ,KAAK,CAAC;EAClB;EAGA,MAAM,kBAAkB,KAAK,QAAQ,QAAQ,IAAI,GAAG,OAAO;EAC3D,MAAM,YAAY,2BAA2B,eAAe;EAC5D,IAAI,UAAU,SAAS,GAAG;GACtB,KAAK,MAAM,QAAQ,wBAAwB,cAAc,eAAe,QAAQ,IAAI,GAAG,eAAe,GAAG,SAAS,GAC9G,QAAQ,MAAM,IAAI;GAEtB,QAAQ,KAAK,CAAC;EAClB;CACJ;CAEA,MAAM,iBAAiB,KAAK,iBAAiB,EAAE,KAAK;CACpD,IAAI,gBAAgB;EAChB,MAAM,UAAU,mBAAmB,cAAc;EACjD,IAAI,SAAS;GACT,QAAQ,MAAM,MAAM,IAAI,uBAAuB,QAAQ,EAAE,CAAC;GAC1D,QAAQ,MAAM,MAAM,KAAK,iFAAiF,CAAC;GAC3G,QAAQ,KAAK,CAAC;EAClB;CACJ;CAEA,MAAM,cAAc,KAAK;CACzB,IAAI,eAAe,CAAC,eAAe,MAAK,MAAK,EAAE,UAAU,WAAW,GAAG;EACnE,QAAQ,MAAM,MAAM,IAAI,qBAAqB,YAAY,gBAAgB,eAAe,KAAI,MAAK,EAAE,KAAK,CAAC,CAAC,KAAK,IAAI,GAAG,CAAC;EACvH,QAAQ,KAAK,CAAC;CAClB;CAEA,MAAM,cAAc,KAAK,kBAAkB,OAAO,OAAO,KAAA;CAGzD,MAAM,cAAc,kBAAkB,KAAK,cAAc,CAAC,CAAC;CAK3D,MAAM,YAAY,KAAK,oBAAoB;CAC3C,MAAM,cAAc,YAAY,QAAS,KAAK,gBAAgB;CAC9D,MAAM,iBAAiB,aAAa,KAAK,iBAAiB;CAE1D,IAAI,kBAAkB;EAElB,MAAM,cAAc,WAAW;EAC/B,MAAM,kBAAkB,KAAK,QAAQ,QAAQ,IAAI,GAAG,WAAW;EAC/D,MAAM,oBAAoB,KAAK,QAAQ,SAAU,aAAa,UAAU;EACxE,MAAM,aAAa,cAAc,EAAE;EAEnC,OAAO;GACH,aAAa,KAAK,SAAS,eAAe;GAC1C,KAAK,KAAK,YAAY;GACtB,aAAa;GACb;GACA;GACA,aAAa,KAAK,qBAAqB,KAAA;GACvC,YAAY,KAAK,mBAAmB;GACpC,QAAQ,eAAe;GACvB,gBAAgB,CAAC,CAAC;GAClB,UAAU,eAAe;GACzB;GACA;GACA,cAAc,KAAK,gBAAgB,KAAA;GACnC,UAAU,KAAK,kBAAkB,KAAA;GACjC,UAAU,gBAAgB,OAAO;GAGjC,QAAQ,eAAe,CAAC;GACxB,aAAa;EACjB;CACJ;CAKA,IAAI,CAAC,QAAQ,MAAM,OAAO;EACtB,MAAM,QAAQ,iBAAiB,SAAS,qBAAqB,CAAC;EAC9D,QAAQ,MAAM,MAAM,IAAI,6DAA6D,CAAC;EACtF,QAAQ,MAAM,MAAM,OAAO,oFAAoF,CAAC;EAChH,QAAQ,MAAM,MAAM,OAAO,OAAO,MAAM,SAAS,CAAC;EAClD,IAAI,MAAM,SAAS,SAAS,GACxB,QAAQ,MAAM,MAAM,KAAK,KAAK,WAAW,UAAU,MAAM,QAAQ,CAAC,EAAE,GAAG,MAAM,SAAS,WAAW,IAAI,OAAO,MAAM,oCAAoC,CAAC;EAE3J,QAAQ,MAAM,MAAM,KAAK,kHAAkH,CAAC;EAC5I,QAAQ,KAAK,CAAC;CAClB;CAEA,MAAM,YAAY,mBAAmB;EACjC;EACA;EACA;EACA,YAAY,CAAC,CAAC,KAAK;EACnB;EACA;CACJ,CAAC;CAGD,MAAM,UAAU,MAAM,SAAS,OAAO,SAAS;CAC/C,MAAM,SAAS,eAAe,MAAM,gBAAgB;CAEpD,MAAM,kBAAkB,KAAK,QAAQ,QAAQ,IAAI,GAAG,WAAW,QAAQ,WAAW;CAClF,MAAM,cAAc,KAAK,SAAS,eAAe;CACjD,MAAM,oBAAoB,KAAK,QAAQ,SAAU,aAAa,UAAU;CACxE,MAAM,aAAa,cAAc,EAAE;CAEnC,OAAO;EACH;EACA,KAAK,KAAK,YAAY,QAAQ,OAAO;EACrC,aAAa,YAAY,QAAS,KAAK,gBAAgB,QAAQ,eAAe;EAC9E;EACA;EACA,aAAc,QAAQ,aAAwB,KAAK,KAAK,KAAA;EACxD,YAAY,QAAQ,cAAc;EAClC,QAAQ,eAAgB,QAAQ,UAA6B;EAG7D,gBAAgB,CAAC,CAAC;EAClB,UAAU,eAAe,QAAQ,QAAQ,QAAQ;EACjD;EACA;EACA,cAAc,KAAK,gBAAgB,KAAA;EACnC,UAAU,KAAK,kBAAkB,KAAA;EACjC,UAAU,gBAAgB,OAAO;EACjC;EACA,aAAa;CACjB;AACJ;;;;;;;;;;;;;;;;;;AAmBA,SAAgB,iBACZ,SACA,YACuC;CACvC,MAAM,OAAO,QAAQ,MAAM,CAAC;CAC5B,MAAM,cAAc,KAAK,QAAQ,MAAM;CACvC,MAAM,2BAAW,IAAI,IAAY;CAEjC,MAAM,QAAQ,MAAc,UAA0B;EAClD,IAAI,SAAS,eAAe;GACxB,SAAS,IAAI,eAAe;GAC5B,OAAO;EACX;EACA,IAAI,SAAS,kBACT,OAAO,MAAM,QAAQ,oDAAoD,MAAM,WAAmB;GAC9F,SAAS,IAAI,gCAAgC;GAC7C,OAAO,GAAG,OAAO;EACrB,CAAC;EAEL,OAAO;CACX;CAEA,MAAM,QAAkB,CAAC;CACzB,KAAK,IAAI,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK;EAClC,IAAI,MAAM,aAAa;EACvB,MAAM,OAAO,KAAK;EAClB,MAAM,SAAS,0BAA0B,KAAK,IAAI;EAClD,IAAI,QAAQ;GACR,MAAM,KAAK,GAAG,OAAO,GAAG,GAAG,UAAU,KAAK,OAAO,IAAI,OAAO,EAAE,CAAC,GAAG;GAClE;EACJ;EACA,MAAM,WAAW,IAAI,KAAK,IAAI,MAAM,cAAc,KAAK,IAAI,KAAK;EAChE,MAAM,KAAK,UAAU,SAAS,WAAW,IAAI,IAAI,KAAK,UAAU,IAAI,IAAI,IAAI,CAAC;CACjF;CAEA,OAAO;EACH,SAAS;GAAC,GAAG;GAAY;GAAQ,GAAG;GAAO;EAAO,CAAC,CAAC,KAAK,GAAG;EAC5D,UAAU,CAAC,GAAG,QAAQ;CAC1B;AACJ;;AAGA,SAAS,UAAU,MAAsB;CACrC,OAAO,mBAAmB,KAAK,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ,MAAM,OAAO,EAAE;AAClF;AAEA,SAAS,UAAU,OAAkC;CACjD,OAAO,MAAM,UAAU,IAAI,MAAM,KAAK,EAAE,IAAI,GAAG,MAAM,MAAM,GAAG,EAAE,CAAC,CAAC,KAAK,IAAI,EAAE,OAAO,MAAM,MAAM,SAAS;AAC7G;AAEA,SAAS,WAAW,MAAsB;CACtC,OAAO,KAAK,OAAO,CAAC,CAAC,CAAC,YAAY,IAAI,KAAK,MAAM,CAAC;AACtD;;;;;;;;;;;AAYA,eAAe,oBAAoB,SAAqC;CACpE,IAAI,CAAC,QAAQ,gBAAgB,CAAC,QAAQ,UAAU;CAEhD,MAAM,YAAY,sBAAsB,MAAM,KAAK,oBAAoB,EAAE,QAAQ,MAAM,KAAK,mBAAmB,EAAE;CACjH,IAAI,CAAC,QAAQ,gBAAgB,CAAC,QAAQ,UAAU;EAC5C,QAAQ,KAAK,MAAM,OAAO,mEAAmE,CAAC;EAC9F,QAAQ,KAAK,MAAM,OAAO,KAAK,WAAW,CAAC;EAC3C;CACJ;CAEA,IAAI;EACA,MAAM,MAAM,MAAM,MAAM,GAAG,QAAQ,SAAS,oCAAoC;GAC5E,QAAQ;GACR,SAAS,EAAE,gBAAgB,mBAAmB;GAC9C,MAAM,KAAK,UAAU;IAAE,WAAW,QAAQ;IACtD,UAAU,QAAQ;GAAS,CAAC;EACpB,CAAC;EACD,MAAM,OAAQ,MAAM,IAAI,KAAK,CAAC,CAAC,aAAa,CAAC,EAAE;EAI/C,IAAI,CAAC,IAAI,MAAM,KAAK,SAAS,OAAO,KAAA,GAAW;GAC3C,QAAQ,KAAK,MAAM,OAAO,qCAAqC,KAAK,OAAO,WAAW,IAAI,YAAY,CAAC;GACvG,QAAQ,KAAK,MAAM,OAAO,KAAK,WAAW,CAAC;GAC3C;EACJ;EACA,UACI;GACI,KAAK,OAAO,QAAQ,QAAQ;GAC5B,WAAW,OAAO,KAAK,QAAQ,EAAE;GACjC,MAAM,KAAK,QAAQ;GACnB,aAAa,KAAK,QAAQ;EAC9B,GACA,QAAQ,eACZ;EACA,QAAQ,IAAI,EAAE;EACd,QAAQ,IAAI,KAAK,MAAM,MAAM,GAAG,EAAE,2BAA2B,MAAM,KAAK,KAAK,QAAQ,aAAa,QAAQ,YAAY,GAAG;CAC7H,SAAS,GAAG;EACR,QAAQ,KAAK,MAAM,OAAO,wCAAwC,aAAa,QAAQ,EAAE,UAAU,OAAO,CAAC,GAAG,CAAC;EAC/G,QAAQ,KAAK,MAAM,OAAO,KAAK,WAAW,CAAC;CAC/C;AACJ;;;;;;;;;;;;;;;;;;AAmBA,eAAe,eAAe,iBAAwC;CAClE,IAAI;EAIA,MAAM,MAAM,OAAO,CAAC,OAAO,IAAI,GAAG,EAAE,KAAK,gBAAgB,CAAC;EAI1D,IAAI,WAAmC,CAAC;EACxC,IAAI;GACA,MAAM,MAAM,OAAO,CAAC,UAAU,YAAY,GAAG,EAAE,KAAK,gBAAgB,CAAC;EACzE,QAAQ;GACJ,WAAW;IACP,iBAAiB;IACjB,kBAAkB;IAClB,oBAAoB;IACpB,qBAAqB;GACzB;EACJ;EACA,MAAM,MAAM,OAAO;GAAC;GAAU;GAAM;EAA4B,GAAG;GAC/D,KAAK;GACL,KAAK;EACT,CAAC;CACL,QAAQ;EACJ,QAAQ,KAAK,MAAM,OAAO,gDAAgD,CAAC;CAC/E;AACJ;;;;;;;;;;;;AA6BA,SAAgB,cAAc,OAAqC;CAC/D,MAAM,EAAE,UAAU,aAAa,YAAY,cAAc,WAAW,UAAU,OAAO;CACrF,MAAM,OAAO,UAA4B,KAAK,MAAM,KAAK,MAAM,KAAK,GAAG,CAAC;CACxE,MAAM,QAAQ,SAAyB,MAAM,KAAK,OAAO,MAAM;CAC/D,MAAM,SAAS,IAAI,GAAG,IAAI,KAAK,CAAC;CAChC,MAAM,QAAkB,CAAC;CAEzB,IAAI,UAAU,MAAM,KAAK,KAAK,MAAM,KAAK,IAAI,EAAE,GAAG,UAAU;CAC5D,IAAI,CAAC,WAAW,MAAM,KAAK,IAAI,GAAG,OAAO,CAAC;CAC1C,MAAM,KAAK,EAAE;CAEb,IAAI,cAAc;EACd,MAAM,KAAK,KAAK,yDAAyD,CAAC;EAC1E,MAAM,KAAK,KAAK,WACV,2BACA,oDAAoD,CAAC;EAC3D,MAAM,KAAK,MAAM;CACrB,OAAO,IAAI,YAAY;EAGnB,MAAM,KAAK,KAAK,6CAA6C,CAAC;EAC9D,MAAM,KAAK,IAAI,GAAG,KAAK,UAAU;GAAC;GAAU;GAAc;EAAS,CAAC,CAAC,CAAC;EACtE,MAAM,KAAK,IAAI,GAAG,KAAK,UAAU,CAAC,UAAU,UAAU,CAAC,CAAC,CAAC;EACzD,MAAM,KAAK,EAAE;EACb,MAAM,KAAK,KAAK,oCAAoC,CAAC;EACrD,MAAM,KAAK,MAAM;CACrB,OAAO,IAAI,UAAU;EAKjB,MAAM,KAAK,KAAK,cACV,wDACA,4DAA4D,CAAC;EACnE,MAAM,KAAK,KAAK,cACV,8BACA,sDAAsD,CAAC;EAC7D,MAAM,KAAK,MAAM;EACjB,MAAM,KAAK,EAAE;EACb,MAAM,KAAK,KAAK,cACV,0BACA,6DAA6D,CAAC;EACpE,MAAM,KAAK,KAAK,sEAAsE,CAAC;EACvF,MAAM,KAAK,KAAK,mCAAmC,CAAC;EACpD,MAAM,KAAK,IAAI,CAAC,mDAAmD,CAAC,CAAC;EAIrE,MAAM,KAAK,IAAI,CAAC,qFAAqF,CAAC,CAAC;EACvG,MAAM,KAAK,IAAI,CAAC,4FAA4F,CAAC,CAAC;EAC9G,MAAM,KAAK,KAAK,8CAA8C,CAAC;CACnE,OAAO,IAAI,aAAa;EACpB,MAAM,KAAK,KAAK,6CAA6C,CAAC;EAC9D,MAAM,KAAK,KAAK,oEAAoE,CAAC;EACrF,MAAM,KAAK,IAAI,GAAG,IAAI,SAAS,CAAC,CAAC;EACjC,MAAM,KAAK,EAAE;EACb,MAAM,KAAK,KAAK,oCAAoC,CAAC;EACrD,MAAM,KAAK,MAAM;CACrB,OAAO;EAeH,MAAM,KAAK,KAAK,4DAA4D,CAAC;EAC7E,MAAM,KAAK,KAAK,gEAAgE,CAAC;EACjF,MAAM,KAAK,KAAK,6CAA6C,CAAC;EAC9D,MAAM,KAAK,MAAM;EACjB,MAAM,KAAK,EAAE;EACb,MAAM,KAAK,KAAK,wEAAwE,CAAC;CAC7F;CAEA,MAAM,KAAK,EAAE;CASb,MAAM,KAAK,KAAK,4DAA4D,CAAC;CAC7E,MAAM,KAAK,KAAK,6EAA6E,CAAC;CAC9F,MAAM,KAAK,KAAK,wDAAwD,CAAC;CAEzE,MAAM,KAAK,EAAE;CAIb,MAAM,KAAK,WACL,eACI,MAAM,KAAK,2EAA2E,IAClF,MAAM,KAAK,mFAAmF,IAC9F,MAAM,KAAK,wCAAwC,IACvD,MAAM,KAAK,iFAAiF,IACxF,MAAM,KAAK,iGAAiG,IACpH,MAAM,KAAK,kDAAkD,IACzD,MAAM,KAAK,gDAAgD,CAAC;CACtE,OAAO;AACX;;;;;;;;;;;AAYA,SAAgB,eAAe,QAA6B,aAAsB,IAA0B;CACxG,MAAM,QAAQ,SACV,KAAK,MAAM,KAAK,CAAC,UAAU,GAAG,IAAI,CAAC,CAAC,KAAK,GAAG,CAAC,EAAE,IAAI,MAAM,KAAK,IAAI,EAAE,IAAI,MAAM,KAAK,GAAG,KAAK,UAAU,IAAI,CAAC,CAAC,KAAK,GAAG,CAAC;CACxH,IAAI,OAAO,SAAS,GAChB,OAAO;EACH,MAAM,KAAK,gBAAgB,OAAO,KAAI,MAAK,OAAO,EAAE,CAAC,KAAK,CAAC,CAAC,KAAK,IAAI,EAAE,kCAAkC;EACzG,MAAM,KAAK,oDAAoD;EAC/D;EACA,KAAK;GAAC;GAAU;GAAW;GAAW,OAAO,KAAK,GAAG;EAAC,CAAC;CAC3D;CAEJ,IAAI,aACA,OAAO;EACH,MAAM,KAAK,yFAAyF;EACpG;EACA,KAAK;GAAC;GAAU;GAAW;EAAO,CAAC;CACvC;CAEJ,OAAO;EACH,MAAM,KAAK,0FAA0F;EACrG,MAAM,KAAK,cAAc,WAAW,KAAK,IAAI,EAAE,UAAU;EACzD;EACA,KAAK;GAAC;GAAU;GAAW;GAAW;GAAW;EAAO,CAAC;CAC7D;AACJ;;;;;;;;;;;;;AAcA,IAAa,6CAAkD,IAAI,IAAI;CACnE;CAAQ;CAAO;CAAQ;CACvB;CAAa;CAAa;CAC1B;CAAW;CAAc;AAC7B,CAAC;;AAGD,SAAgB,2BAA2B,KAAuB;CAC9D,IAAI,CAAC,GAAG,WAAW,GAAG,GAAG,OAAO,CAAC;CACjC,OAAO,GAAG,YAAY,KAAK,EAAE,eAAe,KAAK,CAAC,CAAC,CAC9C,QAAO,UAAS,CAAC,2BAA2B,IAAI,MAAM,IAAI,CAAC,CAAC,CAC5D,KAAI,UAAS,MAAM,YAAY,IAAI,GAAG,MAAM,KAAK,KAAK,MAAM,IAAI,CAAC,CACjE,KAAK;AACd;;;;;;;AAQA,SAAgB,wBACZ,aACA,aACA,WACA,aAAgC,qBAAqB,GAC7C;CACR,MAAM,QAAQ,UAAU,MAAM,GAAG,CAAC,CAAC,CAAC,KAAK,IAAI,KAAK,UAAU,SAAS,IAAI,SAAS,UAAU,SAAS,EAAE,SAAS;CAChH,OAAO;EACH,GAAG,MAAM,IAAI,KAAK,OAAO,EAAE,cAAc,YAAY,mEAAmE;EACxH,MAAM,OAAO,8BAA8B,MAAM,KAAK;GAAC,GAAG;GAAY;GAAQ;EAAiB,CAAC,CAAC,KAAK,GAAG,CAAC,EAAE,0BAA0B,eAAe,iBAAiB,qBAAqB;EAC3L,MAAM,KAAK,gFAAgF;CAC/F;AACJ;;;;;;;;AASA,eAAsB,kBAAkB,iBAA2C;CAC/E,IAAI,GAAG,WAAW,KAAK,KAAK,iBAAiB,MAAM,CAAC,GAAG;EACnD,QAAQ,IAAI,MAAM,KAAK,wCAAwC,CAAC;EAChE,OAAO;CACX;CACA,QAAQ,IAAI,MAAM,KAAK,kCAAkC,CAAC;CAC1D,IAAI;EACA,MAAM,MAAM,OAAO,CAAC,MAAM,GAAG,EAAE,KAAK,gBAAgB,CAAC;EAKrD,IAAI;GACA,MAAM,MAAM,OAAO;IAAC;IAAgB;IAAQ;GAAiB,GAAG,EAAE,KAAK,gBAAgB,CAAC;EAC5F,QAAQ,CAER;EACA,OAAO;CACX,QAAQ;EACJ,QAAQ,KAAK,MAAM,OAAO,gDAAgD,CAAC;EAC3E,OAAO;CACX;AACJ;AAEA,eAAe,cAAc,SAAsB;CAC/C,MAAM,YAAY,KAAK,IAAI;CAC3B,MAAM,YAAY,2BAA2B,QAAQ,eAAe;CACpE,IAAI,UAAU,SAAS,GAAG;EACtB,KAAK,MAAM,QAAQ,wBAAwB,QAAQ,aAAa,eAAe,QAAQ,IAAI,GAAG,QAAQ,eAAe,GAAG,SAAS,GAC7H,QAAQ,MAAM,IAAI;EAEtB,QAAQ,KAAK,CAAC;CAClB;CACA,GAAG,UAAU,QAAQ,iBAAiB,EAAE,WAAW,KAAK,CAAC;CAGzD,IAAI;EACA,MAAM,OAAO,QAAQ,mBAAmB,GAAG,UAAU,IAAI;CAC7D,QAAQ;EACJ,QAAQ,MAAM,GAAG,MAAM,IAAI,KAAK,OAAO,EAAE,yBAAyB,QAAQ,mBAAmB;EAC7F,QAAQ,KAAK,CAAC;CAClB;CAGA,QAAQ,IAAI,MAAM,KAAK,4BAA4B,CAAC;CACpD,IAAI;EACA,MAAM,GAAG,QAAQ,mBAAmB,QAAQ,iBAAiB;GACzD,WAAW;GACX,SAAS,WAAmB;IACxB,MAAM,WAAW,KAAK,SAAS,MAAM;IAErC,OAAO,aAAa,kBAAkB,aAAa;GACvD;EACJ,CAAC;CACL,SAAS,KAAc;EACnB,QAAQ,MAAM,GAAG,MAAM,IAAI,KAAK,OAAO,EAAE,kCAAkC,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,GAAG;EAC7H,QAAQ,KAAK,CAAC;CAClB;CAKA,KAAK,MAAM,CAAC,MAAM,OAAO,CAAC,CAAC,aAAa,YAAY,GAAG,CAAC,SAAS,QAAQ,CAAC,GAAY;EAClF,MAAM,UAAU,KAAK,KAAK,QAAQ,iBAAiB,IAAI;EACvD,IAAI,GAAG,WAAW,OAAO,GACrB,GAAG,WAAW,SAAS,KAAK,KAAK,QAAQ,iBAAiB,EAAE,CAAC;CAErE;CAGA,IAAI,QAAQ,YAAY,QAAQ,gBAG5B,QAAQ,IAAI,MAAM,OAAO,yBAAyB,QAAQ,OAAO,8CAA8C,CAAC;CAEpH,IAAI,CAAC,QAAQ,UAAU;EAInB,IAAI,QAAQ,cAAc,QAAQ,WAAW,SACzC,QAAQ,IAAI,MAAM,KAAK,uEAAuE,CAAC;EAEnG,MAAM,UAAU,MAAM,YAAY,QAAQ,iBAAiB,QAAQ,aAAa,UAAU,QAAQ,MAAM;EACxG,mBAAmB,QAAQ,iBAAiB,OAAO;CACvD;CAGA,MAAM,cAAc,QAAQ,iBAAiB,QAAQ,QAAQ;CAG7D,MAAM,oBAAoB,OAAO;CAGjC,MAAM,iBAAiB,QAAQ,iBAAiB,QAAQ,WAAW;CAMnE,MAAM,uCAAuC,QAAQ,iBAAiB,QAAQ,UAAU;CAGxF,0BAA0B,QAAQ,iBAAiB,QAAQ,EAAE;CAG7D,IAAI,QAAQ,OAAO,SAAS,GAAG;EAC3B,QAAQ,IAAI,MAAM,KAAK,kCAAkC,CAAC;EAC1D,gBAAgB,gBAAgB,QAAQ,QAAQ,QAAQ,iBAAiB,KAAA,GAAW,QAAQ,EAAE,GAAG,QAAQ,EAAE;CAC/G;CAIA,MAAM,iBAAiB,QAAQ,MAAM,MAAM,kBAAkB,QAAQ,eAAe,IAAI;CAExF,MAAM,EAAE,IAAI,eAAe;CAC3B,MAAM,aAAa,WAAW;CAC9B,MAAM,UAAU,WAAW,KAAK,UAAU;EAAC;EAAU;EAAc;CAAS,CAAC;CAG7E,MAAM,cAAc,WAAW,KAAK,UAAU,CAAC,UAAU,UAAU,CAAC;CAGpE,IAAI,YAAY;CAChB,IAAI,QAAQ,aAAa;EACrB,QAAQ,IAAI,EAAE;EACd,QAAQ,IAAI,MAAM,KAAK,kCAAkC,GAAG,IAAI,CAAC;EACjE,QAAQ,IAAI,EAAE;EACd,IAAI;GACA,MAAM,MAAM,WAAW,IAAI,WAAW,MAAM,CAAC,GAAG;IAC5C,KAAK,QAAQ;IACb,OAAO;GACX,CAAC;GACD,YAAY;EAChB,QAAQ;GACJ,QAAQ,KAAK,MAAM,OAAO,oEAAoE,WAAW,KAAK,GAAG,EAAE,aAAa,CAAC;EACrI;CACJ;CAKA,IAAI,eAAe;CAEnB,IAAI,QAAQ,YAAY;EACpB,QAAQ,IAAI,EAAE;EACd,IAAI,QAAQ,aAAa;GACrB,QAAQ,IAAI,MAAM,KAAK,wDAAwD,CAAC;GAChF,QAAQ,IAAI,EAAE;GACd,IAAI;IAEA,MAAM,MAAM,QAAQ,IAAI,QAAQ,MAAM,CAAC,GAAG;KACtC,KAAK,QAAQ;KACb,OAAO;IACX,CAAC;IAID,MAAM,MAAM,YAAY,IAAI,YAAY,MAAM,CAAC,GAAG;KAC9C,KAAK,QAAQ;KACb,OAAO;IACX,CAAC;IACD,QAAQ,IAAI,MAAM,MAAM,uCAAuC,CAAC;IAChE,eAAe;GACnB,QAAQ;IACJ,QAAQ,KAAK,MAAM,OAAO,yDAAyD,CAAC;IACpF,QAAQ,KAAK,MAAM,OAAO,mBAAmB,QAAQ,KAAK,GAAG,EAAE,YAAY,YAAY,KAAK,GAAG,EAAE,yBAAyB,CAAC;GAC/H;EACJ,OAAO;GACH,QAAQ,KAAK,MAAM,OAAO,mEAAmE,CAAC;GAC9F,QAAQ,KAAK,MAAM,OAAO,WAAW,WAAW,KAAK,GAAG,EAAE,YAAY,QAAQ,KAAK,GAAG,EAAE,aAAa,CAAC;EAC1G;CACJ;CAEA,IAAI,gBAAgB,MAAM,eAAe,QAAQ,eAAe;CAEhE,MAAM,oBAAoB,OAAO;CAGjC,QAAQ,IAAI,EAAE;CACd,QAAQ,IAAI,GAAG,MAAM,MAAM,KAAK,GAAG,EAAE,WAAW,MAAM,KAAK,QAAQ,WAAW,EAAE,uBAAuB;CACvG,QAAQ,IAAI,EAAE;CACd,QAAQ,IAAI,MAAM,KAAK,aAAa,CAAC;CACrC,QAAQ,IAAI,EAAE;CACd,MAAM,YAAY,cAAc;EAC5B,UAAU,QAAQ;EAClB,aAAa,QAAQ,QAAQ,WAAW;EACxC,YAAY,QAAQ,QAAQ,UAAU;EACtC;EACA;EAIA,UAAU,eAAe,QAAQ,IAAI,GAAG,QAAQ,eAAe;EAC/D,IAAI;CACR,CAAC;CACD,KAAK,MAAM,QAAQ,WAAW,QAAQ,IAAI,IAAI;CAC9C,QAAQ,IAAI,EAAE;CACd,QAAQ,IAAI,MAAM,KAAK,+BAA+B,CAAC;CACvD,QAAQ,IAAI,MAAM,KAAK,6CAA6C,CAAC;CACrE,QAAQ,IAAI,EAAE;CACd,QAAQ,IAAI,MAAM,KAAK,oBAAoB,CAAC;CAC5C,QAAQ,IAAI,EAAE;CACd,KAAK,MAAM,QAAQ,eAAe,QAAQ,QAAQ,QAAQ,gBAAgB,MAAM,UAAU,GAAG,QAAQ,IAAI,IAAI;CAC7G,QAAQ,IAAI,EAAE;CASd,MAAM,iBAAiB;EACnB,QAAQ,QAAQ,WAAW,SAAS,QAAQ;EAC5C,UAAU,QAAQ,QAAQ,QAAQ;EAClC,iBAAiB,QAAQ;EACzB,gBAAgB,QAAQ,QAAQ,WAAW;EAC3C;EACA,cAAc,QAAQ,QAAQ,WAAW;EACzC,cAAc,QAAQ,QAAQ,YAAY;EAC1C,KAAK,QAAQ,QAAQ,GAAG;EACxB,UAAU,eAAe,KAAK,IAAI,IAAI,SAAS;CACnD;CAGA,IAAI,MAAM,iBAAiB,EAAE,eAAe,KAAK,CAAC,GAC9C,MAAM,YAAY,YAAY,gBAAgB,EAAE,aAAa,QAAQ,gBAAgB,CAAC;AAE9F;;;;;;;;;;;;;;;;;;;;;;;;AAyBA,eAAe,cAAc,iBAAyB,UAAkC;CACpF,IAAI,CAAC,UAAU;CAEf,GAAG,OAAO,KAAK,KAAK,iBAAiB,UAAU,GAAG;EAAE,WAAW;EACnE,OAAO;CAAK,CAAC;CAIT,GAAG,OAAO,KAAK,KAAK,iBAAiB,UAAU,aAAa,GAAG;EAAE,WAAW;EAChF,OAAO;CAAK,CAAC;CACT,KAAK,MAAM,SAAS,CAAC,YAAY,sBAAsB,GACnD,GAAG,OAAO,KAAK,KAAK,iBAAiB,UAAU,KAAK,GAAG,EAAE,OAAO,KAAK,CAAC;CAG1E,GAAG,OAAO,KAAK,KAAK,iBAAiB,WAAW,OAAO,qBAAqB,GAAG,EAAE,OAAO,KAAK,CAAC;CAE9F,MAAM,aAAa,KAAK,QAAQ,SAAU,aAAa,YAAY,MAAM;CACzE,IAAI,CAAC,GAAG,WAAW,UAAU,GAAG;EAC5B,QAAQ,MAAM,GAAG,MAAM,IAAI,KAAK,OAAO,EAAE,sCAAsC,YAAY;EAC3F,QAAQ,KAAK,CAAC;CAClB;CAEA,MAAM,GAAG,YAAY,iBAAiB;EAClC,WAAW;EACX,OAAO;EACP,SAAS,WAAmB;GACxB,MAAM,WAAW,KAAK,SAAS,MAAM;GACrC,OAAO,aAAa,kBAAkB,aAAa;EACvD;CACJ,CAAC;AACL;;;;;;;;;AAUA,eAAe,uCACX,iBACA,YACa;CACb,MAAM,eAAe,KAAK,KAAK,iBAAiB,aAAa;CAC7D,IAAI,CAAC,GAAG,WAAW,YAAY,GAAG;CAElC,IAAI;EACA,MAAM,WAAW,KAAK,MAAM,GAAG,aAAa,cAAc,MAAM,CAAC;EAGjE,IAAI,UAAU;EACd,KAAK,MAAM,OAAO,OAAO,OAAO,SAAS,QAAQ,CAAC,CAAC,GAAG;GAClD,IAAI,IAAI,SAAS,YAAY,CAAC,IAAI,QAAQ,CAAC,IAAI,OAAO;GACtD,MAAM,YAAY,WAAW,aAAa,IAAI,MAAM,OAAO,CAAC,CAAC,KAAK,GAAG;GACrE,IAAI,cAAc,IAAI,OAAO;IACzB,IAAI,QAAQ;IACZ,UAAU;GACd;EACJ;EACA,IAAI,SACA,GAAG,cAAc,cAAc,GAAG,KAAK,UAAU,UAAU,MAAM,CAAC,EAAE,KAAK,MAAM;CAEvF,QAAQ,CAGR;AACJ;;AAGA,eAAe,YAAY,iBAAyB,QAAiD;CACjG,MAAM,iBAAiB,KAAK,KAAK,iBAAiB,UAAU,aAAa;CACzE,MAAM,aAAa,KAAK,KAAK,gBAAgB,SAAS;CAEtD,IAAI,WAAW,QAAQ;EACnB,MAAM,YAAY,KAAK,KAAK,YAAY,MAAM;EAC9C,IAAI,CAAC,GAAG,WAAW,SAAS,GAAG;GAC3B,QAAQ,KAAK,MAAM,OAAO,sBAAsB,OAAO,4CAA4C,CAAC;GACpG,eAAe,UAAU;GACzB,OAAO;EACX;EAIA,KAAK,MAAM,QAAQ;GADA;GAAY;GAAc;GAAW;EACrC,GAAW;GAC1B,MAAM,WAAW,KAAK,KAAK,gBAAgB,IAAI;GAC/C,IAAI,GAAG,WAAW,QAAQ,GACtB,GAAG,WAAW,QAAQ;EAE9B;EAGA,MAAM,cAAc,GAAG,YAAY,SAAS,CAAC,CAAC,QAAO,MAAK,EAAE,SAAS,KAAK,CAAC;EAC3E,KAAK,MAAM,QAAQ,aACf,GAAG,aACC,KAAK,KAAK,WAAW,IAAI,GACzB,KAAK,KAAK,gBAAgB,IAAI,CAClC;CAER;CAGA,eAAe,UAAU;CACzB,OAAO;AACX;;;;;;;;;;;;;;;;;;;;;;;AAwBA,SAAgB,kBAAkB,QAAgC;CAC9D,OAAO,KAAK,QAAQ,SAAU,aAAa,qBAAqB,GAAG,OAAO,IAAI;AAClF;;AAGA,SAAS,mBAAmB,iBAAyB,QAA8B;CAC/E,MAAM,cAAc,KAAK,KAAK,iBAAiB,WAAW,OAAO,qBAAqB;CACtF,GAAG,UAAU,KAAK,QAAQ,WAAW,GAAG,EAAE,WAAW,KAAK,CAAC;CAC3D,GAAG,aAAa,kBAAkB,MAAM,GAAG,WAAW;AAC1D;AAEA,SAAS,eAAe,YAA0B;CAC9C,IAAI,GAAG,WAAW,UAAU,GACxB,GAAG,OAAO,YAAY;EAAE,WAAW;EAC3C,OAAO;CAAK,CAAC;AAEb;;AAgCA,IAAM,wBAAwB;AAE9B,eAAe,aAAa,SAAwC;CAChE,IAAI;EACA,MAAM,EAAE,WAAW,MAAM,MAAM,OAAO;GAAC;GAAQ,GAAG,sBAAsB,GAAG;GAAW;EAAS,CAAC;EAChG,IAAI,OAAO,KAAK,MAAM,SAAS,OAAO,EAAE,MAAM,YAAY;EAG1D,OAAO;GAAE,MAAM;GAAO,OAAO,OAAO,KAAK,KAAK;EAAU;CAC5D,SAAS,OAAO;EACZ,MAAM,OAAO,GAAI,MAA8B,UAAU,KAAM,MAAgB,WAAW;EAC1F,IAAI,6CAA6C,KAAK,IAAI,GACtD,OAAO;GAAE,MAAM;GAAO,OAAO;EAAU;EAE3C,OAAO;GAAE,MAAM;GAAe,QAAQ,UAAU,IAAI,KAAK;EAAoC;CACjG;AACJ;AAEA,SAAS,UAAU,MAAsB;CACrC,OAAO,KAAK,MAAM,IAAI,CAAC,CAAC,KAAI,MAAK,EAAE,KAAK,CAAC,CAAC,CAAC,OAAO,OAAO,CAAC,CAAC,MAAM;AACrE;AAEA,eAAe,oBAAoB,SAAsB;CACrD,MAAM,iBAAiB;CAIvB,MAAM,UAAU,WAAW;CAE3B,MAAM,+BAAe,IAAI,IAAoB;CAC7C,MAAM,8BAAc,IAAI,IAAY;CAEpC,KAAK,MAAM,QAAQ,gBAAgB;EAC/B,MAAM,WAAW,KAAK,QAAQ,QAAQ,iBAAiB,IAAI;EAC3D,IAAI,CAAC,GAAG,WAAW,QAAQ,GAAG;EAC9B,MAAM,UAAU,GAAG,aAAa,UAAU,OAAO;EACjD,aAAa,IAAI,UAAU,OAAO;EAElC,MAAM,UAAU,CAAC,GAAG,QAAQ,SAAS,0CAA0C,CAAC;EAChF,KAAK,MAAM,SAAS,SAAS,YAAY,IAAI,MAAM,EAAE;CACzD;CAKA,MAAM,QADY,gBAAgB,QAAQ,IAAI,UAAU,MAAM,QAAQ,YAAY,YAC1C,EAAE,MAAM,YAAY,IAAI,MAAM,aAAa,OAAO;CAE1F,IAAI,MAAM,SAAS,OACf,MAAM,IAAI,MACN,UAAU,QAAQ,qCACf,sBAAsB,UAAU,QAAQ,8BAA8B,MAAM,MAAM,6EACV,YAAY,KAAK;;;;oBAKvE,KAAK,SAAS,QAAQ,eAAe,EAAE,0DAEhE;CAGJ,IAAI,MAAM,SAAS,aACf,QAAQ,IAAI,MAAM,KAAK,aAAa,YAAY,KAAK,4BAA4B,QAAQ,IAAI,CAAC;CAGlG,IAAI,MAAM,SAAS,eAAe;EAC9B,QAAQ,IAAI,MAAM,KAAK,oCAAoC,QAAQ,uBAAuB,CAAC;EAC3F,QAAQ,IAAI,MAAM,OAAO,iDAAiD,MAAM,QAAQ,CAAC;EACzF,QAAQ,IAAI,MAAM,KAAK,gFAAgF,CAAC;CAC5G;CAEA,KAAK,MAAM,CAAC,UAAU,oBAAoB,aAAa,QAAQ,GAAG;EAC9D,IAAI,UAAU,gBAAgB,QAAQ,yBAAyB,QAAQ,WAAW;EAClF,KAAK,MAAM,WAAW,aAClB,UAAU,QAAQ,QACd,IAAI,OAAO,IAAI,QAAQ,wBAAwB,GAAG,GAClD,IAAI,QAAQ,MAAM,QAAQ,EAC9B;EAEJ,GAAG,cAAc,UAAU,SAAS,OAAO;CAC/C;AACJ;;AAIA,SAAS,QAAQ,MAAc,MAAiC;CAC5D,OAAO,IAAI,SAAS,YAAY;EAC5B,MAAM,SAAS,IAAI,aAAa;EAChC,OAAO,KAAK,UAAU,QAA+B;GAIjD,QAAQ,IAAI,SAAS,kBAAkB,IAAI,SAAS,eAAe;EACvE,CAAC;EACD,OAAO,KAAK,mBAAmB;GAC3B,OAAO,YAAY,QAAQ,IAAI,CAAC;EACpC,CAAC;EACD,IAAI,SAAS,KAAA,GAAW,OAAO,OAAO,IAAI;OACrC,OAAO,OAAO,MAAM,IAAI;CACjC,CAAC;AACL;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA8BA,eAAsB,gBAAgB,MAAgC;CAClE,OAAQ,MAAM,QAAQ,IAAI,KAAO,MAAM,QAAQ,MAAM,WAAW,KAAO,MAAM,QAAQ,MAAM,KAAK;AACpG;;AAGA,IAAM,wBAAwB;AAC9B,IAAM,uBAAuB;;;;;;;;;;;;;;AAe7B,SAAgB,mBAAmB,iBAAiC;CAEhE,OAAO,wBADQ,OAAO,WAAW,QAAQ,CAAC,CAAC,OAAO,KAAK,QAAQ,eAAe,CAAC,CAAC,CAAC,OACjD,CAAA,CAAO,aAAa,CAAC,IAAI;AAC7D;;;;;;;;;;AAWA,eAAsB,oBAClB,iBACA,SAGI,CAAC,GACU;CACf,MAAM,SAAS,OAAO,UAAU;CAChC,MAAM,UAAW,OAAO,OAAO,WAAW,mBAAA,CAAoB,qBAAM,IAAI,IAAY;CACpF,IAAI,OAAO,mBAAmB,eAAe;CAC7C,OAAO,QAAQ,IAAI,IAAI,KAAK,CAAE,MAAM,OAAO,IAAI,GAC3C;CAEJ,OAAO;AACX;;;;;;;;;;;AAYA,SAAS,iBAAyB;CAC9B,MAAM,UAAU,WAAW;CAC3B,OAAO,YAAY,YAAY,WAAW;AAC9C;;;;;;;;;;;;;;;;;;;AAoBA,SAAgB,uBAAuB,YAAoD;CAEvF,IAAI,kBAAkB,KAAK,UAAU,GACjC,OAAO;EACH,KAAK;EACL,MAAM,qCAAqC,WAAW;;CAG1D;CAEJ,OAAO,EAAE,KAAK,WAAW;AAC7B;;;;;;;;;;;;;AAcA,SAAS,YAAY,SAAiB,KAAa,OAAe,QAAyB;CACvF,MAAM,OAAO,IAAI,OAAO,UAAU,IAAI,OAAO,GAAG;CAChD,IAAI,KAAK,KAAK,OAAO,GAAG,OAAO,QAAQ,QAAQ,MAAM,GAAG,IAAI,GAAG,OAAO;CACtE,OAAO,GAAG,QAAQ,QAAQ,EAAE,MAAM,SAAS,GAAG,OAAO,MAAM,KAAK,IAAI,GAAG,MAAM;AACjF;;;;;;;;;;;;;;;;;;AAmBA,SAAgB,qBAAqB,kBAAkC;CACnE,IAAI;CACJ,IAAI;EACA,MAAM,IAAI,IAAI,gBAAgB;CAClC,QAAQ;EACJ,OAAO;CACX;CACA,IAAI,IAAI,aAAa,eAAe,IAAI,aAAa,eAAe,OAAO;CAC3E,IAAI;EAAC;EAAW;EAAO;EAAQ;CAAU,CAAC,CAAC,MAAK,UAAS,IAAI,aAAa,IAAI,KAAK,CAAC,GAAG,OAAO;CAG9F,MAAM,OAAO,IAAI,SAAS,QAAQ,cAAc,IAAI;CAEpD,IAAI,EADa,SAAS,eAAe,SAAS,SAAS,SAAS,aAAa,wBAAwB,KAAK,IAAI,IACnG,OAAO;CAItB,IAAI,SAAS,IAAI,SAAS,GAAG,IAAI,OAAO,oBAAoB;CAC5D,OAAO,IAAI,SAAS;AACxB;AAEA,eAAsB,iBAAiB,iBAAyB,aAAsB;CAClF,MAAM,iBAAiB,KAAK,KAAK,iBAAiB,cAAc;CAChE,MAAM,UAAU,KAAK,KAAK,iBAAiB,MAAM;CACjD,IAAI,GAAG,WAAW,cAAc,KAAK,CAAC,GAAG,WAAW,OAAO,GAAG;EAE1D,GAAG,aAAa,gBAAgB,OAAO;EASvC,GAAG,UAAU,SAAS,GAAK;EAG3B,MAAM,YAAY,OAAO,YAAY,EAAE,CAAC,CAAC,SAAS,KAAK;EACvD,MAAM,aAAa,OAAO,YAAY,EAAE,CAAC,CAAC,SAAS,KAAK;EACxD,MAAM,aAAa,OAAO,YAAY,EAAE,CAAC,CAAC,SAAS,QAAQ;EAO3D,MAAM,gBAAgB,OAAO,YAAY,EAAE,CAAC,CAAC,SAAS,WAAW;EAEjE,IAAI,aAAa,GAAG,aAAa,SAAS,OAAO;EAOjD,aAAa,WAAW,QACpB,sEACC,QAAQ,MAAc,KAAa,UAAkB;GAClD,MAAM,cAAc;GAEpB,MAAM,QAAQ,IAAI,SAAS,MAAM,SAAS;GAC1C,OAAO,OAAO,YAAY,MAAM,GAAG,KAAK,CAAC,CAAC,OAAO,OAAO,GAAG,IAAI;EACnE,CACJ;EAEA,aAAa,WAAW,QACpB,oBACA,cAAc,WAClB;EAKA,aAAa,WAAW,QACpB,gCACA,sBAAsB,YAC1B;EAKA,MAAM,iBAAiB,eAAe,KAAK,UAAU,CAAC,GAAG,MAAM;EAkB/D,aAAa,WAAW,QACpB,0BACA,iCAAiC,gBACrC;EAuBA,aAAa,YACT,YACA,sBACA,qBACA,iiBAQJ;EACA,aAAa,YAAY,YAAY,yBAAyB,aAAa;EAsB3E,aAAa,WAAW,QAAQ,2BAA2B,eAAe;EAe1E,MAAM,EAAE,KAAK,gBAAgB,SAAS,uBAAuB,eAAe,CAAC;EAC7E,MAAM,SAAS,GAAG,OAAO,GAAG,KAAK,MAAM,GAAG,iBAAiB;EAC3D,aAAa,4BAA4B,KAAK,UAAU,IAClD,WAAW,QAAQ,6BAA6B,MAAM,IACtD,GAAG,WAAW,QAAQ,EAAE,yIAAyI,OAAO;EAE9K,IAAI,aAAa;GACb,IAAI,SAAS,KAAK,WAAW,GACzB,MAAM,IAAI,MAAM,yDAAyD;GAc7E,MAAM,EAAE,kBAAkB,MAAM,OAAO;GACvC,MAAM,YAAY,qBAAqB,cAAc,WAAW,CAAC;GAWjE,aAAa,WAAW,QACpB,4BACM,gBAAgB,UAAU,sBAAsB,YAC1D;EACJ,OAAO;GACH,MAAM,SAAS,MAAM,oBAAoB,eAAe;GACxD,aAAa,WAAW,QACpB,sBAiCA;IACI;IACA;IACA;IACA,0CAA0C,WAAW,aAAa,OAAO;IACzE,qBAAqB;GACzB,CAAC,CAAC,KAAK,IAAI,CACf;GAIA,aAAa,WAAW,WAAW,0BAA0B,cAAc,OAAO,QAAQ;GAmB1F,MAAM,sBAAsB,KAAK,KAAK,iBAAiB,cAAc;GACrE,IAAI,GAAG,WAAW,mBAAmB,GAAG;IACpC,IAAI,iBAAiB,GAAG,aAAa,qBAAqB,OAAO;IACjE,iBAAiB,eAAe,QAC5B,yCACA;KACI;KACA;KACA;KACA;KACA,6DAA6D,OAAO;IACxE,CAAC,CAAC,KAAK,IAAI,CACf;IAKA,iBAAiB,eAAe,WAC5B,0BACA,cAAc,OAAO,QACzB;IACA,GAAG,cAAc,qBAAqB,gBAAgB,OAAO;GACjE;GAGA,MAAM,oBAAoB,KAAK,KAAK,iBAAiB,oBAAoB;GACzE,IAAI,GAAG,WAAW,iBAAiB,GAAG;IAClC,IAAI,uBAAuB,GAAG,aAAa,mBAAmB,OAAO;IACrE,uBAAuB,qBAAqB,QACxC,oBACA,MAAM,OAAO,OACjB;IACA,GAAG,cAAc,mBAAmB,sBAAsB,OAAO;GACrE;EACJ;EAIA,GAAG,cAAc,SAAS,YAAY;GAClC,UAAU;GACV,MAAM;EACV,CAAC;EACD,GAAG,UAAU,SAAS,GAAK;CAC/B;AACJ"}
|