[
  {
    "path": ".github/CONTRIBUTING.md",
    "content": "# Contributing to Better-T-Stack\n\nThank you for your interest in contributing to Better-T-Stack! This document provides guidelines and setup instructions for contributors.\n\n> **⚠️ Important**: Before starting work on any new features or major changes, please open an issue first to discuss your proposal and get approval. We don't want you to waste time on work that might not align with the project's direction or get merged.\n\n## Project Structure\n\nThis repository is organized as a monorepo containing:\n\n- **CLI**: [`apps/cli`](apps/cli) - The scaffolding CLI tool (`create-better-t-stack`)\n- **Documentation**: [`apps/web`](apps/web) - Official website and documentation\n\n## Development Setup\n\n### Prerequisites\n\n- Node.js (lts)\n- Bun (recommended)\n- Git\n\n### Initial Setup\n\n1. **Clone the repository**\n\n   ```bash\n   git clone https://github.com/AmanVarshney01/create-better-t-stack.git\n   cd create-better-t-stack\n   ```\n\n2. **Install dependencies**\n   ```bash\n   bun install\n   ```\n\n### CLI Development\n\n1. **Navigate to CLI directory**\n\n   ```bash\n   cd apps/cli\n   ```\n\n2. **Link the CLI globally** (optional, for testing anywhere in your system)\n\n   ```bash\n   bun link\n   ```\n\n   Now you can use `create-better-t-stack` from anywhere in your system.\n\n3. **Start development server**\n\n   ```bash\n   bun dev\n   ```\n\n   This runs tsdown build in watch mode, automatically rebuilding on changes.\n\n4. **Test the CLI**\n   Now go to anywhere else in your system (maybe like a test folder) and run:\n   ```bash\n   create-better-t-stack\n   ```\n   This will run the locally installed CLI.\n\n### Web Development\n\n1. **Install dependencies**\n\n   ```bash\n   # from repo root\n   bun i\n   ```\n\n2. **Setup backend**\n\n   ```bash\n   cd packages/backend\n   bun dev:setup  # you can choose local development too in prompts\n   ```\n\n3. **Configure environment**\n   Copy the Convex URL from `packages/backend/.env.local` to `apps/web/.env`:\n\n   ```\n   NEXT_PUBLIC_CONVEX_URL=http://127.0.0.1:3210/\n   ```\n\n4. **Set GitHub tokens**\n   Now run `bun dev` in the root. It will complain about GitHub token, so run this in `packages/backend`:\n\n   ```bash\n   npx convex env set GITHUB_ACCESS_TOKEN=xxxxx\n   npx convex env set GITHUB_WEBHOOK_SECRET=xxxxx\n   ```\n\n5. **Start the documentation website**\n   ```bash\n   bun dev\n   ```\n   This starts the Next.js development server for the documentation site.\n\n## Contribution Guidelines\n\n### Standard Contribution Steps\n\n1. **Create an issue** (if one doesn't exist)\n   - Describe the bug or feature request\n   - Include steps to reproduce (for bugs)\n   - Discuss the proposed solution\n\n2. **Fork the repository**\n   - Click the \"Fork\" button on GitHub\n   - Clone your fork locally\n\n3. **Create a feature branch**\n\n   ```bash\n   git checkout -b feature/your-feature-name\n   # or\n   git checkout -b fix/your-bug-fix\n   ```\n\n4. **Make your changes**\n   - Follow the existing code style\n   - Update documentation as needed\n\n5. **Test and format your changes** (see Testing section below)\n\n6. **Commit your changes**\n\n   ```bash\n   git add .\n   git commit -m \"feat(web): add your feature description\"\n   # or\n   git commit -m \"fix(cli): fix your bug description\"\n   ```\n\n7. **Push to your fork**\n\n   ```bash\n   git push origin feature/your-feature-name\n   ```\n\n8. **Create a Pull Request**\n   - Link to the related issue\n   - Describe your changes\n\n### Testing\n\n**Before committing, make sure to test your changes:**\n\n```bash\n# For CLI changes\ncd apps/cli\nbun dev\nbun run test\n\n# Lint and format files (from root, uses oxlint and oxfmt)\nbun run check\n```\n\n- **Manual testing**: Test your changes manually to ensure everything works as expected\n- For CLI changes: Test with different configurations and options\n- For web changes: Ensure the site builds and displays correctly\n\n## Commit Conventions\n\nUse conventional commit messages with the appropriate scope:\n\n- `feat(cli): add new CLI feature`\n- `fix(cli): fix CLI bug`\n- `feat(web): add new web feature`\n- `fix(web): fix web bug`\n- `chore(web): update dependencies`\n\n## Getting Help\n\n- Open an issue for bugs or feature requests\n- Join discussions for questions or ideas\n- Check existing issues and PRs for similar work\n- Join our [Discord](https://discord.gg/ZYsbjpDaM5) if you have any problems\n\n## License\n\nBy contributing to Better-T-Stack, you agree that your contributions will be licensed under the MIT License.\n"
  },
  {
    "path": ".github/FUNDING.yml",
    "content": "# These are supported funding model platforms\n\ngithub: [amanvarshney01]\npatreon: # Replace with a single Patreon username\nopen_collective: # Replace with a single Open Collective username\nko_fi: # Replace with a single Ko-fi username\ntidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel\ncommunity_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry\nliberapay: # Replace with a single Liberapay username\nissuehunt: # Replace with a single IssueHunt username\nlfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry\npolar: # Replace with a single Polar username\nbuy_me_a_coffee: # Replace with a single Buy Me a Coffee username\nthanks_dev: # Replace with a single thanks.dev username\ncustom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']\n"
  },
  {
    "path": ".github/workflows/pr-preview.yaml",
    "content": "name: PR Preview\n\non:\n  pull_request_target:\n    types: [labeled]\n\npermissions:\n  contents: read\n  pull-requests: write\n  id-token: write\n\nconcurrency:\n  group: pr-preview-${{ github.event.pull_request.number }}\n  cancel-in-progress: true\n\njobs:\n  publish-preview:\n    name: Publish Preview\n    runs-on: ubuntu-latest\n    if: github.event.label.name == 'preview'\n    steps:\n      - name: Checkout PR Code\n        uses: actions/checkout@v4\n        with:\n          ref: ${{ github.event.pull_request.head.sha }}\n          fetch-depth: 0\n\n      - name: Setup Node.js\n        uses: actions/setup-node@v4\n        with:\n          node-version-file: package.json\n          check-latest: true\n          registry-url: https://registry.npmjs.org\n\n      - name: Setup Bun\n        uses: oven-sh/setup-bun@v2\n        with:\n          bun-version: latest\n\n      - name: Install Dependencies\n        run: bun install --frozen-lockfile\n        env:\n          BTS_TELEMETRY: 0\n\n      - name: Verify NPM auth\n        run: npm whoami\n        env:\n          NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}\n\n      - name: Generate Preview Version\n        id: version\n        run: |\n          PR_NUMBER=${{ github.event.pull_request.number }}\n          COMMIT_SHA=$(echo \"${{ github.event.pull_request.head.sha }}\" | cut -c1-7)\n          BASE_VERSION=$(jq -r '.version' apps/cli/package.json | sed -E 's/^([0-9]+\\.[0-9]+\\.[0-9]+).*/\\1/')\n          PREVIEW_VERSION=\"${BASE_VERSION}-pr${PR_NUMBER}.${COMMIT_SHA}\"\n          NPM_TAG=\"pr${PR_NUMBER}\"\n\n          echo \"version=$PREVIEW_VERSION\" >> $GITHUB_OUTPUT\n          echo \"tag=$NPM_TAG\" >> $GITHUB_OUTPUT\n          echo \"pr=$PR_NUMBER\" >> $GITHUB_OUTPUT\n          echo \"commit=$COMMIT_SHA\" >> $GITHUB_OUTPUT\n\n          echo \"Preview version: $PREVIEW_VERSION\"\n          echo \"NPM tag: $NPM_TAG\"\n\n      - name: Update types package version\n        run: |\n          cd packages/types\n          jq --arg v \"${{ steps.version.outputs.version }}\" '.version = $v' package.json > tmp.json && mv tmp.json package.json\n\n      - name: Build types package\n        run: cd packages/types && bun run build\n\n      - name: Publish types to NPM\n        run: cd packages/types && npm publish --access public --provenance --tag ${{ steps.version.outputs.tag }}\n        env:\n          NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}\n          BTS_TELEMETRY: 0\n\n      - name: Update template-generator package version and types dependency\n        run: |\n          cd packages/template-generator\n          jq --arg v \"${{ steps.version.outputs.version }}\" '.version = $v | .dependencies[\"@better-t-stack/types\"] = $v' package.json > tmp.json && mv tmp.json package.json\n\n      - name: Build template-generator package\n        run: cd packages/template-generator && bun run build\n\n      - name: Publish template-generator to NPM\n        run: cd packages/template-generator && npm publish --access public --provenance --tag ${{ steps.version.outputs.tag }}\n        env:\n          NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}\n          BTS_TELEMETRY: 0\n\n      - name: Update CLI package version and dependencies\n        run: |\n          cd apps/cli\n          jq --arg v \"${{ steps.version.outputs.version }}\" '.version = $v | .dependencies[\"@better-t-stack/types\"] = $v | .dependencies[\"@better-t-stack/template-generator\"] = $v' package.json > tmp.json && mv tmp.json package.json\n\n      - name: Update create-bts alias package version\n        run: |\n          cd packages/create-bts\n          jq --arg v \"${{ steps.version.outputs.version }}\" '.version = $v | .dependencies[\"create-better-t-stack\"] = $v' package.json > tmp.json && mv tmp.json package.json\n\n      - name: Build CLI\n        run: cd apps/cli && bun run build\n        env:\n          BTS_TELEMETRY: 0\n\n      - name: Publish CLI to NPM\n        run: cd apps/cli && npm publish --access public --provenance --tag ${{ steps.version.outputs.tag }}\n        env:\n          NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}\n          BTS_TELEMETRY: 0\n\n      - name: Publish create-bts to NPM\n        run: cd packages/create-bts && npm publish --access public --provenance --tag ${{ steps.version.outputs.tag }}\n        env:\n          NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}\n          BTS_TELEMETRY: 0\n\n      - name: Find existing preview comment\n        uses: peter-evans/find-comment@v3\n        id: find-comment\n        with:\n          issue-number: ${{ github.event.pull_request.number }}\n          comment-author: \"github-actions[bot]\"\n          body-includes: \"PR Preview Release\"\n\n      - name: Create or update PR comment\n        uses: peter-evans/create-or-update-comment@v4\n        with:\n          comment-id: ${{ steps.find-comment.outputs.comment-id }}\n          issue-number: ${{ github.event.pull_request.number }}\n          edit-mode: replace\n          body: |\n            ## PR Preview Release\n\n            A preview version has been published for this PR.\n\n            | Package | Version | NPM Tag |\n            |---------|---------|---------|\n            | `create-better-t-stack` | `${{ steps.version.outputs.version }}` | `pr${{ steps.version.outputs.pr }}` |\n            | `create-bts` | `${{ steps.version.outputs.version }}` | `pr${{ steps.version.outputs.pr }}` |\n            | `@better-t-stack/types` | `${{ steps.version.outputs.version }}` | `pr${{ steps.version.outputs.pr }}` |\n            | `@better-t-stack/template-generator` | `${{ steps.version.outputs.version }}` | `pr${{ steps.version.outputs.pr }}` |\n\n            **Commit:** `${{ steps.version.outputs.commit }}`\n\n            ### Install\n\n            ```bash\n            # Using the PR tag (always gets latest for this PR)\n            bunx create-better-t-stack@pr${{ steps.version.outputs.pr }}\n            npx create-better-t-stack@pr${{ steps.version.outputs.pr }}\n\n            # Using exact version\n            bunx create-better-t-stack@${{ steps.version.outputs.version }}\n            npx create-better-t-stack@${{ steps.version.outputs.version }}\n\n            # Using the alias\n            bunx create-bts@pr${{ steps.version.outputs.pr }}\n            npx create-bts@pr${{ steps.version.outputs.pr }}\n            ```\n\n            ### NPM Links\n            - [create-better-t-stack@${{ steps.version.outputs.version }}](https://www.npmjs.com/package/create-better-t-stack/v/${{ steps.version.outputs.version }})\n            - [create-bts@${{ steps.version.outputs.version }}](https://www.npmjs.com/package/create-bts/v/${{ steps.version.outputs.version }})\n            - [@better-t-stack/types@${{ steps.version.outputs.version }}](https://www.npmjs.com/package/@better-t-stack/types/v/${{ steps.version.outputs.version }})\n            - [@better-t-stack/template-generator@${{ steps.version.outputs.version }}](https://www.npmjs.com/package/@better-t-stack/template-generator/v/${{ steps.version.outputs.version }})\n\n            ---\n            *To publish a new preview after more commits, remove and re-add the `preview` label.*\n"
  },
  {
    "path": ".github/workflows/release.yaml",
    "content": "name: Release\n\non:\n  push:\n    branches:\n      - main\n\npermissions:\n  contents: write\n  pull-requests: write\n  id-token: write\n\nconcurrency:\n  group: release-${{ github.ref }}\n  cancel-in-progress: false\n\njobs:\n  release:\n    name: Release to NPM\n    if: startsWith(github.event.head_commit.message, 'chore(release):')\n    runs-on: ubuntu-latest\n    timeout-minutes: 15\n    steps:\n      - name: Checkout Code\n        uses: actions/checkout@v4\n        with:\n          fetch-depth: 0\n\n      - name: Setup Node.js\n        uses: actions/setup-node@v4\n        with:\n          node-version-file: package.json\n          check-latest: true\n          registry-url: https://registry.npmjs.org\n\n      - name: Extract version from commit message\n        id: version\n        run: |\n          VERSION=$(echo \"${{ github.event.head_commit.message }}\" | sed -E 's/^chore\\(release\\): ([0-9]+\\.[0-9]+\\.[0-9]+).*/\\1/')\n          echo \"version=$VERSION\" >> $GITHUB_OUTPUT\n          echo \"Releasing version: $VERSION\"\n\n      - name: Validate version format\n        run: |\n          VERSION=\"${{ steps.version.outputs.version }}\"\n          if [[ ! \"$VERSION\" =~ ^[0-9]+\\.[0-9]+\\.[0-9]+$ ]]; then\n            echo \"Error: Invalid version format '$VERSION'. Expected semver (x.y.z)\"\n            exit 1\n          fi\n\n      - name: Check if version already exists\n        id: check-version\n        run: |\n          VERSION=\"${{ steps.version.outputs.version }}\"\n          if npm view create-better-t-stack@$VERSION version 2>/dev/null; then\n            echo \"Version $VERSION already exists on NPM\"\n            echo \"exists=true\" >> $GITHUB_OUTPUT\n          else\n            echo \"Version $VERSION is new\"\n            echo \"exists=false\" >> $GITHUB_OUTPUT\n          fi\n\n      - name: Setup Bun\n        if: steps.check-version.outputs.exists == 'false'\n        uses: oven-sh/setup-bun@v2\n        with:\n          bun-version: latest\n\n      - name: Install Dependencies\n        if: steps.check-version.outputs.exists == 'false'\n        run: bun install --frozen-lockfile\n\n      - name: Verify NPM auth\n        if: steps.check-version.outputs.exists == 'false'\n        run: npm whoami\n        env:\n          NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}\n\n      - name: Update types package version\n        if: steps.check-version.outputs.exists == 'false'\n        run: |\n          VERSION=\"${{ steps.version.outputs.version }}\"\n          cd packages/types\n          jq --arg v \"$VERSION\" '.version = $v' package.json > tmp.json && mv tmp.json package.json\n\n      - name: Build types package\n        if: steps.check-version.outputs.exists == 'false'\n        run: cd packages/types && bun run build\n\n      - name: Update template-generator package version and types dependency\n        if: steps.check-version.outputs.exists == 'false'\n        run: |\n          VERSION=\"${{ steps.version.outputs.version }}\"\n          cd packages/template-generator\n          jq --arg v \"$VERSION\" --arg dep \"^$VERSION\" '.version = $v | .dependencies[\"@better-t-stack/types\"] = $dep' package.json > tmp.json && mv tmp.json package.json\n\n      - name: Build template-generator package\n        if: steps.check-version.outputs.exists == 'false'\n        run: cd packages/template-generator && bun run build\n\n      - name: Update CLI types and template-generator dependencies and version\n        if: steps.check-version.outputs.exists == 'false'\n        run: |\n          VERSION=\"${{ steps.version.outputs.version }}\"\n          cd apps/cli\n          jq --arg v \"^$VERSION\" '.dependencies[\"@better-t-stack/types\"] = $v | .dependencies[\"@better-t-stack/template-generator\"] = $v' package.json > tmp.json && mv tmp.json package.json\n\n      - name: Build CLI\n        if: steps.check-version.outputs.exists == 'false'\n        run: cd apps/cli && bun run build\n        env:\n          BTS_TELEMETRY: 1\n          CONVEX_INGEST_URL: ${{ secrets.CONVEX_INGEST_URL }}\n\n      - name: Update create-bts alias package version\n        if: steps.check-version.outputs.exists == 'false'\n        run: |\n          VERSION=\"${{ steps.version.outputs.version }}\"\n          cd packages/create-bts\n          jq --arg v \"$VERSION\" --arg dep \"^$VERSION\" '.version = $v | .dependencies[\"create-better-t-stack\"] = $dep' package.json > tmp.json && mv tmp.json package.json\n\n      - name: Enable pnpm via corepack\n        if: steps.check-version.outputs.exists == 'false'\n        run: corepack enable pnpm\n\n      # Gate all publishes on the smoke so a failure here doesn't leave a\n      # partial release on npm (e.g. types/template-generator published but\n      # create-better-t-stack broken).\n      - name: Publish smoke test (npm, pnpm, bun)\n        if: steps.check-version.outputs.exists == 'false'\n        run: bun run smoke:publish\n\n      - name: Publish types to NPM\n        if: steps.check-version.outputs.exists == 'false'\n        run: cd packages/types && npm publish --access public --provenance\n        env:\n          NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}\n          BTS_TELEMETRY: 1\n          CONVEX_INGEST_URL: ${{ secrets.CONVEX_INGEST_URL }}\n\n      - name: Publish template-generator to NPM\n        if: steps.check-version.outputs.exists == 'false'\n        run: cd packages/template-generator && npm publish --access public --provenance\n        env:\n          NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}\n          BTS_TELEMETRY: 1\n          CONVEX_INGEST_URL: ${{ secrets.CONVEX_INGEST_URL }}\n\n      - name: Publish CLI to NPM\n        if: steps.check-version.outputs.exists == 'false'\n        run: cd apps/cli && npm publish --access public --provenance\n        env:\n          NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}\n          BTS_TELEMETRY: 1\n          CONVEX_INGEST_URL: ${{ secrets.CONVEX_INGEST_URL }}\n\n      - name: Publish create-bts alias to NPM\n        if: steps.check-version.outputs.exists == 'false'\n        run: cd packages/create-bts && npm publish --access public --provenance\n        env:\n          NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}\n          BTS_TELEMETRY: 1\n          CONVEX_INGEST_URL: ${{ secrets.CONVEX_INGEST_URL }}\n\n      - name: Create and push tag\n        if: steps.check-version.outputs.exists == 'false'\n        run: |\n          VERSION=\"${{ steps.version.outputs.version }}\"\n          git config --local user.email \"amanvarshney.work@gmail.com\"\n          git config --local user.name \"Aman Varshney\"\n          if ! git rev-parse \"v$VERSION\" >/dev/null 2>&1; then\n            git tag -a \"v$VERSION\" -m \"Release v$VERSION\"\n            git push --tags\n          else\n            echo \"Tag v$VERSION already exists, skipping\"\n          fi\n\n      - name: Create GitHub Release\n        if: steps.check-version.outputs.exists == 'false'\n        run: |\n          VERSION=\"${{ steps.version.outputs.version }}\"\n          if ! gh release view \"v$VERSION\" >/dev/null 2>&1; then\n            bunx changelogithub\n          else\n            echo \"Release v$VERSION already exists, skipping\"\n          fi\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n\n      - name: Release Summary\n        if: steps.check-version.outputs.exists == 'false'\n        run: |\n          VERSION=\"${{ steps.version.outputs.version }}\"\n          echo \"## Release v$VERSION Complete!\" >> $GITHUB_STEP_SUMMARY\n          echo \"\" >> $GITHUB_STEP_SUMMARY\n          echo \"### Published Packages\" >> $GITHUB_STEP_SUMMARY\n          echo \"- [create-better-t-stack@$VERSION](https://www.npmjs.com/package/create-better-t-stack/v/$VERSION)\" >> $GITHUB_STEP_SUMMARY\n          echo \"- [create-bts@$VERSION](https://www.npmjs.com/package/create-bts/v/$VERSION)\" >> $GITHUB_STEP_SUMMARY\n          echo \"- [@better-t-stack/types@$VERSION](https://www.npmjs.com/package/@better-t-stack/types/v/$VERSION)\" >> $GITHUB_STEP_SUMMARY\n          echo \"- [@better-t-stack/template-generator@$VERSION](https://www.npmjs.com/package/@better-t-stack/template-generator/v/$VERSION)\" >> $GITHUB_STEP_SUMMARY\n          echo \"\" >> $GITHUB_STEP_SUMMARY\n          echo \"### GitHub Release\" >> $GITHUB_STEP_SUMMARY\n          echo \"[v$VERSION](https://github.com/${{ github.repository }}/releases/tag/v$VERSION)\" >> $GITHUB_STEP_SUMMARY\n"
  },
  {
    "path": ".github/workflows/test.yaml",
    "content": "name: Test\n\non:\n  pull_request:\n    types: [opened, synchronize, reopened]\n\nconcurrency:\n  group: test-${{ github.head_ref }}\n  cancel-in-progress: true\n\njobs:\n  test:\n    name: Test Suite\n    runs-on: ubuntu-latest\n    timeout-minutes: 15\n    steps:\n      - name: Checkout Code\n        uses: actions/checkout@v4\n\n      - name: Setup Node.js\n        uses: actions/setup-node@v4\n        with:\n          node-version-file: package.json\n          check-latest: true\n\n      - name: Setup Git user\n        run: |\n          git config --global user.name \"github-actions[bot]\"\n          git config --global user.email \"41898282+github-actions[bot]@users.noreply.github.com\"\n\n      - name: Setup Bun\n        uses: oven-sh/setup-bun@v2\n        with:\n          bun-version: latest\n\n      - name: Install Dependencies\n        run: bun install --frozen-lockfile\n        env:\n          BTS_TELEMETRY: 0\n\n      - name: Build Types\n        run: cd packages/types && bun run build\n\n      - name: Build Template Generator\n        run: cd packages/template-generator && bun run build\n\n      - name: Build and Test CLI\n        working-directory: apps/cli\n        run: bun run build && bun run test:ci\n        env:\n          AGENT: 1\n          BTS_TELEMETRY: 0\n\n      - name: Enable pnpm via corepack\n        run: corepack enable pnpm\n\n      - name: Publish smoke test (npm, pnpm, bun)\n        run: bun run smoke:publish\n"
  },
  {
    "path": ".gitignore",
    "content": "# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.\n\n# Dependencies\nnode_modules\n.pnp\n.pnp.js\n\n# Local env files\n.env\n.env.local\n.env.development.local\n.env.test.local\n.env.production.local\n\n# Testing\ncoverage\n\n# Turbo\n.turbo\n\n# Vercel\n.vercel\n\n# Build Outputs\nout/\nbuild\ndist\n\n\n# Debug\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\n\n# Misc\n.DS_Store\n*.pem\n.vscode\n.env*.local\n\n.smoke\n\n.idea\n\ntemplates-binary\n\n# opensrc - source code for packages\nopensrc/\n"
  },
  {
    "path": ".oxfmtrc.json",
    "content": "{\n  \"$schema\": \"./node_modules/oxfmt/configuration_schema.json\",\n  \"experimentalSortImports\": {\n    \"order\": \"asc\"\n  },\n  \"experimentalSortPackageJson\": true,\n  \"ignorePatterns\": [\n    \"*.hbs\",\n    \"apps/cli/templates/**\",\n    \"packages/backend/convex/_generated/**\",\n    \"packages/template-generator/src/templates.generated.ts\"\n  ]\n}\n"
  },
  {
    "path": "AGENTS.md",
    "content": "# Repository Guidelines\n\n## Project Structure & Module Organization\n\nThis repo is a Bun + Turborepo monorepo.\n\n- `apps/cli`: published CLI (`create-better-t-stack`), with source in `apps/cli/src` and tests in `apps/cli/test`.\n- `apps/web`: Next.js docs/site (`apps/web/src`, `apps/web/content/docs`, `apps/web/public`).\n- `packages/template-generator`: template generation engine used by the CLI.\n- `packages/types`: shared schemas/types.\n- `packages/backend`: Convex backend used by web features.\n\n## Build, Test, and Development Commands\n\n- `bun install`: install workspace dependencies.\n- `bun dev:cli`: watch-build CLI package.\n- `bun dev:web`: run web app locally (`next dev --port 3333`).\n- `bun build`: build all packages/apps through Turbo.\n- `bun build:cli`: build only the CLI target.\n- `bun run check`: format + lint (`oxfmt . && oxlint .`).\n- `cd apps/cli && bun run test`: run CLI tests.\n\n## Coding Style & Naming Conventions\n\n- Language: TypeScript (strict mode enabled across projects).\n- Modules: ESM-first (`\"type\": \"module\"` where applicable).\n- Formatting/linting: `oxfmt` and `oxlint`; run `bun run check` before committing.\n- File naming: prefer kebab-case files (for example `database-setup.ts`).\n- Symbols: `camelCase` for functions/variables, `PascalCase` for types/components.\n- Keep feature logic near domain folders (`helpers`, `utils`, `template-handlers`).\n\n## Error Handling Conventions\n\n- In CLI code, prefer `better-result` over ad-hoc `try/catch` for recoverable flows.\n- Return typed `Result<T, E>` and use `Result.ok`, `Result.err`, `Result.try`, and `Result.tryPromise`.\n- Reuse domain errors from `apps/cli/src/utils/errors.ts` (`CLIError`, `ProjectCreationError`, `UserCancelledError`) and convert thrown prompt errors at boundaries.\n\n## Template Authoring (Handlebars)\n\n- Templates live in `packages/template-generator/templates` and use helpers from `packages/template-generator/src/core/template-processor.ts` (`eq`, `ne`, `and`, `or`, `includes`).\n- For conditional ORM-specific output, use helper form with quoted values:\n  - `{{#if (eq orm \"prisma\")}}`\n  - `{{else if (eq orm \"drizzle\")}}`\n  - `{{/if}}`\n  - Example: `packages/template-generator/templates/packages/infra/alchemy.run.ts.hbs`.\n- When files must contain literal `{{ ... }}` (Vue/JSX/template syntax), escape opening braces as `\\{{` in `.hbs` files so Handlebars does not evaluate them.\n  - Example: `packages/template-generator/templates/frontend/nuxt/app/pages/index.vue.hbs`.\n\n## Testing Guidelines\n\n- Framework: `bun:test`.\n- Test files use `*.test.ts` naming (see `apps/cli/test` and `packages/template-generator/test`).\n- Add or update tests with behavior changes, especially prompt flows, template output, and config validation.\n- Keep tests deterministic; reuse shared setup utilities in `apps/cli/test/setup.ts`.\n\n## Commit & Pull Request Guidelines\n\n- Use Conventional Commits with scope, matching history:\n  - `feat(cli): ...`, `fix(web): ...`, `docs(cli): ...`\n- Open an issue/discussion before major feature work.\n- PRs should include:\n  - clear summary,\n  - linked issue (if applicable),\n  - verification steps run (`bun run check`, relevant tests),\n  - screenshots/GIFs for web UI changes.\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2025 Better T Stack\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# Better-T-Stack\n\nA modern CLI tool for scaffolding end-to-end type-safe TypeScript projects with best practices and customizable configurations\n\n<br />\n<a href=\"https://vercel.com/oss\">\n  <img alt=\"Vercel OSS Program\" src=\"https://vercel.com/oss/program-badge.svg\" />\n</a>\n\n## Sponsors\n\n<p align=\"center\">\n<img src=\"https://sponsors.amanv.dev/sponsors.png\" alt=\"Sponsors\">\n</p>\n\n![demo](https://github.com/user-attachments/assets/12fd4d67-8494-462a-8124-76670798308a)\n\n## Philosophy\n\n- Roll your own stack: you pick only the parts you need, nothing extra.\n- Minimal templates: bare-bones scaffolds with zero bloat.\n- Latest dependencies: always use current, stable versions by default.\n- Free and open source: forever.\n\n## Quick Start\n\n```bash\n# Using bun (recommended)\nbun create better-t-stack@latest\n\n# Using pnpm\npnpm create better-t-stack@latest\n\n# Using npm\nnpx create-better-t-stack@latest\n```\n\n## Features\n\n- Frontend: React (TanStack Router, React Router, TanStack Start), Next.js, Nuxt, Svelte, Solid, Astro, React Native (Bare, NativeWind, Unistyles), or none\n- Backend: Hono, Express, Fastify, Elysia, Self (fullstack web app), Convex, or none\n- API: tRPC or oRPC (or none)\n- Runtime: Bun, Node.js, or Cloudflare Workers\n- Databases: SQLite, PostgreSQL, MySQL, MongoDB (or none)\n- ORMs: Drizzle, Prisma, Mongoose (or none)\n- Auth: Better Auth or Clerk (optional)\n- Addons: Turborepo, Nx, PWA, Tauri, Electrobun, Biome, Lefthook, Husky, Starlight, Fumadocs, Ultracite, Oxlint, MCP, OpenTUI, WXT, Skills\n- Examples: Todo, AI\n- DB Setup: Turso, Neon, Supabase, Prisma PostgreSQL, MongoDB Atlas, Cloudflare D1, Docker\n- Web Deploy: Cloudflare Workers\n\nType safety end-to-end, clean monorepo layout, and zero lock-in: you choose only what you need.\n\n## Repository Structure\n\nThis repository is organized as a monorepo containing:\n\n- **CLI**: [`apps/cli`](apps/cli) - The scaffolding CLI tool\n- **Documentation**: [`apps/web`](apps/web) - Official website and documentation\n\n## Documentation\n\nVisit [better-t-stack.dev](https://better-t-stack.dev) for full documentation, guides, and examples. You can also use the visual Stack Builder at `https://better-t-stack.dev/new` to generate a command for your stack.\n\n## Development\n\n```bash\n# Clone the repository\ngit clone https://github.com/AmanVarshney01/create-better-t-stack.git\n\n# Install dependencies\nbun install\n\n# Start CLI development\nbun dev:cli\n\n# Start website development\nbun dev:web\n```\n\n## Want to contribute?\n\nPlease read the Contribution Guide first and open an issue before starting new features to ensure alignment with project goals.\n\n- Docs: [`./apps/web/content/docs/contributing.mdx`](./apps/web/content/docs/contributing.mdx)\n- Repo guide: [`./.github/CONTRIBUTING.md`](./.github/CONTRIBUTING.md)\n\n## Star History\n\n<a href=\"https://www.star-history.com/#AmanVarshney01/create-better-t-stack&Date\">\n <picture>\n   <source media=\"(prefers-color-scheme: dark)\" srcset=\"https://api.star-history.com/svg?repos=AmanVarshney01/create-better-t-stack&type=Date&theme=dark\" />\n   <source media=\"(prefers-color-scheme: light)\" srcset=\"https://api.star-history.com/svg?repos=AmanVarshney01/create-better-t-stack&type=Date\" />\n   <img alt=\"Star History Chart\" src=\"https://api.star-history.com/svg?repos=AmanVarshney01/create-better-t-stack&type=Date\" />\n </picture>\n</a>\n"
  },
  {
    "path": "apps/cli/.gitignore",
    "content": "/node_modules\n/dist\n.smoke"
  },
  {
    "path": "apps/cli/README.md",
    "content": "# Create Better-T-Stack CLI\n\nA modern CLI tool for scaffolding end-to-end type-safe TypeScript projects with best practices and customizable configurations\n\n## Sponsors\n\n<p align=\"center\">\n<img src=\"https://sponsors.amanv.dev/sponsors.png\" alt=\"Sponsors\">\n</p>\n\n![demo](https://cdn.jsdelivr.net/gh/amanvarshney01/create-better-t-stack@master/demo.gif)\n\n## Quick Start\n\nRun without installing globally:\n\n```bash\n# Using bun (recommended)\nbun create better-t-stack@latest\n\n# Using pnpm\npnpm create better-t-stack@latest\n\n# Using npm\nnpx create-better-t-stack@latest\n```\n\nFollow the prompts to configure your project or use the `--yes` flag for defaults.\n\n## Features\n\n| Category                 | Options                                                                                                                                                                                                                                                                                                                                                                                                                 |\n| ------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| **TypeScript**           | End-to-end type safety across all parts of your application                                                                                                                                                                                                                                                                                                                                                             |\n| **Frontend**             | • React with TanStack Router<br>• React with React Router<br>• React with TanStack Start (SSR)<br>• Next.js<br>• SvelteKit<br>• Nuxt (Vue)<br>• SolidJS<br>• Astro<br>• React Native bare Expo<br>• React Native with NativeWind (via Expo)<br>• React Native with Unistyles (via Expo)<br>• None                                                                                                                       |\n| **Backend**              | • Hono<br>• Express<br>• Elysia<br>• Fastify<br>• Self (fullstack inside the web app)<br>• Convex<br>• None                                                                                                                                                                                                                                                                                                             |\n| **API Layer**            | • tRPC (type-safe APIs)<br>• oRPC (OpenAPI-compatible type-safe APIs)<br>• None                                                                                                                                                                                                                                                                                                                                         |\n| **Runtime**              | • Bun<br>• Node.js<br>• Cloudflare Workers<br>• None                                                                                                                                                                                                                                                                                                                                                                    |\n| **Database**             | • SQLite<br>• PostgreSQL<br>• MySQL<br>• MongoDB<br>• None                                                                                                                                                                                                                                                                                                                                                              |\n| **ORM**                  | • Drizzle (TypeScript-first)<br>• Prisma (feature-rich)<br>• Mongoose (for MongoDB)<br>• None                                                                                                                                                                                                                                                                                                                           |\n| **Database Setup**       | • Turso (SQLite)<br>• Cloudflare D1 (SQLite)<br>• Neon (PostgreSQL)<br>• Supabase (PostgreSQL)<br>• Prisma Postgres<br>• MongoDB Atlas<br>• None (manual setup)                                                                                                                                                                                                                                                         |\n| **Authentication**       | • Better Auth<br>• Clerk                                                                                                                                                                                                                                                                                                                                                                                                |\n| **Styling**              | Tailwind CSS with a shared shadcn/ui package for React web apps                                                                                                                                                                                                                                                                                                                                                         |\n| **Addons**               | • PWA support<br>• Tauri (desktop applications)<br>• Electrobun (lightweight desktop shell)<br>• Starlight and Fumadocs (documentation sites)<br>• Biome, Oxlint, Ultracite (linting and formatting)<br>• Lefthook, Husky (Git hooks)<br>• evlog (request logging for server/fullstack backends)<br>• MCP, Skills (agent tooling)<br>• OpenTUI, WXT (platform extensions)<br>• Turborepo or Nx (monorepo orchestration) |\n| **Examples**             | • Todo app<br>• AI Chat interface (using Vercel AI SDK)                                                                                                                                                                                                                                                                                                                                                                 |\n| **Developer Experience** | • Automatic Git initialization<br>• Package manager choice (npm, pnpm, bun)<br>• Automatic dependency installation                                                                                                                                                                                                                                                                                                      |\n\n## Usage\n\n```bash\nUsage: create-better-t-stack [project-directory] [options]\n\nOptions:\n  -V, --version                   Output the version number\n  -y, --yes                       Use default configuration\n  --template <type>               Use a template (mern, pern, t3, uniwind, none)\n  --database <type>               Database type (none, sqlite, postgres, mysql, mongodb)\n  --orm <type>                    ORM type (none, drizzle, prisma, mongoose)\n  --dry-run                       Validate configuration without writing files\n  --auth <provider>               Authentication (better-auth, clerk, none)\n  --payments <provider>           Payments provider (polar, none)\n  --frontend <types...>           Frontend types (tanstack-router, react-router, tanstack-start, next, nuxt, svelte, solid, astro, native-bare, native-uniwind, native-unistyles, none)\n  --addons <types...>             Additional addons (pwa, tauri, electrobun, starlight, biome, lefthook, husky, mcp, turborepo, nx, fumadocs, ultracite, oxlint, opentui, wxt, skills, evlog, none)\n  --examples <types...>           Examples to include (todo, ai, none)\n  --git                           Initialize git repository\n  --no-git                        Skip git initialization\n  --package-manager <pm>          Package manager (npm, pnpm, bun)\n  --install                       Install dependencies\n  --no-install                    Skip installing dependencies\n  --db-setup <setup>              Database setup (turso, d1, neon, supabase, prisma-postgres, planetscale, mongodb-atlas, docker, none)\n  --web-deploy <setup>            Web deployment (cloudflare, none)\n  --server-deploy <setup>         Server deployment (cloudflare, none)\n  --backend <framework>           Backend framework (hono, express, fastify, elysia, convex, self, none)\n  --runtime <runtime>             Runtime (bun, node, workers, none)\n  --api <type>                    API type (trpc, orpc, none)\n  --directory-conflict <strategy> Directory strategy (merge, overwrite, increment, error)\n  --manual-db                     Skip automatic database setup prompts\n  -h, --help                      Display help\n```\n\n### Agent-Focused Commands\n\n```bash\n# Raw JSON payload input (agent-friendly)\ncreate-better-t-stack create-json --input '{\"projectName\":\"my-app\",\"yes\":true,\"dryRun\":true}'\ncreate-better-t-stack add-json --input '{\"projectDir\":\"./my-app\",\"addons\":[\"wxt\"],\"addonOptions\":{\"wxt\":{\"template\":\"react\"}}}'\ncreate-better-t-stack create-json --input '{\"projectName\":\"db-app\",\"database\":\"postgres\",\"orm\":\"drizzle\",\"dbSetup\":\"neon\",\"dbSetupOptions\":{\"mode\":\"manual\"}}'\n\n# Runtime schema/introspection output\ncreate-better-t-stack schema --name all\ncreate-better-t-stack schema --name createInput\ncreate-better-t-stack schema --name addInput\ncreate-better-t-stack schema --name addonOptions\ncreate-better-t-stack schema --name dbSetupOptions\ncreate-better-t-stack schema --name cli\n\n# Local stdio MCP server\nnpx create-better-t-stack@latest mcp\n```\n\nTo install Better T Stack into supported agent configs with `add-mcp` and avoid relying on a global CLI install:\n\n```bash\nnpx -y add-mcp@latest \"npx -y create-better-t-stack@latest mcp\"\n```\n\nWhen you scaffold with the `mcp` addon, Better T Stack itself can also be installed into supported agent configs through `add-mcp` using a package runner command instead of assuming a global CLI install. For Bun projects, the generated config uses the equivalent `bunx create-better-t-stack@latest mcp` server command inside `add-mcp`.\n\nFor MCP project creation, prefer `install: false`. Long dependency installs can exceed common MCP client request timeouts, so the safest flow is to scaffold first and run your package manager install command afterward in the project directory.\n\n## Telemetry\n\nThis CLI collects anonymous usage data to help improve the tool. The data collected includes:\n\n- Configuration options selected\n- CLI version\n- Node.js version\n- Platform (OS)\n\n**Telemetry is enabled by default in published versions** to help us understand usage patterns and improve the tool.\n\n### Disabling Telemetry\n\nYou can disable telemetry by setting the `BTS_TELEMETRY_DISABLED` environment variable:\n\n```bash\n# Disable telemetry for a single run\nBTS_TELEMETRY_DISABLED=1 npx create-better-t-stack\n\n# Disable telemetry globally in your shell profile (.bashrc, .zshrc, etc.)\nexport BTS_TELEMETRY_DISABLED=1\n```\n\n## Examples\n\nCreate a project with default configuration:\n\n```bash\nnpx create-better-t-stack --yes\n```\n\nValidate a command without writing files:\n\n```bash\nnpx create-better-t-stack --yes --dry-run\n```\n\nCreate a project with specific options:\n\n```bash\nnpx create-better-t-stack --database postgres --orm drizzle --auth better-auth --addons pwa biome\n```\n\nCreate a project with Elysia backend and Node.js runtime:\n\n```bash\nnpx create-better-t-stack --backend elysia --runtime node\n```\n\nCreate a project with multiple frontend options (one web + one native):\n\n```bash\nnpx create-better-t-stack --frontend tanstack-router native-bare\n```\n\nCreate a project with examples:\n\n```bash\nnpx create-better-t-stack --examples todo ai\n```\n\nCreate a project with Turso database setup:\n\n```bash\nnpx create-better-t-stack --database sqlite --orm drizzle --db-setup turso\n```\n\nCreate a project with Supabase PostgreSQL setup:\n\n```bash\nnpx create-better-t-stack --database postgres --orm drizzle --db-setup supabase --auth better-auth\n```\n\nCreate a project with Convex backend:\n\n```bash\nnpx create-better-t-stack --backend convex --frontend tanstack-router\n```\n\nCreate a project with documentation site:\n\n```bash\nnpx create-better-t-stack --addons starlight\n```\n\nCreate a minimal TypeScript project with no backend:\n\n```bash\nnpx create-better-t-stack --backend none --frontend tanstack-router\n```\n\nCreate a backend-only project with no frontend:\n\n```bash\nnpx create-better-t-stack --frontend none --backend hono --database postgres --orm drizzle\n```\n\nCreate a simple frontend-only project:\n\n```bash\nnpx create-better-t-stack --backend none --frontend next --addons none --examples none\n```\n\nCreate a Cloudflare Workers project:\n\n```bash\nnpx create-better-t-stack --backend hono --runtime workers --database sqlite --orm drizzle --db-setup d1\n```\n\nCreate a self-hosted fullstack project on Cloudflare with D1:\n\n```bash\nnpx create-better-t-stack --backend self --frontend next --api trpc --database sqlite --orm drizzle --db-setup d1 --web-deploy cloudflare\n```\n\nCreate a minimal API-only project:\n\n```bash\nnpx create-better-t-stack --frontend none --backend hono --api trpc --database none --addons none\n```\n\n## Compatibility Notes\n\n- **Convex backend**: Requires `database`, `orm`, `api`, `runtime`, and `server-deploy` to be `none`; auth can be `better-auth`, `clerk`, or `none` depending frontend compatibility\n- **Backend 'none'**: If selected, this option will force related options like API, ORM, database, authentication, and runtime to 'none'. Examples will also be disabled (set to none/empty).\n- **Frontend 'none'**: Creates a backend-only project. When selected, PWA, Tauri, Electrobun, and certain examples may be disabled.\n- **API 'none'**: Disables tRPC/oRPC setup. Can be used with backend frameworks for REST APIs or custom API implementations.\n- **Database 'none'**: Disables database setup and requires ORM to be `none`.\n- **ORM 'none'**: Can be used when you want to handle database operations manually or use a different ORM.\n- **Runtime 'none'**: Only available with Convex backend, backend `none`, or backend `self`.\n- **Cloudflare Workers runtime**: Only compatible with Hono backend. If a database is used, MongoDB is not supported.\n- **Cloudflare D1 setup**: Requires `sqlite` and either `--runtime workers --server-deploy cloudflare` or `--backend self --web-deploy cloudflare`. For `backend self`, D1 is supported on `next`, `tanstack-start`, `nuxt`, `svelte`, and `astro`.\n- **Addons 'none'**: Skips all addons.\n- **Examples 'none'**: Skips all example implementations (todo, AI chat).\n- **Nuxt, Svelte, SolidJS, and Astro** frontends are only compatible with oRPC API layer\n- **PWA support** requires TanStack Router, React Router, Next.js, or SolidJS\n- **Tauri desktop app** requires TanStack Router, React Router, TanStack Start, Next.js, Nuxt, SvelteKit, SolidJS, or Astro\n- **Electrobun desktop app** requires TanStack Router, React Router, TanStack Start, Next.js, Nuxt, SvelteKit, SolidJS, or Astro. Desktop packaging uses static web assets, so SSR-first frontends need a static/export build before desktop builds will work.\n- **AI example** is not compatible with Solid or Astro. With Convex backend, it also excludes Nuxt and Svelte.\n\n## Project Structure\n\nThe created project follows a clean monorepo structure:\n\n```\nmy-better-t-app/\n├── apps/\n│   ├── web/          # Frontend application\n│   ├── server/       # Backend API\n│   ├── native/       # (optional) Mobile application\n│   └── docs/         # (optional) Documentation site\n├── packages/         # Shared packages\n└── README.md         # Auto-generated project documentation\n```\n\nAfter project creation, you'll receive detailed instructions for next steps and additional setup requirements.\n"
  },
  {
    "path": "apps/cli/bunfig.toml",
    "content": "[test]\n# Preload setup file for global setup/teardown\npreload = [\"./test/setup.ts\"]\n\n# Per-test timeout (3 minutes for smoke tests)\ntimeout = 180000\n\n# Skip test files from coverage reports\ncoverageSkipTestFiles = true\n\n# Exclude patterns from coverage\ncoveragePathIgnorePatterns = [\"test/**\", \"dist/**\", \"templates/**\", \"node_modules/**\"]\n"
  },
  {
    "path": "apps/cli/package.json",
    "content": "{\n  \"name\": \"create-better-t-stack\",\n  \"version\": \"3.28.0\",\n  \"description\": \"A modern CLI tool for scaffolding end-to-end type-safe TypeScript projects with best practices and customizable configurations\",\n  \"keywords\": [\n    \"better-auth\",\n    \"better-t-stack\",\n    \"biome\",\n    \"boilerplate\",\n    \"cli\",\n    \"drizzle\",\n    \"elysia\",\n    \"expo\",\n    \"fullstack\",\n    \"hono\",\n    \"monorepo\",\n    \"prisma\",\n    \"pwa\",\n    \"react\",\n    \"react-native\",\n    \"shadcn\",\n    \"starter\",\n    \"tailwind\",\n    \"tanstack\",\n    \"tauri\",\n    \"trpc\",\n    \"turborepo\",\n    \"type-safety\",\n    \"typescript\"\n  ],\n  \"homepage\": \"https://better-t-stack.dev/\",\n  \"license\": \"MIT\",\n  \"author\": \"Aman Varshney\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/AmanVarshney01/create-better-t-stack.git\",\n    \"directory\": \"apps/cli\"\n  },\n  \"bin\": {\n    \"create-better-t-stack\": \"dist/cli.mjs\"\n  },\n  \"files\": [\n    \"dist\"\n  ],\n  \"type\": \"module\",\n  \"exports\": {\n    \".\": {\n      \"types\": \"./dist/index.d.mts\",\n      \"import\": \"./dist/index.mjs\"\n    },\n    \"./cli\": {\n      \"import\": \"./dist/cli.mjs\"\n    },\n    \"./virtual\": {\n      \"types\": \"./dist/virtual.d.mts\",\n      \"import\": \"./dist/virtual.mjs\"\n    }\n  },\n  \"publishConfig\": {\n    \"access\": \"public\"\n  },\n  \"scripts\": {\n    \"build\": \"tsdown --publint\",\n    \"dev\": \"tsdown --watch\",\n    \"check-types\": \"tsc --noEmit\",\n    \"test\": \"bun run build && bun test\",\n    \"test:watch\": \"bun run build && bun test --watch\",\n    \"test:coverage\": \"bun run build && bun test --coverage\",\n    \"test:ci\": \"bun run build && CI=1 bun test --bail=5\",\n    \"prepublishOnly\": \"npm run build\"\n  },\n  \"dependencies\": {\n    \"@better-t-stack/template-generator\": \"workspace:*\",\n    \"@better-t-stack/types\": \"workspace:*\",\n    \"@clack/core\": \"^1.2.0\",\n    \"@clack/prompts\": \"^1.2.0\",\n    \"@modelcontextprotocol/sdk\": \"1.29.0\",\n    \"@trpc/server\": \"^11.16.0\",\n    \"better-result\": \"^2.8.2\",\n    \"consola\": \"^3.4.2\",\n    \"env-paths\": \"^4.0.0\",\n    \"execa\": \"^9.6.1\",\n    \"fs-extra\": \"^11.3.4\",\n    \"gradient-string\": \"^3.0.0\",\n    \"handlebars\": \"^4.7.9\",\n    \"jsonc-parser\": \"^3.3.1\",\n    \"oxfmt\": \"^0.46.0\",\n    \"picocolors\": \"^1.1.1\",\n    \"tinyglobby\": \"^0.2.15\",\n    \"trpc-cli\": \"^0.14.0\",\n    \"ts-morph\": \"^28.0.0\",\n    \"yaml\": \"^2.8.3\",\n    \"zod\": \"^4.3.6\"\n  },\n  \"devDependencies\": {\n    \"@types/bun\": \"^1.3.12\",\n    \"@types/fs-extra\": \"^11.0.4\",\n    \"@types/node\": \"^25.6.0\",\n    \"publint\": \"^0.3.18\",\n    \"tsdown\": \"^0.21.9\",\n    \"typescript\": \"^6.0.3\"\n  }\n}\n"
  },
  {
    "path": "apps/cli/src/cli.ts",
    "content": "import { createBtsCli } from \"./index\";\nimport { startBtsMcpServer } from \"./mcp\";\n\nconst [, , command, ...args] = process.argv;\n\nif (command === \"mcp\") {\n  if (args.includes(\"--help\") || args.includes(\"-h\")) {\n    console.log(`Usage: create-better-t-stack mcp\n\nStart the Better T Stack MCP server over stdio.\n\nThis command is intended to be launched by an MCP client, for example:\n  create-better-t-stack mcp`);\n    process.exit(0);\n  }\n\n  await startBtsMcpServer();\n} else {\n  await createBtsCli().run();\n}\n"
  },
  {
    "path": "apps/cli/src/commands/history.ts",
    "content": "import { intro, log } from \"@clack/prompts\";\nimport pc from \"picocolors\";\n\nimport { clearHistory, getHistory, type ProjectHistoryEntry } from \"../utils/project-history\";\nimport { renderTitle } from \"../utils/render-title\";\n\nexport type HistoryCommandInput = {\n  limit: number;\n  clear: boolean;\n  json: boolean;\n};\n\nfunction formatStackSummary(entry: ProjectHistoryEntry): string {\n  const parts: string[] = [];\n\n  if (entry.stack.frontend.length > 0 && !entry.stack.frontend.includes(\"none\")) {\n    parts.push(entry.stack.frontend.join(\", \"));\n  }\n\n  if (entry.stack.backend && entry.stack.backend !== \"none\") {\n    parts.push(entry.stack.backend);\n  }\n\n  if (entry.stack.database && entry.stack.database !== \"none\") {\n    parts.push(entry.stack.database);\n  }\n\n  if (entry.stack.orm && entry.stack.orm !== \"none\") {\n    parts.push(entry.stack.orm);\n  }\n\n  return parts.length > 0 ? parts.join(\" + \") : \"minimal\";\n}\n\nfunction formatDate(isoString: string): string {\n  const date = new Date(isoString);\n  return date.toLocaleDateString(\"en-US\", {\n    year: \"numeric\",\n    month: \"short\",\n    day: \"numeric\",\n    hour: \"2-digit\",\n    minute: \"2-digit\",\n  });\n}\n\nexport async function historyHandler(input: HistoryCommandInput): Promise<void> {\n  if (input.clear) {\n    const clearResult = await clearHistory();\n    if (clearResult.isErr()) {\n      log.warn(pc.yellow(clearResult.error.message));\n      return;\n    }\n    log.success(pc.green(\"Project history cleared.\"));\n    return;\n  }\n\n  const historyResult = await getHistory(input.limit);\n  if (historyResult.isErr()) {\n    log.warn(pc.yellow(historyResult.error.message));\n    return;\n  }\n  const entries = historyResult.value;\n\n  if (entries.length === 0) {\n    log.info(pc.dim(\"No projects in history yet.\"));\n    log.info(pc.dim(\"Create a project with: create-better-t-stack my-app\"));\n    return;\n  }\n\n  if (input.json) {\n    console.log(JSON.stringify(entries, null, 2));\n    return;\n  }\n\n  renderTitle();\n  intro(pc.magenta(`Project History (${entries.length} entries)`));\n\n  for (const [index, entry] of entries.entries()) {\n    const num = pc.dim(`${index + 1}.`);\n    const name = pc.cyan(pc.bold(entry.projectName));\n    const stack = pc.dim(formatStackSummary(entry));\n\n    log.message(`${num} ${name}`);\n    log.message(`   ${pc.dim(\"Created:\")} ${formatDate(entry.createdAt)}`);\n    log.message(`   ${pc.dim(\"Path:\")} ${entry.projectDir}`);\n    log.message(`   ${pc.dim(\"Stack:\")} ${stack}`);\n    log.message(`   ${pc.dim(\"Command:\")} ${pc.dim(entry.reproducibleCommand)}`);\n    log.message(\"\");\n  }\n}\n"
  },
  {
    "path": "apps/cli/src/commands/meta.ts",
    "content": "import { intro, log } from \"@clack/prompts\";\nimport { Result } from \"better-result\";\nimport pc from \"picocolors\";\n\nimport { displayError } from \"../utils/errors\";\nimport { openUrl } from \"../utils/open-url\";\nimport { renderTitle } from \"../utils/render-title\";\nimport { displaySponsors, fetchSponsors } from \"../utils/sponsors\";\n\nconst DOCS_URL = \"https://better-t-stack.dev/docs\";\nconst BUILDER_URL = \"https://better-t-stack.dev/new\";\n\nasync function openExternalUrl(url: string, successMessage: string) {\n  const result = await Result.tryPromise({\n    try: () => openUrl(url),\n    catch: () => null,\n  });\n\n  if (result.isOk()) {\n    log.success(pc.blue(successMessage));\n  } else {\n    log.message(`Please visit ${url}`);\n  }\n}\n\nexport async function showSponsorsCommand() {\n  renderTitle();\n  intro(pc.magenta(\"Better-T-Stack Sponsors\"));\n\n  const sponsorsResult = await fetchSponsors();\n  if (sponsorsResult.isErr()) {\n    displayError(sponsorsResult.error);\n    process.exit(1);\n    return;\n  }\n\n  displaySponsors(sponsorsResult.value);\n}\n\nexport async function openDocsCommand() {\n  await openExternalUrl(DOCS_URL, \"Opened docs in your default browser.\");\n}\n\nexport async function openBuilderCommand() {\n  await openExternalUrl(BUILDER_URL, \"Opened builder in your default browser.\");\n}\n"
  },
  {
    "path": "apps/cli/src/constants.ts",
    "content": "import path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\n\nimport { desktopWebFrontends } from \"@better-t-stack/types\";\n\nimport { getUserPkgManager } from \"./utils/get-package-manager\";\n\n// Re-export from template-generator (single source of truth)\nexport {\n  dependencyVersionMap,\n  type AvailableDependencies,\n} from \"@better-t-stack/template-generator\";\n\nconst __filename = fileURLToPath(import.meta.url);\nconst distPath = path.dirname(__filename);\nexport const PKG_ROOT = path.join(distPath, \"../\");\n\nexport const DEFAULT_CONFIG_BASE = {\n  projectName: \"my-better-t-app\",\n  relativePath: \"my-better-t-app\",\n  frontend: [\"tanstack-router\"],\n  database: \"sqlite\",\n  orm: \"drizzle\",\n  auth: \"better-auth\",\n  payments: \"none\",\n  addons: [\"turborepo\"],\n  examples: [],\n  git: true,\n  install: true,\n  dbSetup: \"none\",\n  backend: \"hono\",\n  runtime: \"bun\",\n  api: \"trpc\",\n  webDeploy: \"none\",\n  serverDeploy: \"none\",\n} as const;\n\nexport function getDefaultConfig() {\n  return {\n    ...DEFAULT_CONFIG_BASE,\n    projectDir: path.resolve(process.cwd(), DEFAULT_CONFIG_BASE.projectName),\n    packageManager: getUserPkgManager(),\n    frontend: [...DEFAULT_CONFIG_BASE.frontend],\n    addons: [...DEFAULT_CONFIG_BASE.addons],\n    examples: [...DEFAULT_CONFIG_BASE.examples],\n  };\n}\n\nexport const DEFAULT_CONFIG = getDefaultConfig();\n\nexport { desktopWebFrontends };\n\nexport const ADDON_COMPATIBILITY = {\n  pwa: [\"tanstack-router\", \"react-router\", \"solid\", \"next\"],\n  tauri: desktopWebFrontends,\n  electrobun: desktopWebFrontends,\n  biome: [],\n  husky: [],\n  lefthook: [],\n  turborepo: [],\n  nx: [],\n  starlight: [],\n  ultracite: [],\n  mcp: [],\n  oxlint: [],\n  fumadocs: [],\n  opentui: [],\n  wxt: [],\n  skills: [],\n  evlog: [],\n  none: [],\n} as const;\n"
  },
  {
    "path": "apps/cli/src/helpers/addons/addons-setup.ts",
    "content": "import path from \"node:path\";\n\nimport { Result } from \"better-result\";\nimport fs from \"fs-extra\";\nimport pc from \"picocolors\";\n\nimport { desktopWebFrontends, type ProjectConfig } from \"../../types\";\nimport { addPackageDependency } from \"../../utils/add-package-deps\";\nimport { AddonSetupError, UserCancelledError } from \"../../utils/errors\";\nimport { cliConsola } from \"../../utils/terminal-output\";\nimport { setupEvlog } from \"./evlog-setup\";\nimport { setupFumadocs } from \"./fumadocs-setup\";\nimport { setupMcp } from \"./mcp-setup\";\nimport { setupOxlint } from \"./oxlint-setup\";\nimport { setupSkills } from \"./skills-setup\";\nimport { setupStarlight } from \"./starlight-setup\";\nimport { setupTauri } from \"./tauri-setup\";\nimport { setupTui } from \"./tui-setup\";\nimport { setupUltracite } from \"./ultracite-setup\";\nimport { setupWxt } from \"./wxt-setup\";\n\n// Helper to run setup and handle Result\nasync function runSetup<T, E extends AddonSetupError | UserCancelledError>(\n  setupFn: () => Promise<Result<T, E>>,\n): Promise<void> {\n  const result = await setupFn();\n  if (result.isErr()) {\n    // Re-throw user cancellation to propagate up\n    if (UserCancelledError.is(result.error)) {\n      throw result.error;\n    }\n    // Log other errors but don't fail the overall project creation\n    cliConsola.error(pc.red(result.error.message));\n  }\n}\n\nasync function runAddonStep(addon: string, step: () => Promise<void>): Promise<void> {\n  const result = await Result.tryPromise({\n    try: async () => step(),\n    catch: (e) =>\n      new AddonSetupError({\n        addon,\n        message: `Failed to set up ${addon}: ${e instanceof Error ? e.message : String(e)}`,\n        cause: e,\n      }),\n  });\n\n  if (result.isErr()) {\n    cliConsola.error(pc.red(result.error.message));\n  }\n}\n\nexport async function setupAddons(config: ProjectConfig) {\n  const { addons, frontend, projectDir } = config;\n  const hasWebFrontend = frontend.some((value) =>\n    (desktopWebFrontends as readonly string[]).includes(value),\n  );\n\n  if (addons.includes(\"tauri\") && hasWebFrontend) {\n    await runSetup(() => setupTauri(config));\n  }\n\n  const hasUltracite = addons.includes(\"ultracite\");\n  const hasBiome = addons.includes(\"biome\");\n  const hasHusky = addons.includes(\"husky\");\n  const hasLefthook = addons.includes(\"lefthook\");\n  const hasOxlint = addons.includes(\"oxlint\");\n\n  if (hasUltracite) {\n    const gitHooks: string[] = [];\n    if (hasHusky) gitHooks.push(\"husky\");\n    if (hasLefthook) gitHooks.push(\"lefthook\");\n    await runSetup(() => setupUltracite(config, gitHooks));\n  } else {\n    if (hasBiome) {\n      await runAddonStep(\"biome\", () => setupBiome(projectDir));\n    }\n\n    if (hasOxlint) {\n      await runSetup(() => setupOxlint(projectDir, config.packageManager));\n    }\n\n    if (hasHusky || hasLefthook) {\n      let linter: \"biome\" | \"oxlint\" | undefined;\n      if (hasOxlint) {\n        linter = \"oxlint\";\n      } else if (hasBiome) {\n        linter = \"biome\";\n      }\n      if (hasHusky) {\n        await runAddonStep(\"husky\", () => setupHusky(projectDir, linter));\n      }\n      if (hasLefthook) {\n        await runAddonStep(\"lefthook\", () => setupLefthook(projectDir));\n      }\n    }\n  }\n\n  if (addons.includes(\"starlight\")) {\n    await runSetup(() => setupStarlight(config));\n  }\n\n  if (addons.includes(\"fumadocs\")) {\n    await runSetup(() => setupFumadocs(config));\n  }\n\n  if (addons.includes(\"opentui\")) {\n    await runSetup(() => setupTui(config));\n  }\n\n  if (addons.includes(\"wxt\")) {\n    await runSetup(() => setupWxt(config));\n  }\n\n  if (addons.includes(\"skills\")) {\n    await runSetup(() => setupSkills(config));\n  }\n\n  if (addons.includes(\"mcp\")) {\n    await runSetup(() => setupMcp(config));\n  }\n\n  if (addons.includes(\"evlog\")) {\n    await runSetup(() => setupEvlog(config));\n  }\n}\n\nexport async function setupBiome(projectDir: string) {\n  await addPackageDependency({\n    devDependencies: [\"@biomejs/biome\"],\n    projectDir,\n  });\n\n  const packageJsonPath = path.join(projectDir, \"package.json\");\n  if (await fs.pathExists(packageJsonPath)) {\n    const packageJson = await fs.readJson(packageJsonPath);\n\n    packageJson.scripts = {\n      ...packageJson.scripts,\n      check: \"biome check --write .\",\n    };\n\n    await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 });\n  }\n}\n\nexport async function setupHusky(projectDir: string, linter?: \"biome\" | \"oxlint\") {\n  await addPackageDependency({\n    devDependencies: [\"husky\", \"lint-staged\"],\n    projectDir,\n  });\n\n  const packageJsonPath = path.join(projectDir, \"package.json\");\n  if (await fs.pathExists(packageJsonPath)) {\n    const packageJson = await fs.readJson(packageJsonPath);\n\n    packageJson.scripts = {\n      ...packageJson.scripts,\n      prepare: \"husky\",\n    };\n\n    if (linter === \"oxlint\") {\n      packageJson[\"lint-staged\"] = {\n        \"*\": [\"oxlint\", \"oxfmt --write\"],\n      };\n    } else if (linter === \"biome\") {\n      packageJson[\"lint-staged\"] = {\n        \"*.{js,ts,cjs,mjs,d.cts,d.mts,jsx,tsx,json,jsonc}\": [\"biome check --write .\"],\n      };\n    } else {\n      packageJson[\"lint-staged\"] = {\n        \"**/*.{js,mjs,cjs,jsx,ts,mts,cts,tsx,vue,astro,svelte}\": \"\",\n      };\n    }\n\n    await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 });\n  }\n}\n\nexport async function setupLefthook(projectDir: string) {\n  await addPackageDependency({\n    devDependencies: [\"lefthook\"],\n    projectDir,\n  });\n  // lefthook.yml is generated by template-generator from templates/addons/lefthook/\n}\n"
  },
  {
    "path": "apps/cli/src/helpers/addons/evlog-setup.ts",
    "content": "import path from \"node:path\";\n\nimport { Result } from \"better-result\";\nimport fs from \"fs-extra\";\n\nimport type { Backend, Frontend, ProjectConfig } from \"../../types\";\nimport { AddonSetupError } from \"../../utils/errors\";\n\ntype EvlogBackend = Extract<Backend, \"hono\" | \"express\" | \"fastify\" | \"elysia\">;\ntype EvlogWebFrontend = Extract<Frontend, \"next\" | \"nuxt\" | \"svelte\" | \"tanstack-start\" | \"astro\">;\n\nconst evlogBackends = [\"hono\", \"express\", \"fastify\", \"elysia\"] as const;\nconst evlogWebFrontends = [\"next\", \"nuxt\", \"svelte\", \"tanstack-start\", \"astro\"] as const;\n\nfunction isEvlogBackend(backend: Backend): backend is EvlogBackend {\n  return (evlogBackends as readonly Backend[]).includes(backend);\n}\n\nfunction getEvlogWebFrontend(frontends: Frontend[]): EvlogWebFrontend | undefined {\n  return frontends.find((frontend): frontend is EvlogWebFrontend =>\n    (evlogWebFrontends as readonly Frontend[]).includes(frontend),\n  );\n}\n\nfunction shouldIdentifyWebAuth(config: ProjectConfig) {\n  return config.auth === \"better-auth\" && config.backend === \"self\";\n}\n\nfunction prependMissingImports(content: string, imports: string[]) {\n  const missingImports = imports.filter((line) => !content.includes(line));\n  if (missingImports.length === 0) return content;\n\n  const importBlock = `${missingImports.join(\"\\n\")}\\n`;\n  const referenceMatch = content.match(/^(?:\\/\\/\\/ <reference[^\\n]*>\\n)+/);\n  if (referenceMatch) {\n    return `${referenceMatch[0]}${importBlock}${content.slice(referenceMatch[0].length)}`;\n  }\n\n  return `${importBlock}${content}`;\n}\n\nfunction addNamedImport(content: string, moduleName: string, names: string[]) {\n  const importRegex = new RegExp(\n    `import \\\\{([^}]+)\\\\} from \"${moduleName.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\")}\";`,\n  );\n  const match = content.match(importRegex);\n\n  if (!match) {\n    return prependMissingImports(content, [`import { ${names.join(\", \")} } from \"${moduleName}\";`]);\n  }\n\n  const existingNames = match[1]\n    .split(\",\")\n    .map((name) => name.trim())\n    .filter(Boolean);\n  const nextNames = [...existingNames];\n\n  for (const name of names) {\n    if (!nextNames.includes(name)) {\n      nextNames.push(name);\n    }\n  }\n\n  return content.replace(match[0], `import { ${nextNames.join(\", \")} } from \"${moduleName}\";`);\n}\n\nfunction insertBeforeOnce(\n  content: string,\n  marker: string,\n  snippet: string,\n  alreadyPresent: string,\n) {\n  if (content.includes(alreadyPresent)) return content;\n  if (!content.includes(marker)) return content;\n  return content.replace(marker, `${snippet}${marker}`);\n}\n\nfunction insertAfterOnce(content: string, marker: string, snippet: string, alreadyPresent: string) {\n  if (content.includes(alreadyPresent)) return content;\n  if (!content.includes(marker)) return content;\n  return content.replace(marker, `${marker}${snippet}`);\n}\n\nasync function writeFileIfChanged(filePath: string, content: string) {\n  const existing = (await fs.pathExists(filePath))\n    ? await fs.readFile(filePath, \"utf-8\")\n    : undefined;\n  if (existing === content) return;\n  await fs.ensureDir(path.dirname(filePath));\n  await fs.writeFile(filePath, content);\n}\n\nasync function updateFileIfExists(filePath: string, update: (content: string) => string) {\n  if (!(await fs.pathExists(filePath))) return;\n  const content = await fs.readFile(filePath, \"utf-8\");\n  const nextContent = update(content);\n  if (nextContent !== content) {\n    await fs.writeFile(filePath, nextContent);\n  }\n}\n\nfunction usesCreateAuthFactory(config: ProjectConfig) {\n  return (\n    config.runtime === \"workers\" ||\n    config.serverDeploy === \"cloudflare\" ||\n    (config.backend === \"self\" && config.webDeploy === \"cloudflare\")\n  );\n}\n\nfunction getAuthImportLine(config: ProjectConfig) {\n  return usesCreateAuthFactory(config)\n    ? `import { createAuth } from \"@${config.projectName}/auth\";`\n    : `import { auth } from \"@${config.projectName}/auth\";`;\n}\n\nfunction getAuthExpression(config: ProjectConfig) {\n  return usesCreateAuthFactory(config) ? \"createAuth()\" : \"auth\";\n}\n\nfunction addAiSdkEvlogTelemetry(content: string, loggerExpression: string) {\n  let nextContent = addNamedImport(content, \"evlog/ai\", [\n    \"createAILogger\",\n    \"createEvlogIntegration\",\n  ]);\n\n  if (!nextContent.includes(\"const ai = createAILogger(\")) {\n    nextContent = nextContent.replace(\n      /^(\\s*)const model = wrapLanguageModel\\({/m,\n      (_match, indent: string) =>\n        `${indent}const ai = createAILogger(${loggerExpression});\\n${indent}const model = wrapLanguageModel({`,\n    );\n  }\n\n  if (!nextContent.includes(\"model: ai.wrap(model)\")) {\n    nextContent = nextContent.replace(\n      /(const result = streamText\\({\\n\\s*)model,/,\n      \"$1model: ai.wrap(model),\",\n    );\n  }\n\n  if (!nextContent.includes(\"createEvlogIntegration(ai)\")) {\n    nextContent = nextContent.replace(\n      /(messages:\\s*await convertToModelMessages\\([^)]+\\),?)/,\n      (match) =>\n        `${match.endsWith(\",\") ? match : `${match},`}\\n\\t\\texperimental_telemetry: {\\n\\t\\t\\tisEnabled: true,\\n\\t\\t\\tintegrations: [createEvlogIntegration(ai)],\\n\\t\\t},`,\n    );\n  }\n\n  return nextContent;\n}\n\nfunction addEvlogBetterAuthServerSetup(\n  content: string,\n  backend: EvlogBackend,\n  authExpression: string,\n) {\n  let nextContent = addNamedImport(content, \"evlog/better-auth\", [\n    \"createAuthMiddleware\",\n    \"type BetterAuthInstance\",\n  ]);\n  const usesAuthFactory = authExpression.endsWith(\"()\");\n  const evlogAuthExpression = `${authExpression} as BetterAuthInstance`;\n  const authOptions = '{ exclude: [\"/api/auth/**\"], maskEmail: true }';\n  const identifySnippet = usesAuthFactory\n    ? \"\"\n    : `const identifyUser = createAuthMiddleware(${evlogAuthExpression}, ${authOptions});\\n\\n`;\n  const identifyUserSetup = usesAuthFactory\n    ? `\\n\\tconst identifyUser = createAuthMiddleware(${evlogAuthExpression}, ${authOptions});`\n    : \"\";\n\n  if (backend === \"hono\") {\n    nextContent = insertBeforeOnce(\n      nextContent,\n      \"const app = new Hono\",\n      identifySnippet,\n      \"createAuthMiddleware(\",\n    );\n    return insertAfterOnce(\n      nextContent,\n      \"app.use(evlog());\",\n      `\\napp.use(\"*\", async (c, next) => {${identifyUserSetup}\\n\\tawait identifyUser(c.get(\"log\"), c.req.raw.headers, c.req.path);\\n\\tawait next();\\n});`,\n      'identifyUser(c.get(\"log\")',\n    );\n  }\n\n  if (backend === \"express\") {\n    nextContent = insertBeforeOnce(\n      nextContent,\n      \"const app = express();\",\n      identifySnippet,\n      \"createAuthMiddleware(\",\n    );\n    return insertAfterOnce(\n      nextContent,\n      \"app.use(evlog());\",\n      `\\napp.use(async (req, _res, next) => {${identifyUserSetup}\\n\\tawait identifyUser(req.log, req.headers, req.path);\\n\\tnext();\\n});`,\n      \"identifyUser(req.log\",\n    );\n  }\n\n  if (backend === \"fastify\") {\n    nextContent = addNamedImport(nextContent, \"evlog/fastify\", [\"useLogger\"]);\n    nextContent = insertBeforeOnce(\n      nextContent,\n      \"const fastify = Fastify\",\n      identifySnippet,\n      \"createAuthMiddleware(\",\n    );\n    return insertAfterOnce(\n      nextContent,\n      \"fastify.register(evlog);\",\n      `\\nfastify.addHook(\"preHandler\", async (request) => {${identifyUserSetup}\\n\\tawait identifyUser(useLogger(), request.headers, request.url);\\n});`,\n      \"identifyUser(useLogger()\",\n    );\n  }\n\n  nextContent = insertBeforeOnce(\n    nextContent,\n    \"new Elysia\",\n    identifySnippet,\n    \"createAuthMiddleware(\",\n  );\n  return insertAfterOnce(\n    nextContent,\n    \".use(evlog())\",\n    `\\n\\t.derive(async ({ request, log }) => {${identifyUserSetup.replace(/\\n\\t/g, \"\\n\\t\\t\")}\\n\\t\\tawait identifyUser(log, request.headers, new URL(request.url).pathname);\\n\\t\\treturn {};\\n\\t})`,\n    \"identifyUser(log\",\n  );\n}\n\nexport function addEvlogServerSetup(content: string, backend: EvlogBackend, serviceName: string) {\n  const initSnippet = `initLogger({\\n\\tenv: { service: \"${serviceName}\" },\\n});\\n\\n`;\n\n  if (backend === \"hono\") {\n    let nextContent = prependMissingImports(content, [\n      'import { initLogger } from \"evlog\";',\n      'import { evlog, type EvlogVariables } from \"evlog/hono\";',\n    ]);\n    nextContent = insertBeforeOnce(\n      nextContent,\n      \"const app = new Hono\",\n      initSnippet,\n      \"initLogger({\",\n    );\n    nextContent = nextContent.replace(\n      \"const app = new Hono();\",\n      \"const app = new Hono<EvlogVariables>();\",\n    );\n    nextContent = nextContent\n      .replace('import { logger } from \"hono/logger\";\\n', \"\")\n      .replace(/\\napp\\.use\\(logger\\(\\)\\);/, \"\");\n    return insertAfterOnce(\n      nextContent,\n      \"const app = new Hono<EvlogVariables>();\",\n      \"\\n\\napp.use(evlog());\",\n      \"app.use(evlog());\",\n    );\n  }\n\n  if (backend === \"express\") {\n    let nextContent = prependMissingImports(content, [\n      'import { initLogger } from \"evlog\";',\n      'import { evlog } from \"evlog/express\";',\n    ]);\n    nextContent = insertBeforeOnce(\n      nextContent,\n      \"const app = express();\",\n      initSnippet,\n      \"initLogger({\",\n    );\n    return insertAfterOnce(\n      nextContent,\n      \"const app = express();\",\n      \"\\n\\napp.use(evlog());\",\n      \"app.use(evlog());\",\n    );\n  }\n\n  if (backend === \"fastify\") {\n    let nextContent = prependMissingImports(content, [\n      'import { initLogger } from \"evlog\";',\n      'import { evlog } from \"evlog/fastify\";',\n    ]);\n    nextContent = insertBeforeOnce(\n      nextContent,\n      \"const fastify = Fastify\",\n      initSnippet,\n      \"initLogger({\",\n    );\n    return insertBeforeOnce(\n      nextContent,\n      \"fastify.register(fastifyCors\",\n      \"fastify.register(evlog);\\n\",\n      \"fastify.register(evlog);\",\n    );\n  }\n\n  let nextContent = prependMissingImports(content, [\n    'import { initLogger } from \"evlog\";',\n    'import { evlog } from \"evlog/elysia\";',\n  ]);\n  nextContent = insertBeforeOnce(nextContent, \"new Elysia\", initSnippet, \"initLogger({\");\n  for (const marker of [\"new Elysia({ adapter: node() })\", \"new Elysia()\"]) {\n    nextContent = insertAfterOnce(nextContent, marker, \"\\n\\t.use(evlog())\", \".use(evlog())\");\n  }\n  return nextContent;\n}\n\nfunction addNuxtEvlogSetup(content: string, serviceName: string) {\n  let nextContent = content;\n  if (!nextContent.includes('\"evlog/nuxt\"') && !nextContent.includes(\"'evlog/nuxt'\")) {\n    nextContent = nextContent.replace(/modules:\\s*\\[/, (match) => `${match}\\n    \"evlog/nuxt\",`);\n  }\n\n  if (!nextContent.includes(\"evlog:\")) {\n    nextContent = nextContent.replace(/\\n\\}\\)\\s*$/, (match) => {\n      const contentBeforeConfigClose = nextContent.slice(0, -match.length);\n      const needsComma = !/[,{]\\s*$/.test(contentBeforeConfigClose);\n      return `${needsComma ? \",\" : \"\"}\\n  evlog: {\\n    env: { service: \"${serviceName}\" },\\n  },\\n})`;\n    });\n  }\n\n  return nextContent;\n}\n\nfunction addSvelteViteEvlogSetup(content: string, serviceName: string) {\n  let nextContent = prependMissingImports(content, ['import evlog from \"evlog/vite\";']);\n  if (nextContent.includes(\"evlog({\")) return nextContent;\n\n  return nextContent.replace(\n    \"plugins: [tailwindcss(), sveltekit()],\",\n    `plugins: [\\n    tailwindcss(),\\n    sveltekit(),\\n    evlog({ service: \"${serviceName}\" }),\\n  ],`,\n  );\n}\n\nfunction addSvelteHooksEvlogSetup(content: string) {\n  let nextContent = prependMissingImports(content, [\n    'import { createEvlogHooks } from \"evlog/sveltekit\";',\n  ]);\n\n  if (!nextContent.includes(\"export const handle\") && !nextContent.includes(\"const authHandle\")) {\n    if (!nextContent.includes(\"createEvlogHooks()\")) {\n      nextContent = `${nextContent.trimEnd()}\\n\\nexport const { handle, handleError } = createEvlogHooks();\\n`;\n    }\n    return nextContent;\n  }\n\n  nextContent = prependMissingImports(nextContent, [\n    'import { sequence } from \"@sveltejs/kit/hooks\";',\n  ]);\n  if (!nextContent.includes(\"const { handle: evlogHandle, handleError }\")) {\n    nextContent = nextContent.replace(\n      /((?:import .+\\n)+)/,\n      `$1\\nconst { handle: evlogHandle, handleError } = createEvlogHooks();\\n\\n`,\n    );\n  }\n  nextContent = nextContent.replace(\n    /export const handle(:\\s*Handle)?\\s*=\\s*async/,\n    (_match, typeAnnotation: string | undefined) =>\n      `const authHandle${typeAnnotation ?? \"\"} = async`,\n  );\n\n  if (!nextContent.includes(\"sequence(evlogHandle, authHandle)\")) {\n    nextContent = `${nextContent.trimEnd()}\\n\\nexport const handle = sequence(evlogHandle as Handle, authHandle);\\nexport { handleError };\\n`;\n  }\n\n  return nextContent;\n}\n\nfunction addSvelteLocalsType(content: string) {\n  let nextContent = prependMissingImports(content, ['import type { RequestLogger } from \"evlog\";']);\n\n  if (nextContent.includes(\"log: RequestLogger\")) return nextContent;\n\n  if (nextContent.includes(\"// interface Locals {}\")) {\n    return nextContent.replace(\n      \"// interface Locals {}\",\n      \"interface Locals {\\n\\t\\t\\tlog: RequestLogger;\\n\\t\\t}\",\n    );\n  }\n\n  return nextContent.replace(\n    \"namespace App {\",\n    \"namespace App {\\n\\t\\tinterface Locals {\\n\\t\\t\\tlog: RequestLogger;\\n\\t\\t}\\n\",\n  );\n}\n\nfunction addTanstackStartRootEvlogSetup(content: string) {\n  let nextContent = prependMissingImports(content, [\n    'import { createMiddleware } from \"@tanstack/react-start\";',\n    'import { evlogErrorHandler } from \"evlog/nitro/v3\";',\n  ]);\n\n  const middlewareEntry = \"createMiddleware().server(evlogErrorHandler)\";\n  if (nextContent.includes(`middleware: [${middlewareEntry}]`)) {\n    return nextContent;\n  }\n\n  if (nextContent.includes(\"middleware: [\")) {\n    return nextContent.replace(\"middleware: [\", `middleware: [${middlewareEntry}, `);\n  }\n\n  if (/server:\\s*{/.test(nextContent)) {\n    return nextContent.replace(\n      /server:\\s*{\\n/,\n      `server: {\\n    middleware: [${middlewareEntry}],\\n`,\n    );\n  }\n\n  return nextContent.replace(\n    \"head: () => ({\",\n    `server: {\\n    middleware: [${middlewareEntry}],\\n  },\\n\\n  head: () => ({`,\n  );\n}\n\nfunction addAstroMiddlewareEvlogSetup(content: string, serviceName: string) {\n  let nextContent = prependMissingImports(content, [\n    'import { createRequestLogger, initLogger } from \"evlog\";',\n  ]);\n  const initSnippet = `initLogger({\\n  env: { service: \"${serviceName}\" },\\n});\\n\\n`;\n\n  nextContent = insertBeforeOnce(\n    nextContent,\n    \"export const onRequest\",\n    initSnippet,\n    \"initLogger({\",\n  );\n\n  if (nextContent.includes(\"createRequestLogger({\")) return nextContent;\n\n  const contextMarker = \"export const onRequest = defineMiddleware(async (context, next) => {\";\n  if (nextContent.includes(contextMarker)) {\n    nextContent = insertAfterOnce(\n      nextContent,\n      contextMarker,\n      `\\n  const url = new URL(context.request.url);\\n  const log = createRequestLogger({\\n    method: context.request.method,\\n    path: url.pathname,\\n  });\\n\\n  context.locals.log = log;\\n`,\n      \"const log = createRequestLogger({\",\n    );\n\n    return nextContent.replace(\n      \"return next();\",\n      \"const response = await next();\\n  log.emit();\\n  return response;\",\n    );\n  }\n\n  const localsMarker =\n    \"export const onRequest = defineMiddleware(async ({ request, locals }, next) => {\";\n  if (nextContent.includes(localsMarker)) {\n    nextContent = insertAfterOnce(\n      nextContent,\n      localsMarker,\n      `\\n  const url = new URL(request.url);\\n  const log = createRequestLogger({\\n    method: request.method,\\n    path: url.pathname,\\n  });\\n\\n  locals.log = log;\\n`,\n      \"const log = createRequestLogger({\",\n    );\n\n    return nextContent.replace(\n      \"return next();\",\n      \"const response = await next();\\n  log.emit();\\n  return response;\",\n    );\n  }\n\n  return nextContent;\n}\n\nfunction addAstroLocalsType(content: string) {\n  let nextContent = prependMissingImports(content, ['import type { RequestLogger } from \"evlog\";']);\n\n  if (nextContent.includes(\"log: RequestLogger\")) return nextContent;\n\n  if (nextContent.includes(\"interface Locals {\")) {\n    return nextContent.replace(\"interface Locals {\", \"interface Locals {\\n    log: RequestLogger;\");\n  }\n\n  if (nextContent.includes(\"declare namespace App {\")) {\n    return nextContent.replace(\n      \"declare namespace App {\",\n      \"declare namespace App {\\n  interface Locals {\\n    log: RequestLogger;\\n  }\\n\",\n    );\n  }\n\n  return `${nextContent.trimEnd()}\\n\\ndeclare namespace App {\\n  interface Locals {\\n    log: RequestLogger;\\n  }\\n}\\n`;\n}\n\nfunction addNextRouteWrappers(content: string) {\n  let nextContent = prependMissingImports(content, ['import { withEvlog } from \"@/lib/evlog\";']);\n  if (\n    nextContent.includes(\"withEvlog(handler)\") ||\n    nextContent.includes(\"withEvlog(handleRequest)\")\n  ) {\n    return nextContent;\n  }\n\n  nextContent = nextContent.replace(\n    \"export { handler as GET, handler as POST };\",\n    \"export const GET = withEvlog(handler);\\nexport const POST = withEvlog(handler);\",\n  );\n\n  for (const method of [\"GET\", \"POST\", \"PUT\", \"PATCH\", \"DELETE\"]) {\n    nextContent = nextContent.replace(\n      `export const ${method} = handleRequest;`,\n      `export const ${method} = withEvlog(handleRequest);`,\n    );\n  }\n\n  return nextContent;\n}\n\nfunction addNextAiEvlogSetup(content: string) {\n  let nextContent = addNamedImport(content, \"@/lib/evlog\", [\"withEvlog\"]);\n\n  if (!nextContent.includes(\"withEvlog(async (req: Request)\")) {\n    nextContent = nextContent.replace(\n      \"export async function POST(req: Request) {\",\n      \"export const POST = withEvlog(async (req: Request) => {\",\n    );\n    if (nextContent.includes(\"export const POST = withEvlog(async (req: Request) => {\")) {\n      nextContent = nextContent.replace(/\\n}\\s*$/, \"\\n});\\n\");\n    }\n  }\n\n  // Next emits the evlog route event when the streaming Response is returned.\n  // AI SDK stream telemetry arrives later, so wiring createAILogger here drops `ai`.\n  return nextContent;\n}\n\nfunction addNuxtAiEvlogSetup(content: string) {\n  return addAiSdkEvlogTelemetry(content, \"useLogger(event)\");\n}\n\nfunction addSvelteAiEvlogSetup(content: string) {\n  let nextContent = content.replace(\n    \"export const POST: RequestHandler = async ({ request }) => {\",\n    \"export const POST: RequestHandler = async ({ request, locals }) => {\",\n  );\n\n  return addAiSdkEvlogTelemetry(nextContent, \"locals.log\");\n}\n\nfunction addTanstackStartAiEvlogSetup(content: string) {\n  let nextContent = prependMissingImports(content, [\n    'import type { RequestLogger } from \"evlog\";',\n    'import { useRequest } from \"nitro/context\";',\n  ]);\n\n  return addAiSdkEvlogTelemetry(nextContent, \"useRequest().context.log as RequestLogger\");\n}\n\nfunction addBackendAiEvlogSetup(content: string, backend: EvlogBackend) {\n  if (backend === \"hono\") {\n    return addAiSdkEvlogTelemetry(content, 'c.get(\"log\")');\n  }\n\n  if (backend === \"express\") {\n    return addAiSdkEvlogTelemetry(content, \"req.log\");\n  }\n\n  if (backend === \"fastify\") {\n    const nextContent = addNamedImport(content, \"evlog/fastify\", [\"useLogger\"]);\n    return addAiSdkEvlogTelemetry(nextContent, \"useLogger()\");\n  }\n\n  return addAiSdkEvlogTelemetry(content, \"context.log\");\n}\n\nfunction addNextBetterAuthToRoute(content: string) {\n  let nextContent = addNamedImport(content, \"@/lib/evlog-auth\", [\"identifyEvlogUser\"]);\n\n  nextContent = nextContent.replace(\"function handler(req:\", \"async function handler(req:\");\n\n  for (const marker of [\n    \"async function handler(req: NextRequest) {\",\n    \"async function handleRequest(req: NextRequest) {\",\n    \"export const POST = withEvlog(async (req: Request) => {\",\n  ]) {\n    nextContent = insertAfterOnce(\n      nextContent,\n      marker,\n      \"\\n\\tawait identifyEvlogUser(req);\",\n      \"identifyEvlogUser(req)\",\n    );\n  }\n\n  return nextContent;\n}\n\nfunction addSvelteBetterAuthEvlogSetup(content: string, config: ProjectConfig) {\n  if (!content.includes(\"authHandle\") || content.includes(\"evlogAuthHandle\")) {\n    return content;\n  }\n\n  let nextContent = addNamedImport(content, \"evlog/better-auth\", [\n    \"createAuthMiddleware\",\n    \"type BetterAuthInstance\",\n  ]);\n  if (!nextContent.includes(`@${config.projectName}/auth`)) {\n    nextContent = prependMissingImports(nextContent, [getAuthImportLine(config)]);\n  }\n  if (\n    usesCreateAuthFactory(config) &&\n    config.webDeploy === \"cloudflare\" &&\n    !nextContent.includes(`@${config.projectName}/env/server`)\n  ) {\n    nextContent = prependMissingImports(nextContent, [\n      `import { env as localEnv } from \"@${config.projectName}/env/server\";`,\n    ]);\n  }\n  const authExpression = getAuthExpression(config);\n  const authOptions = '{ exclude: [\"/api/auth/**\"], maskEmail: true }';\n  const authHandleSnippet =\n    usesCreateAuthFactory(config) && config.webDeploy === \"cloudflare\"\n      ? `const evlogAuthHandle: Handle = async ({ event, resolve }) => {\\n\\tif (building) {\\n\\t\\treturn resolve(event);\\n\\t}\\n\\n\\tconst authEnv = event.platform?.env ?? localEnv;\\n\\tconst identifyUser = createAuthMiddleware(createAuth(authEnv) as BetterAuthInstance, ${authOptions});\\n\\tawait identifyUser(event.locals.log, event.request.headers, event.url.pathname);\\n\\treturn resolve(event);\\n};\\n\\n`\n      : `const identifyUser = createAuthMiddleware(${authExpression} as BetterAuthInstance, ${authOptions});\\n\\nconst evlogAuthHandle: Handle = async ({ event, resolve }) => {\\n\\tawait identifyUser(event.locals.log, event.request.headers, event.url.pathname);\\n\\treturn resolve(event);\\n};\\n\\n`;\n\n  nextContent = insertAfterOnce(\n    nextContent,\n    \"const { handle: evlogHandle, handleError } = createEvlogHooks();\\n\\n\",\n    authHandleSnippet,\n    \"evlogAuthHandle\",\n  );\n\n  return nextContent\n    .replace(\n      \"sequence(evlogHandle as Handle, authHandle)\",\n      \"sequence(evlogHandle as Handle, evlogAuthHandle, authHandle)\",\n    )\n    .replace(\n      \"sequence(evlogHandle, authHandle)\",\n      \"sequence(evlogHandle as Handle, evlogAuthHandle, authHandle)\",\n    );\n}\n\nfunction addAstroBetterAuthEvlogSetup(content: string, config: ProjectConfig) {\n  if (content.includes(\"createAuthMiddleware(\")) return content;\n\n  let nextContent = addNamedImport(content, \"evlog/better-auth\", [\n    \"createAuthMiddleware\",\n    \"type BetterAuthInstance\",\n  ]);\n  if (!nextContent.includes(`@${config.projectName}/auth`)) {\n    nextContent = prependMissingImports(nextContent, [getAuthImportLine(config)]);\n  }\n  const authExpression = getAuthExpression(config);\n  const authOptions = '{ exclude: [\"/api/auth/**\"], maskEmail: true }';\n  const usesFactory = usesCreateAuthFactory(config);\n  if (!usesFactory) {\n    nextContent = insertBeforeOnce(\n      nextContent,\n      \"export const onRequest\",\n      `const identifyUser = createAuthMiddleware(${authExpression} as BetterAuthInstance, ${authOptions});\\n\\n`,\n      \"const identifyUser = createAuthMiddleware(\",\n    );\n  }\n\n  for (const marker of [\"context.locals.log = log;\", \"locals.log = log;\"]) {\n    if (!nextContent.includes(marker)) continue;\n\n    const requestExpression = marker.startsWith(\"context\") ? \"context.request\" : \"request\";\n    const identifySnippet = usesFactory\n      ? `\\n\\n  const identifyUser = createAuthMiddleware(${authExpression} as BetterAuthInstance, ${authOptions});\\n  await identifyUser(log, ${requestExpression}.headers, url.pathname);`\n      : `\\n\\n  await identifyUser(log, ${requestExpression}.headers, url.pathname);`;\n\n    return insertAfterOnce(nextContent, marker, identifySnippet, \"identifyUser(log\");\n  }\n\n  return nextContent;\n}\n\nfunction getNextEvlogFile(serviceName: string) {\n  return `import { createEvlog } from \"evlog/next\";\nimport { createInstrumentation } from \"evlog/next/instrumentation\";\n\nexport const { withEvlog, useLogger, log, createError } = createEvlog({\n  service: \"${serviceName}\",\n});\n\nexport const { register, onRequestError } = createInstrumentation({\n  service: \"${serviceName}\",\n});\n`;\n}\n\nfunction getNextInstrumentationFile() {\n  return `import { defineNodeInstrumentation } from \"evlog/next/instrumentation\";\n\nexport const { register, onRequestError } = defineNodeInstrumentation(() => import(\"./src/lib/evlog\"));\n`;\n}\n\nfunction getNextProxyFile() {\n  return `import { evlogMiddleware } from \"evlog/next\";\n\nexport const proxy = evlogMiddleware();\n\nexport const config = {\n  matcher: [\"/api/:path*\"],\n};\n`;\n}\n\nfunction getNextEvlogAuthFile(config: ProjectConfig) {\n  if (usesCreateAuthFactory(config)) {\n    return `${getAuthImportLine(config)}\nimport { createAuthMiddleware, type BetterAuthInstance } from \"evlog/better-auth\";\nimport { useLogger } from \"@/lib/evlog\";\n\nexport async function identifyEvlogUser(request: Request) {\n  const identifyUser = createAuthMiddleware(${getAuthExpression(config)} as BetterAuthInstance, {\n    exclude: [\"/api/auth/**\"],\n    maskEmail: true,\n  });\n  await identifyUser(useLogger(), request.headers, new URL(request.url).pathname);\n}\n`;\n  }\n\n  return `${getAuthImportLine(config)}\nimport { createAuthMiddleware, type BetterAuthInstance } from \"evlog/better-auth\";\nimport { useLogger } from \"@/lib/evlog\";\n\nconst identifyUser = createAuthMiddleware(${getAuthExpression(config)} as BetterAuthInstance, {\n  exclude: [\"/api/auth/**\"],\n  maskEmail: true,\n});\n\nexport async function identifyEvlogUser(request: Request) {\n  await identifyUser(useLogger(), request.headers, new URL(request.url).pathname);\n}\n`;\n}\n\nfunction getNitroEvlogAuthPluginFile(config: ProjectConfig) {\n  if (usesCreateAuthFactory(config)) {\n    return `${getAuthImportLine(config)}\nimport { createAuthIdentifier, type BetterAuthInstance } from \"evlog/better-auth\";\n\nexport default defineNitroPlugin((nitroApp) => {\n  nitroApp.hooks.hook(\"request\", async (event) => {\n    const identify = createAuthIdentifier(${getAuthExpression(config)} as BetterAuthInstance, {\n      exclude: [\"/api/auth/**\"],\n      maskEmail: true,\n    });\n    await identify(event);\n  });\n});\n`;\n  }\n\n  return `${getAuthImportLine(config)}\nimport { createAuthIdentifier, type BetterAuthInstance } from \"evlog/better-auth\";\n\nexport default defineNitroPlugin((nitroApp) => {\n  nitroApp.hooks.hook(\n    \"request\",\n    createAuthIdentifier(${getAuthExpression(config)} as BetterAuthInstance, {\n      exclude: [\"/api/auth/**\"],\n      maskEmail: true,\n    }),\n  );\n});\n`;\n}\n\nfunction getNuxtEvlogAuthMiddlewareFile(config: ProjectConfig) {\n  if (usesCreateAuthFactory(config)) {\n    return `${getAuthImportLine(config)}\nimport { createAuthMiddleware, type BetterAuthInstance } from \"evlog/better-auth\";\n\nexport default defineEventHandler(async (event) => {\n  if (!event.context.log) return;\n  const identify = createAuthMiddleware(${getAuthExpression(config)} as BetterAuthInstance, {\n    exclude: [\"/api/auth/**\"],\n    maskEmail: true,\n  });\n  await identify(event.context.log, event.headers, event.path);\n});\n`;\n  }\n\n  return `${getAuthImportLine(config)}\nimport { createAuthMiddleware, type BetterAuthInstance } from \"evlog/better-auth\";\n\nconst identify = createAuthMiddleware(${getAuthExpression(config)} as BetterAuthInstance, {\n  exclude: [\"/api/auth/**\"],\n  maskEmail: true,\n});\n\nexport default defineEventHandler(async (event) => {\n  if (!event.context.log) return;\n  await identify(event.context.log, event.headers, event.path);\n});\n`;\n}\n\nfunction getTanstackNitroConfigFile(serviceName: string) {\n  return `import { defineConfig } from \"nitro\";\nimport evlog from \"evlog/nitro/v3\";\n\nexport default defineConfig({\n  experimental: {\n    asyncContext: true,\n  },\n  modules: [\n    evlog({\n      env: { service: \"${serviceName}\" },\n    }),\n  ],\n});\n`;\n}\n\nfunction getAstroMiddlewareFile(serviceName: string) {\n  return `import { defineMiddleware } from \"astro:middleware\";\nimport { createRequestLogger, initLogger } from \"evlog\";\n\ninitLogger({\n  env: { service: \"${serviceName}\" },\n});\n\nexport const onRequest = defineMiddleware(async ({ request, locals }, next) => {\n  const url = new URL(request.url);\n  const log = createRequestLogger({\n    method: request.method,\n    path: url.pathname,\n  });\n\n  locals.log = log;\n\n  try {\n    const response = await next();\n    log.emit();\n    return response;\n  } catch (error) {\n    log.error(error instanceof Error ? error : new Error(String(error)));\n    log.emit();\n    throw error;\n  }\n});\n`;\n}\n\nfunction getAstroEnvFile() {\n  return `/// <reference types=\"astro/client\" />\n\nimport type { RequestLogger } from \"evlog\";\n\ndeclare namespace App {\n  interface Locals {\n    log: RequestLogger;\n  }\n}\n`;\n}\n\nasync function setupNextEvlog(config: ProjectConfig, serviceName: string) {\n  const webDir = path.join(config.projectDir, \"apps/web\");\n\n  const evlogPath = path.join(webDir, \"src/lib/evlog.ts\");\n  if (!(await fs.pathExists(evlogPath))) {\n    await writeFileIfChanged(evlogPath, getNextEvlogFile(serviceName));\n  }\n\n  const identifyWebAuth = shouldIdentifyWebAuth(config);\n\n  if (identifyWebAuth) {\n    const evlogAuthPath = path.join(webDir, \"src/lib/evlog-auth.ts\");\n    if (!(await fs.pathExists(evlogAuthPath))) {\n      await writeFileIfChanged(evlogAuthPath, getNextEvlogAuthFile(config));\n    }\n  }\n\n  const instrumentationPath = path.join(webDir, \"instrumentation.ts\");\n  if (!(await fs.pathExists(instrumentationPath))) {\n    await writeFileIfChanged(instrumentationPath, getNextInstrumentationFile());\n  }\n\n  const proxyPath = path.join(webDir, \"src/proxy.ts\");\n  const rootProxyPath = path.join(webDir, \"proxy.ts\");\n  if (!(await fs.pathExists(proxyPath)) && !(await fs.pathExists(rootProxyPath))) {\n    await writeFileIfChanged(proxyPath, getNextProxyFile());\n  }\n\n  const updateNextApiRoute = (content: string) => {\n    let nextContent = addNextRouteWrappers(content);\n    if (identifyWebAuth) {\n      nextContent = addNextBetterAuthToRoute(nextContent);\n    }\n    return nextContent;\n  };\n\n  await updateFileIfExists(\n    path.join(webDir, \"src/app/api/trpc/[trpc]/route.ts\"),\n    updateNextApiRoute,\n  );\n  await updateFileIfExists(\n    path.join(webDir, \"src/app/api/rpc/[[...rest]]/route.ts\"),\n    updateNextApiRoute,\n  );\n\n  if (config.examples.includes(\"ai\")) {\n    await updateFileIfExists(path.join(webDir, \"src/app/api/ai/route.ts\"), (content) => {\n      let nextContent = addNextAiEvlogSetup(content);\n      if (identifyWebAuth) {\n        nextContent = addNextBetterAuthToRoute(nextContent);\n      }\n      return nextContent;\n    });\n  }\n}\n\nasync function setupNuxtEvlog(config: ProjectConfig, serviceName: string) {\n  const webDir = path.join(config.projectDir, \"apps/web\");\n  await updateFileIfExists(path.join(webDir, \"nuxt.config.ts\"), (content) =>\n    addNuxtEvlogSetup(content, serviceName),\n  );\n\n  if (shouldIdentifyWebAuth(config)) {\n    const oldAuthPluginPath = path.join(webDir, \"server/plugins/evlog-auth.ts\");\n    if (await fs.pathExists(oldAuthPluginPath)) {\n      const oldAuthPlugin = await fs.readFile(oldAuthPluginPath, \"utf-8\");\n      if (oldAuthPlugin.includes(\"evlog/better-auth\")) {\n        await fs.remove(oldAuthPluginPath);\n      }\n    }\n\n    const authMiddlewarePath = path.join(webDir, \"server/middleware/evlog-auth.ts\");\n    if (!(await fs.pathExists(authMiddlewarePath))) {\n      await writeFileIfChanged(authMiddlewarePath, getNuxtEvlogAuthMiddlewareFile(config));\n    }\n  }\n\n  if (config.examples.includes(\"ai\")) {\n    await updateFileIfExists(path.join(webDir, \"server/api/ai.post.ts\"), addNuxtAiEvlogSetup);\n  }\n}\n\nasync function setupSvelteEvlog(config: ProjectConfig, serviceName: string) {\n  const webDir = path.join(config.projectDir, \"apps/web\");\n  await updateFileIfExists(path.join(webDir, \"vite.config.ts\"), (content) =>\n    addSvelteViteEvlogSetup(content, serviceName),\n  );\n\n  const hooksPath = path.join(webDir, \"src/hooks.server.ts\");\n  if (await fs.pathExists(hooksPath)) {\n    await updateFileIfExists(hooksPath, addSvelteHooksEvlogSetup);\n  } else {\n    await writeFileIfChanged(\n      hooksPath,\n      `import { createEvlogHooks } from \"evlog/sveltekit\";\n\nexport const { handle, handleError } = createEvlogHooks();\n`,\n    );\n  }\n\n  await updateFileIfExists(path.join(webDir, \"src/app.d.ts\"), addSvelteLocalsType);\n\n  if (shouldIdentifyWebAuth(config)) {\n    await updateFileIfExists(path.join(webDir, \"src/hooks.server.ts\"), (content) =>\n      addSvelteBetterAuthEvlogSetup(content, config),\n    );\n  }\n\n  if (config.examples.includes(\"ai\")) {\n    await updateFileIfExists(\n      path.join(webDir, \"src/routes/api/ai/+server.ts\"),\n      addSvelteAiEvlogSetup,\n    );\n  }\n}\n\nasync function setupTanstackStartEvlog(config: ProjectConfig, serviceName: string) {\n  const webDir = path.join(config.projectDir, \"apps/web\");\n  const nitroConfigPath = path.join(webDir, \"nitro.config.ts\");\n  if (!(await fs.pathExists(nitroConfigPath))) {\n    await writeFileIfChanged(nitroConfigPath, getTanstackNitroConfigFile(serviceName));\n  }\n  await updateFileIfExists(\n    path.join(webDir, \"src/routes/__root.tsx\"),\n    addTanstackStartRootEvlogSetup,\n  );\n\n  if (shouldIdentifyWebAuth(config)) {\n    const authPluginPath = path.join(webDir, \"server/plugins/evlog-auth.ts\");\n    if (!(await fs.pathExists(authPluginPath))) {\n      await writeFileIfChanged(authPluginPath, getNitroEvlogAuthPluginFile(config));\n    }\n  }\n\n  if (config.examples.includes(\"ai\")) {\n    await updateFileIfExists(\n      path.join(webDir, \"src/routes/api/ai/$.ts\"),\n      addTanstackStartAiEvlogSetup,\n    );\n  }\n}\n\nasync function setupAstroEvlog(config: ProjectConfig, serviceName: string) {\n  const webDir = path.join(config.projectDir, \"apps/web\");\n  const middlewarePath = path.join(webDir, \"src/middleware.ts\");\n  if (!(await fs.pathExists(middlewarePath))) {\n    await writeFileIfChanged(middlewarePath, getAstroMiddlewareFile(serviceName));\n  } else {\n    await updateFileIfExists(middlewarePath, (content) =>\n      addAstroMiddlewareEvlogSetup(content, serviceName),\n    );\n  }\n\n  const envPath = path.join(webDir, \"src/env.d.ts\");\n  if (!(await fs.pathExists(envPath))) {\n    await writeFileIfChanged(envPath, getAstroEnvFile());\n  } else {\n    await updateFileIfExists(envPath, addAstroLocalsType);\n  }\n\n  if (shouldIdentifyWebAuth(config)) {\n    await updateFileIfExists(middlewarePath, (content) =>\n      addAstroBetterAuthEvlogSetup(content, config),\n    );\n  }\n}\n\nasync function setupEvlogWeb(config: ProjectConfig) {\n  const frontend = getEvlogWebFrontend(config.frontend);\n  if (!frontend) return;\n\n  const serviceName = `${config.projectName}-web`;\n\n  if (frontend === \"next\") {\n    await setupNextEvlog(config, serviceName);\n  } else if (frontend === \"nuxt\") {\n    await setupNuxtEvlog(config, serviceName);\n  } else if (frontend === \"svelte\") {\n    await setupSvelteEvlog(config, serviceName);\n  } else if (frontend === \"tanstack-start\") {\n    await setupTanstackStartEvlog(config, serviceName);\n  } else if (frontend === \"astro\") {\n    await setupAstroEvlog(config, serviceName);\n  }\n}\n\nexport async function setupEvlog(config: ProjectConfig): Promise<Result<void, AddonSetupError>> {\n  return Result.tryPromise({\n    try: async () => {\n      if (isEvlogBackend(config.backend)) {\n        const serverIndexPath = path.join(config.projectDir, \"apps/server/src/index.ts\");\n        if (await fs.pathExists(serverIndexPath)) {\n          const content = await fs.readFile(serverIndexPath, \"utf-8\");\n          let nextContent = addEvlogServerSetup(\n            content,\n            config.backend,\n            `${config.projectName}-server`,\n          );\n\n          if (config.auth === \"better-auth\") {\n            nextContent = addEvlogBetterAuthServerSetup(\n              nextContent,\n              config.backend,\n              getAuthExpression(config),\n            );\n          }\n\n          if (config.examples.includes(\"ai\")) {\n            nextContent = addBackendAiEvlogSetup(nextContent, config.backend);\n          }\n\n          if (nextContent !== content) {\n            await fs.writeFile(serverIndexPath, nextContent);\n          }\n        }\n      }\n\n      await setupEvlogWeb(config);\n    },\n    catch: (error) =>\n      new AddonSetupError({\n        addon: \"evlog\",\n        message: `Failed to set up evlog: ${error instanceof Error ? error.message : String(error)}`,\n        cause: error,\n      }),\n  });\n}\n"
  },
  {
    "path": "apps/cli/src/helpers/addons/fumadocs-setup.ts",
    "content": "import path from \"node:path\";\n\nimport { Result } from \"better-result\";\nimport { $ } from \"execa\";\nimport fs from \"fs-extra\";\n\nimport { navigableSelect } from \"../../prompts/navigable\";\nimport { navigableGroup } from \"../../prompts/navigable-group\";\nimport type { ProjectConfig } from \"../../types\";\nimport { isSilent } from \"../../utils/context\";\nimport { AddonSetupError, UserCancelledError, userCancelled } from \"../../utils/errors\";\nimport { shouldSkipExternalCommands } from \"../../utils/external-commands\";\nimport { getPackageExecutionArgs } from \"../../utils/package-runner\";\nimport { cliLog, createSpinner } from \"../../utils/terminal-output\";\n\ntype FumadocsTemplate =\n  | \"next-mdx\"\n  | \"next-mdx-static\"\n  | \"waku\"\n  | \"react-router\"\n  | \"react-router-spa\"\n  | \"tanstack-start\"\n  | \"tanstack-start-spa\";\n\ntype FumadocsSearch = \"orama\" | \"orama-cloud\";\ntype FumadocsOgImage = \"next-og\" | \"takumi\";\ntype FumadocsAiChat = \"openrouter\" | \"inkeep\";\n\nconst TEMPLATES = {\n  \"next-mdx\": {\n    label: \"Next.js: Fumadocs MDX\",\n    hint: \"recommended\",\n    value: \"+next+fuma-docs-mdx\",\n  },\n  \"next-mdx-static\": {\n    label: \"Next.js Static: Fumadocs MDX\",\n    value: \"+next+fuma-docs-mdx+static\",\n  },\n  waku: {\n    label: \"Waku: Fumadocs MDX\",\n    value: \"waku\",\n  },\n  \"react-router\": {\n    label: \"React Router: Fumadocs MDX (not RSC)\",\n    value: \"react-router\",\n  },\n  \"react-router-spa\": {\n    label: \"React Router SPA: Fumadocs MDX (not RSC)\",\n    hint: \"SPA mode allows you to host the site statically, compatible with a CDN.\",\n    value: \"react-router-spa\",\n  },\n  \"tanstack-start\": {\n    label: \"Tanstack Start: Fumadocs MDX (not RSC)\",\n    value: \"tanstack-start\",\n  },\n  \"tanstack-start-spa\": {\n    label: \"Tanstack Start SPA: Fumadocs MDX (not RSC)\",\n    hint: \"SPA mode allows you to host the site statically, compatible with a CDN.\",\n    value: \"tanstack-start-spa\",\n  },\n} as const;\n\nconst DEFAULT_TEMPLATE: FumadocsTemplate = \"next-mdx\";\nconst DEFAULT_DEV_PORT = 4000;\n\nfunction aiChatDisabledForTemplate(template: FumadocsTemplate): boolean {\n  return template === \"next-mdx-static\" || template.endsWith(\"-spa\");\n}\n\nexport async function setupFumadocs(\n  config: ProjectConfig,\n): Promise<Result<void, AddonSetupError | UserCancelledError>> {\n  if (shouldSkipExternalCommands()) {\n    return Result.ok(undefined);\n  }\n\n  const { packageManager, projectDir } = config;\n\n  cliLog.info(\"Setting up Fumadocs...\");\n\n  const configuredOptions = config.addonOptions?.fumadocs;\n\n  let template = configuredOptions?.template;\n  let search: FumadocsSearch | undefined = configuredOptions?.search;\n  let ogImage: FumadocsOgImage | undefined = configuredOptions?.ogImage;\n  let aiChat: FumadocsAiChat | undefined = configuredOptions?.aiChat;\n\n  if (isSilent()) {\n    template = template ?? DEFAULT_TEMPLATE;\n  } else {\n    const promptResult = await Result.tryPromise({\n      try: () =>\n        navigableGroup<{\n          template: FumadocsTemplate;\n          search: FumadocsSearch;\n          ogImage: FumadocsOgImage | \"skip\";\n          aiChat: FumadocsAiChat | \"none\";\n        }>({\n          template: async () => {\n            if (template !== undefined) return template;\n            return navigableSelect<FumadocsTemplate>({\n              message: \"Choose a template\",\n              options: Object.entries(TEMPLATES).map(([key, t]) => ({\n                value: key as FumadocsTemplate,\n                label: t.label,\n                hint: \"hint\" in t ? t.hint : undefined,\n              })),\n              initialValue: DEFAULT_TEMPLATE,\n            });\n          },\n          search: async () => {\n            if (search !== undefined) return search;\n            return navigableSelect<FumadocsSearch>({\n              message: \"Choose a search solution?\",\n              options: [\n                {\n                  value: \"orama\",\n                  label: \"Default\",\n                  hint: \"local search powered by Orama, recommended\",\n                },\n                {\n                  value: \"orama-cloud\",\n                  label: \"Orama Cloud\",\n                  hint: \"3rd party search solution, signup needed\",\n                },\n              ],\n              initialValue: \"orama\",\n            });\n          },\n          ogImage: async ({ results }) => {\n            if (ogImage !== undefined) return ogImage;\n            const picked = results.template ?? template ?? DEFAULT_TEMPLATE;\n            if (!picked.startsWith(\"next-\")) return \"skip\";\n            return navigableSelect<FumadocsOgImage>({\n              message: \"Configure Open Graph Image generation?\",\n              options: [\n                { value: \"next-og\", label: \"next/og\", hint: \"Next.js built-in solution\" },\n                {\n                  value: \"takumi\",\n                  label: \"Takumi\",\n                  hint: \"Output WebP format, framework-agnostic\",\n                },\n              ],\n              initialValue: \"next-og\",\n            });\n          },\n          aiChat: async ({ results }) => {\n            if (aiChat !== undefined) return aiChat;\n            const picked = results.template ?? template ?? DEFAULT_TEMPLATE;\n            if (aiChatDisabledForTemplate(picked)) return \"none\";\n            return navigableSelect<FumadocsAiChat | \"none\">({\n              message: \"Configure AI Chat?\",\n              options: [\n                { value: \"none\", label: \"No\" },\n                { value: \"openrouter\", label: \"AI SDK\", hint: \"default to OpenRouter\" },\n                { value: \"inkeep\", label: \"Inkeep AI\", hint: \"API key required\" },\n              ],\n              initialValue: \"none\",\n            });\n          },\n        }),\n      catch: (e) =>\n        new AddonSetupError({\n          addon: \"fumadocs\",\n          message: `Failed to run Fumadocs prompts: ${e instanceof Error ? e.message : String(e)}`,\n          cause: e,\n        }),\n    });\n\n    if (promptResult.isErr()) return Result.err(promptResult.error);\n    const results = promptResult.value;\n\n    // Cancel mid-group leaves later slots undefined; skip/none sentinels are defined.\n    if (\n      results.template === undefined ||\n      results.search === undefined ||\n      results.ogImage === undefined ||\n      results.aiChat === undefined\n    ) {\n      return userCancelled(\"Operation cancelled\");\n    }\n\n    template = results.template;\n    search = results.search;\n    ogImage = results.ogImage === \"skip\" ? undefined : results.ogImage;\n    aiChat = results.aiChat === \"none\" ? undefined : results.aiChat;\n  }\n\n  if (!template) {\n    return userCancelled(\"Operation cancelled\");\n  }\n\n  const isNextTemplate = template.startsWith(\"next-\");\n\n  // Normalize pre-configured flags against the chosen template so we don't emit\n  // upstream-broken combinations (AI chat on a static export, etc.).\n  if (!isNextTemplate) {\n    ogImage = undefined;\n  }\n  if (aiChatDisabledForTemplate(template)) {\n    aiChat = undefined;\n  }\n\n  const templateArg = TEMPLATES[template].value;\n  const devPort = configuredOptions?.devPort ?? DEFAULT_DEV_PORT;\n\n  const options: string[] = [`--template ${templateArg}`, `--pm ${packageManager}`, \"--no-git\"];\n\n  if (isNextTemplate) {\n    options.push(\"--src\");\n  }\n\n  // create-fumadocs-app's --linter flag only accepts \"eslint\" or \"biome\"\n  // (oxlint exists only in the interactive prompt, not as a flag choice).\n  if (config.addons.includes(\"biome\") || config.addons.includes(\"ultracite\")) {\n    options.push(\"--linter biome\");\n  }\n\n  if (search) options.push(`--search ${search}`);\n  if (ogImage) options.push(`--og-image ${ogImage}`);\n  if (aiChat) options.push(`--ai-chat ${aiChat}`);\n\n  const commandWithArgs = `create-fumadocs-app@latest fumadocs ${options.join(\" \")}`;\n  const args = getPackageExecutionArgs(packageManager, commandWithArgs);\n\n  const appsDir = path.join(projectDir, \"apps\");\n  await fs.ensureDir(appsDir);\n\n  const s = createSpinner();\n  s.start(\"Running Fumadocs create command...\");\n\n  const result = await Result.tryPromise({\n    try: async () => {\n      await $({ cwd: appsDir, env: { CI: \"true\" } })`${args}`;\n\n      const fumadocsDir = path.join(projectDir, \"apps\", \"fumadocs\");\n      const packageJsonPath = path.join(fumadocsDir, \"package.json\");\n\n      if (await fs.pathExists(packageJsonPath)) {\n        const packageJson = await fs.readJson(packageJsonPath);\n        packageJson.name = \"fumadocs\";\n\n        if (packageJson.scripts?.dev) {\n          packageJson.scripts.dev = `${packageJson.scripts.dev} --port=${devPort}`;\n        }\n\n        await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 });\n      }\n    },\n    catch: (e) =>\n      new AddonSetupError({\n        addon: \"fumadocs\",\n        message: `Failed to set up Fumadocs: ${e instanceof Error ? e.message : String(e)}`,\n        cause: e,\n      }),\n  });\n\n  if (result.isErr()) {\n    s.stop(\"Failed to set up Fumadocs\");\n    return result;\n  }\n\n  s.stop(\"Fumadocs setup complete!\");\n  return Result.ok(undefined);\n}\n"
  },
  {
    "path": "apps/cli/src/helpers/addons/mcp-setup.ts",
    "content": "import { Result } from \"better-result\";\nimport { $ } from \"execa\";\nimport pc from \"picocolors\";\n\nimport { navigableMultiselect, navigableSelect } from \"../../prompts/navigable\";\nimport { navigableGroup } from \"../../prompts/navigable-group\";\nimport type { AddonOptions, ProjectConfig } from \"../../types\";\nimport { isSilent } from \"../../utils/context\";\nimport { AddonSetupError, UserCancelledError } from \"../../utils/errors\";\nimport { shouldSkipExternalCommands } from \"../../utils/external-commands\";\nimport { getPackageExecutionCommand, getPackageRunnerPrefix } from \"../../utils/package-runner\";\nimport { cliLog, createSpinner } from \"../../utils/terminal-output\";\n\ntype McpTransport = \"http\" | \"sse\";\n\ntype McpOptions = NonNullable<AddonOptions[\"mcp\"]>;\ntype McpServerKey = NonNullable<McpOptions[\"servers\"]>[number];\ntype McpAgent = NonNullable<McpOptions[\"agents\"]>[number];\ntype InstallScope = NonNullable<McpOptions[\"scope\"]>;\n\nexport type McpServerDef = {\n  key: McpServerKey;\n  label: string;\n  name: string;\n  target: string;\n  transport?: McpTransport;\n  headers?: string[];\n};\n\ntype AgentScope = \"project\" | \"global\" | \"both\";\n\ntype AgentOption = {\n  value: McpAgent;\n  label: string;\n  scope: AgentScope;\n};\n\nconst MCP_AGENTS: AgentOption[] = [\n  { value: \"antigravity\", label: \"Antigravity\", scope: \"global\" },\n  { value: \"cline\", label: \"Cline VSCode Extension\", scope: \"global\" },\n  { value: \"cline-cli\", label: \"Cline CLI\", scope: \"global\" },\n  { value: \"cursor\", label: \"Cursor\", scope: \"both\" },\n  { value: \"claude-code\", label: \"Claude Code\", scope: \"both\" },\n  { value: \"codex\", label: \"Codex\", scope: \"both\" },\n  { value: \"opencode\", label: \"OpenCode\", scope: \"both\" },\n  { value: \"gemini-cli\", label: \"Gemini CLI\", scope: \"both\" },\n  { value: \"github-copilot-cli\", label: \"GitHub Copilot CLI\", scope: \"both\" },\n  { value: \"mcporter\", label: \"MCPorter\", scope: \"both\" },\n  { value: \"vscode\", label: \"VS Code (GitHub Copilot)\", scope: \"both\" },\n  { value: \"zed\", label: \"Zed\", scope: \"both\" },\n  { value: \"claude-desktop\", label: \"Claude Desktop\", scope: \"global\" },\n  { value: \"goose\", label: \"Goose\", scope: \"global\" },\n];\n\nconst DEFAULT_SCOPE: InstallScope = \"project\";\nconst DEFAULT_AGENTS: McpAgent[] = [\"cursor\", \"claude-code\", \"vscode\"];\n\nfunction uniqueValues<T>(values: T[]): T[] {\n  return Array.from(new Set(values));\n}\n\nfunction hasReactBasedFrontend(frontend: ProjectConfig[\"frontend\"]): boolean {\n  return (\n    frontend.includes(\"react-router\") ||\n    frontend.includes(\"tanstack-router\") ||\n    frontend.includes(\"tanstack-start\") ||\n    frontend.includes(\"next\")\n  );\n}\n\nfunction hasNativeFrontend(frontend: ProjectConfig[\"frontend\"]): boolean {\n  return (\n    frontend.includes(\"native-bare\") ||\n    frontend.includes(\"native-uniwind\") ||\n    frontend.includes(\"native-unistyles\")\n  );\n}\n\nfunction getAllMcpServers(config: ProjectConfig): McpServerDef[] {\n  return [\n    {\n      key: \"better-t-stack\",\n      label: \"Better T Stack\",\n      name: \"better-t-stack\",\n      target: getPackageExecutionCommand(config.packageManager, \"create-better-t-stack@latest mcp\"),\n    },\n    {\n      key: \"context7\",\n      label: \"Context7\",\n      name: \"context7\",\n      target: \"@upstash/context7-mcp\",\n    },\n    {\n      key: \"nx\",\n      label: \"Nx Workspace\",\n      name: \"nx\",\n      target: \"npx nx mcp .\",\n    },\n    {\n      key: \"cloudflare-docs\",\n      label: \"Cloudflare Docs\",\n      name: \"cloudflare-docs\",\n      target: \"https://docs.mcp.cloudflare.com/sse\",\n      transport: \"sse\",\n    },\n    {\n      key: \"convex\",\n      label: \"Convex\",\n      name: \"convex\",\n      target: \"npx -y convex@latest mcp start\",\n    },\n    {\n      key: \"shadcn\",\n      label: \"shadcn/ui\",\n      name: \"shadcn\",\n      target: \"npx -y shadcn@latest mcp\",\n    },\n    {\n      key: \"next-devtools\",\n      label: \"Next Devtools\",\n      name: \"next-devtools\",\n      target: \"npx -y next-devtools-mcp@latest\",\n    },\n    {\n      key: \"nuxt-docs\",\n      label: \"Nuxt Docs\",\n      name: \"nuxt\",\n      target: \"https://nuxt.com/mcp\",\n    },\n    {\n      key: \"nuxt-ui-docs\",\n      label: \"Nuxt UI Docs\",\n      name: \"nuxt-ui\",\n      target: \"https://ui.nuxt.com/mcp\",\n    },\n    {\n      key: \"svelte-docs\",\n      label: \"Svelte Docs\",\n      name: \"svelte\",\n      target: \"https://mcp.svelte.dev/mcp\",\n    },\n    {\n      key: \"astro-docs\",\n      label: \"Astro Docs\",\n      name: \"astro-docs\",\n      target: \"https://mcp.docs.astro.build/mcp\",\n    },\n    {\n      key: \"planetscale\",\n      label: \"PlanetScale\",\n      name: \"planetscale\",\n      target: \"https://mcp.pscale.dev/mcp/planetscale\",\n    },\n    {\n      key: \"neon\",\n      label: \"Neon\",\n      name: \"neon\",\n      target: \"https://mcp.neon.tech/mcp\",\n    },\n    {\n      key: \"supabase\",\n      label: \"Supabase\",\n      name: \"supabase\",\n      target: \"https://mcp.supabase.com/mcp\",\n    },\n    {\n      key: \"better-auth\",\n      label: \"Better Auth\",\n      name: \"better-auth\",\n      target: \"https://mcp.inkeep.com/better-auth/mcp\",\n    },\n    {\n      key: \"clerk\",\n      label: \"Clerk\",\n      name: \"clerk\",\n      target: \"https://mcp.clerk.com/mcp\",\n    },\n    {\n      key: \"expo\",\n      label: \"Expo\",\n      name: \"expo-mcp\",\n      target: \"https://mcp.expo.dev/mcp\",\n    },\n    {\n      key: \"polar\",\n      label: \"Polar\",\n      name: \"polar\",\n      target: \"https://mcp.polar.sh/mcp/polar-mcp\",\n    },\n  ];\n}\n\nexport function getRecommendedMcpServers(\n  config: ProjectConfig,\n  scope: InstallScope,\n): McpServerDef[] {\n  const serversByKey = new Map(getAllMcpServers(config).map((server) => [server.key, server]));\n  const recommendedServerKeys: McpServerKey[] = [\"better-t-stack\", \"context7\"];\n\n  if (scope === \"project\" && config.addons.includes(\"nx\")) {\n    recommendedServerKeys.push(\"nx\");\n  }\n\n  if (\n    config.runtime === \"workers\" ||\n    config.webDeploy === \"cloudflare\" ||\n    config.serverDeploy === \"cloudflare\"\n  ) {\n    recommendedServerKeys.push(\"cloudflare-docs\");\n  }\n\n  if (config.backend === \"convex\") {\n    recommendedServerKeys.push(\"convex\");\n  }\n\n  if (hasReactBasedFrontend(config.frontend)) {\n    recommendedServerKeys.push(\"shadcn\");\n  }\n\n  if (config.frontend.includes(\"next\")) {\n    recommendedServerKeys.push(\"next-devtools\");\n  }\n\n  if (config.frontend.includes(\"nuxt\")) {\n    recommendedServerKeys.push(\"nuxt-docs\", \"nuxt-ui-docs\");\n  }\n\n  if (config.frontend.includes(\"svelte\")) {\n    recommendedServerKeys.push(\"svelte-docs\");\n  }\n\n  if (config.frontend.includes(\"astro\")) {\n    recommendedServerKeys.push(\"astro-docs\");\n  }\n\n  if (config.dbSetup === \"planetscale\") {\n    recommendedServerKeys.push(\"planetscale\");\n  }\n\n  if (config.dbSetup === \"neon\") {\n    recommendedServerKeys.push(\"neon\");\n  }\n\n  if (config.dbSetup === \"supabase\") {\n    recommendedServerKeys.push(\"supabase\");\n  }\n\n  if (config.auth === \"better-auth\") {\n    recommendedServerKeys.push(\"better-auth\");\n  }\n\n  if (config.auth === \"clerk\") {\n    recommendedServerKeys.push(\"clerk\");\n  }\n\n  if (hasNativeFrontend(config.frontend)) {\n    recommendedServerKeys.push(\"expo\");\n  }\n\n  if (config.payments === \"polar\") {\n    recommendedServerKeys.push(\"polar\");\n  }\n\n  return uniqueValues(recommendedServerKeys)\n    .map((serverKey) => serversByKey.get(serverKey))\n    .filter((server): server is McpServerDef => server !== undefined);\n}\n\nfunction filterAgentsForScope(scope: InstallScope): AgentOption[] {\n  return MCP_AGENTS.filter((a) => a.scope === \"both\" || a.scope === scope);\n}\n\nexport async function setupMcp(\n  config: ProjectConfig,\n): Promise<Result<void, AddonSetupError | UserCancelledError>> {\n  if (shouldSkipExternalCommands()) {\n    return Result.ok(undefined);\n  }\n\n  const { packageManager, projectDir } = config;\n\n  cliLog.info(\"Setting up MCP servers...\");\n\n  const configuredScope = config.addonOptions?.mcp?.scope;\n  const configuredServerKeys = config.addonOptions?.mcp?.servers;\n  const configuredAgents = config.addonOptions?.mcp?.agents;\n\n  const allServersByKey = new Map(getAllMcpServers(config).map((server) => [server.key, server]));\n  const availableServerKeys = new Set(allServersByKey.keys());\n\n  let scope: InstallScope;\n  let selectedServerKeys: McpServerKey[];\n  let selectedAgents: McpAgent[];\n\n  if (isSilent()) {\n    scope = configuredScope ?? DEFAULT_SCOPE;\n    const recommendedServers = getRecommendedMcpServers(config, scope);\n    if (recommendedServers.length === 0) return Result.ok(undefined);\n    const serverOptions = recommendedServers.map((s) => s.key);\n    selectedServerKeys =\n      configuredServerKeys?.filter((k) => availableServerKeys.has(k)) ?? serverOptions;\n    if (selectedServerKeys.length === 0) return Result.ok(undefined);\n    const agentOptions = filterAgentsForScope(scope);\n    const defaultAgents = uniqueValues(\n      DEFAULT_AGENTS.filter((a) => agentOptions.some((o) => o.value === a)),\n    );\n    selectedAgents =\n      configuredAgents?.filter((a) => agentOptions.some((o) => o.value === a)) ?? defaultAgents;\n    if (selectedAgents.length === 0) return Result.ok(undefined);\n  } else {\n    const results = await navigableGroup<{\n      scope: InstallScope;\n      servers: McpServerKey[];\n      agents: McpAgent[];\n    }>({\n      scope: async () => {\n        if (configuredScope !== undefined) return configuredScope;\n        return navigableSelect<InstallScope>({\n          message: \"Where should MCP servers be installed?\",\n          options: [\n            {\n              value: \"project\",\n              label: \"Project\",\n              hint: \"Writes to project config files (recommended for teams)\",\n            },\n            {\n              value: \"global\",\n              label: \"Global\",\n              hint: \"Writes to user-level config files (personal machine)\",\n            },\n          ],\n          initialValue: DEFAULT_SCOPE,\n        });\n      },\n      servers: async ({ results: r }) => {\n        const currentScope = (r.scope ?? configuredScope ?? DEFAULT_SCOPE) as InstallScope;\n        const recommended = getRecommendedMcpServers(config, currentScope);\n        if (recommended.length === 0) return [];\n        const options = recommended.map((s) => ({\n          value: s.key,\n          label: s.label,\n          hint: s.target,\n        }));\n        if (configuredServerKeys !== undefined) {\n          return configuredServerKeys.filter((k) => availableServerKeys.has(k));\n        }\n        return navigableMultiselect<McpServerKey>({\n          message: \"Select MCP servers to install\",\n          options,\n          required: false,\n          initialValues: options.map((o) => o.value),\n        });\n      },\n      agents: async ({ results: r }) => {\n        const currentScope = (r.scope ?? configuredScope ?? DEFAULT_SCOPE) as InstallScope;\n        const currentServers = r.servers as McpServerKey[] | undefined;\n        if (currentServers !== undefined && currentServers.length === 0) return [];\n        const agentOpts = filterAgentsForScope(currentScope);\n        if (agentOpts.length === 0) return [];\n        const defaults = uniqueValues(\n          DEFAULT_AGENTS.filter((a) => agentOpts.some((o) => o.value === a)),\n        );\n        if (configuredAgents !== undefined) {\n          return configuredAgents.filter((a) => agentOpts.some((o) => o.value === a));\n        }\n        return navigableMultiselect<McpAgent>({\n          message: \"Select agents to install MCP servers to\",\n          options: agentOpts.map((a) => ({ value: a.value, label: a.label })),\n          required: false,\n          initialValues: defaults,\n        });\n      },\n    });\n\n    if (\n      results.scope === undefined ||\n      results.servers === undefined ||\n      results.agents === undefined\n    ) {\n      return Result.err(new UserCancelledError({ message: \"Operation cancelled\" }));\n    }\n\n    scope = results.scope;\n    selectedServerKeys = results.servers as McpServerKey[];\n    selectedAgents = results.agents as McpAgent[];\n\n    if (selectedServerKeys.length === 0 || selectedAgents.length === 0) {\n      return Result.ok(undefined);\n    }\n  }\n\n  const selectedServers: McpServerDef[] = [];\n  for (const key of selectedServerKeys) {\n    const server = allServersByKey.get(key);\n    if (server) selectedServers.push(server);\n  }\n\n  if (selectedServers.length === 0) {\n    return Result.ok(undefined);\n  }\n\n  const installSpinner = createSpinner();\n  installSpinner.start(\"Installing MCP servers...\");\n\n  const runner = getPackageRunnerPrefix(packageManager);\n  const globalFlags = scope === \"global\" ? [\"-g\"] : [];\n  let successfulInstalls = 0;\n\n  for (const server of selectedServers) {\n    const transportFlags = server.transport ? [\"-t\", server.transport] : [];\n    const headerFlags = (server.headers ?? []).flatMap((h) => [\"--header\", h]);\n    const agentFlags = selectedAgents.flatMap((agent) => [\"-a\", agent]);\n\n    const args = [\n      ...runner,\n      \"add-mcp@latest\",\n      server.target,\n      \"--name\",\n      server.name,\n      ...transportFlags,\n      ...headerFlags,\n      ...agentFlags,\n      ...globalFlags,\n      \"-y\",\n    ];\n\n    const installResult = await Result.tryPromise({\n      try: async () => {\n        await $({ cwd: projectDir, env: { CI: \"true\" } })`${args}`;\n      },\n      catch: (e) =>\n        new AddonSetupError({\n          addon: \"mcp\",\n          message: `Failed to install MCP server '${server.name}': ${e instanceof Error ? e.message : String(e)}`,\n          cause: e,\n        }),\n    });\n\n    if (installResult.isErr()) {\n      cliLog.warn(pc.yellow(`Warning: Could not install MCP server '${server.name}'`));\n      continue;\n    }\n\n    successfulInstalls += 1;\n  }\n\n  if (successfulInstalls === 0) {\n    installSpinner.stop(pc.red(\"Failed to install MCP servers\"));\n    return Result.err(\n      new AddonSetupError({\n        addon: \"mcp\",\n        message: `Failed to install all requested MCP servers: ${selectedServers.map((server) => server.name).join(\", \")}`,\n      }),\n    );\n  }\n\n  installSpinner.stop(\n    successfulInstalls === selectedServers.length\n      ? \"MCP servers installed\"\n      : \"MCP servers installed with warnings\",\n  );\n  return Result.ok(undefined);\n}\n"
  },
  {
    "path": "apps/cli/src/helpers/addons/oxlint-setup.ts",
    "content": "import path from \"node:path\";\n\nimport { Result } from \"better-result\";\nimport { $ } from \"execa\";\nimport fs from \"fs-extra\";\n\nimport type { PackageManager } from \"../../types\";\nimport { addPackageDependency } from \"../../utils/add-package-deps\";\nimport { AddonSetupError } from \"../../utils/errors\";\nimport { shouldSkipExternalCommands } from \"../../utils/external-commands\";\nimport { getPackageExecutionArgs } from \"../../utils/package-runner\";\nimport { createSpinner } from \"../../utils/terminal-output\";\n\nexport async function setupOxlint(\n  projectDir: string,\n  packageManager: PackageManager,\n): Promise<Result<void, AddonSetupError>> {\n  return Result.tryPromise({\n    try: async () => {\n      await addPackageDependency({\n        devDependencies: [\"oxlint\", \"oxfmt\"],\n        projectDir,\n      });\n\n      const packageJsonPath = path.join(projectDir, \"package.json\");\n      if (await fs.pathExists(packageJsonPath)) {\n        const packageJson = await fs.readJson(packageJsonPath);\n\n        packageJson.scripts = {\n          ...packageJson.scripts,\n          check: \"oxlint && oxfmt --write\",\n        };\n\n        await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 });\n      }\n\n      if (shouldSkipExternalCommands()) {\n        return;\n      }\n\n      const s = createSpinner();\n\n      try {\n        s.start(\"Initializing oxlint and oxfmt...\");\n\n        const oxlintArgs = getPackageExecutionArgs(packageManager, \"oxlint@latest --init\");\n        await $({ cwd: projectDir, env: { CI: \"true\" } })`${oxlintArgs}`;\n\n        const oxfmtArgs = getPackageExecutionArgs(packageManager, \"oxfmt@latest --init\");\n        await $({ cwd: projectDir, env: { CI: \"true\" } })`${oxfmtArgs}`;\n\n        s.stop(\"oxlint and oxfmt initialized successfully!\");\n      } catch (error) {\n        s.stop(\"Failed to initialize oxlint and oxfmt\");\n        throw error;\n      }\n    },\n    catch: (error) =>\n      new AddonSetupError({\n        addon: \"oxlint\",\n        message: `Failed to set up oxlint: ${error instanceof Error ? error.message : String(error)}`,\n        cause: error,\n      }),\n  });\n}\n"
  },
  {
    "path": "apps/cli/src/helpers/addons/skills-setup.ts",
    "content": "import { Result } from \"better-result\";\nimport { $ } from \"execa\";\nimport pc from \"picocolors\";\n\nimport { navigableMultiselect, navigableSelect } from \"../../prompts/navigable\";\nimport { navigableGroup } from \"../../prompts/navigable-group\";\nimport type { AddonOptions, ProjectConfig } from \"../../types\";\nimport { readBtsConfig } from \"../../utils/bts-config\";\nimport { isSilent } from \"../../utils/context\";\nimport { AddonSetupError, UserCancelledError } from \"../../utils/errors\";\nimport { shouldSkipExternalCommands } from \"../../utils/external-commands\";\nimport { getPackageRunnerPrefix } from \"../../utils/package-runner\";\nimport { cliLog, createSpinner } from \"../../utils/terminal-output\";\n\ntype SkillSource = {\n  label: string;\n};\n\ntype AgentOption = {\n  value: SkillAgent;\n  label: string;\n};\n\ntype SkillsOptions = NonNullable<AddonOptions[\"skills\"]>;\ntype SkillAgent = NonNullable<SkillsOptions[\"agents\"]>[number];\ntype InstallScope = NonNullable<SkillsOptions[\"scope\"]>;\n\n// Skill sources - using GitHub shorthand or full URLs\nconst SKILL_SOURCES = {\n  \"vercel-labs/agent-skills\": {\n    label: \"Vercel Agent Skills\",\n  },\n  \"vercel/ai\": {\n    label: \"Vercel AI SDK\",\n  },\n  \"vercel/turborepo\": {\n    label: \"Turborepo\",\n  },\n  \"yusukebe/hono-skill\": {\n    label: \"Hono Backend\",\n  },\n  \"vercel-labs/next-skills\": {\n    label: \"Next.js Best Practices\",\n  },\n  \"nuxt/ui\": {\n    label: \"Nuxt UI\",\n  },\n  \"heroui-inc/heroui\": {\n    label: \"HeroUI Native\",\n  },\n  \"shadcn/ui\": {\n    label: \"shadcn/ui\",\n  },\n  \"better-auth/skills\": {\n    label: \"Better Auth\",\n  },\n  \"clerk/skills\": {\n    label: \"Clerk\",\n  },\n  \"neondatabase/agent-skills\": {\n    label: \"Neon Database\",\n  },\n  \"supabase/agent-skills\": {\n    label: \"Supabase\",\n  },\n  \"planetscale/database-skills\": {\n    label: \"PlanetScale\",\n  },\n  \"expo/skills\": {\n    label: \"Expo\",\n  },\n  \"prisma/skills\": {\n    label: \"Prisma\",\n  },\n  \"elysiajs/skills\": {\n    label: \"ElysiaJS\",\n  },\n  \"waynesutton/convexskills\": {\n    label: \"Convex\",\n  },\n  \"msmps/opentui-skill\": {\n    label: \"OpenTUI Platform\",\n  },\n  \"haydenbleasel/ultracite\": {\n    label: \"Ultracite\",\n  },\n  \"https://www.evlog.dev\": {\n    label: \"evlog\",\n  },\n} satisfies Record<string, SkillSource>;\n\ntype SourceKey = keyof typeof SKILL_SOURCES;\n\n// All available agents from add-skill CLI\nconst AVAILABLE_AGENTS: AgentOption[] = [\n  { value: \"cursor\", label: \"Cursor\" },\n  { value: \"claude-code\", label: \"Claude Code\" },\n  { value: \"cline\", label: \"Cline\" },\n  { value: \"github-copilot\", label: \"GitHub Copilot\" },\n  { value: \"codex\", label: \"Codex\" },\n  { value: \"opencode\", label: \"OpenCode\" },\n  { value: \"windsurf\", label: \"Windsurf\" },\n  { value: \"goose\", label: \"Goose\" },\n  { value: \"roo\", label: \"Roo Code\" },\n  { value: \"kilo\", label: \"Kilo Code\" },\n  { value: \"gemini-cli\", label: \"Gemini CLI\" },\n  { value: \"antigravity\", label: \"Antigravity\" },\n  { value: \"openhands\", label: \"OpenHands\" },\n  { value: \"trae\", label: \"Trae\" },\n  { value: \"amp\", label: \"Amp\" },\n  { value: \"pi\", label: \"Pi\" },\n  { value: \"qoder\", label: \"Qoder\" },\n  { value: \"qwen-code\", label: \"Qwen Code\" },\n  { value: \"kiro-cli\", label: \"Kiro CLI\" },\n  { value: \"droid\", label: \"Droid\" },\n  { value: \"command-code\", label: \"Command Code\" },\n  { value: \"clawdbot\", label: \"Clawdbot\" },\n  { value: \"zencoder\", label: \"Zencoder\" },\n  { value: \"neovate\", label: \"Neovate\" },\n  { value: \"mcpjam\", label: \"MCPJam\" },\n];\n\nconst DEFAULT_SCOPE: InstallScope = \"project\";\nconst DEFAULT_AGENTS: SkillAgent[] = [\"cursor\", \"claude-code\", \"github-copilot\"];\n\nfunction hasReactBasedFrontend(frontend: ProjectConfig[\"frontend\"]): boolean {\n  return (\n    frontend.includes(\"react-router\") ||\n    frontend.includes(\"tanstack-router\") ||\n    frontend.includes(\"tanstack-start\") ||\n    frontend.includes(\"next\")\n  );\n}\n\nfunction hasNativeFrontend(frontend: ProjectConfig[\"frontend\"]): boolean {\n  return (\n    frontend.includes(\"native-bare\") ||\n    frontend.includes(\"native-uniwind\") ||\n    frontend.includes(\"native-unistyles\")\n  );\n}\n\nfunction getRecommendedSourceKeys(config: ProjectConfig): SourceKey[] {\n  const sources: SourceKey[] = [];\n  const { frontend, backend, dbSetup, auth, examples, addons, orm } = config;\n\n  if (hasReactBasedFrontend(frontend)) {\n    sources.push(\"vercel-labs/agent-skills\");\n    sources.push(\"shadcn/ui\");\n  }\n\n  if (frontend.includes(\"next\")) {\n    sources.push(\"vercel-labs/next-skills\");\n  }\n\n  if (frontend.includes(\"nuxt\")) {\n    sources.push(\"nuxt/ui\");\n  }\n\n  if (frontend.includes(\"native-uniwind\")) {\n    sources.push(\"heroui-inc/heroui\");\n  }\n\n  if (hasNativeFrontend(frontend)) {\n    sources.push(\"expo/skills\");\n  }\n\n  if (auth === \"better-auth\") {\n    sources.push(\"better-auth/skills\");\n  }\n\n  if (auth === \"clerk\") {\n    sources.push(\"clerk/skills\");\n  }\n\n  if (dbSetup === \"neon\") {\n    sources.push(\"neondatabase/agent-skills\");\n  }\n\n  if (dbSetup === \"supabase\") {\n    sources.push(\"supabase/agent-skills\");\n  }\n\n  if (dbSetup === \"planetscale\") {\n    sources.push(\"planetscale/database-skills\");\n  }\n\n  if (orm === \"prisma\" || dbSetup === \"prisma-postgres\") {\n    sources.push(\"prisma/skills\");\n  }\n\n  if (examples.includes(\"ai\")) {\n    sources.push(\"vercel/ai\");\n  }\n\n  if (addons.includes(\"turborepo\")) {\n    sources.push(\"vercel/turborepo\");\n  }\n\n  if (backend === \"hono\") {\n    sources.push(\"yusukebe/hono-skill\");\n  }\n\n  if (backend === \"elysia\") {\n    sources.push(\"elysiajs/skills\");\n  }\n\n  if (backend === \"convex\") {\n    sources.push(\"waynesutton/convexskills\");\n  }\n\n  if (addons.includes(\"opentui\")) {\n    sources.push(\"msmps/opentui-skill\");\n  }\n\n  if (addons.includes(\"ultracite\")) {\n    sources.push(\"haydenbleasel/ultracite\");\n  }\n\n  if (addons.includes(\"evlog\")) {\n    sources.push(\"https://www.evlog.dev\");\n  }\n\n  return sources;\n}\n\nconst CURATED_SKILLS_BY_SOURCE: Record<SourceKey, (config: ProjectConfig) => string[]> = {\n  \"vercel-labs/agent-skills\": (config) => {\n    const skills = [\n      \"web-design-guidelines\",\n      \"vercel-composition-patterns\",\n      \"vercel-react-best-practices\",\n    ];\n    if (hasNativeFrontend(config.frontend)) {\n      skills.push(\"vercel-react-native-skills\");\n    }\n    return skills;\n  },\n  \"vercel/ai\": () => [\"ai-sdk\"],\n  \"vercel/turborepo\": () => [\"turborepo\"],\n  \"yusukebe/hono-skill\": () => [\"hono\"],\n  \"vercel-labs/next-skills\": () => [\"next-best-practices\", \"next-cache-components\"],\n  \"nuxt/ui\": () => [\"nuxt-ui\"],\n  \"heroui-inc/heroui\": () => [\"heroui-native\"],\n  \"shadcn/ui\": () => [\"shadcn\"],\n  \"better-auth/skills\": () => [\"better-auth-best-practices\"],\n  \"clerk/skills\": (config) => {\n    const skills = [\n      \"clerk\",\n      \"clerk-setup\",\n      \"clerk-custom-ui\",\n      \"clerk-webhooks\",\n      \"clerk-testing\",\n      \"clerk-orgs\",\n    ];\n\n    if (config.frontend.includes(\"next\")) {\n      skills.push(\"clerk-nextjs-patterns\");\n    }\n\n    return skills;\n  },\n  \"neondatabase/agent-skills\": () => [\"neon-postgres\"],\n  \"supabase/agent-skills\": () => [\"supabase-postgres-best-practices\"],\n  \"planetscale/database-skills\": (config) => {\n    if (config.dbSetup !== \"planetscale\") {\n      return [];\n    }\n\n    if (config.database === \"postgres\") {\n      return [\"postgres\", \"neki\"];\n    }\n\n    if (config.database === \"mysql\") {\n      return [\"mysql\", \"vitess\"];\n    }\n\n    return [];\n  },\n  \"expo/skills\": (config) => {\n    const skills = [\n      \"expo-dev-client\",\n      \"building-native-ui\",\n      \"native-data-fetching\",\n      \"expo-deployment\",\n      \"expo-cicd-workflows\",\n    ];\n    if (config.frontend.includes(\"native-uniwind\")) {\n      skills.push(\"expo-tailwind-setup\");\n    }\n    return skills;\n  },\n  \"prisma/skills\": (config) => {\n    const skills: string[] = [];\n\n    if (config.orm === \"prisma\") {\n      skills.push(\"prisma-cli\", \"prisma-client-api\", \"prisma-database-setup\");\n    }\n\n    if (config.dbSetup === \"prisma-postgres\") {\n      skills.push(\"prisma-postgres\");\n    }\n\n    return skills;\n  },\n  \"elysiajs/skills\": () => [\"elysiajs\"],\n  \"waynesutton/convexskills\": () => [\n    \"convex-best-practices\",\n    \"convex-functions\",\n    \"convex-schema-validator\",\n    \"convex-realtime\",\n    \"convex-http-actions\",\n    \"convex-cron-jobs\",\n    \"convex-file-storage\",\n    \"convex-migrations\",\n    \"convex-security-check\",\n  ],\n  \"msmps/opentui-skill\": () => [\"opentui\"],\n  \"haydenbleasel/ultracite\": () => [\"ultracite\"],\n  \"https://www.evlog.dev\": () => [\"review-logging-patterns\", \"analyze-logs\"],\n};\n\nfunction getCuratedSkillNamesForSourceKey(sourceKey: SourceKey, config: ProjectConfig): string[] {\n  return CURATED_SKILLS_BY_SOURCE[sourceKey](config);\n}\n\nfunction uniqueValues<T>(values: T[]): T[] {\n  return Array.from(new Set(values));\n}\n\nexport async function setupSkills(\n  config: ProjectConfig,\n): Promise<Result<void, AddonSetupError | UserCancelledError>> {\n  if (shouldSkipExternalCommands()) {\n    return Result.ok(undefined);\n  }\n\n  const { packageManager, projectDir } = config;\n\n  // Load full config from bts.jsonc to get all addons (existing + new)\n  const btsConfig = await readBtsConfig(projectDir);\n  const fullConfig: ProjectConfig = btsConfig\n    ? {\n        ...config,\n        addons: btsConfig.addons ?? config.addons,\n        addonOptions: btsConfig.addonOptions ?? config.addonOptions,\n      }\n    : config;\n\n  const recommendedSourceKeys = getRecommendedSourceKeys(fullConfig);\n  const skillsOptions = fullConfig.addonOptions?.skills;\n  const configuredSourceKeys = uniqueValues(\n    (skillsOptions?.selections ?? []).map((selection) => selection.source),\n  );\n  const sourceKeys = uniqueValues([...recommendedSourceKeys, ...configuredSourceKeys]);\n\n  if (sourceKeys.length === 0) {\n    return Result.ok(undefined);\n  }\n\n  const skillOptions = sourceKeys.flatMap((sourceKey) => {\n    const source = SKILL_SOURCES[sourceKey];\n    const skillNames = getCuratedSkillNamesForSourceKey(sourceKey, fullConfig);\n    return skillNames.map((skillName) => ({\n      value: `${sourceKey}::${skillName}`,\n      label: skillName,\n      hint: source.label,\n    }));\n  });\n\n  if (skillOptions.length === 0) {\n    return Result.ok(undefined);\n  }\n\n  const configuredScope = skillsOptions?.scope;\n  const configuredSelections = skillsOptions?.selections;\n  const configuredAgents = skillsOptions?.agents;\n  const allSkillValues = skillOptions.map((opt) => opt.value);\n\n  let scope: InstallScope;\n  let selectedSkills: string[];\n  let selectedAgents: SkillAgent[];\n\n  if (isSilent()) {\n    scope = configuredScope ?? DEFAULT_SCOPE;\n    selectedSkills =\n      configuredSelections !== undefined\n        ? configuredSelections.flatMap((selection) =>\n            selection.skills.map((skill) => `${selection.source}::${skill}`),\n          )\n        : allSkillValues;\n    if (selectedSkills.length === 0) return Result.ok(undefined);\n    selectedAgents = configuredAgents ? [...configuredAgents] : [...DEFAULT_AGENTS];\n    if (selectedAgents.length === 0) return Result.ok(undefined);\n  } else {\n    const results = await navigableGroup<{\n      scope: InstallScope;\n      skills: string[];\n      agents: SkillAgent[];\n    }>({\n      scope: async () => {\n        if (configuredScope !== undefined) return configuredScope;\n        return navigableSelect<InstallScope>({\n          message: \"Where should skills be installed?\",\n          options: [\n            {\n              value: \"project\",\n              label: \"Project\",\n              hint: \"Writes to project config files (recommended for teams)\",\n            },\n            {\n              value: \"global\",\n              label: \"Global\",\n              hint: \"Writes to user-level config files (personal machine)\",\n            },\n          ],\n          initialValue: DEFAULT_SCOPE,\n        });\n      },\n      skills: async () => {\n        if (configuredSelections !== undefined) {\n          return configuredSelections.flatMap((selection) =>\n            selection.skills.map((skill) => `${selection.source}::${skill}`),\n          );\n        }\n        return navigableMultiselect<string>({\n          message: \"Select skills to install\",\n          options: skillOptions,\n          required: false,\n          initialValues: allSkillValues,\n        });\n      },\n      agents: async ({ results: r }) => {\n        const pickedSkills = r.skills as string[] | undefined;\n        if (pickedSkills !== undefined && pickedSkills.length === 0) return [];\n        if (configuredAgents !== undefined) return [...configuredAgents];\n        return navigableMultiselect<SkillAgent>({\n          message: \"Select agents to install skills to\",\n          options: AVAILABLE_AGENTS,\n          required: false,\n          initialValues: [...DEFAULT_AGENTS],\n        });\n      },\n    });\n\n    if (\n      results.scope === undefined ||\n      results.skills === undefined ||\n      results.agents === undefined\n    ) {\n      return Result.err(new UserCancelledError({ message: \"Operation cancelled\" }));\n    }\n\n    scope = results.scope;\n    selectedSkills = results.skills as string[];\n    selectedAgents = results.agents as SkillAgent[];\n\n    if (selectedSkills.length === 0 || selectedAgents.length === 0) {\n      return Result.ok(undefined);\n    }\n  }\n\n  // Group skills by source\n  const skillsBySource: Record<string, string[]> = {};\n  for (const skillKey of selectedSkills) {\n    const [source, skillName] = skillKey.split(\"::\");\n    if (!skillsBySource[source]) {\n      skillsBySource[source] = [];\n    }\n    skillsBySource[source].push(skillName);\n  }\n\n  const installSpinner = createSpinner();\n  installSpinner.start(\"Installing skills...\");\n\n  const runner = getPackageRunnerPrefix(packageManager);\n  const globalFlags = scope === \"global\" ? [\"-g\"] : [];\n\n  // Install skills grouped by source (project scope, no -g flag)\n  for (const [source, skills] of Object.entries(skillsBySource)) {\n    const installResult = await Result.tryPromise({\n      try: async () => {\n        const args = [\n          ...runner,\n          \"skills@latest\",\n          \"add\",\n          source,\n          ...globalFlags,\n          \"--skill\",\n          ...skills,\n          \"--agent\",\n          ...selectedAgents,\n          \"-y\",\n        ];\n        await $({ cwd: projectDir, env: { CI: \"true\" } })`${args}`;\n      },\n      catch: (e) =>\n        new AddonSetupError({\n          addon: \"skills\",\n          message: `Failed to install skills from ${source}: ${e instanceof Error ? e.message : String(e)}`,\n          cause: e,\n        }),\n    });\n\n    if (installResult.isErr()) {\n      cliLog.warn(pc.yellow(`Warning: Could not install skills from ${source}`));\n    }\n  }\n\n  installSpinner.stop(\"Skills installed\");\n\n  return Result.ok(undefined);\n}\n"
  },
  {
    "path": "apps/cli/src/helpers/addons/starlight-setup.ts",
    "content": "import path from \"node:path\";\n\nimport { Result } from \"better-result\";\nimport { $ } from \"execa\";\nimport fs from \"fs-extra\";\n\nimport type { ProjectConfig } from \"../../types\";\nimport { AddonSetupError } from \"../../utils/errors\";\nimport { shouldSkipExternalCommands } from \"../../utils/external-commands\";\nimport { getPackageExecutionArgs } from \"../../utils/package-runner\";\nimport { createSpinner } from \"../../utils/terminal-output\";\n\nexport async function setupStarlight(\n  config: ProjectConfig,\n): Promise<Result<void, AddonSetupError>> {\n  if (shouldSkipExternalCommands()) {\n    return Result.ok(undefined);\n  }\n\n  const { packageManager, projectDir } = config;\n  const s = createSpinner();\n\n  s.start(\"Setting up Starlight docs...\");\n\n  const starlightArgs = [\n    \"docs\",\n    \"--template\",\n    \"starlight\",\n    \"--yes\",\n    \"--no-install\",\n    \"--no-git\",\n    \"--skip-houston\",\n  ];\n  const starlightArgsString = starlightArgs.join(\" \");\n\n  const commandWithArgs = `create-astro@latest ${starlightArgsString}`;\n  const args = getPackageExecutionArgs(packageManager, commandWithArgs);\n\n  const appsDir = path.join(projectDir, \"apps\");\n  await fs.ensureDir(appsDir);\n\n  const result = await Result.tryPromise({\n    try: async () => {\n      await $({ cwd: appsDir, env: { CI: \"true\" } })`${args}`;\n    },\n    catch: (e) =>\n      new AddonSetupError({\n        addon: \"starlight\",\n        message: `Failed to set up Starlight docs: ${e instanceof Error ? e.message : String(e)}`,\n        cause: e,\n      }),\n  });\n\n  if (result.isErr()) {\n    s.stop(\"Failed to set up Starlight docs\");\n    return result;\n  }\n\n  s.stop(\"Starlight docs setup successfully!\");\n  return Result.ok(undefined);\n}\n"
  },
  {
    "path": "apps/cli/src/helpers/addons/tauri-setup.ts",
    "content": "import path from \"node:path\";\n\nimport { Result } from \"better-result\";\nimport { execa } from \"execa\";\nimport fs from \"fs-extra\";\n\nimport { desktopWebFrontends, type ProjectConfig } from \"../../types\";\nimport { AddonSetupError } from \"../../utils/errors\";\nimport { shouldSkipExternalCommands } from \"../../utils/external-commands\";\nimport { getPackageRunnerPrefix } from \"../../utils/package-runner\";\nimport { createSpinner } from \"../../utils/terminal-output\";\n\nfunction getWebFrontend(frontend: Pick<ProjectConfig, \"frontend\">[\"frontend\"]) {\n  return frontend.find((value) => (desktopWebFrontends as readonly string[]).includes(value));\n}\n\nfunction getTauriDevUrl(frontend: Pick<ProjectConfig, \"frontend\">[\"frontend\"]) {\n  const webFrontend = getWebFrontend(frontend);\n\n  switch (webFrontend) {\n    case \"react-router\":\n    case \"svelte\":\n      return \"http://localhost:5173\";\n    case \"astro\":\n      return \"http://localhost:4321\";\n    default:\n      return \"http://localhost:3001\";\n  }\n}\n\nfunction getTauriFrontendDist(frontend: Pick<ProjectConfig, \"frontend\">[\"frontend\"]) {\n  const webFrontend = getWebFrontend(frontend);\n\n  switch (webFrontend) {\n    case \"react-router\":\n      return \"../build/client\";\n    case \"tanstack-start\":\n      return \"../dist/client\";\n    case \"next\":\n      return \"../out\";\n    case \"nuxt\":\n      return \"../.output/public\";\n    case \"svelte\":\n      return \"../build\";\n    default:\n      return \"../dist\";\n  }\n}\n\nfunction getTauriBeforeBuildCommand(\n  packageManager: Pick<ProjectConfig, \"packageManager\">[\"packageManager\"],\n  frontend: Pick<ProjectConfig, \"frontend\">[\"frontend\"],\n) {\n  return frontend.includes(\"nuxt\")\n    ? `${packageManager} run generate`\n    : `${packageManager} run build`;\n}\n\nexport function buildTauriInitArgs(\n  config: Pick<ProjectConfig, \"packageManager\" | \"frontend\" | \"projectDir\">,\n) {\n  const { packageManager, frontend, projectDir } = config;\n\n  return [\n    ...getPackageRunnerPrefix(packageManager),\n    \"@tauri-apps/cli@latest\",\n    \"init\",\n    \"--ci\",\n    \"--app-name\",\n    path.basename(projectDir),\n    \"--window-title\",\n    path.basename(projectDir),\n    \"--frontend-dist\",\n    getTauriFrontendDist(frontend),\n    \"--dev-url\",\n    getTauriDevUrl(frontend),\n    \"--before-dev-command\",\n    `${packageManager} run dev`,\n    \"--before-build-command\",\n    getTauriBeforeBuildCommand(packageManager, frontend),\n  ];\n}\n\nexport async function setupTauri(config: ProjectConfig): Promise<Result<void, AddonSetupError>> {\n  if (shouldSkipExternalCommands()) {\n    return Result.ok(undefined);\n  }\n\n  const { packageManager, frontend, projectDir } = config;\n  const s = createSpinner();\n  const clientPackageDir = path.join(projectDir, \"apps/web\");\n\n  if (!(await fs.pathExists(clientPackageDir))) {\n    return Result.ok(undefined);\n  }\n\n  s.start(\"Setting up Tauri desktop app support...\");\n\n  const [command, ...args] = buildTauriInitArgs({ packageManager, frontend, projectDir });\n\n  const result = await Result.tryPromise({\n    try: async () => {\n      await execa(command, args, {\n        cwd: clientPackageDir,\n        env: { CI: \"true\" },\n      });\n    },\n    catch: (e) =>\n      new AddonSetupError({\n        addon: \"tauri\",\n        message: `Failed to set up Tauri: ${e instanceof Error ? e.message : String(e)}`,\n        cause: e,\n      }),\n  });\n\n  if (result.isErr()) {\n    s.stop(\"Failed to set up Tauri\");\n    return result;\n  }\n\n  s.stop(\"Tauri desktop app support configured successfully!\");\n  return Result.ok(undefined);\n}\n"
  },
  {
    "path": "apps/cli/src/helpers/addons/tui-setup.ts",
    "content": "import path from \"node:path\";\n\nimport { Result } from \"better-result\";\nimport { $ } from \"execa\";\nimport fs from \"fs-extra\";\nimport pc from \"picocolors\";\n\nimport { isCancel, navigableSelect, setIsFirstPrompt } from \"../../prompts/navigable\";\nimport type { ProjectConfig } from \"../../types\";\nimport { isSilent } from \"../../utils/context\";\nimport { AddonSetupError, UserCancelledError, userCancelled } from \"../../utils/errors\";\nimport { shouldSkipExternalCommands } from \"../../utils/external-commands\";\nimport { getPackageExecutionArgs } from \"../../utils/package-runner\";\nimport { cliLog, createSpinner } from \"../../utils/terminal-output\";\n\ntype TuiTemplate = \"core\" | \"react\" | \"solid\";\n\ntype TuiSetupResult = Result<void, AddonSetupError | UserCancelledError>;\n\nconst TEMPLATES = {\n  core: {\n    label: \"Core\",\n    hint: \"Basic OpenTUI template\",\n  },\n  react: {\n    label: \"React\",\n    hint: \"React-based OpenTUI template\",\n  },\n  solid: {\n    label: \"Solid\",\n    hint: \"SolidJS-based OpenTUI template\",\n  },\n} as const;\n\nconst DEFAULT_TEMPLATE: TuiTemplate = \"core\";\nconst TUI_LOCKFILES = [\"bun.lock\", \"package-lock.json\", \"pnpm-lock.yaml\", \"yarn.lock\"] as const;\n\nexport function resolveTuiTemplate(config: ProjectConfig): TuiTemplate | undefined {\n  const configuredTemplate = config.addonOptions?.opentui?.template;\n\n  if (configuredTemplate) {\n    return configuredTemplate;\n  }\n\n  if (isSilent()) {\n    return DEFAULT_TEMPLATE;\n  }\n\n  return undefined;\n}\n\nexport async function setupTui(config: ProjectConfig): Promise<TuiSetupResult> {\n  if (shouldSkipExternalCommands()) {\n    return Result.ok(undefined);\n  }\n\n  const { packageManager, projectDir } = config;\n\n  cliLog.info(\"Setting up OpenTUI...\");\n\n  let template = resolveTuiTemplate(config);\n\n  if (!template) {\n    setIsFirstPrompt(true);\n    const selectedTemplate = await navigableSelect<TuiTemplate>({\n      message: \"Choose a template\",\n      options: Object.entries(TEMPLATES).map(([key, templateOption]) => ({\n        value: key as TuiTemplate,\n        label: templateOption.label,\n        hint: templateOption.hint,\n      })),\n      initialValue: DEFAULT_TEMPLATE,\n    });\n\n    if (isCancel(selectedTemplate)) {\n      return userCancelled(\"Operation cancelled\");\n    }\n\n    template = selectedTemplate as TuiTemplate;\n  }\n\n  const commandWithArgs = `create-tui@latest --template ${template} --no-git --no-install tui`;\n  const args = getPackageExecutionArgs(packageManager, commandWithArgs);\n\n  const appsDir = path.join(projectDir, \"apps\");\n\n  const ensureDirResult = await Result.tryPromise({\n    try: () => fs.ensureDir(appsDir),\n    catch: (e) =>\n      new AddonSetupError({\n        addon: \"tui\",\n        message: `Failed to create directory: ${e instanceof Error ? e.message : String(e)}`,\n        cause: e,\n      }),\n  });\n\n  if (ensureDirResult.isErr()) {\n    return ensureDirResult;\n  }\n\n  const s = createSpinner();\n  s.start(\"Running OpenTUI create command...\");\n\n  const initResult = await Result.tryPromise({\n    try: async () => {\n      await $({ cwd: appsDir, env: { CI: \"true\" } })`${args}`;\n    },\n    catch: (e) => {\n      s.stop(pc.red(\"Failed to run OpenTUI create command\"));\n      return new AddonSetupError({\n        addon: \"tui\",\n        message: `Failed to set up OpenTUI: ${e instanceof Error ? e.message : String(e)}`,\n        cause: e,\n      });\n    },\n  });\n\n  if (initResult.isErr()) {\n    cliLog.error(pc.red(\"Failed to set up OpenTUI\"));\n    return initResult;\n  }\n\n  const postProcessResult = await postProcessTuiWorkspace(path.join(appsDir, \"tui\"));\n  if (postProcessResult.isErr()) {\n    s.stop(pc.yellow(\"OpenTUI setup completed with warnings\"));\n    cliLog.warn(pc.yellow(\"OpenTUI setup completed but workspace normalization had warnings\"));\n    return postProcessResult;\n  }\n\n  s.stop(\"OpenTUI setup complete!\");\n  return Result.ok(undefined);\n}\n\nexport async function postProcessTuiWorkspace(\n  tuiDir: string,\n): Promise<Result<void, AddonSetupError | UserCancelledError>> {\n  const packageJsonPath = path.join(tuiDir, \"package.json\");\n\n  const packageJsonResult = await Result.tryPromise({\n    try: async () => {\n      const packageJson = await fs.readJson(packageJsonPath);\n      packageJson.scripts = packageJson.scripts || {};\n\n      if (!packageJson.scripts[\"check-types\"]) {\n        packageJson.scripts[\"check-types\"] = \"tsc --noEmit\";\n      }\n\n      await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 });\n    },\n    catch: (e) =>\n      new AddonSetupError({\n        addon: \"tui\",\n        message: `Failed to normalize OpenTUI package.json: ${e instanceof Error ? e.message : String(e)}`,\n        cause: e,\n      }),\n  });\n\n  if (packageJsonResult.isErr()) {\n    return packageJsonResult;\n  }\n\n  for (const lockfile of TUI_LOCKFILES) {\n    const lockfilePath = path.join(tuiDir, lockfile);\n\n    const removeLockfileResult = await Result.tryPromise({\n      try: async () => {\n        if (await fs.pathExists(lockfilePath)) {\n          await fs.remove(lockfilePath);\n        }\n      },\n      catch: (e) =>\n        new AddonSetupError({\n          addon: \"tui\",\n          message: `Failed to remove nested OpenTUI lockfile '${lockfile}': ${e instanceof Error ? e.message : String(e)}`,\n          cause: e,\n        }),\n    });\n\n    if (removeLockfileResult.isErr()) {\n      return removeLockfileResult;\n    }\n  }\n\n  return Result.ok(undefined);\n}\n"
  },
  {
    "path": "apps/cli/src/helpers/addons/ultracite-setup.ts",
    "content": "import { Result } from \"better-result\";\nimport { $ } from \"execa\";\nimport pc from \"picocolors\";\n\nimport { navigableMultiselect, navigableSelect } from \"../../prompts/navigable\";\nimport { navigableGroup } from \"../../prompts/navigable-group\";\nimport type { ProjectConfig } from \"../../types\";\nimport { isSilent } from \"../../utils/context\";\nimport { AddonSetupError, UserCancelledError, userCancelled } from \"../../utils/errors\";\nimport { shouldSkipExternalCommands } from \"../../utils/external-commands\";\nimport { getPackageRunnerPrefix } from \"../../utils/package-runner\";\nimport { cliLog, createSpinner } from \"../../utils/terminal-output\";\n\ntype UltraciteLinter = \"biome\" | \"eslint\" | \"oxlint\";\n\ntype UltraciteEditor =\n  | \"vscode\"\n  | \"cursor\"\n  | \"windsurf\"\n  | \"codebuddy\"\n  | \"antigravity\"\n  | \"bob\"\n  | \"kiro\"\n  | \"trae\"\n  | \"void\"\n  | \"zed\";\n\ntype UltraciteAgent =\n  | \"universal\"\n  | \"claude\"\n  | \"codex\"\n  | \"jules\"\n  | \"replit\"\n  | \"devin\"\n  | \"lovable\"\n  | \"zencoder\"\n  | \"ona\"\n  | \"openclaw\"\n  | \"continue\"\n  | \"snowflake-cortex\"\n  | \"deepagents\"\n  | \"qoder\"\n  | \"kimi-cli\"\n  | \"mcpjam\"\n  | \"mux\"\n  | \"pi\"\n  | \"adal\"\n  | \"copilot\"\n  | \"cline\"\n  | \"amp\"\n  | \"aider\"\n  | \"firebase-studio\"\n  | \"open-hands\"\n  | \"gemini\"\n  | \"junie\"\n  | \"augmentcode\"\n  | \"bob\"\n  | \"kilo-code\"\n  | \"goose\"\n  | \"roo-code\"\n  | \"warp\"\n  | \"droid\"\n  | \"opencode\"\n  | \"crush\"\n  | \"qwen\"\n  | \"amazon-q-cli\"\n  | \"firebender\"\n  | \"cursor-cli\"\n  | \"mistral-vibe\"\n  | \"vercel\";\n\ntype UltraciteHook = \"cursor\" | \"windsurf\" | \"codebuddy\" | \"claude\" | \"copilot\";\n\ntype UltraciteSetupResult = Result<void, AddonSetupError | UserCancelledError>;\ntype UltraciteInitArgsInput = {\n  packageManager: ProjectConfig[\"packageManager\"];\n  linter: UltraciteLinter;\n  frameworks: string[];\n  editors: UltraciteEditor[];\n  agents: UltraciteAgent[];\n  hooks: UltraciteHook[];\n  gitHooks: string[];\n};\n\nconst LINTERS = {\n  biome: { label: \"Biome (Recommended)\" },\n  eslint: { label: \"ESLint + Prettier + Stylelint\" },\n  oxlint: { label: \"Oxlint + Oxfmt\" },\n} as const;\n\nconst AGENTS = {\n  universal: { label: \"Universal (AGENTS.md — covers all agents)\" },\n  claude: { label: \"Claude Code\" },\n  codex: { label: \"Codex\" },\n  jules: { label: \"Jules\" },\n  replit: { label: \"Replit Agent\" },\n  devin: { label: \"Devin\" },\n  lovable: { label: \"Lovable\" },\n  zencoder: { label: \"Zencoder\" },\n  ona: { label: \"Ona\" },\n  openclaw: { label: \"OpenClaw\" },\n  continue: { label: \"Continue\" },\n  \"snowflake-cortex\": { label: \"Snowflake Cortex\" },\n  deepagents: { label: \"Deepagents\" },\n  qoder: { label: \"Qoder\" },\n  \"kimi-cli\": { label: \"Kimi CLI\" },\n  mcpjam: { label: \"MCPJam\" },\n  mux: { label: \"Mux\" },\n  pi: { label: \"Pi\" },\n  adal: { label: \"AdaL\" },\n  copilot: { label: \"GitHub Copilot\" },\n  cline: { label: \"Cline\" },\n  amp: { label: \"AMP\" },\n  aider: { label: \"Aider\" },\n  \"firebase-studio\": { label: \"Firebase Studio\" },\n  \"open-hands\": { label: \"OpenHands\" },\n  gemini: { label: \"Gemini\" },\n  junie: { label: \"Junie\" },\n  augmentcode: { label: \"Augment Code\" },\n  bob: { label: \"IBM Bob\" },\n  \"kilo-code\": { label: \"Kilo Code\" },\n  goose: { label: \"Goose\" },\n  \"roo-code\": { label: \"Roo Code\" },\n  warp: { label: \"Warp\" },\n  droid: { label: \"Droid\" },\n  opencode: { label: \"OpenCode\" },\n  crush: { label: \"Crush\" },\n  qwen: { label: \"Qwen Code\" },\n  \"amazon-q-cli\": { label: \"Amazon Q CLI\" },\n  firebender: { label: \"Firebender\" },\n  \"cursor-cli\": { label: \"Cursor CLI\" },\n  \"mistral-vibe\": { label: \"Mistral Vibe\" },\n  vercel: { label: \"Vercel Agent\" },\n} as const;\n\nconst HOOKS = {\n  cursor: { label: \"Cursor\" },\n  windsurf: { label: \"Windsurf\" },\n  codebuddy: { label: \"CodeBuddy\" },\n  claude: { label: \"Claude Code\" },\n  copilot: { label: \"GitHub Copilot\" },\n} as const;\n\nconst DEFAULT_LINTER: UltraciteLinter = \"biome\";\nconst DEFAULT_EDITORS: UltraciteEditor[] = [\"vscode\"];\nconst DEFAULT_AGENTS: UltraciteAgent[] = [\"universal\"];\nconst DEFAULT_HOOKS: UltraciteHook[] = [];\n\nfunction getFrameworksFromFrontend(frontend: string[]): string[] {\n  const frameworkMap: Record<string, string> = {\n    \"tanstack-router\": \"react\",\n    \"react-router\": \"react\",\n    \"tanstack-start\": \"react\",\n    next: \"next\",\n    nuxt: \"vue\",\n    \"native-bare\": \"react\",\n    \"native-uniwind\": \"react\",\n    \"native-unistyles\": \"react\",\n    svelte: \"svelte\",\n    solid: \"solid\",\n    astro: \"astro\",\n  };\n\n  const frameworks = new Set<string>();\n\n  for (const f of frontend) {\n    if (f !== \"none\" && frameworkMap[f]) {\n      frameworks.add(frameworkMap[f]);\n    }\n  }\n\n  return Array.from(frameworks);\n}\n\nexport function buildUltraciteInitArgs({\n  packageManager,\n  linter,\n  frameworks,\n  editors,\n  agents,\n  hooks,\n  gitHooks,\n}: UltraciteInitArgsInput): string[] {\n  const ultraciteArgs = [\"init\", \"--pm\", packageManager, \"--linter\", linter];\n\n  if (frameworks.length > 0) {\n    ultraciteArgs.push(\"--frameworks\", ...frameworks);\n  }\n\n  if (editors.length > 0) {\n    ultraciteArgs.push(\"--editors\", ...editors);\n  }\n\n  if (agents.length > 0) {\n    ultraciteArgs.push(\"--agents\", ...agents);\n  }\n\n  if (hooks.length > 0) {\n    ultraciteArgs.push(\"--hooks\", ...hooks);\n  }\n\n  if (gitHooks.length > 0) {\n    ultraciteArgs.push(\"--integrations\", ...gitHooks);\n  }\n\n  return [\n    ...getPackageRunnerPrefix(packageManager),\n    \"ultracite@latest\",\n    ...ultraciteArgs,\n    \"--skip-install\",\n    \"--quiet\",\n  ];\n}\n\nexport async function setupUltracite(\n  config: ProjectConfig,\n  gitHooks: string[],\n): Promise<UltraciteSetupResult> {\n  if (shouldSkipExternalCommands()) {\n    return Result.ok(undefined);\n  }\n\n  const { packageManager, projectDir, frontend } = config;\n\n  cliLog.info(\"Setting up Ultracite...\");\n\n  const configuredOptions = config.addonOptions?.ultracite;\n  let linter = configuredOptions?.linter;\n  let editors = configuredOptions?.editors;\n  let agents = configuredOptions?.agents;\n  let hooks = configuredOptions?.hooks;\n\n  if (!linter || !editors || !agents || !hooks) {\n    if (isSilent()) {\n      linter = linter ?? DEFAULT_LINTER;\n      editors = editors ?? [...DEFAULT_EDITORS];\n      agents = agents ?? [...DEFAULT_AGENTS];\n      hooks = hooks ?? [...DEFAULT_HOOKS];\n    } else {\n      const results = await navigableGroup<{\n        linter: UltraciteLinter;\n        editors: UltraciteEditor[];\n        agents: UltraciteAgent[];\n        hooks: UltraciteHook[];\n      }>({\n        linter: async () => {\n          if (linter !== undefined) return linter;\n          return navigableSelect<UltraciteLinter>({\n            message: \"Which linter do you want to use?\",\n            options: Object.entries(LINTERS).map(([key, linterOption]) => ({\n              value: key as UltraciteLinter,\n              label: linterOption.label,\n            })),\n            initialValue: linter ?? DEFAULT_LINTER,\n          });\n        },\n        editors: async () => {\n          if (editors !== undefined) return editors;\n          return navigableMultiselect<UltraciteEditor>({\n            message: \"Which editors do you want to configure (recommended)?\",\n            required: false,\n            options: [\n              { value: \"vscode\", label: \"VSCode / Cursor / Windsurf\" },\n              { value: \"zed\", label: \"Zed\" },\n            ],\n          });\n        },\n        agents: async () => {\n          if (agents !== undefined) return agents;\n          return navigableMultiselect<UltraciteAgent>({\n            message: \"Which agent files do you want to add (optional)?\",\n            required: false,\n            options: Object.entries(AGENTS).map(([key, agent]) => ({\n              value: key as UltraciteAgent,\n              label: agent.label,\n            })),\n          });\n        },\n        hooks: async () => {\n          if (hooks !== undefined) return hooks;\n          return navigableMultiselect<UltraciteHook>({\n            message: \"Which agent hooks do you want to enable (optional)?\",\n            required: false,\n            options: Object.entries(HOOKS).map(([key, hook]) => ({\n              value: key as UltraciteHook,\n              label: hook.label,\n            })),\n          });\n        },\n      });\n\n      if (\n        results.linter === undefined ||\n        results.editors === undefined ||\n        results.agents === undefined ||\n        results.hooks === undefined\n      ) {\n        return userCancelled(\"Operation cancelled\");\n      }\n\n      linter = results.linter;\n      editors = results.editors;\n      agents = results.agents;\n      hooks = results.hooks;\n    }\n  }\n\n  const frameworks = getFrameworksFromFrontend(frontend);\n  const args = buildUltraciteInitArgs({\n    packageManager,\n    linter,\n    frameworks,\n    editors,\n    agents,\n    hooks,\n    gitHooks,\n  });\n\n  const s = createSpinner();\n  s.start(\"Running Ultracite init command...\");\n\n  const initResult = await Result.tryPromise({\n    try: async () => {\n      await $({ cwd: projectDir, env: { CI: \"true\" } })`${args}`;\n    },\n    catch: (e) => {\n      s.stop(pc.red(\"Failed to run Ultracite init command\"));\n      return new AddonSetupError({\n        addon: \"ultracite\",\n        message: `Failed to set up Ultracite: ${e instanceof Error ? e.message : String(e)}`,\n        cause: e,\n      });\n    },\n  });\n\n  if (initResult.isErr()) {\n    cliLog.error(pc.red(\"Failed to set up Ultracite\"));\n    return initResult;\n  }\n\n  s.stop(\"Ultracite setup successfully!\");\n  return Result.ok(undefined);\n}\n"
  },
  {
    "path": "apps/cli/src/helpers/addons/wxt-setup.ts",
    "content": "import path from \"node:path\";\n\nimport { Result } from \"better-result\";\nimport { $ } from \"execa\";\nimport fs from \"fs-extra\";\nimport pc from \"picocolors\";\n\nimport { isCancel, navigableSelect, setIsFirstPrompt } from \"../../prompts/navigable\";\nimport type { ProjectConfig } from \"../../types\";\nimport { isSilent } from \"../../utils/context\";\nimport { AddonSetupError, UserCancelledError, userCancelled } from \"../../utils/errors\";\nimport { shouldSkipExternalCommands } from \"../../utils/external-commands\";\nimport { getPackageExecutionArgs } from \"../../utils/package-runner\";\nimport { cliLog, createSpinner } from \"../../utils/terminal-output\";\n\ntype WxtTemplate = \"vanilla\" | \"vue\" | \"react\" | \"solid\" | \"svelte\";\n\ntype WxtSetupResult = Result<void, AddonSetupError | UserCancelledError>;\n\nconst TEMPLATES = {\n  vanilla: {\n    label: \"Vanilla\",\n    hint: \"Vanilla JavaScript template\",\n  },\n  vue: {\n    label: \"Vue\",\n    hint: \"Vue.js template\",\n  },\n  react: {\n    label: \"React\",\n    hint: \"React template\",\n  },\n  solid: {\n    label: \"Solid\",\n    hint: \"SolidJS template\",\n  },\n  svelte: {\n    label: \"Svelte\",\n    hint: \"Svelte template\",\n  },\n} as const;\n\nconst DEFAULT_TEMPLATE: WxtTemplate = \"react\";\nconst DEFAULT_DEV_PORT = 5555;\n\nexport async function setupWxt(config: ProjectConfig): Promise<WxtSetupResult> {\n  if (shouldSkipExternalCommands()) {\n    return Result.ok(undefined);\n  }\n\n  const { packageManager, projectDir } = config;\n\n  cliLog.info(\"Setting up WXT...\");\n\n  const configuredOptions = config.addonOptions?.wxt;\n  let template = configuredOptions?.template;\n\n  if (!template) {\n    if (isSilent()) {\n      template = DEFAULT_TEMPLATE;\n    } else {\n      setIsFirstPrompt(true);\n      const selectedTemplate = await navigableSelect<WxtTemplate>({\n        message: \"Choose a template\",\n        options: Object.entries(TEMPLATES).map(([key, templateOption]) => ({\n          value: key as WxtTemplate,\n          label: templateOption.label,\n          hint: templateOption.hint,\n        })),\n        initialValue: DEFAULT_TEMPLATE,\n      });\n\n      if (isCancel(selectedTemplate)) {\n        return userCancelled(\"Operation cancelled\");\n      }\n\n      template = selectedTemplate as WxtTemplate;\n    }\n  }\n\n  const devPort = configuredOptions?.devPort ?? DEFAULT_DEV_PORT;\n\n  const commandWithArgs = `wxt@latest init extension --template ${template} --pm ${packageManager}`;\n  const args = getPackageExecutionArgs(packageManager, commandWithArgs);\n\n  const appsDir = path.join(projectDir, \"apps\");\n\n  const ensureDirResult = await Result.tryPromise({\n    try: () => fs.ensureDir(appsDir),\n    catch: (e) =>\n      new AddonSetupError({\n        addon: \"wxt\",\n        message: `Failed to create directory: ${e instanceof Error ? e.message : String(e)}`,\n        cause: e,\n      }),\n  });\n\n  if (ensureDirResult.isErr()) {\n    return ensureDirResult;\n  }\n\n  const s = createSpinner();\n  s.start(\"Running WXT init command...\");\n\n  const initResult = await Result.tryPromise({\n    try: async () => {\n      await $({ cwd: appsDir, env: { CI: \"true\" } })`${args}`;\n    },\n    catch: (e) => {\n      s.stop(pc.red(\"Failed to run WXT init command\"));\n      return new AddonSetupError({\n        addon: \"wxt\",\n        message: `Failed to set up WXT: ${e instanceof Error ? e.message : String(e)}`,\n        cause: e,\n      });\n    },\n  });\n\n  if (initResult.isErr()) {\n    cliLog.error(pc.red(\"Failed to set up WXT\"));\n    return initResult;\n  }\n\n  const extensionDir = path.join(projectDir, \"apps\", \"extension\");\n  const packageJsonPath = path.join(extensionDir, \"package.json\");\n\n  const updatePackageResult = await Result.tryPromise({\n    try: async () => {\n      if (await fs.pathExists(packageJsonPath)) {\n        const packageJson = await fs.readJson(packageJsonPath);\n        packageJson.name = \"extension\";\n\n        if (packageJson.scripts?.dev) {\n          packageJson.scripts.dev = `${packageJson.scripts.dev} --port ${devPort}`;\n        }\n\n        await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 });\n      }\n    },\n    catch: (e) =>\n      new AddonSetupError({\n        addon: \"wxt\",\n        message: `Failed to update package.json: ${e instanceof Error ? e.message : String(e)}`,\n        cause: e,\n      }),\n  });\n\n  if (updatePackageResult.isErr()) {\n    // Log but don't fail - the main setup succeeded\n    cliLog.warn(pc.yellow(\"WXT setup completed but failed to update package.json\"));\n  }\n\n  s.stop(\"WXT setup complete!\");\n  return Result.ok(undefined);\n}\n"
  },
  {
    "path": "apps/cli/src/helpers/core/add-handler.ts",
    "content": "import path from \"node:path\";\n\nimport {\n  EMBEDDED_TEMPLATES,\n  processAddonTemplates,\n  processAddonsDeps,\n  VirtualFileSystem,\n} from \"@better-t-stack/template-generator\";\nimport { writeTree } from \"@better-t-stack/template-generator/fs-writer\";\nimport { intro, log, outro } from \"@clack/prompts\";\nimport { Result } from \"better-result\";\nimport fs from \"fs-extra\";\nimport pc from \"picocolors\";\n\nimport { getAddonsToAdd } from \"../../prompts/addons\";\nimport type { AddInput, Addons, AddonOptions, ProjectConfig } from \"../../types\";\nimport { updateBtsConfig } from \"../../utils/bts-config\";\nimport { validateAddonsAgainstConfig } from \"../../utils/compatibility-rules\";\nimport { isSilent, runWithContextAsync } from \"../../utils/context\";\nimport { CLIError, UserCancelledError, displayError } from \"../../utils/errors\";\nimport { validateAgentSafePathInput } from \"../../utils/input-hardening\";\nimport { renderTitle } from \"../../utils/render-title\";\nimport { setupAddons } from \"../addons/addons-setup\";\nimport { detectProjectConfig } from \"./detect-project-config\";\nimport { installDependencies } from \"./install-dependencies\";\n\nexport interface AddHandlerOptions {\n  silent?: boolean;\n}\n\nexport interface AddResult {\n  success: boolean;\n  addedAddons: Addons[];\n  projectDir: string;\n  dryRun?: boolean;\n  plannedFileCount?: number;\n  error?: string;\n}\n\nfunction mergeAddonOptions(\n  existingAddonOptions?: AddonOptions,\n  nextAddonOptions?: AddonOptions,\n): AddonOptions | undefined {\n  if (!existingAddonOptions && !nextAddonOptions) {\n    return undefined;\n  }\n\n  const mergedAddonOptions: Partial<AddonOptions> = { ...existingAddonOptions };\n\n  if (nextAddonOptions) {\n    for (const addonKey of Object.keys(nextAddonOptions) as (keyof AddonOptions)[]) {\n      const existingOptionsForAddon = existingAddonOptions?.[addonKey];\n      const nextOptionsForAddon = nextAddonOptions[addonKey];\n      const mergedOptionsForAddon =\n        existingOptionsForAddon && nextOptionsForAddon\n          ? { ...existingOptionsForAddon, ...nextOptionsForAddon }\n          : nextOptionsForAddon;\n\n      (\n        mergedAddonOptions as Record<\n          keyof AddonOptions,\n          AddonOptions[keyof AddonOptions] | undefined\n        >\n      )[addonKey] = mergedOptionsForAddon as AddonOptions[keyof AddonOptions];\n    }\n  }\n\n  return Object.keys(mergedAddonOptions).length > 0\n    ? (mergedAddonOptions as AddonOptions)\n    : undefined;\n}\n\nexport async function addHandler(\n  input: AddInput,\n  options: AddHandlerOptions = {},\n): Promise<AddResult | undefined> {\n  const { silent = false } = options;\n\n  return runWithContextAsync({ silent }, async () => {\n    const result = await addHandlerInternal(input);\n\n    if (result.isOk()) {\n      return result.value;\n    }\n\n    const error = result.error;\n\n    if (UserCancelledError.is(error)) {\n      if (isSilent()) {\n        return {\n          success: false,\n          addedAddons: [],\n          projectDir: \"\",\n          error: error.message,\n        };\n      }\n      return undefined;\n    }\n\n    if (isSilent()) {\n      return {\n        success: false,\n        addedAddons: [],\n        projectDir: \"\",\n        error: error.message,\n      };\n    }\n\n    displayError(error);\n    process.exit(1);\n  });\n}\n\nasync function addHandlerInternal(\n  input: AddInput,\n): Promise<Result<AddResult, UserCancelledError | CLIError>> {\n  const projectDir = input.projectDir || process.cwd();\n  const hardeningResult = validateAgentSafePathInput(projectDir, \"projectDir\");\n  if (hardeningResult.isErr()) {\n    return Result.err(\n      new CLIError({\n        message: hardeningResult.error.message,\n        cause: hardeningResult.error,\n      }),\n    );\n  }\n\n  if (!isSilent()) {\n    renderTitle();\n    intro(pc.magenta(\"Add addons to your Better-T-Stack project\"));\n  }\n\n  // Detect existing project configuration\n  const existingConfig = await detectProjectConfig(projectDir);\n\n  if (!existingConfig) {\n    return Result.err(\n      new CLIError({\n        message: `No Better-T-Stack project found in ${projectDir}. Make sure bts.jsonc exists.`,\n      }),\n    );\n  }\n\n  if (!isSilent()) {\n    log.info(pc.dim(`Detected project: ${existingConfig.projectName}`));\n  }\n\n  // Determine which addons to add\n  let addonsToAdd: Addons[];\n\n  if (input.addons && input.addons.length > 0) {\n    // Filter out 'none' and already installed addons\n    addonsToAdd = input.addons.filter(\n      (addon) => addon !== \"none\" && !existingConfig.addons.includes(addon),\n    );\n\n    if (addonsToAdd.length === 0) {\n      if (!isSilent()) {\n        log.warn(pc.yellow(\"All specified addons are already installed or invalid.\"));\n      }\n      return Result.ok({\n        success: true,\n        addedAddons: [],\n        projectDir,\n      });\n    }\n  } else if (isSilent()) {\n    return Result.err(\n      new CLIError({\n        message: \"Addons are required in silent mode. Provide them via add() or add-json.\",\n      }),\n    );\n  } else {\n    // Interactive mode - prompt user to select addons\n    const promptResult = await Result.tryPromise({\n      try: () => getAddonsToAdd(existingConfig),\n      catch: (e: unknown) => {\n        if (UserCancelledError.is(e)) return e;\n        return new CLIError({\n          message: e instanceof Error ? e.message : String(e),\n          cause: e,\n        });\n      },\n    });\n\n    if (promptResult.isErr()) {\n      return Result.err(promptResult.error);\n    }\n\n    const selectedAddons = promptResult.value;\n\n    if (selectedAddons.length === 0) {\n      if (!isSilent()) {\n        log.info(pc.dim(\"No addons selected.\"));\n        outro(pc.magenta(\"Nothing to add.\"));\n      }\n      return Result.ok({\n        success: true,\n        addedAddons: [],\n        projectDir,\n      });\n    }\n\n    addonsToAdd = selectedAddons;\n  }\n\n  const addonsValidationResult = validateAddonsAgainstConfig(addonsToAdd, existingConfig);\n  if (addonsValidationResult.isErr()) {\n    return Result.err(new CLIError({ message: addonsValidationResult.error.message }));\n  }\n\n  if (!isSilent()) {\n    log.info(pc.cyan(`Adding addons: ${addonsToAdd.join(\", \")}`));\n  }\n\n  // Build config for addon setup\n  const updatedAddons = [...existingConfig.addons, ...addonsToAdd];\n  const mergedAddonOptions = mergeAddonOptions(existingConfig.addonOptions, input.addonOptions);\n  const config: ProjectConfig = {\n    projectName: existingConfig.projectName,\n    projectDir,\n    relativePath: \".\",\n    addonOptions: mergedAddonOptions,\n    database: existingConfig.database,\n    orm: existingConfig.orm,\n    backend: existingConfig.backend,\n    runtime: existingConfig.runtime,\n    frontend: existingConfig.frontend,\n    addons: addonsToAdd, // Only the new addons for template processing\n    examples: existingConfig.examples,\n    auth: existingConfig.auth,\n    payments: existingConfig.payments,\n    git: false,\n    packageManager: input.packageManager || existingConfig.packageManager,\n    install: input.install ?? false,\n    dbSetup: existingConfig.dbSetup,\n    api: existingConfig.api,\n    webDeploy: existingConfig.webDeploy,\n    serverDeploy: existingConfig.serverDeploy,\n  };\n\n  // Create VFS and process addon templates using template-generator's logic\n  if (!isSilent()) {\n    log.info(pc.dim(\"Installing addon files...\"));\n  }\n\n  const vfs = new VirtualFileSystem();\n\n  // Pre-load existing package.json files into VFS so processAddonsDeps can modify them\n  const packageJsonPaths = [\n    \"package.json\",\n    \"apps/web/package.json\",\n    \"apps/server/package.json\",\n    \"apps/native/package.json\",\n  ];\n  for (const pkgPath of packageJsonPaths) {\n    const fullPath = path.join(projectDir, pkgPath);\n    if (await fs.pathExists(fullPath)) {\n      const content = await fs.readFile(fullPath, \"utf-8\");\n      vfs.writeFile(pkgPath, content);\n    }\n  }\n\n  // Process addon templates\n  await processAddonTemplates(vfs, EMBEDDED_TEMPLATES, config);\n\n  // Process addon dependencies (adds deps to package.json files in VFS)\n  processAddonsDeps(vfs, config);\n\n  // Write VFS to disk\n  const tree = {\n    root: vfs.toTree(\"\"),\n    fileCount: vfs.getFileCount(),\n    directoryCount: vfs.getDirectoryCount(),\n    config,\n  };\n\n  if (input.dryRun) {\n    if (!isSilent()) {\n      log.success(pc.green(\"Dry run validation passed. No addon files were written.\"));\n      log.info(pc.dim(`Planned addon files: ${vfs.getFileCount()}`));\n      outro(pc.magenta(\"Dry run complete.\"));\n    }\n\n    return Result.ok({\n      success: true,\n      addedAddons: addonsToAdd,\n      projectDir,\n      dryRun: true,\n      plannedFileCount: vfs.getFileCount(),\n    });\n  }\n\n  const writeResult = await writeTree(tree, projectDir);\n\n  if (writeResult.isErr()) {\n    return Result.err(\n      new CLIError({\n        message: `Failed to write addon files: ${writeResult.error.message}`,\n      }),\n    );\n  }\n\n  if (vfs.getFileCount() > 0 && !isSilent()) {\n    log.info(pc.dim(`Wrote ${vfs.getFileCount()} addon files`));\n  }\n\n  // Run addon setup (handles deps and interactive prompts)\n  // Wrap with Result.tryPromise since setupAddons can throw UserCancelledError\n  const setupResult = await Result.tryPromise({\n    try: () => setupAddons(config),\n    catch: (e: unknown) => {\n      if (UserCancelledError.is(e)) return e;\n      return new CLIError({\n        message: e instanceof Error ? e.message : String(e),\n        cause: e,\n      });\n    },\n  });\n\n  if (setupResult.isErr()) {\n    return Result.err(setupResult.error);\n  }\n\n  // Update bts.jsonc with new addons\n  await updateBtsConfig(projectDir, {\n    addons: updatedAddons,\n    addonOptions: config.addonOptions,\n  });\n\n  // Install dependencies if requested\n  if (input.install) {\n    if (!isSilent()) {\n      log.info(pc.dim(\"Installing dependencies...\"));\n    }\n    await installDependencies({ projectDir, packageManager: config.packageManager });\n  }\n\n  if (!isSilent()) {\n    log.success(pc.green(`Successfully added: ${addonsToAdd.join(\", \")}`));\n\n    if (!input.install) {\n      log.info(\n        pc.yellow(\n          `Run '${config.packageManager === \"npm\" ? \"npm install\" : `${config.packageManager} install`}' to install new dependencies.`,\n        ),\n      );\n    }\n\n    outro(pc.magenta(\"Addons added successfully!\"));\n  }\n\n  return Result.ok({\n    success: true,\n    addedAddons: addonsToAdd,\n    projectDir,\n    plannedFileCount: vfs.getFileCount(),\n  });\n}\n"
  },
  {
    "path": "apps/cli/src/helpers/core/command-handlers.ts",
    "content": "import path from \"node:path\";\n\nimport { generateReproducibleCommand } from \"@better-t-stack/template-generator\";\nimport { intro, log, outro } from \"@clack/prompts\";\nimport { Result, UnhandledException } from \"better-result\";\nimport fs from \"fs-extra\";\nimport pc from \"picocolors\";\n\nimport { getDefaultConfig } from \"../../constants\";\nimport { gatherConfig } from \"../../prompts/config-prompts\";\nimport { getProjectName } from \"../../prompts/project-name\";\nimport type { CreateInput, DirectoryConflict, ProjectConfig } from \"../../types\";\nimport { trackProjectCreation } from \"../../utils/analytics\";\nimport { validateAddonsAgainstFrontends } from \"../../utils/compatibility-rules\";\nimport { isSilent, runWithContextAsync } from \"../../utils/context\";\nimport { displayConfig } from \"../../utils/display-config\";\nimport {\n  type AppError,\n  CLIError,\n  DirectoryConflictError,\n  ProjectCreationError,\n  UserCancelledError,\n  displayError,\n} from \"../../utils/errors\";\nimport { validateAgentSafePathInput } from \"../../utils/input-hardening\";\nimport { handleDirectoryConflict, setupProjectDirectory } from \"../../utils/project-directory\";\nimport { addToHistory } from \"../../utils/project-history\";\nimport { validateProjectName } from \"../../utils/project-name-validation\";\nimport { renderTitle } from \"../../utils/render-title\";\nimport { getTemplateConfig, getTemplateDescription } from \"../../utils/templates\";\nimport { cliConsola } from \"../../utils/terminal-output\";\nimport {\n  getProvidedFlags,\n  processAndValidateFlags,\n  processProvidedFlagsWithoutValidation,\n  validateConfigCompatibility,\n} from \"../../validation\";\nimport { createProject } from \"./create-project\";\nimport { mergeResolvedDbSetupOptions } from \"./db-setup-options\";\n\nexport interface CreateHandlerOptions {\n  silent?: boolean;\n}\n\n/**\n * Result type for project creation\n */\nexport interface CreateProjectResult {\n  success: boolean;\n  projectConfig: ProjectConfig;\n  reproducibleCommand: string;\n  timeScaffolded: string;\n  elapsedTimeMs: number;\n  projectDirectory: string;\n  relativePath: string;\n  error?: string;\n}\n\n/**\n * Create an empty/failed result\n */\nfunction createEmptyResult(\n  timeScaffolded: string,\n  elapsedTimeMs: number,\n  error?: string,\n): CreateProjectResult {\n  return {\n    success: false,\n    projectConfig: {\n      projectName: \"\",\n      projectDir: \"\",\n      relativePath: \"\",\n      database: \"none\",\n      orm: \"none\",\n      backend: \"none\",\n      runtime: \"none\",\n      frontend: [],\n      addons: [],\n      examples: [],\n      auth: \"none\",\n      payments: \"none\",\n      git: false,\n      packageManager: \"npm\",\n      install: false,\n      dbSetup: \"none\",\n      api: \"none\",\n      webDeploy: \"none\",\n      serverDeploy: \"none\",\n    } satisfies ProjectConfig,\n    reproducibleCommand: \"\",\n    timeScaffolded,\n    elapsedTimeMs,\n    projectDirectory: \"\",\n    relativePath: \"\",\n    error,\n  };\n}\n\ntype CreateHandlerError =\n  | UserCancelledError\n  | CLIError\n  | DirectoryConflictError\n  | ProjectCreationError\n  | UnhandledException;\n\nexport async function createProjectHandler(\n  input: CreateInput & { projectName?: string },\n  options: CreateHandlerOptions = {},\n): Promise<CreateProjectResult | undefined> {\n  const { silent = false } = options;\n\n  return runWithContextAsync({ silent }, async () => {\n    const startTime = Date.now();\n    const timeScaffolded = new Date().toISOString();\n\n    const result = await createProjectHandlerInternal(input, startTime, timeScaffolded);\n\n    // Handle success case\n    if (result.isOk()) {\n      return result.value;\n    }\n\n    // Handle error cases\n    const error = result.error;\n    const elapsedTimeMs = Date.now() - startTime;\n\n    // Handle user cancellation specially\n    if (UserCancelledError.is(error)) {\n      if (isSilent()) {\n        return createEmptyResult(timeScaffolded, elapsedTimeMs, error.message);\n      }\n      // In CLI mode, just return undefined (the cancel UI was already shown)\n      return undefined;\n    }\n\n    // For silent mode, always return a failed result instead of throwing\n    if (isSilent()) {\n      return createEmptyResult(timeScaffolded, elapsedTimeMs, error.message);\n    }\n\n    // In CLI mode, display error and exit\n    displayError(error as AppError);\n    process.exit(1);\n  });\n}\n\nasync function createProjectHandlerInternal(\n  input: CreateInput & { projectName?: string },\n  startTime: number,\n  timeScaffolded: string,\n): Promise<Result<CreateProjectResult, CreateHandlerError>> {\n  return Result.gen(async function* () {\n    if (!isSilent() && input.renderTitle !== false) {\n      renderTitle();\n    }\n    if (!isSilent()) intro(pc.magenta(\"Creating a new Better-T-Stack project\"));\n\n    if (!isSilent() && input.yolo) {\n      cliConsola.fatal(\"YOLO mode enabled - skipping checks. Things may break!\");\n    }\n\n    // Get project name\n    let currentPathInput: string;\n    if (isSilent()) {\n      const silentProjectName = yield* Result.await(resolveProjectNameForSilent(input));\n      currentPathInput = silentProjectName;\n    } else if (input.yes && input.projectName) {\n      currentPathInput = input.projectName;\n    } else if (input.yes) {\n      const defaultConfig = getDefaultConfig();\n      let defaultName: string = defaultConfig.relativePath;\n      let counter = 1;\n      while (\n        (await fs.pathExists(path.resolve(process.cwd(), defaultName))) &&\n        (await fs.readdir(path.resolve(process.cwd(), defaultName))).length > 0\n      ) {\n        defaultName = `${defaultConfig.projectName}-${counter}`;\n        counter++;\n      }\n      currentPathInput = defaultName;\n    } else {\n      // getProjectName may throw UserCancelledError\n      const projectNameResult = yield* Result.await(\n        Result.tryPromise({\n          try: async () => getProjectName(input.projectName),\n          catch: (e: unknown) => {\n            if (e instanceof UserCancelledError) return e;\n            return new CLIError({\n              message: e instanceof Error ? e.message : String(e),\n              cause: e,\n            });\n          },\n        }),\n      );\n      currentPathInput = projectNameResult;\n    }\n\n    yield* validateResolvedProjectPathInput(currentPathInput);\n\n    // Handle directory conflict\n    let finalPathInput: string;\n    let shouldClearDirectory: boolean;\n\n    const conflictResult = yield* Result.await(\n      handleDirectoryConflictResult(currentPathInput, input.directoryConflict),\n    );\n    finalPathInput = conflictResult.finalPathInput;\n    shouldClearDirectory = conflictResult.shouldClearDirectory;\n    yield* validateResolvedProjectPathInput(finalPathInput);\n\n    let finalResolvedPath: string;\n    let finalBaseName: string;\n\n    if (input.dryRun) {\n      finalResolvedPath =\n        finalPathInput === \".\" ? process.cwd() : path.resolve(process.cwd(), finalPathInput);\n      finalBaseName = path.basename(finalResolvedPath);\n    } else {\n      // Setup project directory\n      const setupResult = yield* Result.await(\n        Result.tryPromise({\n          try: async () => setupProjectDirectory(finalPathInput, shouldClearDirectory),\n          catch: (e: unknown) => {\n            if (e instanceof UserCancelledError) return e;\n            return new CLIError({\n              message: e instanceof Error ? e.message : String(e),\n              cause: e,\n            });\n          },\n        }),\n      );\n      finalResolvedPath = setupResult.finalResolvedPath;\n      finalBaseName = setupResult.finalBaseName;\n    }\n\n    const originalInput = {\n      ...input,\n      projectDirectory: input.projectName,\n    };\n\n    const providedFlags = getProvidedFlags(originalInput);\n\n    let cliInput = originalInput;\n\n    // Handle template\n    if (input.template && input.template !== \"none\") {\n      const templateConfig = getTemplateConfig(input.template);\n      if (templateConfig) {\n        const templateName = input.template.toUpperCase();\n        const templateDescription = getTemplateDescription(input.template);\n        if (!isSilent()) {\n          log.message(pc.bold(pc.cyan(`Using template: ${pc.white(templateName)}`)));\n          log.message(pc.dim(`   ${templateDescription}`));\n        }\n        const userOverrides: Record<string, unknown> = {};\n        for (const [key, value] of Object.entries(originalInput)) {\n          if (value !== undefined) {\n            userOverrides[key] = value;\n          }\n        }\n        cliInput = {\n          ...templateConfig,\n          ...userOverrides,\n          template: input.template,\n          projectDirectory: originalInput.projectDirectory,\n        };\n      }\n    }\n\n    // Build config\n    let config: ProjectConfig;\n    if (cliInput.yes) {\n      const flagConfigResult = processProvidedFlagsWithoutValidation(cliInput, finalBaseName);\n      if (flagConfigResult.isErr()) {\n        return Result.err(\n          new CLIError({ message: flagConfigResult.error.message, cause: flagConfigResult.error }),\n        );\n      }\n      const flagConfig = flagConfigResult.value;\n\n      config = {\n        ...getDefaultConfig(),\n        ...flagConfig,\n        projectName: finalBaseName,\n        projectDir: finalResolvedPath,\n        relativePath: finalPathInput,\n      };\n\n      // Validate config compatibility\n      const validationResult = validateConfigCompatibility(config, providedFlags, cliInput);\n      if (validationResult.isErr()) {\n        return Result.err(\n          new CLIError({ message: validationResult.error.message, cause: validationResult.error }),\n        );\n      }\n\n      if (!isSilent()) {\n        log.info(pc.yellow(\"Using default/flag options (config prompts skipped):\"));\n        log.message(displayConfig(config));\n      }\n    } else {\n      // Process and validate flags\n      const flagConfigResult = processAndValidateFlags(cliInput, providedFlags, finalBaseName);\n      if (flagConfigResult.isErr()) {\n        return Result.err(\n          new CLIError({ message: flagConfigResult.error.message, cause: flagConfigResult.error }),\n        );\n      }\n      const flagConfig = flagConfigResult.value;\n      const { projectName: _projectNameFromFlags, ...otherFlags } = flagConfig;\n\n      if (!isSilent() && Object.keys(otherFlags).length > 0) {\n        log.info(pc.yellow(\"Using these pre-selected options:\"));\n        log.message(displayConfig(otherFlags));\n        log.message(\"\");\n      }\n\n      // gatherConfig may throw UserCancelledError\n      const gatherResult = yield* Result.await(\n        Result.tryPromise({\n          try: async () =>\n            gatherConfig(flagConfig, finalBaseName, finalResolvedPath, finalPathInput),\n          catch: (e: unknown) => {\n            if (e instanceof UserCancelledError) return e;\n            return new CLIError({\n              message: e instanceof Error ? e.message : String(e),\n              cause: e,\n            });\n          },\n        }),\n      );\n      config = gatherResult;\n    }\n\n    const effectiveDbSetupOptions = mergeResolvedDbSetupOptions(\n      config.dbSetup,\n      config.dbSetupOptions,\n      {\n        manualDb: cliInput.manualDb ?? input.manualDb,\n        dbSetupOptions: cliInput.dbSetupOptions ?? input.dbSetupOptions,\n      },\n    );\n\n    if (effectiveDbSetupOptions) {\n      config = {\n        ...config,\n        dbSetupOptions: effectiveDbSetupOptions,\n      };\n    }\n\n    if (!input.yolo) {\n      const addonsValidationResult = validateAddonsAgainstFrontends(\n        config.addons,\n        config.frontend,\n        config.auth,\n        config.backend,\n        config.runtime,\n      );\n      if (addonsValidationResult.isErr()) {\n        return Result.err(new CLIError({ message: addonsValidationResult.error.message }));\n      }\n    }\n\n    const reproducibleCommand = generateReproducibleCommand(config);\n\n    if (input.dryRun) {\n      const elapsedTimeMs = Date.now() - startTime;\n\n      if (!isSilent()) {\n        if (shouldClearDirectory) {\n          log.warn(\n            pc.yellow(\n              `Dry run: directory \"${finalPathInput}\" would be cleared due to overwrite strategy.`,\n            ),\n          );\n        }\n        log.success(pc.green(\"Dry run validation passed. No files were written.\"));\n        log.message(pc.dim(`Target directory: ${finalResolvedPath}`));\n        log.message(pc.dim(`Run without --dry-run to create the project.`));\n        outro(pc.magenta(\"Dry run complete.\"));\n      }\n\n      return Result.ok({\n        success: true,\n        projectConfig: config,\n        reproducibleCommand,\n        timeScaffolded,\n        elapsedTimeMs,\n        projectDirectory: config.projectDir,\n        relativePath: config.relativePath,\n      });\n    }\n\n    // Create the project\n    yield* Result.await(\n      createProject(config, {\n        manualDb: cliInput.manualDb ?? input.manualDb,\n        dbSetupOptions: effectiveDbSetupOptions,\n      }),\n    );\n\n    if (!isSilent()) {\n      log.success(\n        pc.blue(`You can reproduce this setup with the following command:\\n${reproducibleCommand}`),\n      );\n    }\n\n    await trackProjectCreation(config, input.disableAnalytics);\n\n    // Track locally in history.json (non-fatal)\n    const historyResult = await addToHistory(config, reproducibleCommand);\n    if (historyResult.isErr() && !isSilent()) {\n      log.warn(pc.yellow(historyResult.error.message));\n    }\n\n    const elapsedTimeMs = Date.now() - startTime;\n    if (!isSilent()) {\n      const elapsedTimeInSeconds = (elapsedTimeMs / 1000).toFixed(2);\n      outro(\n        pc.magenta(`Project created successfully in ${pc.bold(elapsedTimeInSeconds)} seconds!`),\n      );\n    }\n\n    return Result.ok({\n      success: true,\n      projectConfig: config,\n      reproducibleCommand,\n      timeScaffolded,\n      elapsedTimeMs,\n      projectDirectory: config.projectDir,\n      relativePath: config.relativePath,\n    });\n  });\n}\n\ninterface DirectoryConflictResult {\n  finalPathInput: string;\n  shouldClearDirectory: boolean;\n}\n\nfunction isPathWithinCwd(targetPath: string) {\n  const resolved = path.resolve(targetPath);\n  const rel = path.relative(process.cwd(), resolved);\n  return !rel.startsWith(\"..\") && !path.isAbsolute(rel);\n}\n\nfunction validateResolvedProjectPathInput(candidate: string): Result<void, CLIError> {\n  const hardeningResult = validateAgentSafePathInput(candidate, \"projectName\");\n  if (hardeningResult.isErr()) {\n    return Result.err(\n      new CLIError({\n        message: hardeningResult.error.message,\n        cause: hardeningResult.error,\n      }),\n    );\n  }\n\n  if (candidate === \".\") {\n    return Result.ok(undefined);\n  }\n\n  const finalDirName = path.basename(candidate);\n  const validationResult = validateProjectName(finalDirName);\n  if (validationResult.isErr()) {\n    return Result.err(\n      new CLIError({\n        message: validationResult.error.message,\n        cause: validationResult.error,\n      }),\n    );\n  }\n\n  if (!isPathWithinCwd(candidate)) {\n    return Result.err(\n      new CLIError({\n        message: \"Project path must be within current directory\",\n      }),\n    );\n  }\n\n  return Result.ok(undefined);\n}\n\nasync function resolveProjectNameForSilent(\n  input: CreateInput & { projectName?: string },\n): Promise<Result<string, CLIError>> {\n  const defaultConfig = getDefaultConfig();\n  const rawProjectName = input.projectName?.trim() || undefined;\n  const candidate = rawProjectName ?? defaultConfig.relativePath;\n  return Result.ok(candidate);\n}\n\nasync function handleDirectoryConflictResult(\n  currentPathInput: string,\n  strategy?: DirectoryConflict,\n): Promise<\n  Result<DirectoryConflictResult, UserCancelledError | CLIError | DirectoryConflictError>\n> {\n  if (strategy) {\n    return handleDirectoryConflictProgrammatically(currentPathInput, strategy);\n  }\n\n  // Use interactive handler\n  return Result.tryPromise({\n    try: async () => handleDirectoryConflict(currentPathInput),\n    catch: (e: unknown) => {\n      if (e instanceof UserCancelledError) return e;\n      if (e instanceof CLIError) return e;\n      return new CLIError({\n        message: e instanceof Error ? e.message : String(e),\n        cause: e,\n      });\n    },\n  });\n}\n\nasync function handleDirectoryConflictProgrammatically(\n  currentPathInput: string,\n  strategy: DirectoryConflict,\n): Promise<Result<DirectoryConflictResult, DirectoryConflictError>> {\n  const currentPath = path.resolve(process.cwd(), currentPathInput);\n\n  if (!(await fs.pathExists(currentPath))) {\n    return Result.ok({ finalPathInput: currentPathInput, shouldClearDirectory: false });\n  }\n\n  const dirContents = await fs.readdir(currentPath);\n  const isNotEmpty = dirContents.length > 0;\n\n  if (!isNotEmpty) {\n    return Result.ok({ finalPathInput: currentPathInput, shouldClearDirectory: false });\n  }\n\n  switch (strategy) {\n    case \"overwrite\":\n      return Result.ok({ finalPathInput: currentPathInput, shouldClearDirectory: true });\n\n    case \"merge\":\n      return Result.ok({ finalPathInput: currentPathInput, shouldClearDirectory: false });\n\n    case \"increment\": {\n      let counter = 1;\n      const baseName = currentPathInput;\n      let finalPathInput = `${baseName}-${counter}`;\n\n      while (\n        (await fs.pathExists(path.resolve(process.cwd(), finalPathInput))) &&\n        (await fs.readdir(path.resolve(process.cwd(), finalPathInput))).length > 0\n      ) {\n        counter++;\n        finalPathInput = `${baseName}-${counter}`;\n      }\n\n      return Result.ok({ finalPathInput, shouldClearDirectory: false });\n    }\n\n    case \"error\":\n      return Result.err(new DirectoryConflictError({ directory: currentPathInput }));\n\n    default:\n      return Result.err(new DirectoryConflictError({ directory: currentPathInput }));\n  }\n}\n"
  },
  {
    "path": "apps/cli/src/helpers/core/convex-codegen.ts",
    "content": "import path from \"node:path\";\n\nimport { $ } from \"execa\";\n\nimport type { PackageManager } from \"../../types\";\nimport { getPackageExecutionArgs } from \"../../utils/package-runner\";\n\n// having problems running this in convex + better-auth\nexport async function runConvexCodegen(\n  projectDir: string,\n  packageManager: PackageManager | null | undefined,\n) {\n  const backendDir = path.join(projectDir, \"packages/backend\");\n  const args = getPackageExecutionArgs(packageManager, \"convex codegen\");\n  await $({ cwd: backendDir })`${args}`;\n}\n"
  },
  {
    "path": "apps/cli/src/helpers/core/create-project.ts",
    "content": "import os from \"node:os\";\nimport path from \"node:path\";\n\nimport { generate, EMBEDDED_TEMPLATES } from \"@better-t-stack/template-generator\";\nimport { writeTree } from \"@better-t-stack/template-generator/fs-writer\";\nimport { log } from \"@clack/prompts\";\nimport { Result } from \"better-result\";\nimport { $ } from \"execa\";\nimport fs from \"fs-extra\";\n\nimport type { DbSetupOptions, ProjectConfig } from \"../../types\";\nimport { isSilent } from \"../../utils/context\";\nimport { ProjectCreationError } from \"../../utils/errors\";\nimport { formatProject } from \"../../utils/file-formatter\";\nimport { getLatestCLIVersion } from \"../../utils/get-latest-cli-version\";\nimport { setupAddons } from \"../addons/addons-setup\";\nimport { setupDatabase } from \"../core/db-setup\";\nimport { initializeGit } from \"./git\";\nimport { installDependencies } from \"./install-dependencies\";\nimport { displayPostInstallInstructions } from \"./post-installation\";\n\nexport interface CreateProjectOptions {\n  manualDb?: boolean;\n  dbSetupOptions?: DbSetupOptions;\n}\n\n/**\n * Creates a new project with the given configuration.\n * Returns a Result with the project directory path on success, or an error on failure.\n */\nexport async function createProject(\n  options: ProjectConfig,\n  cliInput: CreateProjectOptions = {},\n): Promise<Result<string, ProjectCreationError>> {\n  return Result.gen(async function* () {\n    const projectDir = options.projectDir;\n    const isConvex = options.backend === \"convex\";\n\n    // Ensure project directory exists\n    yield* Result.await(\n      Result.tryPromise({\n        try: () => fs.ensureDir(projectDir),\n        catch: (e) =>\n          new ProjectCreationError({\n            phase: \"directory-setup\",\n            message: `Failed to create project directory: ${e instanceof Error ? e.message : String(e)}`,\n            cause: e,\n          }),\n      }),\n    );\n\n    // Generate virtual project using Result-based API\n    const tree = yield* Result.await(\n      generate({\n        config: options,\n        templates: EMBEDDED_TEMPLATES,\n        version: getLatestCLIVersion(),\n      }).then((result) =>\n        result.mapError(\n          (e) =>\n            new ProjectCreationError({\n              phase: e.phase || \"template-generation\",\n              message: e.message,\n              cause: e,\n            }),\n        ),\n      ),\n    );\n\n    // Write tree to filesystem using Result-based API\n    yield* Result.await(\n      writeTree(tree, projectDir).then((result) =>\n        result.mapError(\n          (e) =>\n            new ProjectCreationError({\n              phase: \"file-writing\",\n              message: e.message,\n              cause: e,\n            }),\n        ),\n      ),\n    );\n\n    // Set package manager version\n    yield* Result.await(setPackageManagerVersion(projectDir, options.packageManager));\n\n    // Setup database if needed\n    if (!isConvex && options.database !== \"none\") {\n      yield* Result.await(\n        Result.tryPromise({\n          try: () => setupDatabase(options, cliInput),\n          catch: (e) =>\n            new ProjectCreationError({\n              phase: \"database-setup\",\n              message: `Failed to setup database: ${e instanceof Error ? e.message : String(e)}`,\n              cause: e,\n            }),\n        }),\n      );\n    }\n\n    // Setup addons if any\n    if (options.addons.length > 0 && options.addons[0] !== \"none\") {\n      yield* Result.await(\n        Result.tryPromise({\n          try: () => setupAddons(options),\n          catch: (e) =>\n            new ProjectCreationError({\n              phase: \"addons-setup\",\n              message: `Failed to setup addons: ${e instanceof Error ? e.message : String(e)}`,\n              cause: e,\n            }),\n        }),\n      );\n    }\n\n    // Format project\n    yield* Result.await(formatProject(projectDir));\n\n    if (!isSilent()) log.success(\"Project template successfully scaffolded!\");\n\n    // Install dependencies if requested\n    if (options.install) {\n      yield* Result.await(\n        installDependencies({\n          projectDir,\n          packageManager: options.packageManager,\n        }),\n      );\n    }\n\n    // Initialize git if requested\n    yield* Result.await(initializeGit(projectDir, options.git));\n\n    // Display post-install instructions\n    if (!isSilent()) {\n      await displayPostInstallInstructions({\n        ...options,\n        depsInstalled: options.install,\n      });\n    }\n\n    return Result.ok(projectDir);\n  });\n}\n\nasync function setPackageManagerVersion(\n  projectDir: string,\n  packageManager: ProjectConfig[\"packageManager\"],\n): Promise<Result<void, ProjectCreationError>> {\n  const pkgJsonPath = path.join(projectDir, \"package.json\");\n\n  if (!(await fs.pathExists(pkgJsonPath))) {\n    return Result.ok(undefined);\n  }\n\n  // First, try to get the version\n  const versionResult = await Result.tryPromise({\n    try: async () => {\n      // Run in a neutral directory to avoid local package manager shims affecting lookup.\n      const { stdout } = await $({ cwd: os.tmpdir() })`${packageManager} -v`;\n      return stdout.trim();\n    },\n    catch: () => null, // Return null if we can't get version\n  });\n\n  // Now update the package.json\n  return Result.tryPromise({\n    try: async () => {\n      const pkgJson = await fs.readJson(pkgJsonPath);\n\n      if (versionResult.isOk() && versionResult.value) {\n        pkgJson.packageManager = `${packageManager}@${versionResult.value}`;\n      } else {\n        // If we can't get the version, just remove the packageManager field\n        delete pkgJson.packageManager;\n      }\n\n      await fs.writeJson(pkgJsonPath, pkgJson, { spaces: 2 });\n    },\n    catch: (e) =>\n      new ProjectCreationError({\n        phase: \"package-manager-version\",\n        message: `Failed to set package manager version: ${e instanceof Error ? e.message : String(e)}`,\n        cause: e,\n      }),\n  });\n}\n"
  },
  {
    "path": "apps/cli/src/helpers/core/db-setup-options.ts",
    "content": "import type { DatabaseSetup, DbSetupOptions } from \"../../types\";\nimport { isSilent } from \"../../utils/context\";\n\nexport interface DatabaseSetupCliOptions {\n  manualDb?: boolean;\n  dbSetupOptions?: DbSetupOptions;\n}\n\nexport type DbSetupMode = NonNullable<DbSetupOptions[\"mode\"]>;\n\nconst REMOTE_PROVISIONING_DB_SETUPS: DatabaseSetup[] = [\n  \"turso\",\n  \"neon\",\n  \"prisma-postgres\",\n  \"supabase\",\n  \"mongodb-atlas\",\n];\n\nexport function requiresProvisioningGuardrails(dbSetup: DatabaseSetup): boolean {\n  return REMOTE_PROVISIONING_DB_SETUPS.includes(dbSetup);\n}\n\nexport function resolveDbSetupMode(\n  dbSetup: DatabaseSetup,\n  cliOptions: DatabaseSetupCliOptions = {},\n): DbSetupMode | undefined {\n  if (dbSetup === \"none\") {\n    return undefined;\n  }\n\n  const explicitMode = cliOptions.dbSetupOptions?.mode;\n  if (explicitMode) {\n    return explicitMode;\n  }\n\n  if (cliOptions.manualDb === true) {\n    return \"manual\";\n  }\n\n  if (isSilent() && requiresProvisioningGuardrails(dbSetup)) {\n    return \"manual\";\n  }\n\n  return undefined;\n}\n\nexport function mergeResolvedDbSetupOptions(\n  dbSetup: DatabaseSetup,\n  dbSetupOptions: DbSetupOptions | undefined,\n  cliOptions: DatabaseSetupCliOptions = {},\n): DbSetupOptions | undefined {\n  if (dbSetup === \"none\") {\n    return undefined;\n  }\n\n  const resolvedMode = resolveDbSetupMode(dbSetup, {\n    ...cliOptions,\n    dbSetupOptions: dbSetupOptions ?? cliOptions.dbSetupOptions,\n  });\n\n  if (!dbSetupOptions && !resolvedMode) {\n    return undefined;\n  }\n\n  return {\n    ...dbSetupOptions,\n    ...(resolvedMode ? { mode: resolvedMode } : {}),\n  };\n}\n"
  },
  {
    "path": "apps/cli/src/helpers/core/db-setup.ts",
    "content": "/**\n * Database setup - CLI-only operations\n * Calls external database provider CLIs (turso, neon, prisma-postgres, etc.)\n * Dependencies are handled by the generator's db-deps processor\n */\n\nimport path from \"node:path\";\n\nimport { Result } from \"better-result\";\nimport fs from \"fs-extra\";\nimport pc from \"picocolors\";\n\nimport type { ProjectConfig } from \"../../types\";\nimport { DatabaseSetupError, UserCancelledError } from \"../../utils/errors\";\nimport { cliConsola } from \"../../utils/terminal-output\";\nimport { setupCloudflareD1 } from \"../database-providers/d1-setup\";\nimport { setupDockerCompose } from \"../database-providers/docker-compose-setup\";\nimport { setupMongoDBAtlas } from \"../database-providers/mongodb-atlas-setup\";\nimport { setupNeonPostgres } from \"../database-providers/neon-setup\";\nimport { setupPlanetScale } from \"../database-providers/planetscale-setup\";\nimport { setupPrismaPostgres } from \"../database-providers/prisma-postgres-setup\";\nimport { setupSupabase } from \"../database-providers/supabase-setup\";\nimport { setupTurso } from \"../database-providers/turso-setup\";\nimport { type DatabaseSetupCliOptions, mergeResolvedDbSetupOptions } from \"./db-setup-options\";\n\nexport async function setupDatabase(config: ProjectConfig, cliInput?: DatabaseSetupCliOptions) {\n  const { database, dbSetup, backend, projectDir } = config;\n\n  if (backend === \"convex\" || database === \"none\") {\n    // Clean up server db dir if not using convex\n    if (backend !== \"convex\") {\n      const serverDbDir = path.join(projectDir, \"apps/server/src/db\");\n      if (await fs.pathExists(serverDbDir)) {\n        await fs.remove(serverDbDir);\n      }\n    }\n    return;\n  }\n\n  const dbPackageDir = path.join(projectDir, \"packages/db\");\n  if (!(await fs.pathExists(dbPackageDir))) {\n    return;\n  }\n\n  // Helper to run setup and handle Result\n  async function runSetup<T, E extends UserCancelledError | DatabaseSetupError>(\n    setupFn: () => Promise<Result<T, E>>,\n  ): Promise<void> {\n    const result = await setupFn();\n    if (result.isErr()) {\n      // Re-throw user cancellation to propagate up\n      if (UserCancelledError.is(result.error)) {\n        throw result.error;\n      }\n      // Log other errors but don't fail the overall project creation\n      cliConsola.error(pc.red(result.error.message));\n    }\n  }\n\n  const resolvedCliInput: DatabaseSetupCliOptions = {\n    ...cliInput,\n    dbSetupOptions: mergeResolvedDbSetupOptions(dbSetup, config.dbSetupOptions, cliInput),\n  };\n\n  // Call external database provider CLIs\n  if (dbSetup === \"docker\") {\n    await runSetup(() => setupDockerCompose(config));\n  } else if (database === \"sqlite\" && dbSetup === \"turso\") {\n    await runSetup(() => setupTurso(config, resolvedCliInput));\n  } else if (database === \"sqlite\" && dbSetup === \"d1\") {\n    await runSetup(() => setupCloudflareD1(config));\n  } else if (database === \"postgres\") {\n    if (dbSetup === \"prisma-postgres\") {\n      await runSetup(() => setupPrismaPostgres(config, resolvedCliInput));\n    } else if (dbSetup === \"neon\") {\n      await runSetup(() => setupNeonPostgres(config, resolvedCliInput));\n    } else if (dbSetup === \"planetscale\") {\n      await runSetup(() => setupPlanetScale(config));\n    } else if (dbSetup === \"supabase\") {\n      await runSetup(() => setupSupabase(config, resolvedCliInput));\n    }\n  } else if (database === \"mysql\" && dbSetup === \"planetscale\") {\n    await runSetup(() => setupPlanetScale(config));\n  } else if (database === \"mongodb\" && dbSetup === \"mongodb-atlas\") {\n    await runSetup(() => setupMongoDBAtlas(config, resolvedCliInput));\n  }\n}\n"
  },
  {
    "path": "apps/cli/src/helpers/core/detect-project-config.ts",
    "content": "import path from \"node:path\";\n\nimport { Result } from \"better-result\";\nimport fs from \"fs-extra\";\n\nimport { readBtsConfig } from \"../../utils/bts-config\";\n\nexport async function detectProjectConfig(projectDir: string) {\n  const result = await Result.tryPromise({\n    try: async () => {\n      const btsConfig = await readBtsConfig(projectDir);\n      if (btsConfig) {\n        return {\n          projectDir,\n          projectName: path.basename(projectDir),\n          addonOptions: btsConfig.addonOptions,\n          dbSetupOptions: btsConfig.dbSetupOptions,\n          database: btsConfig.database,\n          orm: btsConfig.orm,\n          backend: btsConfig.backend,\n          runtime: btsConfig.runtime,\n          frontend: btsConfig.frontend,\n          addons: btsConfig.addons,\n          examples: btsConfig.examples,\n          auth: btsConfig.auth,\n          payments: btsConfig.payments,\n          packageManager: btsConfig.packageManager,\n          dbSetup: btsConfig.dbSetup,\n          api: btsConfig.api,\n          webDeploy: btsConfig.webDeploy,\n          serverDeploy: btsConfig.serverDeploy,\n        };\n      }\n\n      return null;\n    },\n    catch: () => null,\n  });\n\n  return result.isOk() ? result.value : null;\n}\n\nexport async function isBetterTStackProject(projectDir: string): Promise<boolean> {\n  const result = await Result.tryPromise({\n    try: () => fs.pathExists(path.join(projectDir, \"bts.jsonc\")),\n    catch: () => false,\n  });\n\n  return result.isOk() ? result.value : false;\n}\n"
  },
  {
    "path": "apps/cli/src/helpers/core/git.ts",
    "content": "import { Result } from \"better-result\";\nimport { $ } from \"execa\";\nimport pc from \"picocolors\";\n\nimport { ProjectCreationError } from \"../../utils/errors\";\nimport { cliLog } from \"../../utils/terminal-output\";\n\nexport async function initializeGit(\n  projectDir: string,\n  useGit: boolean,\n): Promise<Result<void, ProjectCreationError>> {\n  if (!useGit) return Result.ok(undefined);\n\n  const gitVersionResult = await $({\n    cwd: projectDir,\n    reject: false,\n    stderr: \"pipe\",\n  })`git --version`;\n\n  if (gitVersionResult.exitCode !== 0) {\n    cliLog.warn(pc.yellow(\"Git is not installed\"));\n    return Result.ok(undefined);\n  }\n\n  const result = await $({\n    cwd: projectDir,\n    reject: false,\n    stderr: \"pipe\",\n  })`git init`;\n\n  if (result.exitCode !== 0) {\n    return Result.err(\n      new ProjectCreationError({\n        phase: \"git-initialization\",\n        message: `Git initialization failed: ${result.stderr}`,\n      }),\n    );\n  }\n\n  return Result.tryPromise({\n    try: async () => {\n      await $({ cwd: projectDir })`git add -A`;\n      await $({ cwd: projectDir })`git commit -m ${\"initial commit\"}`;\n    },\n    catch: (e) =>\n      new ProjectCreationError({\n        phase: \"git-initialization\",\n        message: `Git commit failed: ${e instanceof Error ? e.message : String(e)}`,\n        cause: e,\n      }),\n  });\n}\n"
  },
  {
    "path": "apps/cli/src/helpers/core/install-dependencies.ts",
    "content": "import { Result } from \"better-result\";\nimport { $ } from \"execa\";\nimport pc from \"picocolors\";\n\nimport type { Addons, PackageManager } from \"../../types\";\nimport { ProjectCreationError } from \"../../utils/errors\";\nimport { shouldSkipExternalCommands } from \"../../utils/external-commands\";\nimport { createSpinner } from \"../../utils/terminal-output\";\n\nexport async function installDependencies({\n  projectDir,\n  packageManager,\n}: {\n  projectDir: string;\n  packageManager: PackageManager;\n  addons?: Addons[];\n}): Promise<Result<void, ProjectCreationError>> {\n  if (shouldSkipExternalCommands()) {\n    return Result.ok(undefined);\n  }\n\n  const s = createSpinner();\n\n  s.start(`Running ${packageManager} install...`);\n\n  const result = await Result.tryPromise({\n    try: async () => {\n      await $({\n        cwd: projectDir,\n        stderr: \"inherit\",\n      })`${packageManager} install`;\n    },\n    catch: (e) =>\n      new ProjectCreationError({\n        phase: \"dependency-installation\",\n        message: `Installation error: ${e instanceof Error ? e.message : String(e)}`,\n        cause: e,\n      }),\n  });\n\n  if (result.isOk()) {\n    s.stop(\"Dependencies installed successfully\");\n  } else {\n    s.stop(pc.red(\"Failed to install dependencies\"));\n  }\n\n  return result;\n}\n"
  },
  {
    "path": "apps/cli/src/helpers/core/post-installation.ts",
    "content": "import pc from \"picocolors\";\n\nimport type {\n  Backend,\n  Database,\n  DatabaseSetup,\n  Frontend,\n  ORM,\n  ProjectConfig,\n  Runtime,\n  ServerDeploy,\n  WebDeploy,\n} from \"../../types\";\nimport { desktopWebFrontends } from \"../../types\";\nimport { getDockerStatus } from \"../../utils/docker-utils\";\nimport {\n  fetchSponsorsQuietly,\n  formatPostInstallSpecialSponsorsSection,\n} from \"../../utils/sponsors\";\nimport { cliConsola } from \"../../utils/terminal-output\";\n\nfunction getDesktopStaticBuildNote(frontend: Frontend[]): string {\n  const staticBuildFrontends = new Map<Frontend, string>([\n    [\"tanstack-start\", \"TanStack Start\"],\n    [\"next\", \"Next.js\"],\n    [\"nuxt\", \"Nuxt\"],\n    [\"svelte\", \"SvelteKit\"],\n    [\"astro\", \"Astro\"],\n  ]);\n\n  const staticBuildFrontend = frontend.find((value) => staticBuildFrontends.has(value));\n  if (!staticBuildFrontend) {\n    return \"\";\n  }\n\n  return `${pc.yellow(\n    \"NOTE:\",\n  )} Desktop builds package static web assets.\\n   ${staticBuildFrontends.get(\n    staticBuildFrontend,\n  )} needs a static/export web build before desktop packaging will work.`;\n}\n\nexport async function displayPostInstallInstructions(\n  config: ProjectConfig & { depsInstalled: boolean },\n) {\n  const {\n    api,\n    database,\n    relativePath,\n    packageManager,\n    depsInstalled,\n    orm,\n    addons,\n    runtime,\n    frontend,\n    backend,\n    dbSetup,\n    webDeploy,\n    serverDeploy,\n  } = config;\n\n  const isConvex = backend === \"convex\";\n  const isBackendSelf = backend === \"self\";\n  const runCmd =\n    packageManager === \"npm\" ? \"npm run\" : packageManager === \"pnpm\" ? \"pnpm run\" : \"bun run\";\n  const cdCmd = `cd ${relativePath}`;\n  const hasHusky = addons?.includes(\"husky\");\n  const hasLefthook = addons?.includes(\"lefthook\");\n  const hasGitHooksOrLinting =\n    addons?.includes(\"husky\") ||\n    addons?.includes(\"biome\") ||\n    addons?.includes(\"lefthook\") ||\n    addons?.includes(\"oxlint\");\n\n  const databaseInstructions =\n    !isConvex && database !== \"none\"\n      ? await getDatabaseInstructions(\n          database,\n          orm,\n          runCmd,\n          runtime,\n          dbSetup,\n          webDeploy,\n          serverDeploy,\n          backend,\n        )\n      : \"\";\n\n  const tauriInstructions = addons?.includes(\"tauri\") ? getTauriInstructions(runCmd, frontend) : \"\";\n  const electrobunInstructions = addons?.includes(\"electrobun\")\n    ? getElectrobunInstructions(runCmd, frontend)\n    : \"\";\n  const huskyInstructions = hasHusky ? getHuskyInstructions(runCmd) : \"\";\n  const lefthookInstructions = hasLefthook ? getLefthookInstructions(packageManager) : \"\";\n  const lintingInstructions = hasGitHooksOrLinting ? getLintingInstructions(runCmd) : \"\";\n  const nativeInstructions =\n    (frontend?.includes(\"native-bare\") ||\n      frontend?.includes(\"native-uniwind\") ||\n      frontend?.includes(\"native-unistyles\")) &&\n    backend !== \"none\"\n      ? getNativeInstructions(isConvex, isBackendSelf, frontend || [], runCmd)\n      : \"\";\n  const pwaInstructions =\n    addons?.includes(\"pwa\") && frontend?.includes(\"react-router\") ? getPwaInstructions() : \"\";\n  const starlightInstructions = addons?.includes(\"starlight\")\n    ? getStarlightInstructions(runCmd)\n    : \"\";\n  const clerkInstructions =\n    config.auth === \"clerk\" ? getClerkInstructions(frontend || [], backend, api) : \"\";\n  const polarInstructions =\n    config.payments === \"polar\" && config.auth === \"better-auth\"\n      ? getPolarInstructions(backend)\n      : \"\";\n  const alchemyDeployInstructions = getAlchemyDeployInstructions(\n    runCmd,\n    webDeploy,\n    serverDeploy,\n    backend,\n  );\n\n  const hasWeb = frontend?.some((f) => (desktopWebFrontends as readonly string[]).includes(f));\n  const hasNative =\n    frontend?.includes(\"native-bare\") ||\n    frontend?.includes(\"native-uniwind\") ||\n    frontend?.includes(\"native-unistyles\");\n\n  const hasReactRouter = frontend?.includes(\"react-router\");\n  const hasTanStackRouter = frontend?.includes(\"tanstack-router\");\n  const hasSvelte = frontend?.includes(\"svelte\");\n  const hasAstro = frontend?.includes(\"astro\");\n  const webPort =\n    hasReactRouter || hasTanStackRouter || hasSvelte ? \"5173\" : hasAstro ? \"4321\" : \"3001\";\n\n  const betterAuthConvexInstructions =\n    isConvex && config.auth === \"better-auth\"\n      ? getBetterAuthConvexInstructions(hasWeb ?? false, webPort, packageManager)\n      : \"\";\n\n  const bunWebNativeWarning =\n    packageManager === \"bun\" && hasNative && hasWeb ? getBunWebNativeWarning() : \"\";\n  const noOrmWarning = !isConvex && database !== \"none\" && orm === \"none\" ? getNoOrmWarning() : \"\";\n\n  let output = `${pc.bold(\"Next steps\")}\\n${pc.cyan(\"1.\")} ${cdCmd}\\n`;\n  let stepCounter = 2;\n\n  if (!depsInstalled) {\n    output += `${pc.cyan(`${stepCounter++}.`)} ${packageManager} install\\n`;\n  }\n\n  if (database === \"sqlite\" && dbSetup !== \"d1\") {\n    output += `${pc.cyan(`${stepCounter++}.`)} ${runCmd} db:local\\n${pc.dim(\n      \"   (optional - starts local SQLite database)\",\n    )}\\n`;\n  }\n\n  if (isConvex) {\n    output += `${pc.cyan(`${stepCounter++}.`)} ${runCmd} dev:setup\\n${pc.dim(\n      \"   (this will guide you through Convex project setup)\",\n    )}\\n`;\n\n    output += `${pc.cyan(`${stepCounter++}.`)} Copy environment variables from\\n${pc.white(\n      \"   packages/backend/.env.local\",\n    )} to ${pc.white(\"apps/*/.env\")}\\n`;\n    output += `${pc.cyan(`${stepCounter++}.`)} ${runCmd} dev\\n\\n`;\n  } else if (isBackendSelf) {\n    output += `${pc.cyan(`${stepCounter++}.`)} ${runCmd} dev\\n`;\n  } else {\n    if (runtime !== \"workers\") {\n      output += `${pc.cyan(`${stepCounter++}.`)} ${runCmd} dev\\n`;\n    }\n\n    if (runtime === \"workers\") {\n      if (dbSetup === \"d1\") {\n        output += `${pc.yellow(\n          \"IMPORTANT:\",\n        )} Complete D1 database setup first\\n   (see Database commands below)\\n`;\n      }\n      output += `${pc.cyan(`${stepCounter++}.`)} ${runCmd} dev\\n`;\n    }\n  }\n\n  const hasStandaloneBackend = backend !== \"none\";\n  const hasAnyService =\n    hasWeb || hasStandaloneBackend || addons?.includes(\"starlight\") || addons?.includes(\"fumadocs\");\n\n  if (hasAnyService) {\n    output += `${pc.bold(\"Your project will be available at:\")}\\n`;\n\n    if (hasWeb) {\n      output += `${pc.cyan(\"•\")} Frontend: http://localhost:${webPort}\\n`;\n    } else if (!hasNative && !addons?.includes(\"starlight\")) {\n      output += `${pc.yellow(\n        \"NOTE:\",\n      )} You are creating a backend-only app\\n   (no frontend selected)\\n`;\n    }\n\n    if (!isConvex && !isBackendSelf && hasStandaloneBackend) {\n      output += `${pc.cyan(\"•\")} Backend API: http://localhost:3000\\n`;\n\n      if (api === \"orpc\") {\n        output += `${pc.cyan(\"•\")} OpenAPI (Scalar UI): http://localhost:3000/api-reference\\n`;\n      }\n    }\n\n    if (isBackendSelf && api === \"orpc\") {\n      const rpcPath =\n        frontend?.includes(\"next\") || frontend?.includes(\"tanstack-start\") ? \"/api/rpc\" : \"/rpc\";\n      output += `${pc.cyan(\"•\")} OpenAPI (Scalar UI): http://localhost:${webPort}${rpcPath}/api-reference\\n`;\n    }\n\n    if (addons?.includes(\"starlight\")) {\n      output += `${pc.cyan(\"•\")} Docs: http://localhost:4321\\n`;\n    }\n\n    if (addons?.includes(\"fumadocs\")) {\n      output += `${pc.cyan(\"•\")} Fumadocs: http://localhost:4000\\n`;\n    }\n  }\n\n  if (nativeInstructions) output += `\\n${nativeInstructions.trim()}\\n`;\n  if (databaseInstructions) output += `\\n${databaseInstructions.trim()}\\n`;\n  if (tauriInstructions) output += `\\n${tauriInstructions.trim()}\\n`;\n  if (electrobunInstructions) output += `\\n${electrobunInstructions.trim()}\\n`;\n  if (huskyInstructions) output += `\\n${huskyInstructions.trim()}\\n`;\n  if (lefthookInstructions) output += `\\n${lefthookInstructions.trim()}\\n`;\n  if (lintingInstructions) output += `\\n${lintingInstructions.trim()}\\n`;\n  if (pwaInstructions) output += `\\n${pwaInstructions.trim()}\\n`;\n  if (alchemyDeployInstructions) output += `\\n${alchemyDeployInstructions.trim()}\\n`;\n  if (starlightInstructions) output += `\\n${starlightInstructions.trim()}\\n`;\n  if (clerkInstructions) output += `\\n${clerkInstructions.trim()}\\n`;\n  if (betterAuthConvexInstructions) output += `\\n${betterAuthConvexInstructions.trim()}\\n`;\n  if (polarInstructions) output += `\\n${polarInstructions.trim()}\\n`;\n\n  if (noOrmWarning) output += `\\n${noOrmWarning.trim()}\\n`;\n  if (bunWebNativeWarning) output += `\\n${bunWebNativeWarning.trim()}\\n`;\n\n  const sponsorsResult = await fetchSponsorsQuietly();\n  const specialSponsorsSection = sponsorsResult.isOk()\n    ? formatPostInstallSpecialSponsorsSection(sponsorsResult.value)\n    : \"\";\n\n  if (specialSponsorsSection) {\n    output += `\\n${specialSponsorsSection.trim()}\\n`;\n  }\n\n  output += `\\n${pc.bold(\n    \"Like Better-T-Stack?\",\n  )} Please consider giving us a star\\n   on GitHub:\\n`;\n  output += pc.cyan(\"https://github.com/AmanVarshney01/create-better-t-stack\");\n\n  cliConsola.box(output);\n}\n\nfunction getNativeInstructions(\n  isConvex: boolean,\n  isBackendSelf: boolean,\n  frontend: Frontend[],\n  runCmd: string,\n) {\n  const envVar = isConvex ? \"EXPO_PUBLIC_CONVEX_URL\" : \"EXPO_PUBLIC_SERVER_URL\";\n  const selfBackendPort = frontend.includes(\"svelte\")\n    ? \"5173\"\n    : frontend.includes(\"astro\")\n      ? \"4321\"\n      : \"3001\";\n  const exampleUrl = isConvex\n    ? \"https://<YOUR_CONVEX_URL>\"\n    : isBackendSelf\n      ? `http://<YOUR_LOCAL_IP>:${selfBackendPort}`\n      : \"http://<YOUR_LOCAL_IP>:3000\";\n  const envFileName = \".env\";\n  const ipNote = isConvex\n    ? \"your Convex deployment URL (find after running 'dev:setup')\"\n    : \"your local IP address\";\n\n  let instructions = `${pc.yellow(\n    \"NOTE:\",\n  )} For Expo connectivity issues, update\\n   apps/native/${envFileName} with ${ipNote}:\\n   ${`${envVar}=${exampleUrl}`}\\n`;\n\n  if (isConvex) {\n    instructions += `\\n${pc.yellow(\n      \"IMPORTANT:\",\n    )} When using local development with Convex and native apps,\\n   ensure you use your local IP address instead of localhost or 127.0.0.1\\n   for proper connectivity.\\n`;\n  }\n\n  if (frontend.includes(\"native-unistyles\")) {\n    instructions += `\\n${pc.yellow(\n      \"NOTE:\",\n    )} Unistyles requires a development build.\\n   cd apps/native and run ${runCmd} android or ${runCmd} ios\\n`;\n  }\n\n  return instructions;\n}\n\nfunction getHuskyInstructions(runCmd: string) {\n  return `${pc.bold(\"Git hooks with Husky:\")}\\n${pc.cyan(\n    \"•\",\n  )} Initialize hooks: ${`${runCmd} prepare`}\\n`;\n}\n\nfunction getLintingInstructions(runCmd: string) {\n  return `${pc.bold(\"Linting and formatting:\")}\\n${pc.cyan(\n    \"•\",\n  )} Format and lint fix: ${`${runCmd} check`}\\n`;\n}\n\nfunction getLefthookInstructions(packageManager: string) {\n  const cmd = packageManager === \"npm\" ? \"npx\" : packageManager;\n  return `${pc.bold(\"Git hooks with Lefthook:\")}\\n${pc.cyan(\n    \"•\",\n  )} Install hooks: ${cmd} lefthook install\\n`;\n}\n\nasync function getDatabaseInstructions(\n  database: Database,\n  orm: ORM,\n  runCmd: string,\n  _runtime: Runtime,\n  dbSetup: DatabaseSetup,\n  webDeploy: WebDeploy,\n  serverDeploy: ServerDeploy,\n  backend: Backend,\n) {\n  const instructions: string[] = [];\n  const isD1Alchemy =\n    dbSetup === \"d1\" &&\n    (serverDeploy === \"cloudflare\" || (backend === \"self\" && webDeploy === \"cloudflare\"));\n\n  if (dbSetup === \"docker\") {\n    const dockerStatus = await getDockerStatus(database);\n\n    if (dockerStatus.message) {\n      instructions.push(dockerStatus.message);\n      instructions.push(\"\");\n    }\n  }\n\n  if (isD1Alchemy) {\n    if (orm === \"drizzle\") {\n      instructions.push(`${pc.cyan(\"•\")} Generate migrations: ${`${runCmd} db:generate`}`);\n    } else if (orm === \"prisma\") {\n      instructions.push(`${pc.cyan(\"•\")} Generate Prisma client: ${`${runCmd} db:generate`}`);\n      instructions.push(`${pc.cyan(\"•\")} Apply migrations: ${`${runCmd} db:migrate`}`);\n    }\n  }\n\n  if (dbSetup === \"planetscale\") {\n    if (database === \"mysql\" && orm === \"drizzle\") {\n      instructions.push(\n        `${pc.yellow(\"NOTE:\")} Enable foreign key constraints in PlanetScale database settings`,\n      );\n    }\n    if (database === \"mysql\" && orm === \"prisma\") {\n      instructions.push(\n        `${pc.yellow(\n          \"NOTE:\",\n        )} How to handle Prisma migrations with PlanetScale:\\n   https://github.com/prisma/prisma/issues/7292`,\n      );\n    }\n  }\n\n  if (dbSetup === \"turso\" && orm === \"prisma\") {\n    instructions.push(\n      `${pc.yellow(\n        \"NOTE:\",\n      )} Follow Turso's Prisma guide for migrations via the Turso CLI:\\n   https://docs.turso.tech/sdk/ts/orm/prisma`,\n    );\n  }\n\n  if (orm === \"prisma\") {\n    if (database === \"mongodb\" && dbSetup === \"docker\") {\n      instructions.push(\n        `${pc.yellow(\"WARNING:\")} Prisma + MongoDB + Docker combination\\n   may not work.`,\n      );\n    }\n    if (dbSetup === \"docker\") {\n      instructions.push(`${pc.cyan(\"•\")} Start docker container: ${`${runCmd} db:start`}`);\n    }\n    if (!isD1Alchemy) {\n      instructions.push(`${pc.cyan(\"•\")} Generate Prisma Client: ${`${runCmd} db:generate`}`);\n      instructions.push(`${pc.cyan(\"•\")} Apply schema: ${`${runCmd} db:push`}`);\n    }\n    if (!isD1Alchemy) {\n      instructions.push(`${pc.cyan(\"•\")} Database UI: ${`${runCmd} db:studio`}`);\n    }\n  } else if (orm === \"drizzle\") {\n    if (dbSetup === \"docker\") {\n      instructions.push(`${pc.cyan(\"•\")} Start docker container: ${`${runCmd} db:start`}`);\n    }\n    if (!isD1Alchemy) {\n      instructions.push(`${pc.cyan(\"•\")} Apply schema: ${`${runCmd} db:push`}`);\n    }\n    if (!isD1Alchemy) {\n      instructions.push(`${pc.cyan(\"•\")} Database UI: ${`${runCmd} db:studio`}`);\n    }\n  } else if (orm === \"mongoose\") {\n    if (dbSetup === \"docker\") {\n      instructions.push(`${pc.cyan(\"•\")} Start docker container: ${`${runCmd} db:start`}`);\n    }\n  } else if (orm === \"none\") {\n    instructions.push(`${pc.yellow(\"NOTE:\")} Manual database schema setup\\n   required.`);\n  }\n\n  return instructions.length ? `${pc.bold(\"Database commands:\")}\\n${instructions.join(\"\\n\")}` : \"\";\n}\n\nfunction getTauriInstructions(runCmd: string, frontend: Frontend[]) {\n  const staticBuildNote = getDesktopStaticBuildNote(frontend);\n\n  return `\\n${pc.bold(\"Desktop app with Tauri:\")}\\n${pc.cyan(\n    \"•\",\n  )} Start desktop app: ${`cd apps/web && ${runCmd} desktop:dev`}\\n${pc.cyan(\n    \"•\",\n  )} Build desktop app: ${`cd apps/web && ${runCmd} desktop:build`}\\n${pc.yellow(\n    \"NOTE:\",\n  )} Tauri requires Rust and platform-specific dependencies.\\n   See: ${\"https://v2.tauri.app/start/prerequisites/\"}${\n    staticBuildNote ? `\\n${staticBuildNote}` : \"\"\n  }`;\n}\n\nfunction getElectrobunInstructions(runCmd: string, frontend: Frontend[]) {\n  const staticBuildNote = getDesktopStaticBuildNote(frontend);\n\n  return `\\n${pc.bold(\"Desktop app with Electrobun:\")}\\n${pc.cyan(\n    \"•\",\n  )} Start desktop app with HMR: ${`${runCmd} dev:desktop`}\\n${pc.cyan(\n    \"•\",\n  )} Build stable desktop app (DMG/App): ${`${runCmd} build:desktop`}\\n${pc.cyan(\n    \"•\",\n  )} Build canary desktop app: ${`${runCmd} build:desktop:canary`}\\n${pc.yellow(\n    \"NOTE:\",\n  )} Electrobun wraps your web frontend in a desktop shell.\\n   See: ${\"https://blackboard.sh/electrobun/docs/\"}${\n    staticBuildNote ? `\\n${staticBuildNote}` : \"\"\n  }`;\n}\n\nfunction getPwaInstructions() {\n  return `\\n${pc.bold(\"PWA with React Router v7:\")}\\n${pc.yellow(\n    \"NOTE:\",\n  )} There is a known compatibility issue between VitePWA\\n   and React Router v7. See:\\n   https://github.com/vite-pwa/vite-plugin-pwa/issues/809`;\n}\n\nfunction getStarlightInstructions(runCmd: string) {\n  return `\\n${pc.bold(\"Documentation with Starlight:\")}\\n${pc.cyan(\n    \"•\",\n  )} Start docs site: ${`cd apps/docs && ${runCmd} dev`}\\n${pc.cyan(\n    \"•\",\n  )} Build docs site: ${`cd apps/docs && ${runCmd} build`}`;\n}\n\nfunction getNoOrmWarning() {\n  return `\\n${pc.yellow(\n    \"WARNING:\",\n  )} Database selected without an ORM. Features requiring\\n   database access (e.g., examples, auth) need manual setup.`;\n}\n\nfunction getBunWebNativeWarning() {\n  return `\\n${pc.yellow(\n    \"WARNING:\",\n  )} 'bun' might cause issues with web + native apps in a monorepo.\\n   Use 'pnpm' if problems arise.`;\n}\n\nfunction getClerkQuickstartUrl(frontend: Frontend[]) {\n  if (frontend.includes(\"next\")) return \"https://clerk.com/docs/nextjs/getting-started/quickstart\";\n  if (frontend.includes(\"react-router\")) {\n    return \"https://clerk.com/docs/react-router/getting-started/quickstart\";\n  }\n  if (frontend.includes(\"tanstack-start\")) {\n    return \"https://clerk.com/docs/tanstack-react-start/getting-started/quickstart\";\n  }\n  if (frontend.includes(\"tanstack-router\")) {\n    return \"https://clerk.com/docs/react/getting-started/quickstart\";\n  }\n  if (\n    frontend.includes(\"native-bare\") ||\n    frontend.includes(\"native-uniwind\") ||\n    frontend.includes(\"native-unistyles\")\n  ) {\n    return \"https://clerk.com/docs/expo/getting-started/quickstart\";\n  }\n\n  return \"https://clerk.com/docs\";\n}\n\nfunction getClerkInstructionLines(\n  frontend: Frontend[],\n  backend: Backend,\n  api: ProjectConfig[\"api\"],\n) {\n  const lines: string[] = [];\n\n  if (frontend.includes(\"next\")) {\n    lines.push(\"Set NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY in apps/web/.env\");\n  }\n\n  if (\n    frontend.some((value) => [\"react-router\", \"tanstack-router\", \"tanstack-start\"].includes(value))\n  ) {\n    lines.push(\"Set VITE_CLERK_PUBLISHABLE_KEY in apps/web/.env\");\n  }\n\n  if (\n    frontend.some((value) => [\"native-bare\", \"native-uniwind\", \"native-unistyles\"].includes(value))\n  ) {\n    lines.push(\"Set EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY in apps/native/.env\");\n  }\n\n  if (backend === \"convex\") {\n    return [\n      \"Set CLERK_JWT_ISSUER_DOMAIN in Convex Dashboard\",\n      ...lines,\n      ...(frontend.some((value) => [\"next\", \"react-router\", \"tanstack-start\"].includes(value))\n        ? [\"Set CLERK_SECRET_KEY in apps/web/.env for Clerk server middleware\"]\n        : []),\n    ];\n  }\n\n  const hasClerkServerFrontend = frontend.some((value) =>\n    [\"next\", \"react-router\", \"tanstack-start\"].includes(value),\n  );\n  const serverEnvPath = backend === \"self\" ? \"apps/web/.env\" : \"apps/server/.env\";\n  const needsServerSideClerkAuth = backend !== \"none\";\n  const needsClerkBackendPublishableKey = [\"express\", \"fastify\"].includes(backend);\n  const needsClerkRequestVerification =\n    api !== \"none\" && [\"self\", \"hono\", \"elysia\"].includes(backend);\n\n  if (hasClerkServerFrontend && backend === \"self\") {\n    lines.push(\n      \"Set CLERK_SECRET_KEY in apps/web/.env for Clerk server middleware and server-side Clerk auth\",\n    );\n  } else {\n    if (hasClerkServerFrontend) {\n      lines.push(\"Set CLERK_SECRET_KEY in apps/web/.env for Clerk server middleware\");\n    }\n\n    if (needsServerSideClerkAuth) {\n      lines.push(`Set CLERK_SECRET_KEY in ${serverEnvPath} for server-side Clerk auth`);\n    }\n  }\n\n  if (needsClerkRequestVerification) {\n    lines.push(\n      `Set CLERK_PUBLISHABLE_KEY in ${serverEnvPath} for server-side Clerk request verification`,\n    );\n  }\n\n  if (needsClerkBackendPublishableKey) {\n    lines.push(`Set CLERK_PUBLISHABLE_KEY in ${serverEnvPath} for Clerk backend middleware`);\n  }\n\n  return lines;\n}\n\nfunction getClerkInstructions(frontend: Frontend[], backend: Backend, api: ProjectConfig[\"api\"]) {\n  const lines = [\n    `${pc.bold(\"Clerk Authentication Setup:\")}`,\n    `${pc.cyan(\"•\")} Follow the guide: ${pc.underline(getClerkQuickstartUrl(frontend))}`,\n    ...getClerkInstructionLines(frontend, backend, api).map((line) => `${pc.cyan(\"•\")} ${line}`),\n  ];\n\n  return lines.join(\"\\n\");\n}\n\nfunction getBetterAuthConvexInstructions(hasWeb: boolean, webPort: string, packageManager: string) {\n  const cmd = packageManager === \"npm\" ? \"npx\" : packageManager;\n  return (\n    `${pc.bold(\"Better Auth + Convex Setup:\")}\\n` +\n    `${pc.cyan(\"•\")} Set environment variables from ${pc.white(\"packages/backend\")}:\\n` +\n    `${pc.white(\"   cd packages/backend\")}\\n` +\n    `${pc.white(`   ${cmd} convex env set BETTER_AUTH_SECRET=$(openssl rand -base64 32)`)}\\n` +\n    (hasWeb ? `${pc.white(`   ${cmd} convex env set SITE_URL http://localhost:${webPort}`)}\\n` : \"\")\n  );\n}\n\nfunction getPolarInstructions(backend: Backend) {\n  const envPath = backend === \"self\" ? \"apps/web/.env\" : \"apps/server/.env\";\n  return `${pc.bold(\"Polar Payments Setup:\")}\\n${pc.cyan(\"•\")} Get access token & product ID from ${pc.underline(\"https://sandbox.polar.sh/\")}\\n${pc.cyan(\"•\")} Set POLAR_ACCESS_TOKEN in ${envPath}`;\n}\n\nfunction getAlchemyDeployInstructions(\n  runCmd: string,\n  webDeploy: WebDeploy,\n  serverDeploy: ServerDeploy,\n  backend: Backend,\n) {\n  const instructions: string[] = [];\n  const isBackendSelf = backend === \"self\";\n\n  if (webDeploy === \"cloudflare\" && serverDeploy !== \"cloudflare\" && !isBackendSelf) {\n    instructions.push(\n      `${pc.bold(\"Deploy web with Cloudflare (Alchemy):\")}\\n${pc.cyan(\"•\")} Dev: ${`${runCmd} dev`}\\n${pc.cyan(\"•\")} Deploy: ${`${runCmd} deploy`}\\n${pc.cyan(\"•\")} Destroy: ${`${runCmd} destroy`}`,\n    );\n  } else if (serverDeploy === \"cloudflare\" && webDeploy !== \"cloudflare\" && !isBackendSelf) {\n    instructions.push(\n      `${pc.bold(\"Deploy server with Cloudflare (Alchemy):\")}\\n${pc.cyan(\"•\")} Dev: ${`${runCmd} dev`}\\n${pc.cyan(\"•\")} Deploy: ${`${runCmd} deploy`}\\n${pc.cyan(\"•\")} Destroy: ${`${runCmd} destroy`}`,\n    );\n  } else if (webDeploy === \"cloudflare\" && (serverDeploy === \"cloudflare\" || isBackendSelf)) {\n    instructions.push(\n      `${pc.bold(\"Deploy with Cloudflare (Alchemy):\")}\\n${pc.cyan(\"•\")} Dev: ${`${runCmd} dev`}\\n${pc.cyan(\"•\")} Deploy: ${`${runCmd} deploy`}\\n${pc.cyan(\"•\")} Destroy: ${`${runCmd} destroy`}`,\n    );\n  }\n\n  return instructions.length ? `\\n${instructions.join(\"\\n\")}` : \"\";\n}\n"
  },
  {
    "path": "apps/cli/src/helpers/database-providers/d1-setup.ts",
    "content": "import path from \"node:path\";\n\nimport { Result } from \"better-result\";\n\nimport type { ProjectConfig } from \"../../types\";\nimport { addPackageDependency } from \"../../utils/add-package-deps\";\nimport { addEnvVariablesToFile, type EnvVariable } from \"../../utils/env-utils\";\nimport { DatabaseSetupError } from \"../../utils/errors\";\n\nexport async function setupCloudflareD1(\n  config: ProjectConfig,\n): Promise<Result<void, DatabaseSetupError>> {\n  const { projectDir, serverDeploy, webDeploy, orm, backend } = config;\n\n  const isCloudflareD1Target =\n    orm === \"prisma\" &&\n    (serverDeploy === \"cloudflare\" || (backend === \"self\" && webDeploy === \"cloudflare\"));\n\n  if (!isCloudflareD1Target) {\n    return Result.ok(undefined);\n  }\n\n  return Result.tryPromise({\n    try: async () => {\n      const targetApp = backend === \"self\" ? \"apps/web\" : \"apps/server\";\n      const envPath = path.join(projectDir, targetApp, \".env\");\n      const variables: EnvVariable[] = [\n        {\n          key: \"DATABASE_URL\",\n          value: `file:${path.join(projectDir, targetApp, \"local.db\")}`,\n          condition: true,\n        },\n      ];\n\n      await addEnvVariablesToFile(envPath, variables);\n\n      const serverDir = path.join(projectDir, backend === \"self\" ? \"apps/web\" : \"apps/server\");\n      await addPackageDependency({\n        dependencies: [\"@prisma/adapter-d1\"],\n        projectDir: serverDir,\n      });\n    },\n    catch: (e) =>\n      new DatabaseSetupError({\n        provider: \"d1\",\n        message: `Failed to set up Cloudflare D1: ${e instanceof Error ? e.message : String(e)}`,\n        cause: e,\n      }),\n  });\n}\n"
  },
  {
    "path": "apps/cli/src/helpers/database-providers/docker-compose-setup.ts",
    "content": "import path from \"node:path\";\n\nimport { Result } from \"better-result\";\n\nimport type { Database, ProjectConfig } from \"../../types\";\nimport { addEnvVariablesToFile, type EnvVariable } from \"../../utils/env-utils\";\nimport { DatabaseSetupError } from \"../../utils/errors\";\n\nexport async function setupDockerCompose(\n  config: ProjectConfig,\n): Promise<Result<void, DatabaseSetupError>> {\n  const { database, projectDir, projectName, backend } = config;\n\n  if (database === \"none\" || database === \"sqlite\") {\n    return Result.ok(undefined);\n  }\n\n  const result = await Result.tryPromise({\n    try: async () => {\n      await writeEnvFile(projectDir, database, projectName, backend);\n    },\n    catch: (e) =>\n      new DatabaseSetupError({\n        provider: \"docker-compose\",\n        message: `Failed to setup docker compose env: ${e instanceof Error ? e.message : String(e)}`,\n        cause: e,\n      }),\n  });\n\n  return result.isErr() ? result : Result.ok(undefined);\n}\n\nasync function writeEnvFile(\n  projectDir: string,\n  database: Database,\n  projectName: string,\n  backend?: string,\n) {\n  const targetApp = backend === \"self\" ? \"apps/web\" : \"apps/server\";\n  const envPath = path.join(projectDir, targetApp, \".env\");\n  const variables: EnvVariable[] = [\n    {\n      key: \"DATABASE_URL\",\n      value: getDatabaseUrl(database, projectName),\n      condition: true,\n    },\n  ];\n  await addEnvVariablesToFile(envPath, variables);\n}\n\nfunction getDatabaseUrl(database: Database, projectName: string) {\n  switch (database) {\n    case \"postgres\":\n      return `postgresql://postgres:password@localhost:5432/${projectName}`;\n    case \"mysql\":\n      return `mysql://user:password@localhost:3306/${projectName}`;\n    case \"mongodb\":\n      return `mongodb://root:password@localhost:27017/${projectName}?authSource=admin`;\n    default:\n      return \"\";\n  }\n}\n"
  },
  {
    "path": "apps/cli/src/helpers/database-providers/mongodb-atlas-setup.ts",
    "content": "import path from \"node:path\";\n\nimport { cancel, isCancel, select, text } from \"@clack/prompts\";\nimport { Result } from \"better-result\";\nimport { $ } from \"execa\";\nimport fs from \"fs-extra\";\nimport pc from \"picocolors\";\n\nimport type { ProjectConfig } from \"../../types\";\nimport { commandExists } from \"../../utils/command-exists\";\nimport { isSilent } from \"../../utils/context\";\nimport { addEnvVariablesToFile, type EnvVariable } from \"../../utils/env-utils\";\nimport {\n  DatabaseSetupError,\n  databaseSetupError,\n  UserCancelledError,\n  userCancelled,\n} from \"../../utils/errors\";\nimport { cliLog } from \"../../utils/terminal-output\";\nimport {\n  type DatabaseSetupCliOptions,\n  type DbSetupMode,\n  resolveDbSetupMode,\n} from \"../core/db-setup-options\";\n\ntype MongoDBConfig = {\n  connectionString: string;\n};\n\ntype MongoDBSetupResult = Result<void, DatabaseSetupError | UserCancelledError>;\n\nasync function checkAtlasCLI(): Promise<boolean> {\n  const exists = await commandExists(\"atlas\");\n  if (exists) {\n    cliLog.info(\"MongoDB Atlas CLI found\");\n  } else {\n    cliLog.warn(pc.yellow(\"MongoDB Atlas CLI not found\"));\n  }\n  return exists;\n}\n\nasync function initMongoDBAtlas(\n  serverDir: string,\n): Promise<Result<MongoDBConfig, DatabaseSetupError | UserCancelledError>> {\n  const hasAtlas = await checkAtlasCLI();\n\n  if (!hasAtlas) {\n    cliLog.info(\n      pc.yellow(\n        \"Please install it from: https://www.mongodb.com/docs/atlas/cli/current/install-atlas-cli/\",\n      ),\n    );\n    return databaseSetupError(\"mongodb-atlas\", \"MongoDB Atlas CLI not found\");\n  }\n\n  cliLog.info(\"Running MongoDB Atlas setup...\");\n\n  const deployResult = await Result.tryPromise({\n    try: async () => {\n      await $({ cwd: serverDir, stdio: \"inherit\" })`atlas deployments setup`;\n      cliLog.success(\"MongoDB Atlas deployment ready\");\n    },\n    catch: (e) =>\n      new DatabaseSetupError({\n        provider: \"mongodb-atlas\",\n        message: `Failed to setup MongoDB Atlas deployment: ${e instanceof Error ? e.message : String(e)}`,\n        cause: e,\n      }),\n  });\n\n  if (deployResult.isErr()) {\n    return deployResult;\n  }\n\n  const connectionString = await text({\n    message: \"Enter your MongoDB connection string:\",\n    placeholder: \"mongodb+srv://username:password@cluster.mongodb.net/database\",\n    validate(value) {\n      if (!value) return \"Please enter a connection string\";\n      if (!value.startsWith(\"mongodb\")) {\n        return \"URL should start with mongodb:// or mongodb+srv://\";\n      }\n    },\n  });\n\n  if (isCancel(connectionString)) {\n    cancel(\"MongoDB setup cancelled\");\n    return userCancelled(\"MongoDB setup cancelled\");\n  }\n\n  return Result.ok({\n    connectionString: connectionString as string,\n  });\n}\n\nasync function writeEnvFile(\n  projectDir: string,\n  backend: ProjectConfig[\"backend\"],\n  config?: MongoDBConfig,\n): Promise<Result<void, DatabaseSetupError>> {\n  return Result.tryPromise({\n    try: async () => {\n      const targetApp = backend === \"self\" ? \"apps/web\" : \"apps/server\";\n      const envPath = path.join(projectDir, targetApp, \".env\");\n      const variables: EnvVariable[] = [\n        {\n          key: \"DATABASE_URL\",\n          value: config?.connectionString ?? \"mongodb://localhost:27017/mydb\",\n          condition: true,\n        },\n      ];\n      await addEnvVariablesToFile(envPath, variables);\n    },\n    catch: (e) =>\n      new DatabaseSetupError({\n        provider: \"mongodb-atlas\",\n        message: `Failed to update environment configuration: ${e instanceof Error ? e.message : String(e)}`,\n        cause: e,\n      }),\n  });\n}\n\nfunction displayManualSetupInstructions() {\n  cliLog.info(`\n${pc.green(\"MongoDB Atlas Manual Setup Instructions:\")}\n\n1. Install Atlas CLI:\n   ${pc.blue(\"https://www.mongodb.com/docs/atlas/cli/stable/install-atlas-cli/\")}\n\n2. Run the following command and follow the prompts:\n   ${pc.blue(\"atlas deployments setup\")}\n\n3. Get your connection string from the Atlas dashboard:\n   Format: ${pc.dim(\"mongodb+srv://USERNAME:PASSWORD@CLUSTER.mongodb.net/DATABASE_NAME\")}\n\n4. Add the connection string to your .env file:\n   ${pc.dim('DATABASE_URL=\"your_connection_string\"')}\n`);\n}\n\nexport async function setupMongoDBAtlas(\n  config: ProjectConfig,\n  cliInput?: DatabaseSetupCliOptions,\n): Promise<MongoDBSetupResult> {\n  const { projectDir, backend } = config;\n  const setupMode = resolveDbSetupMode(\"mongodb-atlas\", {\n    manualDb: cliInput?.manualDb,\n    dbSetupOptions: cliInput?.dbSetupOptions ?? config.dbSetupOptions,\n  });\n\n  const serverDir = path.join(projectDir, \"packages/db\");\n\n  const ensureDirResult = await Result.tryPromise({\n    try: () => fs.ensureDir(serverDir),\n    catch: (e) =>\n      new DatabaseSetupError({\n        provider: \"mongodb-atlas\",\n        message: `Failed to create directory: ${e instanceof Error ? e.message : String(e)}`,\n        cause: e,\n      }),\n  });\n\n  if (ensureDirResult.isErr()) {\n    return ensureDirResult;\n  }\n\n  if (setupMode === \"manual\") {\n    cliLog.info(\"MongoDB Atlas manual setup selected\");\n    const envResult = await writeEnvFile(projectDir, backend);\n    if (envResult.isErr()) {\n      return envResult;\n    }\n    displayManualSetupInstructions();\n    return Result.ok(undefined);\n  }\n\n  let mode: DbSetupMode | undefined = setupMode;\n\n  if (!mode) {\n    if (isSilent()) {\n      cliLog.warn(\n        pc.yellow(\n          \"MongoDB Atlas automatic setup requires interactive input. Falling back to manual setup.\",\n        ),\n      );\n      const envResult = await writeEnvFile(projectDir, backend);\n      if (envResult.isErr()) {\n        return envResult;\n      }\n      displayManualSetupInstructions();\n      return Result.ok(undefined);\n    }\n\n    const promptedMode = await select<DbSetupMode>({\n      message: \"MongoDB Atlas setup: choose mode\",\n      options: [\n        {\n          label: \"Automatic\",\n          value: \"auto\",\n          hint: \"Automated setup with provider CLI, sets .env\",\n        },\n        {\n          label: \"Manual\",\n          value: \"manual\",\n          hint: \"Manual setup, add env vars yourself\",\n        },\n      ],\n      initialValue: \"auto\",\n    });\n\n    if (isCancel(promptedMode)) {\n      return userCancelled(\"Operation cancelled\");\n    }\n\n    mode = promptedMode;\n  }\n\n  if (mode === \"manual\") {\n    cliLog.info(\"MongoDB Atlas manual setup selected\");\n    const envResult = await writeEnvFile(projectDir, backend);\n    if (envResult.isErr()) {\n      return envResult;\n    }\n    displayManualSetupInstructions();\n    return Result.ok(undefined);\n  }\n\n  const mongoConfigResult = await initMongoDBAtlas(serverDir);\n\n  if (mongoConfigResult.isOk()) {\n    const envResult = await writeEnvFile(projectDir, backend, mongoConfigResult.value);\n    if (envResult.isErr()) {\n      return envResult;\n    }\n    cliLog.success(pc.green(\"MongoDB Atlas setup complete! Connection saved to .env file.\"));\n    return Result.ok(undefined);\n  }\n\n  // Handle errors - check for user cancellation\n  if (UserCancelledError.is(mongoConfigResult.error)) {\n    return mongoConfigResult;\n  }\n\n  cliLog.warn(pc.yellow(\"Falling back to local MongoDB configuration\"));\n  const envResult = await writeEnvFile(projectDir, backend);\n  if (envResult.isErr()) {\n    return envResult;\n  }\n  displayManualSetupInstructions();\n  return Result.ok(undefined);\n}\n"
  },
  {
    "path": "apps/cli/src/helpers/database-providers/neon-setup.ts",
    "content": "import path from \"node:path\";\n\nimport { isCancel, select, text } from \"@clack/prompts\";\nimport { Result } from \"better-result\";\nimport { $ } from \"execa\";\nimport fs from \"fs-extra\";\nimport pc from \"picocolors\";\n\nimport type { PackageManager, ProjectConfig } from \"../../types\";\nimport { isSilent } from \"../../utils/context\";\nimport { addEnvVariablesToFile, type EnvVariable } from \"../../utils/env-utils\";\nimport {\n  DatabaseSetupError,\n  databaseSetupError,\n  UserCancelledError,\n  userCancelled,\n} from \"../../utils/errors\";\nimport { getPackageExecutionArgs } from \"../../utils/package-runner\";\nimport { cliLog, createSpinner } from \"../../utils/terminal-output\";\nimport {\n  type DatabaseSetupCliOptions,\n  type DbSetupMode,\n  resolveDbSetupMode,\n} from \"../core/db-setup-options\";\n\ntype NeonConfig = {\n  connectionString: string;\n  projectId: string;\n  dbName: string;\n  roleName: string;\n};\n\ntype NeonRegion = {\n  label: string;\n  value: string;\n};\n\ntype NeonSetupResult = Result<void, DatabaseSetupError | UserCancelledError>;\n\nconst NEON_REGIONS: NeonRegion[] = [\n  { label: \"AWS US East (N. Virginia)\", value: \"aws-us-east-1\" },\n  { label: \"AWS US East (Ohio)\", value: \"aws-us-east-2\" },\n  { label: \"AWS US West (Oregon)\", value: \"aws-us-west-2\" },\n  { label: \"AWS Europe (Frankfurt)\", value: \"aws-eu-central-1\" },\n  { label: \"AWS Asia Pacific (Singapore)\", value: \"aws-ap-southeast-1\" },\n  { label: \"AWS South America East 1 (São Paulo)\", value: \"aws-sa-east-1\" },\n  { label: \"AWS Asia Pacific (Sydney)\", value: \"aws-ap-southeast-2\" },\n  { label: \"Azure East US 2 region (Virginia)\", value: \"azure-eastus2\" },\n];\n\nasync function executeNeonCommand(\n  packageManager: PackageManager,\n  commandArgsString: string,\n  spinnerText?: string,\n): Promise<Result<{ stdout: string; stderr: string }, DatabaseSetupError>> {\n  const s = createSpinner();\n  const args = getPackageExecutionArgs(packageManager, commandArgsString);\n\n  if (spinnerText) s.start(spinnerText);\n\n  return Result.tryPromise({\n    try: async () => {\n      const result = await $`${args}`;\n      if (spinnerText)\n        s.stop(pc.green(spinnerText.replace(\"...\", \"\").replace(\"ing \", \"ed \").trim()));\n      return result;\n    },\n    catch: (e) => {\n      if (s) s.stop(pc.red(`Failed: ${spinnerText || \"Command execution\"}`));\n      return new DatabaseSetupError({\n        provider: \"neon\",\n        message: `Command failed: ${e instanceof Error ? e.message : String(e)}`,\n        cause: e,\n      });\n    },\n  });\n}\n\nasync function createNeonProject(\n  projectName: string,\n  regionId: string,\n  packageManager: PackageManager,\n): Promise<Result<NeonConfig, DatabaseSetupError>> {\n  const commandArgsString = `neonctl@latest projects create --name ${projectName} --region-id ${regionId} --output json`;\n  const execResult = await executeNeonCommand(\n    packageManager,\n    commandArgsString,\n    `Creating Neon project \"${projectName}\"...`,\n  );\n\n  if (execResult.isErr()) {\n    return execResult;\n  }\n\n  const parseResult = Result.try({\n    try: () => JSON.parse(execResult.value.stdout),\n    catch: (e) =>\n      new DatabaseSetupError({\n        provider: \"neon\",\n        message: `Failed to parse Neon response: ${e instanceof Error ? e.message : String(e)}`,\n        cause: e,\n      }),\n  });\n\n  if (parseResult.isErr()) {\n    return parseResult;\n  }\n\n  const response = parseResult.value;\n\n  if (response.project && response.connection_uris && response.connection_uris.length > 0) {\n    const projectId = response.project.id;\n    const connectionUri = response.connection_uris[0].connection_uri;\n    const params = response.connection_uris[0].connection_parameters;\n\n    return Result.ok({\n      connectionString: connectionUri,\n      projectId: projectId,\n      dbName: params.database,\n      roleName: params.role,\n    });\n  }\n\n  return databaseSetupError(\"neon\", \"Failed to extract connection information from Neon response\");\n}\n\nasync function writeEnvFile(\n  projectDir: string,\n  backend: ProjectConfig[\"backend\"],\n  config?: NeonConfig,\n): Promise<Result<void, DatabaseSetupError>> {\n  return Result.tryPromise({\n    try: async () => {\n      const targetApp = backend === \"self\" ? \"apps/web\" : \"apps/server\";\n      const envPath = path.join(projectDir, targetApp, \".env\");\n      const variables: EnvVariable[] = [\n        {\n          key: \"DATABASE_URL\",\n          value:\n            config?.connectionString ??\n            \"postgresql://postgres:postgres@localhost:5432/mydb?schema=public\",\n          condition: true,\n        },\n      ];\n      await addEnvVariablesToFile(envPath, variables);\n    },\n    catch: (e) =>\n      new DatabaseSetupError({\n        provider: \"neon\",\n        message: `Failed to update .env file: ${e instanceof Error ? e.message : String(e)}`,\n        cause: e,\n      }),\n  });\n}\n\nasync function setupWithNeonDb(\n  projectDir: string,\n  packageManager: PackageManager,\n  backend: ProjectConfig[\"backend\"],\n): Promise<Result<void, DatabaseSetupError>> {\n  const s = createSpinner();\n  s.start(\"Creating Neon database using get-db...\");\n\n  const targetApp = backend === \"self\" ? \"apps/web\" : \"apps/server\";\n  const targetDir = path.join(projectDir, targetApp);\n\n  const ensureDirResult = await Result.tryPromise({\n    try: () => fs.ensureDir(targetDir),\n    catch: (e) =>\n      new DatabaseSetupError({\n        provider: \"neon\",\n        message: `Failed to create directory: ${e instanceof Error ? e.message : String(e)}`,\n        cause: e,\n      }),\n  });\n\n  if (ensureDirResult.isErr()) {\n    s.stop(pc.red(\"Failed to create directory\"));\n    return ensureDirResult;\n  }\n\n  const packageArgs = getPackageExecutionArgs(\n    packageManager,\n    `get-db@latest --yes --ref \"sbA3tIe\"`,\n  );\n\n  return Result.tryPromise({\n    try: async () => {\n      await $({ cwd: targetDir })`${packageArgs}`;\n      s.stop(pc.green(\"Neon database created successfully!\"));\n    },\n    catch: (e) => {\n      s.stop(pc.red(\"Failed to create database with get-db\"));\n      return new DatabaseSetupError({\n        provider: \"neon\",\n        message: `Failed to create database with get-db: ${e instanceof Error ? e.message : String(e)}`,\n        cause: e,\n      });\n    },\n  });\n}\n\nfunction displayManualSetupInstructions(target: \"apps/web\" | \"apps/server\") {\n  cliLog.info(`Manual Neon PostgreSQL Setup Instructions:\n\n1. Get Neon with Better T Stack referral: https://get.neon.com/sbA3tIe\n2. Create a new project from the dashboard\n3. Get your connection string\n4. Add the database URL to the .env file in ${target}/.env\n\nDATABASE_URL=\"your_connection_string\"`);\n}\n\nexport async function setupNeonPostgres(\n  config: ProjectConfig,\n  cliInput?: DatabaseSetupCliOptions,\n): Promise<NeonSetupResult> {\n  const { packageManager, projectDir, backend } = config;\n  const setupMode = resolveDbSetupMode(\"neon\", {\n    manualDb: cliInput?.manualDb,\n    dbSetupOptions: cliInput?.dbSetupOptions ?? config.dbSetupOptions,\n  });\n  const target: \"apps/web\" | \"apps/server\" = backend === \"self\" ? \"apps/web\" : \"apps/server\";\n\n  if (setupMode === \"manual\") {\n    const envResult = await writeEnvFile(projectDir, backend);\n    if (envResult.isErr()) {\n      return envResult;\n    }\n    displayManualSetupInstructions(target);\n    return Result.ok(undefined);\n  }\n\n  let selectedMode: DbSetupMode | undefined = setupMode;\n\n  if (!selectedMode) {\n    if (isSilent()) {\n      selectedMode = \"manual\";\n    } else {\n      const promptedMode = await select<DbSetupMode>({\n        message: \"Neon setup: choose mode\",\n        options: [\n          {\n            label: \"Automatic\",\n            value: \"auto\",\n            hint: \"Automated setup with provider CLI, sets .env\",\n          },\n          {\n            label: \"Manual\",\n            value: \"manual\",\n            hint: \"Manual setup, add env vars yourself\",\n          },\n        ],\n        initialValue: \"auto\",\n      });\n\n      if (isCancel(promptedMode)) {\n        return userCancelled(\"Operation cancelled\");\n      }\n\n      selectedMode = promptedMode;\n    }\n  }\n\n  if (selectedMode === \"manual\") {\n    const envResult = await writeEnvFile(projectDir, backend);\n    if (envResult.isErr()) {\n      return envResult;\n    }\n    displayManualSetupInstructions(target);\n    return Result.ok(undefined);\n  }\n\n  let setupMethod: \"neondb\" | \"neonctl\" | undefined =\n    cliInput?.dbSetupOptions?.neon?.method ?? config.dbSetupOptions?.neon?.method;\n\n  if (!setupMethod) {\n    if (isSilent()) {\n      setupMethod = \"neondb\";\n    } else {\n      const promptedSetupMethod = await select<\"neondb\" | \"neonctl\">({\n        message: \"Choose your Neon setup method:\",\n        options: [\n          {\n            label: \"Quick setup with get-db\",\n            value: \"neondb\",\n            hint: \"fastest, no auth required\",\n          },\n          {\n            label: \"Custom setup with neonctl\",\n            value: \"neonctl\",\n            hint: \"More control - choose project name and region\",\n          },\n        ],\n        initialValue: \"neondb\",\n      });\n\n      if (isCancel(promptedSetupMethod)) {\n        return userCancelled(\"Operation cancelled\");\n      }\n\n      setupMethod = promptedSetupMethod;\n    }\n  }\n\n  if (setupMethod === \"neondb\") {\n    const neonDbResult = await setupWithNeonDb(projectDir, packageManager, backend);\n    if (neonDbResult.isErr()) {\n      cliLog.error(pc.red(neonDbResult.error.message));\n      const envResult = await writeEnvFile(projectDir, backend);\n      if (envResult.isErr()) {\n        return envResult;\n      }\n      displayManualSetupInstructions(target);\n      return Result.ok(undefined);\n    }\n\n    cliLog.info(\n      `Get Neon with Better T Stack referral: ${pc.cyan(\"https://get.neon.com/sbA3tIe\")}`,\n    );\n    return Result.ok(undefined);\n  }\n\n  // neonctl setup path\n  const suggestedProjectName = path.basename(projectDir);\n  let projectName =\n    cliInput?.dbSetupOptions?.neon?.projectName ?? config.dbSetupOptions?.neon?.projectName;\n\n  if (!projectName) {\n    if (isSilent()) {\n      projectName = suggestedProjectName;\n    } else {\n      const promptedProjectName = await text({\n        message: \"Enter a name for your Neon project:\",\n        defaultValue: suggestedProjectName,\n        initialValue: suggestedProjectName,\n      });\n\n      if (isCancel(promptedProjectName)) {\n        return userCancelled(\"Operation cancelled\");\n      }\n\n      projectName = promptedProjectName as string;\n    }\n  }\n\n  let regionId = cliInput?.dbSetupOptions?.neon?.regionId ?? config.dbSetupOptions?.neon?.regionId;\n\n  if (!regionId) {\n    if (isSilent()) {\n      regionId = NEON_REGIONS[0]!.value;\n    } else {\n      const promptedRegionId = await select({\n        message: \"Select a region for your Neon project:\",\n        options: NEON_REGIONS,\n        initialValue: NEON_REGIONS[0].value,\n      });\n\n      if (isCancel(promptedRegionId)) {\n        return userCancelled(\"Operation cancelled\");\n      }\n\n      regionId = promptedRegionId;\n    }\n  }\n\n  const neonConfigResult = await createNeonProject(projectName, regionId, packageManager);\n\n  if (neonConfigResult.isErr()) {\n    cliLog.error(pc.red(neonConfigResult.error.message));\n    const envResult = await writeEnvFile(projectDir, backend);\n    if (envResult.isErr()) {\n      return envResult;\n    }\n    displayManualSetupInstructions(target);\n    return Result.ok(undefined);\n  }\n\n  const finalSpinner = createSpinner();\n  finalSpinner.start(\"Configuring database connection\");\n\n  const envResult = await writeEnvFile(projectDir, backend, neonConfigResult.value);\n  if (envResult.isErr()) {\n    finalSpinner.stop(pc.red(\"Failed to configure database connection\"));\n    return envResult;\n  }\n\n  finalSpinner.stop(\"Neon database configured!\");\n  cliLog.info(`Get Neon with Better T Stack referral: ${pc.cyan(\"https://get.neon.com/sbA3tIe\")}`);\n  return Result.ok(undefined);\n}\n"
  },
  {
    "path": "apps/cli/src/helpers/database-providers/planetscale-setup.ts",
    "content": "import path from \"node:path\";\n\nimport { Result } from \"better-result\";\nimport fs from \"fs-extra\";\n\nimport type { ProjectConfig } from \"../../types\";\nimport { addEnvVariablesToFile, type EnvVariable } from \"../../utils/env-utils\";\nimport { DatabaseSetupError } from \"../../utils/errors\";\n\nexport async function setupPlanetScale(\n  config: ProjectConfig,\n): Promise<Result<void, DatabaseSetupError>> {\n  const { projectDir, database, orm, backend } = config;\n\n  if (![\"mysql\", \"postgres\"].includes(database)) {\n    return Result.ok(undefined);\n  }\n\n  return Result.tryPromise({\n    try: async () => {\n      const targetApp = backend === \"self\" ? \"apps/web\" : \"apps/server\";\n      const envPath = path.join(projectDir, targetApp, \".env\");\n\n      if (database === \"mysql\" && orm === \"drizzle\") {\n        const variables: EnvVariable[] = [\n          {\n            key: \"DATABASE_URL\",\n            value: 'mysql://username:password@host/database?ssl={\"rejectUnauthorized\":true}',\n            condition: true,\n          },\n          {\n            key: \"DATABASE_HOST\",\n            value: \"\",\n            condition: true,\n          },\n          {\n            key: \"DATABASE_USERNAME\",\n            value: \"\",\n            condition: true,\n          },\n          {\n            key: \"DATABASE_PASSWORD\",\n            value: \"\",\n            condition: true,\n          },\n        ];\n\n        await fs.ensureDir(path.join(projectDir, targetApp));\n        await addEnvVariablesToFile(envPath, variables);\n      }\n\n      if (database === \"postgres\" && orm === \"prisma\") {\n        const variables: EnvVariable[] = [\n          {\n            key: \"DATABASE_URL\",\n            value: \"postgresql://username:password@host/database?sslaccept=strict\",\n            condition: true,\n          },\n        ];\n\n        await fs.ensureDir(path.join(projectDir, targetApp));\n        await addEnvVariablesToFile(envPath, variables);\n      }\n\n      if (database === \"postgres\" && orm === \"drizzle\") {\n        const variables: EnvVariable[] = [\n          {\n            key: \"DATABASE_URL\",\n            value: \"postgresql://username:password@host/database?sslmode=verify-full\",\n            condition: true,\n          },\n        ];\n\n        await fs.ensureDir(path.join(projectDir, targetApp));\n        await addEnvVariablesToFile(envPath, variables);\n      }\n\n      if (database === \"mysql\" && orm === \"prisma\") {\n        const variables: EnvVariable[] = [\n          {\n            key: \"DATABASE_URL\",\n            value: \"mysql://username:password@host/database?sslaccept=strict\",\n            condition: true,\n          },\n        ];\n\n        await fs.ensureDir(path.join(projectDir, targetApp));\n        await addEnvVariablesToFile(envPath, variables);\n      }\n    },\n    catch: (e) =>\n      new DatabaseSetupError({\n        provider: \"planetscale\",\n        message: `Failed to set up PlanetScale env: ${e instanceof Error ? e.message : String(e)}`,\n        cause: e,\n      }),\n  });\n}\n"
  },
  {
    "path": "apps/cli/src/helpers/database-providers/prisma-postgres-setup.ts",
    "content": "import path from \"node:path\";\n\nimport { isCancel, select } from \"@clack/prompts\";\nimport { Result } from \"better-result\";\nimport { $ } from \"execa\";\nimport fs from \"fs-extra\";\nimport pc from \"picocolors\";\n\nimport type { PackageManager, ProjectConfig } from \"../../types\";\nimport { isSilent } from \"../../utils/context\";\nimport { addEnvVariablesToFile, type EnvVariable } from \"../../utils/env-utils\";\nimport { DatabaseSetupError, UserCancelledError, userCancelled } from \"../../utils/errors\";\nimport { getPackageRunnerPrefix } from \"../../utils/package-runner\";\nimport { cliLog, createSpinner } from \"../../utils/terminal-output\";\nimport {\n  type DatabaseSetupCliOptions,\n  type DbSetupMode,\n  resolveDbSetupMode,\n} from \"../core/db-setup-options\";\n\ntype PrismaConfig = {\n  databaseUrl: string;\n  claimUrl?: string;\n};\n\ntype CreateDbResponse = {\n  connectionString: string;\n  directConnectionString: string;\n  claimUrl: string;\n  deletionDate: string;\n  region: string;\n  name: string;\n  projectId: string;\n};\n\ntype PrismaSetupResult = Result<void, DatabaseSetupError | UserCancelledError>;\n\nconst AVAILABLE_REGIONS = [\n  { value: \"ap-southeast-1\", label: \"Asia Pacific (Singapore)\" },\n  { value: \"ap-northeast-1\", label: \"Asia Pacific (Tokyo)\" },\n  { value: \"eu-central-1\", label: \"Europe (Frankfurt)\" },\n  { value: \"eu-west-3\", label: \"Europe (Paris)\" },\n  { value: \"us-east-1\", label: \"US East (N. Virginia)\" },\n  { value: \"us-west-1\", label: \"US West (N. California)\" },\n];\n\nconst CREATE_DB_USER_AGENT = \"aman/better-t-stack\";\n\nasync function setupWithCreateDb(\n  serverDir: string,\n  packageManager: PackageManager,\n  regionId?: string,\n): Promise<Result<PrismaConfig, DatabaseSetupError | UserCancelledError>> {\n  cliLog.info(\"Starting Prisma Postgres setup with create-db.\");\n\n  let selectedRegion = regionId;\n\n  if (!selectedRegion) {\n    if (isSilent()) {\n      selectedRegion = \"ap-southeast-1\";\n    } else {\n      const promptedRegion = await select({\n        message: \"Select your preferred region:\",\n        options: AVAILABLE_REGIONS,\n        initialValue: \"ap-southeast-1\",\n      });\n\n      if (isCancel(promptedRegion)) {\n        return userCancelled(\"Operation cancelled\");\n      }\n\n      selectedRegion = promptedRegion;\n    }\n  }\n\n  const createDbArgs = [\n    ...getPackageRunnerPrefix(packageManager),\n    \"create-db@latest\",\n    \"create\",\n    \"--json\",\n    \"--region\",\n    selectedRegion,\n    \"--user-agent\",\n    CREATE_DB_USER_AGENT,\n  ];\n\n  const s = createSpinner();\n  s.start(\"Creating Prisma Postgres database...\");\n\n  const execResult = await Result.tryPromise({\n    try: async () => {\n      const { stdout } = await $({ cwd: serverDir })`${createDbArgs}`;\n      s.stop(\"Database created successfully!\");\n      return stdout;\n    },\n    catch: (e) => {\n      s.stop(pc.red(\"Failed to create database\"));\n      return new DatabaseSetupError({\n        provider: \"prisma-postgres\",\n        message: `Failed to create database: ${e instanceof Error ? e.message : String(e)}`,\n        cause: e,\n      });\n    },\n  });\n\n  if (execResult.isErr()) {\n    return execResult;\n  }\n\n  const parseResult = Result.try({\n    try: () => JSON.parse(execResult.value) as CreateDbResponse,\n    catch: (e) =>\n      new DatabaseSetupError({\n        provider: \"prisma-postgres\",\n        message: `Failed to parse create-db response: ${e instanceof Error ? e.message : String(e)}`,\n        cause: e,\n      }),\n  });\n\n  if (parseResult.isErr()) {\n    return parseResult;\n  }\n\n  const createDbResponse = parseResult.value;\n\n  return Result.ok({\n    databaseUrl: createDbResponse.connectionString,\n    claimUrl: createDbResponse.claimUrl,\n  });\n}\n\nasync function writeEnvFile(\n  projectDir: string,\n  backend: ProjectConfig[\"backend\"],\n  config?: PrismaConfig,\n): Promise<Result<void, DatabaseSetupError>> {\n  return Result.tryPromise({\n    try: async () => {\n      const targetApp = backend === \"self\" ? \"apps/web\" : \"apps/server\";\n      const envPath = path.join(projectDir, targetApp, \".env\");\n      const variables: EnvVariable[] = [\n        {\n          key: \"DATABASE_URL\",\n          value:\n            config?.databaseUrl ??\n            \"postgresql://postgres:postgres@localhost:5432/mydb?schema=public\",\n          condition: true,\n        },\n      ];\n\n      if (config?.claimUrl) {\n        variables.push({\n          key: \"CLAIM_URL\",\n          value: config.claimUrl,\n          condition: true,\n        });\n      }\n\n      await addEnvVariablesToFile(envPath, variables);\n    },\n    catch: (e) =>\n      new DatabaseSetupError({\n        provider: \"prisma-postgres\",\n        message: `Failed to update environment configuration: ${e instanceof Error ? e.message : String(e)}`,\n        cause: e,\n      }),\n  });\n}\n\nfunction displayManualSetupInstructions(target: \"apps/web\" | \"apps/server\") {\n  cliLog.info(`Manual Prisma PostgreSQL Setup Instructions:\n\n1. Visit https://console.prisma.io and create an account\n2. Create a new PostgreSQL database from the dashboard\n3. Get your database URL\n4. Add the database URL to the .env file in ${target}/.env\n\nDATABASE_URL=\"your_database_url\"`);\n}\n\nexport async function setupPrismaPostgres(\n  config: ProjectConfig,\n  cliInput?: DatabaseSetupCliOptions,\n): Promise<PrismaSetupResult> {\n  const { packageManager, projectDir, backend } = config;\n  const setupMode = resolveDbSetupMode(\"prisma-postgres\", {\n    manualDb: cliInput?.manualDb,\n    dbSetupOptions: cliInput?.dbSetupOptions ?? config.dbSetupOptions,\n  });\n  const dbDir = path.join(projectDir, \"packages/db\");\n  const target: \"apps/web\" | \"apps/server\" = backend === \"self\" ? \"apps/web\" : \"apps/server\";\n\n  const ensureDirResult = await Result.tryPromise({\n    try: () => fs.ensureDir(dbDir),\n    catch: (e) =>\n      new DatabaseSetupError({\n        provider: \"prisma-postgres\",\n        message: `Failed to create directory: ${e instanceof Error ? e.message : String(e)}`,\n        cause: e,\n      }),\n  });\n\n  if (ensureDirResult.isErr()) {\n    return ensureDirResult;\n  }\n\n  if (setupMode === \"manual\") {\n    const envResult = await writeEnvFile(projectDir, backend);\n    if (envResult.isErr()) {\n      return envResult;\n    }\n    displayManualSetupInstructions(target);\n    return Result.ok(undefined);\n  }\n\n  let selectedSetupMode: DbSetupMode | undefined = setupMode;\n\n  if (!selectedSetupMode) {\n    if (isSilent()) {\n      selectedSetupMode = \"manual\";\n    } else {\n      const promptedSetupMode = await select<DbSetupMode>({\n        message: \"Prisma Postgres setup: choose mode\",\n        options: [\n          {\n            label: \"Automatic (create-db)\",\n            value: \"auto\",\n            hint: \"Provision a database via Prisma's create-db CLI\",\n          },\n          {\n            label: \"Manual\",\n            value: \"manual\",\n            hint: \"Add your own DATABASE_URL later\",\n          },\n        ],\n        initialValue: \"auto\",\n      });\n\n      if (isCancel(promptedSetupMode)) {\n        return userCancelled(\"Operation cancelled\");\n      }\n\n      selectedSetupMode = promptedSetupMode;\n    }\n  }\n\n  if (selectedSetupMode === \"manual\") {\n    const envResult = await writeEnvFile(projectDir, backend);\n    if (envResult.isErr()) {\n      return envResult;\n    }\n    displayManualSetupInstructions(target);\n    return Result.ok(undefined);\n  }\n\n  const prismaConfigResult = await setupWithCreateDb(\n    dbDir,\n    packageManager,\n    cliInput?.dbSetupOptions?.prismaPostgres?.regionId ??\n      config.dbSetupOptions?.prismaPostgres?.regionId,\n  );\n\n  if (prismaConfigResult.isErr()) {\n    // Check for user cancellation\n    if (UserCancelledError.is(prismaConfigResult.error)) {\n      return prismaConfigResult;\n    }\n\n    cliLog.error(pc.red(prismaConfigResult.error.message));\n    const envResult = await writeEnvFile(projectDir, backend);\n    if (envResult.isErr()) {\n      return envResult;\n    }\n    displayManualSetupInstructions(target);\n    cliLog.info(\"Setup completed with manual configuration required.\");\n    return Result.ok(undefined);\n  }\n\n  const envResult = await writeEnvFile(projectDir, backend, prismaConfigResult.value);\n  if (envResult.isErr()) {\n    return envResult;\n  }\n\n  cliLog.success(pc.green(\"Prisma Postgres database configured successfully!\"));\n\n  if (prismaConfigResult.value.claimUrl) {\n    cliLog.info(pc.blue(`Claim URL saved to .env: ${prismaConfigResult.value.claimUrl}`));\n  }\n\n  return Result.ok(undefined);\n}\n"
  },
  {
    "path": "apps/cli/src/helpers/database-providers/supabase-setup.ts",
    "content": "import path from \"node:path\";\n\nimport { isCancel, select } from \"@clack/prompts\";\nimport { Result } from \"better-result\";\nimport { type ExecaError, execa } from \"execa\";\nimport fs from \"fs-extra\";\nimport pc from \"picocolors\";\n\nimport type { PackageManager, ProjectConfig } from \"../../types\";\nimport { isSilent } from \"../../utils/context\";\nimport { addEnvVariablesToFile, type EnvVariable } from \"../../utils/env-utils\";\nimport {\n  DatabaseSetupError,\n  databaseSetupError,\n  UserCancelledError,\n  userCancelled,\n} from \"../../utils/errors\";\nimport { getPackageExecutionArgs } from \"../../utils/package-runner\";\nimport { cliLog } from \"../../utils/terminal-output\";\nimport {\n  type DatabaseSetupCliOptions,\n  type DbSetupMode,\n  resolveDbSetupMode,\n} from \"../core/db-setup-options\";\n\ntype SupabaseSetupResult = Result<void, DatabaseSetupError | UserCancelledError>;\n\nasync function writeSupabaseEnvFile(\n  projectDir: string,\n  backend: ProjectConfig[\"backend\"],\n  databaseUrl: string,\n): Promise<Result<void, DatabaseSetupError>> {\n  return Result.tryPromise({\n    try: async () => {\n      const targetApp = backend === \"self\" ? \"apps/web\" : \"apps/server\";\n      const envPath = path.join(projectDir, targetApp, \".env\");\n      const dbUrlToUse = databaseUrl || \"postgresql://postgres:postgres@127.0.0.1:54322/postgres\";\n      const variables: EnvVariable[] = [\n        {\n          key: \"DATABASE_URL\",\n          value: dbUrlToUse,\n          condition: true,\n        },\n        {\n          key: \"DIRECT_URL\",\n          value: dbUrlToUse,\n          condition: true,\n        },\n      ];\n      await addEnvVariablesToFile(envPath, variables);\n    },\n    catch: (e) =>\n      new DatabaseSetupError({\n        provider: \"supabase\",\n        message: `Failed to update .env file: ${e instanceof Error ? e.message : String(e)}`,\n        cause: e,\n      }),\n  });\n}\n\nfunction extractDbUrl(output: string): string | null {\n  const dbUrlMatch = output.match(/DB URL:\\s*(postgresql:\\/\\/[^\\s]+)/);\n  return dbUrlMatch?.[1] ?? null;\n}\n\nasync function initializeSupabase(\n  serverDir: string,\n  packageManager: PackageManager,\n): Promise<Result<void, DatabaseSetupError>> {\n  cliLog.info(\"Initializing Supabase project...\");\n  return Result.tryPromise({\n    try: async () => {\n      const supabaseInitArgs = getPackageExecutionArgs(packageManager, \"supabase init\");\n      await execa(supabaseInitArgs[0], supabaseInitArgs.slice(1), {\n        cwd: serverDir,\n        stdio: \"inherit\",\n      });\n      cliLog.success(\"Supabase project initialized\");\n    },\n    catch: (e) => {\n      const error = e as Error;\n      const isNotFound = error.message?.includes(\"ENOENT\");\n      const message = isNotFound\n        ? \"Supabase CLI not found. Please install it globally (npm install -g supabase) or ensure it's in your PATH.\"\n        : `Failed to initialize Supabase project: ${error.message ?? String(e)}`;\n\n      return new DatabaseSetupError({\n        provider: \"supabase\",\n        message,\n        cause: e,\n      });\n    },\n  });\n}\n\nasync function startSupabase(\n  serverDir: string,\n  packageManager: PackageManager,\n): Promise<Result<string, DatabaseSetupError>> {\n  cliLog.info(\"Starting Supabase services (this may take a moment)...\");\n  const supabaseStartArgs = getPackageExecutionArgs(packageManager, \"supabase start\");\n\n  return Result.tryPromise({\n    try: async () => {\n      const subprocess = execa(supabaseStartArgs[0], supabaseStartArgs.slice(1), {\n        cwd: serverDir,\n      });\n\n      let stdoutData = \"\";\n\n      if (subprocess.stdout) {\n        subprocess.stdout.on(\"data\", (data) => {\n          const text = data.toString();\n          if (!isSilent()) {\n            process.stdout.write(text);\n          }\n          stdoutData += text;\n        });\n      }\n\n      if (subprocess.stderr) {\n        subprocess.stderr.pipe(process.stderr);\n      }\n\n      await subprocess;\n      await new Promise((resolve) => setTimeout(resolve, 100));\n\n      return stdoutData;\n    },\n    catch: (e) => {\n      const execaError = e as ExecaError;\n      const isDockerError = execaError?.message?.includes(\"Docker is not running\");\n      const message = isDockerError\n        ? \"Docker is not running. Please start Docker and try again.\"\n        : `Failed to start Supabase services: ${execaError?.message ?? String(e)}`;\n\n      return new DatabaseSetupError({\n        provider: \"supabase\",\n        message,\n        cause: e,\n      });\n    },\n  });\n}\n\nfunction displayManualSupabaseInstructions(\n  targetApp: \"apps/web\" | \"apps/server\",\n  output?: string | null,\n) {\n  cliLog.info(\n    `\"Manual Supabase Setup Instructions:\"\n1. Ensure Docker is installed and running.\n2. Install the Supabase CLI (e.g., \\`npm install -g supabase\\`).\n3. Run \\`supabase init\\` in your project's \\`packages/db\\` directory.\n4. Run \\`supabase start\\` in your project's \\`packages/db\\` directory.\n5. Copy the 'DB URL' from the output.${\n      output\n        ? `\n${pc.bold(\"Relevant output from `supabase start`:\")}\n${pc.dim(output)}`\n        : \"\"\n    }\n6. Add the DB URL to the .env file in \\`${targetApp}/.env\\` as \\`DATABASE_URL\\`:\n\t\t\t${pc.gray('DATABASE_URL=\"your_supabase_db_url\"')}`,\n  );\n}\n\nexport async function setupSupabase(\n  config: ProjectConfig,\n  cliInput?: DatabaseSetupCliOptions,\n): Promise<SupabaseSetupResult> {\n  const { projectDir, packageManager, backend } = config;\n  const targetApp: \"apps/web\" | \"apps/server\" = backend === \"self\" ? \"apps/web\" : \"apps/server\";\n  const setupMode = resolveDbSetupMode(\"supabase\", {\n    manualDb: cliInput?.manualDb,\n    dbSetupOptions: cliInput?.dbSetupOptions ?? config.dbSetupOptions,\n  });\n\n  const serverDir = path.join(projectDir, \"packages\", \"db\");\n\n  const ensureDirResult = await Result.tryPromise({\n    try: () => fs.ensureDir(serverDir),\n    catch: (e) =>\n      new DatabaseSetupError({\n        provider: \"supabase\",\n        message: `Failed to create directory: ${e instanceof Error ? e.message : String(e)}`,\n        cause: e,\n      }),\n  });\n\n  if (ensureDirResult.isErr()) {\n    return ensureDirResult;\n  }\n\n  if (setupMode === \"manual\") {\n    displayManualSupabaseInstructions(targetApp);\n    return writeSupabaseEnvFile(projectDir, backend, \"\");\n  }\n\n  let mode: DbSetupMode | undefined = setupMode;\n\n  if (!mode) {\n    if (isSilent()) {\n      mode = \"manual\";\n    } else {\n      const promptedMode = await select<DbSetupMode>({\n        message: \"Supabase setup: choose mode\",\n        options: [\n          {\n            label: \"Automatic\",\n            value: \"auto\",\n            hint: \"Automated setup with provider CLI, sets .env\",\n          },\n          {\n            label: \"Manual\",\n            value: \"manual\",\n            hint: \"Manual setup, add env vars yourself\",\n          },\n        ],\n        initialValue: \"auto\",\n      });\n\n      if (isCancel(promptedMode)) {\n        return userCancelled(\"Operation cancelled\");\n      }\n\n      mode = promptedMode;\n    }\n  }\n\n  if (mode === \"manual\") {\n    displayManualSupabaseInstructions(targetApp);\n    return writeSupabaseEnvFile(projectDir, backend, \"\");\n  }\n\n  const initResult = await initializeSupabase(serverDir, packageManager);\n  if (initResult.isErr()) {\n    cliLog.error(pc.red(initResult.error.message));\n    displayManualSupabaseInstructions(targetApp);\n    return writeSupabaseEnvFile(projectDir, backend, \"\");\n  }\n\n  const startResult = await startSupabase(serverDir, packageManager);\n  if (startResult.isErr()) {\n    cliLog.error(pc.red(startResult.error.message));\n    displayManualSupabaseInstructions(targetApp);\n    return writeSupabaseEnvFile(projectDir, backend, \"\");\n  }\n\n  const supabaseOutput = startResult.value;\n  const dbUrl = extractDbUrl(supabaseOutput);\n\n  if (dbUrl) {\n    const envResult = await writeSupabaseEnvFile(projectDir, backend, dbUrl);\n    if (envResult.isOk()) {\n      cliLog.success(pc.green(\"Supabase local development setup ready!\"));\n    } else {\n      cliLog.error(pc.red(\"Supabase setup completed, but failed to update .env automatically.\"));\n      displayManualSupabaseInstructions(targetApp, supabaseOutput);\n    }\n    return envResult;\n  }\n\n  cliLog.error(pc.yellow(\"Supabase started, but could not extract DB URL automatically.\"));\n  displayManualSupabaseInstructions(targetApp, supabaseOutput);\n  return databaseSetupError(\n    \"supabase\",\n    \"Could not extract database URL from Supabase output. Please configure manually.\",\n  );\n}\n"
  },
  {
    "path": "apps/cli/src/helpers/database-providers/turso-setup.ts",
    "content": "import os from \"node:os\";\nimport path from \"node:path\";\n\nimport { confirm, isCancel, select, text } from \"@clack/prompts\";\nimport { Result } from \"better-result\";\nimport { $ } from \"execa\";\nimport pc from \"picocolors\";\n\nimport type { ProjectConfig } from \"../../types\";\nimport { commandExists } from \"../../utils/command-exists\";\nimport { isSilent } from \"../../utils/context\";\nimport { addEnvVariablesToFile, type EnvVariable } from \"../../utils/env-utils\";\nimport { DatabaseSetupError, UserCancelledError, userCancelled } from \"../../utils/errors\";\nimport { cliLog, createSpinner } from \"../../utils/terminal-output\";\nimport {\n  type DatabaseSetupCliOptions,\n  type DbSetupMode,\n  resolveDbSetupMode,\n} from \"../core/db-setup-options\";\n\ntype TursoConfig = {\n  dbUrl: string;\n  authToken: string;\n};\n\ntype TursoSetupResult = Result<void, DatabaseSetupError | UserCancelledError>;\n\nasync function isTursoInstalled(): Promise<boolean> {\n  return commandExists(\"turso\");\n}\n\nasync function isTursoLoggedIn(): Promise<boolean> {\n  const result = await Result.tryPromise({\n    try: async () => {\n      const output = await $`turso auth whoami`;\n      return !output.stdout.includes(\"You are not logged in\");\n    },\n    catch: () => false,\n  });\n  return result.isOk() ? result.value : false;\n}\n\nasync function loginToTurso(): Promise<Result<void, DatabaseSetupError>> {\n  const s = createSpinner();\n  s.start(\"Logging in to Turso...\");\n\n  return Result.tryPromise({\n    try: async () => {\n      await $`turso auth login`;\n      s.stop(\"Logged into Turso\");\n    },\n    catch: (e) => {\n      s.stop(pc.red(\"Failed to log in to Turso\"));\n      return new DatabaseSetupError({\n        provider: \"turso\",\n        message: `Failed to log in to Turso: ${e instanceof Error ? e.message : String(e)}`,\n        cause: e,\n      });\n    },\n  });\n}\n\nasync function installTursoCLI(isMac: boolean): Promise<Result<void, DatabaseSetupError>> {\n  const s = createSpinner();\n  s.start(\"Installing Turso CLI...\");\n\n  return Result.tryPromise({\n    try: async () => {\n      if (isMac) {\n        await $`brew install tursodatabase/tap/turso`;\n      } else {\n        const { stdout: installScript } = await $`curl -sSfL https://get.tur.so/install.sh`;\n        await $`bash -c '${installScript}'`;\n      }\n      s.stop(\"Turso CLI installed\");\n    },\n    catch: (e) => {\n      const error = e as Error;\n      const isCancelled = error.message?.includes(\"User force closed\");\n      s.stop(\n        isCancelled ? \"Turso CLI installation cancelled\" : pc.red(\"Failed to install Turso CLI\"),\n      );\n\n      return new DatabaseSetupError({\n        provider: \"turso\",\n        message: isCancelled\n          ? \"Installation cancelled by user\"\n          : `Failed to install Turso CLI: ${error.message ?? String(e)}`,\n        cause: e,\n      });\n    },\n  });\n}\n\ntype TursoGroup = {\n  name: string;\n  locations: string;\n  version: string;\n  status: string;\n};\n\nasync function getTursoGroups(): Promise<TursoGroup[]> {\n  const s = createSpinner();\n  s.start(\"Fetching Turso groups...\");\n\n  const result = await Result.tryPromise({\n    try: async () => {\n      const { stdout } = await $`turso group list`;\n      const lines = stdout.trim().split(\"\\n\");\n\n      if (lines.length <= 1) {\n        s.stop(\"No Turso groups found\");\n        return [];\n      }\n\n      const groups = lines.slice(1).map((line) => {\n        const [name, locations, version, status] = line.trim().split(/\\s{2,}/);\n        return { name, locations, version, status };\n      });\n\n      s.stop(`Found ${groups.length} Turso groups`);\n      return groups;\n    },\n    catch: () => {\n      s.stop(pc.red(\"Error fetching Turso groups\"));\n      return [] as TursoGroup[];\n    },\n  });\n\n  return result.isOk() ? result.value : [];\n}\n\nasync function selectTursoGroup(): Promise<Result<string | null, UserCancelledError>> {\n  const groups = await getTursoGroups();\n\n  if (groups.length === 0) {\n    return Result.ok(null);\n  }\n\n  if (groups.length === 1) {\n    cliLog.info(`Using the only available group: ${pc.blue(groups[0].name)}`);\n    return Result.ok(groups[0].name);\n  }\n\n  const groupOptions = groups.map((group) => ({\n    value: group.name,\n    label: `${group.name} (${group.locations})`,\n  }));\n\n  const selectedGroup = await select({\n    message: \"Select a Turso database group:\",\n    options: groupOptions,\n  });\n\n  if (isCancel(selectedGroup)) {\n    return userCancelled(\"Operation cancelled\");\n  }\n\n  return Result.ok(selectedGroup as string);\n}\n\nasync function createTursoDatabase(\n  dbName: string,\n  groupName: string | null,\n): Promise<Result<TursoConfig, DatabaseSetupError>> {\n  const s = createSpinner();\n  s.start(`Creating Turso database \"${dbName}\"${groupName ? ` in group \"${groupName}\"` : \"\"}...`);\n\n  const createResult = await Result.tryPromise({\n    try: async () => {\n      if (groupName) {\n        await $`turso db create ${dbName} --group ${groupName}`;\n      } else {\n        await $`turso db create ${dbName}`;\n      }\n      s.stop(`Turso database \"${dbName}\" created`);\n    },\n    catch: (e) => {\n      const error = e as Error;\n      s.stop(pc.red(`Failed to create database \"${dbName}\"`));\n\n      if (error.message?.includes(\"already exists\")) {\n        return new DatabaseSetupError({\n          provider: \"turso\",\n          message: \"DATABASE_EXISTS\",\n          cause: e,\n        });\n      }\n\n      return new DatabaseSetupError({\n        provider: \"turso\",\n        message: `Failed to create database: ${error.message ?? String(e)}`,\n        cause: e,\n      });\n    },\n  });\n\n  if (createResult.isErr()) {\n    return createResult;\n  }\n\n  s.start(\"Retrieving database connection details...\");\n\n  return Result.tryPromise({\n    try: async () => {\n      const { stdout: dbUrl } = await $`turso db show ${dbName} --url`;\n      const { stdout: authToken } = await $`turso db tokens create ${dbName}`;\n\n      s.stop(\"Database connection details retrieved\");\n\n      return {\n        dbUrl: dbUrl.trim(),\n        authToken: authToken.trim(),\n      };\n    },\n    catch: (e) => {\n      s.stop(pc.red(\"Failed to retrieve database connection details\"));\n      return new DatabaseSetupError({\n        provider: \"turso\",\n        message: `Failed to retrieve connection details: ${e instanceof Error ? e.message : String(e)}`,\n        cause: e,\n      });\n    },\n  });\n}\n\nasync function writeEnvFile(\n  projectDir: string,\n  backend: ProjectConfig[\"backend\"],\n  config?: TursoConfig,\n): Promise<Result<void, DatabaseSetupError>> {\n  return Result.tryPromise({\n    try: async () => {\n      const targetApp = backend === \"self\" ? \"apps/web\" : \"apps/server\";\n      const envPath = path.join(projectDir, targetApp, \".env\");\n      const variables: EnvVariable[] = [\n        {\n          key: \"DATABASE_URL\",\n          value: config?.dbUrl ?? \"\",\n          condition: true,\n        },\n        {\n          key: \"DATABASE_AUTH_TOKEN\",\n          value: config?.authToken ?? \"\",\n          condition: true,\n        },\n      ];\n      await addEnvVariablesToFile(envPath, variables);\n    },\n    catch: (e) =>\n      new DatabaseSetupError({\n        provider: \"turso\",\n        message: `Failed to update .env file: ${e instanceof Error ? e.message : String(e)}`,\n        cause: e,\n      }),\n  });\n}\n\nfunction displayManualSetupInstructions(targetApp: \"apps/web\" | \"apps/server\") {\n  cliLog.info(`Manual Turso Setup Instructions:\n\n1. Visit https://turso.tech and create an account\n2. Create a new database from the dashboard\n3. Get your database URL and authentication token\n4. Add these credentials to the .env file in ${targetApp}/.env\n\nDATABASE_URL=your_database_url\nDATABASE_AUTH_TOKEN=your_auth_token`);\n}\n\nexport async function setupTurso(\n  config: ProjectConfig,\n  cliInput?: DatabaseSetupCliOptions,\n): Promise<TursoSetupResult> {\n  const { projectDir, backend } = config;\n  const targetApp: \"apps/web\" | \"apps/server\" = backend === \"self\" ? \"apps/web\" : \"apps/server\";\n  const setupMode = resolveDbSetupMode(\"turso\", {\n    manualDb: cliInput?.manualDb,\n    dbSetupOptions: cliInput?.dbSetupOptions ?? config.dbSetupOptions,\n  });\n  const setupSpinner = createSpinner();\n\n  if (setupMode === \"manual\") {\n    const envResult = await writeEnvFile(projectDir, backend);\n    if (envResult.isErr()) {\n      return envResult;\n    }\n    displayManualSetupInstructions(targetApp);\n    return Result.ok(undefined);\n  }\n\n  let mode: DbSetupMode | undefined = setupMode;\n\n  if (!mode) {\n    if (isSilent()) {\n      mode = \"manual\";\n    } else {\n      const promptedMode = await select<DbSetupMode>({\n        message: \"Turso setup: choose mode\",\n        options: [\n          {\n            label: \"Automatic\",\n            value: \"auto\",\n            hint: \"Automated setup with provider CLI, sets .env\",\n          },\n          {\n            label: \"Manual\",\n            value: \"manual\",\n            hint: \"Manual setup, add env vars yourself\",\n          },\n        ],\n        initialValue: \"auto\",\n      });\n\n      if (isCancel(promptedMode)) {\n        return userCancelled(\"Operation cancelled\");\n      }\n\n      mode = promptedMode;\n    }\n  }\n\n  if (mode === \"manual\") {\n    const envResult = await writeEnvFile(projectDir, backend);\n    if (envResult.isErr()) {\n      return envResult;\n    }\n    displayManualSetupInstructions(targetApp);\n    return Result.ok(undefined);\n  }\n\n  setupSpinner.start(\"Checking Turso CLI availability...\");\n  const platform = os.platform();\n  const isMac = platform === \"darwin\";\n  const isWindows = platform === \"win32\";\n\n  if (isWindows) {\n    setupSpinner.stop(pc.yellow(\"Turso setup not supported on Windows\"));\n    cliLog.warn(pc.yellow(\"Automatic Turso setup is not supported on Windows.\"));\n    const envResult = await writeEnvFile(projectDir, backend);\n    if (envResult.isErr()) {\n      return envResult;\n    }\n    displayManualSetupInstructions(targetApp);\n    return Result.ok(undefined);\n  }\n\n  setupSpinner.stop(\"Turso CLI availability checked\");\n\n  const isCliInstalled = await isTursoInstalled();\n\n  if (!isCliInstalled) {\n    let shouldInstall = cliInput?.dbSetupOptions?.turso?.installCli;\n\n    if (shouldInstall === undefined) {\n      if (isSilent()) {\n        shouldInstall = false;\n      } else {\n        const promptedInstall = await confirm({\n          message: \"Would you like to install Turso CLI?\",\n          initialValue: true,\n        });\n\n        if (isCancel(promptedInstall)) {\n          return userCancelled(\"Operation cancelled\");\n        }\n\n        shouldInstall = promptedInstall;\n      }\n    }\n\n    if (!shouldInstall) {\n      const envResult = await writeEnvFile(projectDir, backend);\n      if (envResult.isErr()) {\n        return envResult;\n      }\n      displayManualSetupInstructions(targetApp);\n      return Result.ok(undefined);\n    }\n\n    const installResult = await installTursoCLI(isMac);\n    if (installResult.isErr()) {\n      cliLog.error(pc.red(installResult.error.message));\n      const envResult = await writeEnvFile(projectDir, backend);\n      if (envResult.isErr()) {\n        return envResult;\n      }\n      displayManualSetupInstructions(targetApp);\n      return Result.ok(undefined);\n    }\n  }\n\n  const isLoggedIn = await isTursoLoggedIn();\n  if (!isLoggedIn) {\n    if (isSilent()) {\n      cliLog.warn(pc.yellow(\"Turso CLI is not logged in. Falling back to manual setup.\"));\n      const envResult = await writeEnvFile(projectDir, backend);\n      if (envResult.isErr()) {\n        return envResult;\n      }\n      displayManualSetupInstructions(targetApp);\n      return Result.ok(undefined);\n    }\n\n    const loginResult = await loginToTurso();\n    if (loginResult.isErr()) {\n      cliLog.error(pc.red(loginResult.error.message));\n      const envResult = await writeEnvFile(projectDir, backend);\n      if (envResult.isErr()) {\n        return envResult;\n      }\n      displayManualSetupInstructions(targetApp);\n      return Result.ok(undefined);\n    }\n  }\n\n  let selectedGroup =\n    cliInput?.dbSetupOptions?.turso?.groupName ?? config.dbSetupOptions?.turso?.groupName ?? null;\n\n  if (!selectedGroup) {\n    if (isSilent()) {\n      const groups = await getTursoGroups();\n      selectedGroup = groups[0]?.name ?? null;\n    } else {\n      const groupResult = await selectTursoGroup();\n      if (groupResult.isErr()) {\n        return groupResult;\n      }\n      selectedGroup = groupResult.value;\n    }\n  }\n\n  let suggestedName =\n    cliInput?.dbSetupOptions?.turso?.databaseName ??\n    config.dbSetupOptions?.turso?.databaseName ??\n    path.basename(projectDir);\n\n  if (isSilent()) {\n    const createResult = await createTursoDatabase(suggestedName, selectedGroup);\n    if (createResult.isErr()) {\n      cliLog.error(pc.red(createResult.error.message));\n      const envResult = await writeEnvFile(projectDir, backend);\n      if (envResult.isErr()) {\n        return envResult;\n      }\n      displayManualSetupInstructions(targetApp);\n      cliLog.success(\"Setup completed with manual configuration required.\");\n      return Result.ok(undefined);\n    }\n\n    const envResult = await writeEnvFile(projectDir, backend, createResult.value);\n    if (envResult.isErr()) {\n      return envResult;\n    }\n\n    cliLog.success(\"Turso database setup completed successfully!\");\n    return Result.ok(undefined);\n  }\n\n  while (true) {\n    const dbNameResponse = await text({\n      message: \"Enter a name for your database:\",\n      defaultValue: suggestedName,\n      initialValue: suggestedName,\n      placeholder: suggestedName,\n    });\n\n    if (isCancel(dbNameResponse)) {\n      return userCancelled(\"Operation cancelled\");\n    }\n\n    const dbName = dbNameResponse as string;\n\n    const createResult = await createTursoDatabase(dbName, selectedGroup);\n\n    if (createResult.isErr()) {\n      if (createResult.error.message === \"DATABASE_EXISTS\") {\n        cliLog.warn(pc.yellow(`Database \"${pc.red(dbName)}\" already exists`));\n        suggestedName = `${dbName}-${Math.floor(Math.random() * 1000)}`;\n        continue;\n      }\n      cliLog.error(pc.red(createResult.error.message));\n      const envResult = await writeEnvFile(projectDir, backend);\n      if (envResult.isErr()) {\n        return envResult;\n      }\n      displayManualSetupInstructions(targetApp);\n      cliLog.success(\"Setup completed with manual configuration required.\");\n      return Result.ok(undefined);\n    }\n\n    const envResult = await writeEnvFile(projectDir, backend, createResult.value);\n    if (envResult.isErr()) {\n      return envResult;\n    }\n\n    cliLog.success(\"Turso database setup completed successfully!\");\n    return Result.ok(undefined);\n  }\n}\n"
  },
  {
    "path": "apps/cli/src/index.ts",
    "content": "import { getAllJsonSchemas } from \"@better-t-stack/types/json-schema\";\nimport { initTRPC } from \"@trpc/server\";\nimport { Result } from \"better-result\";\nimport { createCli, type TrpcCliMeta } from \"trpc-cli\";\nimport z from \"zod\";\n\nimport { historyHandler } from \"./commands/history\";\nimport { openBuilderCommand, openDocsCommand, showSponsorsCommand } from \"./commands/meta\";\nimport { addHandler, type AddResult } from \"./helpers/core/add-handler\";\nimport { createProjectHandler } from \"./helpers/core/command-handlers\";\nimport {\n  type Addons,\n  AddonsSchema,\n  type AddonOptions,\n  type DbSetupOptions,\n  DbSetupOptionsSchema,\n  AddInputSchema,\n  type API,\n  APISchema,\n  type Auth,\n  AuthSchema,\n  type Backend,\n  BackendSchema,\n  type BetterTStackConfig,\n  type CLIInput,\n  type CreateInput,\n  CreateInputSchema,\n  type Database,\n  DatabaseSchema,\n  type DatabaseSetup,\n  DatabaseSetupSchema,\n  type DirectoryConflict,\n  DirectoryConflictSchema,\n  type Examples,\n  ExamplesSchema,\n  type Frontend,\n  FrontendSchema,\n  type InitResult,\n  type ORM,\n  ORMSchema,\n  type PackageManager,\n  PackageManagerSchema,\n  type Payments,\n  PaymentsSchema,\n  type ProjectConfig,\n  ProjectNameSchema,\n  type Runtime,\n  RuntimeSchema,\n  type ServerDeploy,\n  ServerDeploySchema,\n  type Template,\n  TemplateSchema,\n  type WebDeploy,\n  WebDeploySchema,\n} from \"./types\";\nimport { CLIError, ProjectCreationError, UserCancelledError } from \"./utils/errors\";\nimport { getLatestCLIVersion } from \"./utils/get-latest-cli-version\";\nimport { validateConfigCompatibility } from \"./validation\";\n\nexport const SchemaNameSchema = z\n  .enum([\n    \"all\",\n    \"cli\",\n    \"database\",\n    \"orm\",\n    \"backend\",\n    \"runtime\",\n    \"frontend\",\n    \"addons\",\n    \"examples\",\n    \"packageManager\",\n    \"databaseSetup\",\n    \"api\",\n    \"auth\",\n    \"payments\",\n    \"webDeploy\",\n    \"serverDeploy\",\n    \"directoryConflict\",\n    \"template\",\n    \"addonOptions\",\n    \"dbSetupOptions\",\n    \"createInput\",\n    \"addInput\",\n    \"projectConfig\",\n    \"betterTStackConfig\",\n    \"initResult\",\n  ])\n  .default(\"all\");\n\nexport type SchemaName = z.infer<typeof SchemaNameSchema>;\n\nconst t = initTRPC.meta<TrpcCliMeta>().create();\n\nfunction getCliSchemaJson(): unknown {\n  return createCli({\n    router,\n    name: \"create-better-t-stack\",\n    version: getLatestCLIVersion(),\n  }).toJSON();\n}\n\nexport function getSchemaResult(name: SchemaName): unknown {\n  const schemas = getAllJsonSchemas();\n  if (name === \"all\") {\n    return {\n      cli: getCliSchemaJson(),\n      schemas,\n    };\n  }\n  if (name === \"cli\") {\n    return getCliSchemaJson();\n  }\n  return schemas[name];\n}\n\nexport const router = t.router({\n  create: t.procedure\n    .meta({\n      description: \"Create a new Better-T-Stack project\",\n      default: true,\n      negateBooleans: true,\n    })\n    .input(\n      z.tuple([\n        ProjectNameSchema.optional(),\n        z.object({\n          template: TemplateSchema.optional().describe(\"Use a predefined template\"),\n          yes: z.boolean().optional().default(false).describe(\"Use default configuration\"),\n          yolo: z\n            .boolean()\n            .optional()\n            .default(false)\n            .describe(\"(WARNING - NOT RECOMMENDED) Bypass validations and compatibility checks\"),\n          dryRun: z\n            .boolean()\n            .optional()\n            .default(false)\n            .describe(\"Validate setup without writing files\"),\n          verbose: z\n            .boolean()\n            .optional()\n            .default(false)\n            .describe(\"Show detailed result information\"),\n          database: DatabaseSchema.optional(),\n          orm: ORMSchema.optional(),\n          auth: AuthSchema.optional(),\n          payments: PaymentsSchema.optional(),\n          frontend: z.array(FrontendSchema).optional(),\n          addons: z.array(AddonsSchema).optional(),\n          examples: z.array(ExamplesSchema).optional(),\n          git: z.boolean().optional(),\n          packageManager: PackageManagerSchema.optional(),\n          install: z.boolean().optional(),\n          dbSetup: DatabaseSetupSchema.optional(),\n          backend: BackendSchema.optional(),\n          runtime: RuntimeSchema.optional(),\n          api: APISchema.optional(),\n          webDeploy: WebDeploySchema.optional(),\n          serverDeploy: ServerDeploySchema.optional(),\n          directoryConflict: DirectoryConflictSchema.optional(),\n          renderTitle: z.boolean().optional(),\n          disableAnalytics: z.boolean().optional().default(false).describe(\"Disable analytics\"),\n          manualDb: z\n            .boolean()\n            .optional()\n            .default(false)\n            .describe(\"Skip automatic/manual database setup prompt and use manual setup\"),\n          dbSetupOptions: DbSetupOptionsSchema.optional().describe(\n            \"Structured database setup options\",\n          ),\n        }),\n      ]),\n    )\n    .mutation(async ({ input }) => {\n      const [projectName, options] = input;\n      const combinedInput = {\n        projectName,\n        ...options,\n      };\n      const result = await createProjectHandler(combinedInput);\n\n      if (options.verbose || options.dryRun) {\n        return result;\n      }\n    }),\n  createJson: t.procedure\n    .meta({\n      description: \"Create a project from a raw JSON payload (agent-friendly)\",\n      jsonInput: true,\n    })\n    .input(CreateInputSchema)\n    .mutation(async ({ input }) => {\n      const result = await createProjectHandler(input, { silent: true });\n      if (!result) {\n        throw new UserCancelledError({ message: \"Operation cancelled\" });\n      }\n      if (!result.success) {\n        throw new CLIError({\n          message: result.error || \"Unknown error occurred\",\n        });\n      }\n      return result;\n    }),\n  schema: t.procedure\n    .meta({ description: \"Show runtime CLI and input schemas as JSON\" })\n    .input(\n      z.object({\n        name: SchemaNameSchema.describe(\"Schema name to inspect\"),\n      }),\n    )\n    .query(({ input }) => getSchemaResult(input.name)),\n  sponsors: t.procedure\n    .meta({ description: \"Show Better-T-Stack sponsors\" })\n    .mutation(() => showSponsorsCommand()),\n  docs: t.procedure\n    .meta({ description: \"Open Better-T-Stack documentation\" })\n    .mutation(() => openDocsCommand()),\n  builder: t.procedure\n    .meta({ description: \"Open the web-based stack builder\" })\n    .mutation(() => openBuilderCommand()),\n  add: t.procedure\n    .meta({ description: \"Add addons to an existing Better-T-Stack project\" })\n    .input(\n      z.object({\n        addons: z.array(AddonsSchema).optional().describe(\"Addons to add\"),\n        install: z\n          .boolean()\n          .optional()\n          .default(false)\n          .describe(\"Install dependencies after adding\"),\n        packageManager: PackageManagerSchema.optional().describe(\"Package manager to use\"),\n        projectDir: z.string().optional().describe(\"Project directory (defaults to current)\"),\n      }),\n    )\n    .mutation(async ({ input }) => {\n      await addHandler(input);\n    }),\n  addJson: t.procedure\n    .meta({\n      description: \"Add addons from a raw JSON payload (agent-friendly)\",\n      jsonInput: true,\n    })\n    .input(AddInputSchema)\n    .mutation(async ({ input }) => {\n      const result = await addHandler(input, { silent: true });\n      if (!result) {\n        throw new UserCancelledError({ message: \"Operation cancelled\" });\n      }\n      if (!result.success) {\n        throw new CLIError({\n          message: result.error || \"Unknown error occurred\",\n        });\n      }\n      return result;\n    }),\n  history: t.procedure\n    .meta({ description: \"Show project creation history\" })\n    .input(\n      z.object({\n        limit: z.number().optional().default(10).describe(\"Number of entries to show\"),\n        clear: z.boolean().optional().default(false).describe(\"Clear all history\"),\n        json: z.boolean().optional().default(false).describe(\"Output as JSON\"),\n      }),\n    )\n    .mutation(async ({ input }) => {\n      await historyHandler(input);\n    }),\n});\n\nexport function createBtsCli(): ReturnType<typeof createCli> {\n  return createCli({\n    router,\n    name: \"create-better-t-stack\",\n    version: getLatestCLIVersion(),\n  });\n}\n\n// Re-export Result type from better-result for programmatic API consumers\nexport { Result } from \"better-result\";\n\n/**\n * Error types that can be returned from create/createVirtual\n */\nexport type CreateError = UserCancelledError | CLIError | ProjectCreationError;\n\n/**\n * Programmatic API to create a new Better-T-Stack project.\n * Returns a Result type - no console output, no interactive prompts.\n *\n * @example\n * ```typescript\n * import { create, Result } from \"create-better-t-stack\";\n *\n * const result = await create(\"my-app\", {\n *   frontend: [\"tanstack-router\"],\n *   backend: \"hono\",\n *   runtime: \"bun\",\n *   database: \"sqlite\",\n *   orm: \"drizzle\",\n * });\n *\n * result.match({\n *   ok: (data) => console.log(`Project created at: ${data.projectDirectory}`),\n *   err: (error) => console.error(`Failed: ${error.message}`),\n * });\n *\n * // Or use unwrapOr for a default value\n * const data = result.unwrapOr(null);\n * ```\n */\nexport async function create(\n  projectName?: string,\n  options?: Partial<CreateInput>,\n): Promise<Result<InitResult, CreateError>> {\n  const input = {\n    ...options,\n    projectName,\n    renderTitle: false,\n    verbose: true,\n    disableAnalytics: options?.disableAnalytics ?? true,\n    directoryConflict: options?.directoryConflict ?? \"error\",\n  } as CreateInput & { projectName?: string };\n\n  return Result.tryPromise({\n    try: async () => {\n      const result = await createProjectHandler(input, { silent: true });\n      if (!result) {\n        // User cancelled (undefined return means cancellation in CLI mode)\n        throw new UserCancelledError({ message: \"Operation cancelled\" });\n      }\n      if (!result.success) {\n        throw new CLIError({\n          message: result.error || \"Unknown error occurred\",\n        });\n      }\n      return result as InitResult;\n    },\n    catch: (e: unknown) => {\n      if (e instanceof UserCancelledError) return e;\n      if (e instanceof CLIError) return e;\n      if (e instanceof ProjectCreationError) return e;\n      return new CLIError({\n        message: e instanceof Error ? e.message : String(e),\n        cause: e,\n      });\n    },\n  });\n}\n\nexport async function sponsors() {\n  return showSponsorsCommand();\n}\n\nexport async function docs() {\n  return openDocsCommand();\n}\n\nexport async function builder() {\n  return openBuilderCommand();\n}\n\n// Re-export virtual filesystem types for programmatic usage\nexport {\n  VirtualFileSystem,\n  type VirtualFileTree,\n  type VirtualFile,\n  type VirtualDirectory,\n  type VirtualNode,\n  type GeneratorOptions,\n  GeneratorError,\n  generate,\n  EMBEDDED_TEMPLATES,\n  TEMPLATE_COUNT,\n} from \"@better-t-stack/template-generator\";\n\n// Import for createVirtual\nimport {\n  generate,\n  GeneratorError,\n  type VirtualFileTree,\n  EMBEDDED_TEMPLATES,\n} from \"@better-t-stack/template-generator\";\n\n/**\n * Programmatic API to generate a project in-memory (virtual filesystem).\n * Returns a Result with a VirtualFileTree without writing to disk.\n * Useful for web previews and testing.\n *\n * @example\n * ```typescript\n * import { createVirtual, EMBEDDED_TEMPLATES, Result } from \"create-better-t-stack\";\n *\n * const result = await createVirtual({\n *   frontend: [\"tanstack-router\"],\n *   backend: \"hono\",\n *   runtime: \"bun\",\n *   database: \"sqlite\",\n *   orm: \"drizzle\",\n * });\n *\n * result.match({\n *   ok: (tree) => console.log(`Generated ${tree.fileCount} files`),\n *   err: (error) => console.error(`Failed: ${error.message}`),\n * });\n * ```\n */\nexport async function createVirtual(\n  options: Partial<Omit<ProjectConfig, \"projectDir\" | \"relativePath\">>,\n): Promise<Result<VirtualFileTree, GeneratorError>> {\n  const config: ProjectConfig = {\n    projectName: options.projectName || \"my-project\",\n    projectDir: \"/virtual\",\n    relativePath: \"./virtual\",\n    addonOptions: options.addonOptions,\n    dbSetupOptions: options.dbSetupOptions,\n    database: options.database || \"none\",\n    orm: options.orm || \"none\",\n    backend: options.backend || \"hono\",\n    runtime: options.runtime || \"bun\",\n    frontend: options.frontend || [\"tanstack-router\"],\n    addons: options.addons || [],\n    examples: options.examples || [],\n    auth: options.auth || \"none\",\n    payments: options.payments || \"none\",\n    git: options.git ?? false,\n    packageManager: options.packageManager || \"bun\",\n    install: false,\n    dbSetup: options.dbSetup || \"none\",\n    api: options.api || \"trpc\",\n    webDeploy: options.webDeploy || \"none\",\n    serverDeploy: options.serverDeploy || \"none\",\n  };\n\n  const providedFlags = new Set([\n    \"database\",\n    \"orm\",\n    \"backend\",\n    \"runtime\",\n    \"frontend\",\n    \"addons\",\n    \"examples\",\n    \"auth\",\n    \"dbSetup\",\n    \"payments\",\n    \"api\",\n    \"webDeploy\",\n    \"serverDeploy\",\n  ]);\n  const validationResult = validateConfigCompatibility(\n    config,\n    providedFlags,\n    config as unknown as CLIInput,\n  );\n  if (validationResult.isErr()) {\n    return Result.err(\n      new GeneratorError({\n        message: validationResult.error.message,\n        phase: \"validation\",\n        cause: validationResult.error,\n      }),\n    );\n  }\n\n  return generate({\n    config,\n    templates: EMBEDDED_TEMPLATES,\n  });\n}\n\nexport type {\n  CreateInput,\n  InitResult,\n  BetterTStackConfig,\n  Database,\n  ORM,\n  Backend,\n  Runtime,\n  Frontend,\n  Addons,\n  AddonOptions,\n  DbSetupOptions,\n  Examples,\n  PackageManager,\n  DatabaseSetup,\n  API,\n  Auth,\n  Payments,\n  WebDeploy,\n  ServerDeploy,\n  Template,\n  DirectoryConflict,\n};\n\nexport type { AddResult };\n\n/**\n * Programmatic API to add addons to an existing Better-T-Stack project.\n *\n * @example\n * ```typescript\n * import { add } from \"create-better-t-stack\";\n *\n * const result = await add({\n *   addons: [\"biome\", \"husky\"],\n *   install: true,\n * });\n *\n * if (result?.success) {\n *   console.log(`Added: ${result.addedAddons.join(\", \")}`);\n * }\n * ```\n */\nexport async function add(\n  options: {\n    addons?: Addons[];\n    addonOptions?: AddonOptions;\n    install?: boolean;\n    packageManager?: PackageManager;\n    projectDir?: string;\n    dryRun?: boolean;\n  } = {},\n): Promise<AddResult | undefined> {\n  return addHandler(options, { silent: true });\n}\n\n// Re-export error types for consumers\nexport {\n  UserCancelledError,\n  CLIError,\n  ProjectCreationError,\n  ValidationError,\n  CompatibilityError,\n  DirectoryConflictError,\n  DatabaseSetupError,\n} from \"./utils/errors\";\n"
  },
  {
    "path": "apps/cli/src/mcp.ts",
    "content": "import { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport { StdioServerTransport } from \"@modelcontextprotocol/sdk/server/stdio.js\";\nimport z from \"zod\";\n\nimport { add, create, type SchemaName, SchemaNameSchema, getSchemaResult } from \"./index\";\nimport {\n  AddInputSchema,\n  AddonOptionsSchema,\n  AddonsSchema,\n  APISchema,\n  AuthSchema,\n  BackendSchema,\n  CreateInputSchema,\n  DatabaseSchema,\n  DatabaseSetupSchema,\n  DbSetupOptionsSchema,\n  DirectoryConflictSchema,\n  ExamplesSchema,\n  FrontendSchema,\n  ORMSchema,\n  PackageManagerSchema,\n  PaymentsSchema,\n  RuntimeSchema,\n  ServerDeploySchema,\n  WebDeploySchema,\n} from \"./types\";\nimport { getLatestCLIVersion } from \"./utils/get-latest-cli-version\";\n\nconst ToolResponseSchema = z.object({\n  ok: z.boolean(),\n  data: z.any().optional(),\n  error: z.string().optional(),\n});\n\nconst McpCreateProjectInputSchema = CreateInputSchema.safeExtend({\n  projectName: z.string().describe(\"Project name or relative path\"),\n  frontend: z\n    .array(FrontendSchema)\n    .describe(\"Explicit frontend app surfaces. Do not use native frontends as styling options.\"),\n  backend: BackendSchema.describe(\"Explicit backend framework\"),\n  runtime: RuntimeSchema.describe(\"Explicit runtime environment\"),\n  database: DatabaseSchema.describe(\"Explicit database choice\"),\n  orm: ORMSchema.describe(\"Explicit ORM choice\"),\n  api: APISchema.describe(\"Explicit API layer\"),\n  auth: AuthSchema.describe(\"Explicit authentication provider\"),\n  payments: PaymentsSchema.describe(\"Explicit payments provider\"),\n  addons: z.array(AddonsSchema).describe(\"Explicit addon list. Use [] when no addons are needed.\"),\n  examples: z\n    .array(ExamplesSchema)\n    .describe(\"Explicit example list. Use [] when no examples are needed.\"),\n  git: z.boolean().describe(\"Whether to initialize a git repository\"),\n  packageManager: PackageManagerSchema.describe(\"Explicit package manager\"),\n  install: z.boolean().describe(\"Whether to install dependencies\"),\n  dbSetup: DatabaseSetupSchema.describe(\"Explicit database setup/provisioning choice\"),\n  webDeploy: WebDeploySchema.describe(\"Explicit web deployment choice\"),\n  serverDeploy: ServerDeploySchema.describe(\"Explicit server deployment choice\"),\n  addonOptions: AddonOptionsSchema.optional(),\n  dbSetupOptions: DbSetupOptionsSchema.optional(),\n  directoryConflict: DirectoryConflictSchema.optional(),\n}).describe(\n  \"Explicit Better T Stack project configuration for MCP use. Provide the full stack config instead of relying on inferred defaults.\",\n);\n\nfunction formatToolSuccess(data: unknown) {\n  return {\n    content: [\n      {\n        type: \"text\" as const,\n        text: JSON.stringify(data, null, 2),\n      },\n    ],\n    structuredContent: {\n      ok: true,\n      data,\n    },\n  };\n}\n\nfunction formatToolError(error: unknown) {\n  const message = error instanceof Error ? error.message : String(error);\n  return {\n    content: [\n      {\n        type: \"text\" as const,\n        text: message,\n      },\n    ],\n    structuredContent: {\n      ok: false,\n      error: message,\n    },\n    isError: true,\n  };\n}\n\nfunction getProjectToolAnnotations() {\n  return {\n    destructiveHint: true,\n    idempotentHint: false,\n    openWorldHint: true,\n  };\n}\n\nfunction getMcpInstallTimeoutMessage(packageManager: string) {\n  return [\n    \"MCP project creation requires `install: false`.\",\n    \"Dependency installation can exceed common MCP client request timeouts and cause the connection to close before the tool returns.\",\n    `Scaffold the project first, then run \\`${packageManager} install\\` in the generated project directory from a terminal.`,\n  ].join(\" \");\n}\n\nfunction getStackGuidance() {\n  return {\n    workflow: [\n      \"Call bts_get_schema or bts_get_stack_guidance before constructing a config if the request is ambiguous.\",\n      \"For project creation, build a full explicit config before calling bts_plan_project.\",\n      \"Always call bts_plan_project before bts_create_project.\",\n      \"Only call bts_create_project after the plan succeeds and matches the user's intent.\",\n      \"Use bts_plan_addons before bts_add_addons for existing projects.\",\n    ],\n    createContract: {\n      requiresExplicitFields: [\n        \"projectName\",\n        \"frontend\",\n        \"backend\",\n        \"runtime\",\n        \"database\",\n        \"orm\",\n        \"api\",\n        \"auth\",\n        \"payments\",\n        \"addons\",\n        \"examples\",\n        \"git\",\n        \"packageManager\",\n        \"install\",\n        \"dbSetup\",\n        \"webDeploy\",\n        \"serverDeploy\",\n      ],\n      optionalFields: [\"addonOptions\", \"dbSetupOptions\", \"directoryConflict\"],\n      rule: \"Do not call bts_plan_project or bts_create_project with a partial payload. MCP project creation requires the full explicit stack config.\",\n    },\n    fieldNotes: {\n      frontend:\n        \"frontend is for app surfaces only. Choose explicit app targets such as next, react-router, tanstack-router, native-bare, native-uniwind, or native-unistyles.\",\n      addons: \"addons must be an explicit array. Use [] when no addons are requested.\",\n      examples: \"examples must be an explicit array. Use [] when no examples are requested.\",\n      dbSetup:\n        \"dbSetup is always required. Use 'none' when no managed database provisioning is requested.\",\n      webDeploy:\n        \"webDeploy is always required. Use 'none' when no web deployment target is requested.\",\n      serverDeploy:\n        \"serverDeploy is always required. Use 'none' when no server deployment target is requested.\",\n      packageManager:\n        \"packageManager is always required because installation and reproducible commands depend on it.\",\n      install:\n        \"install is always required. For MCP project creation, prefer false because many clients enforce request timeouts around long-running dependency installs.\",\n      git: \"git is always required. Set it to true or false explicitly instead of relying on defaults.\",\n    },\n    ambiguityRules: [\n      \"If the user request leaves major stack choices unspecified, stop and resolve them before calling bts_plan_project.\",\n      \"Do not infer extra app surfaces, addons, examples, or provisioning choices from a template name or styling preference.\",\n      \"If the user wants the smallest valid stack, still send the full config with explicit 'none', [] , true, or false values where appropriate.\",\n      \"For MCP execution, scaffold with install=false and let the user or agent run dependency installation separately from a terminal session.\",\n    ],\n  };\n}\n\nexport function createBtsMcpServer() {\n  const server = new McpServer(\n    {\n      name: \"create-better-t-stack\",\n      version: getLatestCLIVersion(),\n    },\n    {\n      capabilities: {\n        logging: {},\n      },\n    },\n  );\n\n  server.registerTool(\n    \"bts_get_stack_guidance\",\n    {\n      title: \"Get Better T Stack MCP Guidance\",\n      description:\n        \"Read MCP-specific guidance for choosing valid Better T Stack configurations. Use this before planning when user intent is ambiguous. This explains the full explicit config required by MCP project creation, plus important field semantics and ambiguity rules.\",\n      inputSchema: z.object({}),\n      outputSchema: ToolResponseSchema,\n      annotations: {\n        title: \"Get Better T Stack MCP Guidance\",\n        readOnlyHint: true,\n        destructiveHint: false,\n        idempotentHint: true,\n        openWorldHint: false,\n      },\n    },\n    async () => {\n      try {\n        return formatToolSuccess(getStackGuidance());\n      } catch (error) {\n        return formatToolError(error);\n      }\n    },\n  );\n\n  server.registerTool(\n    \"bts_get_schema\",\n    {\n      title: \"Get Better T Stack Schemas\",\n      description:\n        \"Inspect Better T Stack CLI and input schemas so agents can plan valid create/add requests. Use this together with bts_get_stack_guidance before creating a project if any part of the request is ambiguous.\",\n      inputSchema: z.object({\n        name: SchemaNameSchema.optional().describe(\"Schema name to inspect. Defaults to all.\"),\n      }),\n      outputSchema: ToolResponseSchema,\n      annotations: {\n        title: \"Get Better T Stack Schemas\",\n        readOnlyHint: true,\n        destructiveHint: false,\n        idempotentHint: true,\n        openWorldHint: false,\n      },\n    },\n    async ({ name }) => {\n      try {\n        return formatToolSuccess(getSchemaResult((name ?? \"all\") as SchemaName));\n      } catch (error) {\n        return formatToolError(error);\n      }\n    },\n  );\n\n  server.registerTool(\n    \"bts_plan_project\",\n    {\n      title: \"Plan Better T Stack Project\",\n      description:\n        \"Validate and preview a Better T Stack project creation without writing files or provisioning resources. Always use this before bts_create_project. This tool requires an explicit full stack config rather than a partial payload with inferred defaults.\",\n      inputSchema: McpCreateProjectInputSchema,\n      outputSchema: ToolResponseSchema,\n      annotations: {\n        title: \"Plan Better T Stack Project\",\n        readOnlyHint: true,\n        destructiveHint: false,\n        idempotentHint: true,\n        openWorldHint: false,\n      },\n    },\n    async (input) => {\n      try {\n        const result = await create(input.projectName, {\n          ...input,\n          dryRun: true,\n          disableAnalytics: true,\n        });\n\n        if (result.isErr()) {\n          return formatToolError(result.error);\n        }\n\n        const planningData = input.install\n          ? {\n              ...result.value,\n              warnings: [getMcpInstallTimeoutMessage(input.packageManager)],\n              recommendedMcpExecution: {\n                ...input,\n                install: false,\n              },\n            }\n          : result.value;\n\n        return formatToolSuccess(planningData);\n      } catch (error) {\n        return formatToolError(error);\n      }\n    },\n  );\n\n  server.registerTool(\n    \"bts_create_project\",\n    {\n      title: \"Create Better T Stack Project\",\n      description:\n        \"Create a Better T Stack project on disk using the same silent programmatic flow as the CLI JSON API. Call this only after bts_plan_project succeeds and the plan clearly matches the user's intent. This tool requires an explicit full stack config.\",\n      inputSchema: McpCreateProjectInputSchema,\n      outputSchema: ToolResponseSchema,\n      annotations: {\n        title: \"Create Better T Stack Project\",\n        ...getProjectToolAnnotations(),\n      },\n    },\n    async (input) => {\n      try {\n        if (input.install) {\n          return formatToolError(getMcpInstallTimeoutMessage(input.packageManager));\n        }\n\n        const result = await create(input.projectName, {\n          ...input,\n          disableAnalytics: true,\n        });\n\n        if (result.isErr()) {\n          return formatToolError(result.error);\n        }\n\n        return formatToolSuccess(result.value);\n      } catch (error) {\n        return formatToolError(error);\n      }\n    },\n  );\n\n  server.registerTool(\n    \"bts_plan_addons\",\n    {\n      title: \"Plan Better T Stack Addons\",\n      description:\n        \"Validate and preview addon installation for an existing Better T Stack project without writing files. Always use this before bts_add_addons when the addon set or nested options are uncertain.\",\n      inputSchema: AddInputSchema,\n      outputSchema: ToolResponseSchema,\n      annotations: {\n        title: \"Plan Better T Stack Addons\",\n        readOnlyHint: true,\n        destructiveHint: false,\n        idempotentHint: true,\n        openWorldHint: false,\n      },\n    },\n    async (input) => {\n      try {\n        const result = await add({\n          ...input,\n          dryRun: true,\n        });\n\n        if (!result?.success) {\n          return formatToolError(result?.error ?? \"Failed to plan addon installation\");\n        }\n\n        return formatToolSuccess(result);\n      } catch (error) {\n        return formatToolError(error);\n      }\n    },\n  );\n\n  server.registerTool(\n    \"bts_add_addons\",\n    {\n      title: \"Add Better T Stack Addons\",\n      description:\n        \"Install addons into an existing Better T Stack project using the same silent flow as add-json. Call this only after bts_plan_addons succeeds and the planned changes match the user's intent.\",\n      inputSchema: AddInputSchema,\n      outputSchema: ToolResponseSchema,\n      annotations: {\n        title: \"Add Better T Stack Addons\",\n        destructiveHint: true,\n        idempotentHint: false,\n        openWorldHint: true,\n      },\n    },\n    async (input) => {\n      try {\n        const result = await add(input);\n\n        if (!result?.success) {\n          return formatToolError(result?.error ?? \"Failed to add addons\");\n        }\n\n        return formatToolSuccess(result);\n      } catch (error) {\n        return formatToolError(error);\n      }\n    },\n  );\n\n  return server;\n}\n\nexport async function startBtsMcpServer() {\n  const server = createBtsMcpServer();\n  const transport = new StdioServerTransport();\n  await server.connect(transport);\n}\n"
  },
  {
    "path": "apps/cli/src/prompts/addons.ts",
    "content": "import { DEFAULT_CONFIG } from \"../constants\";\nimport {\n  type Addons,\n  AddonsSchema,\n  type Auth,\n  type Backend,\n  type Frontend,\n  type ProjectConfig,\n  type Runtime,\n} from \"../types\";\nimport { getCompatibleAddons, validateAddonCompatibility } from \"../utils/compatibility-rules\";\nimport { UserCancelledError } from \"../utils/errors\";\nimport { isCancel, navigableGroupMultiselect } from \"./navigable\";\n\ntype AddonOption = {\n  value: Addons;\n  label: string;\n  hint: string;\n};\n\ntype AddonProjectConfig = Pick<\n  ProjectConfig,\n  \"frontend\" | \"addons\" | \"auth\" | \"backend\" | \"runtime\"\n>;\n\nfunction getAddonDisplay(addon: Addons): { label: string; hint: string } {\n  let label: string;\n  let hint: string;\n\n  switch (addon) {\n    case \"turborepo\":\n      label = \"Turborepo\";\n      hint = \"High-performance build system\";\n      break;\n    case \"nx\":\n      label = \"Nx\";\n      hint = \"Smart monorepo orchestration and task graph\";\n      break;\n    case \"pwa\":\n      label = \"PWA\";\n      hint = \"Make your app installable and work offline\";\n      break;\n    case \"tauri\":\n      label = \"Tauri\";\n      hint = \"Build native desktop apps from your web frontend\";\n      break;\n    case \"electrobun\":\n      label = \"Electrobun\";\n      hint = \"Wrap web frontends in a lightweight desktop shell\";\n      break;\n    case \"biome\":\n      label = \"Biome\";\n      hint = \"Format, lint, and more\";\n      break;\n    case \"oxlint\":\n      label = \"Oxlint\";\n      hint = \"Oxlint + Oxfmt (linting & formatting)\";\n      break;\n    case \"ultracite\":\n      label = \"Ultracite\";\n      hint = \"Zero-config Biome preset with AI integration\";\n      break;\n    case \"lefthook\":\n      label = \"Lefthook\";\n      hint = \"Fast and powerful Git hooks manager\";\n      break;\n    case \"husky\":\n      label = \"Husky\";\n      hint = \"Modern native Git hooks made easy\";\n      break;\n    case \"starlight\":\n      label = \"Starlight\";\n      hint = \"Build stellar docs with astro\";\n      break;\n    case \"fumadocs\":\n      label = \"Fumadocs\";\n      hint = \"Build excellent documentation site\";\n      break;\n    case \"opentui\":\n      label = \"OpenTUI\";\n      hint = \"Build terminal user interfaces\";\n      break;\n    case \"wxt\":\n      label = \"WXT\";\n      hint = \"Build browser extensions\";\n      break;\n    case \"skills\":\n      label = \"Skills\";\n      hint = \"AI coding agent skills for your stack\";\n      break;\n    case \"mcp\":\n      label = \"MCP\";\n      hint = \"Install MCP servers, including Better T Stack, via add-mcp\";\n      break;\n    case \"evlog\":\n      label = \"evlog\";\n      hint = \"Request logging with Better Auth context and AI SDK telemetry\";\n      break;\n    default:\n      label = addon;\n      hint = `Add ${addon}`;\n  }\n\n  return { label, hint };\n}\n\nconst ADDON_GROUPS = {\n  \"Monorepo & Tasks\": [\"turborepo\", \"nx\"],\n  \"Code Quality\": [\"biome\", \"oxlint\", \"ultracite\", \"husky\", \"lefthook\"],\n  Documentation: [\"starlight\", \"fumadocs\"],\n  \"Platform Extensions\": [\"pwa\", \"tauri\", \"electrobun\", \"opentui\", \"wxt\"],\n  Observability: [\"evlog\"],\n  \"AI & Agent Tools\": [\"skills\", \"mcp\"],\n};\n\nfunction createGroupedOptions(): Record<string, AddonOption[]> {\n  return Object.fromEntries(Object.keys(ADDON_GROUPS).map((group) => [group, [] as AddonOption[]]));\n}\n\nfunction addOptionToGroup(groupedOptions: Record<string, AddonOption[]>, option: AddonOption) {\n  for (const [group, addons] of Object.entries(ADDON_GROUPS)) {\n    if (addons.includes(option.value)) {\n      groupedOptions[group]?.push(option);\n      return;\n    }\n  }\n}\n\nfunction sortAndPruneGroupedOptions(groupedOptions: Record<string, AddonOption[]>) {\n  Object.keys(groupedOptions).forEach((group) => {\n    if (groupedOptions[group].length === 0) {\n      delete groupedOptions[group];\n      return;\n    }\n\n    const groupOrder = ADDON_GROUPS[group as keyof typeof ADDON_GROUPS] || [];\n    groupedOptions[group].sort((a, b) => {\n      const indexA = groupOrder.indexOf(a.value);\n      const indexB = groupOrder.indexOf(b.value);\n      return indexA - indexB;\n    });\n  });\n}\n\nfunction validateAddonSelection(selected: Addons[] | undefined) {\n  if (selected?.includes(\"turborepo\") && selected.includes(\"nx\")) {\n    return \"Choose either Turborepo or Nx as your monorepo tool, not both.\";\n  }\n}\n\nexport async function getAddonsChoice(\n  addons?: Addons[],\n  frontends?: Frontend[],\n  auth?: Auth,\n  backend?: Backend,\n  runtime?: Runtime,\n) {\n  if (addons !== undefined) return addons;\n\n  const allAddons = AddonsSchema.options.filter((addon) => addon !== \"none\");\n  const groupedOptions = createGroupedOptions();\n\n  const frontendsArray = frontends || [];\n\n  for (const addon of allAddons) {\n    const { isCompatible } = validateAddonCompatibility(\n      addon,\n      frontendsArray,\n      auth,\n      backend,\n      runtime,\n    );\n    if (!isCompatible) continue;\n\n    const { label, hint } = getAddonDisplay(addon);\n    const option = { value: addon, label, hint };\n    addOptionToGroup(groupedOptions, option);\n  }\n\n  sortAndPruneGroupedOptions(groupedOptions);\n\n  const initialValues = DEFAULT_CONFIG.addons.filter((addonValue) =>\n    Object.values(groupedOptions).some((options) =>\n      options.some((opt) => opt.value === addonValue),\n    ),\n  );\n\n  const response = await navigableGroupMultiselect<Addons>({\n    message: \"Select addons\",\n    options: groupedOptions,\n    initialValues: initialValues,\n    required: false,\n    validate: validateAddonSelection,\n  });\n\n  if (isCancel(response)) throw new UserCancelledError({ message: \"Operation cancelled\" });\n\n  return response;\n}\n\nexport async function getAddonsToAdd(config: AddonProjectConfig) {\n  const groupedOptions = createGroupedOptions();\n\n  const frontendArray = config.frontend || [];\n\n  const compatibleAddons = getCompatibleAddons(\n    AddonsSchema.options.filter((addon) => addon !== \"none\"),\n    frontendArray,\n    config.addons,\n    config.auth,\n    config.backend,\n    config.runtime,\n  );\n\n  for (const addon of compatibleAddons) {\n    const { label, hint } = getAddonDisplay(addon);\n    const option = { value: addon, label, hint };\n    addOptionToGroup(groupedOptions, option);\n  }\n\n  sortAndPruneGroupedOptions(groupedOptions);\n\n  if (Object.keys(groupedOptions).length === 0) {\n    return [];\n  }\n\n  const response = await navigableGroupMultiselect<Addons>({\n    message: \"Select addons to add\",\n    options: groupedOptions,\n    required: false,\n    validate: validateAddonSelection,\n  });\n\n  if (isCancel(response)) throw new UserCancelledError({ message: \"Operation cancelled\" });\n\n  return response;\n}\n"
  },
  {
    "path": "apps/cli/src/prompts/api.ts",
    "content": "import type { API, Backend, Frontend } from \"../types\";\nimport {\n  allowedApisForFrontends,\n  validateApiFrontendCompatibility,\n} from \"../utils/compatibility-rules\";\nimport { UserCancelledError } from \"../utils/errors\";\nimport { isCancel, navigableSelect } from \"./navigable\";\n\nexport async function getApiChoice(\n  Api?: API | undefined,\n  frontend?: Frontend[],\n  backend?: Backend,\n) {\n  if (backend === \"convex\" || backend === \"none\") {\n    return \"none\";\n  }\n\n  const allowed = allowedApisForFrontends(frontend ?? []);\n\n  if (Api) {\n    const compat = validateApiFrontendCompatibility(Api, frontend ?? []);\n    if (compat.isErr()) throw compat.error;\n    return Api;\n  }\n  const apiOptions = allowed.map((a) =>\n    a === \"trpc\"\n      ? {\n          value: \"trpc\" as const,\n          label: \"tRPC\",\n          hint: \"End-to-end typesafe APIs made easy\",\n        }\n      : a === \"orpc\"\n        ? {\n            value: \"orpc\" as const,\n            label: \"oRPC\",\n            hint: \"End-to-end type-safe APIs that adhere to OpenAPI standards\",\n          }\n        : {\n            value: \"none\" as const,\n            label: \"None\",\n            hint: \"No API layer (e.g. for full-stack frameworks like Next.js with Route Handlers)\",\n          },\n  );\n\n  const apiType = await navigableSelect<API>({\n    message: \"Select API type\",\n    options: apiOptions,\n    initialValue: apiOptions[0].value,\n  });\n\n  if (isCancel(apiType)) throw new UserCancelledError({ message: \"Operation cancelled\" });\n\n  return apiType;\n}\n"
  },
  {
    "path": "apps/cli/src/prompts/auth.ts",
    "content": "import { DEFAULT_CONFIG } from \"../constants\";\nimport type { Auth, Backend, Frontend } from \"../types\";\nimport { supportsConvexBetterAuth } from \"../utils/compatibility-rules\";\nimport { UserCancelledError } from \"../utils/errors\";\nimport { isCancel, navigableSelect } from \"./navigable\";\n\nexport function getAvailableAuthProviders(\n  backend?: Backend,\n  frontend: readonly Frontend[] = [],\n): Auth[] {\n  if (backend === \"none\") {\n    return [\"none\"];\n  }\n\n  const hasClerkCompatibleFrontends = frontend.some((f) =>\n    [\n      \"react-router\",\n      \"tanstack-router\",\n      \"tanstack-start\",\n      \"next\",\n      \"native-bare\",\n      \"native-uniwind\",\n      \"native-unistyles\",\n    ].includes(f),\n  );\n\n  const options: Auth[] = [];\n\n  if (backend === \"convex\") {\n    if (supportsConvexBetterAuth(frontend)) {\n      options.push(\"better-auth\");\n    }\n  } else {\n    options.push(\"better-auth\");\n  }\n\n  if (hasClerkCompatibleFrontends) {\n    options.push(\"clerk\");\n  }\n\n  if (options.length === 0) {\n    return [\"none\"];\n  }\n\n  return [...options, \"none\"];\n}\n\nexport async function getAuthChoice(\n  auth: Auth | undefined,\n  backend?: Backend,\n  frontend: readonly Frontend[] = [],\n) {\n  if (auth !== undefined) return auth;\n  const availableProviders = getAvailableAuthProviders(backend, frontend);\n\n  if (availableProviders.length === 1 && availableProviders[0] === \"none\") {\n    return \"none\" as Auth;\n  }\n\n  const options = availableProviders.map((provider) => {\n    switch (provider) {\n      case \"better-auth\":\n        return {\n          value: \"better-auth\",\n          label: \"Better-Auth\",\n          hint: \"comprehensive auth framework for TypeScript\",\n        };\n      case \"clerk\":\n        return {\n          value: \"clerk\",\n          label: \"Clerk\",\n          hint: \"More than auth, Complete User Management\",\n        };\n      default:\n        return { value: \"none\", label: \"None\", hint: \"No auth\" };\n    }\n  });\n\n  const response = await navigableSelect({\n    message: \"Select authentication provider\",\n    options,\n    initialValue: options.some((option) => option.value === DEFAULT_CONFIG.auth)\n      ? DEFAULT_CONFIG.auth\n      : \"none\",\n  });\n\n  if (isCancel(response)) throw new UserCancelledError({ message: \"Operation cancelled\" });\n\n  return response as Auth;\n}\n"
  },
  {
    "path": "apps/cli/src/prompts/backend.ts",
    "content": "import { DEFAULT_CONFIG } from \"../constants\";\nimport type { Backend, Frontend } from \"../types\";\nimport { UserCancelledError } from \"../utils/errors\";\nimport { isCancel, navigableSelect } from \"./navigable\";\n\n// Frontends that support backend=\"self\" (fullstack mode with built-in server routes)\nconst FULLSTACK_FRONTENDS: readonly Frontend[] = [\n  \"next\",\n  \"tanstack-start\",\n  \"nuxt\",\n  \"svelte\",\n  \"astro\",\n] as const;\n\nexport async function getBackendFrameworkChoice(\n  backendFramework?: Backend,\n  frontends?: Frontend[],\n) {\n  if (backendFramework !== undefined) return backendFramework;\n\n  const hasIncompatibleFrontend = frontends?.some((f) => f === \"solid\" || f === \"astro\");\n  const hasFullstackFrontend = frontends?.some((f) => FULLSTACK_FRONTENDS.includes(f));\n\n  const backendOptions: Array<{\n    value: Backend;\n    label: string;\n    hint: string;\n  }> = [];\n\n  if (hasFullstackFrontend) {\n    backendOptions.push({\n      value: \"self\" as const,\n      label: \"Self (Fullstack)\",\n      hint: \"Use frontend's built-in api routes\",\n    });\n  }\n\n  backendOptions.push(\n    {\n      value: \"hono\" as const,\n      label: \"Hono\",\n      hint: \"Lightweight, ultrafast web framework\",\n    },\n    {\n      value: \"express\" as const,\n      label: \"Express\",\n      hint: \"Fast, unopinionated, minimalist web framework for Node.js\",\n    },\n    {\n      value: \"fastify\" as const,\n      label: \"Fastify\",\n      hint: \"Fast, low-overhead web framework for Node.js\",\n    },\n    {\n      value: \"elysia\" as const,\n      label: \"Elysia\",\n      hint: \"Ergonomic web framework for building backend servers\",\n    },\n  );\n\n  if (!hasIncompatibleFrontend) {\n    backendOptions.push({\n      value: \"convex\" as const,\n      label: \"Convex\",\n      hint: \"Reactive backend-as-a-service platform\",\n    });\n  }\n\n  backendOptions.push({\n    value: \"none\" as const,\n    label: \"None\",\n    hint: \"No backend server\",\n  });\n\n  const response = await navigableSelect<Backend>({\n    message: \"Select backend\",\n    options: backendOptions,\n    initialValue: hasFullstackFrontend ? \"self\" : DEFAULT_CONFIG.backend,\n  });\n\n  if (isCancel(response)) throw new UserCancelledError({ message: \"Operation cancelled\" });\n\n  return response;\n}\n"
  },
  {
    "path": "apps/cli/src/prompts/config-prompts.ts",
    "content": "import { DEFAULT_CONFIG } from \"../constants\";\nimport type {\n  Addons,\n  API,\n  Auth,\n  Backend,\n  Database,\n  DatabaseSetup,\n  Examples,\n  Frontend,\n  ORM,\n  PackageManager,\n  Payments,\n  ProjectConfig,\n  Runtime,\n  ServerDeploy,\n  WebDeploy,\n} from \"../types\";\nimport { isSilent } from \"../utils/context\";\nimport { UserCancelledError } from \"../utils/errors\";\nimport { getAddonsChoice } from \"./addons\";\nimport { getApiChoice } from \"./api\";\nimport { getAuthChoice } from \"./auth\";\nimport { getBackendFrameworkChoice } from \"./backend\";\nimport { getDatabaseChoice } from \"./database\";\nimport { getDBSetupChoice } from \"./database-setup\";\nimport { getExamplesChoice } from \"./examples\";\nimport { getFrontendChoice } from \"./frontend\";\nimport { getGitChoice } from \"./git\";\nimport { getinstallChoice } from \"./install\";\nimport { navigableGroup } from \"./navigable-group\";\nimport { getORMChoice } from \"./orm\";\nimport { getPackageManagerChoice } from \"./package-manager\";\nimport { getPaymentsChoice } from \"./payments\";\nimport { getRuntimeChoice } from \"./runtime\";\nimport { getServerDeploymentChoice } from \"./server-deploy\";\nimport { getDeploymentChoice } from \"./web-deploy\";\n\ntype PromptGroupResults = {\n  frontend: Frontend[];\n  backend: Backend;\n  runtime: Runtime;\n  database: Database;\n  orm: ORM;\n  api: API;\n  auth: Auth;\n  payments: Payments;\n  addons: Addons[];\n  examples: Examples[];\n  dbSetup: DatabaseSetup;\n  git: boolean;\n  packageManager: PackageManager;\n  install: boolean;\n  webDeploy: WebDeploy;\n  serverDeploy: ServerDeploy;\n};\n\nexport async function gatherConfig(\n  flags: Partial<ProjectConfig>,\n  projectName: string,\n  projectDir: string,\n  relativePath: string,\n) {\n  if (isSilent()) {\n    return {\n      projectName,\n      projectDir,\n      relativePath,\n      addonOptions: flags.addonOptions,\n      dbSetupOptions: flags.dbSetupOptions,\n      frontend: flags.frontend ?? [...DEFAULT_CONFIG.frontend],\n      backend: flags.backend ?? DEFAULT_CONFIG.backend,\n      runtime: flags.runtime ?? DEFAULT_CONFIG.runtime,\n      database: flags.database ?? DEFAULT_CONFIG.database,\n      orm: flags.orm ?? DEFAULT_CONFIG.orm,\n      auth: flags.auth ?? DEFAULT_CONFIG.auth,\n      payments: flags.payments ?? DEFAULT_CONFIG.payments,\n      addons: flags.addons ?? [...DEFAULT_CONFIG.addons],\n      examples: flags.examples ?? [...DEFAULT_CONFIG.examples],\n      git: flags.git ?? DEFAULT_CONFIG.git,\n      packageManager: flags.packageManager ?? DEFAULT_CONFIG.packageManager,\n      install: flags.install ?? DEFAULT_CONFIG.install,\n      dbSetup: flags.dbSetup ?? DEFAULT_CONFIG.dbSetup,\n      api: flags.api ?? DEFAULT_CONFIG.api,\n      webDeploy: flags.webDeploy ?? DEFAULT_CONFIG.webDeploy,\n      serverDeploy: flags.serverDeploy ?? DEFAULT_CONFIG.serverDeploy,\n    };\n  }\n\n  const result = await navigableGroup<PromptGroupResults>(\n    {\n      frontend: () => getFrontendChoice(flags.frontend, flags.backend, flags.auth),\n      backend: ({ results }) => getBackendFrameworkChoice(flags.backend, results.frontend),\n      runtime: ({ results }) => getRuntimeChoice(flags.runtime, results.backend),\n      database: ({ results }) =>\n        getDatabaseChoice(flags.database, results.backend, results.runtime),\n      orm: ({ results }) =>\n        getORMChoice(\n          flags.orm,\n          results.database !== \"none\",\n          results.database,\n          results.backend,\n          results.runtime,\n        ),\n      api: ({ results }) =>\n        getApiChoice(flags.api, results.frontend, results.backend) as Promise<API>,\n      auth: ({ results }) => getAuthChoice(flags.auth, results.backend, results.frontend),\n      payments: ({ results }) =>\n        getPaymentsChoice(flags.payments, results.auth, results.backend, results.frontend),\n      addons: ({ results }) =>\n        getAddonsChoice(\n          flags.addons,\n          results.frontend,\n          results.auth,\n          results.backend,\n          results.runtime,\n        ),\n      examples: ({ results }) =>\n        getExamplesChoice(\n          flags.examples,\n          results.database,\n          results.frontend,\n          results.backend,\n          results.api,\n        ) as Promise<Examples[]>,\n      dbSetup: ({ results }) =>\n        getDBSetupChoice(\n          results.database ?? \"none\",\n          flags.dbSetup,\n          results.orm,\n          results.backend,\n          results.runtime,\n        ),\n      webDeploy: ({ results }) =>\n        getDeploymentChoice(\n          flags.webDeploy,\n          results.runtime,\n          results.backend,\n          results.frontend,\n          results.dbSetup,\n        ),\n      serverDeploy: ({ results }) =>\n        getServerDeploymentChoice(\n          flags.serverDeploy,\n          results.runtime,\n          results.backend,\n          results.webDeploy,\n        ),\n      git: () => getGitChoice(flags.git),\n      packageManager: () => getPackageManagerChoice(flags.packageManager),\n      install: () => getinstallChoice(flags.install),\n    },\n    {\n      onCancel: () => {\n        throw new UserCancelledError({ message: \"Operation cancelled\" });\n      },\n    },\n  );\n\n  return {\n    projectName: projectName,\n    projectDir: projectDir,\n    relativePath: relativePath,\n    addonOptions: flags.addonOptions,\n    dbSetupOptions: flags.dbSetupOptions,\n    frontend: result.frontend,\n    backend: result.backend,\n    runtime: result.runtime,\n    database: result.database,\n    orm: result.orm,\n    auth: result.auth,\n    payments: result.payments,\n    addons: result.addons,\n    examples: result.examples,\n    git: result.git,\n    packageManager: result.packageManager,\n    install: result.install,\n    dbSetup: result.dbSetup,\n    api: result.api,\n    webDeploy: result.webDeploy,\n    serverDeploy: result.serverDeploy,\n  };\n}\n"
  },
  {
    "path": "apps/cli/src/prompts/database-setup.ts",
    "content": "import type { Backend, DatabaseSetup, ORM, Runtime } from \"../types\";\nimport { UserCancelledError } from \"../utils/errors\";\nimport { isCancel, navigableSelect } from \"./navigable\";\n\nexport async function getDBSetupChoice(\n  databaseType: string,\n  dbSetup: DatabaseSetup | undefined,\n  _orm?: ORM,\n  backend?: Backend,\n  runtime?: Runtime,\n) {\n  if (backend === \"convex\") {\n    return \"none\";\n  }\n\n  if (dbSetup !== undefined) return dbSetup as DatabaseSetup;\n\n  if (databaseType === \"none\") {\n    return \"none\";\n  }\n\n  let options: Array<{ value: DatabaseSetup; label: string; hint: string }> = [];\n\n  if (databaseType === \"sqlite\") {\n    options = [\n      {\n        value: \"turso\" as const,\n        label: \"Turso\",\n        hint: \"SQLite for Production. Powered by libSQL\",\n      },\n      ...(runtime === \"workers\" || backend === \"self\"\n        ? [\n            {\n              value: \"d1\" as const,\n              label: \"Cloudflare D1\",\n              hint: \"Cloudflare's managed, serverless database with SQLite's SQL semantics\",\n            },\n          ]\n        : []),\n      { value: \"none\" as const, label: \"None\", hint: \"Manual setup\" },\n    ];\n  } else if (databaseType === \"postgres\") {\n    options = [\n      {\n        value: \"neon\" as const,\n        label: \"Neon Postgres\",\n        hint: \"Serverless Postgres with branching capability\",\n      },\n      {\n        value: \"planetscale\" as const,\n        label: \"PlanetScale\",\n        hint: \"Postgres & Vitess (MySQL) on NVMe\",\n      },\n      {\n        value: \"supabase\" as const,\n        label: \"Supabase\",\n        hint: \"Local Supabase stack (requires Docker)\",\n      },\n      {\n        value: \"prisma-postgres\" as const,\n        label: \"Prisma Postgres\",\n        hint: \"Instant Postgres for Global Applications\",\n      },\n      {\n        value: \"docker\" as const,\n        label: \"Docker\",\n        hint: \"Run locally with docker compose\",\n      },\n      { value: \"none\" as const, label: \"None\", hint: \"Manual setup\" },\n    ];\n  } else if (databaseType === \"mysql\") {\n    options = [\n      {\n        value: \"planetscale\" as const,\n        label: \"PlanetScale\",\n        hint: \"MySQL on Vitess (NVMe, HA)\",\n      },\n      {\n        value: \"docker\" as const,\n        label: \"Docker\",\n        hint: \"Run locally with docker compose\",\n      },\n      { value: \"none\" as const, label: \"None\", hint: \"Manual setup\" },\n    ];\n  } else if (databaseType === \"mongodb\") {\n    options = [\n      {\n        value: \"mongodb-atlas\" as const,\n        label: \"MongoDB Atlas\",\n        hint: \"The most effective way to deploy MongoDB\",\n      },\n      {\n        value: \"docker\" as const,\n        label: \"Docker\",\n        hint: \"Run locally with docker compose\",\n      },\n      { value: \"none\" as const, label: \"None\", hint: \"Manual setup\" },\n    ];\n  } else {\n    return \"none\";\n  }\n\n  const response = await navigableSelect<DatabaseSetup>({\n    message: `Select ${databaseType} setup option`,\n    options,\n    initialValue: \"none\",\n  });\n\n  if (isCancel(response)) throw new UserCancelledError({ message: \"Operation cancelled\" });\n\n  return response;\n}\n"
  },
  {
    "path": "apps/cli/src/prompts/database.ts",
    "content": "import { DEFAULT_CONFIG } from \"../constants\";\nimport type { Backend, Database, Runtime } from \"../types\";\nimport { UserCancelledError } from \"../utils/errors\";\nimport { isCancel, navigableSelect } from \"./navigable\";\n\nexport async function getDatabaseChoice(database?: Database, backend?: Backend, runtime?: Runtime) {\n  if (backend === \"convex\" || backend === \"none\") {\n    return \"none\";\n  }\n\n  if (database !== undefined) return database;\n\n  const databaseOptions: Array<{\n    value: Database;\n    label: string;\n    hint: string;\n  }> = [\n    {\n      value: \"none\",\n      label: \"None\",\n      hint: \"No database setup\",\n    },\n    {\n      value: \"sqlite\",\n      label: \"SQLite\",\n      hint: \"lightweight, server-less, embedded relational database\",\n    },\n    {\n      value: \"postgres\",\n      label: \"PostgreSQL\",\n      hint: \"powerful, open source object-relational database system\",\n    },\n    {\n      value: \"mysql\",\n      label: \"MySQL\",\n      hint: \"popular open-source relational database system\",\n    },\n  ];\n\n  if (runtime !== \"workers\") {\n    databaseOptions.push({\n      value: \"mongodb\",\n      label: \"MongoDB\",\n      hint: \"open-source NoSQL database that stores data in JSON-like documents called BSON\",\n    });\n  }\n\n  const response = await navigableSelect<Database>({\n    message: \"Select database\",\n    options: databaseOptions,\n    initialValue: DEFAULT_CONFIG.database,\n  });\n\n  if (isCancel(response)) throw new UserCancelledError({ message: \"Operation cancelled\" });\n\n  return response;\n}\n"
  },
  {
    "path": "apps/cli/src/prompts/examples.ts",
    "content": "import { DEFAULT_CONFIG } from \"../constants\";\nimport type { API, Backend, Database, Examples, Frontend } from \"../types\";\nimport { isExampleAIAllowed, isExampleTodoAllowed } from \"../utils/compatibility-rules\";\nimport { UserCancelledError } from \"../utils/errors\";\nimport { isCancel, navigableMultiselect } from \"./navigable\";\n\nexport async function getExamplesChoice(\n  examples?: Examples[],\n  database?: Database,\n  frontends?: Frontend[],\n  backend?: Backend,\n  api?: API,\n) {\n  if (examples !== undefined) return examples;\n\n  if (backend === \"none\") {\n    return [];\n  }\n\n  let response: Examples[] | symbol = [];\n  const options: { value: Examples; label: string; hint: string }[] = [];\n\n  if (isExampleTodoAllowed(backend, database, api)) {\n    options.push({\n      value: \"todo\" as const,\n      label: \"Todo App\",\n      hint: \"A simple CRUD example app\",\n    });\n  }\n\n  if (isExampleAIAllowed(backend, frontends ?? [])) {\n    options.push({\n      value: \"ai\" as const,\n      label: \"AI Chat\",\n      hint: \"A simple AI chat interface using AI SDK\",\n    });\n  }\n\n  if (options.length === 0) return [];\n\n  response = await navigableMultiselect<Examples>({\n    message: \"Include examples\",\n    options: options,\n    required: false,\n    initialValues: DEFAULT_CONFIG.examples?.filter((ex) => options.some((o) => o.value === ex)),\n  });\n\n  if (isCancel(response)) throw new UserCancelledError({ message: \"Operation cancelled\" });\n\n  return response;\n}\n"
  },
  {
    "path": "apps/cli/src/prompts/frontend.ts",
    "content": "import { DEFAULT_CONFIG } from \"../constants\";\nimport type { Backend, Frontend } from \"../types\";\nimport { isFrontendAllowedWithBackend } from \"../utils/compatibility-rules\";\nimport { isFirstPrompt } from \"../utils/context\";\nimport { UserCancelledError } from \"../utils/errors\";\nimport {\n  GO_BACK_SYMBOL,\n  isCancel,\n  isGoBack,\n  navigableMultiselect,\n  navigableSelect,\n  setIsFirstPrompt,\n} from \"./navigable\";\n\nexport async function getFrontendChoice(\n  frontendOptions?: Frontend[],\n  backend?: Backend,\n  auth?: string,\n): Promise<Frontend[] | symbol> {\n  if (frontendOptions !== undefined) return frontendOptions;\n\n  while (true) {\n    const wasFirstPrompt = isFirstPrompt();\n\n    const frontendTypes = await navigableMultiselect({\n      message: \"Select project type\",\n      options: [\n        {\n          value: \"web\",\n          label: \"Web\",\n          hint: \"React, Vue or Svelte Web Application\",\n        },\n        {\n          value: \"native\",\n          label: \"Native\",\n          hint: \"Create a React Native/Expo app\",\n        },\n      ],\n      required: false,\n      initialValues: [\"web\"],\n    });\n\n    if (isGoBack(frontendTypes)) return GO_BACK_SYMBOL;\n    if (isCancel(frontendTypes)) throw new UserCancelledError({ message: \"Operation cancelled\" });\n\n    setIsFirstPrompt(false);\n\n    const result: Frontend[] = [];\n    let shouldRestart = false;\n\n    if (frontendTypes.includes(\"web\")) {\n      const allWebOptions = [\n        {\n          value: \"tanstack-router\" as const,\n          label: \"TanStack Router\",\n          hint: \"Modern and scalable routing for React Applications\",\n        },\n        {\n          value: \"react-router\" as const,\n          label: \"React Router\",\n          hint: \"A user‑obsessed, standards‑focused, multi‑strategy router\",\n        },\n        {\n          value: \"next\" as const,\n          label: \"Next.js\",\n          hint: \"The React Framework for the Web\",\n        },\n        {\n          value: \"nuxt\" as const,\n          label: \"Nuxt\",\n          hint: \"The Progressive Web Framework for Vue.js\",\n        },\n        {\n          value: \"svelte\" as const,\n          label: \"Svelte\",\n          hint: \"web development for the rest of us\",\n        },\n        {\n          value: \"solid\" as const,\n          label: \"Solid\",\n          hint: \"Simple and performant reactivity for building user interfaces\",\n        },\n        {\n          value: \"astro\" as const,\n          label: \"Astro\",\n          hint: \"The web framework for content-driven websites\",\n        },\n        {\n          value: \"tanstack-start\" as const,\n          label: \"TanStack Start\",\n          hint: \"SSR, Server Functions, API Routes and more with TanStack Router\",\n        },\n      ];\n\n      const webOptions = allWebOptions.filter((option) =>\n        isFrontendAllowedWithBackend(option.value, backend, auth),\n      );\n\n      const webFramework = await navigableSelect<Frontend>({\n        message: \"Choose web\",\n        options: webOptions,\n        initialValue: DEFAULT_CONFIG.frontend[0],\n      });\n\n      if (isGoBack(webFramework)) {\n        shouldRestart = true;\n      } else if (isCancel(webFramework)) {\n        throw new UserCancelledError({ message: \"Operation cancelled\" });\n      } else {\n        result.push(webFramework as Frontend);\n      }\n    }\n\n    if (shouldRestart) {\n      setIsFirstPrompt(wasFirstPrompt);\n      continue;\n    }\n\n    if (frontendTypes.includes(\"native\")) {\n      const nativeFramework = await navigableSelect<Frontend>({\n        message: \"Choose native\",\n        options: [\n          {\n            value: \"native-bare\" as const,\n            label: \"Bare\",\n            hint: \"Bare Expo without styling library\",\n          },\n          {\n            value: \"native-uniwind\" as const,\n            label: \"Uniwind\",\n            hint: \"Fastest Tailwind bindings for React Native with HeroUI Native\",\n          },\n          {\n            value: \"native-unistyles\" as const,\n            label: \"Unistyles\",\n            hint: \"Consistent styling for React Native\",\n          },\n        ],\n        initialValue: \"native-bare\",\n      });\n\n      if (isGoBack(nativeFramework)) {\n        if (frontendTypes.includes(\"web\")) {\n          shouldRestart = true;\n        } else {\n          setIsFirstPrompt(wasFirstPrompt);\n          continue;\n        }\n      } else if (isCancel(nativeFramework)) {\n        throw new UserCancelledError({ message: \"Operation cancelled\" });\n      } else {\n        result.push(nativeFramework as Frontend);\n      }\n    }\n\n    if (shouldRestart) {\n      setIsFirstPrompt(wasFirstPrompt);\n      continue;\n    }\n\n    return result;\n  }\n}\n"
  },
  {
    "path": "apps/cli/src/prompts/git.ts",
    "content": "import { DEFAULT_CONFIG } from \"../constants\";\nimport { UserCancelledError } from \"../utils/errors\";\nimport { isCancel, navigableConfirm } from \"./navigable\";\n\nexport async function getGitChoice(git?: boolean) {\n  if (git !== undefined) return git;\n\n  const response = await navigableConfirm({\n    message: \"Initialize git repository?\",\n    initialValue: DEFAULT_CONFIG.git,\n  });\n\n  if (isCancel(response)) throw new UserCancelledError({ message: \"Operation cancelled\" });\n\n  return response;\n}\n"
  },
  {
    "path": "apps/cli/src/prompts/install.ts",
    "content": "import { DEFAULT_CONFIG } from \"../constants\";\nimport { UserCancelledError } from \"../utils/errors\";\nimport { isCancel, navigableConfirm } from \"./navigable\";\n\nexport async function getinstallChoice(install?: boolean) {\n  if (install !== undefined) return install;\n\n  const response = await navigableConfirm({\n    message: \"Install dependencies?\",\n    initialValue: DEFAULT_CONFIG.install,\n  });\n\n  if (isCancel(response)) throw new UserCancelledError({ message: \"Operation cancelled\" });\n\n  return response;\n}\n"
  },
  {
    "path": "apps/cli/src/prompts/navigable-group.ts",
    "content": "/**\n * Navigable group - a group of prompts that allows going back\n */\n\nimport { didLastPromptShowUI, setIsFirstPrompt, setLastPromptShownUI } from \"../utils/context\";\nimport { isGoBack } from \"../utils/navigation\";\nimport { isCancel } from \"./navigable\";\n\ntype Prettify<T> = {\n  [P in keyof T]: T[P];\n} & {};\n\nexport type PromptGroupAwaitedReturn<T> = {\n  [P in keyof T]: Exclude<Awaited<T[P]>, symbol>;\n};\n\nexport interface NavigablePromptGroupOptions<T> {\n  /**\n   * Control how the group can be canceled\n   * if one of the prompts is canceled.\n   */\n  onCancel?: (opts: { results: Prettify<Partial<PromptGroupAwaitedReturn<T>>> }) => void;\n}\n\nexport type NavigablePromptGroup<T> = {\n  [P in keyof T]: (opts: {\n    results: Prettify<Partial<PromptGroupAwaitedReturn<Omit<T, P>>>>;\n  }) => undefined | Promise<T[P] | symbol | undefined>;\n};\n\n/**\n * Define a group of prompts that supports going back to previous prompts.\n * Returns a result object with all the values, or handles cancel/go-back navigation.\n */\nexport async function navigableGroup<T>(\n  prompts: NavigablePromptGroup<T>,\n  opts?: NavigablePromptGroupOptions<T>,\n): Promise<Prettify<PromptGroupAwaitedReturn<T>>> {\n  const results = {} as any;\n  const promptNames = Object.keys(prompts) as (keyof T)[];\n  let currentIndex = 0;\n  let goingBack = false;\n\n  while (currentIndex < promptNames.length) {\n    const name = promptNames[currentIndex];\n    const prompt = prompts[name];\n\n    setIsFirstPrompt(currentIndex === 0);\n\n    setLastPromptShownUI(false);\n\n    const result = await prompt({ results })?.catch((e) => {\n      throw e;\n    });\n\n    if (isGoBack(result)) {\n      goingBack = true;\n      if (currentIndex > 0) {\n        const prevName = promptNames[currentIndex - 1];\n        delete results[prevName];\n        currentIndex--;\n        continue;\n      }\n      goingBack = false;\n      continue;\n    }\n\n    if (isCancel(result)) {\n      if (typeof opts?.onCancel === \"function\") {\n        results[name] = \"canceled\";\n        opts.onCancel({ results });\n      }\n      setIsFirstPrompt(false);\n      return results;\n    }\n\n    if (goingBack && !didLastPromptShowUI()) {\n      if (currentIndex > 0) {\n        const prevName = promptNames[currentIndex - 1];\n        delete results[prevName];\n        currentIndex--;\n        continue;\n      }\n    }\n\n    goingBack = false;\n\n    results[name] = result;\n    currentIndex++;\n  }\n\n  setIsFirstPrompt(false);\n\n  return results;\n}\n"
  },
  {
    "path": "apps/cli/src/prompts/navigable.ts",
    "content": "/**\n * Navigable prompt wrappers using @clack/core\n * These prompts return GO_BACK_SYMBOL when 'b' is pressed (instead of canceling)\n */\n\nimport {\n  ConfirmPrompt,\n  type State,\n  GroupMultiSelectPrompt,\n  MultiSelectPrompt,\n  SelectPrompt,\n  TextPrompt,\n  isCancel,\n} from \"@clack/core\";\nimport pc from \"picocolors\";\n\nimport {\n  didLastPromptShowUI as ctxDidLastPromptShowUI,\n  isFirstPrompt as ctxIsFirstPrompt,\n  setIsFirstPrompt as ctxSetIsFirstPrompt,\n  setLastPromptShownUI as ctxSetLastPromptShownUI,\n} from \"../utils/context\";\nimport { GO_BACK_SYMBOL } from \"../utils/navigation\";\n\nconst unicode = process.platform !== \"win32\";\nconst S_STEP_ACTIVE = unicode ? \"◆\" : \"*\";\nconst S_STEP_CANCEL = unicode ? \"■\" : \"x\";\nconst S_STEP_ERROR = unicode ? \"▲\" : \"x\";\nconst S_STEP_SUBMIT = unicode ? \"◇\" : \"o\";\nconst S_BAR = unicode ? \"│\" : \"|\";\nconst S_BAR_END = unicode ? \"└\" : \"—\";\nconst S_RADIO_ACTIVE = unicode ? \"●\" : \">\";\nconst S_RADIO_INACTIVE = unicode ? \"○\" : \" \";\nconst S_CHECKBOX_ACTIVE = unicode ? \"◻\" : \"[•]\";\nconst S_CHECKBOX_SELECTED = unicode ? \"◼\" : \"[+]\";\nconst S_CHECKBOX_INACTIVE = unicode ? \"◻\" : \"[ ]\";\n\nfunction symbol(state: State) {\n  switch (state) {\n    case \"initial\":\n    case \"active\":\n      return pc.cyan(S_STEP_ACTIVE);\n    case \"cancel\":\n      return pc.red(S_STEP_CANCEL);\n    case \"error\":\n      return pc.yellow(S_STEP_ERROR);\n    case \"submit\":\n      return pc.green(S_STEP_SUBMIT);\n  }\n}\n\nconst KEYBOARD_HINT = pc.dim(\n  `${pc.gray(\"↑/↓\")} navigate • ${pc.gray(\"enter\")} confirm • ${pc.gray(\"b\")} back • ${pc.gray(\"ctrl+c\")} cancel`,\n);\n\nconst KEYBOARD_HINT_FIRST = pc.dim(\n  `${pc.gray(\"↑/↓\")} navigate • ${pc.gray(\"enter\")} confirm • ${pc.gray(\"ctrl+c\")} cancel`,\n);\n\nconst KEYBOARD_HINT_MULTI = pc.dim(\n  `${pc.gray(\"↑/↓\")} navigate • ${pc.gray(\"space\")} select • ${pc.gray(\"enter\")} confirm • ${pc.gray(\"b\")} back • ${pc.gray(\"ctrl+c\")} cancel`,\n);\n\nconst KEYBOARD_HINT_MULTI_FIRST = pc.dim(\n  `${pc.gray(\"↑/↓\")} navigate • ${pc.gray(\"space\")} select • ${pc.gray(\"enter\")} confirm • ${pc.gray(\"ctrl+c\")} cancel`,\n);\n\nexport const setIsFirstPrompt = ctxSetIsFirstPrompt;\nexport const setLastPromptShownUI = ctxSetLastPromptShownUI;\nexport const didLastPromptShowUI = ctxDidLastPromptShowUI;\n\nfunction getHint(): string {\n  return ctxIsFirstPrompt() ? KEYBOARD_HINT_FIRST : KEYBOARD_HINT;\n}\n\nfunction getMultiHint(): string {\n  return ctxIsFirstPrompt() ? KEYBOARD_HINT_MULTI_FIRST : KEYBOARD_HINT_MULTI;\n}\n\nfunction normalizeValidationMessage(\n  validationMessage: string | Error | undefined,\n): string | undefined {\n  return validationMessage instanceof Error ? validationMessage.message : validationMessage;\n}\n\nasync function runWithNavigation<T>(prompt: any): Promise<T | symbol> {\n  let goBack = false;\n\n  prompt.on(\"key\", (char: string | undefined) => {\n    if (char === \"b\" && !ctxIsFirstPrompt()) {\n      goBack = true;\n      prompt.state = \"cancel\";\n    }\n  });\n\n  ctxSetLastPromptShownUI(true);\n  const result = await prompt.prompt();\n\n  return goBack ? GO_BACK_SYMBOL : result;\n}\n\ninterface SelectOption<T> {\n  value: T;\n  label?: string;\n  hint?: string;\n  disabled?: boolean;\n}\n\nexport interface NavigableSelectOptions<T> {\n  message: string;\n  options: SelectOption<T>[];\n  initialValue?: T;\n}\n\nexport async function navigableSelect<T>(opts: NavigableSelectOptions<T>): Promise<T | symbol> {\n  const opt = (\n    option: SelectOption<T>,\n    state: \"inactive\" | \"active\" | \"selected\" | \"cancelled\" | \"disabled\",\n  ) => {\n    const label = option.label ?? String(option.value);\n    switch (state) {\n      case \"disabled\":\n        return `${pc.gray(S_RADIO_INACTIVE)} ${pc.gray(label)}${option.hint ? ` ${pc.dim(`(${option.hint ?? \"disabled\"})`)}` : \"\"}`;\n      case \"selected\":\n        return `${pc.dim(label)}`;\n      case \"active\":\n        return `${pc.green(S_RADIO_ACTIVE)} ${label}${option.hint ? ` ${pc.dim(`(${option.hint})`)}` : \"\"}`;\n      case \"cancelled\":\n        return `${pc.strikethrough(pc.dim(label))}`;\n      default:\n        return `${pc.dim(S_RADIO_INACTIVE)} ${pc.dim(label)}`;\n    }\n  };\n\n  const prompt = new SelectPrompt({\n    options: opts.options,\n    initialValue: opts.initialValue,\n    render() {\n      const title = `${pc.gray(S_BAR)}\\n${symbol(this.state)}  ${opts.message}\\n`;\n\n      switch (this.state) {\n        case \"submit\": {\n          return `${title}${pc.gray(S_BAR)}  ${opt(this.options[this.cursor], \"selected\")}`;\n        }\n        case \"cancel\": {\n          return `${title}${pc.gray(S_BAR)}  ${opt(this.options[this.cursor], \"cancelled\")}\\n${pc.gray(S_BAR)}`;\n        }\n        default: {\n          const optionsText = this.options\n            .map((option, i) =>\n              opt(option, option.disabled ? \"disabled\" : i === this.cursor ? \"active\" : \"inactive\"),\n            )\n            .join(`\\n${pc.cyan(S_BAR)}  `);\n          const hint = `\\n${pc.gray(S_BAR)}  ${getHint()}`;\n          return `${title}${pc.cyan(S_BAR)}  ${optionsText}\\n${pc.cyan(S_BAR_END)}${hint}\\n`;\n        }\n      }\n    },\n  });\n\n  return runWithNavigation(prompt) as Promise<T | symbol>;\n}\n\nexport interface NavigableMultiselectOptions<T> {\n  message: string;\n  options: SelectOption<T>[];\n  initialValues?: T[];\n  required?: boolean;\n  validate?: (selected: T[] | undefined) => string | Error | undefined;\n}\n\nexport async function navigableMultiselect<T>(\n  opts: NavigableMultiselectOptions<T>,\n): Promise<T[] | symbol> {\n  const required = opts.required ?? true;\n\n  const opt = (\n    option: SelectOption<T>,\n    state:\n      | \"inactive\"\n      | \"active\"\n      | \"selected\"\n      | \"active-selected\"\n      | \"submitted\"\n      | \"cancelled\"\n      | \"disabled\",\n  ) => {\n    const label = option.label ?? String(option.value);\n    if (state === \"disabled\") {\n      return `${pc.gray(S_CHECKBOX_INACTIVE)} ${pc.strikethrough(pc.gray(label))}${option.hint ? ` ${pc.dim(`(${option.hint ?? \"disabled\"})`)}` : \"\"}`;\n    }\n    if (state === \"active\") {\n      return `${pc.cyan(S_CHECKBOX_ACTIVE)} ${label}${option.hint ? ` ${pc.dim(`(${option.hint})`)}` : \"\"}`;\n    }\n    if (state === \"selected\") {\n      return `${pc.green(S_CHECKBOX_SELECTED)} ${pc.dim(label)}${option.hint ? ` ${pc.dim(`(${option.hint})`)}` : \"\"}`;\n    }\n    if (state === \"cancelled\") {\n      return `${pc.strikethrough(pc.dim(label))}`;\n    }\n    if (state === \"active-selected\") {\n      return `${pc.green(S_CHECKBOX_SELECTED)} ${label}${option.hint ? ` ${pc.dim(`(${option.hint})`)}` : \"\"}`;\n    }\n    if (state === \"submitted\") {\n      return `${pc.dim(label)}`;\n    }\n    return `${pc.dim(S_CHECKBOX_INACTIVE)} ${pc.dim(label)}`;\n  };\n\n  const prompt = new MultiSelectPrompt({\n    options: opts.options,\n    initialValues: opts.initialValues,\n    required,\n    validate(selected: T[] | undefined) {\n      if (required && (selected === undefined || selected.length === 0)) {\n        return `Please select at least one option.\\n${pc.reset(pc.dim(`Press ${pc.gray(pc.bgWhite(pc.inverse(\" space \")))} to select, ${pc.gray(pc.bgWhite(pc.inverse(\" enter \")))} to submit`))}`;\n      }\n      return normalizeValidationMessage(opts.validate?.(selected));\n    },\n    render() {\n      const title = `${pc.gray(S_BAR)}\\n${symbol(this.state)}  ${opts.message}\\n`;\n      const value = this.value ?? [];\n\n      const styleOption = (option: SelectOption<T>, active: boolean) => {\n        if (option.disabled) {\n          return opt(option, \"disabled\");\n        }\n        const selected = value.includes(option.value);\n        if (active && selected) {\n          return opt(option, \"active-selected\");\n        }\n        if (selected) {\n          return opt(option, \"selected\");\n        }\n        return opt(option, active ? \"active\" : \"inactive\");\n      };\n\n      switch (this.state) {\n        case \"submit\": {\n          const submitText =\n            this.options\n              .filter(({ value: optionValue }) => value.includes(optionValue))\n              .map((option) => opt(option, \"submitted\"))\n              .join(pc.dim(\", \")) || pc.dim(\"none\");\n          return `${title}${pc.gray(S_BAR)}  ${submitText}`;\n        }\n        case \"cancel\": {\n          const label = this.options\n            .filter(({ value: optionValue }) => value.includes(optionValue))\n            .map((option) => opt(option, \"cancelled\"))\n            .join(pc.dim(\", \"));\n          return `${title}${pc.gray(S_BAR)}  ${label}\\n${pc.gray(S_BAR)}`;\n        }\n        case \"error\": {\n          const footer = this.error\n            .split(\"\\n\")\n            .map((ln, i) => (i === 0 ? `${pc.yellow(S_BAR_END)}  ${pc.yellow(ln)}` : `   ${ln}`))\n            .join(\"\\n\");\n          const optionsText = this.options\n            .map((option, i) => styleOption(option, i === this.cursor))\n            .join(`\\n${pc.yellow(S_BAR)}  `);\n          return `${title}${pc.yellow(S_BAR)}  ${optionsText}\\n${footer}\\n`;\n        }\n        default: {\n          const optionsText = this.options\n            .map((option, i) => styleOption(option, i === this.cursor))\n            .join(`\\n${pc.cyan(S_BAR)}  `);\n          const hint = `\\n${pc.gray(S_BAR)}  ${getMultiHint()}`;\n          return `${title}${pc.cyan(S_BAR)}  ${optionsText}\\n${pc.cyan(S_BAR_END)}${hint}\\n`;\n        }\n      }\n    },\n  });\n\n  return runWithNavigation(prompt) as Promise<T[] | symbol>;\n}\n\nexport interface NavigableConfirmOptions {\n  message: string;\n  active?: string;\n  inactive?: string;\n  initialValue?: boolean;\n}\n\nexport async function navigableConfirm(opts: NavigableConfirmOptions): Promise<boolean | symbol> {\n  const active = opts.active ?? \"Yes\";\n  const inactive = opts.inactive ?? \"No\";\n\n  const prompt = new ConfirmPrompt({\n    active,\n    inactive,\n    initialValue: opts.initialValue ?? true,\n    render() {\n      const title = `${pc.gray(S_BAR)}\\n${symbol(this.state)}  ${opts.message}\\n`;\n      const value = this.value ? active : inactive;\n\n      switch (this.state) {\n        case \"submit\":\n          return `${title}${pc.gray(S_BAR)}  ${pc.dim(value)}`;\n        case \"cancel\":\n          return `${title}${pc.gray(S_BAR)}  ${pc.strikethrough(pc.dim(value))}\\n${pc.gray(S_BAR)}`;\n        default: {\n          const hint = `\\n${pc.gray(S_BAR)}  ${getHint()}`;\n          return `${title}${pc.cyan(S_BAR)}  ${\n            this.value\n              ? `${pc.green(S_RADIO_ACTIVE)} ${active}`\n              : `${pc.dim(S_RADIO_INACTIVE)} ${pc.dim(active)}`\n          } ${pc.dim(\"/\")} ${\n            !this.value\n              ? `${pc.green(S_RADIO_ACTIVE)} ${inactive}`\n              : `${pc.dim(S_RADIO_INACTIVE)} ${pc.dim(inactive)}`\n          }\\n${pc.cyan(S_BAR_END)}${hint}\\n`;\n        }\n      }\n    },\n  });\n\n  return runWithNavigation(prompt) as Promise<boolean | symbol>;\n}\n\nexport interface NavigableTextOptions {\n  message: string;\n  placeholder?: string;\n  defaultValue?: string;\n  initialValue?: string;\n  validate?: (value: string | undefined) => string | Error | undefined;\n}\n\nexport async function navigableText(opts: NavigableTextOptions): Promise<string | symbol> {\n  const prompt = new TextPrompt({\n    validate: opts.validate,\n    placeholder: opts.placeholder,\n    defaultValue: opts.defaultValue,\n    initialValue: opts.initialValue,\n    render() {\n      const title = `${pc.gray(S_BAR)}\\n${symbol(this.state)}  ${opts.message}\\n`;\n      const placeholder = opts.placeholder\n        ? pc.inverse(opts.placeholder[0]) + pc.dim(opts.placeholder.slice(1))\n        : pc.inverse(pc.hidden(\"_\"));\n      // biome-ignore lint/suspicious/noExplicitAny: TextPrompt has userInput but types don't expose it\n      const self = this as any;\n      const userInput = !self.userInput ? placeholder : self.userInputWithCursor;\n      const value = this.value ?? \"\";\n\n      switch (this.state) {\n        case \"error\": {\n          const errorText = this.error ? `  ${pc.yellow(this.error)}` : \"\";\n          return `${title.trim()}\\n${pc.yellow(S_BAR)}  ${userInput}\\n${pc.yellow(S_BAR_END)}${errorText}\\n`;\n        }\n        case \"submit\": {\n          const valueText = value ? `  ${pc.dim(value)}` : \"\";\n          return `${title}${pc.gray(S_BAR)}${valueText}`;\n        }\n        case \"cancel\": {\n          const valueText = value ? `  ${pc.strikethrough(pc.dim(value))}` : \"\";\n          return `${title}${pc.gray(S_BAR)}${valueText}${value.trim() ? `\\n${pc.gray(S_BAR)}` : \"\"}`;\n        }\n        default: {\n          const hint = `\\n${pc.gray(S_BAR)}  ${getHint()}`;\n          return `${title}${pc.cyan(S_BAR)}  ${userInput}\\n${pc.cyan(S_BAR_END)}${hint}\\n`;\n        }\n      }\n    },\n  });\n\n  return runWithNavigation(prompt) as Promise<string | symbol>;\n}\n\nexport interface GroupMultiSelectOption<T> {\n  value: T;\n  label?: string;\n  hint?: string;\n  disabled?: boolean;\n}\n\nexport interface NavigableGroupMultiselectOptions<T> {\n  message: string;\n  options: Record<string, GroupMultiSelectOption<T>[]>;\n  initialValues?: T[];\n  required?: boolean;\n  validate?: (selected: T[] | undefined) => string | Error | undefined;\n}\n\nexport async function navigableGroupMultiselect<T>(\n  opts: NavigableGroupMultiselectOptions<T>,\n): Promise<T[] | symbol> {\n  const required = opts.required ?? true;\n\n  const opt = (\n    option: GroupMultiSelectOption<T> & { group: string | boolean },\n    state:\n      | \"inactive\"\n      | \"active\"\n      | \"selected\"\n      | \"active-selected\"\n      | \"group-active\"\n      | \"group-active-selected\"\n      | \"submitted\"\n      | \"cancelled\",\n    options: (GroupMultiSelectOption<T> & { group: string | boolean })[] = [],\n  ) => {\n    const label = option.label ?? String(option.value);\n    const isItem = typeof option.group === \"string\";\n    const next = isItem && (options[options.indexOf(option) + 1] ?? { group: true });\n    const isLast = isItem && next && next.group === true;\n    const prefix = isItem ? `${isLast ? S_BAR_END : S_BAR} ` : \"\";\n\n    if (state === \"active\") {\n      return `${pc.dim(prefix)}${pc.cyan(S_CHECKBOX_ACTIVE)} ${label}${option.hint ? ` ${pc.dim(`(${option.hint})`)}` : \"\"}`;\n    }\n    if (state === \"group-active\") {\n      return `${prefix}${pc.cyan(S_CHECKBOX_ACTIVE)} ${pc.dim(label)}`;\n    }\n    if (state === \"group-active-selected\") {\n      return `${prefix}${pc.green(S_CHECKBOX_SELECTED)} ${pc.dim(label)}`;\n    }\n    if (state === \"selected\") {\n      const selectedCheckbox = isItem ? pc.green(S_CHECKBOX_SELECTED) : \"\";\n      return `${pc.dim(prefix)}${selectedCheckbox} ${pc.dim(label)}${option.hint ? ` ${pc.dim(`(${option.hint})`)}` : \"\"}`;\n    }\n    if (state === \"cancelled\") {\n      return `${pc.strikethrough(pc.dim(label))}`;\n    }\n    if (state === \"active-selected\") {\n      return `${pc.dim(prefix)}${pc.green(S_CHECKBOX_SELECTED)} ${label}${option.hint ? ` ${pc.dim(`(${option.hint})`)}` : \"\"}`;\n    }\n    if (state === \"submitted\") {\n      return `${pc.dim(label)}`;\n    }\n    const unselectedCheckbox = isItem ? pc.dim(S_CHECKBOX_INACTIVE) : \"\";\n    return `${pc.dim(prefix)}${unselectedCheckbox} ${pc.dim(label)}`;\n  };\n\n  const prompt = new GroupMultiSelectPrompt<GroupMultiSelectOption<T>>({\n    options: opts.options,\n    initialValues: opts.initialValues,\n    required,\n    selectableGroups: true,\n    validate(selected: T[] | undefined) {\n      if (required && (selected === undefined || selected.length === 0)) {\n        return `Please select at least one option.\\n${pc.reset(pc.dim(`Press ${pc.gray(pc.bgWhite(pc.inverse(\" space \")))} to select, ${pc.gray(pc.bgWhite(pc.inverse(\" enter \")))} to submit`))}`;\n      }\n      return normalizeValidationMessage(opts.validate?.(selected));\n    },\n    render() {\n      const title = `${pc.gray(S_BAR)}\\n${symbol(this.state)}  ${opts.message}\\n`;\n      const value = this.value ?? [];\n\n      switch (this.state) {\n        case \"submit\": {\n          const selectedOptions = this.options\n            .filter(({ value: optionValue }) => value.includes(optionValue))\n            .map((option) => opt(option, \"submitted\"));\n          const optionsText =\n            selectedOptions.length === 0 ? \"\" : `  ${selectedOptions.join(pc.dim(\", \"))}`;\n          return `${title}${pc.gray(S_BAR)}${optionsText}`;\n        }\n        case \"cancel\": {\n          const label = this.options\n            .filter(({ value: optionValue }) => value.includes(optionValue))\n            .map((option) => opt(option, \"cancelled\"))\n            .join(pc.dim(\", \"));\n          return `${title}${pc.gray(S_BAR)}  ${label.trim() ? `${label}\\n${pc.gray(S_BAR)}` : \"\"}`;\n        }\n        case \"error\": {\n          const footer = this.error\n            .split(\"\\n\")\n            .map((ln, i) => (i === 0 ? `${pc.yellow(S_BAR_END)}  ${pc.yellow(ln)}` : `   ${ln}`))\n            .join(\"\\n\");\n          const optionsText = this.options\n            .map((option, i, options) => {\n              const selected =\n                value.includes(option.value) ||\n                (option.group === true && this.isGroupSelected(`${option.value}`));\n              const active = i === this.cursor;\n              const groupActive =\n                !active &&\n                typeof option.group === \"string\" &&\n                this.options[this.cursor].value === option.group;\n              if (groupActive) {\n                return opt(option, selected ? \"group-active-selected\" : \"group-active\", options);\n              }\n              if (active && selected) {\n                return opt(option, \"active-selected\", options);\n              }\n              if (selected) {\n                return opt(option, \"selected\", options);\n              }\n              return opt(option, active ? \"active\" : \"inactive\", options);\n            })\n            .join(`\\n${pc.yellow(S_BAR)}  `);\n          return `${title}${pc.yellow(S_BAR)}  ${optionsText}\\n${footer}\\n`;\n        }\n        default: {\n          const optionsText = this.options\n            .map((option, i, options) => {\n              const selected =\n                value.includes(option.value) ||\n                (option.group === true && this.isGroupSelected(`${option.value}`));\n              const active = i === this.cursor;\n              const groupActive =\n                !active &&\n                typeof option.group === \"string\" &&\n                this.options[this.cursor].value === option.group;\n              let optionText = \"\";\n              if (groupActive) {\n                optionText = opt(\n                  option,\n                  selected ? \"group-active-selected\" : \"group-active\",\n                  options,\n                );\n              } else if (active && selected) {\n                optionText = opt(option, \"active-selected\", options);\n              } else if (selected) {\n                optionText = opt(option, \"selected\", options);\n              } else {\n                optionText = opt(option, active ? \"active\" : \"inactive\", options);\n              }\n              const optPrefix = i !== 0 && !optionText.startsWith(\"\\n\") ? \"  \" : \"\";\n              return `${optPrefix}${optionText}`;\n            })\n            .join(`\\n${pc.cyan(S_BAR)}`);\n          const optionsPrefix = optionsText.startsWith(\"\\n\") ? \"\" : \"  \";\n          const hint = `\\n${pc.gray(S_BAR)}  ${getMultiHint()}`;\n          return `${title}${pc.cyan(S_BAR)}${optionsPrefix}${optionsText}\\n${pc.cyan(S_BAR_END)}${hint}\\n`;\n        }\n      }\n    },\n  });\n\n  return runWithNavigation(prompt) as Promise<T[] | symbol>;\n}\n\nexport { isCancel };\nexport { isGoBack, GO_BACK_SYMBOL } from \"../utils/navigation\";\n"
  },
  {
    "path": "apps/cli/src/prompts/orm.ts",
    "content": "import { DEFAULT_CONFIG } from \"../constants\";\nimport type { Backend, Database, ORM, Runtime } from \"../types\";\nimport { validateOrmDatabaseCompat } from \"../utils/config-validation\";\nimport { UserCancelledError } from \"../utils/errors\";\nimport { isCancel, navigableSelect } from \"./navigable\";\n\nconst ormOptions = {\n  prisma: {\n    value: \"prisma\" as const,\n    label: \"Prisma\",\n    hint: \"Powerful, feature-rich ORM\",\n  },\n  mongoose: {\n    value: \"mongoose\" as const,\n    label: \"Mongoose\",\n    hint: \"Elegant object modeling tool\",\n  },\n  drizzle: {\n    value: \"drizzle\" as const,\n    label: \"Drizzle\",\n    hint: \"Lightweight and performant TypeScript ORM\",\n  },\n};\n\nexport async function getORMChoice(\n  orm: ORM | undefined,\n  hasDatabase: boolean,\n  database?: Database,\n  backend?: Backend,\n  runtime?: Runtime,\n) {\n  if (backend === \"convex\") {\n    return \"none\";\n  }\n\n  if (!hasDatabase) return \"none\";\n  if (orm !== undefined) {\n    const compat = validateOrmDatabaseCompat(orm, database);\n    if (compat.isErr()) throw compat.error;\n    return orm;\n  }\n\n  const options =\n    database === \"mongodb\"\n      ? [ormOptions.prisma, ormOptions.mongoose]\n      : [ormOptions.drizzle, ormOptions.prisma];\n\n  const response = await navigableSelect<ORM>({\n    message: \"Select ORM\",\n    options,\n    initialValue:\n      database === \"mongodb\" ? \"prisma\" : runtime === \"workers\" ? \"drizzle\" : DEFAULT_CONFIG.orm,\n  });\n\n  if (isCancel(response)) throw new UserCancelledError({ message: \"Operation cancelled\" });\n\n  return response;\n}\n"
  },
  {
    "path": "apps/cli/src/prompts/package-manager.ts",
    "content": "import type { PackageManager } from \"../types\";\nimport { UserCancelledError } from \"../utils/errors\";\nimport { getUserPkgManager } from \"../utils/get-package-manager\";\nimport { isCancel, navigableSelect } from \"./navigable\";\n\nexport async function getPackageManagerChoice(packageManager?: PackageManager) {\n  if (packageManager !== undefined) return packageManager;\n\n  const detectedPackageManager = getUserPkgManager();\n\n  const response = await navigableSelect<PackageManager>({\n    message: \"Choose package manager\",\n    options: [\n      { value: \"npm\", label: \"npm\", hint: \"not recommended\" },\n      {\n        value: \"pnpm\",\n        label: \"pnpm\",\n        hint: \"Fast, disk space efficient package manager\",\n      },\n      {\n        value: \"bun\",\n        label: \"bun\",\n        hint: \"All-in-one JavaScript runtime & toolkit\",\n      },\n    ],\n    initialValue: detectedPackageManager,\n  });\n\n  if (isCancel(response)) throw new UserCancelledError({ message: \"Operation cancelled\" });\n\n  return response;\n}\n"
  },
  {
    "path": "apps/cli/src/prompts/payments.ts",
    "content": "import { DEFAULT_CONFIG } from \"../constants\";\nimport type { Auth, Backend, Frontend, Payments } from \"../types\";\nimport { splitFrontends } from \"../utils/compatibility-rules\";\nimport { UserCancelledError } from \"../utils/errors\";\nimport { isCancel, navigableSelect } from \"./navigable\";\n\nexport async function getPaymentsChoice(\n  payments?: Payments,\n  auth?: Auth,\n  backend?: Backend,\n  frontends?: Frontend[],\n) {\n  if (payments !== undefined) return payments;\n\n  if (backend === \"none\") {\n    return \"none\" as Payments;\n  }\n\n  const isPolarCompatible =\n    auth === \"better-auth\" &&\n    backend !== \"convex\" &&\n    (frontends?.length === 0 || splitFrontends(frontends).web.length > 0);\n\n  if (!isPolarCompatible) {\n    return \"none\" as Payments;\n  }\n\n  const options = [\n    {\n      value: \"polar\" as Payments,\n      label: \"Polar\",\n      hint: \"Turn your software into a business. 6 lines of code.\",\n    },\n    {\n      value: \"none\" as Payments,\n      label: \"None\",\n      hint: \"No payments integration\",\n    },\n  ];\n\n  const response = await navigableSelect<Payments>({\n    message: \"Select payments provider\",\n    options,\n    initialValue: DEFAULT_CONFIG.payments,\n  });\n\n  if (isCancel(response)) throw new UserCancelledError({ message: \"Operation cancelled\" });\n\n  return response;\n}\n"
  },
  {
    "path": "apps/cli/src/prompts/project-name.ts",
    "content": "import path from \"node:path\";\n\nimport { isCancel, text } from \"@clack/prompts\";\nimport fs from \"fs-extra\";\nimport pc from \"picocolors\";\n\nimport { DEFAULT_CONFIG } from \"../constants\";\nimport { ProjectNameSchema } from \"../types\";\nimport { UserCancelledError } from \"../utils/errors\";\nimport { cliConsola } from \"../utils/terminal-output\";\n\nfunction isPathWithinCwd(targetPath: string) {\n  const resolved = path.resolve(targetPath);\n  const rel = path.relative(process.cwd(), resolved);\n  return !rel.startsWith(\"..\") && !path.isAbsolute(rel);\n}\n\nfunction validateDirectoryName(name: string) {\n  if (name === \".\") return undefined;\n\n  const result = ProjectNameSchema.safeParse(name);\n  if (!result.success) {\n    return result.error.issues[0]?.message || \"Invalid project name\";\n  }\n  return undefined;\n}\n\nexport async function getProjectName(initialName?: string): Promise<string> {\n  if (initialName) {\n    if (initialName === \".\") {\n      return initialName;\n    }\n    const finalDirName = path.basename(initialName);\n    const validationError = validateDirectoryName(finalDirName);\n    if (!validationError) {\n      const projectDir = path.resolve(process.cwd(), initialName);\n      if (isPathWithinCwd(projectDir)) {\n        return initialName;\n      }\n      cliConsola.error(pc.red(\"Project path must be within current directory\"));\n    }\n  }\n\n  let isValid = false;\n  let projectPath = \"\";\n  let defaultName: string = DEFAULT_CONFIG.projectName;\n  let counter = 1;\n\n  while (\n    (await fs.pathExists(path.resolve(process.cwd(), defaultName))) &&\n    (await fs.readdir(path.resolve(process.cwd(), defaultName))).length > 0\n  ) {\n    defaultName = `${DEFAULT_CONFIG.projectName}-${counter}`;\n    counter++;\n  }\n\n  while (!isValid) {\n    const response = await text({\n      message: \"Enter your project name or path (relative to current directory)\",\n      placeholder: defaultName,\n      initialValue: initialName,\n      defaultValue: defaultName,\n      validate: (value) => {\n        const nameToUse = String(value ?? \"\").trim() || defaultName;\n\n        const finalDirName = path.basename(nameToUse);\n        const validationError = validateDirectoryName(finalDirName);\n        if (validationError) return validationError;\n\n        if (nameToUse !== \".\") {\n          const projectDir = path.resolve(process.cwd(), nameToUse);\n          if (!isPathWithinCwd(projectDir)) {\n            return \"Project path must be within current directory\";\n          }\n        }\n\n        return undefined;\n      },\n    });\n\n    if (isCancel(response)) {\n      throw new UserCancelledError({ message: \"Operation cancelled.\" });\n    }\n\n    projectPath = response || defaultName;\n    isValid = true;\n  }\n\n  return projectPath;\n}\n"
  },
  {
    "path": "apps/cli/src/prompts/runtime.ts",
    "content": "import { DEFAULT_CONFIG } from \"../constants\";\nimport type { Backend, Runtime } from \"../types\";\nimport { UserCancelledError } from \"../utils/errors\";\nimport { isCancel, navigableSelect } from \"./navigable\";\n\nexport async function getRuntimeChoice(runtime?: Runtime, backend?: Backend) {\n  if (backend === \"convex\" || backend === \"none\" || backend === \"self\") {\n    return \"none\";\n  }\n\n  if (runtime !== undefined) return runtime;\n\n  const runtimeOptions: Array<{\n    value: Runtime;\n    label: string;\n    hint: string;\n  }> = [\n    {\n      value: \"bun\",\n      label: \"Bun\",\n      hint: \"Fast all-in-one JavaScript runtime\",\n    },\n    {\n      value: \"node\",\n      label: \"Node.js\",\n      hint: \"Traditional Node.js runtime\",\n    },\n  ];\n\n  if (backend === \"hono\") {\n    runtimeOptions.push({\n      value: \"workers\",\n      label: \"Cloudflare Workers\",\n      hint: \"Edge runtime on Cloudflare's global network\",\n    });\n  }\n\n  const response = await navigableSelect<Runtime>({\n    message: \"Select runtime\",\n    options: runtimeOptions,\n    initialValue: DEFAULT_CONFIG.runtime,\n  });\n\n  if (isCancel(response)) throw new UserCancelledError({ message: \"Operation cancelled\" });\n\n  return response;\n}\n"
  },
  {
    "path": "apps/cli/src/prompts/server-deploy.ts",
    "content": "import { DEFAULT_CONFIG } from \"../constants\";\nimport type { Backend, Runtime, ServerDeploy, WebDeploy } from \"../types\";\nimport { UserCancelledError } from \"../utils/errors\";\nimport { isCancel, navigableSelect } from \"./navigable\";\n\ntype DeploymentOption = {\n  value: ServerDeploy;\n  label: string;\n  hint: string;\n};\n\nfunction getDeploymentDisplay(deployment: ServerDeploy): {\n  label: string;\n  hint: string;\n} {\n  if (deployment === \"cloudflare\") {\n    return {\n      label: \"Cloudflare\",\n      hint: \"Deploy to Cloudflare Workers using Alchemy\",\n    };\n  }\n  return {\n    label: deployment,\n    hint: `Add ${deployment} deployment`,\n  };\n}\n\nexport async function getServerDeploymentChoice(\n  deployment?: ServerDeploy,\n  runtime?: Runtime,\n  backend?: Backend,\n  _webDeploy?: WebDeploy,\n) {\n  if (deployment !== undefined) return deployment;\n\n  if (backend === \"none\" || backend === \"convex\") {\n    return \"none\";\n  }\n\n  if (backend !== \"hono\") {\n    return \"none\";\n  }\n\n  // Auto-select cloudflare for workers runtime since it's the only valid option\n  if (runtime === \"workers\") {\n    return \"cloudflare\";\n  }\n\n  return \"none\";\n}\n\nexport async function getServerDeploymentToAdd(\n  runtime?: Runtime,\n  existingDeployment?: ServerDeploy,\n  backend?: Backend,\n) {\n  if (backend !== \"hono\") {\n    return \"none\";\n  }\n\n  const options: DeploymentOption[] = [];\n\n  if (runtime === \"workers\") {\n    if (existingDeployment !== \"cloudflare\") {\n      const { label, hint } = getDeploymentDisplay(\"cloudflare\");\n      options.push({\n        value: \"cloudflare\",\n        label,\n        hint,\n      });\n    }\n  }\n\n  if (existingDeployment && existingDeployment !== \"none\") {\n    return \"none\";\n  }\n\n  if (options.length === 0) {\n    return \"none\";\n  }\n\n  const response = await navigableSelect<ServerDeploy>({\n    message: \"Select server deployment\",\n    options,\n    initialValue: DEFAULT_CONFIG.serverDeploy,\n  });\n\n  if (isCancel(response)) throw new UserCancelledError({ message: \"Operation cancelled\" });\n\n  return response;\n}\n"
  },
  {
    "path": "apps/cli/src/prompts/web-deploy.ts",
    "content": "import { DEFAULT_CONFIG } from \"../constants\";\nimport type { Backend, DatabaseSetup, Frontend, Runtime, WebDeploy } from \"../types\";\nimport { WEB_FRAMEWORKS } from \"../utils/compatibility\";\nimport { UserCancelledError } from \"../utils/errors\";\nimport { isCancel, navigableSelect } from \"./navigable\";\n\nfunction hasWebFrontend(frontends: Frontend[]) {\n  return frontends.some((f) => WEB_FRAMEWORKS.includes(f));\n}\n\ntype DeploymentOption = {\n  value: WebDeploy;\n  label: string;\n  hint: string;\n};\n\nfunction getDeploymentDisplay(deployment: WebDeploy): {\n  label: string;\n  hint: string;\n} {\n  if (deployment === \"cloudflare\") {\n    return {\n      label: \"Cloudflare\",\n      hint: \"Deploy to Cloudflare Workers using Alchemy\",\n    };\n  }\n  return {\n    label: deployment,\n    hint: `Add ${deployment} deployment`,\n  };\n}\n\nexport async function getDeploymentChoice(\n  deployment?: WebDeploy,\n  _runtime?: Runtime,\n  backend?: Backend,\n  frontend: Frontend[] = [],\n  dbSetup?: DatabaseSetup,\n) {\n  if (deployment !== undefined) return deployment;\n  if (!hasWebFrontend(frontend)) {\n    return \"none\";\n  }\n\n  if (backend === \"self\" && dbSetup === \"d1\") {\n    return \"cloudflare\";\n  }\n\n  const availableDeployments = [\"cloudflare\", \"none\"];\n\n  const options: DeploymentOption[] = availableDeployments.map((deploy) => {\n    const { label, hint } = getDeploymentDisplay(deploy as WebDeploy);\n    return {\n      value: deploy as WebDeploy,\n      label,\n      hint,\n    };\n  });\n\n  const response = await navigableSelect<WebDeploy>({\n    message: \"Select web deployment\",\n    options,\n    initialValue: DEFAULT_CONFIG.webDeploy,\n  });\n\n  if (isCancel(response)) throw new UserCancelledError({ message: \"Operation cancelled\" });\n\n  return response;\n}\n\nexport async function getDeploymentToAdd(frontend: Frontend[], existingDeployment?: WebDeploy) {\n  if (!hasWebFrontend(frontend)) {\n    return \"none\";\n  }\n\n  const options: DeploymentOption[] = [];\n\n  if (existingDeployment !== \"cloudflare\") {\n    const { label, hint } = getDeploymentDisplay(\"cloudflare\");\n    options.push({\n      value: \"cloudflare\",\n      label,\n      hint,\n    });\n  }\n\n  if (existingDeployment && existingDeployment !== \"none\") {\n    return \"none\";\n  }\n\n  if (options.length > 0) {\n    options.push({\n      value: \"none\",\n      label: \"None\",\n      hint: \"Skip deployment setup\",\n    });\n  }\n\n  if (options.length === 0) {\n    return \"none\";\n  }\n\n  const response = await navigableSelect<WebDeploy>({\n    message: \"Select web deployment\",\n    options,\n    initialValue: DEFAULT_CONFIG.webDeploy,\n  });\n\n  if (isCancel(response)) throw new UserCancelledError({ message: \"Operation cancelled\" });\n\n  return response;\n}\n"
  },
  {
    "path": "apps/cli/src/types.ts",
    "content": "export * from \"@better-t-stack/types\";\n"
  },
  {
    "path": "apps/cli/src/utils/add-package-deps.ts",
    "content": "import path from \"node:path\";\n\nimport fs from \"fs-extra\";\n\nimport { type AvailableDependencies, dependencyVersionMap } from \"../constants\";\n\nexport const addPackageDependency = async (opts: {\n  dependencies?: AvailableDependencies[];\n  devDependencies?: AvailableDependencies[];\n  customDependencies?: Record<string, string>;\n  customDevDependencies?: Record<string, string>;\n  projectDir: string;\n}) => {\n  const {\n    dependencies = [],\n    devDependencies = [],\n    customDependencies = {},\n    customDevDependencies = {},\n    projectDir,\n  } = opts;\n\n  const pkgJsonPath = path.join(projectDir, \"package.json\");\n\n  const pkgJson = await fs.readJson(pkgJsonPath);\n\n  if (!pkgJson.dependencies) pkgJson.dependencies = {};\n  if (!pkgJson.devDependencies) pkgJson.devDependencies = {};\n\n  for (const pkgName of dependencies) {\n    const version = dependencyVersionMap[pkgName];\n    if (version) {\n      pkgJson.dependencies[pkgName] = version;\n    } else {\n      console.warn(`Warning: Dependency ${pkgName} not found in version map.`);\n    }\n  }\n\n  for (const pkgName of devDependencies) {\n    const version = dependencyVersionMap[pkgName];\n    if (version) {\n      pkgJson.devDependencies[pkgName] = version;\n    } else {\n      console.warn(`Warning: Dev dependency ${pkgName} not found in version map.`);\n    }\n  }\n\n  for (const [pkgName, version] of Object.entries(customDependencies)) {\n    pkgJson.dependencies[pkgName] = version;\n  }\n\n  for (const [pkgName, version] of Object.entries(customDevDependencies)) {\n    pkgJson.devDependencies[pkgName] = version;\n  }\n\n  await fs.writeJson(pkgJsonPath, pkgJson, {\n    spaces: 2,\n  });\n};\n"
  },
  {
    "path": "apps/cli/src/utils/analytics.ts",
    "content": "import { Result } from \"better-result\";\n\nimport type { ProjectConfig } from \"../types\";\nimport { getLatestCLIVersion } from \"./get-latest-cli-version\";\nimport { isTelemetryEnabled } from \"./telemetry\";\n\nconst CONVEX_INGEST_URL = process.env.CONVEX_INGEST_URL;\n\nasync function sendConvexEvent(payload: Record<string, unknown>): Promise<void> {\n  if (!CONVEX_INGEST_URL) return;\n\n  await Result.tryPromise({\n    try: () =>\n      fetch(CONVEX_INGEST_URL, {\n        method: \"POST\",\n        headers: {\n          \"Content-Type\": \"application/json\",\n        },\n        body: JSON.stringify(payload),\n      }),\n    catch: () => undefined, // Silent failure\n  });\n}\n\nexport async function trackProjectCreation(\n  config: ProjectConfig,\n  disableAnalytics = false,\n): Promise<void> {\n  if (!isTelemetryEnabled() || disableAnalytics) return;\n\n  const {\n    projectName: _projectName,\n    projectDir: _projectDir,\n    relativePath: _relativePath,\n    ...safeConfig\n  } = config;\n\n  await Result.tryPromise({\n    try: () =>\n      sendConvexEvent({\n        ...safeConfig,\n        cli_version: getLatestCLIVersion(),\n        node_version: typeof process !== \"undefined\" ? process.version : \"\",\n        platform: typeof process !== \"undefined\" ? process.platform : \"\",\n      }),\n    catch: () => undefined, // Silent failure\n  });\n}\n"
  },
  {
    "path": "apps/cli/src/utils/bts-config.ts",
    "content": "import path from \"node:path\";\n\nimport type { BetterTStackConfig } from \"@better-t-stack/types\";\nimport fs from \"fs-extra\";\nimport { applyEdits, modify, parse } from \"jsonc-parser\";\n\nconst BTS_CONFIG_FILE = \"bts.jsonc\";\n\n/**\n * Reads the BTS configuration file from the project directory.\n */\nexport async function readBtsConfig(projectDir: string): Promise<BetterTStackConfig | null> {\n  try {\n    const configPath = path.join(projectDir, BTS_CONFIG_FILE);\n\n    if (!(await fs.pathExists(configPath))) {\n      return null;\n    }\n\n    const configContent = await fs.readFile(configPath, \"utf-8\");\n    const config = parse(configContent) as BetterTStackConfig;\n    return config;\n  } catch {\n    return null;\n  }\n}\n\n/**\n * Updates specific fields in the BTS configuration file.\n */\nexport async function updateBtsConfig(\n  projectDir: string,\n  updates: Partial<\n    Pick<\n      BetterTStackConfig,\n      \"addons\" | \"addonOptions\" | \"dbSetupOptions\" | \"webDeploy\" | \"serverDeploy\"\n    >\n  >,\n): Promise<void> {\n  try {\n    const configPath = path.join(projectDir, BTS_CONFIG_FILE);\n\n    if (!(await fs.pathExists(configPath))) {\n      return;\n    }\n\n    let content = await fs.readFile(configPath, \"utf-8\");\n\n    // Apply each update using jsonc-parser's modify (preserves comments)\n    for (const [key, value] of Object.entries(updates)) {\n      const edits = modify(content, [key], value, { formattingOptions: { tabSize: 2 } });\n      content = applyEdits(content, edits);\n    }\n\n    await fs.writeFile(configPath, content, \"utf-8\");\n  } catch {\n    // Silent failure\n  }\n}\n"
  },
  {
    "path": "apps/cli/src/utils/command-exists.ts",
    "content": "import { Result } from \"better-result\";\nimport { $ } from \"execa\";\n\nexport async function commandExists(command: string): Promise<boolean> {\n  const result = await Result.tryPromise({\n    try: async () => {\n      const isWindows = process.platform === \"win32\";\n      if (isWindows) {\n        const execResult = await $({ reject: false })`where ${command}`;\n        return execResult.exitCode === 0;\n      }\n\n      const execResult = await $({ reject: false })`which ${command}`;\n      return execResult.exitCode === 0;\n    },\n    catch: () => false,\n  });\n\n  return result.isOk() ? result.value : false;\n}\n"
  },
  {
    "path": "apps/cli/src/utils/compatibility-rules.ts",
    "content": "import { Result } from \"better-result\";\n\nimport { ADDON_COMPATIBILITY } from \"../constants\";\nimport type {\n  Addons,\n  API,\n  Auth,\n  Backend,\n  CLIInput,\n  Frontend,\n  Payments,\n  ProjectConfig,\n  Runtime,\n  ServerDeploy,\n  WebDeploy,\n} from \"../types\";\nimport { WEB_FRAMEWORKS } from \"./compatibility\";\nimport { ValidationError } from \"./errors\";\n\ntype ValidationResult = Result<void, ValidationError>;\ntype AddonCompatibilityConfig = Pick<ProjectConfig, \"frontend\" | \"auth\" | \"backend\" | \"runtime\">;\n\nexport const CONVEX_BETTER_AUTH_INCOMPATIBLE_FRONTENDS = [\n  \"nuxt\",\n  \"svelte\",\n  \"solid\",\n  \"astro\",\n] as const;\n\nexport const CONVEX_BETTER_AUTH_SUPPORTED_FRONTENDS = [\n  \"tanstack-router\",\n  \"react-router\",\n  \"tanstack-start\",\n  \"next\",\n  \"native-bare\",\n  \"native-uniwind\",\n  \"native-unistyles\",\n] as const;\n\nfunction validationErr(message: string): ValidationResult {\n  return Result.err(new ValidationError({ message }));\n}\n\nexport function isWebFrontend(value: Frontend) {\n  return WEB_FRAMEWORKS.includes(value);\n}\n\nexport function splitFrontends(values: Frontend[] = []): {\n  web: Frontend[];\n  native: Frontend[];\n} {\n  const web = values.filter((f) => isWebFrontend(f));\n  const native = values.filter(\n    (f) => f === \"native-bare\" || f === \"native-uniwind\" || f === \"native-unistyles\",\n  );\n  return { web, native };\n}\n\nexport function ensureSingleWebAndNative(frontends: Frontend[]): ValidationResult {\n  const { web, native } = splitFrontends(frontends);\n  if (web.length > 1) {\n    return validationErr(\n      \"Cannot select multiple web frameworks. Choose only one of: tanstack-router, tanstack-start, react-router, next, nuxt, svelte, solid, astro\",\n    );\n  }\n  if (native.length > 1) {\n    return validationErr(\n      \"Cannot select multiple native frameworks. Choose only one of: native-bare, native-uniwind, native-unistyles\",\n    );\n  }\n  return Result.ok(undefined);\n}\n\n// Frontends that support backend=\"self\" (fullstack mode with built-in server routes)\nconst FULLSTACK_FRONTENDS: readonly Frontend[] = [\n  \"next\",\n  \"tanstack-start\",\n  \"nuxt\",\n  \"svelte\",\n  \"astro\",\n] as const;\n\nconst EVLOG_SERVER_BACKENDS: readonly Backend[] = [\"hono\", \"express\", \"fastify\", \"elysia\"];\nconst EVLOG_FULLSTACK_FRONTENDS: readonly Frontend[] = FULLSTACK_FRONTENDS;\n\nconst evlogCompatibilityMessage =\n  \"evlog addon supports Hono, Express, Fastify, Elysia, or backend self with Next.js, TanStack Start, Nuxt, SvelteKit, or Astro. Convex and backend none are not supported yet.\";\n\nexport function supportsEvlogAddon(\n  frontend: Frontend[] = [],\n  backend?: Backend,\n  _runtime?: Runtime,\n) {\n  if (!backend) return true;\n\n  if (EVLOG_SERVER_BACKENDS.includes(backend)) {\n    return true;\n  }\n\n  if (backend === \"self\") {\n    if (frontend.length === 0) return true;\n    return frontend.some((f) => EVLOG_FULLSTACK_FRONTENDS.includes(f));\n  }\n\n  return false;\n}\n\nexport function validateSelfBackendCompatibility(\n  providedFlags: Set<string>,\n  options: CLIInput,\n  config: Partial<ProjectConfig>,\n): ValidationResult {\n  const backend = config.backend || options.backend;\n  const frontends = config.frontend || options.frontend || [];\n\n  if (backend === \"self\") {\n    const { web, native } = splitFrontends(frontends);\n    const hasSupportedWeb = web.length === 1 && FULLSTACK_FRONTENDS.includes(web[0]);\n\n    if (!hasSupportedWeb) {\n      return validationErr(\n        \"Backend 'self' (fullstack) currently only supports Next.js, TanStack Start, Nuxt, SvelteKit, and Astro frontends. Please use --frontend next, --frontend tanstack-start, --frontend nuxt, --frontend svelte, or --frontend astro.\",\n      );\n    }\n\n    if (native.length > 1) {\n      return validationErr(\n        \"Cannot select multiple native frameworks. Choose only one of: native-bare, native-uniwind, native-unistyles\",\n      );\n    }\n  }\n\n  const hasFullstackFrontend = frontends.some((f) => FULLSTACK_FRONTENDS.includes(f));\n  if (providedFlags.has(\"backend\") && !hasFullstackFrontend && backend === \"self\") {\n    return validationErr(\n      \"Backend 'self' (fullstack) currently only supports Next.js, TanStack Start, Nuxt, SvelteKit, and Astro frontends. Please use --frontend next, --frontend tanstack-start, --frontend nuxt, --frontend svelte, --frontend astro, or choose a different backend.\",\n    );\n  }\n\n  return Result.ok(undefined);\n}\n\nexport function validateWorkersCompatibility(\n  providedFlags: Set<string>,\n  options: CLIInput,\n  config: Partial<ProjectConfig>,\n): ValidationResult {\n  if (\n    providedFlags.has(\"runtime\") &&\n    options.runtime === \"workers\" &&\n    config.backend &&\n    config.backend !== \"hono\"\n  ) {\n    return validationErr(\n      `Cloudflare Workers runtime (--runtime workers) is only supported with Hono backend (--backend hono). Current backend: ${config.backend}. Please use '--backend hono' or choose a different runtime.`,\n    );\n  }\n\n  if (\n    providedFlags.has(\"backend\") &&\n    config.backend &&\n    config.backend !== \"hono\" &&\n    config.runtime === \"workers\"\n  ) {\n    return validationErr(\n      `Backend '${config.backend}' is not compatible with Cloudflare Workers runtime. Cloudflare Workers runtime is only supported with Hono backend. Please use '--backend hono' or choose a different runtime.`,\n    );\n  }\n\n  if (\n    providedFlags.has(\"runtime\") &&\n    options.runtime === \"workers\" &&\n    config.database === \"mongodb\"\n  ) {\n    return validationErr(\n      \"Cloudflare Workers runtime (--runtime workers) is not compatible with MongoDB database. MongoDB requires Prisma or Mongoose ORM, but Workers runtime only supports Drizzle or Prisma ORM. Please use a different database or runtime.\",\n    );\n  }\n\n  if (\n    providedFlags.has(\"database\") &&\n    config.database === \"mongodb\" &&\n    config.runtime === \"workers\"\n  ) {\n    return validationErr(\n      \"MongoDB database is not compatible with Cloudflare Workers runtime. MongoDB requires Prisma or Mongoose ORM, but Workers runtime only supports Drizzle or Prisma ORM. Please use a different database or runtime.\",\n    );\n  }\n\n  return Result.ok(undefined);\n}\n\nexport function validateApiFrontendCompatibility(\n  api: API | undefined,\n  frontends: Frontend[] = [],\n): ValidationResult {\n  const includesNuxt = frontends.includes(\"nuxt\");\n  const includesSvelte = frontends.includes(\"svelte\");\n  const includesSolid = frontends.includes(\"solid\");\n  const includesAstro = frontends.includes(\"astro\");\n  if ((includesNuxt || includesSvelte || includesSolid || includesAstro) && api === \"trpc\") {\n    return validationErr(\n      `tRPC API is not supported with '${includesNuxt ? \"nuxt\" : includesSvelte ? \"svelte\" : includesSolid ? \"solid\" : \"astro\"}' frontend. Please use --api orpc or --api none or remove '${includesNuxt ? \"nuxt\" : includesSvelte ? \"svelte\" : includesSolid ? \"solid\" : \"astro\"}' from --frontend.`,\n    );\n  }\n  return Result.ok(undefined);\n}\n\nexport function isFrontendAllowedWithBackend(\n  frontend: Frontend,\n  backend?: ProjectConfig[\"backend\"],\n  auth?: string,\n) {\n  if (backend === \"convex\") {\n    if (\n      auth === \"better-auth\" &&\n      CONVEX_BETTER_AUTH_INCOMPATIBLE_FRONTENDS.includes(\n        frontend as (typeof CONVEX_BETTER_AUTH_INCOMPATIBLE_FRONTENDS)[number],\n      )\n    ) {\n      return false;\n    }\n\n    if (frontend === \"solid\" || frontend === \"astro\") return false;\n  }\n\n  if (auth === \"clerk\") {\n    const incompatibleFrontends = [\"nuxt\", \"svelte\", \"solid\", \"astro\"];\n    if (incompatibleFrontends.includes(frontend)) return false;\n  }\n\n  return true;\n}\n\nexport function supportsConvexBetterAuth(frontends: readonly Frontend[] = []) {\n  return frontends.some((frontend) =>\n    CONVEX_BETTER_AUTH_SUPPORTED_FRONTENDS.includes(\n      frontend as (typeof CONVEX_BETTER_AUTH_SUPPORTED_FRONTENDS)[number],\n    ),\n  );\n}\n\nexport function allowedApisForFrontends(frontends: Frontend[] = []) {\n  const includesNuxt = frontends.includes(\"nuxt\");\n  const includesSvelte = frontends.includes(\"svelte\");\n  const includesSolid = frontends.includes(\"solid\");\n  const includesAstro = frontends.includes(\"astro\");\n  const base: API[] = [\"trpc\", \"orpc\", \"none\"];\n  if (includesNuxt || includesSvelte || includesSolid || includesAstro) {\n    return [\"orpc\", \"none\"];\n  }\n  return base;\n}\n\nexport function isExampleTodoAllowed(\n  backend?: ProjectConfig[\"backend\"],\n  database?: ProjectConfig[\"database\"],\n  api?: API,\n) {\n  // Convex handles its own data layer, no need for database or API\n  if (backend === \"convex\") return true;\n  // Todo requires both database and API to communicate\n  if (database === \"none\" || api === \"none\") return false;\n  return true;\n}\n\nexport function isExampleAIAllowed(backend?: ProjectConfig[\"backend\"], frontends: Frontend[] = []) {\n  const includesSolid = frontends.includes(\"solid\");\n  const includesAstro = frontends.includes(\"astro\");\n  if (includesSolid || includesAstro) return false;\n\n  // Convex AI example only supports React-based frontends (not Svelte or Nuxt)\n  if (backend === \"convex\") {\n    const includesNuxt = frontends.includes(\"nuxt\");\n    const includesSvelte = frontends.includes(\"svelte\");\n    if (includesNuxt || includesSvelte) return false;\n  }\n\n  return true;\n}\n\nexport function validateWebDeployRequiresWebFrontend(\n  webDeploy: WebDeploy | undefined,\n  hasWebFrontendFlag: boolean,\n): ValidationResult {\n  if (webDeploy && webDeploy !== \"none\" && !hasWebFrontendFlag) {\n    return validationErr(\n      \"'--web-deploy' requires a web frontend. Please select a web frontend or set '--web-deploy none'.\",\n    );\n  }\n  return Result.ok(undefined);\n}\n\nexport function validateServerDeployRequiresBackend(\n  serverDeploy: ServerDeploy | undefined,\n  backend: Backend | undefined,\n): ValidationResult {\n  if (serverDeploy && serverDeploy !== \"none\" && (!backend || backend === \"none\")) {\n    return validationErr(\n      \"'--server-deploy' requires a backend. Please select a backend or set '--server-deploy none'.\",\n    );\n  }\n  return Result.ok(undefined);\n}\n\nexport function validateAddonCompatibility(\n  addon: Addons,\n  frontend: Frontend[],\n  _auth?: Auth,\n  backend?: Backend,\n  runtime?: Runtime,\n): { isCompatible: boolean; reason?: string } {\n  if (addon === \"evlog\" && !supportsEvlogAddon(frontend, backend, runtime)) {\n    return {\n      isCompatible: false,\n      reason: evlogCompatibilityMessage,\n    };\n  }\n\n  const compatibleFrontends = ADDON_COMPATIBILITY[addon];\n\n  if (compatibleFrontends.length > 0) {\n    const hasCompatibleFrontend = frontend.some((f) =>\n      (compatibleFrontends as readonly string[]).includes(f),\n    );\n\n    if (!hasCompatibleFrontend) {\n      const frontendList = compatibleFrontends.join(\", \");\n      return {\n        isCompatible: false,\n        reason: `${addon} addon requires one of these frontends: ${frontendList}`,\n      };\n    }\n  }\n\n  return { isCompatible: true };\n}\n\nexport function getCompatibleAddons(\n  allAddons: Addons[],\n  frontend: Frontend[],\n  existingAddons: Addons[] = [],\n  auth?: Auth,\n  backend?: Backend,\n  runtime?: Runtime,\n) {\n  return allAddons.filter((addon) => {\n    if (existingAddons.includes(addon)) return false;\n\n    if (addon === \"none\") return false;\n\n    const { isCompatible } = validateAddonCompatibility(addon, frontend, auth, backend, runtime);\n    return isCompatible;\n  });\n}\n\nexport function validateAddonsAgainstFrontends(\n  addons: Addons[] = [],\n  frontends: Frontend[] = [],\n  auth?: Auth,\n  backend?: Backend,\n  runtime?: Runtime,\n): ValidationResult {\n  if (addons.includes(\"turborepo\") && addons.includes(\"nx\")) {\n    return validationErr(\"Cannot combine 'turborepo' and 'nx' addons. Choose one monorepo tool.\");\n  }\n\n  for (const addon of addons) {\n    if (addon === \"none\") continue;\n    const { isCompatible, reason } = validateAddonCompatibility(\n      addon,\n      frontends,\n      auth,\n      backend,\n      runtime,\n    );\n    if (!isCompatible) {\n      return validationErr(`Incompatible addon/frontend combination: ${reason}`);\n    }\n  }\n  return Result.ok(undefined);\n}\n\nexport function validateAddonsAgainstConfig(\n  addons: Addons[] = [],\n  config: Partial<AddonCompatibilityConfig>,\n): ValidationResult {\n  return validateAddonsAgainstFrontends(\n    addons,\n    config.frontend ?? [],\n    config.auth,\n    config.backend,\n    config.runtime,\n  );\n}\n\nexport function validatePaymentsCompatibility(\n  payments: Payments | undefined,\n  auth: Auth | undefined,\n  _backend: Backend | undefined,\n  frontends: Frontend[] = [],\n): ValidationResult {\n  if (!payments || payments === \"none\") return Result.ok(undefined);\n\n  if (payments === \"polar\") {\n    if (!auth || auth === \"none\" || auth !== \"better-auth\") {\n      return validationErr(\n        \"Polar payments requires Better Auth. Please use '--auth better-auth' or choose a different payments provider.\",\n      );\n    }\n\n    const { web } = splitFrontends(frontends);\n    if (web.length === 0 && frontends.length > 0) {\n      return validationErr(\n        \"Polar payments requires a web frontend or no frontend. Please select a web frontend or choose a different payments provider.\",\n      );\n    }\n  }\n\n  return Result.ok(undefined);\n}\n\nexport function validateExamplesCompatibility(\n  examples: string[] | undefined,\n  backend: ProjectConfig[\"backend\"] | undefined,\n  database: ProjectConfig[\"database\"] | undefined,\n  frontend?: Frontend[],\n  api?: API,\n): ValidationResult {\n  const examplesArr = examples ?? [];\n  if (examplesArr.length === 0 || examplesArr.includes(\"none\")) return Result.ok(undefined);\n\n  if (examplesArr.includes(\"todo\") && backend !== \"convex\") {\n    if (database === \"none\") {\n      return validationErr(\n        \"The 'todo' example requires a database. Cannot use --examples todo when database is 'none'.\",\n      );\n    }\n    if (api === \"none\") {\n      return validationErr(\n        \"The 'todo' example requires an API layer (tRPC or oRPC). Cannot use --examples todo when api is 'none'.\",\n      );\n    }\n  }\n\n  if (examplesArr.includes(\"ai\") && (frontend ?? []).includes(\"solid\")) {\n    return validationErr(\"The 'ai' example is not compatible with the Solid frontend.\");\n  }\n\n  // Convex AI example only supports React-based frontends\n  if (examplesArr.includes(\"ai\") && backend === \"convex\") {\n    const frontendArr = frontend ?? [];\n    const includesNuxt = frontendArr.includes(\"nuxt\");\n    const includesSvelte = frontendArr.includes(\"svelte\");\n    if (includesNuxt || includesSvelte) {\n      return validationErr(\n        \"The 'ai' example with Convex backend only supports React-based frontends (Next.js, TanStack Router, TanStack Start, React Router). Svelte and Nuxt are not supported with Convex AI.\",\n      );\n    }\n  }\n\n  return Result.ok(undefined);\n}\n"
  },
  {
    "path": "apps/cli/src/utils/compatibility.ts",
    "content": "import type { Frontend } from \"../types\";\n\nexport const WEB_FRAMEWORKS: readonly Frontend[] = [\n  \"tanstack-router\",\n  \"react-router\",\n  \"tanstack-start\",\n  \"next\",\n  \"nuxt\",\n  \"svelte\",\n  \"solid\",\n  \"astro\",\n] as const;\n"
  },
  {
    "path": "apps/cli/src/utils/config-processing.ts",
    "content": "import path from \"node:path\";\n\nimport { Result } from \"better-result\";\n\nimport type {\n  API,\n  Auth,\n  Backend,\n  CLIInput,\n  Database,\n  DatabaseSetup,\n  ORM,\n  PackageManager,\n  Payments,\n  ProjectConfig,\n  Runtime,\n  ServerDeploy,\n  WebDeploy,\n} from \"../types\";\nimport { ValidationError } from \"./errors\";\n\nexport function processArrayOption<T>(options: (T | \"none\")[] | undefined) {\n  if (!options || options.length === 0) return [];\n  if (options.includes(\"none\" as T | \"none\")) return [];\n  return options.filter((item): item is T => item !== \"none\");\n}\n\nexport function deriveProjectName(projectName?: string, projectDirectory?: string) {\n  if (projectName) {\n    return projectName;\n  }\n  if (projectDirectory) {\n    return path.basename(path.resolve(process.cwd(), projectDirectory));\n  }\n  return \"\";\n}\n\nexport function processFlags(options: CLIInput, projectName?: string) {\n  const config: Partial<ProjectConfig> = {};\n\n  if (options.api) {\n    config.api = options.api as API;\n  }\n\n  if (options.addonOptions) {\n    config.addonOptions = options.addonOptions;\n  }\n\n  if (options.dbSetupOptions) {\n    config.dbSetupOptions = options.dbSetupOptions;\n  }\n\n  if (options.backend) {\n    config.backend = options.backend as Backend;\n  }\n\n  if (options.database) {\n    config.database = options.database as Database;\n  }\n\n  if (options.orm) {\n    config.orm = options.orm as ORM;\n  }\n\n  if (options.auth !== undefined) {\n    config.auth = options.auth as Auth;\n  }\n\n  if (options.payments !== undefined) {\n    config.payments = options.payments as Payments;\n  }\n\n  if (options.git !== undefined) {\n    config.git = options.git;\n  }\n\n  if (options.install !== undefined) {\n    config.install = options.install;\n  }\n\n  if (options.runtime) {\n    config.runtime = options.runtime as Runtime;\n  }\n\n  if (options.dbSetup) {\n    config.dbSetup = options.dbSetup as DatabaseSetup;\n  }\n\n  if (options.packageManager) {\n    config.packageManager = options.packageManager as PackageManager;\n  }\n\n  if (options.webDeploy) {\n    config.webDeploy = options.webDeploy as WebDeploy;\n  }\n\n  if (options.serverDeploy) {\n    config.serverDeploy = options.serverDeploy as ServerDeploy;\n  }\n\n  const derivedName = deriveProjectName(projectName, options.projectDirectory);\n  if (derivedName) {\n    config.projectName = projectName || derivedName;\n  }\n\n  if (options.frontend && options.frontend.length > 0) {\n    config.frontend = processArrayOption(options.frontend);\n  }\n\n  if (options.addons && options.addons.length > 0) {\n    config.addons = processArrayOption(options.addons);\n  }\n\n  if (options.examples && options.examples.length > 0) {\n    config.examples = processArrayOption(options.examples);\n  }\n\n  return config;\n}\n\nexport function getProvidedFlags(options: CLIInput) {\n  return new Set(\n    Object.keys(options).filter((key) => options[key as keyof CLIInput] !== undefined),\n  );\n}\n\nfunction validateNoneExclusivity<T>(\n  options: (T | \"none\")[] | undefined,\n  optionName: string,\n): Result<void, ValidationError> {\n  if (!options || options.length === 0) return Result.ok(undefined);\n\n  if (options.includes(\"none\" as T | \"none\") && options.length > 1) {\n    return Result.err(\n      new ValidationError({\n        message: `Cannot combine 'none' with other ${optionName}.`,\n      }),\n    );\n  }\n  return Result.ok(undefined);\n}\n\nexport function validateArrayOptions(options: CLIInput): Result<void, ValidationError> {\n  const frontendResult = validateNoneExclusivity(options.frontend, \"frontend options\");\n  if (frontendResult.isErr()) return frontendResult;\n\n  const addonsResult = validateNoneExclusivity(options.addons, \"addons\");\n  if (addonsResult.isErr()) return addonsResult;\n\n  const examplesResult = validateNoneExclusivity(options.examples, \"examples\");\n  if (examplesResult.isErr()) return examplesResult;\n\n  return Result.ok(undefined);\n}\n"
  },
  {
    "path": "apps/cli/src/utils/config-validation.ts",
    "content": "import { Result } from \"better-result\";\n\nimport type { CLIInput, Database, DatabaseSetup, ProjectConfig, Runtime } from \"../types\";\nimport {\n  CONVEX_BETTER_AUTH_INCOMPATIBLE_FRONTENDS,\n  CONVEX_BETTER_AUTH_SUPPORTED_FRONTENDS,\n  ensureSingleWebAndNative,\n  isWebFrontend,\n  supportsConvexBetterAuth,\n  validateAddonsAgainstFrontends,\n  validateApiFrontendCompatibility,\n  validateExamplesCompatibility,\n  validatePaymentsCompatibility,\n  validateSelfBackendCompatibility,\n  validateServerDeployRequiresBackend,\n  validateWebDeployRequiresWebFrontend,\n  validateWorkersCompatibility,\n} from \"./compatibility-rules\";\nimport { ValidationError } from \"./errors\";\n\ntype ValidationResult = Result<void, ValidationError>;\n\nfunction validationErr(message: string): ValidationResult {\n  return Result.err(new ValidationError({ message }));\n}\n\nfunction hasResolvedWorkersD1Target(config: Partial<ProjectConfig>) {\n  return (\n    config.backend === \"hono\" &&\n    config.runtime === \"workers\" &&\n    config.serverDeploy === \"cloudflare\"\n  );\n}\n\nfunction hasResolvedSelfCloudflareD1Target(config: Partial<ProjectConfig>) {\n  return (\n    config.backend === \"self\" && config.runtime === \"none\" && config.webDeploy === \"cloudflare\"\n  );\n}\n\nfunction canResolveWorkersD1Target(config: Partial<ProjectConfig>) {\n  return (\n    (config.backend === undefined || config.backend === \"hono\") &&\n    (config.runtime === undefined || config.runtime === \"workers\") &&\n    (config.serverDeploy === undefined || config.serverDeploy === \"cloudflare\")\n  );\n}\n\nfunction canResolveSelfCloudflareD1Target(config: Partial<ProjectConfig>) {\n  return (\n    (config.backend === undefined || config.backend === \"self\") &&\n    (config.runtime === undefined || config.runtime === \"none\") &&\n    (config.webDeploy === undefined || config.webDeploy === \"cloudflare\")\n  );\n}\n\n/**\n * Pure ORM + database compatibility check. Used by the flag-path\n * validator below and by the orm prompt directly (for flag+prompt\n * combos where one value came from a flag and the other from a\n * prompt).\n */\nexport function validateOrmDatabaseCompat(\n  orm: ProjectConfig[\"orm\"] | undefined,\n  database: ProjectConfig[\"database\"] | undefined,\n): ValidationResult {\n  if (orm === \"mongoose\" && database && database !== \"mongodb\") {\n    return validationErr(\n      \"Mongoose ORM requires MongoDB database. Please use '--database mongodb' or choose a different ORM.\",\n    );\n  }\n\n  if (orm === \"drizzle\" && database === \"mongodb\") {\n    return validationErr(\n      \"Drizzle ORM does not support MongoDB. Please use '--orm mongoose' or '--orm prisma' or choose a different database.\",\n    );\n  }\n\n  if (database === \"mongodb\" && orm && orm !== \"mongoose\" && orm !== \"prisma\" && orm !== \"none\") {\n    return validationErr(\n      \"MongoDB database requires Mongoose or Prisma ORM. Please use '--orm mongoose' or '--orm prisma' or choose a different database.\",\n    );\n  }\n\n  if (database && database !== \"none\" && orm === \"none\") {\n    return validationErr(\n      \"Database selection requires an ORM. Please choose '--orm drizzle', '--orm prisma', or '--orm mongoose'.\",\n    );\n  }\n\n  if (orm && orm !== \"none\" && database === \"none\") {\n    return validationErr(\n      \"ORM selection requires a database. Please choose a database or set '--orm none'.\",\n    );\n  }\n\n  return Result.ok(undefined);\n}\n\nexport function validateDatabaseOrmAuth(\n  cfg: Partial<ProjectConfig>,\n  flags?: Set<string>,\n): ValidationResult {\n  const has = (k: string) => (flags ? flags.has(k) : true);\n  if (!has(\"orm\") || !has(\"database\")) return Result.ok(undefined);\n  return validateOrmDatabaseCompat(cfg.orm, cfg.database);\n}\n\nexport function validateDatabaseSetup(\n  config: Partial<ProjectConfig>,\n  providedFlags: Set<string>,\n): ValidationResult {\n  const { dbSetup, database, runtime } = config;\n\n  if (\n    providedFlags.has(\"dbSetup\") &&\n    providedFlags.has(\"database\") &&\n    dbSetup &&\n    dbSetup !== \"none\" &&\n    database === \"none\"\n  ) {\n    return validationErr(\n      \"Database setup requires a database. Please choose a database or set '--db-setup none'.\",\n    );\n  }\n\n  const setupValidations: Record<\n    DatabaseSetup,\n    { database?: Database; runtime?: Runtime; errorMessage: string }\n  > = {\n    turso: {\n      database: \"sqlite\",\n      errorMessage:\n        \"Turso setup requires SQLite database. Please use '--database sqlite' or choose a different setup.\",\n    },\n    neon: {\n      database: \"postgres\",\n      errorMessage:\n        \"Neon setup requires PostgreSQL database. Please use '--database postgres' or choose a different setup.\",\n    },\n    \"prisma-postgres\": {\n      database: \"postgres\",\n      errorMessage:\n        \"Prisma PostgreSQL setup requires PostgreSQL database. Please use '--database postgres' or choose a different setup.\",\n    },\n    planetscale: {\n      errorMessage:\n        \"PlanetScale setup requires PostgreSQL or MySQL database. Please use '--database postgres' or '--database mysql' or choose a different setup.\",\n    },\n    \"mongodb-atlas\": {\n      database: \"mongodb\",\n      errorMessage:\n        \"MongoDB Atlas setup requires MongoDB database. Please use '--database mongodb' or choose a different setup.\",\n    },\n    supabase: {\n      database: \"postgres\",\n      errorMessage:\n        \"Supabase setup requires PostgreSQL database. Please use '--database postgres' or choose a different setup.\",\n    },\n    d1: {\n      database: \"sqlite\",\n      errorMessage: \"Cloudflare D1 setup requires SQLite database.\",\n    },\n    docker: {\n      errorMessage:\n        \"Docker setup is not compatible with SQLite database or Cloudflare Workers runtime.\",\n    },\n    none: { errorMessage: \"\" },\n  };\n\n  if (dbSetup && dbSetup !== \"none\") {\n    const validation = setupValidations[dbSetup];\n\n    if (dbSetup === \"planetscale\") {\n      if (database !== \"postgres\" && database !== \"mysql\") {\n        return validationErr(validation.errorMessage);\n      }\n    } else {\n      if (validation.database && database !== validation.database) {\n        return validationErr(validation.errorMessage);\n      }\n    }\n\n    if (validation.runtime && runtime !== validation.runtime) {\n      return validationErr(validation.errorMessage);\n    }\n\n    if (dbSetup === \"d1\") {\n      const isWorkersTarget = hasResolvedWorkersD1Target(config);\n      const isSelfCloudflareTarget = hasResolvedSelfCloudflareD1Target(config);\n      const canResolveWorkersTarget = canResolveWorkersD1Target(config);\n      const canResolveSelfCloudflareTarget = canResolveSelfCloudflareD1Target(config);\n\n      if (\n        !isWorkersTarget &&\n        !isSelfCloudflareTarget &&\n        !canResolveWorkersTarget &&\n        !canResolveSelfCloudflareTarget\n      ) {\n        return validationErr(\n          \"Cloudflare D1 setup requires SQLite database and either Cloudflare Workers runtime with server deployment or backend 'self' with Cloudflare web deployment.\",\n        );\n      }\n    }\n\n    if (dbSetup === \"docker\") {\n      if (database === \"sqlite\") {\n        return validationErr(\n          \"Docker setup is not compatible with SQLite database. SQLite is file-based and doesn't require Docker. Please use '--database postgres', '--database mysql', '--database mongodb', or choose a different setup.\",\n        );\n      }\n      if (runtime === \"workers\") {\n        return validationErr(\n          \"Docker setup is not compatible with Cloudflare Workers runtime. Workers runtime uses serverless databases (D1) and doesn't support local Docker containers. Please use '--db-setup d1' for SQLite or choose a different runtime.\",\n        );\n      }\n    }\n  }\n\n  return Result.ok(undefined);\n}\n\nexport function validateConvexConstraints(\n  config: Partial<ProjectConfig>,\n  providedFlags: Set<string>,\n): ValidationResult {\n  const { backend } = config;\n\n  if (backend !== \"convex\") {\n    return Result.ok(undefined);\n  }\n\n  const has = (k: string) => providedFlags.has(k);\n\n  if (has(\"runtime\") && config.runtime !== \"none\") {\n    return validationErr(\n      \"Convex backend requires '--runtime none'. Please remove the --runtime flag or set it to 'none'.\",\n    );\n  }\n\n  if (has(\"database\") && config.database !== \"none\") {\n    return validationErr(\n      \"Convex backend requires '--database none'. Please remove the --database flag or set it to 'none'.\",\n    );\n  }\n\n  if (has(\"orm\") && config.orm !== \"none\") {\n    return validationErr(\n      \"Convex backend requires '--orm none'. Please remove the --orm flag or set it to 'none'.\",\n    );\n  }\n\n  if (has(\"api\") && config.api !== \"none\") {\n    return validationErr(\n      \"Convex backend requires '--api none'. Please remove the --api flag or set it to 'none'.\",\n    );\n  }\n\n  if (has(\"dbSetup\") && config.dbSetup !== \"none\") {\n    return validationErr(\n      \"Convex backend requires '--db-setup none'. Please remove the --db-setup flag or set it to 'none'.\",\n    );\n  }\n\n  if (has(\"serverDeploy\") && config.serverDeploy !== \"none\") {\n    return validationErr(\n      \"Convex backend requires '--server-deploy none'. Please remove the --server-deploy flag or set it to 'none'.\",\n    );\n  }\n\n  if (has(\"auth\") && config.auth === \"better-auth\") {\n    const incompatibleFrontends =\n      config.frontend?.filter((f) =>\n        CONVEX_BETTER_AUTH_INCOMPATIBLE_FRONTENDS.includes(\n          f as (typeof CONVEX_BETTER_AUTH_INCOMPATIBLE_FRONTENDS)[number],\n        ),\n      ) ?? [];\n    const hasSupportedFrontend = supportsConvexBetterAuth(config.frontend);\n\n    if (incompatibleFrontends.length > 0) {\n      return validationErr(\n        `Better Auth with '--backend convex' is not compatible with the following frontends: ${incompatibleFrontends.join(\n          \", \",\n        )}. Please use a React-based web frontend (next, tanstack-start, tanstack-router, react-router), a supported native frontend, or choose a different auth provider.`,\n      );\n    }\n\n    if (!hasSupportedFrontend) {\n      return validationErr(\n        `Better Auth with '--backend convex' requires a supported frontend (${CONVEX_BETTER_AUTH_SUPPORTED_FRONTENDS.join(\n          \", \",\n        )}).`,\n      );\n    }\n  }\n\n  return Result.ok(undefined);\n}\n\nexport function validateBackendNoneConstraints(\n  config: Partial<ProjectConfig>,\n  providedFlags: Set<string>,\n): ValidationResult {\n  const { backend } = config;\n\n  if (backend !== \"none\") {\n    return Result.ok(undefined);\n  }\n\n  const has = (k: string) => providedFlags.has(k);\n\n  if (has(\"runtime\") && config.runtime !== \"none\") {\n    return validationErr(\n      \"Backend 'none' requires '--runtime none'. Please remove the --runtime flag or set it to 'none'.\",\n    );\n  }\n\n  if (has(\"database\") && config.database !== \"none\") {\n    return validationErr(\n      \"Backend 'none' requires '--database none'. Please remove the --database flag or set it to 'none'.\",\n    );\n  }\n\n  if (has(\"orm\") && config.orm !== \"none\") {\n    return validationErr(\n      \"Backend 'none' requires '--orm none'. Please remove the --orm flag or set it to 'none'.\",\n    );\n  }\n\n  if (has(\"api\") && config.api !== \"none\") {\n    return validationErr(\n      \"Backend 'none' requires '--api none'. Please remove the --api flag or set it to 'none'.\",\n    );\n  }\n\n  if (has(\"auth\") && config.auth !== \"none\") {\n    return validationErr(\n      \"Backend 'none' requires '--auth none'. Please remove the --auth flag or set it to 'none'.\",\n    );\n  }\n\n  if (has(\"payments\") && config.payments !== \"none\") {\n    return validationErr(\n      \"Backend 'none' requires '--payments none'. Please remove the --payments flag or set it to 'none'.\",\n    );\n  }\n\n  if (has(\"dbSetup\") && config.dbSetup !== \"none\") {\n    return validationErr(\n      \"Backend 'none' requires '--db-setup none'. Please remove the --db-setup flag or set it to 'none'.\",\n    );\n  }\n\n  if (has(\"serverDeploy\") && config.serverDeploy !== \"none\") {\n    return validationErr(\n      \"Backend 'none' requires '--server-deploy none'. Please remove the --server-deploy flag or set it to 'none'.\",\n    );\n  }\n\n  return Result.ok(undefined);\n}\n\nexport function validateSelfBackendConstraints(\n  config: Partial<ProjectConfig>,\n  providedFlags: Set<string>,\n): ValidationResult {\n  const { backend } = config;\n\n  if (backend !== \"self\") {\n    return Result.ok(undefined);\n  }\n\n  const has = (k: string) => providedFlags.has(k);\n\n  if (has(\"runtime\") && config.runtime !== \"none\") {\n    return validationErr(\n      \"Backend 'self' (fullstack) requires '--runtime none'. Please remove the --runtime flag or set it to 'none'.\",\n    );\n  }\n\n  return Result.ok(undefined);\n}\n\nexport function validateBackendConstraints(\n  config: Partial<ProjectConfig>,\n  providedFlags: Set<string>,\n  options: CLIInput,\n): ValidationResult {\n  const { backend } = config;\n\n  if (config.auth === \"clerk\" && config.frontend) {\n    const incompatibleFrontends = config.frontend.filter((f) =>\n      [\"nuxt\", \"svelte\", \"solid\", \"astro\"].includes(f),\n    );\n    if (incompatibleFrontends.length > 0) {\n      return validationErr(\n        `Clerk authentication is not compatible with the following frontends: ${incompatibleFrontends.join(\n          \", \",\n        )}. Please choose a different frontend or auth provider.`,\n      );\n    }\n  }\n\n  if (\n    providedFlags.has(\"backend\") &&\n    backend &&\n    backend !== \"convex\" &&\n    backend !== \"none\" &&\n    backend !== \"self\"\n  ) {\n    if (providedFlags.has(\"runtime\") && options.runtime === \"none\") {\n      return validationErr(\n        \"'--runtime none' is only supported with '--backend convex', '--backend none', or '--backend self'. Please choose 'bun', 'node', or remove the --runtime flag.\",\n      );\n    }\n  }\n\n  if (backend === \"convex\" && providedFlags.has(\"frontend\") && options.frontend) {\n    const incompatibleFrontends = options.frontend.filter((f) => f === \"solid\" || f === \"astro\");\n    if (incompatibleFrontends.length > 0) {\n      return validationErr(\n        `The following frontends are not compatible with '--backend convex': ${incompatibleFrontends.join(\n          \", \",\n        )}. Please choose a different frontend or backend.`,\n      );\n    }\n  }\n\n  return Result.ok(undefined);\n}\n\nexport function validateFrontendConstraints(\n  config: Partial<ProjectConfig>,\n  providedFlags: Set<string>,\n): ValidationResult {\n  const { frontend } = config;\n\n  if (frontend && frontend.length > 0) {\n    const singleWebNativeResult = ensureSingleWebAndNative(frontend);\n    if (singleWebNativeResult.isErr()) {\n      return singleWebNativeResult;\n    }\n\n    if (providedFlags.has(\"api\") && providedFlags.has(\"frontend\") && config.api) {\n      const apiResult = validateApiFrontendCompatibility(config.api, frontend);\n      if (apiResult.isErr()) {\n        return apiResult;\n      }\n    }\n  }\n\n  const hasWebFrontendFlag = (frontend ?? []).some((f) => isWebFrontend(f));\n  const webDeployResult = validateWebDeployRequiresWebFrontend(\n    config.webDeploy,\n    hasWebFrontendFlag,\n  );\n  if (webDeployResult.isErr()) {\n    return webDeployResult;\n  }\n\n  return Result.ok(undefined);\n}\n\nexport function validateApiConstraints(\n  config: Partial<ProjectConfig>,\n  options: CLIInput,\n): ValidationResult {\n  if (config.api === \"none\") {\n    if (\n      options.examples?.includes(\"todo\") &&\n      options.backend !== \"convex\" &&\n      options.backend !== \"none\"\n    ) {\n      return validationErr(\n        \"Cannot use '--examples todo' when '--api' is set to 'none'. The todo example requires an API layer. Please remove 'todo' from --examples or choose an API type.\",\n      );\n    }\n  }\n\n  return Result.ok(undefined);\n}\n\nexport function validateFullConfig(\n  config: Partial<ProjectConfig>,\n  providedFlags: Set<string>,\n  options: CLIInput,\n): ValidationResult {\n  return Result.gen(function* () {\n    yield* validateDatabaseOrmAuth(config, providedFlags);\n    yield* validateDatabaseSetup(config, providedFlags);\n\n    yield* validateConvexConstraints(config, providedFlags);\n    yield* validateBackendNoneConstraints(config, providedFlags);\n    yield* validateSelfBackendConstraints(config, providedFlags);\n    yield* validateBackendConstraints(config, providedFlags, options);\n\n    yield* validateFrontendConstraints(config, providedFlags);\n\n    yield* validateApiConstraints(config, options);\n\n    yield* validateServerDeployRequiresBackend(config.serverDeploy, config.backend);\n\n    yield* validateSelfBackendCompatibility(providedFlags, options, config);\n    yield* validateWorkersCompatibility(providedFlags, options, config);\n\n    if (config.runtime === \"workers\" && config.serverDeploy === \"none\") {\n      yield* validationErr(\n        \"Cloudflare Workers runtime requires a server deployment. Please choose 'cloudflare' for --server-deploy.\",\n      );\n    }\n\n    if (\n      providedFlags.has(\"serverDeploy\") &&\n      config.serverDeploy === \"cloudflare\" &&\n      config.runtime !== \"workers\"\n    ) {\n      yield* validationErr(\n        `Server deployment '${config.serverDeploy}' requires '--runtime workers'. Please use '--runtime workers' or choose a different server deployment.`,\n      );\n    }\n\n    if (config.addons && config.addons.length > 0) {\n      yield* validateAddonsAgainstFrontends(\n        config.addons,\n        config.frontend,\n        config.auth,\n        config.backend,\n        config.runtime,\n      );\n      config.addons = [...new Set(config.addons)];\n    }\n\n    yield* validateExamplesCompatibility(\n      config.examples ?? [],\n      config.backend,\n      config.database,\n      config.frontend ?? [],\n      config.api,\n    );\n\n    yield* validatePaymentsCompatibility(\n      config.payments,\n      config.auth,\n      config.backend,\n      config.frontend ?? [],\n    );\n\n    return Result.ok(undefined);\n  });\n}\n\nexport function validateConfigForProgrammaticUse(config: Partial<ProjectConfig>): ValidationResult {\n  return Result.gen(function* () {\n    yield* validateDatabaseOrmAuth(config);\n\n    if (config.frontend && config.frontend.length > 0) {\n      yield* ensureSingleWebAndNative(config.frontend);\n    }\n\n    yield* validateApiFrontendCompatibility(config.api, config.frontend);\n\n    yield* validatePaymentsCompatibility(\n      config.payments,\n      config.auth,\n      config.backend,\n      config.frontend,\n    );\n\n    if (config.addons && config.addons.length > 0) {\n      yield* validateAddonsAgainstFrontends(\n        config.addons,\n        config.frontend,\n        config.auth,\n        config.backend,\n        config.runtime,\n      );\n    }\n\n    yield* validateExamplesCompatibility(\n      config.examples ?? [],\n      config.backend,\n      config.database,\n      config.frontend ?? [],\n      config.api,\n    );\n\n    return Result.ok(undefined);\n  });\n}\n"
  },
  {
    "path": "apps/cli/src/utils/context.ts",
    "content": "import { AsyncLocalStorage } from \"node:async_hooks\";\n\nimport type { PackageManager } from \"../types\";\n\nexport type NavigationState = {\n  isFirstPrompt: boolean;\n  lastPromptShownUI: boolean;\n};\n\nexport type CLIContext = {\n  navigation: NavigationState;\n  silent: boolean;\n  verbose: boolean;\n  projectDir?: string;\n  projectName?: string;\n  packageManager?: PackageManager;\n};\n\nconst cliStorage = new AsyncLocalStorage<CLIContext>();\n\nfunction defaultContext(): CLIContext {\n  return {\n    navigation: {\n      isFirstPrompt: false,\n      lastPromptShownUI: false,\n    },\n    silent: false,\n    verbose: false,\n  };\n}\n\nexport function getContext(): CLIContext {\n  const ctx = cliStorage.getStore();\n  if (!ctx) {\n    return defaultContext();\n  }\n  return ctx;\n}\n\nexport function tryGetContext(): CLIContext | undefined {\n  return cliStorage.getStore();\n}\n\nexport function isSilent(): boolean {\n  return getContext().silent;\n}\n\nexport function isVerbose(): boolean {\n  return getContext().verbose;\n}\n\nexport function getNavigation(): NavigationState {\n  return getContext().navigation;\n}\n\nexport function isFirstPrompt(): boolean {\n  return getContext().navigation.isFirstPrompt;\n}\n\nexport function didLastPromptShowUI(): boolean {\n  return getContext().navigation.lastPromptShownUI;\n}\n\nexport function getProjectDir(): string | undefined {\n  return getContext().projectDir;\n}\n\nexport function getPackageManager(): PackageManager | undefined {\n  return getContext().packageManager;\n}\n\nexport function setIsFirstPrompt(value: boolean): void {\n  const ctx = tryGetContext();\n  if (ctx) {\n    ctx.navigation.isFirstPrompt = value;\n  }\n}\n\nexport function setLastPromptShownUI(value: boolean): void {\n  const ctx = tryGetContext();\n  if (ctx) {\n    ctx.navigation.lastPromptShownUI = value;\n  }\n}\n\nexport function setProjectInfo(info: {\n  projectDir?: string;\n  projectName?: string;\n  packageManager?: PackageManager;\n}): void {\n  const ctx = tryGetContext();\n  if (ctx) {\n    if (info.projectDir !== undefined) ctx.projectDir = info.projectDir;\n    if (info.projectName !== undefined) ctx.projectName = info.projectName;\n    if (info.packageManager !== undefined) ctx.packageManager = info.packageManager;\n  }\n}\n\nexport type ContextOptions = {\n  silent?: boolean;\n  verbose?: boolean;\n  projectDir?: string;\n  projectName?: string;\n  packageManager?: PackageManager;\n};\n\nexport function runWithContext<T>(options: ContextOptions, fn: () => T): T {\n  const ctx: CLIContext = {\n    navigation: {\n      isFirstPrompt: false,\n      lastPromptShownUI: false,\n    },\n    silent: options.silent ?? false,\n    verbose: options.verbose ?? false,\n    projectDir: options.projectDir,\n    projectName: options.projectName,\n    packageManager: options.packageManager,\n  };\n\n  return cliStorage.run(ctx, fn);\n}\n\nexport async function runWithContextAsync<T>(\n  options: ContextOptions,\n  fn: () => Promise<T>,\n): Promise<T> {\n  const ctx: CLIContext = {\n    navigation: {\n      isFirstPrompt: false,\n      lastPromptShownUI: false,\n    },\n    silent: options.silent ?? false,\n    verbose: options.verbose ?? false,\n    projectDir: options.projectDir,\n    projectName: options.projectName,\n    packageManager: options.packageManager,\n  };\n\n  return cliStorage.run(ctx, fn);\n}\n"
  },
  {
    "path": "apps/cli/src/utils/display-config.ts",
    "content": "import pc from \"picocolors\";\n\nimport type { ProjectConfig } from \"../types\";\n\nexport function displayConfig(config: Partial<ProjectConfig>) {\n  const configDisplay: string[] = [];\n\n  if (config.projectName) {\n    configDisplay.push(`${pc.blue(\"Project Name:\")} ${config.projectName}`);\n  }\n\n  if (config.frontend !== undefined) {\n    const frontend = Array.isArray(config.frontend) ? config.frontend : [config.frontend];\n    const frontendText =\n      frontend.length > 0 && frontend[0] !== undefined ? frontend.join(\", \") : \"none\";\n    configDisplay.push(`${pc.blue(\"Frontend:\")} ${frontendText}`);\n  }\n\n  if (config.backend !== undefined) {\n    configDisplay.push(`${pc.blue(\"Backend:\")} ${String(config.backend)}`);\n  }\n\n  if (config.runtime !== undefined) {\n    configDisplay.push(`${pc.blue(\"Runtime:\")} ${String(config.runtime)}`);\n  }\n\n  if (config.api !== undefined) {\n    configDisplay.push(`${pc.blue(\"API:\")} ${String(config.api)}`);\n  }\n\n  if (config.database !== undefined) {\n    configDisplay.push(`${pc.blue(\"Database:\")} ${String(config.database)}`);\n  }\n\n  if (config.orm !== undefined) {\n    configDisplay.push(`${pc.blue(\"ORM:\")} ${String(config.orm)}`);\n  }\n\n  if (config.auth !== undefined) {\n    configDisplay.push(`${pc.blue(\"Auth:\")} ${String(config.auth)}`);\n  }\n\n  if (config.payments !== undefined) {\n    configDisplay.push(`${pc.blue(\"Payments:\")} ${String(config.payments)}`);\n  }\n\n  if (config.addons !== undefined) {\n    const addons = Array.isArray(config.addons) ? config.addons : [config.addons];\n    const addonsText = addons.length > 0 && addons[0] !== undefined ? addons.join(\", \") : \"none\";\n    configDisplay.push(`${pc.blue(\"Addons:\")} ${addonsText}`);\n  }\n\n  if (config.examples !== undefined) {\n    const examples = Array.isArray(config.examples) ? config.examples : [config.examples];\n    const examplesText =\n      examples.length > 0 && examples[0] !== undefined ? examples.join(\", \") : \"none\";\n    configDisplay.push(`${pc.blue(\"Examples:\")} ${examplesText}`);\n  }\n\n  if (config.git !== undefined) {\n    const gitText =\n      typeof config.git === \"boolean\" ? (config.git ? \"Yes\" : \"No\") : String(config.git);\n    configDisplay.push(`${pc.blue(\"Git Init:\")} ${gitText}`);\n  }\n\n  if (config.packageManager !== undefined) {\n    configDisplay.push(`${pc.blue(\"Package Manager:\")} ${String(config.packageManager)}`);\n  }\n\n  if (config.install !== undefined) {\n    const installText =\n      typeof config.install === \"boolean\"\n        ? config.install\n          ? \"Yes\"\n          : \"No\"\n        : String(config.install);\n    configDisplay.push(`${pc.blue(\"Install Dependencies:\")} ${installText}`);\n  }\n\n  if (config.dbSetup !== undefined) {\n    configDisplay.push(`${pc.blue(\"Database Setup:\")} ${String(config.dbSetup)}`);\n  }\n\n  if (config.webDeploy !== undefined) {\n    configDisplay.push(`${pc.blue(\"Web Deployment:\")} ${String(config.webDeploy)}`);\n  }\n\n  if (config.serverDeploy !== undefined) {\n    configDisplay.push(`${pc.blue(\"Server Deployment:\")} ${String(config.serverDeploy)}`);\n  }\n\n  if (configDisplay.length === 0) {\n    return pc.yellow(\"No configuration selected.\");\n  }\n\n  return configDisplay.join(\"\\n\");\n}\n"
  },
  {
    "path": "apps/cli/src/utils/docker-utils.ts",
    "content": "import os from \"node:os\";\n\nimport { Result } from \"better-result\";\nimport { $ } from \"execa\";\nimport pc from \"picocolors\";\n\nimport type { Database } from \"../types\";\nimport { commandExists } from \"./command-exists\";\n\nexport async function isDockerInstalled() {\n  return commandExists(\"docker\");\n}\n\nexport async function isDockerRunning(): Promise<boolean> {\n  const result = await Result.tryPromise({\n    try: async () => {\n      await $`docker info`;\n      return true;\n    },\n    catch: () => false,\n  });\n\n  return result.isOk() ? result.value : false;\n}\n\nexport function getDockerInstallInstructions(platform: string, database: Database) {\n  const isMac = platform === \"darwin\";\n  const isWindows = platform === \"win32\";\n  const isLinux = platform === \"linux\";\n\n  let installUrl = \"\";\n  let platformName = \"\";\n\n  if (isMac) {\n    installUrl = \"https://docs.docker.com/desktop/setup/install/mac-install/\";\n    platformName = \"macOS\";\n  } else if (isWindows) {\n    installUrl = \"https://docs.docker.com/desktop/setup/install/windows-install/\";\n    platformName = \"Windows\";\n  } else if (isLinux) {\n    installUrl = \"https://docs.docker.com/desktop/setup/install/linux/\";\n    platformName = \"Linux\";\n  }\n\n  const databaseName =\n    database === \"mongodb\" ? \"MongoDB\" : database === \"mysql\" ? \"MySQL\" : \"PostgreSQL\";\n\n  return `${pc.yellow(\"IMPORTANT:\")} Docker required for ${databaseName}. Install for ${platformName}:\\n${pc.blue(installUrl)}`;\n}\n\nexport async function getDockerStatus(database: Database) {\n  const platform = os.platform();\n  const installed = await isDockerInstalled();\n\n  if (!installed) {\n    return {\n      installed: false,\n      running: false,\n      message: getDockerInstallInstructions(platform, database),\n    };\n  }\n\n  const running = await isDockerRunning();\n  if (!running) {\n    return {\n      installed: true,\n      running: false,\n      message: `${pc.yellow(\"IMPORTANT:\")} Docker is installed but not running.`,\n    };\n  }\n\n  return {\n    installed: true,\n    running: true,\n  };\n}\n"
  },
  {
    "path": "apps/cli/src/utils/env-utils.ts",
    "content": "import fs from \"fs-extra\";\n\nexport interface EnvVariable {\n  key: string;\n  value: string;\n  condition?: boolean;\n}\n\nexport async function addEnvVariablesToFile(\n  envPath: string,\n  variables: EnvVariable[],\n): Promise<void> {\n  let content = \"\";\n\n  if (fs.existsSync(envPath)) {\n    content = await fs.readFile(envPath, \"utf-8\");\n  } else {\n    // If file doesn't exist, create it\n    await fs.ensureFile(envPath);\n  }\n\n  const existingLines = content.split(\"\\n\");\n  const newLines: string[] = [];\n  const keysToAdd = new Map<string, string>();\n\n  for (const variable of variables) {\n    if (variable.condition === false || !variable.key) {\n      continue;\n    }\n    keysToAdd.set(variable.key, variable.value);\n  }\n\n  let foundKeys = new Set<string>();\n\n  for (const line of existingLines) {\n    const trimmedLine = line.trim();\n    let lineProcessed = false;\n\n    for (const [key, value] of keysToAdd) {\n      if (trimmedLine.startsWith(`${key}=`)) {\n        newLines.push(`${key}=${value}`);\n        foundKeys.add(key);\n        lineProcessed = true;\n        break;\n      }\n    }\n\n    if (!lineProcessed) {\n      newLines.push(line);\n    }\n  }\n\n  for (const [key, value] of keysToAdd) {\n    if (!foundKeys.has(key)) {\n      newLines.push(`${key}=${value}`);\n    }\n  }\n\n  // Remove empty line at the end if it exists\n  if (newLines.length > 0 && newLines[newLines.length - 1] === \"\") {\n    newLines.pop();\n  }\n\n  const hasChanges = foundKeys.size > 0 || keysToAdd.size > foundKeys.size;\n\n  if (hasChanges) {\n    await fs.writeFile(envPath, newLines.join(\"\\n\") + \"\\n\");\n  }\n}\n"
  },
  {
    "path": "apps/cli/src/utils/errors.ts",
    "content": "import { cancel } from \"@clack/prompts\";\nimport { Result, TaggedError } from \"better-result\";\nimport pc from \"picocolors\";\n\nimport { cliConsola } from \"./terminal-output\";\n\n// ============================================================================\n// Tagged Error Classes\n// ============================================================================\n\n/**\n * User cancelled the operation (e.g., Ctrl+C in prompts)\n */\nexport class UserCancelledError extends TaggedError(\"UserCancelledError\")<{\n  message: string;\n}>() {\n  constructor(args?: { message?: string }) {\n    super({ message: args?.message ?? \"Operation cancelled\" });\n  }\n}\n\n/**\n * General CLI error for validation failures, invalid flags, etc.\n */\nexport class CLIError extends TaggedError(\"CLIError\")<{\n  message: string;\n  cause?: unknown;\n}>() {}\n\n/**\n * Validation error for config/flag validation failures\n */\nexport class ValidationError extends TaggedError(\"ValidationError\")<{\n  field?: string;\n  value?: unknown;\n  message: string;\n}>() {\n  constructor(args: { field?: string; value?: unknown; message: string }) {\n    super(args);\n  }\n}\n\n/**\n * Compatibility error for incompatible option combinations\n */\nexport class CompatibilityError extends TaggedError(\"CompatibilityError\")<{\n  options: string[];\n  message: string;\n}>() {\n  constructor(args: { options: string[]; message: string }) {\n    super(args);\n  }\n}\n\n/**\n * Directory conflict error when target directory exists and is not empty\n */\nexport class DirectoryConflictError extends TaggedError(\"DirectoryConflictError\")<{\n  directory: string;\n  message: string;\n}>() {\n  constructor(args: { directory: string }) {\n    super({\n      directory: args.directory,\n      message: `Directory \"${args.directory}\" already exists and is not empty. Use directoryConflict: \"overwrite\", \"merge\", or \"increment\" to handle this.`,\n    });\n  }\n}\n\n/**\n * Project creation error for failures during scaffolding\n */\nexport class ProjectCreationError extends TaggedError(\"ProjectCreationError\")<{\n  phase: string;\n  message: string;\n  cause?: unknown;\n}>() {\n  constructor(args: { phase: string; message: string; cause?: unknown }) {\n    super(args);\n  }\n}\n\n/**\n * Database setup error for failures during database configuration\n */\nexport class DatabaseSetupError extends TaggedError(\"DatabaseSetupError\")<{\n  provider: string;\n  message: string;\n  cause?: unknown;\n}>() {\n  constructor(args: { provider: string; message: string; cause?: unknown }) {\n    super(args);\n  }\n}\n\n/**\n * Addon setup error for failures during addon configuration\n */\nexport class AddonSetupError extends TaggedError(\"AddonSetupError\")<{\n  addon: string;\n  message: string;\n  cause?: unknown;\n}>() {\n  constructor(args: { addon: string; message: string; cause?: unknown }) {\n    super(args);\n  }\n}\n\n// ============================================================================\n// Error Type Unions\n// ============================================================================\n\n/**\n * All possible CLI errors\n */\nexport type AppError =\n  | UserCancelledError\n  | CLIError\n  | ValidationError\n  | CompatibilityError\n  | DirectoryConflictError\n  | ProjectCreationError\n  | DatabaseSetupError\n  | AddonSetupError;\n\n// ============================================================================\n// Result Helper Functions\n// ============================================================================\n\n/**\n * Create an error Result from a message string\n */\nexport function cliError(message: string): Result<never, CLIError> {\n  return Result.err(new CLIError({ message }));\n}\n\n/**\n * Create a validation error Result\n */\nexport function validationError(\n  message: string,\n  field?: string,\n  value?: unknown,\n): Result<never, ValidationError> {\n  return Result.err(new ValidationError({ message, field, value }));\n}\n\n/**\n * Create a compatibility error Result\n */\nexport function compatibilityError(\n  message: string,\n  options: string[],\n): Result<never, CompatibilityError> {\n  return Result.err(new CompatibilityError({ message, options }));\n}\n\n/**\n * Create a user cancelled error Result\n */\nexport function userCancelled(message?: string): Result<never, UserCancelledError> {\n  return Result.err(new UserCancelledError({ message }));\n}\n\n/**\n * Create a directory conflict error Result\n */\nexport function directoryConflict(directory: string): Result<never, DirectoryConflictError> {\n  return Result.err(new DirectoryConflictError({ directory }));\n}\n\n/**\n * Create a project creation error Result\n */\nexport function projectCreationError(\n  phase: string,\n  message: string,\n  cause?: unknown,\n): Result<never, ProjectCreationError> {\n  return Result.err(new ProjectCreationError({ phase, message, cause }));\n}\n\n/**\n * Create a database setup error Result\n */\nexport function databaseSetupError(\n  provider: string,\n  message: string,\n  cause?: unknown,\n): Result<never, DatabaseSetupError> {\n  return Result.err(new DatabaseSetupError({ provider, message, cause }));\n}\n\n/**\n * Create an addon setup error Result\n */\nexport function addonSetupError(\n  addon: string,\n  message: string,\n  cause?: unknown,\n): Result<never, AddonSetupError> {\n  return Result.err(new AddonSetupError({ addon, message, cause }));\n}\n\n// ============================================================================\n// Error Display Utilities\n// ============================================================================\n\n/**\n * Display an error to the user (for CLI mode)\n */\nexport function displayError(error: AppError): void {\n  if (UserCancelledError.is(error)) {\n    cancel(pc.red(error.message));\n  } else {\n    cliConsola.error(pc.red(error.message));\n  }\n}\n\n/**\n * Handle a Result error by displaying it and exiting (for CLI mode)\n */\nexport function handleResultError(error: AppError): never {\n  displayError(error);\n  process.exit(1);\n}\n"
  },
  {
    "path": "apps/cli/src/utils/external-commands.ts",
    "content": "export function shouldSkipExternalCommands(): boolean {\n  return process.env.BTS_SKIP_EXTERNAL_COMMANDS === \"1\" || process.env.BTS_TEST_MODE === \"1\";\n}\n"
  },
  {
    "path": "apps/cli/src/utils/file-formatter.ts",
    "content": "import path from \"node:path\";\n\nimport { Result } from \"better-result\";\nimport fs from \"fs-extra\";\nimport { format, type FormatOptions } from \"oxfmt\";\n\nimport { ProjectCreationError } from \"./errors\";\n\nconst formatOptions: FormatOptions = {\n  experimentalSortPackageJson: true,\n  experimentalSortImports: {\n    order: \"asc\",\n  },\n};\n\nexport async function formatCode(filePath: string, content: string): Promise<string | null> {\n  const result = await Result.tryPromise({\n    try: async () => {\n      const formatResult = await format(path.basename(filePath), content, formatOptions);\n\n      if (formatResult.errors && formatResult.errors.length > 0) {\n        return null;\n      }\n\n      return formatResult.code;\n    },\n    catch: () => null,\n  });\n\n  return result.isOk() ? result.value : null;\n}\n\nexport async function formatProject(\n  projectDir: string,\n): Promise<Result<void, ProjectCreationError>> {\n  return Result.tryPromise({\n    try: async () => {\n      async function formatDirectory(dir: string) {\n        const entries = await fs.readdir(dir, { withFileTypes: true });\n\n        await Promise.all(\n          entries.map(async (entry) => {\n            const fullPath = path.join(dir, entry.name);\n\n            if (entry.isDirectory()) {\n              await formatDirectory(fullPath);\n            } else if (entry.isFile()) {\n              const fileResult = await Result.tryPromise({\n                try: async () => {\n                  const content = await fs.readFile(fullPath, \"utf-8\");\n                  const formatted = await formatCode(fullPath, content);\n                  if (formatted && formatted !== content) {\n                    await fs.writeFile(fullPath, formatted, \"utf-8\");\n                  }\n                },\n                catch: () => undefined, // Ignore individual file formatting errors\n              });\n              // Result is intentionally unused - we silently ignore errors\n              void fileResult;\n            }\n          }),\n        );\n      }\n\n      await formatDirectory(projectDir);\n    },\n    catch: (e) =>\n      new ProjectCreationError({\n        phase: \"formatting\",\n        message: `Failed to format project: ${e instanceof Error ? e.message : String(e)}`,\n        cause: e,\n      }),\n  });\n}\n"
  },
  {
    "path": "apps/cli/src/utils/get-latest-cli-version.ts",
    "content": "import path from \"node:path\";\n\nimport { Result } from \"better-result\";\nimport fs from \"fs-extra\";\n\nimport { PKG_ROOT } from \"../constants\";\nimport { CLIError } from \"./errors\";\n\nexport function getLatestCLIVersionResult(): Result<string, CLIError> {\n  const packageJsonPath = path.join(PKG_ROOT, \"package.json\");\n\n  return Result.try({\n    try: () => {\n      const packageJsonContent = fs.readJSONSync(packageJsonPath);\n      return String(packageJsonContent.version ?? \"1.0.0\");\n    },\n    catch: (e) =>\n      new CLIError({\n        message: `Failed to read CLI version from package.json: ${e instanceof Error ? e.message : String(e)}`,\n        cause: e,\n      }),\n  });\n}\n\nexport function getLatestCLIVersion(): string {\n  return getLatestCLIVersionResult().unwrapOr(\"1.0.0\");\n}\n"
  },
  {
    "path": "apps/cli/src/utils/get-package-manager.ts",
    "content": "import type { PackageManager } from \"../types\";\n\nexport const getUserPkgManager: () => PackageManager = () => {\n  const userAgent = process.env.npm_config_user_agent;\n\n  if (userAgent?.startsWith(\"pnpm\")) {\n    return \"pnpm\";\n  }\n  if (userAgent?.startsWith(\"bun\")) {\n    return \"bun\";\n  }\n  return \"npm\";\n};\n"
  },
  {
    "path": "apps/cli/src/utils/input-hardening.ts",
    "content": "import { Result } from \"better-result\";\n\nimport { ValidationError } from \"./errors\";\n\ntype ValidationResult = Result<void, ValidationError>;\n\nfunction hasControlCharacters(value: string): boolean {\n  for (const char of value) {\n    const charCode = char.charCodeAt(0);\n    if (charCode < 0x20 || charCode === 0x7f) {\n      return true;\n    }\n  }\n  return false;\n}\n\nfunction hardeningError(field: string, value: string, message: string): ValidationResult {\n  return Result.err(\n    new ValidationError({\n      field,\n      value,\n      message,\n    }),\n  );\n}\n\nexport function validateAgentSafePathInput(value: string, field: string): ValidationResult {\n  if (hasControlCharacters(value)) {\n    return hardeningError(field, value, `Invalid ${field}: control characters are not allowed.`);\n  }\n\n  return Result.ok(undefined);\n}\n"
  },
  {
    "path": "apps/cli/src/utils/navigation.ts",
    "content": "export const GO_BACK_SYMBOL = Symbol(\"clack:goBack\");\n\nexport function isGoBack(value: unknown): value is symbol {\n  return value === GO_BACK_SYMBOL;\n}\n"
  },
  {
    "path": "apps/cli/src/utils/open-url.ts",
    "content": "import { $ } from \"execa\";\n\nexport async function openUrl(url: string): Promise<void> {\n  const platform = process.platform;\n\n  if (platform === \"darwin\") {\n    await $({ stdio: \"ignore\" })`open ${url}`;\n    return;\n  }\n\n  if (platform === \"win32\") {\n    // Windows needs special handling for ampersands\n    const escapedUrl = url.replace(/&/g, \"^&\");\n    await $({ stdio: \"ignore\" })`cmd /c start \"\" ${escapedUrl}`;\n    return;\n  }\n\n  await $({ stdio: \"ignore\" })`xdg-open ${url}`;\n}\n"
  },
  {
    "path": "apps/cli/src/utils/package-runner.ts",
    "content": "import type { PackageManager } from \"../types\";\n\nfunction splitCommandArgs(commandWithArgs: string): string[] {\n  const args: string[] = [];\n  let current = \"\";\n  let quote: \"'\" | '\"' | null = null;\n\n  for (let i = 0; i < commandWithArgs.length; i += 1) {\n    const char = commandWithArgs[i];\n\n    if (quote) {\n      if (char === quote) {\n        quote = null;\n        continue;\n      }\n      if (char === \"\\\\\" && i + 1 < commandWithArgs.length) {\n        const nextChar = commandWithArgs[i + 1];\n        if (nextChar === quote || nextChar === \"\\\\\") {\n          current += nextChar;\n          i += 1;\n          continue;\n        }\n      }\n      current += char;\n      continue;\n    }\n\n    if (char === '\"' || char === \"'\") {\n      quote = char;\n      continue;\n    }\n\n    if (/\\s/.test(char)) {\n      if (current.length > 0) {\n        args.push(current);\n        current = \"\";\n      }\n      continue;\n    }\n\n    current += char;\n  }\n\n  if (current.length > 0) {\n    args.push(current);\n  }\n\n  return args;\n}\n\n/**\n * Returns the appropriate command for running a package without installing it globally,\n * based on the selected package manager.\n *\n * @param packageManager - The selected package manager (e.g., 'npm', 'yarn', 'pnpm', 'bun').\n * @param commandWithArgs - The command to run, including arguments (e.g., \"prisma generate --schema=./prisma/schema.prisma\").\n * @returns The full command string (e.g., \"npx prisma generate --schema=./prisma/schema.prisma\").\n */\nexport function getPackageExecutionCommand(\n  packageManager: PackageManager | null | undefined,\n  commandWithArgs: string,\n) {\n  switch (packageManager) {\n    case \"pnpm\":\n      return `pnpm dlx ${commandWithArgs}`;\n    case \"bun\":\n      return `bunx ${commandWithArgs}`;\n    default:\n      return `npx ${commandWithArgs}`;\n  }\n}\n\n/**\n * Returns the command and arguments as an array for use with execa's $ template syntax.\n * This avoids the need for shell: true and provides better escaping.\n *\n * @param packageManager - The selected package manager (e.g., 'npm', 'yarn', 'pnpm', 'bun').\n * @param commandWithArgs - The command to run, including arguments (e.g., \"prisma generate\").\n * @returns An array of [command, ...args] (e.g., [\"npx\", \"prisma\", \"generate\"]).\n */\nexport function getPackageExecutionArgs(\n  packageManager: PackageManager | null | undefined,\n  commandWithArgs: string,\n): string[] {\n  const args = splitCommandArgs(commandWithArgs);\n  switch (packageManager) {\n    case \"pnpm\":\n      return [\"pnpm\", \"dlx\", ...args];\n    case \"bun\":\n      return [\"bunx\", ...args];\n    default:\n      return [\"npx\", ...args];\n  }\n}\n\n/**\n * Returns just the runner prefix as an array, for when you already have args built.\n * Use this when you have complex arguments that shouldn't be split by spaces.\n *\n * @param packageManager - The selected package manager.\n * @returns The runner prefix as an array (e.g., [\"npx\"] or [\"pnpm\", \"dlx\"]).\n *\n * @example\n * const prefix = getPackageRunnerPrefix(\"bun\");\n * const args = [\"@tauri-apps/cli@latest\", \"init\", \"--app-name=foo\"];\n * await $`${[...prefix, ...args]}`;\n */\nexport function getPackageRunnerPrefix(\n  packageManager: PackageManager | null | undefined,\n): string[] {\n  switch (packageManager) {\n    case \"pnpm\":\n      return [\"pnpm\", \"dlx\"];\n    case \"bun\":\n      return [\"bunx\"];\n    default:\n      return [\"npx\"];\n  }\n}\n"
  },
  {
    "path": "apps/cli/src/utils/project-directory.ts",
    "content": "import path from \"node:path\";\n\nimport { isCancel, log, select, spinner } from \"@clack/prompts\";\nimport { Result } from \"better-result\";\nimport fs from \"fs-extra\";\nimport pc from \"picocolors\";\n\nimport { getProjectName } from \"../prompts/project-name\";\nimport { isSilent } from \"./context\";\nimport { CLIError, UserCancelledError } from \"./errors\";\n\nexport async function handleDirectoryConflict(currentPathInput: string): Promise<{\n  finalPathInput: string;\n  shouldClearDirectory: boolean;\n}> {\n  while (true) {\n    const resolvedPath = path.resolve(process.cwd(), currentPathInput);\n    const dirExists = await fs.pathExists(resolvedPath);\n    const dirIsNotEmpty = dirExists && (await fs.readdir(resolvedPath)).length > 0;\n\n    if (!dirIsNotEmpty) {\n      return { finalPathInput: currentPathInput, shouldClearDirectory: false };\n    }\n\n    if (isSilent()) {\n      throw new CLIError({\n        message: `Directory \"${currentPathInput}\" already exists and is not empty. In silent mode, please provide a different project name or clear the directory manually.`,\n      });\n    }\n\n    log.warn(`Directory \"${pc.yellow(currentPathInput)}\" already exists and is not empty.`);\n\n    const action = await select<\"overwrite\" | \"merge\" | \"rename\" | \"cancel\">({\n      message: \"What would you like to do?\",\n      options: [\n        {\n          value: \"overwrite\",\n          label: \"Overwrite\",\n          hint: \"Empty the directory and create the project\",\n        },\n        {\n          value: \"merge\",\n          label: \"Merge\",\n          hint: \"Create project files inside, potentially overwriting conflicts\",\n        },\n        {\n          value: \"rename\",\n          label: \"Choose a different name/path\",\n          hint: \"Keep the existing directory and create a new one\",\n        },\n        { value: \"cancel\", label: \"Cancel\", hint: \"Abort the process\" },\n      ],\n      initialValue: \"rename\",\n    });\n\n    if (isCancel(action)) {\n      throw new UserCancelledError({ message: \"Operation cancelled.\" });\n    }\n\n    switch (action) {\n      case \"overwrite\":\n        return { finalPathInput: currentPathInput, shouldClearDirectory: true };\n      case \"merge\":\n        log.info(\n          `Proceeding into existing directory \"${pc.yellow(\n            currentPathInput,\n          )}\". Files may be overwritten.`,\n        );\n        return {\n          finalPathInput: currentPathInput,\n          shouldClearDirectory: false,\n        };\n      case \"rename\": {\n        log.info(\"Please choose a different project name or path.\");\n        const newPathInput = await getProjectName(undefined);\n        return await handleDirectoryConflict(newPathInput);\n      }\n      case \"cancel\":\n        throw new UserCancelledError({ message: \"Operation cancelled.\" });\n    }\n  }\n}\n\nexport async function setupProjectDirectory(\n  finalPathInput: string,\n  shouldClearDirectory: boolean,\n): Promise<{ finalResolvedPath: string; finalBaseName: string }> {\n  let finalResolvedPath: string;\n  let finalBaseName: string;\n\n  if (finalPathInput === \".\") {\n    finalResolvedPath = process.cwd();\n    finalBaseName = path.basename(finalResolvedPath);\n  } else {\n    finalResolvedPath = path.resolve(process.cwd(), finalPathInput);\n    finalBaseName = path.basename(finalResolvedPath);\n  }\n\n  if (shouldClearDirectory) {\n    const s = spinner();\n    s.start(`Clearing directory \"${finalResolvedPath}\"...`);\n\n    const clearResult = await Result.tryPromise({\n      try: () => fs.emptyDir(finalResolvedPath),\n      catch: (error) =>\n        new CLIError({\n          message: `Failed to clear directory \"${finalResolvedPath}\".`,\n          cause: error,\n        }),\n    });\n\n    if (clearResult.isErr()) {\n      s.stop(pc.red(`Failed to clear directory \"${finalResolvedPath}\".`));\n      throw clearResult.error;\n    }\n\n    s.stop(`Directory \"${finalResolvedPath}\" cleared.`);\n  } else {\n    await fs.ensureDir(finalResolvedPath);\n  }\n\n  return { finalResolvedPath, finalBaseName };\n}\n"
  },
  {
    "path": "apps/cli/src/utils/project-history.ts",
    "content": "import path from \"node:path\";\n\nimport { Result, TaggedError } from \"better-result\";\nimport envPaths from \"env-paths\";\nimport fs from \"fs-extra\";\n\nimport type { ProjectConfig } from \"../types\";\nimport { getLatestCLIVersion } from \"./get-latest-cli-version\";\n\nconst paths = envPaths(\"better-t-stack\", { suffix: \"\" });\nconst HISTORY_FILE = \"history.json\";\n\nexport class HistoryError extends TaggedError(\"HistoryError\")<{\n  message: string;\n  cause?: unknown;\n}>() {}\n\nexport type ProjectHistoryEntry = {\n  id: string;\n  projectName: string;\n  projectDir: string;\n  createdAt: string;\n  stack: {\n    frontend: string[];\n    backend: string;\n    database: string;\n    orm: string;\n    runtime: string;\n    auth: string;\n    payments: string;\n    api: string;\n    addons: string[];\n    examples: string[];\n    dbSetup: string;\n    packageManager: string;\n  };\n  cliVersion: string;\n  reproducibleCommand: string;\n};\n\ntype HistoryData = {\n  version: number;\n  entries: ProjectHistoryEntry[];\n};\n\nfunction getHistoryDir(): string {\n  return paths.data;\n}\n\nfunction getHistoryPath(): string {\n  return path.join(paths.data, HISTORY_FILE);\n}\n\nfunction generateId(): string {\n  return `${Date.now()}-${Math.random().toString(36).substring(2, 9)}`;\n}\n\nfunction emptyHistory(): HistoryData {\n  return { version: 1, entries: [] };\n}\n\nasync function ensureHistoryDir(): Promise<Result<void, HistoryError>> {\n  return Result.tryPromise({\n    try: async () => {\n      await fs.ensureDir(getHistoryDir());\n    },\n    catch: (e) =>\n      new HistoryError({\n        message: `Failed to create history directory: ${e instanceof Error ? e.message : String(e)}`,\n        cause: e,\n      }),\n  });\n}\n\nexport async function readHistory(): Promise<Result<HistoryData, HistoryError>> {\n  const historyPath = getHistoryPath();\n\n  const existsResult = await Result.tryPromise({\n    try: async () => await fs.pathExists(historyPath),\n    catch: (e) =>\n      new HistoryError({\n        message: `Failed to check history file: ${e instanceof Error ? e.message : String(e)}`,\n        cause: e,\n      }),\n  });\n\n  if (existsResult.isErr()) {\n    return existsResult;\n  }\n\n  if (!existsResult.value) {\n    return Result.ok(emptyHistory());\n  }\n\n  const readResult = await Result.tryPromise({\n    try: async () => (await fs.readJson(historyPath)) as HistoryData,\n    catch: (e) =>\n      new HistoryError({\n        message: `Failed to read history file: ${e instanceof Error ? e.message : String(e)}`,\n        cause: e,\n      }),\n  });\n\n  // If the file is corrupted/unreadable JSON, fall back to empty history.\n  if (readResult.isErr()) {\n    return Result.ok(emptyHistory());\n  }\n\n  return Result.ok(readResult.value);\n}\n\nasync function writeHistory(history: HistoryData): Promise<Result<void, HistoryError>> {\n  const ensureDirResult = await ensureHistoryDir();\n  if (ensureDirResult.isErr()) {\n    return ensureDirResult;\n  }\n\n  return Result.tryPromise({\n    try: async () => {\n      await fs.writeJson(getHistoryPath(), history, { spaces: 2 });\n    },\n    catch: (e) =>\n      new HistoryError({\n        message: `Failed to write history file: ${e instanceof Error ? e.message : String(e)}`,\n        cause: e,\n      }),\n  });\n}\n\nexport async function addToHistory(\n  config: ProjectConfig,\n  reproducibleCommand: string,\n): Promise<Result<void, HistoryError>> {\n  const historyResult = await readHistory();\n  if (historyResult.isErr()) {\n    return historyResult;\n  }\n  const history = historyResult.value;\n\n  const entry: ProjectHistoryEntry = {\n    id: generateId(),\n    projectName: config.projectName,\n    projectDir: config.projectDir,\n    createdAt: new Date().toISOString(),\n    stack: {\n      frontend: config.frontend,\n      backend: config.backend,\n      database: config.database,\n      orm: config.orm,\n      runtime: config.runtime,\n      auth: config.auth,\n      payments: config.payments,\n      api: config.api,\n      addons: config.addons,\n      examples: config.examples,\n      dbSetup: config.dbSetup,\n      packageManager: config.packageManager,\n    },\n    cliVersion: getLatestCLIVersion(),\n    reproducibleCommand,\n  };\n\n  // Add new entry at the beginning (newest first)\n  history.entries.unshift(entry);\n\n  // Keep only the last 100 entries to prevent file from growing too large\n  if (history.entries.length > 100) {\n    history.entries = history.entries.slice(0, 100);\n  }\n\n  return await writeHistory(history);\n}\n\nexport async function getHistory(limit = 10): Promise<Result<ProjectHistoryEntry[], HistoryError>> {\n  const historyResult = await readHistory();\n  if (historyResult.isErr()) {\n    return historyResult;\n  }\n  return Result.ok(historyResult.value.entries.slice(0, limit));\n}\n\nexport async function clearHistory(): Promise<Result<void, HistoryError>> {\n  const historyPath = getHistoryPath();\n\n  return Result.tryPromise({\n    try: async () => {\n      if (await fs.pathExists(historyPath)) {\n        await fs.remove(historyPath);\n      }\n    },\n    catch: (e) =>\n      new HistoryError({\n        message: `Failed to clear history: ${e instanceof Error ? e.message : String(e)}`,\n        cause: e,\n      }),\n  });\n}\n\nexport async function removeFromHistory(id: string): Promise<Result<boolean, HistoryError>> {\n  const historyResult = await readHistory();\n  if (historyResult.isErr()) {\n    return historyResult;\n  }\n\n  const history = historyResult.value;\n  const initialLength = history.entries.length;\n  history.entries = history.entries.filter((entry) => entry.id !== id);\n\n  if (history.entries.length < initialLength) {\n    const writeResult = await writeHistory(history);\n    if (writeResult.isErr()) {\n      return writeResult;\n    }\n    return Result.ok(true);\n  }\n\n  return Result.ok(false);\n}\n"
  },
  {
    "path": "apps/cli/src/utils/project-name-validation.ts",
    "content": "import path from \"node:path\";\n\nimport { Result } from \"better-result\";\n\nimport { ProjectNameSchema } from \"../types\";\nimport { ValidationError } from \"./errors\";\nimport { validateAgentSafePathInput } from \"./input-hardening\";\n\ntype ValidationResult<T> = Result<T, ValidationError>;\n\nexport function validateProjectName(name: string): ValidationResult<void> {\n  const hardeningResult = validateAgentSafePathInput(name, \"projectName\");\n  if (hardeningResult.isErr()) {\n    return Result.err(hardeningResult.error);\n  }\n\n  const result = ProjectNameSchema.safeParse(name);\n  if (!result.success) {\n    return Result.err(\n      new ValidationError({\n        field: \"projectName\",\n        value: name,\n        message: `Invalid project name: ${result.error.issues[0]?.message || \"Invalid project name\"}`,\n      }),\n    );\n  }\n  return Result.ok(undefined);\n}\n\nexport function extractAndValidateProjectName(\n  projectName?: string,\n  projectDirectory?: string,\n): ValidationResult<string> {\n  if (projectName) {\n    const projectNameInputResult = validateAgentSafePathInput(projectName, \"projectName\");\n    if (projectNameInputResult.isErr()) {\n      return Result.err(projectNameInputResult.error);\n    }\n  }\n\n  if (projectDirectory) {\n    const projectDirInputResult = validateAgentSafePathInput(projectDirectory, \"projectDirectory\");\n    if (projectDirInputResult.isErr()) {\n      return Result.err(projectDirInputResult.error);\n    }\n  }\n\n  const derivedName =\n    projectName ||\n    (projectDirectory ? path.basename(path.resolve(process.cwd(), projectDirectory)) : \"\");\n\n  if (!derivedName) {\n    return Result.ok(\"\");\n  }\n\n  const nameToValidate = projectName ? path.basename(projectName) : derivedName;\n\n  const validationResult = validateProjectName(nameToValidate);\n  if (validationResult.isErr()) {\n    return Result.err(validationResult.error);\n  }\n\n  return Result.ok(projectName || derivedName);\n}\n"
  },
  {
    "path": "apps/cli/src/utils/render-title.ts",
    "content": "import gradient from \"gradient-string\";\n\nexport const TITLE_TEXT = `\n ██████╗ ███████╗████████╗████████╗███████╗██████╗\n ██╔══██╗██╔════╝╚══██╔══╝╚══██╔══╝██╔════╝██╔══██╗\n ██████╔╝█████╗     ██║      ██║   █████╗  ██████╔╝\n ██╔══██╗██╔══╝     ██║      ██║   ██╔══╝  ██╔══██╗\n ██████╔╝███████╗   ██║      ██║   ███████╗██║  ██║\n ╚═════╝ ╚══════╝   ╚═╝      ╚═╝   ╚══════╝╚═╝  ╚═╝\n\n ████████╗    ███████╗████████╗ █████╗  ██████╗██╗  ██╗\n ╚══██╔══╝    ██╔════╝╚══██╔══╝██╔══██╗██╔════╝██║ ██╔╝\n    ██║       ███████╗   ██║   ███████║██║     █████╔╝\n    ██║       ╚════██║   ██║   ██╔══██║██║     ██╔═██╗\n    ██║       ███████║   ██║   ██║  ██║╚██████╗██║  ██╗\n    ╚═╝       ╚══════╝   ╚═╝   ╚═╝  ╚═╝ ╚═════╝╚═╝  ╚═╝\n `;\n\nconst catppuccinTheme = {\n  pink: \"#F5C2E7\",\n  mauve: \"#CBA6F7\",\n  red: \"#F38BA8\",\n  maroon: \"#E78284\",\n  peach: \"#FAB387\",\n  yellow: \"#F9E2AF\",\n  green: \"#A6E3A1\",\n  teal: \"#94E2D5\",\n  sky: \"#89DCEB\",\n  sapphire: \"#74C7EC\",\n  lavender: \"#B4BEFE\",\n};\n\nexport const renderTitle = () => {\n  const terminalWidth = process.stdout.columns || 80;\n  const titleLines = TITLE_TEXT.split(\"\\n\");\n  const titleWidth = Math.max(...titleLines.map((line) => line.length));\n\n  if (terminalWidth < titleWidth) {\n    const simplifiedTitle = `Better T Stack`;\n    console.log(gradient(Object.values(catppuccinTheme)).multiline(simplifiedTitle));\n  } else {\n    console.log(gradient(Object.values(catppuccinTheme)).multiline(TITLE_TEXT));\n  }\n};\n"
  },
  {
    "path": "apps/cli/src/utils/sponsors.ts",
    "content": "import { log, outro, spinner } from \"@clack/prompts\";\nimport { Result } from \"better-result\";\nimport pc from \"picocolors\";\nimport z from \"zod\";\n\nimport { CLIError } from \"./errors\";\nimport { cliConsola } from \"./terminal-output\";\n\nexport const SPONSORS_JSON_URL = \"https://sponsors.better-t-stack.dev/sponsors.json\";\nexport const GITHUB_SPONSOR_URL = \"https://github.com/sponsors/AmanVarshney01\";\n\nexport type SponsorSummary = {\n  total_sponsors: number;\n  total_lifetime_amount: number;\n  total_current_monthly: number;\n  special_sponsors: number;\n  current_sponsors: number;\n  past_sponsors: number;\n  backers: number;\n  top_sponsor?: {\n    name: string;\n    amount: number;\n  };\n};\n\nexport type Sponsor = {\n  name?: string;\n  githubId: string;\n  avatarUrl: string;\n  websiteUrl?: string;\n  githubUrl: string;\n  tierName?: string;\n  sinceWhen: string;\n  transactionCount: number;\n  totalProcessedAmount?: number;\n  formattedAmount?: string;\n};\n\nexport type SponsorEntry = {\n  generated_at: string;\n  summary: SponsorSummary;\n  specialSponsors: Sponsor[];\n  sponsors: Sponsor[];\n  pastSponsors: Sponsor[];\n  backers: Sponsor[];\n};\n\ntype FetchSponsorsOptions = {\n  url?: string;\n  withSpinner?: boolean;\n  timeoutMs?: number;\n};\n\nconst nullableString = z\n  .string()\n  .nullish()\n  .transform((value) => value ?? undefined);\nconst nullableNumber = z\n  .number()\n  .nullish()\n  .transform((value) => value ?? undefined);\n\nconst sponsorSchema = z.object({\n  name: nullableString,\n  githubId: z.string(),\n  avatarUrl: z.string(),\n  websiteUrl: nullableString,\n  githubUrl: z.string(),\n  tierName: nullableString,\n  sinceWhen: z.string(),\n  transactionCount: z.number(),\n  totalProcessedAmount: nullableNumber,\n  formattedAmount: nullableString,\n});\n\nconst sponsorSummarySchema = z.object({\n  total_sponsors: z.number(),\n  total_lifetime_amount: z.number(),\n  total_current_monthly: z.number(),\n  special_sponsors: z.number(),\n  current_sponsors: z.number(),\n  past_sponsors: z.number(),\n  backers: z.number(),\n  top_sponsor: z\n    .object({\n      name: z.string(),\n      amount: z.number(),\n    })\n    .nullish()\n    .transform((value) => value ?? undefined),\n});\n\nconst sponsorEntrySchema = z.object({\n  generated_at: z.string(),\n  summary: sponsorSummarySchema,\n  specialSponsors: z.array(sponsorSchema),\n  sponsors: z.array(sponsorSchema),\n  pastSponsors: z.array(sponsorSchema),\n  backers: z.array(sponsorSchema),\n});\n\nexport async function fetchSponsors(url: string = SPONSORS_JSON_URL) {\n  return fetchSponsorsData({ url, withSpinner: true });\n}\n\nexport async function fetchSponsorsQuietly({\n  url = SPONSORS_JSON_URL,\n  timeoutMs = 1500,\n}: Pick<FetchSponsorsOptions, \"url\" | \"timeoutMs\"> = {}) {\n  return fetchSponsorsData({ url, withSpinner: false, timeoutMs });\n}\n\nexport function displaySponsors(sponsors: SponsorEntry) {\n  const { total_sponsors } = sponsors.summary;\n  if (total_sponsors === 0) {\n    log.info(\"No sponsors found. You can be the first one! ✨\");\n    outro(pc.cyan(`Visit ${GITHUB_SPONSOR_URL} to become a sponsor.`));\n    return;\n  }\n\n  displaySponsorsBox(sponsors);\n\n  if (total_sponsors - sponsors.specialSponsors.length > 0) {\n    log.message(\n      pc.blue(`+${total_sponsors - sponsors.specialSponsors.length} more amazing sponsors.\\n`),\n    );\n  }\n  outro(pc.magenta(`Visit ${GITHUB_SPONSOR_URL} to become a sponsor.`));\n}\n\nfunction displaySponsorsBox(sponsors: SponsorEntry) {\n  if (sponsors.specialSponsors.length === 0) {\n    return;\n  }\n\n  let output = `${pc.bold(pc.cyan(\"-> Special Sponsors\"))}\\n\\n`;\n\n  sponsors.specialSponsors.forEach((sponsor: Sponsor, idx: number) => {\n    const displayName = sponsor.name ?? sponsor.githubId;\n    const tier = sponsor.tierName ? ` ${pc.yellow(`(${sponsor.tierName})`)}` : \"\";\n\n    output += `${pc.green(`• ${displayName}`)}${tier}\\n`;\n    output += `  ${pc.dim(\"GitHub:\")} https://github.com/${sponsor.githubId}\\n`;\n\n    const website = sponsor.websiteUrl ?? sponsor.githubUrl;\n    if (website) {\n      output += `  ${pc.dim(\"Website:\")} ${website}\\n`;\n    }\n\n    if (idx < sponsors.specialSponsors.length - 1) {\n      output += \"\\n\";\n    }\n  });\n\n  cliConsola.box(output);\n}\n\nexport function formatPostInstallSpecialSponsorsSection(sponsors: SponsorEntry): string {\n  if (sponsors.specialSponsors.length === 0) {\n    return \"\";\n  }\n\n  const sponsorTokens = sponsors.specialSponsors.map((sponsor) => {\n    const displayName = sponsor.name ?? sponsor.githubId;\n    return `• ${displayName}`;\n  });\n  const wrappedSponsorLines = wrapSponsorTokens(sponsorTokens, getPostInstallSponsorLineWidth());\n\n  let output = `${pc.bold(\"Special sponsors\")}\\n`;\n  wrappedSponsorLines.forEach((line) => {\n    output += `${line}\\n`;\n  });\n  return output.trimEnd();\n}\n\nfunction getPostInstallSponsorLineWidth(): number {\n  const terminalWidth = process.stdout.columns;\n  if (!terminalWidth || terminalWidth <= 0) {\n    return 72;\n  }\n\n  // Keep room for the surrounding box border/padding and avoid edge wrapping.\n  const availableWidth = Math.max(8, terminalWidth - 24);\n  return Math.min(72, availableWidth);\n}\n\nfunction wrapSponsorTokens(tokens: string[], maxLineWidth: number): string[] {\n  const lines: string[] = [];\n  const separator = \"   \";\n  let currentLine = \"\";\n\n  tokens.forEach((token) => {\n    const candidateLine = currentLine ? `${currentLine}${separator}${token}` : token;\n\n    if (candidateLine.length <= maxLineWidth) {\n      currentLine = candidateLine;\n      return;\n    }\n\n    if (currentLine) {\n      lines.push(currentLine);\n      currentLine = token;\n      return;\n    }\n\n    lines.push(token);\n  });\n\n  if (currentLine) {\n    lines.push(currentLine);\n  }\n\n  return lines;\n}\n\nasync function fetchSponsorsData({\n  url = SPONSORS_JSON_URL,\n  withSpinner = false,\n  timeoutMs,\n}: FetchSponsorsOptions): Promise<Result<SponsorEntry, CLIError>> {\n  const s = withSpinner ? spinner() : null;\n  if (s) {\n    s.start(\"Fetching sponsors…\");\n  }\n\n  const controller = timeoutMs ? new AbortController() : null;\n  const timeout = timeoutMs\n    ? setTimeout(() => {\n        controller?.abort();\n      }, timeoutMs)\n    : null;\n\n  try {\n    const response = await fetch(url, controller ? { signal: controller.signal } : undefined);\n    if (!response.ok) {\n      const message = `Failed to fetch sponsors: ${response.statusText || String(response.status)}`;\n      if (s) {\n        s.stop(pc.red(message));\n      }\n      return Result.err(new CLIError({ message }));\n    }\n\n    const rawSponsors = await response.json();\n    const parseResult = sponsorEntrySchema.safeParse(rawSponsors);\n    if (!parseResult.success) {\n      const firstIssue = parseResult.error.issues[0];\n      const path = firstIssue?.path?.join(\".\") || \"unknown\";\n      const message = `Failed to fetch sponsors: invalid response format at \"${path}\"`;\n      if (s) {\n        s.stop(pc.red(message));\n      }\n      return Result.err(new CLIError({ message, cause: parseResult.error }));\n    }\n\n    if (s) {\n      s.stop(\"Sponsors fetched successfully!\");\n    }\n\n    return Result.ok(parseResult.data);\n  } catch (error) {\n    const normalizedError = normalizeSponsorFetchError(error);\n    if (s) {\n      s.stop(pc.red(normalizedError.message));\n    }\n    return Result.err(normalizedError);\n  } finally {\n    if (timeout) {\n      clearTimeout(timeout);\n    }\n  }\n}\n\nfunction normalizeSponsorFetchError(error: unknown): CLIError {\n  if (error instanceof Error && error.name === \"AbortError\") {\n    return new CLIError({\n      message: \"Failed to fetch sponsors: request timed out\",\n      cause: error,\n    });\n  }\n\n  if (CLIError.is(error)) {\n    return error;\n  }\n\n  if (error instanceof Error) {\n    return new CLIError({\n      message: error.message.startsWith(\"Failed to fetch sponsors:\")\n        ? error.message\n        : `Failed to fetch sponsors: ${error.message}`,\n      cause: error,\n    });\n  }\n\n  return new CLIError({\n    message: `Failed to fetch sponsors: ${String(error)}`,\n    cause: error,\n  });\n}\n"
  },
  {
    "path": "apps/cli/src/utils/telemetry.ts",
    "content": "/**\n * Returns true if telemetry/analytics should be enabled, false otherwise.\n *\n * - If BTS_TELEMETRY_DISABLED is present and \"1\", disables analytics.\n * - Otherwise, BTS_TELEMETRY: \"0\" disables, \"1\" enables (default: enabled).\n */\nexport function isTelemetryEnabled() {\n  const BTS_TELEMETRY_DISABLED = process.env.BTS_TELEMETRY_DISABLED;\n  const BTS_TELEMETRY = process.env.BTS_TELEMETRY;\n\n  if (BTS_TELEMETRY_DISABLED !== undefined) {\n    return BTS_TELEMETRY_DISABLED !== \"1\";\n  }\n  if (BTS_TELEMETRY !== undefined) {\n    return BTS_TELEMETRY === \"1\";\n  }\n  // Default: enabled\n  return true;\n}\n"
  },
  {
    "path": "apps/cli/src/utils/templates.ts",
    "content": "import type { CreateInput, Template } from \"../types\";\n\nexport const TEMPLATE_PRESETS: Record<Template, CreateInput | null> = {\n  mern: {\n    database: \"mongodb\",\n    orm: \"mongoose\",\n    backend: \"express\",\n    runtime: \"node\",\n    frontend: [\"react-router\"],\n    api: \"orpc\",\n    auth: \"better-auth\",\n    payments: \"none\",\n    addons: [\"turborepo\"],\n    examples: [\"todo\"],\n    dbSetup: \"mongodb-atlas\",\n    webDeploy: \"none\",\n    serverDeploy: \"none\",\n  },\n  pern: {\n    database: \"postgres\",\n    orm: \"drizzle\",\n    backend: \"express\",\n    runtime: \"node\",\n    frontend: [\"tanstack-router\"],\n    api: \"trpc\",\n    auth: \"better-auth\",\n    payments: \"none\",\n    addons: [\"turborepo\"],\n    examples: [\"todo\"],\n    dbSetup: \"none\",\n    webDeploy: \"none\",\n    serverDeploy: \"none\",\n  },\n  t3: {\n    database: \"postgres\",\n    orm: \"prisma\",\n    backend: \"self\",\n    runtime: \"none\",\n    frontend: [\"next\"],\n    api: \"trpc\",\n    auth: \"better-auth\",\n    payments: \"none\",\n    addons: [\"biome\", \"turborepo\"],\n    examples: [\"none\"],\n    dbSetup: \"none\",\n    webDeploy: \"none\",\n    serverDeploy: \"none\",\n  },\n  uniwind: {\n    database: \"none\",\n    orm: \"none\",\n    backend: \"none\",\n    runtime: \"none\",\n    frontend: [\"native-uniwind\"],\n    api: \"none\",\n    auth: \"none\",\n    payments: \"none\",\n    addons: [\"none\"],\n    examples: [\"none\"],\n    dbSetup: \"none\",\n    webDeploy: \"none\",\n    serverDeploy: \"none\",\n  },\n  none: null,\n};\n\nexport function getTemplateConfig(template: Template) {\n  if (template === \"none\" || !template) {\n    return null;\n  }\n\n  const config = TEMPLATE_PRESETS[template];\n  if (!config) {\n    throw new Error(`Unknown template: ${template}`);\n  }\n\n  return config;\n}\n\nexport function getTemplateDescription(template: Template) {\n  const descriptions: Record<Template, string> = {\n    mern: \"MongoDB + Express + React + Node.js - Classic MERN stack\",\n    pern: \"PostgreSQL + Express + React + Node.js - Popular PERN stack\",\n    t3: \"T3 Stack - Next.js + tRPC + Prisma + PostgreSQL + Better Auth\",\n    uniwind: \"Expo + Uniwind native app with no backend services\",\n    none: \"No template - Full customization\",\n  };\n\n  return descriptions[template] || \"\";\n}\n\nexport function listAvailableTemplates() {\n  return Object.keys(TEMPLATE_PRESETS).filter((t) => t !== \"none\") as Template[];\n}\n"
  },
  {
    "path": "apps/cli/src/utils/terminal-output.ts",
    "content": "import { log, spinner } from \"@clack/prompts\";\nimport { consola, createConsola } from \"consola\";\n\nimport { isSilent } from \"./context\";\n\ntype SpinnerLike = {\n  start(message: string): void;\n  stop(message?: string): void;\n  message(message: string): void;\n};\n\nconst noopSpinner: SpinnerLike = {\n  start() {},\n  stop() {},\n  message() {},\n};\n\nexport function createSpinner(): SpinnerLike {\n  return isSilent() ? noopSpinner : spinner();\n}\n\nconst baseConsola = createConsola({\n  ...consola.options,\n  formatOptions: {\n    ...consola.options.formatOptions,\n    date: false,\n  },\n});\n\nexport const cliLog = {\n  info(message: string) {\n    if (!isSilent()) log.info(message);\n  },\n  warn(message: string) {\n    if (!isSilent()) log.warn(message);\n  },\n  success(message: string) {\n    if (!isSilent()) log.success(message);\n  },\n  error(message: string) {\n    if (!isSilent()) log.error(message);\n  },\n  message(message: string) {\n    if (!isSilent()) log.message(message);\n  },\n};\n\nexport const cliConsola = {\n  error(message: string) {\n    if (!isSilent()) baseConsola.error(message);\n  },\n  warn(message: string) {\n    if (!isSilent()) baseConsola.warn(message);\n  },\n  info(message: string) {\n    if (!isSilent()) baseConsola.info(message);\n  },\n  fatal(message: string) {\n    if (!isSilent()) baseConsola.fatal(message);\n  },\n  box(message: string) {\n    if (!isSilent()) baseConsola.box(message);\n  },\n};\n"
  },
  {
    "path": "apps/cli/src/utils/ts-morph.ts",
    "content": "import {\n  type ArrayLiteralExpression,\n  IndentationText,\n  type ObjectLiteralExpression,\n  Project,\n  QuoteKind,\n  SyntaxKind,\n} from \"ts-morph\";\n\nexport const tsProject = new Project({\n  useInMemoryFileSystem: false,\n  skipAddingFilesFromTsConfig: true,\n  manipulationSettings: {\n    quoteKind: QuoteKind.Single,\n    indentationText: IndentationText.TwoSpaces,\n  },\n});\n\nexport function ensureArrayProperty(obj: ObjectLiteralExpression, name: string) {\n  return (obj.getProperty(name)?.getFirstDescendantByKind(SyntaxKind.ArrayLiteralExpression) ??\n    obj\n      .addPropertyAssignment({ name, initializer: \"[]\" })\n      .getFirstDescendantByKindOrThrow(\n        SyntaxKind.ArrayLiteralExpression,\n      )) as ArrayLiteralExpression;\n}\n"
  },
  {
    "path": "apps/cli/src/validation.ts",
    "content": "import { Result } from \"better-result\";\n\nimport type { CLIInput, ProjectConfig } from \"./types\";\nimport { getProvidedFlags, processFlags, validateArrayOptions } from \"./utils/config-processing\";\nimport { validateConfigForProgrammaticUse, validateFullConfig } from \"./utils/config-validation\";\nimport { ValidationError } from \"./utils/errors\";\nimport { extractAndValidateProjectName } from \"./utils/project-name-validation\";\n\ntype ValidationResult<T> = Result<T, ValidationError>;\n\nconst CORE_STACK_FLAGS = new Set([\n  \"database\",\n  \"orm\",\n  \"backend\",\n  \"runtime\",\n  \"frontend\",\n  \"addons\",\n  \"examples\",\n  \"auth\",\n  \"dbSetup\",\n  \"payments\",\n  \"api\",\n  \"webDeploy\",\n  \"serverDeploy\",\n]);\n\nfunction validateYesFlagCombination(\n  options: CLIInput,\n  providedFlags: Set<string>,\n): ValidationResult<void> {\n  if (!options.yes) return Result.ok(undefined);\n\n  if (options.template && options.template !== \"none\") {\n    return Result.ok(undefined);\n  }\n\n  const coreStackFlagsProvided = Array.from(providedFlags).filter((flag) =>\n    CORE_STACK_FLAGS.has(flag),\n  );\n\n  if (coreStackFlagsProvided.length > 0) {\n    return Result.err(\n      new ValidationError({\n        message:\n          `Cannot combine --yes with core stack configuration flags: ${coreStackFlagsProvided.map((f) => `--${f}`).join(\", \")}. ` +\n          \"The --yes flag uses default configuration. Remove these flags or use --yes without them.\",\n      }),\n    );\n  }\n\n  return Result.ok(undefined);\n}\n\nexport function processAndValidateFlags(\n  options: CLIInput,\n  providedFlags: Set<string>,\n  projectName?: string,\n): ValidationResult<Partial<ProjectConfig>> {\n  if (options.yolo) {\n    const cfg = processFlags(options, projectName);\n    const validatedProjectNameResult = extractAndValidateProjectName(\n      projectName,\n      options.projectDirectory,\n    );\n    if (validatedProjectNameResult.isOk() && validatedProjectNameResult.value) {\n      cfg.projectName = validatedProjectNameResult.value;\n    }\n    return Result.ok(cfg);\n  }\n\n  const yesFlagResult = validateYesFlagCombination(options, providedFlags);\n  if (yesFlagResult.isErr()) {\n    return Result.err(yesFlagResult.error);\n  }\n\n  const arrayOptionsResult = validateArrayOptions(options);\n  if (arrayOptionsResult.isErr()) {\n    return Result.err(arrayOptionsResult.error);\n  }\n\n  const config = processFlags(options, projectName);\n\n  const validatedProjectNameResult = extractAndValidateProjectName(\n    projectName,\n    options.projectDirectory,\n  );\n  if (validatedProjectNameResult.isErr()) {\n    return Result.err(validatedProjectNameResult.error);\n  }\n  if (validatedProjectNameResult.value) {\n    config.projectName = validatedProjectNameResult.value;\n  }\n\n  const fullConfigResult = validateFullConfig(config, providedFlags, options);\n  if (fullConfigResult.isErr()) {\n    return Result.err(fullConfigResult.error);\n  }\n\n  return Result.ok(config);\n}\n\nexport function processProvidedFlagsWithoutValidation(\n  options: CLIInput,\n  projectName?: string,\n): ValidationResult<Partial<ProjectConfig>> {\n  if (!options.yolo) {\n    const providedFlags = getProvidedFlags(options);\n    const yesFlagResult = validateYesFlagCombination(options, providedFlags);\n    if (yesFlagResult.isErr()) {\n      return Result.err(yesFlagResult.error);\n    }\n  }\n\n  const config = processFlags(options, projectName);\n\n  const validatedProjectNameResult = extractAndValidateProjectName(\n    projectName,\n    options.projectDirectory,\n  );\n  if (validatedProjectNameResult.isErr()) {\n    return Result.err(validatedProjectNameResult.error);\n  }\n  if (validatedProjectNameResult.value) {\n    config.projectName = validatedProjectNameResult.value;\n  }\n\n  return Result.ok(config);\n}\n\nexport function validateConfigCompatibility(\n  config: Partial<ProjectConfig>,\n  providedFlags?: Set<string>,\n  options?: CLIInput,\n): ValidationResult<void> {\n  if (options?.yolo) return Result.ok(undefined);\n  if (options && providedFlags) {\n    return validateFullConfig(config, providedFlags, options);\n  } else {\n    return validateConfigForProgrammaticUse(config);\n  }\n}\n\nexport { getProvidedFlags };\n"
  },
  {
    "path": "apps/cli/src/virtual.ts",
    "content": "/**\n * Virtual filesystem export for web preview\n * Re-exports from @better-t-stack/template-generator for browser-compatible usage\n */\n\n// Re-export everything from template-generator for web/programmatic usage\nexport {\n  // Generator functions\n  generate,\n  // Virtual file system types\n  VirtualFileSystem,\n  type VirtualFileTree,\n  type VirtualFile,\n  type VirtualDirectory,\n  type VirtualNode,\n  // Generator types\n  type GeneratorOptions,\n  // Error types\n  GeneratorError,\n  // Embedded templates for browser usage\n  EMBEDDED_TEMPLATES,\n  TEMPLATE_COUNT,\n} from \"@better-t-stack/template-generator\";\n\nexport { Result } from \"better-result\";\n\n// Re-export types needed for configuration options\nexport type {\n  Database,\n  ORM,\n  Backend,\n  Runtime,\n  Frontend,\n  Addons,\n  Examples,\n  PackageManager,\n  DatabaseSetup,\n  API,\n  Auth,\n  Payments,\n  WebDeploy,\n  ServerDeploy,\n  ProjectConfig,\n} from \"@better-t-stack/types\";\n"
  },
  {
    "path": "apps/cli/test/add-handler.test.ts",
    "content": "import { describe, expect, it } from \"bun:test\";\nimport { mkdir } from \"node:fs/promises\";\nimport { join } from \"node:path\";\n\nimport { add } from \"../src/index\";\nimport { SMOKE_DIR } from \"./setup\";\n\ndescribe(\"add()\", () => {\n  it(\"returns an error in silent mode instead of exiting when the project config is missing\", async () => {\n    const projectDir = join(SMOKE_DIR, \"missing-bts-config\");\n    await mkdir(projectDir, { recursive: true });\n\n    const result = await add({\n      projectDir,\n      addons: [\"biome\"],\n      install: false,\n    });\n\n    expect(result).toBeDefined();\n    expect(result?.success).toBe(false);\n    expect(result?.error).toContain(\"No Better-T-Stack project found\");\n  });\n});\n"
  },
  {
    "path": "apps/cli/test/addon-options.test.ts",
    "content": "import { beforeEach, describe, expect, it } from \"bun:test\";\nimport path from \"node:path\";\n\nimport fs from \"fs-extra\";\n\nimport { add, create } from \"../src/index\";\nimport { readBtsConfig } from \"../src/utils/bts-config\";\n\nconst SMOKE_DIR_PATH = path.join(import.meta.dir, \"..\", \".smoke\");\n\ndescribe(\"Addon options\", () => {\n  beforeEach(() => {\n    process.env.BTS_SKIP_EXTERNAL_COMMANDS = \"1\";\n    process.env.BTS_TEST_MODE = \"1\";\n  });\n\n  it(\"persists addonOptions during create and keeps reproducible command on normal flags\", async () => {\n    const projectPath = path.join(SMOKE_DIR_PATH, \"addon-options-create\");\n    await fs.remove(projectPath);\n\n    const addonOptions = {\n      wxt: { template: \"react\" as const, devPort: 5555 },\n      opentui: { template: \"react\" as const },\n      fumadocs: { template: \"next-mdx\" as const, devPort: 4000 },\n      mcp: {\n        scope: \"project\" as const,\n        servers: [\"context7\"] as const,\n        agents: [\"cursor\", \"codex\"] as const,\n      },\n      skills: {\n        scope: \"project\" as const,\n        agents: [\"cursor\", \"codex\"] as const,\n        selections: [\n          {\n            source: \"vercel-labs/agent-skills\" as const,\n            skills: [\"web-design-guidelines\"],\n          },\n        ],\n      },\n      ultracite: {\n        linter: \"biome\" as const,\n        editors: [\"vscode\", \"cursor\"] as const,\n        agents: [\"claude\", \"codex\"] as const,\n        hooks: [\"claude\"] as const,\n      },\n    };\n\n    const result = await create(projectPath, {\n      frontend: [\"tanstack-router\"],\n      backend: \"hono\",\n      runtime: \"bun\",\n      database: \"sqlite\",\n      orm: \"drizzle\",\n      auth: \"none\",\n      payments: \"none\",\n      api: \"trpc\",\n      addons: [\"wxt\", \"opentui\", \"fumadocs\", \"mcp\", \"skills\", \"ultracite\"],\n      examples: [\"none\"],\n      dbSetup: \"none\",\n      webDeploy: \"none\",\n      serverDeploy: \"none\",\n      install: false,\n      addonOptions,\n    });\n\n    expect(result.isOk()).toBe(true);\n    if (result.isErr()) return;\n\n    expect(result.value.projectConfig.addonOptions).toEqual(addonOptions);\n    expect(result.value.reproducibleCommand).toContain(\"--frontend tanstack-router\");\n    expect(result.value.reproducibleCommand).toContain(\n      \"--addons wxt opentui fumadocs mcp skills ultracite\",\n    );\n    expect(result.value.reproducibleCommand).not.toContain(\"create-json --input\");\n\n    const btsConfig = await readBtsConfig(projectPath);\n    expect(btsConfig?.addonOptions).toEqual(addonOptions);\n  });\n\n  it(\"persists addonOptions during add\", async () => {\n    const projectPath = path.join(SMOKE_DIR_PATH, \"addon-options-add\");\n    await fs.remove(projectPath);\n\n    const createResult = await create(projectPath, {\n      yes: true,\n      install: false,\n      disableAnalytics: true,\n    });\n\n    expect(createResult.isOk()).toBe(true);\n    if (createResult.isErr()) return;\n\n    const addonOptions = {\n      wxt: { template: \"react\" as const },\n      mcp: {\n        scope: \"project\" as const,\n        servers: [\"context7\"] as const,\n        agents: [\"cursor\"] as const,\n      },\n    };\n\n    const addResult = await add({\n      projectDir: projectPath,\n      addons: [\"wxt\", \"mcp\"],\n      addonOptions,\n      install: false,\n      packageManager: \"bun\",\n    });\n\n    expect(addResult?.success).toBe(true);\n\n    const btsConfig = await readBtsConfig(projectPath);\n    expect(btsConfig?.addonOptions).toEqual(addonOptions);\n    expect(btsConfig?.addons).toEqual(expect.arrayContaining([\"turborepo\", \"wxt\", \"mcp\"]));\n  });\n\n  it(\"deep merges nested addonOptions during add\", async () => {\n    const projectPath = path.join(SMOKE_DIR_PATH, \"addon-options-deep-merge\");\n    await fs.remove(projectPath);\n\n    const createResult = await create(projectPath, {\n      yes: true,\n      install: false,\n      disableAnalytics: true,\n    });\n\n    expect(createResult.isOk()).toBe(true);\n    if (createResult.isErr()) return;\n\n    const firstAddResult = await add({\n      projectDir: projectPath,\n      addons: [\"mcp\"],\n      addonOptions: {\n        mcp: {\n          scope: \"project\",\n          servers: [\"context7\"],\n        },\n      },\n      install: false,\n      packageManager: \"bun\",\n    });\n\n    expect(firstAddResult?.success).toBe(true);\n\n    const secondAddResult = await add({\n      projectDir: projectPath,\n      addons: [\"wxt\"],\n      addonOptions: {\n        mcp: {\n          agents: [\"codex\"],\n        },\n        wxt: {\n          template: \"react\",\n        },\n      },\n      install: false,\n      packageManager: \"bun\",\n    });\n\n    expect(secondAddResult?.success).toBe(true);\n\n    const btsConfig = await readBtsConfig(projectPath);\n    expect(btsConfig?.addonOptions).toEqual({\n      mcp: {\n        scope: \"project\",\n        servers: [\"context7\"],\n        agents: [\"codex\"],\n      },\n      wxt: {\n        template: \"react\",\n      },\n    });\n  });\n});\n"
  },
  {
    "path": "apps/cli/test/addon-setup-regressions.test.ts",
    "content": "import { describe, expect, it } from \"bun:test\";\nimport path from \"node:path\";\n\nimport fs from \"fs-extra\";\n\nimport { setupMcp, getRecommendedMcpServers } from \"../src/helpers/addons/mcp-setup\";\nimport { setupSkills } from \"../src/helpers/addons/skills-setup\";\nimport type { ProjectConfig } from \"../src/types\";\nimport { runWithContextAsync } from \"../src/utils/context\";\nimport { SMOKE_DIR } from \"./setup\";\n\nfunction createProjectConfig(overrides: Partial<ProjectConfig> = {}): ProjectConfig {\n  return {\n    projectName: \"test-app\",\n    projectDir: path.join(SMOKE_DIR, \"addon-setup-regressions\"),\n    relativePath: \".\",\n    database: \"sqlite\",\n    orm: \"drizzle\",\n    backend: \"hono\",\n    runtime: \"bun\",\n    frontend: [\"tanstack-router\"],\n    addons: [\"none\"],\n    examples: [\"none\"],\n    auth: \"none\",\n    payments: \"none\",\n    git: false,\n    packageManager: \"bun\",\n    install: false,\n    dbSetup: \"none\",\n    api: \"trpc\",\n    webDeploy: \"none\",\n    serverDeploy: \"none\",\n    ...overrides,\n  };\n}\n\nasync function writeFakeBunx(binDir: string, markerFile: string, exitCode = 99) {\n  const bunxPath = path.join(binDir, \"bunx\");\n  await fs.ensureDir(binDir);\n  await fs.writeFile(\n    bunxPath,\n    `#!/bin/sh\nprintf '%s\\n' \"$*\" >> \"${markerFile}\"\nexit ${exitCode}\n`,\n  );\n  await fs.chmod(bunxPath, 0o755);\n}\n\nasync function runWithFakeBunx<T>(\n  projectDir: string,\n  callback: () => Promise<T>,\n  exitCode = 99,\n): Promise<{ markerFile: string; result: T }> {\n  const binDir = path.join(projectDir, \".fake-bin\");\n  const markerFile = path.join(projectDir, \"runner.log\");\n  await fs.ensureDir(projectDir);\n  await writeFakeBunx(binDir, markerFile, exitCode);\n\n  const previousPath = process.env.PATH;\n  const previousSkipExternal = process.env.BTS_SKIP_EXTERNAL_COMMANDS;\n  const previousTestMode = process.env.BTS_TEST_MODE;\n\n  process.env.PATH = `${binDir}${path.delimiter}${previousPath ?? \"\"}`;\n  delete process.env.BTS_SKIP_EXTERNAL_COMMANDS;\n  delete process.env.BTS_TEST_MODE;\n\n  try {\n    const result = await callback();\n    return { markerFile, result };\n  } finally {\n    if (previousPath === undefined) {\n      delete process.env.PATH;\n    } else {\n      process.env.PATH = previousPath;\n    }\n\n    if (previousSkipExternal === undefined) {\n      delete process.env.BTS_SKIP_EXTERNAL_COMMANDS;\n    } else {\n      process.env.BTS_SKIP_EXTERNAL_COMMANDS = previousSkipExternal;\n    }\n\n    if (previousTestMode === undefined) {\n      delete process.env.BTS_TEST_MODE;\n    } else {\n      process.env.BTS_TEST_MODE = previousTestMode;\n    }\n  }\n}\n\ndescribe(\"Addon setup regressions\", () => {\n  it(\"uses a package execution command for the Better T Stack MCP server target\", () => {\n    const servers = getRecommendedMcpServers(createProjectConfig(), \"project\");\n    const betterTStackServer = servers.find((server) => server.key === \"better-t-stack\");\n\n    expect(betterTStackServer?.target).toBe(\"bunx create-better-t-stack@latest mcp\");\n  });\n\n  it(\"preserves explicit empty MCP selections in silent mode\", async () => {\n    const projectDir = path.join(SMOKE_DIR, \"mcp-explicit-empty\");\n    await fs.remove(projectDir);\n\n    const config = createProjectConfig({\n      projectDir,\n      addons: [\"mcp\"],\n      addonOptions: {\n        mcp: {\n          scope: \"project\",\n          servers: [],\n          agents: [],\n        },\n      },\n    });\n\n    const { markerFile, result } = await runWithFakeBunx(projectDir, () =>\n      runWithContextAsync({ silent: true }, () => setupMcp(config)),\n    );\n\n    expect(result.isOk()).toBe(true);\n    expect(await fs.pathExists(markerFile)).toBe(false);\n  });\n\n  it(\"installs explicitly configured MCP servers even when they are not recommended\", async () => {\n    const projectDir = path.join(SMOKE_DIR, \"mcp-nonrecommended-server\");\n    await fs.remove(projectDir);\n\n    const config = createProjectConfig({\n      projectDir,\n      addons: [\"mcp\"],\n      addonOptions: {\n        mcp: {\n          scope: \"project\",\n          servers: [\"next-devtools\"],\n          agents: [\"cursor\"],\n        },\n      },\n    });\n\n    const { markerFile, result } = await runWithFakeBunx(\n      projectDir,\n      () => runWithContextAsync({ silent: true }, () => setupMcp(config)),\n      0,\n    );\n\n    expect(result.isOk()).toBe(true);\n    expect(await fs.readFile(markerFile, \"utf8\")).toContain(\"--name next-devtools\");\n  });\n\n  it(\"returns an error when every requested MCP install fails\", async () => {\n    const projectDir = path.join(SMOKE_DIR, \"mcp-all-installs-fail\");\n    await fs.remove(projectDir);\n\n    const config = createProjectConfig({\n      projectDir,\n      addons: [\"mcp\"],\n      addonOptions: {\n        mcp: {\n          scope: \"project\",\n          servers: [\"context7\"],\n          agents: [\"cursor\"],\n        },\n      },\n    });\n\n    const { markerFile, result } = await runWithFakeBunx(projectDir, () =>\n      runWithContextAsync({ silent: true }, () => setupMcp(config)),\n    );\n\n    expect(result.isErr()).toBe(true);\n    expect(await fs.readFile(markerFile, \"utf8\")).toContain(\"context7\");\n  });\n\n  it(\"preserves an explicit empty skills agent list in silent mode\", async () => {\n    const projectDir = path.join(SMOKE_DIR, \"skills-explicit-empty-agents\");\n    await fs.remove(projectDir);\n\n    const config = createProjectConfig({\n      projectDir,\n      frontend: [\"next\"],\n      addons: [\"skills\"],\n      addonOptions: {\n        skills: {\n          scope: \"project\",\n          agents: [],\n          selections: [\n            {\n              source: \"vercel-labs/agent-skills\",\n              skills: [\"web-design-guidelines\"],\n            },\n          ],\n        },\n      },\n    });\n\n    const { markerFile, result } = await runWithFakeBunx(projectDir, () =>\n      runWithContextAsync({ silent: true }, () => setupSkills(config)),\n    );\n\n    expect(result.isOk()).toBe(true);\n    expect(await fs.pathExists(markerFile)).toBe(false);\n  });\n\n  it(\"uses persisted skills options and preserves explicit non-recommended selections\", async () => {\n    const projectDir = path.join(SMOKE_DIR, \"skills-persisted-selections\");\n    await fs.remove(projectDir);\n    await fs.ensureDir(projectDir);\n    await fs.writeFile(\n      path.join(projectDir, \"bts.jsonc\"),\n      JSON.stringify({\n        version: \"0.0.0-test\",\n        createdAt: new Date(0).toISOString(),\n        projectName: \"test-app\",\n        database: \"sqlite\",\n        orm: \"drizzle\",\n        backend: \"hono\",\n        runtime: \"bun\",\n        frontend: [\"tanstack-router\"],\n        addons: [\"skills\"],\n        examples: [\"none\"],\n        auth: \"none\",\n        payments: \"none\",\n        packageManager: \"bun\",\n        dbSetup: \"none\",\n        api: \"trpc\",\n        webDeploy: \"none\",\n        serverDeploy: \"none\",\n        addonOptions: {\n          skills: {\n            scope: \"project\",\n            agents: [\"codex\"],\n            selections: [\n              {\n                source: \"vercel/turborepo\",\n                skills: [\"turborepo\"],\n              },\n            ],\n          },\n        },\n      }),\n    );\n\n    const config = createProjectConfig({\n      projectDir,\n      frontend: [\"tanstack-router\"],\n      addons: [\"skills\"],\n    });\n\n    const { markerFile, result } = await runWithFakeBunx(\n      projectDir,\n      () => runWithContextAsync({ silent: true }, () => setupSkills(config)),\n      0,\n    );\n\n    expect(result.isOk()).toBe(true);\n    const commandLog = await fs.readFile(markerFile, \"utf8\");\n    expect(commandLog).toContain(\"skills@latest add vercel/turborepo\");\n    expect(commandLog).toContain(\"--agent codex\");\n    expect(commandLog).toContain(\"--skill turborepo\");\n  });\n\n  it(\"recommends evlog skills when evlog addon is selected\", async () => {\n    const projectDir = path.join(SMOKE_DIR, \"skills-evlog-recommended\");\n    await fs.remove(projectDir);\n\n    const config = createProjectConfig({\n      projectDir,\n      addons: [\"skills\", \"evlog\"],\n      addonOptions: {\n        skills: {\n          scope: \"project\",\n          agents: [\"codex\"],\n        },\n      },\n    });\n\n    const { markerFile, result } = await runWithFakeBunx(\n      projectDir,\n      () => runWithContextAsync({ silent: true }, () => setupSkills(config)),\n      0,\n    );\n\n    expect(result.isOk()).toBe(true);\n    const commandLog = await fs.readFile(markerFile, \"utf8\");\n    expect(commandLog).toContain(\"skills@latest add https://www.evlog.dev\");\n    expect(commandLog).toContain(\"--skill review-logging-patterns analyze-logs\");\n    expect(commandLog).toContain(\"--agent codex\");\n  });\n\n  it(\"does not install upgrade skills from the curated skills addon\", async () => {\n    const projectDir = path.join(SMOKE_DIR, \"skills-no-upgrade-skills\");\n    await fs.remove(projectDir);\n\n    const config = createProjectConfig({\n      projectDir,\n      frontend: [\"native-bare\"],\n      addons: [\"skills\"],\n      addonOptions: {\n        skills: {\n          scope: \"project\",\n          agents: [\"codex\"],\n        },\n      },\n    });\n\n    const { markerFile, result } = await runWithFakeBunx(\n      projectDir,\n      () => runWithContextAsync({ silent: true }, () => setupSkills(config)),\n      0,\n    );\n\n    expect(result.isOk()).toBe(true);\n    const commandLog = await fs.readFile(markerFile, \"utf8\");\n    expect(commandLog).toContain(\"skills@latest add expo/skills\");\n    expect(commandLog).not.toContain(\"upgrading-expo\");\n    expect(commandLog).not.toContain(\"upgrade\");\n  });\n});\n"
  },
  {
    "path": "apps/cli/test/addons.test.ts",
    "content": "import { describe, expect, it } from \"bun:test\";\nimport { existsSync } from \"node:fs\";\nimport { readdir, readFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\n\nimport {\n  DiagnosticCategory,\n  flattenDiagnosticMessageText,\n  ModuleKind,\n  ScriptTarget,\n  transpileModule,\n} from \"typescript\";\n\nimport { add, type Addons, type Backend, type Frontend } from \"../src\";\nimport { getCompatibleAddons } from \"../src/utils/compatibility-rules\";\nimport { expectError, expectSuccess, runTRPCTest, type TestConfig } from \"./test-utils\";\n\nasync function readSourceFiles(dir: string): Promise<{ path: string; content: string }[]> {\n  if (!existsSync(dir)) return [];\n\n  const entries = await readdir(dir, { withFileTypes: true });\n  const files = await Promise.all(\n    entries.map(async (entry) => {\n      const entryPath = join(dir, entry.name);\n      if (entry.isDirectory()) return readSourceFiles(entryPath);\n      if (!/\\.(?:cjs|js|mjs|ts|tsx|vue)$/.test(entry.name)) return [];\n      return [{ path: entryPath, content: await readFile(entryPath, \"utf-8\") }];\n    }),\n  );\n\n  return files.flat();\n}\n\nfunction expectParseableTypeScript(content: string) {\n  const diagnostics =\n    transpileModule(content, {\n      compilerOptions: {\n        module: ModuleKind.ESNext,\n        target: ScriptTarget.ESNext,\n      },\n      reportDiagnostics: true,\n    }).diagnostics?.filter((diagnostic) => diagnostic.category === DiagnosticCategory.Error) ?? [];\n\n  expect(\n    diagnostics.map((diagnostic) => flattenDiagnosticMessageText(diagnostic.messageText, \"\\n\")),\n  ).toEqual([]);\n}\n\nfunction expectDocsShapedEvlogAuth(content: string) {\n  expect(content).not.toContain(\"createEvlogAuth\");\n  expect(content).not.toContain(\"toHeaders\");\n  expect(content).not.toContain(\"GetSessionInput\");\n  expect(content).not.toContain(\"GetSessionResult\");\n  expect(content).not.toContain(\"toEvlogAuthEvent\");\n  expect(content).not.toContain(\"await identifyUser(event);\");\n  expect(content).not.toContain('declare module \"h3\"');\n  expect(content).not.toContain(\"H3EventContext\");\n  expect(content).not.toContain(\"as unknown as BetterAuthInstance\");\n}\n\ndescribe(\"Addon Configurations\", () => {\n  describe(\"Universal Addons (no frontend restrictions)\", () => {\n    const universalAddons = [\"biome\", \"lefthook\", \"husky\", \"turborepo\", \"mcp\"];\n\n    for (const addon of universalAddons) {\n      it(`should work with ${addon} addon on any frontend`, async () => {\n        const result = await runTRPCTest({\n          projectName: `${addon}-universal`,\n          addons: [addon as Addons],\n          frontend: [\"tanstack-router\"],\n          backend: \"hono\",\n          runtime: \"bun\",\n          database: \"sqlite\",\n          orm: \"drizzle\",\n          auth: \"none\",\n          api: \"trpc\",\n          examples: [\"none\"],\n          dbSetup: \"none\",\n          webDeploy: \"none\",\n          serverDeploy: \"none\",\n          install: false,\n        });\n\n        expectSuccess(result);\n      });\n    }\n  });\n\n  describe(\"Frontend-Specific Addons\", () => {\n    describe(\"PWA Addon\", () => {\n      const pwaCompatibleFrontends = [\"tanstack-router\", \"react-router\", \"solid\", \"next\"];\n\n      for (const frontend of pwaCompatibleFrontends) {\n        it(`should work with PWA + ${frontend}`, async () => {\n          const config: TestConfig = {\n            projectName: `pwa-${frontend}`,\n            addons: [\"pwa\"],\n            frontend: [frontend as Frontend],\n            backend: \"hono\",\n            runtime: \"bun\",\n            database: \"sqlite\",\n            orm: \"drizzle\",\n            auth: \"none\",\n            examples: [\"none\"],\n            dbSetup: \"none\",\n            webDeploy: \"none\",\n            serverDeploy: \"none\",\n            install: false,\n          };\n\n          // Handle special frontend requirements\n          if (frontend === \"solid\") {\n            config.api = \"orpc\"; // tRPC not supported with solid\n          } else {\n            config.api = \"trpc\";\n          }\n\n          const result = await runTRPCTest(config);\n          expectSuccess(result);\n        });\n      }\n\n      const pwaIncompatibleFrontends = [\n        \"nuxt\",\n        \"svelte\",\n        \"native-bare\",\n        \"native-uniwind\",\n        \"native-unistyles\",\n      ];\n\n      for (const frontend of pwaIncompatibleFrontends) {\n        it(`should fail with PWA + ${frontend}`, async () => {\n          const config: TestConfig = {\n            projectName: `pwa-${frontend}-fail`,\n            addons: [\"pwa\"],\n            frontend: [frontend as Frontend],\n            backend: \"hono\",\n            runtime: \"bun\",\n            database: \"sqlite\",\n            orm: \"drizzle\",\n            auth: \"none\",\n            examples: [\"none\"],\n            dbSetup: \"none\",\n            webDeploy: \"none\",\n            serverDeploy: \"none\",\n            expectError: true,\n          };\n\n          if ([\"nuxt\", \"svelte\"].includes(frontend)) {\n            config.api = \"orpc\";\n          } else {\n            config.api = \"trpc\";\n          }\n\n          const result = await runTRPCTest(config);\n          expectError(\n            result,\n            \"pwa addon requires one of these frontends: tanstack-router, react-router, solid, next\",\n          );\n        });\n      }\n    });\n\n    describe(\"Tauri Addon\", () => {\n      const tauriCompatibleFrontends = [\n        \"tanstack-router\",\n        \"react-router\",\n        \"tanstack-start\",\n        \"next\",\n        \"nuxt\",\n        \"svelte\",\n        \"solid\",\n        \"astro\",\n      ];\n\n      for (const frontend of tauriCompatibleFrontends) {\n        it(`should work with Tauri + ${frontend}`, async () => {\n          const config: TestConfig = {\n            projectName: `tauri-${frontend}`,\n            addons: [\"tauri\"],\n            frontend: [frontend as Frontend],\n            backend: \"hono\",\n            runtime: \"bun\",\n            database: \"sqlite\",\n            orm: \"drizzle\",\n            auth: \"none\",\n            examples: [\"none\"],\n            dbSetup: \"none\",\n            webDeploy: \"none\",\n            serverDeploy: \"none\",\n            install: false,\n          };\n\n          if ([\"nuxt\", \"svelte\", \"solid\", \"astro\"].includes(frontend)) {\n            config.api = \"orpc\";\n          } else {\n            config.api = \"trpc\";\n          }\n\n          const result = await runTRPCTest(config);\n          expectSuccess(result);\n        });\n      }\n\n      const tauriIncompatibleFrontends = [\"native-bare\", \"native-uniwind\", \"native-unistyles\"];\n\n      for (const frontend of tauriIncompatibleFrontends) {\n        it(`should fail with Tauri + ${frontend}`, async () => {\n          const result = await runTRPCTest({\n            projectName: `tauri-${frontend}-fail`,\n            addons: [\"tauri\"],\n            frontend: [frontend as Frontend],\n            backend: \"hono\",\n            runtime: \"bun\",\n            database: \"sqlite\",\n            orm: \"drizzle\",\n            auth: \"none\",\n            api: \"trpc\",\n            examples: [\"none\"],\n            dbSetup: \"none\",\n            webDeploy: \"none\",\n            serverDeploy: \"none\",\n            expectError: true,\n          });\n\n          expectError(result, \"tauri addon requires one of these frontends\");\n        });\n      }\n    });\n\n    describe(\"Electrobun Addon\", () => {\n      const electrobunCompatibleFrontends = [\n        \"tanstack-router\",\n        \"react-router\",\n        \"tanstack-start\",\n        \"next\",\n        \"nuxt\",\n        \"svelte\",\n        \"solid\",\n        \"astro\",\n      ];\n\n      for (const frontend of electrobunCompatibleFrontends) {\n        it(`should work with Electrobun + ${frontend}`, async () => {\n          const config: TestConfig = {\n            projectName: `electrobun-${frontend}`,\n            addons: [\"electrobun\"],\n            frontend: [frontend as Frontend],\n            backend: \"hono\",\n            runtime: \"bun\",\n            database: \"sqlite\",\n            orm: \"drizzle\",\n            auth: \"none\",\n            examples: [\"none\"],\n            dbSetup: \"none\",\n            webDeploy: \"none\",\n            serverDeploy: \"none\",\n            install: false,\n          };\n\n          config.api = [\"nuxt\", \"svelte\", \"solid\", \"astro\"].includes(frontend) ? \"orpc\" : \"trpc\";\n\n          const result = await runTRPCTest(config);\n          expectSuccess(result);\n        });\n      }\n\n      const electrobunIncompatibleFrontends = [\"native-bare\", \"native-uniwind\", \"native-unistyles\"];\n\n      for (const frontend of electrobunIncompatibleFrontends) {\n        it(`should fail with Electrobun + ${frontend}`, async () => {\n          const config: TestConfig = {\n            projectName: `electrobun-${frontend}-fail`,\n            addons: [\"electrobun\"],\n            frontend: [frontend as Frontend],\n            backend: \"hono\",\n            runtime: \"bun\",\n            database: \"sqlite\",\n            orm: \"drizzle\",\n            auth: \"none\",\n            examples: [\"none\"],\n            dbSetup: \"none\",\n            webDeploy: \"none\",\n            serverDeploy: \"none\",\n            expectError: true,\n          };\n\n          config.api = \"trpc\";\n\n          const result = await runTRPCTest(config);\n          expectError(result, \"electrobun addon requires one of these frontends\");\n        });\n      }\n    });\n  });\n\n  describe(\"Multiple Addons\", () => {\n    it(\"should work with multiple compatible addons\", async () => {\n      const result = await runTRPCTest({\n        projectName: \"multiple-addons\",\n        addons: [\"biome\", \"husky\", \"turborepo\", \"pwa\"],\n        frontend: [\"tanstack-router\"],\n        backend: \"hono\",\n        runtime: \"bun\",\n        database: \"sqlite\",\n        orm: \"drizzle\",\n        auth: \"none\",\n        api: \"trpc\",\n        examples: [\"none\"],\n        dbSetup: \"none\",\n        webDeploy: \"none\",\n        serverDeploy: \"none\",\n        install: false,\n      });\n\n      expectSuccess(result);\n    });\n\n    it(\"should work with lefthook and husky together\", async () => {\n      const result = await runTRPCTest({\n        projectName: \"both-git-hooks\",\n        addons: [\"lefthook\", \"husky\"],\n        frontend: [\"tanstack-router\"],\n        backend: \"hono\",\n        runtime: \"bun\",\n        database: \"sqlite\",\n        orm: \"drizzle\",\n        auth: \"none\",\n        api: \"trpc\",\n        examples: [\"none\"],\n        dbSetup: \"none\",\n        webDeploy: \"none\",\n        serverDeploy: \"none\",\n        install: false,\n      });\n\n      expectSuccess(result);\n    });\n\n    it(\"should fail with incompatible addon combination\", async () => {\n      const result = await runTRPCTest({\n        projectName: \"incompatible-addons-fail\",\n        addons: [\"pwa\"], // PWA not compatible with nuxt\n        frontend: [\"nuxt\"],\n        backend: \"hono\",\n        runtime: \"bun\",\n        database: \"sqlite\",\n        orm: \"drizzle\",\n        auth: \"none\",\n        api: \"orpc\",\n        examples: [\"none\"],\n        dbSetup: \"none\",\n        webDeploy: \"none\",\n        serverDeploy: \"none\",\n        expectError: true,\n      });\n\n      expectError(result, \"pwa addon requires one of these frontends\");\n    });\n\n    it(\"should fail when turborepo and nx are combined\", async () => {\n      const result = await runTRPCTest({\n        projectName: \"monorepo-addon-conflict\",\n        addons: [\"turborepo\", \"nx\"],\n        frontend: [\"tanstack-router\"],\n        backend: \"hono\",\n        runtime: \"bun\",\n        database: \"sqlite\",\n        orm: \"drizzle\",\n        auth: \"none\",\n        api: \"trpc\",\n        examples: [\"none\"],\n        dbSetup: \"none\",\n        webDeploy: \"none\",\n        serverDeploy: \"none\",\n        expectError: true,\n      });\n\n      expectError(result, \"Cannot combine 'turborepo' and 'nx' addons\");\n    });\n\n    it(\"should deduplicate addons\", async () => {\n      const result = await runTRPCTest({\n        projectName: \"duplicate-addons\",\n        addons: [\"biome\", \"biome\", \"turborepo\"], // Duplicate biome\n        frontend: [\"tanstack-router\"],\n        backend: \"hono\",\n        runtime: \"bun\",\n        database: \"sqlite\",\n        orm: \"drizzle\",\n        auth: \"none\",\n        api: \"trpc\",\n        examples: [\"none\"],\n        dbSetup: \"none\",\n        webDeploy: \"none\",\n        serverDeploy: \"none\",\n        install: false,\n      });\n\n      expectSuccess(result);\n    });\n  });\n\n  describe(\"Evlog Addon\", () => {\n    it(\"should not offer evlog for Convex projects\", () => {\n      const compatibleAddons = getCompatibleAddons(\n        [\"evlog\", \"mcp\"] as Addons[],\n        [\"tanstack-start\", \"native-uniwind\"] as Frontend[],\n        [],\n        \"better-auth\",\n        \"convex\",\n        \"none\",\n      );\n\n      expect(compatibleAddons).not.toContain(\"evlog\");\n      expect(compatibleAddons).toContain(\"mcp\");\n    });\n\n    const backendSnippets: Record<Backend, string> = {\n      hono: 'import { evlog, type EvlogVariables } from \"evlog/hono\";',\n      express: 'import { evlog } from \"evlog/express\";',\n      fastify: 'import { evlog } from \"evlog/fastify\";',\n      elysia: 'import { evlog } from \"evlog/elysia\";',\n      convex: \"\",\n      self: \"\",\n      none: \"\",\n    };\n\n    for (const backend of [\"hono\", \"express\", \"fastify\", \"elysia\"] as const) {\n      it(`should wire evlog middleware for ${backend}`, async () => {\n        const result = await runTRPCTest({\n          projectName: `evlog-${backend}`,\n          addons: [\"evlog\"],\n          frontend: [\"tanstack-router\"],\n          backend,\n          runtime: \"bun\",\n          database: \"sqlite\",\n          orm: \"drizzle\",\n          auth: \"none\",\n          api: \"trpc\",\n          examples: [\"none\"],\n          dbSetup: \"none\",\n          webDeploy: \"none\",\n          serverDeploy: \"none\",\n          install: false,\n        });\n\n        expectSuccess(result);\n        const projectDir = result.result?.projectDirectory;\n        if (!projectDir) throw new Error(\"Expected generated project directory\");\n\n        const serverIndex = await readFile(join(projectDir, \"apps/server/src/index.ts\"), \"utf-8\");\n        const serverPackageJson = await readFile(\n          join(projectDir, \"apps/server/package.json\"),\n          \"utf-8\",\n        );\n\n        expect(serverIndex).toContain('import { initLogger } from \"evlog\";');\n        expect(serverIndex).toContain(backendSnippets[backend]);\n        expect(serverIndex).toContain(`env: { service: \"evlog-${backend}-server\" }`);\n        expect(serverPackageJson).toContain('\"evlog\": \"^2.14.1\"');\n      });\n    }\n\n    const webCases = [\n      {\n        frontend: \"next\",\n        api: \"trpc\",\n        files: [\n          [\"apps/web/src/lib/evlog.ts\", \"createEvlog\"],\n          [\"apps/web/instrumentation.ts\", \"defineNodeInstrumentation\"],\n          [\"apps/web/src/proxy.ts\", \"evlogMiddleware\"],\n          [\"apps/web/src/app/api/trpc/[trpc]/route.ts\", \"withEvlog(handler)\"],\n        ],\n      },\n      {\n        frontend: \"nuxt\",\n        api: \"orpc\",\n        files: [[\"apps/web/nuxt.config.ts\", '\"evlog/nuxt\"']],\n      },\n      {\n        frontend: \"svelte\",\n        api: \"orpc\",\n        files: [\n          [\"apps/web/vite.config.ts\", \"evlog({ service:\"],\n          [\"apps/web/src/hooks.server.ts\", \"createEvlogHooks\"],\n          [\"apps/web/src/app.d.ts\", \"log: RequestLogger\"],\n        ],\n      },\n      {\n        frontend: \"tanstack-start\",\n        api: \"trpc\",\n        files: [\n          [\"apps/web/nitro.config.ts\", 'evlog from \"evlog/nitro/v3\"'],\n          [\"apps/web/src/routes/__root.tsx\", \"evlogErrorHandler\"],\n        ],\n      },\n      {\n        frontend: \"astro\",\n        api: \"orpc\",\n        files: [\n          [\"apps/web/src/middleware.ts\", \"createRequestLogger\"],\n          [\"apps/web/src/env.d.ts\", \"log: RequestLogger\"],\n        ],\n      },\n    ] as const;\n\n    for (const webCase of webCases) {\n      it(`should wire evlog for ${webCase.frontend} fullstack projects`, async () => {\n        const result = await runTRPCTest({\n          projectName: `evlog-${webCase.frontend}-web`,\n          addons: [\"evlog\"],\n          frontend: [webCase.frontend as Frontend],\n          backend: \"self\",\n          runtime: \"none\",\n          database: \"sqlite\",\n          orm: \"drizzle\",\n          auth: \"none\",\n          api: webCase.api,\n          examples: [\"none\"],\n          dbSetup: \"none\",\n          webDeploy: \"none\",\n          serverDeploy: \"none\",\n          install: false,\n        });\n\n        expectSuccess(result);\n        const projectDir = result.result?.projectDirectory;\n        if (!projectDir) throw new Error(\"Expected generated project directory\");\n\n        for (const [filePath, snippet] of webCase.files) {\n          const file = await readFile(join(projectDir, filePath), \"utf-8\");\n          expect(file).toContain(snippet);\n        }\n\n        const webPackageJson = await readFile(join(projectDir, \"apps/web/package.json\"), \"utf-8\");\n        expect(webPackageJson).toContain('\"evlog\": \"^2.14.1\"');\n        if (webCase.frontend === \"tanstack-start\") {\n          expect(webPackageJson).toContain('\"nitro\": \"^3.0.260429-beta\"');\n        }\n      });\n    }\n\n    it(\"should keep Nuxt config parseable with Cloudflare web deploy\", async () => {\n      const result = await runTRPCTest({\n        projectName: \"evlog-nuxt-cloudflare-web\",\n        addons: [\"evlog\"],\n        frontend: [\"nuxt\"],\n        backend: \"self\",\n        runtime: \"none\",\n        database: \"sqlite\",\n        orm: \"drizzle\",\n        auth: \"none\",\n        api: \"orpc\",\n        examples: [\"none\"],\n        dbSetup: \"none\",\n        webDeploy: \"cloudflare\",\n        serverDeploy: \"none\",\n        install: false,\n      });\n\n      expectSuccess(result);\n      const projectDir = result.result?.projectDirectory;\n      if (!projectDir) throw new Error(\"Expected generated project directory\");\n\n      const nuxtConfig = await readFile(join(projectDir, \"apps/web/nuxt.config.ts\"), \"utf-8\");\n\n      expect(nuxtConfig).toContain('\"evlog/nuxt\"');\n      expect(nuxtConfig).toContain(\"nitro:\");\n      expect(nuxtConfig).toContain('import { existsSync } from \"node:fs\";');\n      expect(nuxtConfig).toContain('import { fileURLToPath } from \"node:url\";');\n      expect(nuxtConfig).toContain(\"const alchemyConfigPath = fileURLToPath\");\n      expect(nuxtConfig).toContain(\"const hasAlchemyConfig = existsSync(alchemyConfigPath);\");\n      expect(nuxtConfig).toContain(\"const shouldUseAlchemy = !isNuxtPrepare && hasAlchemyConfig;\");\n      expect(nuxtConfig).toContain(\"alchemy({ dev: { configPath: alchemyConfigPath } })\");\n      expect(nuxtConfig).toContain(\"isNuxtDev\");\n      expect(nuxtConfig).toContain(\"const cloudflareWorkersShimPath = fileURLToPath\");\n      expect(nuxtConfig).toContain('\"cloudflare:workers\"');\n      expect(nuxtConfig).toContain(\"cloudflareWorkersShimPath\");\n      expect(nuxtConfig).toContain(\"evlog:\");\n      expectParseableTypeScript(nuxtConfig);\n    });\n\n    it(\"should type Nitro Better Auth events for Nuxt Cloudflare projects\", async () => {\n      const result = await runTRPCTest({\n        projectName: \"evlog-nuxt-cloudflare-auth\",\n        addons: [\"evlog\"],\n        frontend: [\"nuxt\"],\n        backend: \"self\",\n        runtime: \"none\",\n        database: \"sqlite\",\n        orm: \"drizzle\",\n        auth: \"better-auth\",\n        api: \"orpc\",\n        examples: [\"none\"],\n        dbSetup: \"none\",\n        webDeploy: \"cloudflare\",\n        serverDeploy: \"none\",\n        install: false,\n      });\n\n      expectSuccess(result);\n      const projectDir = result.result?.projectDirectory;\n      if (!projectDir) throw new Error(\"Expected generated project directory\");\n\n      const authMiddleware = await readFile(\n        join(projectDir, \"apps/web/server/middleware/evlog-auth.ts\"),\n        \"utf-8\",\n      );\n      const authClient = await readFile(\n        join(projectDir, \"apps/web/app/plugins/auth-client.ts\"),\n        \"utf-8\",\n      );\n      const envServer = await readFile(join(projectDir, \"packages/env/src/server.ts\"), \"utf-8\");\n\n      expect(existsSync(join(projectDir, \"apps/web/server/plugins/evlog-auth.ts\"))).toBe(false);\n      expect(authMiddleware).toContain(\n        'import { createAuthMiddleware, type BetterAuthInstance } from \"evlog/better-auth\";',\n      );\n      expect(authMiddleware).toContain(\n        \"const identify = createAuthMiddleware(createAuth() as BetterAuthInstance, {\",\n      );\n      expect(authMiddleware).toContain('exclude: [\"/api/auth/**\"]');\n      expect(authMiddleware).toContain(\"maskEmail: true\");\n      expect(authMiddleware).toContain(\"export default defineEventHandler(async (event) => {\");\n      expect(authMiddleware).toContain(\n        \"await identify(event.context.log, event.headers, event.path);\",\n      );\n      expect(authMiddleware).not.toContain(\"createAuthIdentifier(\");\n      expectDocsShapedEvlogAuth(authMiddleware);\n      expectParseableTypeScript(authMiddleware);\n\n      expect(authClient).not.toContain(\"baseURL:\");\n      expect(authClient).not.toContain(\"as string\");\n      expectParseableTypeScript(authClient);\n\n      expect(envServer).toContain('/// <reference types=\"@cloudflare/workers-types\" />');\n      expectParseableTypeScript(envServer);\n    });\n\n    const fullstackBetterAuthEvlogCases = [\n      {\n        frontend: \"next\",\n        api: \"trpc\",\n        path: \"apps/web/src/lib/evlog-auth.ts\",\n        expected: \"createAuthMiddleware(auth as BetterAuthInstance\",\n      },\n      {\n        frontend: \"nuxt\",\n        api: \"orpc\",\n        path: \"apps/web/server/middleware/evlog-auth.ts\",\n        expected: \"createAuthMiddleware(auth as BetterAuthInstance\",\n      },\n      {\n        frontend: \"svelte\",\n        api: \"orpc\",\n        path: \"apps/web/src/hooks.server.ts\",\n        expected: \"createAuthMiddleware(auth as BetterAuthInstance\",\n      },\n      {\n        frontend: \"tanstack-start\",\n        api: \"trpc\",\n        path: \"apps/web/server/plugins/evlog-auth.ts\",\n        expected: \"createAuthIdentifier(auth as BetterAuthInstance\",\n      },\n      {\n        frontend: \"astro\",\n        api: \"orpc\",\n        path: \"apps/web/src/middleware.ts\",\n        expected: \"createAuthMiddleware(auth as BetterAuthInstance\",\n      },\n    ] as const;\n\n    for (const webCase of fullstackBetterAuthEvlogCases) {\n      it(`should generate docs-shaped evlog Better Auth wiring for ${webCase.frontend} fullstack projects`, async () => {\n        const result = await runTRPCTest({\n          projectName: `evlog-${webCase.frontend}-fullstack-auth`,\n          addons: [\"evlog\"],\n          frontend: [webCase.frontend as Frontend],\n          backend: \"self\",\n          runtime: \"none\",\n          database: \"sqlite\",\n          orm: \"drizzle\",\n          auth: \"better-auth\",\n          api: webCase.api,\n          examples: [\"none\"],\n          dbSetup: \"none\",\n          webDeploy: \"none\",\n          serverDeploy: \"none\",\n          install: false,\n        });\n\n        expectSuccess(result);\n        const projectDir = result.result?.projectDirectory;\n        if (!projectDir) throw new Error(\"Expected generated project directory\");\n\n        const authFile = await readFile(join(projectDir, webCase.path), \"utf-8\");\n        if (webCase.frontend === \"tanstack-start\") {\n          expect(authFile).toContain(\n            'import { createAuthIdentifier, type BetterAuthInstance } from \"evlog/better-auth\";',\n          );\n          expect(authFile).not.toContain(\"createAuthMiddleware(\");\n        } else {\n          expect(authFile).toContain(\n            'import { createAuthMiddleware, type BetterAuthInstance } from \"evlog/better-auth\";',\n          );\n        }\n        expect(authFile).toContain(webCase.expected);\n        expect(authFile).toContain('exclude: [\"/api/auth/**\"]');\n        expect(authFile).toContain(\"maskEmail: true\");\n        expectDocsShapedEvlogAuth(authFile);\n        expectParseableTypeScript(authFile);\n      });\n    }\n\n    const fullstackBetterAuthFactoryEvlogCases = [\n      {\n        frontend: \"next\",\n        api: \"trpc\",\n        path: \"apps/web/src/lib/evlog-auth.ts\",\n        expected: \"createAuthMiddleware(createAuth() as BetterAuthInstance\",\n        insideMarker: \"export async function identifyEvlogUser\",\n      },\n      {\n        frontend: \"nuxt\",\n        api: \"orpc\",\n        path: \"apps/web/server/middleware/evlog-auth.ts\",\n        expected: \"createAuthMiddleware(createAuth() as BetterAuthInstance\",\n        insideMarker: \"export default defineEventHandler\",\n      },\n      {\n        frontend: \"svelte\",\n        api: \"orpc\",\n        path: \"apps/web/src/hooks.server.ts\",\n        expected: \"createAuthMiddleware(createAuth(authEnv) as BetterAuthInstance\",\n        insideMarker: \"const evlogAuthHandle\",\n      },\n      {\n        frontend: \"tanstack-start\",\n        api: \"trpc\",\n        path: \"apps/web/server/plugins/evlog-auth.ts\",\n        expected: \"createAuthIdentifier(createAuth() as BetterAuthInstance\",\n        insideMarker: 'nitroApp.hooks.hook(\"request\", async (event) => {',\n      },\n      {\n        frontend: \"astro\",\n        api: \"orpc\",\n        path: \"apps/web/src/middleware.ts\",\n        expected: \"createAuthMiddleware(createAuth() as BetterAuthInstance\",\n        insideMarker: \"export const onRequest\",\n      },\n    ] as const;\n\n    for (const webCase of fullstackBetterAuthFactoryEvlogCases) {\n      it(`should keep factory-based evlog auth wiring inside the request path for ${webCase.frontend}`, async () => {\n        const result = await runTRPCTest({\n          projectName: `evlog-${webCase.frontend}-cloudflare-auth`,\n          addons: [\"evlog\"],\n          frontend: [webCase.frontend as Frontend],\n          backend: \"self\",\n          runtime: \"none\",\n          database: \"sqlite\",\n          orm: \"drizzle\",\n          auth: \"better-auth\",\n          api: webCase.api,\n          examples: [\"none\"],\n          dbSetup: \"none\",\n          webDeploy: \"cloudflare\",\n          serverDeploy: \"none\",\n          install: false,\n        });\n\n        expectSuccess(result);\n        const projectDir = result.result?.projectDirectory;\n        if (!projectDir) throw new Error(\"Expected generated project directory\");\n\n        const authFile = await readFile(join(projectDir, webCase.path), \"utf-8\");\n        expect(authFile).toContain(webCase.expected);\n        expect(authFile.indexOf(webCase.insideMarker)).toBeLessThan(\n          authFile.indexOf(webCase.expected),\n        );\n        expect(authFile).toContain('exclude: [\"/api/auth/**\"]');\n        expect(authFile).toContain(\"maskEmail: true\");\n        expectDocsShapedEvlogAuth(authFile);\n        expectParseableTypeScript(authFile);\n      });\n    }\n\n    it(\"should reject evlog for Convex backend projects\", async () => {\n      const result = await runTRPCTest({\n        projectName: \"evlog-convex-fail\",\n        addons: [\"evlog\"],\n        frontend: [\"tanstack-start\", \"native-uniwind\"],\n        backend: \"convex\",\n        runtime: \"none\",\n        database: \"none\",\n        orm: \"none\",\n        auth: \"better-auth\",\n        api: \"none\",\n        examples: [\"none\"],\n        dbSetup: \"none\",\n        webDeploy: \"none\",\n        serverDeploy: \"none\",\n        install: false,\n        expectError: true,\n      });\n\n      expectError(result, \"Convex and backend none are not supported yet\");\n    });\n\n    it(\"should wire evlog Better Auth and AI SDK helpers for server projects\", async () => {\n      const result = await runTRPCTest({\n        projectName: \"evlog-hono-auth-ai\",\n        addons: [\"evlog\"],\n        frontend: [\"tanstack-router\"],\n        backend: \"hono\",\n        runtime: \"bun\",\n        database: \"sqlite\",\n        orm: \"drizzle\",\n        auth: \"better-auth\",\n        api: \"trpc\",\n        examples: [\"ai\"],\n        dbSetup: \"none\",\n        webDeploy: \"none\",\n        serverDeploy: \"none\",\n        install: false,\n      });\n\n      expectSuccess(result);\n      const projectDir = result.result?.projectDirectory;\n      if (!projectDir) throw new Error(\"Expected generated project directory\");\n\n      const serverIndex = await readFile(join(projectDir, \"apps/server/src/index.ts\"), \"utf-8\");\n      expect(serverIndex).toContain(\n        'import { createAuthMiddleware, type BetterAuthInstance } from \"evlog/better-auth\";',\n      );\n      expect(serverIndex).toContain(\n        \"const identifyUser = createAuthMiddleware(auth as BetterAuthInstance\",\n      );\n      expect(serverIndex).toContain(\n        'await identifyUser(c.get(\"log\"), c.req.raw.headers, c.req.path);',\n      );\n      expectDocsShapedEvlogAuth(serverIndex);\n      expect(serverIndex).toContain(\n        'import { createAILogger, createEvlogIntegration } from \"evlog/ai\";',\n      );\n      expect(serverIndex).toContain('const ai = createAILogger(c.get(\"log\"));');\n      expect(serverIndex).toContain(\"model: ai.wrap(model)\");\n      expect(serverIndex).toContain(\"integrations: [createEvlogIntegration(ai)]\");\n    });\n\n    it(\"should wire evlog AI SDK helpers for Express server projects\", async () => {\n      const result = await runTRPCTest({\n        projectName: \"evlog-express-ai\",\n        addons: [\"evlog\"],\n        frontend: [\"nuxt\"],\n        backend: \"express\",\n        runtime: \"node\",\n        database: \"sqlite\",\n        orm: \"drizzle\",\n        auth: \"better-auth\",\n        api: \"orpc\",\n        examples: [\"ai\"],\n        dbSetup: \"none\",\n        webDeploy: \"none\",\n        serverDeploy: \"none\",\n        install: false,\n      });\n\n      expectSuccess(result);\n      const projectDir = result.result?.projectDirectory;\n      if (!projectDir) throw new Error(\"Expected generated project directory\");\n\n      const serverIndex = await readFile(join(projectDir, \"apps/server/src/index.ts\"), \"utf-8\");\n      expect(serverIndex).toContain(\n        'import { createAILogger, createEvlogIntegration } from \"evlog/ai\";',\n      );\n      expect(serverIndex).toContain(\"const ai = createAILogger(req.log);\");\n      expect(serverIndex).toContain(\"model: ai.wrap(model)\");\n      expect(serverIndex).toContain(\"integrations: [createEvlogIntegration(ai)]\");\n    });\n\n    it(\"should wire evlog request and auth helpers for Next fullstack AI projects\", async () => {\n      const result = await runTRPCTest({\n        projectName: \"evlog-next-auth-ai\",\n        addons: [\"evlog\"],\n        frontend: [\"next\"],\n        backend: \"self\",\n        runtime: \"none\",\n        database: \"sqlite\",\n        orm: \"drizzle\",\n        auth: \"better-auth\",\n        api: \"trpc\",\n        examples: [\"ai\"],\n        dbSetup: \"none\",\n        webDeploy: \"none\",\n        serverDeploy: \"none\",\n        install: false,\n      });\n\n      expectSuccess(result);\n      const projectDir = result.result?.projectDirectory;\n      if (!projectDir) throw new Error(\"Expected generated project directory\");\n\n      const evlogAuth = await readFile(join(projectDir, \"apps/web/src/lib/evlog-auth.ts\"), \"utf-8\");\n      const trpcRoute = await readFile(\n        join(projectDir, \"apps/web/src/app/api/trpc/[trpc]/route.ts\"),\n        \"utf-8\",\n      );\n      const aiRoute = await readFile(join(projectDir, \"apps/web/src/app/api/ai/route.ts\"), \"utf-8\");\n\n      expect(evlogAuth).toContain(\n        'import { createAuthMiddleware, type BetterAuthInstance } from \"evlog/better-auth\";',\n      );\n      expect(evlogAuth).toContain(\"createAuthMiddleware(auth as BetterAuthInstance\");\n      expectDocsShapedEvlogAuth(evlogAuth);\n      expect(trpcRoute).toContain(\"withEvlog(handler)\");\n      expect(trpcRoute).toContain(\"await identifyEvlogUser(req);\");\n      expect(aiRoute).toContain(\"withEvlog(async (req: Request)\");\n      expect(aiRoute).toContain(\"await identifyEvlogUser(req);\");\n      expect(aiRoute).not.toContain(\"createAILogger\");\n      expect(aiRoute).not.toContain(\"model: ai.wrap(model)\");\n      expect(aiRoute).not.toContain(\"createEvlogIntegration(ai)\");\n    });\n\n    const separateBackendWebAuthCases = [\n      { frontend: \"next\", api: \"trpc\" },\n      { frontend: \"nuxt\", api: \"orpc\" },\n      { frontend: \"svelte\", api: \"orpc\" },\n      { frontend: \"tanstack-start\", api: \"trpc\" },\n      { frontend: \"astro\", api: \"orpc\" },\n    ] as const;\n\n    for (const webCase of separateBackendWebAuthCases) {\n      it(`should keep Better Auth identifiers in the server for ${webCase.frontend} + separate backend projects`, async () => {\n        const projectName = `evlog-${webCase.frontend}-express-auth-ai`;\n        const result = await runTRPCTest({\n          projectName,\n          addons: [\"evlog\"],\n          frontend: [webCase.frontend as Frontend],\n          backend: \"express\",\n          runtime: \"node\",\n          database: \"sqlite\",\n          orm: \"drizzle\",\n          auth: \"better-auth\",\n          api: webCase.api,\n          examples: [\"todo\", \"ai\"],\n          dbSetup: \"turso\",\n          webDeploy: \"none\",\n          serverDeploy: \"none\",\n          install: false,\n        });\n\n        expectSuccess(result);\n        const projectDir = result.result?.projectDirectory;\n        if (!projectDir) throw new Error(\"Expected generated project directory\");\n\n        const serverIndex = await readFile(join(projectDir, \"apps/server/src/index.ts\"), \"utf-8\");\n        const webPackageJson = await readFile(join(projectDir, \"apps/web/package.json\"), \"utf-8\");\n        const webFiles = await readSourceFiles(join(projectDir, \"apps/web\"));\n        const webContent = webFiles.map((file) => file.content).join(\"\\n\");\n\n        expect(serverIndex).toContain(\n          'import { createAuthMiddleware, type BetterAuthInstance } from \"evlog/better-auth\";',\n        );\n        expect(serverIndex).toContain(\"createAuthMiddleware(auth as BetterAuthInstance\");\n        expectDocsShapedEvlogAuth(serverIndex);\n        expect(serverIndex).toContain(\"maskEmail: true\");\n        expect(webPackageJson).not.toContain(`\"@${projectName}/auth\"`);\n        expect(webPackageJson).not.toContain('\"@libsql/client\"');\n        expect(webPackageJson).not.toContain('\"libsql\"');\n        expect(webContent).not.toContain(`@${projectName}/auth`);\n        expect(webContent).not.toContain(`@${projectName}/db`);\n        expect(webContent).not.toContain(`@${projectName}/env/server`);\n        expect(webContent).not.toContain(\"createAuthMiddleware\");\n        expect(webContent).not.toContain(\"createAuthIdentifier\");\n        expect(webContent).not.toContain(\"identifyEvlogUser\");\n        expect(webContent).not.toContain('serverExternalPackages: [\"libsql\", \"@libsql/client\"]');\n        expect(webContent).not.toContain(\"BETTER_AUTH_SECRET\");\n        expect(webContent).not.toContain(\"DATABASE_URL\");\n      });\n    }\n\n    it(\"should patch an existing server when evlog is added later\", async () => {\n      const created = await runTRPCTest({\n        projectName: \"evlog-add-existing\",\n        addons: [\"none\"],\n        frontend: [\"tanstack-router\"],\n        backend: \"hono\",\n        runtime: \"bun\",\n        database: \"sqlite\",\n        orm: \"drizzle\",\n        auth: \"none\",\n        api: \"trpc\",\n        examples: [\"none\"],\n        dbSetup: \"none\",\n        webDeploy: \"none\",\n        serverDeploy: \"none\",\n        install: false,\n      });\n\n      expectSuccess(created);\n      const projectDir = created.result?.projectDirectory;\n      if (!projectDir) throw new Error(\"Expected generated project directory\");\n\n      const addResult = await add({\n        projectDir,\n        addons: [\"evlog\"],\n        install: false,\n      });\n\n      expect(addResult?.success).toBe(true);\n\n      const serverIndex = await readFile(join(projectDir, \"apps/server/src/index.ts\"), \"utf-8\");\n      const serverPackageJson = await readFile(\n        join(projectDir, \"apps/server/package.json\"),\n        \"utf-8\",\n      );\n\n      expect(serverIndex).toContain('import { evlog, type EvlogVariables } from \"evlog/hono\";');\n      expect(serverIndex).toContain(\"app.use(evlog());\");\n      expect(serverPackageJson).toContain('\"evlog\": \"^2.14.1\"');\n    });\n\n    it(\"should reject evlog when added later to a Convex project\", async () => {\n      const created = await runTRPCTest({\n        projectName: \"evlog-add-convex-fail\",\n        addons: [\"none\"],\n        frontend: [\"tanstack-start\", \"native-uniwind\"],\n        backend: \"convex\",\n        runtime: \"none\",\n        database: \"none\",\n        orm: \"none\",\n        auth: \"better-auth\",\n        api: \"none\",\n        examples: [\"none\"],\n        dbSetup: \"none\",\n        webDeploy: \"none\",\n        serverDeploy: \"none\",\n        install: false,\n      });\n\n      expectSuccess(created);\n      const projectDir = created.result?.projectDirectory;\n      if (!projectDir) throw new Error(\"Expected generated project directory\");\n\n      const addResult = await add({\n        projectDir,\n        addons: [\"evlog\"],\n        install: false,\n      });\n\n      expect(addResult?.success).toBe(false);\n      expect(addResult?.error).toContain(\"Convex and backend none are not supported yet\");\n    });\n  });\n\n  describe(\"Addons with None Option\", () => {\n    it(\"should work with addons none\", async () => {\n      const result = await runTRPCTest({\n        projectName: \"no-addons\",\n        addons: [\"none\"],\n        frontend: [\"tanstack-router\"],\n        backend: \"hono\",\n        runtime: \"bun\",\n        database: \"sqlite\",\n        orm: \"drizzle\",\n        auth: \"none\",\n        api: \"trpc\",\n        examples: [\"none\"],\n        dbSetup: \"none\",\n        webDeploy: \"none\",\n        serverDeploy: \"none\",\n        install: false,\n      });\n\n      expectSuccess(result);\n    });\n\n    it(\"should fail with none + other addons\", async () => {\n      const result = await runTRPCTest({\n        projectName: \"none-with-other-addons-fail\",\n        addons: [\"none\", \"biome\"],\n        frontend: [\"tanstack-router\"],\n        backend: \"hono\",\n        runtime: \"bun\",\n        database: \"sqlite\",\n        orm: \"drizzle\",\n        auth: \"none\",\n        api: \"trpc\",\n        examples: [\"none\"],\n        dbSetup: \"none\",\n        webDeploy: \"none\",\n        serverDeploy: \"none\",\n        expectError: true,\n      });\n\n      expectError(result, \"Cannot combine 'none' with other addons\");\n    });\n  });\n\n  describe(\"All Available Addons\", () => {\n    const testableAddons = [\n      \"pwa\",\n      \"tauri\",\n      \"electrobun\",\n      \"biome\",\n      \"husky\",\n      \"turborepo\",\n      \"nx\",\n      \"oxlint\",\n      \"evlog\",\n      // Note: starlight, ultracite, fumadocs are prompt-controlled only\n    ];\n\n    for (const addon of testableAddons) {\n      it(`should work with ${addon} addon in appropriate setup`, async () => {\n        const config: TestConfig = {\n          projectName: `test-${addon}`,\n          addons: [addon as Addons],\n          backend: \"hono\",\n          runtime: \"bun\",\n          database: \"sqlite\",\n          orm: \"drizzle\",\n          auth: \"none\",\n          api: \"trpc\",\n          examples: [\"none\"],\n          dbSetup: \"none\",\n          webDeploy: \"none\",\n          serverDeploy: \"none\",\n          install: false,\n        };\n\n        // Choose compatible frontend for each addon\n        if ([\"pwa\"].includes(addon)) {\n          config.frontend = [\"tanstack-router\"]; // PWA compatible\n        } else if ([\"tauri\"].includes(addon)) {\n          config.frontend = [\"tanstack-router\"]; // Tauri compatible\n        } else if ([\"electrobun\"].includes(addon)) {\n          config.frontend = [\"tanstack-router\"]; // Electrobun compatible\n        } else {\n          config.frontend = [\"tanstack-router\"]; // Universal addons\n        }\n\n        const result = await runTRPCTest(config);\n        expectSuccess(result);\n      });\n    }\n  });\n});\n"
  },
  {
    "path": "apps/cli/test/api.test.ts",
    "content": "import { describe, expect, it } from \"bun:test\";\n\nimport { createVirtual } from \"../src/index\";\nimport type { API, Backend, Database, Examples, Frontend, ORM, Runtime } from \"../src/types\";\nimport { collectFiles } from \"./setup\";\nimport { expectError, expectSuccess, runTRPCTest, type TestConfig } from \"./test-utils\";\n\ndescribe(\"API Configurations\", () => {\n  describe(\"tRPC API\", () => {\n    const reactFrontends = [\"tanstack-router\", \"react-router\", \"tanstack-start\", \"next\"];\n\n    for (const frontend of reactFrontends) {\n      it(`should work with tRPC + ${frontend}`, async () => {\n        const result = await runTRPCTest({\n          projectName: `trpc-${frontend}`,\n          api: \"trpc\",\n          frontend: [frontend as Frontend],\n          backend: \"hono\",\n          runtime: \"bun\",\n          database: \"sqlite\",\n          orm: \"drizzle\",\n          auth: \"none\",\n          addons: [\"none\"],\n          examples: [\"none\"],\n          dbSetup: \"none\",\n          webDeploy: \"none\",\n          serverDeploy: \"none\",\n          install: false,\n        });\n\n        expectSuccess(result);\n      });\n    }\n\n    const nativeFrontends = [\"native-bare\", \"native-uniwind\", \"native-unistyles\"];\n\n    for (const frontend of nativeFrontends) {\n      it(`should work with tRPC + ${frontend}`, async () => {\n        const result = await runTRPCTest({\n          projectName: `trpc-${frontend}`,\n          api: \"trpc\",\n          frontend: [frontend as Frontend],\n          backend: \"hono\",\n          runtime: \"bun\",\n          database: \"sqlite\",\n          orm: \"drizzle\",\n          auth: \"none\",\n          addons: [\"none\"],\n          examples: [\"none\"],\n          dbSetup: \"none\",\n          webDeploy: \"none\",\n          serverDeploy: \"none\",\n          install: false,\n        });\n\n        expectSuccess(result);\n      });\n    }\n\n    it(\"should fail with tRPC + Nuxt\", async () => {\n      const result = await runTRPCTest({\n        projectName: \"trpc-nuxt-fail\",\n        api: \"trpc\",\n        frontend: [\"nuxt\"],\n        backend: \"hono\",\n        runtime: \"bun\",\n        database: \"sqlite\",\n        orm: \"drizzle\",\n        auth: \"none\",\n        addons: [\"none\"],\n        examples: [\"none\"],\n        dbSetup: \"none\",\n        webDeploy: \"none\",\n        serverDeploy: \"none\",\n        expectError: true,\n      });\n\n      expectError(result, \"tRPC API is not supported with 'nuxt' frontend\");\n    });\n\n    it(\"should fail with tRPC + Svelte\", async () => {\n      const result = await runTRPCTest({\n        projectName: \"trpc-svelte-fail\",\n        api: \"trpc\",\n        frontend: [\"svelte\"],\n        backend: \"hono\",\n        runtime: \"bun\",\n        database: \"sqlite\",\n        orm: \"drizzle\",\n        auth: \"none\",\n        addons: [\"none\"],\n        examples: [\"none\"],\n        dbSetup: \"none\",\n        webDeploy: \"none\",\n        serverDeploy: \"none\",\n        expectError: true,\n      });\n\n      expectError(result, \"tRPC API is not supported with 'svelte' frontend\");\n    });\n\n    it(\"should fail with tRPC + Solid\", async () => {\n      const result = await runTRPCTest({\n        projectName: \"trpc-solid-fail\",\n        api: \"trpc\",\n        frontend: [\"solid\"],\n        backend: \"hono\",\n        runtime: \"bun\",\n        database: \"sqlite\",\n        orm: \"drizzle\",\n        auth: \"none\",\n        addons: [\"none\"],\n        examples: [\"none\"],\n        dbSetup: \"none\",\n        webDeploy: \"none\",\n        serverDeploy: \"none\",\n        expectError: true,\n      });\n\n      expectError(result, \"tRPC API is not supported with 'solid' frontend\");\n    });\n\n    const backends = [\"hono\", \"express\", \"fastify\", \"elysia\"];\n\n    for (const backend of backends) {\n      it(`should work with tRPC + ${backend}`, async () => {\n        const config: TestConfig = {\n          projectName: `trpc-${backend}`,\n          api: \"trpc\",\n          backend: backend as Backend,\n          frontend: [\"tanstack-router\"],\n          database: \"sqlite\",\n          orm: \"drizzle\",\n          auth: \"none\",\n          addons: [\"none\"],\n          examples: [\"none\"],\n          dbSetup: \"none\",\n          webDeploy: \"none\",\n          serverDeploy: \"none\",\n          install: false,\n        };\n\n        if (backend === \"elysia\") {\n          config.runtime = \"bun\";\n        } else {\n          config.runtime = \"bun\";\n        }\n\n        const result = await runTRPCTest(config);\n        expectSuccess(result);\n      });\n    }\n  });\n\n  describe(\"oRPC API\", () => {\n    const frontends = [\n      \"tanstack-router\",\n      \"react-router\",\n      \"tanstack-start\",\n      \"next\",\n      \"nuxt\",\n      \"svelte\",\n      \"solid\",\n      \"native-bare\",\n      \"native-uniwind\",\n      \"native-unistyles\",\n    ];\n\n    for (const frontend of frontends) {\n      it(`should work with oRPC + ${frontend}`, async () => {\n        const result = await runTRPCTest({\n          projectName: `orpc-${frontend}`,\n          api: \"orpc\",\n          frontend: [frontend as Frontend],\n          backend: \"hono\",\n          runtime: \"bun\",\n          database: \"sqlite\",\n          orm: \"drizzle\",\n          auth: \"none\",\n          addons: [\"none\"],\n          examples: [\"none\"],\n          dbSetup: \"none\",\n          webDeploy: \"none\",\n          serverDeploy: \"none\",\n          install: false,\n        });\n\n        expectSuccess(result);\n      });\n    }\n\n    const backends = [\"hono\", \"express\", \"fastify\", \"elysia\"];\n\n    for (const backend of backends) {\n      it(`should work with oRPC + ${backend}`, async () => {\n        const config: TestConfig = {\n          projectName: `orpc-${backend}`,\n          api: \"orpc\",\n          backend: backend as Backend,\n          frontend: [\"tanstack-router\"],\n          database: \"sqlite\",\n          orm: \"drizzle\",\n          auth: \"none\",\n          addons: [\"none\"],\n          examples: [\"none\"],\n          dbSetup: \"none\",\n          webDeploy: \"none\",\n          serverDeploy: \"none\",\n          install: false,\n        };\n\n        if (backend === \"elysia\") {\n          config.runtime = \"bun\";\n        } else {\n          config.runtime = \"bun\";\n        }\n\n        const result = await runTRPCTest(config);\n        expectSuccess(result);\n      });\n    }\n  });\n\n  describe(\"No API\", () => {\n    it(\"should work with API none + basic setup\", async () => {\n      const result = await runTRPCTest({\n        projectName: \"api-none-basic\",\n        api: \"none\",\n        frontend: [\"tanstack-router\"],\n        backend: \"hono\",\n        runtime: \"bun\",\n        database: \"sqlite\",\n        orm: \"drizzle\",\n        auth: \"none\",\n        addons: [\"none\"],\n        examples: [\"none\"],\n        dbSetup: \"none\",\n        webDeploy: \"none\",\n        serverDeploy: \"none\",\n        install: false,\n      });\n\n      expectSuccess(result);\n    });\n\n    it(\"should work with API none + frontend only\", async () => {\n      const result = await runTRPCTest({\n        projectName: \"api-none-frontend-only\",\n        api: \"none\",\n        frontend: [\"tanstack-router\"],\n        backend: \"none\",\n        runtime: \"none\",\n        database: \"none\",\n        orm: \"none\",\n        auth: \"none\",\n        addons: [\"none\"],\n        examples: [\"none\"],\n        dbSetup: \"none\",\n        webDeploy: \"none\",\n        serverDeploy: \"none\",\n        install: false,\n      });\n\n      expectSuccess(result);\n    });\n\n    it(\"should work with API none + convex\", async () => {\n      const result = await runTRPCTest({\n        projectName: \"api-none-convex\",\n        api: \"none\",\n        frontend: [\"tanstack-router\"],\n        backend: \"convex\",\n        runtime: \"none\",\n        database: \"none\",\n        orm: \"none\",\n        auth: \"none\",\n        addons: [\"none\"],\n        examples: [\"none\"],\n        dbSetup: \"none\",\n        webDeploy: \"none\",\n        serverDeploy: \"none\",\n        install: false,\n      });\n\n      expectSuccess(result);\n    });\n\n    it(\"should fail with API none + examples (non-convex backend)\", async () => {\n      const result = await runTRPCTest({\n        projectName: \"api-none-examples-fail\",\n        api: \"none\",\n        frontend: [\"tanstack-router\"],\n        backend: \"hono\",\n        runtime: \"bun\",\n        database: \"sqlite\",\n        orm: \"drizzle\",\n        auth: \"none\",\n        addons: [\"none\"],\n        examples: [\"todo\"],\n        dbSetup: \"none\",\n        webDeploy: \"none\",\n        serverDeploy: \"none\",\n        expectError: true,\n      });\n\n      expectError(result);\n    });\n\n    it(\"should work with API none + examples + convex backend\", async () => {\n      const result = await runTRPCTest({\n        projectName: \"api-none-examples-convex\",\n        api: \"none\",\n        frontend: [\"tanstack-router\"],\n        backend: \"convex\",\n        runtime: \"none\",\n        database: \"none\",\n        orm: \"none\",\n        auth: \"none\",\n        addons: [\"none\"],\n        examples: [\"todo\"],\n        dbSetup: \"none\",\n        webDeploy: \"none\",\n        serverDeploy: \"none\",\n        install: false,\n      });\n\n      expectSuccess(result);\n    });\n  });\n\n  describe(\"API with Different Database Combinations\", () => {\n    const apiDatabaseCombinations = [\n      { api: \"trpc\", database: \"sqlite\", orm: \"drizzle\" },\n      { api: \"trpc\", database: \"postgres\", orm: \"drizzle\" },\n      { api: \"trpc\", database: \"mysql\", orm: \"prisma\" },\n      { api: \"trpc\", database: \"mongodb\", orm: \"mongoose\" },\n      { api: \"orpc\", database: \"sqlite\", orm: \"drizzle\" },\n      { api: \"orpc\", database: \"postgres\", orm: \"prisma\" },\n      { api: \"orpc\", database: \"mysql\", orm: \"drizzle\" },\n      { api: \"orpc\", database: \"mongodb\", orm: \"prisma\" },\n    ];\n\n    for (const { api, database, orm } of apiDatabaseCombinations) {\n      it(`should work with ${api} + ${database} + ${orm}`, async () => {\n        const result = await runTRPCTest({\n          projectName: `${api}-${database}-${orm}`,\n          api: api as API,\n          database: database as Database,\n          orm: orm as ORM,\n          frontend: [\"tanstack-router\"],\n          backend: \"hono\",\n          runtime: \"bun\",\n          auth: \"none\",\n          addons: [\"none\"],\n          examples: [\"none\"],\n          dbSetup: \"none\",\n          webDeploy: \"none\",\n          serverDeploy: \"none\",\n          install: false,\n        });\n\n        expectSuccess(result);\n      });\n    }\n  });\n\n  describe(\"API with Authentication\", () => {\n    it(\"should work with tRPC + better-auth\", async () => {\n      const result = await runTRPCTest({\n        projectName: \"trpc-better-auth\",\n        api: \"trpc\",\n        auth: \"better-auth\",\n        frontend: [\"tanstack-router\"],\n        backend: \"hono\",\n        runtime: \"bun\",\n        database: \"sqlite\",\n        orm: \"drizzle\",\n        addons: [\"none\"],\n        examples: [\"none\"],\n        dbSetup: \"none\",\n        webDeploy: \"none\",\n        serverDeploy: \"none\",\n        install: false,\n      });\n\n      expectSuccess(result);\n    });\n\n    it(\"should work with oRPC + better-auth\", async () => {\n      const result = await runTRPCTest({\n        projectName: \"orpc-better-auth\",\n        api: \"orpc\",\n        auth: \"better-auth\",\n        frontend: [\"tanstack-router\"],\n        backend: \"hono\",\n        runtime: \"bun\",\n        database: \"sqlite\",\n        orm: \"drizzle\",\n        addons: [\"none\"],\n        examples: [\"none\"],\n        dbSetup: \"none\",\n        webDeploy: \"none\",\n        serverDeploy: \"none\",\n        install: false,\n      });\n\n      expectSuccess(result);\n    });\n\n    it(\"should work with API none + convex + clerk\", async () => {\n      const result = await runTRPCTest({\n        projectName: \"api-none-convex-clerk\",\n        api: \"none\",\n        auth: \"clerk\",\n        frontend: [\"tanstack-router\"],\n        backend: \"convex\",\n        runtime: \"none\",\n        database: \"none\",\n        orm: \"none\",\n        addons: [\"none\"],\n        examples: [\"none\"],\n        dbSetup: \"none\",\n        webDeploy: \"none\",\n        serverDeploy: \"none\",\n        install: false,\n      });\n\n      expectSuccess(result);\n    });\n  });\n\n  describe(\"API with Examples\", () => {\n    it(\"should work with tRPC + todo example\", async () => {\n      const result = await runTRPCTest({\n        projectName: \"trpc-todo\",\n        api: \"trpc\",\n        examples: [\"todo\"],\n        frontend: [\"tanstack-router\"],\n        backend: \"hono\",\n        runtime: \"bun\",\n        database: \"sqlite\",\n        orm: \"drizzle\",\n        auth: \"none\",\n        addons: [\"none\"],\n        dbSetup: \"none\",\n        webDeploy: \"none\",\n        serverDeploy: \"none\",\n        install: false,\n      });\n\n      expectSuccess(result);\n    });\n\n    it(\"should work with oRPC + AI example\", async () => {\n      const result = await runTRPCTest({\n        projectName: \"orpc-ai\",\n        api: \"orpc\",\n        examples: [\"ai\"],\n        frontend: [\"tanstack-router\"],\n        backend: \"hono\",\n        runtime: \"bun\",\n        database: \"sqlite\",\n        orm: \"drizzle\",\n        auth: \"none\",\n        addons: [\"none\"],\n        dbSetup: \"none\",\n        webDeploy: \"none\",\n        serverDeploy: \"none\",\n        install: false,\n      });\n\n      expectSuccess(result);\n    });\n\n    const apiExampleCombinations = [\n      { api: \"trpc\", examples: [\"todo\", \"ai\"] },\n      { api: \"orpc\", examples: [\"todo\", \"ai\"] },\n    ];\n\n    for (const { api, examples } of apiExampleCombinations) {\n      it(`should work with ${api} + both examples`, async () => {\n        const result = await runTRPCTest({\n          projectName: `${api}-both-examples`,\n          api: api as API,\n          examples: examples as Examples[],\n          frontend: [\"tanstack-router\"],\n          backend: \"hono\",\n          runtime: \"bun\",\n          database: \"sqlite\",\n          orm: \"drizzle\",\n          auth: \"none\",\n          addons: [\"none\"],\n          dbSetup: \"none\",\n          webDeploy: \"none\",\n          serverDeploy: \"none\",\n          install: false,\n        });\n\n        expectSuccess(result);\n      });\n    }\n  });\n\n  describe(\"All API Types\", () => {\n    const apis = [\"trpc\", \"orpc\", \"none\"];\n\n    for (const api of apis) {\n      it(`should work with ${api} API`, async () => {\n        const config: TestConfig = {\n          projectName: `test-api-${api}`,\n          api: api as API,\n          addons: [\"none\"],\n          examples: [\"none\"],\n          dbSetup: \"none\",\n          webDeploy: \"none\",\n          serverDeploy: \"none\",\n          install: false,\n        };\n\n        if (api === \"none\") {\n          config.backend = \"none\";\n          config.runtime = \"none\";\n          config.database = \"none\";\n          config.orm = \"none\";\n          config.auth = \"none\";\n          config.frontend = [\"tanstack-router\"];\n        } else {\n          config.backend = \"hono\";\n          config.runtime = \"bun\";\n          config.database = \"sqlite\";\n          config.orm = \"drizzle\";\n          config.auth = \"none\";\n          config.frontend = [\"tanstack-router\"];\n        }\n\n        const result = await runTRPCTest(config);\n        expectSuccess(result);\n      });\n    }\n  });\n\n  describe(\"API Edge Cases\", () => {\n    it(\"should scaffold Fastify oRPC context with matching request shapes\", async () => {\n      const result = await createVirtual({\n        projectName: \"fastify-orpc-request-shape\",\n        api: \"orpc\",\n        frontend: [\"tanstack-router\"],\n        backend: \"fastify\",\n        runtime: \"node\",\n        database: \"sqlite\",\n        orm: \"drizzle\",\n        auth: \"none\",\n        addons: [\"none\"],\n        examples: [\"none\"],\n        dbSetup: \"none\",\n        webDeploy: \"none\",\n        serverDeploy: \"none\",\n        install: false,\n        git: false,\n        packageManager: \"bun\",\n        payments: \"none\",\n      });\n\n      if (result.isErr()) {\n        throw result.error;\n      }\n\n      const files = collectFiles(result.value.root, result.value.root.path);\n      const serverFile = files.get(\"apps/server/src/index.ts\");\n      const contextFile = files.get(\"packages/api/src/context.ts\");\n\n      expect(serverFile).toContain(\"context: await createContext(request.headers)\");\n      expect(contextFile).toContain('import type { IncomingHttpHeaders } from \"node:http\";');\n      expect(contextFile).toContain(\n        \"export async function createContext(req: IncomingHttpHeaders)\",\n      );\n    });\n\n    it(\"should handle API with complex frontend combinations\", async () => {\n      const result = await runTRPCTest({\n        projectName: \"api-complex-frontend\",\n        api: \"trpc\",\n        frontend: [\"tanstack-router\", \"native-bare\"],\n        backend: \"hono\",\n        runtime: \"bun\",\n        database: \"sqlite\",\n        orm: \"drizzle\",\n        auth: \"none\",\n        addons: [\"none\"],\n        examples: [\"none\"],\n        dbSetup: \"none\",\n        webDeploy: \"none\",\n        serverDeploy: \"none\",\n        install: false,\n      });\n\n      expectSuccess(result);\n    });\n\n    it(\"should handle API with workers runtime\", async () => {\n      const result = await runTRPCTest({\n        projectName: \"api-workers\",\n        api: \"trpc\",\n        frontend: [\"tanstack-router\"],\n        backend: \"hono\",\n        runtime: \"workers\",\n        database: \"sqlite\",\n        orm: \"drizzle\",\n        auth: \"none\",\n        addons: [\"none\"],\n        examples: [\"none\"],\n        dbSetup: \"none\",\n        webDeploy: \"none\",\n        serverDeploy: \"cloudflare\",\n        install: false,\n      });\n\n      expectSuccess(result);\n    });\n\n    const runtimeApiCombinations = [\n      { runtime: \"bun\", api: \"trpc\" },\n      { runtime: \"node\", api: \"orpc\" },\n      { runtime: \"workers\", api: \"trpc\" },\n    ];\n\n    for (const { runtime, api } of runtimeApiCombinations) {\n      it(`should handle ${api} with ${runtime} runtime`, async () => {\n        const config: TestConfig = {\n          projectName: `${runtime}-${api}`,\n          api: api as API,\n          runtime: runtime as Runtime,\n          frontend: [\"tanstack-router\"],\n          backend: \"hono\",\n          database: \"sqlite\",\n          orm: \"drizzle\",\n          auth: \"none\",\n          addons: [\"none\"],\n          examples: [\"none\"],\n          dbSetup: \"none\",\n          webDeploy: \"none\",\n          serverDeploy: \"none\",\n          install: false,\n        };\n\n        if (runtime === \"workers\") {\n          config.serverDeploy = \"cloudflare\";\n        }\n\n        const result = await runTRPCTest(config);\n        expectSuccess(result);\n      });\n    }\n  });\n});\n"
  },
  {
    "path": "apps/cli/test/auth.test.ts",
    "content": "import { describe, expect, it } from \"bun:test\";\nimport path from \"node:path\";\n\nimport fs from \"fs-extra\";\n\nimport type { Backend, Database, Frontend, ORM } from \"../src/types\";\nimport {\n  AUTH_PROVIDERS,\n  expectError,\n  expectSuccess,\n  runTRPCTest,\n  type TestConfig,\n} from \"./test-utils\";\n\ndescribe(\"Authentication Configurations\", () => {\n  describe(\"Better-Auth Provider\", () => {\n    it(\"should work with better-auth + database\", async () => {\n      const result = await runTRPCTest({\n        projectName: \"better-auth-db\",\n        auth: \"better-auth\",\n        backend: \"hono\",\n        runtime: \"bun\",\n        database: \"sqlite\",\n        orm: \"drizzle\",\n        api: \"trpc\",\n        frontend: [\"tanstack-router\"],\n        addons: [\"turborepo\"],\n        examples: [\"todo\"],\n        dbSetup: \"none\",\n        webDeploy: \"none\",\n        serverDeploy: \"none\",\n        install: false,\n      });\n\n      expectSuccess(result);\n    });\n\n    const databases = [\"sqlite\", \"postgres\", \"mysql\"];\n    for (const database of databases) {\n      it(`should work with better-auth + ${database}`, async () => {\n        const result = await runTRPCTest({\n          projectName: `better-auth-${database}`,\n          auth: \"better-auth\",\n          backend: \"hono\",\n          runtime: \"bun\",\n          database: database as Database,\n          orm: \"drizzle\",\n          api: \"trpc\",\n          frontend: [\"tanstack-router\"],\n          addons: [\"turborepo\"],\n          examples: [\"todo\"],\n          dbSetup: \"none\",\n          webDeploy: \"none\",\n          serverDeploy: \"none\",\n          install: false,\n        });\n\n        expectSuccess(result);\n      });\n    }\n\n    it(\"should work with better-auth + mongodb + mongoose\", async () => {\n      const result = await runTRPCTest({\n        projectName: \"better-auth-mongodb\",\n        auth: \"better-auth\",\n        backend: \"hono\",\n        runtime: \"bun\",\n        database: \"mongodb\",\n        orm: \"mongoose\",\n        api: \"trpc\",\n        frontend: [\"tanstack-router\"],\n        addons: [\"turborepo\"],\n        examples: [\"todo\"],\n        dbSetup: \"none\",\n        webDeploy: \"none\",\n        serverDeploy: \"none\",\n        install: false,\n      });\n\n      expectSuccess(result);\n    });\n\n    it(\"should add nextCookies plugin for Next.js self backend\", async () => {\n      const result = await runTRPCTest({\n        projectName: \"better-auth-next-self-plugins\",\n        auth: \"better-auth\",\n        backend: \"self\",\n        runtime: \"none\",\n        database: \"postgres\",\n        orm: \"drizzle\",\n        api: \"trpc\",\n        frontend: [\"next\"],\n        addons: [\"turborepo\"],\n        examples: [\"none\"],\n        dbSetup: \"none\",\n        webDeploy: \"cloudflare\",\n        serverDeploy: \"none\",\n        install: false,\n      });\n\n      expectSuccess(result);\n      if (!result.projectDir) {\n        throw new Error(\"Expected projectDir to be defined\");\n      }\n\n      const authFile = await fs.readFile(\n        path.join(result.projectDir, \"packages/auth/src/index.ts\"),\n        \"utf8\",\n      );\n\n      expect(authFile).toContain('import { nextCookies } from \"better-auth/next-js\";');\n      expect(authFile).toContain(\"nextCookies()\");\n    });\n\n    it(\"should add tanstackStartCookies plugin for TanStack Start self backend\", async () => {\n      const result = await runTRPCTest({\n        projectName: \"better-auth-tanstack-start-self-plugins\",\n        auth: \"better-auth\",\n        backend: \"self\",\n        runtime: \"none\",\n        database: \"postgres\",\n        orm: \"drizzle\",\n        api: \"trpc\",\n        frontend: [\"tanstack-start\"],\n        addons: [\"turborepo\"],\n        examples: [\"none\"],\n        dbSetup: \"none\",\n        webDeploy: \"none\",\n        serverDeploy: \"none\",\n        install: false,\n      });\n\n      expectSuccess(result);\n      if (!result.projectDir) {\n        throw new Error(\"Expected projectDir to be defined\");\n      }\n\n      const authFile = await fs.readFile(\n        path.join(result.projectDir, \"packages/auth/src/index.ts\"),\n        \"utf8\",\n      );\n\n      expect(authFile).toContain(\n        'import { tanstackStartCookies } from \"better-auth/tanstack-start\";',\n      );\n      expect(authFile).toContain(\"tanstackStartCookies()\");\n    });\n\n    it(\"should fail with better-auth + no database (non-convex)\", async () => {\n      const result = await runTRPCTest({\n        projectName: \"better-auth-no-db-fail\",\n        auth: \"better-auth\",\n        backend: \"hono\",\n        runtime: \"bun\",\n        database: \"none\",\n        orm: \"none\",\n        api: \"trpc\",\n        frontend: [\"tanstack-router\"],\n        addons: [\"turborepo\"],\n        examples: [\"none\"],\n        dbSetup: \"none\",\n        webDeploy: \"none\",\n        serverDeploy: \"none\",\n        install: false,\n      });\n\n      // This should actually succeed - better-auth can work without a database\n      // if no examples require one\n      expectSuccess(result);\n    });\n\n    it(\"should work with better-auth + convex backend (tanstack-router)\", async () => {\n      const result = await runTRPCTest({\n        projectName: \"better-auth-convex-success\",\n        auth: \"better-auth\",\n        backend: \"convex\",\n        runtime: \"none\",\n        database: \"none\",\n        orm: \"none\",\n        api: \"none\",\n        frontend: [\"tanstack-router\"],\n        addons: [\"turborepo\"],\n        examples: [\"todo\"],\n        dbSetup: \"none\",\n        webDeploy: \"none\",\n        serverDeploy: \"none\",\n      });\n\n      expectSuccess(result);\n    });\n\n    it(\"should scaffold react-router with Convex Better Auth wiring\", async () => {\n      const result = await runTRPCTest({\n        projectName: \"better-auth-convex-react-router\",\n        auth: \"better-auth\",\n        backend: \"convex\",\n        runtime: \"none\",\n        database: \"none\",\n        orm: \"none\",\n        api: \"none\",\n        frontend: [\"react-router\"],\n        addons: [\"turborepo\"],\n        examples: [\"todo\"],\n        dbSetup: \"none\",\n        webDeploy: \"none\",\n        serverDeploy: \"none\",\n        install: false,\n      });\n\n      expectSuccess(result);\n      if (!result.projectDir) {\n        throw new Error(\"Expected projectDir to be defined\");\n      }\n\n      const rootFile = await fs.readFile(\n        path.join(result.projectDir, \"apps/web/src/root.tsx\"),\n        \"utf8\",\n      );\n      const authClientFile = await fs.readFile(\n        path.join(result.projectDir, \"apps/web/src/lib/auth-client.ts\"),\n        \"utf8\",\n      );\n      const dashboardFile = await fs.readFile(\n        path.join(result.projectDir, \"apps/web/src/routes/dashboard.tsx\"),\n        \"utf8\",\n      );\n\n      expect(rootFile).toContain(\"ConvexBetterAuthProvider\");\n      expect(rootFile).toContain('import { authClient } from \"@/lib/auth-client\";');\n      expect(authClientFile).toContain(\"crossDomainClient(), convexClient()\");\n      expect(dashboardFile).toContain(\"Authenticated\");\n      expect(dashboardFile).toContain(\"Unauthenticated\");\n    });\n\n    const convexUnsupportedFrontends = [\"nuxt\", \"svelte\", \"solid\", \"astro\"] as const;\n    for (const frontend of convexUnsupportedFrontends) {\n      it(`should fail with Convex Better Auth + ${frontend}`, async () => {\n        const result = await runTRPCTest({\n          projectName: `better-auth-convex-${frontend}-fail`,\n          auth: \"better-auth\",\n          backend: \"convex\",\n          runtime: \"none\",\n          database: \"none\",\n          orm: \"none\",\n          api: \"none\",\n          frontend: [frontend],\n          addons: [\"turborepo\"],\n          examples: [\"none\"],\n          dbSetup: \"none\",\n          webDeploy: \"none\",\n          serverDeploy: \"none\",\n          install: false,\n          expectError: true,\n        });\n\n        expectError(result, \"Better Auth with '--backend convex' is not compatible\");\n      });\n    }\n\n    const compatibleFrontends = [\n      \"tanstack-router\",\n      \"react-router\",\n      \"tanstack-start\",\n      \"next\",\n      \"nuxt\",\n      \"svelte\",\n      \"solid\",\n      \"native-bare\",\n      \"native-uniwind\",\n      \"native-unistyles\",\n    ];\n\n    for (const frontend of compatibleFrontends) {\n      it(`should work with better-auth + ${frontend}`, async () => {\n        const config: TestConfig = {\n          projectName: `better-auth-${frontend}`,\n          auth: \"better-auth\",\n          backend: \"hono\",\n          runtime: \"bun\",\n          database: \"sqlite\",\n          orm: \"drizzle\",\n          frontend: [frontend as Frontend],\n          addons: [\"turborepo\"],\n          examples: [\"todo\"],\n          dbSetup: \"none\",\n          webDeploy: \"none\",\n          serverDeploy: \"none\",\n          install: false,\n        };\n\n        // Handle API compatibility\n        if ([\"nuxt\", \"svelte\", \"solid\"].includes(frontend)) {\n          config.api = \"orpc\";\n        } else {\n          config.api = \"trpc\";\n        }\n\n        const result = await runTRPCTest(config);\n        expectSuccess(result);\n      });\n    }\n  });\n\n  describe(\"Clerk Provider\", () => {\n    it(\"should work with clerk + convex\", async () => {\n      const result = await runTRPCTest({\n        projectName: \"clerk-convex\",\n        auth: \"clerk\",\n        backend: \"convex\",\n        runtime: \"none\",\n        database: \"none\",\n        orm: \"none\",\n        api: \"none\",\n        frontend: [\"tanstack-router\"],\n        addons: [\"turborepo\"],\n        examples: [\"todo\"],\n        dbSetup: \"none\",\n        webDeploy: \"none\",\n        serverDeploy: \"none\",\n        install: false,\n      });\n\n      expectSuccess(result);\n    });\n\n    it(\"should work with clerk + hono backend\", async () => {\n      const result = await runTRPCTest({\n        projectName: \"clerk-hono-success\",\n        auth: \"clerk\",\n        backend: \"hono\",\n        runtime: \"bun\",\n        database: \"sqlite\",\n        examples: [\"todo\"],\n        dbSetup: \"none\",\n        webDeploy: \"none\",\n        serverDeploy: \"none\",\n        addons: [\"turborepo\"],\n        orm: \"drizzle\",\n        api: \"trpc\",\n        frontend: [\"tanstack-router\"],\n        install: false,\n      });\n\n      expectSuccess(result);\n    });\n\n    it(\"should work with clerk + self backend\", async () => {\n      const result = await runTRPCTest({\n        projectName: \"clerk-self-success\",\n        auth: \"clerk\",\n        backend: \"self\",\n        runtime: \"none\",\n        database: \"sqlite\",\n        orm: \"drizzle\",\n        api: \"trpc\",\n        frontend: [\"next\"],\n        addons: [\"turborepo\"],\n        examples: [\"todo\"],\n        dbSetup: \"none\",\n        webDeploy: \"none\",\n        serverDeploy: \"none\",\n        install: false,\n      });\n\n      expectSuccess(result);\n    });\n\n    it(\"should scaffold Next.js Clerk middleware without importing shared server env\", async () => {\n      const result = await runTRPCTest({\n        projectName: \"clerk-next-hono-current\",\n        auth: \"clerk\",\n        backend: \"hono\",\n        runtime: \"bun\",\n        database: \"sqlite\",\n        orm: \"drizzle\",\n        api: \"trpc\",\n        frontend: [\"next\"],\n        addons: [\"turborepo\"],\n        examples: [\"todo\"],\n        dbSetup: \"none\",\n        webDeploy: \"none\",\n        serverDeploy: \"none\",\n        install: false,\n      });\n\n      expectSuccess(result);\n      if (!result.projectDir) {\n        throw new Error(\"Expected projectDir to be defined\");\n      }\n\n      const proxyFile = await fs.readFile(\n        path.join(result.projectDir, \"apps/web/src/proxy.ts\"),\n        \"utf8\",\n      );\n      const dashboardFile = await fs.readFile(\n        path.join(result.projectDir, \"apps/web/src/app/dashboard/page.tsx\"),\n        \"utf8\",\n      );\n      const apiContextFile = await fs.readFile(\n        path.join(result.projectDir, \"packages/api/src/context.ts\"),\n        \"utf8\",\n      );\n      const serverEnvPackageFile = await fs.readFile(\n        path.join(result.projectDir, \"packages/env/src/server.ts\"),\n        \"utf8\",\n      );\n      const serverEnvFile = await fs.readFile(\n        path.join(result.projectDir, \"apps/server/.env\"),\n        \"utf8\",\n      );\n\n      expect(proxyFile).not.toContain('/env/server\"');\n      expect(proxyFile).not.toContain(\"env.CLERK_SECRET_KEY\");\n      expect(dashboardFile).not.toContain(\"SignedIn\");\n      expect(dashboardFile).not.toContain(\"SignedOut\");\n      expect(dashboardFile).toContain(\"useUser\");\n      expect(dashboardFile).toContain(\"privateData.queryOptions()\");\n      expect(apiContextFile).toContain(\"type ClerkContextAuth\");\n      expect(apiContextFile).toContain(\"type ClerkRequestContext\");\n      expect(apiContextFile).toContain(\"function toClerkContextAuth\");\n      expect(apiContextFile).toContain(\"Promise<ClerkRequestContext>\");\n      expect(apiContextFile).toContain(\"publishableKey: env.CLERK_PUBLISHABLE_KEY\");\n      expect(apiContextFile).toContain(\"authorizedParties: [env.CORS_ORIGIN]\");\n      expect(serverEnvPackageFile).toContain(\"CLERK_PUBLISHABLE_KEY\");\n      expect(serverEnvPackageFile).toContain(\"CLERK_SECRET_KEY\");\n      expect(serverEnvFile).toContain(\"CLERK_PUBLISHABLE_KEY=\");\n      expect(serverEnvFile).toContain(\"CLERK_SECRET_KEY=\");\n    });\n\n    it(\"should scaffold TanStack Start Clerk templates without stale control components\", async () => {\n      const result = await runTRPCTest({\n        projectName: \"clerk-tanstack-start-hono-current\",\n        auth: \"clerk\",\n        backend: \"hono\",\n        runtime: \"bun\",\n        database: \"sqlite\",\n        orm: \"drizzle\",\n        api: \"trpc\",\n        frontend: [\"tanstack-start\"],\n        addons: [\"turborepo\"],\n        examples: [\"todo\"],\n        dbSetup: \"none\",\n        webDeploy: \"none\",\n        serverDeploy: \"none\",\n        install: false,\n      });\n\n      expectSuccess(result);\n      if (!result.projectDir) {\n        throw new Error(\"Expected projectDir to be defined\");\n      }\n\n      const startFile = await fs.readFile(\n        path.join(result.projectDir, \"apps/web/src/start.ts\"),\n        \"utf8\",\n      );\n      const dashboardFile = await fs.readFile(\n        path.join(result.projectDir, \"apps/web/src/routes/dashboard.tsx\"),\n        \"utf8\",\n      );\n\n      expect(startFile).not.toContain('/env/server\"');\n      expect(startFile).not.toContain(\"env.CLERK_SECRET_KEY\");\n      expect(dashboardFile).not.toContain(\"SignedIn\");\n      expect(dashboardFile).not.toContain(\"SignedOut\");\n      expect(dashboardFile).toContain(\"useUser\");\n      expect(dashboardFile).toContain(\"privateData.queryOptions()\");\n    });\n\n    it(\"should scaffold Clerk native auth with the current Expo SDK flow\", async () => {\n      const result = await runTRPCTest({\n        projectName: \"clerk-native-hono-current\",\n        auth: \"clerk\",\n        backend: \"hono\",\n        runtime: \"bun\",\n        database: \"sqlite\",\n        orm: \"drizzle\",\n        api: \"trpc\",\n        frontend: [\"native-uniwind\"],\n        addons: [\"turborepo\"],\n        examples: [\"todo\"],\n        dbSetup: \"none\",\n        webDeploy: \"none\",\n        serverDeploy: \"none\",\n        install: false,\n      });\n\n      expectSuccess(result);\n      if (!result.projectDir) {\n        throw new Error(\"Expected projectDir to be defined\");\n      }\n\n      const nativePackageFile = await fs.readFile(\n        path.join(result.projectDir, \"apps/native/package.json\"),\n        \"utf8\",\n      );\n      const signInFile = await fs.readFile(\n        path.join(result.projectDir, \"apps/native/app/(auth)/sign-in.tsx\"),\n        \"utf8\",\n      );\n      const signUpFile = await fs.readFile(\n        path.join(result.projectDir, \"apps/native/app/(auth)/sign-up.tsx\"),\n        \"utf8\",\n      );\n\n      expect(nativePackageFile).toContain('\"@clerk/expo\": \"^3.1.3\"');\n\n      expect(signInFile).not.toContain(\"setActive\");\n      expect(signInFile).not.toContain(\"signIn.create\");\n      expect(signInFile).toContain(\"const { signIn, errors, fetchStatus } = useSignIn()\");\n      expect(signInFile).toContain(\"await signIn.password\");\n      expect(signInFile).toContain(\"await signIn.finalize\");\n\n      expect(signUpFile).not.toContain(\"setActive\");\n      expect(signUpFile).not.toContain(\"prepareEmailAddressVerification\");\n      expect(signUpFile).not.toContain(\"attemptEmailAddressVerification\");\n      expect(signUpFile).toContain(\"const { signUp, errors, fetchStatus } = useSignUp()\");\n      expect(signUpFile).toContain(\"await signUp.password\");\n      expect(signUpFile).toContain(\"await signUp.verifications.sendEmailCode()\");\n      expect(signUpFile).toContain(\"await signUp.verifications.verifyEmailCode\");\n      expect(signUpFile).toContain(\"await signUp.finalize\");\n      expect(signUpFile).toContain('nativeID=\"clerk-captcha\"');\n    });\n\n    const compatibleFrontends = [\n      \"tanstack-router\",\n      \"react-router\",\n      \"tanstack-start\",\n      \"next\",\n      \"native-bare\",\n      \"native-uniwind\",\n      \"native-unistyles\",\n    ];\n\n    for (const frontend of compatibleFrontends) {\n      it(`should work with clerk + ${frontend}`, async () => {\n        const result = await runTRPCTest({\n          projectName: `clerk-${frontend}`,\n          auth: \"clerk\",\n          backend: \"convex\",\n          runtime: \"none\",\n          database: \"none\",\n          webDeploy: \"none\",\n          serverDeploy: \"none\",\n          addons: [\"turborepo\"],\n          dbSetup: \"none\",\n          examples: [\"todo\"],\n          orm: \"none\",\n          api: \"none\",\n          frontend: [frontend as Frontend],\n          install: false,\n        });\n\n        expectSuccess(result);\n      });\n    }\n\n    const incompatibleFrontends = [\"nuxt\", \"svelte\", \"solid\", \"astro\"];\n\n    for (const frontend of incompatibleFrontends) {\n      it(`should fail with clerk + ${frontend}`, async () => {\n        const result = await runTRPCTest({\n          projectName: `clerk-${frontend}-fail`,\n          auth: \"clerk\",\n          backend: \"convex\",\n          runtime: \"none\",\n          database: \"none\",\n          orm: \"none\",\n          api: \"none\",\n          frontend: [frontend as Frontend],\n          addons: [\"turborepo\"],\n          examples: [\"todo\"],\n          dbSetup: \"none\",\n          webDeploy: \"none\",\n          serverDeploy: \"none\",\n          expectError: true,\n        });\n\n        expectError(result, \"Clerk authentication is not compatible\");\n      });\n    }\n  });\n\n  describe(\"No Authentication\", () => {\n    it(\"should work with auth none\", async () => {\n      const result = await runTRPCTest({\n        projectName: \"no-auth\",\n        auth: \"none\",\n        backend: \"hono\",\n        runtime: \"bun\",\n        database: \"sqlite\",\n        orm: \"drizzle\",\n        api: \"trpc\",\n        frontend: [\"tanstack-router\"],\n        addons: [\"turborepo\"],\n        examples: [\"todo\"],\n        dbSetup: \"none\",\n        webDeploy: \"none\",\n        serverDeploy: \"none\",\n        install: false,\n      });\n\n      expectSuccess(result);\n    });\n\n    it(\"should work with auth none + no database\", async () => {\n      // When backend is 'none', examples are automatically cleared\n      const result = await runTRPCTest({\n        projectName: \"no-auth-no-db\",\n        auth: \"none\",\n        backend: \"none\",\n        runtime: \"none\",\n        database: \"none\",\n        orm: \"none\",\n        api: \"none\",\n        frontend: [\"tanstack-router\"],\n        addons: [\"turborepo\"],\n        examples: [\"none\"],\n        dbSetup: \"none\",\n        webDeploy: \"none\",\n        serverDeploy: \"none\",\n        install: false,\n      });\n\n      expectSuccess(result);\n    });\n\n    it(\"should work with auth none + convex\", async () => {\n      const result = await runTRPCTest({\n        projectName: \"no-auth-convex\",\n        auth: \"none\",\n        backend: \"convex\",\n        runtime: \"none\",\n        database: \"none\",\n        orm: \"none\",\n        api: \"none\",\n        frontend: [\"tanstack-router\"],\n        addons: [\"turborepo\"],\n        examples: [\"todo\"],\n        dbSetup: \"none\",\n        webDeploy: \"none\",\n        serverDeploy: \"none\",\n        install: false,\n      });\n\n      expectSuccess(result);\n    });\n  });\n\n  describe(\"Authentication with Different Backends\", () => {\n    const backends = [\"hono\", \"express\", \"fastify\", \"elysia\", \"self\"];\n\n    for (const backend of backends) {\n      it(`should work with better-auth + ${backend}`, async () => {\n        const config: TestConfig = {\n          projectName: `better-auth-${backend}`,\n          auth: \"better-auth\",\n          backend: backend as Backend,\n          database: \"sqlite\",\n          orm: \"drizzle\",\n          api: \"trpc\",\n          frontend: backend === \"self\" ? [\"next\"] : [\"tanstack-router\"],\n          addons: [\"turborepo\"],\n          examples: [\"todo\"],\n          dbSetup: \"none\",\n          webDeploy: \"none\",\n          serverDeploy: \"none\",\n          install: false,\n        };\n\n        // Set appropriate runtime\n        if (backend === \"elysia\") {\n          config.runtime = \"bun\";\n        } else if (backend === \"self\") {\n          config.runtime = \"none\";\n        } else {\n          config.runtime = \"bun\";\n        }\n\n        const result = await runTRPCTest(config);\n        expectSuccess(result);\n      });\n    }\n  });\n\n  describe(\"Authentication with Different ORMs\", () => {\n    const ormCombinations = [\n      { database: \"sqlite\", orm: \"drizzle\" },\n      { database: \"sqlite\", orm: \"prisma\" },\n      { database: \"postgres\", orm: \"drizzle\" },\n      { database: \"postgres\", orm: \"prisma\" },\n      { database: \"mysql\", orm: \"drizzle\" },\n      { database: \"mysql\", orm: \"prisma\" },\n      { database: \"mongodb\", orm: \"mongoose\" },\n      { database: \"mongodb\", orm: \"prisma\" },\n    ];\n\n    for (const { database, orm } of ormCombinations) {\n      it(`should work with better-auth + ${database} + ${orm}`, async () => {\n        const result = await runTRPCTest({\n          projectName: `better-auth-${database}-${orm}`,\n          auth: \"better-auth\",\n          backend: \"hono\",\n          runtime: \"bun\",\n          database: database as Database,\n          orm: orm as ORM,\n          api: \"trpc\",\n          frontend: [\"tanstack-router\"],\n          addons: [\"turborepo\"],\n          examples: [\"todo\"],\n          dbSetup: \"none\",\n          webDeploy: \"none\",\n          serverDeploy: \"none\",\n          install: false,\n        });\n\n        expectSuccess(result);\n      });\n    }\n  });\n\n  describe(\"All Auth Providers\", () => {\n    for (const auth of AUTH_PROVIDERS) {\n      it(`should work with ${auth} in appropriate setup`, async () => {\n        const config: TestConfig = {\n          projectName: `test-${auth}`,\n          auth,\n          frontend: [\"tanstack-router\"],\n          addons: [\"turborepo\"],\n          examples: [\"todo\"],\n          dbSetup: \"none\",\n          webDeploy: \"none\",\n          serverDeploy: \"none\",\n          install: false,\n        };\n\n        // Set appropriate setup for each auth provider\n        if (auth === \"clerk\") {\n          config.backend = \"convex\";\n          config.runtime = \"none\";\n          config.database = \"none\";\n          config.orm = \"none\";\n          config.api = \"none\";\n        } else if (auth === \"better-auth\") {\n          config.backend = \"hono\";\n          config.runtime = \"bun\";\n          config.database = \"sqlite\";\n          config.orm = \"drizzle\";\n          config.api = \"trpc\";\n        } else {\n          // none\n          config.backend = \"hono\";\n          config.runtime = \"bun\";\n          config.database = \"sqlite\";\n          config.orm = \"drizzle\";\n          config.api = \"trpc\";\n        }\n\n        const result = await runTRPCTest(config);\n        expectSuccess(result);\n      });\n    }\n  });\n\n  describe(\"Auth Edge Cases\", () => {\n    it(\"should handle auth with complex frontend combinations\", async () => {\n      const result = await runTRPCTest({\n        projectName: \"auth-web-native-combo\",\n        auth: \"better-auth\",\n        backend: \"hono\",\n        runtime: \"bun\",\n        database: \"sqlite\",\n        orm: \"drizzle\",\n        api: \"trpc\",\n        frontend: [\"tanstack-router\", \"native-bare\"],\n        addons: [\"turborepo\"],\n        examples: [\"todo\"],\n        dbSetup: \"none\",\n        webDeploy: \"none\",\n        serverDeploy: \"none\",\n        install: false,\n      });\n\n      expectSuccess(result);\n    });\n\n    it(\"should handle auth constraints with workers runtime\", async () => {\n      const result = await runTRPCTest({\n        projectName: \"auth-workers\",\n        auth: \"better-auth\",\n        backend: \"hono\",\n        runtime: \"workers\",\n        database: \"sqlite\",\n        orm: \"drizzle\",\n        api: \"trpc\",\n        frontend: [\"tanstack-router\"],\n        addons: [\"turborepo\"],\n        examples: [\"todo\"],\n        dbSetup: \"none\",\n        webDeploy: \"none\",\n        serverDeploy: \"cloudflare\",\n        install: false,\n      });\n\n      expectSuccess(result);\n    });\n  });\n});\n"
  },
  {
    "path": "apps/cli/test/backend-runtime.test.ts",
    "content": "import { describe, it } from \"bun:test\";\n\nimport type { Backend, Frontend, Runtime } from \"../src/types\";\nimport { expectError, expectSuccess, runTRPCTest, type TestConfig } from \"./test-utils\";\n\ndescribe(\"Backend and Runtime Combinations\", () => {\n  describe(\"Valid Backend-Runtime Combinations\", () => {\n    const validCombinations = [\n      // Standard backend-runtime combinations\n      { backend: \"hono\" as const, runtime: \"bun\" as const },\n      { backend: \"hono\" as const, runtime: \"node\" as const },\n      { backend: \"hono\" as const, runtime: \"workers\" as const },\n\n      { backend: \"express\" as const, runtime: \"bun\" as const },\n      { backend: \"express\" as const, runtime: \"node\" as const },\n\n      { backend: \"fastify\" as const, runtime: \"bun\" as const },\n      { backend: \"fastify\" as const, runtime: \"node\" as const },\n\n      { backend: \"elysia\" as const, runtime: \"bun\" as const },\n\n      // Special cases\n      { backend: \"convex\" as const, runtime: \"none\" as const },\n      { backend: \"none\" as const, runtime: \"none\" as const },\n      { backend: \"self\" as const, runtime: \"none\" as const },\n    ];\n\n    for (const { backend, runtime } of validCombinations) {\n      it(`should work with ${backend} + ${runtime}`, async () => {\n        const config: TestConfig = {\n          projectName: `${backend}-${runtime}`,\n          backend,\n          runtime,\n          frontend: [\"tanstack-router\"],\n          webDeploy: \"none\",\n          serverDeploy: \"none\",\n          addons: [\"none\"],\n          examples: [\"none\"],\n          dbSetup: \"none\",\n          install: false,\n        };\n\n        // Set appropriate defaults based on backend\n        if (backend === \"convex\") {\n          config.database = \"none\";\n          config.orm = \"none\";\n          config.auth = \"clerk\";\n          config.api = \"none\";\n        } else if (backend === \"none\") {\n          config.database = \"none\";\n          config.orm = \"none\";\n          config.auth = \"none\";\n          config.api = \"none\";\n        } else if (backend === \"self\") {\n          config.frontend = [\"next\"];\n          config.database = \"sqlite\";\n          config.orm = \"drizzle\";\n          config.auth = \"better-auth\";\n          config.api = \"trpc\";\n        } else {\n          config.database = \"sqlite\";\n          config.orm = \"drizzle\";\n          config.auth = \"none\";\n          config.api = \"trpc\";\n        }\n\n        // Set server deployment for workers runtime\n        if (runtime === \"workers\") {\n          config.serverDeploy = \"cloudflare\";\n        }\n\n        const result = await runTRPCTest(config);\n        expectSuccess(result);\n      });\n    }\n  });\n\n  describe(\"Invalid Backend-Runtime Combinations\", () => {\n    const invalidCombinations = [\n      // Workers runtime only works with Hono\n      {\n        backend: \"express\" as const,\n        runtime: \"workers\" as const,\n        error: \"Cloudflare Workers runtime (--runtime workers) is only supported with Hono backend\",\n      },\n      {\n        backend: \"fastify\",\n        runtime: \"workers\",\n        error: \"Cloudflare Workers runtime (--runtime workers) is only supported with Hono backend\",\n      },\n      {\n        backend: \"elysia\",\n        runtime: \"workers\",\n        error: \"Cloudflare Workers runtime (--runtime workers) is only supported with Hono backend\",\n      },\n\n      // Convex backend requires runtime none\n      {\n        backend: \"convex\",\n        runtime: \"bun\",\n        error: \"Convex backend requires '--runtime none'\",\n      },\n      {\n        backend: \"convex\",\n        runtime: \"node\",\n        error: \"Convex backend requires '--runtime none'\",\n      },\n      {\n        backend: \"convex\",\n        runtime: \"workers\",\n        error: \"Convex backend requires '--runtime none'\",\n      },\n\n      // Backend none requires runtime none\n      {\n        backend: \"none\",\n        runtime: \"bun\",\n        error: \"Backend 'none' requires '--runtime none'\",\n      },\n      {\n        backend: \"none\",\n        runtime: \"node\",\n        error: \"Backend 'none' requires '--runtime none'\",\n      },\n      {\n        backend: \"none\",\n        runtime: \"workers\",\n        error: \"Backend 'none' requires '--runtime none'\",\n      },\n\n      // Self backend requires runtime none\n      {\n        backend: \"self\",\n        runtime: \"bun\",\n        error: \"Backend 'self' (fullstack) requires '--runtime none'\",\n        frontend: [\"next\"], // Need to specify Next.js frontend for self backend\n      },\n      {\n        backend: \"self\",\n        runtime: \"node\",\n        error: \"Backend 'self' (fullstack) requires '--runtime none'\",\n        frontend: [\"next\"], // Need to specify Next.js frontend for self backend\n      },\n      {\n        backend: \"self\",\n        runtime: \"workers\",\n        error: \"Backend 'self' (fullstack) requires '--runtime none'\",\n        frontend: [\"next\"], // Need to specify Next.js frontend for self backend\n      },\n\n      // Runtime none only works with convex, none, or self backend\n      {\n        backend: \"hono\",\n        runtime: \"none\",\n        error:\n          \"'--runtime none' is only supported with '--backend convex', '--backend none', or '--backend self'\",\n      },\n      {\n        backend: \"express\",\n        runtime: \"none\",\n        error:\n          \"'--runtime none' is only supported with '--backend convex', '--backend none', or '--backend self'\",\n      },\n    ];\n\n    for (const { backend, runtime, error, frontend } of invalidCombinations) {\n      it(`should fail with ${backend} + ${runtime}`, async () => {\n        const config: TestConfig = {\n          projectName: `invalid-${backend}-${runtime}`,\n          backend: backend as Backend,\n          runtime: runtime as Runtime,\n          frontend: (frontend || [\"tanstack-router\"]) as Frontend[],\n          auth: \"none\",\n          api: \"trpc\",\n          addons: [\"none\"],\n          examples: [\"none\"],\n          dbSetup: \"none\",\n          webDeploy: \"none\",\n          serverDeploy: \"none\",\n          expectError: true,\n        };\n\n        // Set appropriate defaults based on backend\n        if (backend === \"convex\") {\n          config.database = \"none\";\n          config.orm = \"none\";\n          config.auth = \"clerk\";\n          config.api = \"none\";\n        } else if (backend === \"none\") {\n          config.database = \"none\";\n          config.orm = \"none\";\n          config.auth = \"none\";\n          config.api = \"none\";\n        } else if (backend === \"self\") {\n          config.database = \"sqlite\";\n          config.orm = \"drizzle\";\n          config.auth = \"better-auth\";\n          config.api = \"trpc\";\n        } else {\n          config.database = \"sqlite\";\n          config.orm = \"drizzle\";\n          config.auth = \"none\";\n          config.api = \"trpc\";\n        }\n\n        const result = await runTRPCTest(config);\n        expectError(result, error);\n      });\n    }\n  });\n\n  describe(\"Convex Backend Constraints\", () => {\n    it(\"should enforce all convex constraints\", async () => {\n      const result = await runTRPCTest({\n        projectName: \"convex-app\",\n        backend: \"convex\",\n        runtime: \"none\",\n        database: \"none\",\n        orm: \"none\",\n        auth: \"clerk\",\n        api: \"none\",\n        frontend: [\"tanstack-router\"],\n        addons: [\"none\"],\n        examples: [\"none\"],\n        dbSetup: \"none\",\n        webDeploy: \"none\",\n        serverDeploy: \"none\",\n        install: false,\n      });\n\n      expectSuccess(result);\n    });\n\n    it(\"should work convex with better-auth (tanstack-router)\", async () => {\n      const result = await runTRPCTest({\n        projectName: \"convex-better-auth-success\",\n        backend: \"convex\",\n        runtime: \"none\",\n        database: \"none\",\n        orm: \"none\",\n        auth: \"better-auth\",\n        api: \"none\",\n        frontend: [\"tanstack-router\"],\n        addons: [\"none\"],\n        examples: [\"none\"],\n        dbSetup: \"none\",\n        webDeploy: \"none\",\n        serverDeploy: \"none\",\n      });\n\n      expectSuccess(result);\n    });\n\n    it(\"should fail convex with database\", async () => {\n      const result = await runTRPCTest({\n        projectName: \"convex-with-db\",\n        backend: \"convex\",\n        runtime: \"none\",\n        database: \"postgres\",\n        orm: \"drizzle\",\n        auth: \"clerk\",\n        api: \"none\",\n        frontend: [\"tanstack-router\"],\n        addons: [\"none\"],\n        examples: [\"none\"],\n        dbSetup: \"none\",\n        webDeploy: \"none\",\n        serverDeploy: \"none\",\n        expectError: true,\n      });\n\n      expectError(result, \"Convex backend requires '--database none'\");\n    });\n  });\n\n  describe(\"Workers Runtime Constraints\", () => {\n    it(\"should work with workers + hono + compatible database\", async () => {\n      const result = await runTRPCTest({\n        projectName: \"workers-compatible\",\n        backend: \"hono\",\n        runtime: \"workers\",\n        database: \"sqlite\",\n        orm: \"drizzle\",\n        auth: \"none\",\n        api: \"trpc\",\n        frontend: [\"tanstack-router\"],\n        addons: [\"none\"],\n        examples: [\"none\"],\n        dbSetup: \"none\",\n        webDeploy: \"none\",\n        serverDeploy: \"cloudflare\", // Workers requires server deployment\n        install: false,\n      });\n\n      expectSuccess(result);\n    });\n\n    it(\"should fail workers with mongodb\", async () => {\n      const result = await runTRPCTest({\n        projectName: \"workers-mongodb\",\n        backend: \"hono\",\n        runtime: \"workers\",\n        database: \"mongodb\",\n        orm: \"prisma\",\n        auth: \"none\",\n        api: \"trpc\",\n        frontend: [\"tanstack-router\"],\n        addons: [\"none\"],\n        examples: [\"none\"],\n        dbSetup: \"none\",\n        webDeploy: \"none\",\n        serverDeploy: \"none\",\n        expectError: true,\n      });\n\n      expectError(\n        result,\n        \"Cloudflare Workers runtime (--runtime workers) is not compatible with MongoDB database\",\n      );\n    });\n\n    it(\"should fail workers without server deployment\", async () => {\n      const result = await runTRPCTest({\n        projectName: \"workers-no-deploy\",\n        backend: \"hono\",\n        runtime: \"workers\",\n        database: \"sqlite\",\n        orm: \"drizzle\",\n        auth: \"none\",\n        api: \"trpc\",\n        frontend: [\"tanstack-router\"],\n        addons: [\"none\"],\n        examples: [\"none\"],\n        dbSetup: \"none\",\n        webDeploy: \"none\",\n        serverDeploy: \"none\",\n        expectError: true,\n      });\n\n      expectError(result, \"Cloudflare Workers runtime requires a server deployment\");\n    });\n  });\n\n  describe(\"All Backend Types\", () => {\n    const backends = [\"hono\", \"express\", \"fastify\", \"elysia\", \"convex\", \"none\", \"self\"] as const;\n\n    for (const backend of backends) {\n      it(`should work with appropriate defaults for ${backend}`, async () => {\n        const config: TestConfig = {\n          projectName: `test-${backend}`,\n          backend: backend as Backend,\n          frontend: [\"tanstack-router\"],\n          addons: [\"none\"],\n          examples: [\"none\"],\n          dbSetup: \"none\",\n          webDeploy: \"none\",\n          serverDeploy: \"none\",\n          install: false,\n        };\n\n        // Set appropriate defaults for each backend\n        switch (backend) {\n          case \"convex\":\n            config.runtime = \"none\";\n            config.database = \"none\";\n            config.orm = \"none\";\n            config.auth = \"clerk\";\n            config.api = \"none\";\n            break;\n          case \"none\":\n            config.runtime = \"none\";\n            config.database = \"none\";\n            config.orm = \"none\";\n            config.auth = \"none\";\n            config.api = \"none\";\n            break;\n          case \"self\":\n            config.frontend = [\"next\"]; // Self backend only works with Next.js\n            config.runtime = \"none\";\n            config.database = \"sqlite\";\n            config.orm = \"drizzle\";\n            config.auth = \"better-auth\";\n            config.api = \"trpc\";\n            break;\n          case \"elysia\":\n            config.runtime = \"bun\";\n            config.database = \"sqlite\";\n            config.orm = \"drizzle\";\n            config.auth = \"none\";\n            config.api = \"trpc\";\n            break;\n          default:\n            config.runtime = \"bun\";\n            config.database = \"sqlite\";\n            config.orm = \"drizzle\";\n            config.auth = \"none\";\n            config.api = \"trpc\";\n        }\n\n        const result = await runTRPCTest(config);\n        expectSuccess(result);\n      });\n    }\n  });\n\n  describe(\"Self Backend Constraints\", () => {\n    it(\"should work with self backend and Next.js frontend\", async () => {\n      const result = await runTRPCTest({\n        projectName: \"self-backend-success\",\n        backend: \"self\",\n        runtime: \"none\",\n        frontend: [\"next\"],\n        database: \"sqlite\",\n        orm: \"drizzle\",\n        auth: \"better-auth\",\n        api: \"trpc\",\n        addons: [\"none\"],\n        examples: [\"none\"],\n        dbSetup: \"none\",\n        webDeploy: \"none\",\n        serverDeploy: \"none\",\n        install: false,\n      });\n\n      expectSuccess(result);\n    });\n\n    it(\"should fail self backend with non-Next.js frontend\", async () => {\n      const result = await runTRPCTest({\n        projectName: \"self-backend-invalid-frontend\",\n        backend: \"self\",\n        runtime: \"none\",\n        frontend: [\"tanstack-router\"], // Invalid frontend for self backend\n        database: \"sqlite\",\n        orm: \"drizzle\",\n        auth: \"better-auth\",\n        api: \"trpc\",\n        addons: [\"none\"],\n        examples: [\"none\"],\n        dbSetup: \"none\",\n        webDeploy: \"none\",\n        serverDeploy: \"none\",\n        expectError: true,\n        install: false,\n      });\n\n      expectError(\n        result,\n        \"Backend 'self' (fullstack) currently only supports Next.js, TanStack Start, Nuxt, SvelteKit, and Astro frontends. Please use --frontend next, --frontend tanstack-start, --frontend nuxt, --frontend svelte, or --frontend astro.\",\n      );\n    });\n\n    it(\"should fail self backend with non-none runtime\", async () => {\n      const result = await runTRPCTest({\n        projectName: \"self-backend-invalid-runtime\",\n        backend: \"self\",\n        runtime: \"bun\", // Invalid runtime for self backend\n        frontend: [\"next\"],\n        database: \"sqlite\",\n        orm: \"drizzle\",\n        auth: \"better-auth\",\n        api: \"trpc\",\n        addons: [\"none\"],\n        examples: [\"none\"],\n        dbSetup: \"none\",\n        webDeploy: \"none\",\n        serverDeploy: \"none\",\n        expectError: true,\n        install: false,\n      });\n\n      expectError(result, \"Backend 'self' (fullstack) requires '--runtime none'\");\n    });\n  });\n});\n"
  },
  {
    "path": "apps/cli/test/basic-configurations.test.ts",
    "content": "import { describe, expect, it } from \"bun:test\";\n\nimport { expectError, expectSuccess, PACKAGE_MANAGERS, runTRPCTest } from \"./test-utils\";\n\ndescribe(\"Basic Configurations\", () => {\n  describe(\"Default Configuration\", () => {\n    it(\"should create project with --yes flag (default config)\", async () => {\n      const result = await runTRPCTest({\n        projectName: \"default-app\",\n        yes: true,\n        install: false,\n      });\n\n      expectSuccess(result);\n      expect(result.result?.projectConfig.projectName).toBe(\"default-app\");\n    });\n\n    it(\"should create project with explicit default values\", async () => {\n      const result = await runTRPCTest({\n        projectName: \"explicit-defaults\",\n        database: \"sqlite\",\n        orm: \"drizzle\",\n        backend: \"hono\",\n        runtime: \"bun\",\n        frontend: [\"tanstack-router\"],\n        auth: \"better-auth\",\n        api: \"trpc\",\n        addons: [\"turborepo\"],\n        examples: [\"none\"],\n        dbSetup: \"none\",\n        webDeploy: \"none\",\n        serverDeploy: \"none\",\n        install: false, // Skip installation for faster tests\n      });\n\n      expectSuccess(result);\n      expect(result.result?.projectConfig.projectName).toBe(\"explicit-defaults\");\n    });\n\n    it(\"should create Next.js fullstack project with self backend\", async () => {\n      const result = await runTRPCTest({\n        projectName: \"nextjs-fullstack-defaults\",\n        database: \"sqlite\",\n        orm: \"drizzle\",\n        backend: \"self\",\n        runtime: \"none\",\n        frontend: [\"next\"],\n        auth: \"better-auth\",\n        api: \"trpc\",\n        addons: [\"turborepo\"],\n        examples: [\"none\"],\n        dbSetup: \"none\",\n        webDeploy: \"none\",\n        serverDeploy: \"none\",\n        install: false, // Skip installation for faster tests\n      });\n\n      expectSuccess(result);\n      expect(result.result?.projectConfig.projectName).toBe(\"nextjs-fullstack-defaults\");\n      expect(result.result?.projectConfig.backend).toBe(\"self\");\n      expect(result.result?.projectConfig.runtime).toBe(\"none\");\n      expect(result.result?.projectConfig.frontend).toEqual([\"next\"]);\n    });\n  });\n\n  describe(\"Package Managers\", () => {\n    for (const packageManager of PACKAGE_MANAGERS) {\n      it(`should work with ${packageManager}`, async () => {\n        const result = await runTRPCTest({\n          projectName: `${packageManager}-app`,\n          packageManager,\n          yes: true,\n          install: false,\n        });\n\n        expectSuccess(result);\n        expect(result.result?.projectConfig.packageManager).toBe(packageManager);\n      });\n    }\n  });\n\n  describe(\"Git Options\", () => {\n    it(\"should work with git enabled\", async () => {\n      const result = await runTRPCTest({\n        projectName: \"git-enabled\",\n        yes: true,\n        git: true,\n        install: false,\n      });\n\n      expectSuccess(result);\n      expect(result.result?.projectConfig.git).toBe(true);\n    });\n\n    it(\"should work with git disabled\", async () => {\n      const result = await runTRPCTest({\n        projectName: \"git-disabled\",\n        yes: true,\n        git: false,\n        install: false,\n      });\n\n      expectSuccess(result);\n      expect(result.result?.projectConfig.git).toBe(false);\n    });\n  });\n\n  describe(\"Installation Options\", () => {\n    // Skip install test in CI to avoid timeouts\n    const runInstallTest = process.env.CI ? it.skip : it;\n\n    runInstallTest(\n      \"should work with install enabled\",\n      async () => {\n        const result = await runTRPCTest({\n          projectName: \"install-enabled\",\n          yes: true,\n          install: true,\n        });\n\n        expectSuccess(result);\n        expect(result.result?.projectConfig.install).toBe(true);\n      },\n      300000,\n    ); // 5 minute timeout for install test\n\n    it(\"should work with install disabled\", async () => {\n      const result = await runTRPCTest({\n        projectName: \"install-disabled\",\n        yes: true,\n        install: false,\n      });\n\n      expectSuccess(result);\n      expect(result.result?.projectConfig.install).toBe(false);\n    });\n  });\n\n  describe(\"YOLO Mode\", () => {\n    it(\"should bypass validations with --yolo flag\", async () => {\n      // This would normally fail validation but should pass with yolo\n      const result = await runTRPCTest({\n        projectName: \"yolo-app\",\n        yolo: true,\n        frontend: [\"tanstack-router\"],\n        backend: \"hono\",\n        runtime: \"bun\",\n        api: \"trpc\",\n        database: \"mongodb\",\n        orm: \"drizzle\", // Incompatible combination\n        auth: \"better-auth\",\n        addons: [\"none\"],\n        examples: [\"none\"],\n        dbSetup: \"none\",\n        webDeploy: \"none\",\n        serverDeploy: \"none\",\n        install: false,\n      });\n\n      expectSuccess(result);\n      expect(result.result?.projectConfig.projectName).toBe(\"yolo-app\");\n    });\n  });\n\n  describe(\"Error Handling\", () => {\n    it(\"should fail with invalid project name\", async () => {\n      const result = await runTRPCTest({\n        projectName: \"<invalid>\",\n        expectError: true,\n      });\n\n      expectError(result, \"Invalid project name\");\n    });\n\n    it(\"should fail when combining --yes with configuration flags\", async () => {\n      const result = await runTRPCTest({\n        projectName: \"yes-with-flags\",\n        yes: true, // Explicitly set yes flag\n        database: \"postgres\",\n        orm: \"drizzle\",\n        backend: \"hono\",\n        runtime: \"bun\",\n        frontend: [\"tanstack-router\"],\n        auth: \"better-auth\",\n        api: \"trpc\",\n        addons: [\"none\"],\n        examples: [\"none\"],\n        dbSetup: \"none\",\n        webDeploy: \"none\",\n        serverDeploy: \"none\",\n        expectError: true,\n      });\n\n      expectError(result, \"Cannot combine --yes with core stack configuration flags\");\n    });\n  });\n});\n"
  },
  {
    "path": "apps/cli/test/benchmark.test.ts",
    "content": "import { describe, expect, it } from \"bun:test\";\n\nimport { expectSuccess, runTRPCTest, type TestConfig } from \"./test-utils\";\n\ndescribe(\"CLI Performance Benchmarks\", () => {\n  describe(\"Basic Project Creation Benchmarks\", () => {\n    it(\"should benchmark default configuration creation\", async () => {\n      const startTime = performance.now();\n\n      const result = await runTRPCTest({\n        projectName: \"benchmark-default\",\n        yes: true,\n        install: false, // Skip install for faster benchmarking\n      });\n\n      const endTime = performance.now();\n      const duration = endTime - startTime;\n\n      expectSuccess(result);\n      expect(duration).toBeLessThan(10000); // Should complete within 10 seconds\n\n      console.log(`✅ Default configuration: ${duration.toFixed(2)}ms`);\n    });\n\n    it(\"should benchmark minimal configuration creation\", async () => {\n      const startTime = performance.now();\n\n      const result = await runTRPCTest({\n        projectName: \"benchmark-minimal\",\n        frontend: [\"none\"],\n        backend: \"none\",\n        runtime: \"none\",\n        database: \"none\",\n        orm: \"none\",\n        auth: \"none\",\n        api: \"none\",\n        addons: [\"none\"],\n        examples: [\"none\"],\n        dbSetup: \"none\",\n        serverDeploy: \"none\",\n        webDeploy: \"none\",\n        install: false,\n      });\n\n      const endTime = performance.now();\n      const duration = endTime - startTime;\n\n      expectSuccess(result);\n      expect(duration).toBeLessThan(8000);\n\n      console.log(`✅ Minimal configuration: ${duration.toFixed(2)}ms`);\n    });\n\n    it(\"should benchmark full-stack configuration creation\", async () => {\n      const startTime = performance.now();\n\n      const result = await runTRPCTest({\n        projectName: \"benchmark-fullstack\",\n        frontend: [\"tanstack-router\"],\n        backend: \"hono\",\n        runtime: \"bun\",\n        database: \"postgres\",\n        orm: \"drizzle\",\n        auth: \"better-auth\",\n        api: \"trpc\",\n        addons: [\"turborepo\", \"biome\"],\n        examples: [\"todo\"],\n        dbSetup: \"neon\",\n        webDeploy: \"none\",\n        serverDeploy: \"none\",\n        manualDb: true,\n        install: false,\n      });\n\n      const endTime = performance.now();\n      const duration = endTime - startTime;\n\n      expectSuccess(result);\n      expect(duration).toBeLessThan(15000); // Should complete within 15 seconds\n\n      console.log(`✅ Full-stack configuration: ${duration.toFixed(2)}ms`);\n    });\n  });\n\n  describe(\"Database Setup Benchmarks\", () => {\n    it(\"should benchmark SQLite with Drizzle setup\", async () => {\n      const startTime = performance.now();\n\n      const result = await runTRPCTest({\n        projectName: \"benchmark-sqlite-drizzle\",\n        frontend: [\"tanstack-router\"],\n        backend: \"hono\",\n        runtime: \"bun\",\n        database: \"sqlite\",\n        orm: \"drizzle\",\n        auth: \"none\",\n        api: \"trpc\",\n        addons: [\"none\"],\n        examples: [\"none\"],\n        dbSetup: \"none\",\n        webDeploy: \"none\",\n        serverDeploy: \"none\",\n        install: false,\n      });\n\n      const endTime = performance.now();\n      const duration = endTime - startTime;\n\n      expectSuccess(result);\n      expect(duration).toBeLessThan(12000);\n\n      console.log(`✅ SQLite + Drizzle: ${duration.toFixed(2)}ms`);\n    });\n\n    it(\"should benchmark PostgreSQL with Prisma setup\", async () => {\n      const startTime = performance.now();\n\n      const result = await runTRPCTest({\n        projectName: \"benchmark-postgres-prisma\",\n        frontend: [\"tanstack-router\"],\n        backend: \"hono\",\n        runtime: \"bun\",\n        database: \"postgres\",\n        orm: \"prisma\",\n        auth: \"none\",\n        api: \"trpc\",\n        addons: [\"none\"],\n        examples: [\"none\"],\n        dbSetup: \"none\",\n        webDeploy: \"none\",\n        serverDeploy: \"none\",\n        install: false,\n      });\n\n      const endTime = performance.now();\n      const duration = endTime - startTime;\n\n      expectSuccess(result);\n      expect(duration).toBeLessThan(12000);\n\n      console.log(`✅ PostgreSQL + Prisma: ${duration.toFixed(2)}ms`);\n    });\n\n    it(\"should benchmark MongoDB with Mongoose setup\", async () => {\n      const startTime = performance.now();\n\n      const result = await runTRPCTest({\n        projectName: \"benchmark-mongodb-mongoose\",\n        frontend: [\"tanstack-router\"],\n        backend: \"hono\",\n        runtime: \"bun\",\n        database: \"mongodb\",\n        orm: \"mongoose\",\n        auth: \"none\",\n        api: \"trpc\",\n        addons: [\"none\"],\n        examples: [\"none\"],\n        dbSetup: \"none\",\n        webDeploy: \"none\",\n        serverDeploy: \"none\",\n        install: false,\n      });\n\n      const endTime = performance.now();\n      const duration = endTime - startTime;\n\n      expectSuccess(result);\n      expect(duration).toBeLessThan(12000);\n\n      console.log(`✅ MongoDB + Mongoose: ${duration.toFixed(2)}ms`);\n    });\n  });\n\n  describe(\"Frontend Framework Benchmarks\", () => {\n    it(\"should benchmark TanStack Router setup\", async () => {\n      const startTime = performance.now();\n\n      const result = await runTRPCTest({\n        projectName: \"benchmark-tanstack-router\",\n        frontend: [\"tanstack-router\"],\n        backend: \"hono\",\n        runtime: \"bun\",\n        database: \"sqlite\",\n        orm: \"drizzle\",\n        auth: \"none\",\n        api: \"trpc\",\n        addons: [\"none\"],\n        examples: [\"none\"],\n        dbSetup: \"none\",\n        webDeploy: \"none\",\n        serverDeploy: \"none\",\n        install: false,\n      });\n\n      const endTime = performance.now();\n      const duration = endTime - startTime;\n\n      expectSuccess(result);\n      expect(duration).toBeLessThan(12000);\n\n      console.log(`✅ TanStack Router: ${duration.toFixed(2)}ms`);\n    });\n\n    it(\"should benchmark Next.js setup\", async () => {\n      const startTime = performance.now();\n\n      const result = await runTRPCTest({\n        projectName: \"benchmark-nextjs\",\n        frontend: [\"next\"],\n        backend: \"self\",\n        runtime: \"none\",\n        database: \"sqlite\",\n        orm: \"drizzle\",\n        auth: \"better-auth\",\n        api: \"trpc\",\n        addons: [\"none\"],\n        examples: [\"none\"],\n        dbSetup: \"none\",\n        webDeploy: \"none\",\n        serverDeploy: \"none\",\n        install: false,\n      });\n\n      const endTime = performance.now();\n      const duration = endTime - startTime;\n\n      expectSuccess(result);\n      expect(duration).toBeLessThan(12000);\n\n      console.log(`✅ Next.js: ${duration.toFixed(2)}ms`);\n    });\n\n    it(\"should benchmark Nuxt setup\", async () => {\n      const startTime = performance.now();\n\n      const result = await runTRPCTest({\n        projectName: \"benchmark-nuxt\",\n        frontend: [\"nuxt\"],\n        backend: \"hono\",\n        runtime: \"bun\",\n        database: \"sqlite\",\n        orm: \"drizzle\",\n        auth: \"none\",\n        api: \"orpc\",\n        addons: [\"none\"],\n        examples: [\"none\"],\n        dbSetup: \"none\",\n        webDeploy: \"none\",\n        serverDeploy: \"none\",\n        install: false,\n      });\n\n      const endTime = performance.now();\n      const duration = endTime - startTime;\n\n      expectSuccess(result);\n      expect(duration).toBeLessThan(12000);\n\n      console.log(`✅ Nuxt: ${duration.toFixed(2)}ms`);\n    });\n  });\n\n  describe(\"Backend Framework Benchmarks\", () => {\n    it(\"should benchmark Hono setup\", async () => {\n      const startTime = performance.now();\n\n      const result = await runTRPCTest({\n        projectName: \"benchmark-hono\",\n        frontend: [\"tanstack-router\"],\n        backend: \"hono\",\n        runtime: \"bun\",\n        database: \"sqlite\",\n        orm: \"drizzle\",\n        auth: \"none\",\n        api: \"trpc\",\n        addons: [\"none\"],\n        examples: [\"none\"],\n        dbSetup: \"none\",\n        webDeploy: \"none\",\n        serverDeploy: \"none\",\n        install: false,\n      });\n\n      const endTime = performance.now();\n      const duration = endTime - startTime;\n\n      expectSuccess(result);\n      expect(duration).toBeLessThan(12000);\n\n      console.log(`✅ Hono: ${duration.toFixed(2)}ms`);\n    });\n\n    it(\"should benchmark Express setup\", async () => {\n      const startTime = performance.now();\n\n      const result = await runTRPCTest({\n        projectName: \"benchmark-express\",\n        frontend: [\"tanstack-router\"],\n        backend: \"express\",\n        runtime: \"node\",\n        database: \"sqlite\",\n        orm: \"drizzle\",\n        auth: \"none\",\n        api: \"trpc\",\n        addons: [\"none\"],\n        examples: [\"none\"],\n        dbSetup: \"none\",\n        webDeploy: \"none\",\n        serverDeploy: \"none\",\n        install: false,\n      });\n\n      const endTime = performance.now();\n      const duration = endTime - startTime;\n\n      expectSuccess(result);\n      expect(duration).toBeLessThan(12000);\n\n      console.log(`✅ Express: ${duration.toFixed(2)}ms`);\n    });\n\n    it(\"should benchmark Convex setup\", async () => {\n      const startTime = performance.now();\n\n      const result = await runTRPCTest({\n        projectName: \"benchmark-convex\",\n        frontend: [\"tanstack-router\"],\n        backend: \"convex\",\n        runtime: \"none\",\n        database: \"none\",\n        orm: \"none\",\n        auth: \"none\",\n        api: \"none\",\n        addons: [\"none\"],\n        examples: [\"none\"],\n        dbSetup: \"none\",\n        webDeploy: \"none\",\n        serverDeploy: \"none\",\n        install: false,\n      });\n\n      const endTime = performance.now();\n      const duration = endTime - startTime;\n\n      expectSuccess(result);\n      expect(duration).toBeLessThan(12000);\n\n      console.log(`✅ Convex: ${duration.toFixed(2)}ms`);\n    });\n  });\n\n  describe(\"Addon Benchmarks\", () => {\n    it(\"should benchmark Turborepo addon\", async () => {\n      const startTime = performance.now();\n\n      const result = await runTRPCTest({\n        projectName: \"benchmark-turborepo\",\n        frontend: [\"tanstack-router\"],\n        backend: \"hono\",\n        runtime: \"bun\",\n        database: \"sqlite\",\n        orm: \"drizzle\",\n        auth: \"none\",\n        api: \"trpc\",\n        addons: [\"turborepo\"],\n        examples: [\"none\"],\n        dbSetup: \"none\",\n        webDeploy: \"none\",\n        serverDeploy: \"none\",\n        install: false,\n      });\n\n      const endTime = performance.now();\n      const duration = endTime - startTime;\n\n      expectSuccess(result);\n      expect(duration).toBeLessThan(12000);\n\n      console.log(`✅ Turborepo addon: ${duration.toFixed(2)}ms`);\n    });\n\n    it(\"should benchmark Biome addon\", async () => {\n      const startTime = performance.now();\n\n      const result = await runTRPCTest({\n        projectName: \"benchmark-biome\",\n        frontend: [\"tanstack-router\"],\n        backend: \"hono\",\n        runtime: \"bun\",\n        database: \"sqlite\",\n        orm: \"drizzle\",\n        auth: \"none\",\n        api: \"trpc\",\n        addons: [\"biome\"],\n        examples: [\"none\"],\n        dbSetup: \"none\",\n        webDeploy: \"none\",\n        serverDeploy: \"none\",\n        install: false,\n      });\n\n      const endTime = performance.now();\n      const duration = endTime - startTime;\n\n      expectSuccess(result);\n      expect(duration).toBeLessThan(12000);\n\n      console.log(`✅ Biome addon: ${duration.toFixed(2)}ms`);\n    });\n\n    it(\"should benchmark multiple addons\", async () => {\n      const startTime = performance.now();\n\n      const result = await runTRPCTest({\n        projectName: \"benchmark-multiple-addons\",\n        frontend: [\"tanstack-router\"],\n        backend: \"hono\",\n        runtime: \"bun\",\n        database: \"sqlite\",\n        orm: \"drizzle\",\n        auth: \"none\",\n        api: \"trpc\",\n        addons: [\"turborepo\", \"biome\", \"husky\"],\n        examples: [\"none\"],\n        dbSetup: \"none\",\n        webDeploy: \"none\",\n        serverDeploy: \"none\",\n        install: false,\n      });\n\n      const endTime = performance.now();\n      const duration = endTime - startTime;\n\n      expectSuccess(result);\n      expect(duration).toBeLessThan(15000);\n\n      console.log(`✅ Multiple addons: ${duration.toFixed(2)}ms`);\n    });\n  });\n\n  describe(\"Performance Regression Tests\", () => {\n    const configurations = [\n      {\n        name: \"Minimal\",\n        config: {\n          projectName: \"perf-minimal\",\n          frontend: [\"none\"],\n          backend: \"none\",\n          runtime: \"none\",\n          database: \"none\",\n          orm: \"none\",\n          auth: \"none\",\n          api: \"none\",\n          addons: [\"none\"],\n          examples: [\"none\"],\n          dbSetup: \"none\",\n          webDeploy: \"none\",\n          serverDeploy: \"none\",\n          install: false,\n        },\n        threshold: 5000, // 5 seconds\n      },\n      {\n        name: \"Default\",\n        config: {\n          projectName: \"perf-default\",\n          yes: true,\n          install: false,\n        },\n        threshold: 8000, // 8 seconds\n      },\n      {\n        name: \"Complex\",\n        config: {\n          projectName: \"perf-complex\",\n          frontend: [\"tanstack-router\"],\n          backend: \"hono\",\n          runtime: \"bun\",\n          database: \"postgres\",\n          orm: \"prisma\",\n          auth: \"better-auth\",\n          api: \"trpc\",\n          addons: [\"turborepo\", \"biome\"],\n          examples: [\"todo\"],\n          dbSetup: \"none\",\n          webDeploy: \"none\",\n          serverDeploy: \"none\",\n          install: false,\n        },\n        threshold: 12000, // 12 seconds\n      },\n    ];\n\n    for (const { name, config, threshold } of configurations) {\n      it(`should not exceed performance thresholds for ${name}`, async () => {\n        const startTime = performance.now();\n\n        const result = await runTRPCTest(config as TestConfig);\n\n        const endTime = performance.now();\n        const duration = endTime - startTime;\n\n        expectSuccess(result);\n        expect(duration).toBeLessThan(threshold);\n\n        console.log(`✅ ${name} performance: ${duration.toFixed(2)}ms (threshold: ${threshold}ms)`);\n      });\n    }\n  });\n});\n"
  },
  {
    "path": "apps/cli/test/clerk-matrix.test.ts",
    "content": "import { describe, expect, it } from \"bun:test\";\n\nimport { createVirtual } from \"../src/index\";\nimport { validateConfigCompatibility } from \"../src/validation\";\nimport { collectFiles } from \"./setup\";\n\nconst standardBackends = [\n  { backend: \"hono\", runtime: \"bun\" },\n  { backend: \"hono\", runtime: \"node\" },\n  { backend: \"hono\", runtime: \"workers\" },\n  { backend: \"express\", runtime: \"bun\" },\n  { backend: \"express\", runtime: \"node\" },\n  { backend: \"fastify\", runtime: \"bun\" },\n  { backend: \"fastify\", runtime: \"node\" },\n  { backend: \"elysia\", runtime: \"bun\" },\n] as const;\n\nconst standardWeb = [\n  undefined,\n  \"next\",\n  \"react-router\",\n  \"tanstack-router\",\n  \"tanstack-start\",\n] as const;\nconst selfWeb = [\"next\", \"tanstack-start\"] as const;\nconst nativeOptions = [undefined, \"native-bare\", \"native-uniwind\", \"native-unistyles\"] as const;\nconst apiOptions = [\"trpc\", \"orpc\", \"none\"] as const;\n\nfunction buildFrontendCombos(\n  webOptions: readonly (typeof standardWeb)[number][],\n  { requireWeb = false }: { requireWeb?: boolean } = {},\n) {\n  const combos: string[][] = [];\n\n  for (const web of webOptions) {\n    for (const native of nativeOptions) {\n      const frontend = [web, native].filter(Boolean) as string[];\n      if (frontend.length === 0) continue;\n      if (requireWeb && !web) continue;\n      combos.push(frontend);\n    }\n  }\n\n  return combos;\n}\n\nfunction expectedContextImport(backend: string) {\n  if (backend === \"express\") return \"@clerk/express\";\n  if (backend === \"fastify\") return \"@clerk/fastify\";\n  return \"@clerk/backend\";\n}\n\nfunction usesBackendClerkClient(backend: string, api: string) {\n  return api !== \"none\" && (backend === \"self\" || backend === \"hono\" || backend === \"elysia\");\n}\n\nfunction needsServerClerkPublishableKey(backend: string, api: string) {\n  return (\n    backend === \"express\" ||\n    backend === \"fastify\" ||\n    (api !== \"none\" && (backend === \"self\" || backend === \"hono\" || backend === \"elysia\"))\n  );\n}\n\ndescribe(\"Clerk matrix\", () => {\n  it(\"should generate every supported Clerk combination\", { timeout: 30_000 }, async () => {\n    const standardFrontendCombos = buildFrontendCombos(standardWeb);\n    const selfFrontendCombos = buildFrontendCombos(selfWeb, { requireWeb: true });\n\n    const combos = [\n      ...standardBackends.flatMap((pair) =>\n        standardFrontendCombos.flatMap((frontend) =>\n          apiOptions.map((api) => ({\n            backend: pair.backend,\n            runtime: pair.runtime,\n            frontend,\n            api,\n          })),\n        ),\n      ),\n      ...selfFrontendCombos.flatMap((frontend) =>\n        apiOptions.map((api) => ({\n          backend: \"self\",\n          runtime: \"none\",\n          frontend,\n          api,\n        })),\n      ),\n      ...standardFrontendCombos.map((frontend) => ({\n        backend: \"convex\",\n        runtime: \"none\",\n        frontend,\n        api: \"none\",\n      })),\n    ];\n\n    const failures: string[] = [];\n\n    for (const [index, combo] of combos.entries()) {\n      const config = {\n        projectName: `clerk-matrix-${index}`,\n        frontend: combo.frontend,\n        backend: combo.backend,\n        runtime: combo.runtime,\n        database: combo.backend === \"convex\" || combo.api === \"none\" ? \"none\" : \"sqlite\",\n        orm: combo.backend === \"convex\" || combo.api === \"none\" ? \"none\" : \"drizzle\",\n        auth: \"clerk\" as const,\n        api: combo.api,\n        addons: [\"none\"] as const,\n        examples: [\"none\"] as const,\n        dbSetup: \"none\" as const,\n        webDeploy: \"none\" as const,\n        serverDeploy: combo.runtime === \"workers\" ? (\"cloudflare\" as const) : (\"none\" as const),\n        install: false,\n        git: false,\n        packageManager: \"bun\" as const,\n        payments: \"none\" as const,\n      };\n\n      const validation = validateConfigCompatibility(config);\n      if (validation.isErr()) {\n        failures.push(\n          `${combo.backend}/${combo.runtime}/${combo.frontend.join(\"+\")}/${combo.api}: ${validation.error.message}`,\n        );\n        continue;\n      }\n\n      const result = await createVirtual(config);\n      if (result.isErr()) {\n        failures.push(\n          `${combo.backend}/${combo.runtime}/${combo.frontend.join(\"+\")}/${combo.api}: ${result.error.message}`,\n        );\n        continue;\n      }\n\n      const files = collectFiles(result.value.root, result.value.root.path);\n\n      if (combo.backend !== \"convex\" && combo.runtime !== \"workers\") {\n        const serverEnv = files.get(\"packages/env/src/server.ts\");\n        if (!serverEnv?.includes(\"CLERK_SECRET_KEY\")) {\n          failures.push(\n            `${combo.backend}/${combo.runtime}/${combo.frontend.join(\"+\")}/${combo.api}: missing CLERK_SECRET_KEY in packages/env/src/server.ts`,\n          );\n        }\n\n        if (\n          needsServerClerkPublishableKey(combo.backend, combo.api) &&\n          !serverEnv?.includes(\"CLERK_PUBLISHABLE_KEY\")\n        ) {\n          failures.push(\n            `${combo.backend}/${combo.runtime}/${combo.frontend.join(\"+\")}/${combo.api}: missing CLERK_PUBLISHABLE_KEY in packages/env/src/server.ts`,\n          );\n        }\n      }\n\n      if (combo.backend !== \"convex\" && combo.api !== \"none\") {\n        const contextFile = files.get(\"packages/api/src/context.ts\");\n        const expectedImport = expectedContextImport(combo.backend);\n\n        if (!contextFile?.includes(expectedImport)) {\n          failures.push(\n            `${combo.backend}/${combo.runtime}/${combo.frontend.join(\"+\")}/${combo.api}: missing ${expectedImport} in packages/api/src/context.ts`,\n          );\n        }\n\n        if (!contextFile?.includes(\"type ClerkContextAuth\")) {\n          failures.push(\n            `${combo.backend}/${combo.runtime}/${combo.frontend.join(\"+\")}/${combo.api}: missing ClerkContextAuth in packages/api/src/context.ts`,\n          );\n        }\n\n        if (!contextFile?.includes(\"type ClerkRequestContext\")) {\n          failures.push(\n            `${combo.backend}/${combo.runtime}/${combo.frontend.join(\"+\")}/${combo.api}: missing ClerkRequestContext in packages/api/src/context.ts`,\n          );\n        }\n\n        if (\n          usesBackendClerkClient(combo.backend, combo.api) &&\n          !contextFile?.includes(\"publishableKey: env.CLERK_PUBLISHABLE_KEY\")\n        ) {\n          failures.push(\n            `${combo.backend}/${combo.runtime}/${combo.frontend.join(\"+\")}/${combo.api}: missing publishableKey in packages/api/src/context.ts`,\n          );\n        }\n\n        if (\n          usesBackendClerkClient(combo.backend, combo.api) &&\n          !contextFile?.includes(\"authorizedParties: [env.CORS_ORIGIN]\")\n        ) {\n          failures.push(\n            `${combo.backend}/${combo.runtime}/${combo.frontend.join(\"+\")}/${combo.api}: missing authorizedParties in packages/api/src/context.ts`,\n          );\n        }\n      }\n\n      if (needsServerClerkPublishableKey(combo.backend, combo.api)) {\n        const appEnvPath = combo.backend === \"self\" ? \"apps/web/.env\" : \"apps/server/.env\";\n        const appEnvFile = files.get(appEnvPath);\n\n        if (!appEnvFile?.includes(\"CLERK_PUBLISHABLE_KEY=\")) {\n          failures.push(\n            `${combo.backend}/${combo.runtime}/${combo.frontend.join(\"+\")}/${combo.api}: missing CLERK_PUBLISHABLE_KEY in ${appEnvPath}`,\n          );\n        }\n      }\n\n      if (combo.frontend.includes(\"next\")) {\n        const dashboard = files.get(\"apps/web/src/app/dashboard/page.tsx\");\n        if (!dashboard) {\n          failures.push(\n            `${combo.backend}/${combo.runtime}/${combo.frontend.join(\"+\")}/${combo.api}: missing Next dashboard page`,\n          );\n        } else if (dashboard.includes(\"SignedIn\") || dashboard.includes(\"SignedOut\")) {\n          failures.push(\n            `${combo.backend}/${combo.runtime}/${combo.frontend.join(\"+\")}/${combo.api}: Next dashboard still uses SignedIn/SignedOut`,\n          );\n        } else if (\n          combo.backend !== \"convex\" &&\n          combo.api !== \"none\" &&\n          !dashboard.includes(\"privateData.queryOptions()\")\n        ) {\n          failures.push(\n            `${combo.backend}/${combo.runtime}/${combo.frontend.join(\"+\")}/${combo.api}: Next dashboard is missing protected privateData query`,\n          );\n        }\n\n        if (combo.backend !== \"convex\") {\n          const proxyFile = files.get(\"apps/web/src/proxy.ts\");\n          if (!proxyFile) {\n            failures.push(\n              `${combo.backend}/${combo.runtime}/${combo.frontend.join(\"+\")}/${combo.api}: missing Next proxy file`,\n            );\n          } else if (\n            proxyFile.includes('/env/server\"') ||\n            proxyFile.includes(\"env.CLERK_SECRET_KEY\")\n          ) {\n            failures.push(\n              `${combo.backend}/${combo.runtime}/${combo.frontend.join(\"+\")}/${combo.api}: Next proxy still imports shared server env`,\n            );\n          }\n        }\n      }\n\n      if (combo.frontend.includes(\"react-router\")) {\n        const dashboard = files.get(\"apps/web/src/routes/dashboard.tsx\");\n        if (!dashboard) {\n          failures.push(\n            `${combo.backend}/${combo.runtime}/${combo.frontend.join(\"+\")}/${combo.api}: missing React Router dashboard route`,\n          );\n        } else if (dashboard.includes(\"SignedIn\") || dashboard.includes(\"SignedOut\")) {\n          failures.push(\n            `${combo.backend}/${combo.runtime}/${combo.frontend.join(\"+\")}/${combo.api}: React Router dashboard still uses SignedIn/SignedOut`,\n          );\n        } else if (\n          combo.backend !== \"convex\" &&\n          combo.api !== \"none\" &&\n          !dashboard.includes(\"privateData.queryOptions()\")\n        ) {\n          failures.push(\n            `${combo.backend}/${combo.runtime}/${combo.frontend.join(\"+\")}/${combo.api}: React Router dashboard is missing protected privateData query`,\n          );\n        }\n      }\n\n      if (combo.frontend.includes(\"tanstack-router\")) {\n        const dashboard = files.get(\"apps/web/src/routes/dashboard.tsx\");\n        if (!dashboard) {\n          failures.push(\n            `${combo.backend}/${combo.runtime}/${combo.frontend.join(\"+\")}/${combo.api}: missing TanStack Router dashboard route`,\n          );\n        } else if (dashboard.includes(\"SignedIn\") || dashboard.includes(\"SignedOut\")) {\n          failures.push(\n            `${combo.backend}/${combo.runtime}/${combo.frontend.join(\"+\")}/${combo.api}: TanStack Router dashboard still uses SignedIn/SignedOut`,\n          );\n        } else if (\n          combo.backend !== \"convex\" &&\n          combo.api !== \"none\" &&\n          !dashboard.includes(\"privateData.queryOptions()\")\n        ) {\n          failures.push(\n            `${combo.backend}/${combo.runtime}/${combo.frontend.join(\"+\")}/${combo.api}: TanStack Router dashboard is missing protected privateData query`,\n          );\n        }\n      }\n\n      if (combo.frontend.includes(\"tanstack-start\")) {\n        const dashboard = files.get(\"apps/web/src/routes/dashboard.tsx\");\n        if (!dashboard) {\n          failures.push(\n            `${combo.backend}/${combo.runtime}/${combo.frontend.join(\"+\")}/${combo.api}: missing TanStack Start dashboard route`,\n          );\n        } else if (dashboard.includes(\"SignedIn\") || dashboard.includes(\"SignedOut\")) {\n          failures.push(\n            `${combo.backend}/${combo.runtime}/${combo.frontend.join(\"+\")}/${combo.api}: TanStack Start dashboard still uses SignedIn/SignedOut`,\n          );\n        } else if (\n          combo.backend !== \"convex\" &&\n          combo.api !== \"none\" &&\n          !dashboard.includes(\"privateData.queryOptions()\")\n        ) {\n          failures.push(\n            `${combo.backend}/${combo.runtime}/${combo.frontend.join(\"+\")}/${combo.api}: TanStack Start dashboard is missing protected privateData query`,\n          );\n        }\n\n        if (combo.backend !== \"convex\") {\n          const startFile = files.get(\"apps/web/src/start.ts\");\n          if (!startFile) {\n            failures.push(\n              `${combo.backend}/${combo.runtime}/${combo.frontend.join(\"+\")}/${combo.api}: missing TanStack Start entry file`,\n            );\n          } else if (\n            startFile.includes('/env/server\"') ||\n            startFile.includes(\"env.CLERK_SECRET_KEY\")\n          ) {\n            failures.push(\n              `${combo.backend}/${combo.runtime}/${combo.frontend.join(\"+\")}/${combo.api}: TanStack Start entry still imports shared server env`,\n            );\n          }\n        }\n      }\n\n      const nativeFrontend = combo.frontend.find((entry) => entry.startsWith(\"native-\"));\n      if (nativeFrontend) {\n        const nativePackage = files.get(\"apps/native/package.json\");\n        const nativeSignIn = files.get(\"apps/native/app/(auth)/sign-in.tsx\");\n        const nativeSignUp = files.get(\"apps/native/app/(auth)/sign-up.tsx\");\n\n        if (!nativePackage?.includes('\"@clerk/expo\": \"^3.1.3\"')) {\n          failures.push(\n            `${combo.backend}/${combo.runtime}/${combo.frontend.join(\"+\")}/${combo.api}: native package is missing @clerk/expo ^3.1.3`,\n          );\n        }\n\n        if (!nativeSignIn) {\n          failures.push(\n            `${combo.backend}/${combo.runtime}/${combo.frontend.join(\"+\")}/${combo.api}: missing native sign-in screen`,\n          );\n        } else {\n          if (nativeSignIn.includes(\"setActive\") || nativeSignIn.includes(\"signIn.create\")) {\n            failures.push(\n              `${combo.backend}/${combo.runtime}/${combo.frontend.join(\"+\")}/${combo.api}: native sign-in still uses the legacy Clerk Expo API`,\n            );\n          }\n\n          if (\n            !nativeSignIn.includes(\"const { signIn, errors, fetchStatus } = useSignIn()\") ||\n            !nativeSignIn.includes(\"await signIn.password\") ||\n            !nativeSignIn.includes(\"await signIn.finalize\")\n          ) {\n            failures.push(\n              `${combo.backend}/${combo.runtime}/${combo.frontend.join(\"+\")}/${combo.api}: native sign-in is missing the current Clerk Expo flow`,\n            );\n          }\n        }\n\n        if (!nativeSignUp) {\n          failures.push(\n            `${combo.backend}/${combo.runtime}/${combo.frontend.join(\"+\")}/${combo.api}: missing native sign-up screen`,\n          );\n        } else {\n          if (\n            nativeSignUp.includes(\"setActive\") ||\n            nativeSignUp.includes(\"prepareEmailAddressVerification\") ||\n            nativeSignUp.includes(\"attemptEmailAddressVerification\")\n          ) {\n            failures.push(\n              `${combo.backend}/${combo.runtime}/${combo.frontend.join(\"+\")}/${combo.api}: native sign-up still uses the legacy Clerk Expo API`,\n            );\n          }\n\n          if (\n            !nativeSignUp.includes(\"const { signUp, errors, fetchStatus } = useSignUp()\") ||\n            !nativeSignUp.includes(\"await signUp.password\") ||\n            !nativeSignUp.includes(\"await signUp.verifications.sendEmailCode()\") ||\n            !nativeSignUp.includes(\"await signUp.verifications.verifyEmailCode\") ||\n            !nativeSignUp.includes(\"await signUp.finalize\") ||\n            !nativeSignUp.includes('nativeID=\"clerk-captcha\"')\n          ) {\n            failures.push(\n              `${combo.backend}/${combo.runtime}/${combo.frontend.join(\"+\")}/${combo.api}: native sign-up is missing the current Clerk Expo flow`,\n            );\n          }\n        }\n      }\n    }\n\n    expect(combos).toHaveLength(499);\n    expect(failures).toEqual([]);\n  });\n});\n"
  },
  {
    "path": "apps/cli/test/cli-validation.test.ts",
    "content": "import { expect, test } from \"bun:test\";\n\nimport { FailedToExitError } from \"trpc-cli\";\n\nimport { createBtsCli } from \"../src/index\";\nimport { getProvidedFlags, processAndValidateFlags } from \"../src/validation\";\n\ntest(\"surfaces a friendly validation error for invalid addons\", async () => {\n  const logs: string[] = [];\n\n  const result = await createBtsCli()\n    .run({\n      argv: [\"create\", \"ryu\", \"--addons\", \"ruler\"],\n      logger: {\n        error: (...args) => logs.push(args.map(String).join(\" \")),\n      },\n      process: { exit: () => 0 as never },\n    })\n    .catch((error) => error);\n\n  expect(result).toBeInstanceOf(FailedToExitError);\n  expect(result.exitCode).toBe(1);\n\n  const output = logs.join(\"\\n\");\n\n  expect(output).toContain(\"Invalid option\");\n  expect(output).toContain(\"at [1].addons[0]\");\n  expect(output).not.toContain(\"ORPCError\");\n  expect(output).not.toContain(\"Input validation failed\");\n});\n\ntest(\"allows self + D1 flags before web deploy is resolved by prompts\", () => {\n  const options = {\n    backend: \"self\",\n    frontend: [\"next\"],\n    database: \"sqlite\",\n    orm: \"drizzle\",\n    dbSetup: \"d1\",\n    api: \"trpc\",\n    auth: \"better-auth\",\n    payments: \"none\",\n    addons: [\"none\"],\n    examples: [\"none\"],\n    runtime: \"none\",\n  } as const;\n\n  const result = processAndValidateFlags(options, getProvidedFlags(options), \"my-app\");\n\n  expect(result.isOk()).toBe(true);\n});\n\ntest(\"allows workers + D1 flags before server deploy is resolved by prompts\", () => {\n  const options = {\n    backend: \"hono\",\n    frontend: [\"tanstack-router\"],\n    database: \"sqlite\",\n    orm: \"drizzle\",\n    dbSetup: \"d1\",\n    api: \"trpc\",\n    auth: \"none\",\n    payments: \"none\",\n    addons: [\"none\"],\n    examples: [\"none\"],\n    runtime: \"workers\",\n  } as const;\n\n  const result = processAndValidateFlags(options, getProvidedFlags(options), \"my-app\");\n\n  expect(result.isOk()).toBe(true);\n});\n\ntest(\"still rejects D1 when the remaining prompt flow cannot resolve it to a valid target\", () => {\n  const options = {\n    backend: \"hono\",\n    frontend: [\"tanstack-router\"],\n    database: \"sqlite\",\n    orm: \"drizzle\",\n    dbSetup: \"d1\",\n    api: \"trpc\",\n    auth: \"none\",\n    payments: \"none\",\n    addons: [\"none\"],\n    examples: [\"none\"],\n    runtime: \"node\",\n  } as const;\n\n  const result = processAndValidateFlags(options, getProvidedFlags(options), \"my-app\");\n\n  expect(result.isErr()).toBe(true);\n  if (result.isErr()) {\n    expect(result.error.message).toContain(\n      \"Cloudflare D1 setup requires SQLite database and either Cloudflare Workers runtime with server deployment or backend 'self' with Cloudflare web deployment.\",\n    );\n  }\n});\n"
  },
  {
    "path": "apps/cli/test/cloudflare-db-clients.test.ts",
    "content": "import { describe, expect, it } from \"bun:test\";\n\nimport { createVirtual } from \"../src/index\";\nimport { collectFiles } from \"./setup\";\n\nasync function createVirtualFiles(config: Parameters<typeof createVirtual>[0]) {\n  const result = await createVirtual(config);\n\n  if (result.isErr()) {\n    throw result.error;\n  }\n\n  return collectFiles(result.value.root, result.value.root.path);\n}\n\ndescribe(\"Cloudflare DB client generation\", () => {\n  it(\"uses request-scoped db/auth factories for Workers templates\", async () => {\n    const files = await createVirtualFiles({\n      projectName: \"workers-request-scoped-db\",\n      frontend: [\"tanstack-router\"],\n      backend: \"hono\",\n      runtime: \"workers\",\n      database: \"sqlite\",\n      orm: \"drizzle\",\n      auth: \"better-auth\",\n      addons: [\"none\"],\n      examples: [\"todo\"],\n      dbSetup: \"turso\",\n      webDeploy: \"none\",\n      serverDeploy: \"cloudflare\",\n      install: false,\n      git: false,\n      packageManager: \"bun\",\n      payments: \"none\",\n      api: \"trpc\",\n    });\n    const dbFile = files.get(\"packages/db/src/index.ts\");\n    const authFile = files.get(\"packages/auth/src/index.ts\");\n    const envFile = files.get(\"packages/env/src/server.ts\");\n    const serverFile = files.get(\"apps/server/src/index.ts\");\n    const contextFile = files.get(\"packages/api/src/context.ts\");\n    const todoRouterFile = files.get(\"packages/api/src/routers/todo.ts\");\n\n    expect(dbFile).toContain(\"export function createDb()\");\n    expect(dbFile).not.toContain(\"export const db = createDb();\");\n    expect(authFile).toContain(\"export function createAuth()\");\n    expect(authFile).not.toContain(\"export const auth = createAuth();\");\n    expect(envFile).toContain('export { env } from \"cloudflare:workers\";');\n    expect(serverFile).toContain(\"createAuth().handler(c.req.raw)\");\n    expect(contextFile).toContain(\"createAuth().api.getSession\");\n    expect(todoRouterFile).toContain(\"const db = createDb();\");\n  });\n\n  it(\"uses request-scoped db/auth factories for Next on Cloudflare\", async () => {\n    const files = await createVirtualFiles({\n      projectName: \"next-cloudflare-request-scoped-db\",\n      frontend: [\"next\"],\n      backend: \"self\",\n      runtime: \"none\",\n      database: \"postgres\",\n      orm: \"prisma\",\n      auth: \"better-auth\",\n      addons: [\"none\"],\n      examples: [\"todo\"],\n      dbSetup: \"none\",\n      webDeploy: \"cloudflare\",\n      serverDeploy: \"none\",\n      install: false,\n      git: false,\n      packageManager: \"bun\",\n      payments: \"none\",\n      api: \"trpc\",\n    });\n    const dbFile = files.get(\"packages/db/src/index.ts\");\n    const authFile = files.get(\"packages/auth/src/index.ts\");\n    const envFile = files.get(\"packages/env/src/server.ts\");\n    const envPackageFile = files.get(\"packages/env/package.json\");\n    const routeFile = files.get(\"apps/web/src/app/api/auth/[...all]/route.ts\");\n    const dashboardFile = files.get(\"apps/web/src/app/dashboard/page.tsx\");\n    const contextFile = files.get(\"packages/api/src/context.ts\");\n\n    expect(dbFile).toContain(\"export function createPrismaClient()\");\n    expect(dbFile).not.toContain(\"export default prisma;\");\n    expect(authFile).toContain(\"const prisma = createPrismaClient();\");\n    expect(authFile).not.toContain(\"export const auth = createAuth();\");\n    expect(envFile).toContain('import { getCloudflareContext } from \"@opennextjs/cloudflare\";');\n    expect(envFile).toContain(\"type EnvValue = Env[keyof Env];\");\n    expect(envFile).toContain(\n      \"function resolveEnvValue(key: keyof Env & string): EnvValue | undefined\",\n    );\n    expect(envFile).toContain(\"export async function getEnvAsync()\");\n    expect(envFile).toContain(\"getCloudflareContext({ async: true })\");\n    expect(envFile).toContain(\"export const env = createEnvProxy(resolveEnvValue);\");\n    expect(envFile).not.toContain('export { env } from \"cloudflare:workers\";');\n    expect(envPackageFile).toContain('\"@opennextjs/cloudflare\"');\n    expect(dbFile).toContain(\"maxUses: 1\");\n    expect(routeFile).toContain(\"toNextJsHandler(createAuth()).GET(request)\");\n    expect(routeFile).toContain(\"toNextJsHandler(createAuth()).POST(request)\");\n    expect(dashboardFile).toContain(\"createAuth().api.getSession\");\n    expect(dashboardFile).not.toContain('import { authClient } from \"@/lib/auth-client\";');\n    expect(contextFile).toContain(\"createAuth().api.getSession\");\n  });\n\n  const selfCloudflareD1Scenarios = [\n    {\n      name: \"Next.js\",\n      frontend: \"next\",\n      api: \"trpc\",\n      routePath: \"apps/web/src/app/api/auth/[...all]/route.ts\",\n      routeNeedles: [\n        \"toNextJsHandler(createAuth()).GET(request)\",\n        \"toNextJsHandler(createAuth()).POST(request)\",\n      ],\n      envNeedle: 'import { getCloudflareContext } from \"@opennextjs/cloudflare\";',\n      envAbsentNeedle: 'export { env } from \"cloudflare:workers\";',\n    },\n    {\n      name: \"TanStack Start\",\n      frontend: \"tanstack-start\",\n      api: \"trpc\",\n      routePath: \"apps/web/src/routes/api/auth/$.ts\",\n      routeNeedles: [\"const auth = createAuth()\", \"return auth.handler(request)\"],\n      envNeedle: 'export { env } from \"cloudflare:workers\";',\n    },\n    {\n      name: \"Nuxt\",\n      frontend: \"nuxt\",\n      api: \"orpc\",\n      routePath: \"apps/web/server/api/auth/[...all].ts\",\n      routeNeedles: [\"const auth = createAuth();\", \"return auth.handler(toWebRequest(event));\"],\n      envNeedle: 'export { env } from \"cloudflare:workers\";',\n    },\n    {\n      name: \"Astro\",\n      frontend: \"astro\",\n      api: \"orpc\",\n      routePath: \"apps/web/src/pages/api/auth/[...all].ts\",\n      routeNeedles: [\"const auth = createAuth();\", \"return auth.handler(ctx.request);\"],\n      envNeedle: 'export { env } from \"cloudflare:workers\";',\n    },\n  ] as const;\n\n  for (const scenario of selfCloudflareD1Scenarios) {\n    it(`uses request-scoped D1 db/auth factories for ${scenario.name} with self backend on Cloudflare`, async () => {\n      const files = await createVirtualFiles({\n        projectName: `${scenario.frontend}-self-cloudflare-d1`,\n        frontend: [scenario.frontend],\n        backend: \"self\",\n        runtime: \"none\",\n        database: \"sqlite\",\n        orm: \"drizzle\",\n        auth: \"better-auth\",\n        addons: [\"none\"],\n        examples: [\"todo\"],\n        dbSetup: \"d1\",\n        webDeploy: \"cloudflare\",\n        serverDeploy: \"none\",\n        install: false,\n        git: false,\n        packageManager: \"bun\",\n        payments: \"none\",\n        api: scenario.api,\n      });\n\n      const dbFile = files.get(\"packages/db/src/index.ts\");\n      const authFile = files.get(\"packages/auth/src/index.ts\");\n      const envFile = files.get(\"packages/env/src/server.ts\");\n      const routeFile = files.get(scenario.routePath);\n      const contextFile = files.get(\"packages/api/src/context.ts\");\n      const todoRouterFile = files.get(\"packages/api/src/routers/todo.ts\");\n\n      expect(dbFile).toContain('import { drizzle } from \"drizzle-orm/d1\";');\n      expect(dbFile).toContain(\"return drizzle(env.DB, { schema });\");\n      expect(dbFile).not.toContain('import { drizzle } from \"drizzle-orm/libsql\";');\n      expect(dbFile).not.toContain(\"export const db = createDb();\");\n      expect(authFile).toContain(\"export function createAuth()\");\n      expect(authFile).not.toContain(\"export const auth = createAuth();\");\n      expect(envFile).toContain(scenario.envNeedle);\n      if (scenario.envAbsentNeedle) {\n        expect(envFile).not.toContain(scenario.envAbsentNeedle);\n      }\n      for (const needle of scenario.routeNeedles) {\n        expect(routeFile).toContain(needle);\n      }\n      expect(contextFile).toContain(\"createAuth().api.getSession\");\n      expect(todoRouterFile).toContain(\"const db = createDb();\");\n    });\n  }\n\n  it(\"uses Prisma D1 request-scoped factories for Next self backend on Cloudflare\", async () => {\n    const files = await createVirtualFiles({\n      projectName: \"next-self-cloudflare-prisma-d1\",\n      frontend: [\"next\"],\n      backend: \"self\",\n      runtime: \"none\",\n      database: \"sqlite\",\n      orm: \"prisma\",\n      auth: \"better-auth\",\n      addons: [\"none\"],\n      examples: [\"todo\"],\n      dbSetup: \"d1\",\n      webDeploy: \"cloudflare\",\n      serverDeploy: \"none\",\n      install: false,\n      git: false,\n      packageManager: \"bun\",\n      payments: \"none\",\n      api: \"trpc\",\n    });\n\n    const dbFile = files.get(\"packages/db/src/index.ts\");\n    const authFile = files.get(\"packages/auth/src/index.ts\");\n    const envFile = files.get(\"packages/env/src/server.ts\");\n    const routeFile = files.get(\"apps/web/src/app/api/auth/[...all]/route.ts\");\n    const contextFile = files.get(\"packages/api/src/context.ts\");\n\n    expect(dbFile).toContain('import { PrismaD1 } from \"@prisma/adapter-d1\";');\n    expect(dbFile).toContain(\"const adapter = new PrismaD1(env.DB);\");\n    expect(dbFile).not.toContain(\"export default prisma;\");\n    expect(authFile).toContain(\"const prisma = createPrismaClient();\");\n    expect(authFile).not.toContain(\"export const auth = createAuth();\");\n    expect(envFile).toContain('import { getCloudflareContext } from \"@opennextjs/cloudflare\";');\n    expect(envFile).toContain(\"type EnvValue = Env[keyof Env];\");\n    expect(routeFile).toContain(\"toNextJsHandler(createAuth()).GET(request)\");\n    expect(routeFile).toContain(\"toNextJsHandler(createAuth()).POST(request)\");\n    expect(contextFile).toContain(\"createAuth().api.getSession\");\n  });\n\n  it(\"uses maxUses=1 for Cloudflare-targeted Postgres pools\", async () => {\n    const files = await createVirtualFiles({\n      projectName: \"workers-postgres-pool-config\",\n      frontend: [\"tanstack-router\"],\n      backend: \"hono\",\n      runtime: \"workers\",\n      database: \"postgres\",\n      orm: \"drizzle\",\n      auth: \"better-auth\",\n      addons: [\"none\"],\n      examples: [\"none\"],\n      dbSetup: \"none\",\n      webDeploy: \"none\",\n      serverDeploy: \"cloudflare\",\n      install: false,\n      git: false,\n      packageManager: \"bun\",\n      payments: \"none\",\n      api: \"trpc\",\n    });\n    const dbFile = files.get(\"packages/db/src/index.ts\");\n\n    expect(dbFile).toContain('import { Pool } from \"pg\";');\n    expect(dbFile).toContain(\"maxUses: 1\");\n    expect(dbFile).toContain(\"return drizzle({ client: pool, schema });\");\n  });\n\n  it(\"keeps Better Auth MongoDB templates factory-only for Cloudflare Next deployments\", async () => {\n    const files = await createVirtualFiles({\n      projectName: \"next-cloudflare-mongodb-auth\",\n      frontend: [\"next\"],\n      backend: \"self\",\n      runtime: \"none\",\n      database: \"mongodb\",\n      orm: \"mongoose\",\n      auth: \"better-auth\",\n      addons: [\"none\"],\n      examples: [\"none\"],\n      dbSetup: \"mongodb-atlas\",\n      webDeploy: \"cloudflare\",\n      serverDeploy: \"none\",\n      install: false,\n      git: false,\n      packageManager: \"bun\",\n      payments: \"none\",\n      api: \"trpc\",\n    });\n    const authFile = files.get(\"packages/auth/src/index.ts\");\n    const routeFile = files.get(\"apps/web/src/app/api/auth/[...all]/route.ts\");\n\n    expect(authFile).toContain(\"export function createAuth()\");\n    expect(authFile).not.toContain(\"export const auth = createAuth();\");\n    expect(routeFile).toContain(\"toNextJsHandler(createAuth()).GET(request)\");\n  });\n\n  it(\"keeps singleton exports for non-Cloudflare runtimes\", async () => {\n    const files = await createVirtualFiles({\n      projectName: \"bun-singleton-db\",\n      frontend: [\"tanstack-router\"],\n      backend: \"hono\",\n      runtime: \"bun\",\n      database: \"sqlite\",\n      orm: \"drizzle\",\n      auth: \"better-auth\",\n      addons: [\"none\"],\n      examples: [\"todo\"],\n      dbSetup: \"none\",\n      webDeploy: \"none\",\n      serverDeploy: \"none\",\n      install: false,\n      git: false,\n      packageManager: \"bun\",\n      payments: \"none\",\n      api: \"trpc\",\n    });\n    const dbFile = files.get(\"packages/db/src/index.ts\");\n    const authFile = files.get(\"packages/auth/src/index.ts\");\n    const serverFile = files.get(\"apps/server/src/index.ts\");\n\n    expect(dbFile).toContain(\"export const db = createDb();\");\n    expect(authFile).toContain(\"export const auth = createAuth();\");\n    expect(serverFile).toContain(\"auth.handler(c.req.raw)\");\n  });\n\n  it(\"keeps singleton auth handlers for Next outside Cloudflare\", async () => {\n    const files = await createVirtualFiles({\n      projectName: \"next-singleton-auth\",\n      frontend: [\"next\"],\n      backend: \"self\",\n      runtime: \"none\",\n      database: \"postgres\",\n      orm: \"prisma\",\n      auth: \"better-auth\",\n      addons: [\"none\"],\n      examples: [\"todo\"],\n      dbSetup: \"none\",\n      webDeploy: \"none\",\n      serverDeploy: \"none\",\n      install: false,\n      git: false,\n      packageManager: \"bun\",\n      payments: \"none\",\n      api: \"trpc\",\n    });\n    const authFile = files.get(\"packages/auth/src/index.ts\");\n    const routeFile = files.get(\"apps/web/src/app/api/auth/[...all]/route.ts\");\n\n    expect(authFile).toContain(\"export const auth = createAuth();\");\n    expect(routeFile).toContain(\"export const { GET, POST } = toNextJsHandler(auth);\");\n    expect(routeFile).not.toContain(\"createAuth()\");\n  });\n});\n"
  },
  {
    "path": "apps/cli/test/database-orm.test.ts",
    "content": "import { describe, it } from \"bun:test\";\n\nimport type { Database, ORM } from \"../src/types\";\nimport { DATABASES, expectError, expectSuccess, runTRPCTest } from \"./test-utils\";\n\ndescribe(\"Database and ORM Combinations\", () => {\n  describe(\"Valid Database-ORM Combinations\", () => {\n    const validCombinations: Array<{ database: Database; orm: ORM }> = [\n      // SQLite combinations\n      { database: \"sqlite\" as Database, orm: \"drizzle\" as ORM },\n      { database: \"sqlite\" as Database, orm: \"prisma\" as ORM },\n\n      // PostgreSQL combinations\n      { database: \"postgres\" as Database, orm: \"drizzle\" as ORM },\n      { database: \"postgres\" as Database, orm: \"prisma\" as ORM },\n\n      // MySQL combinations\n      { database: \"mysql\" as Database, orm: \"drizzle\" as ORM },\n      { database: \"mysql\" as Database, orm: \"prisma\" as ORM },\n\n      // MongoDB combinations\n      { database: \"mongodb\" as Database, orm: \"mongoose\" as ORM },\n      { database: \"mongodb\" as Database, orm: \"prisma\" as ORM },\n\n      // None combinations\n      { database: \"none\" as Database, orm: \"none\" as ORM },\n    ];\n\n    for (const { database, orm } of validCombinations) {\n      it(`should work with ${database} + ${orm}`, async () => {\n        const result = await runTRPCTest({\n          projectName: `${database}-${orm}`,\n          database,\n          orm,\n          backend: \"hono\",\n          runtime: \"bun\",\n          frontend: [\"tanstack-router\"],\n          auth: \"none\",\n          api: \"trpc\",\n          addons: [\"none\"],\n          examples: [\"none\"],\n          dbSetup: \"none\",\n          webDeploy: \"none\",\n          serverDeploy: \"none\",\n          install: false,\n        });\n\n        expectSuccess(result);\n      });\n    }\n  });\n\n  describe(\"Invalid Database-ORM Combinations\", () => {\n    const invalidCombinations: Array<{\n      database: Database;\n      orm: ORM;\n      error: string;\n    }> = [\n      // MongoDB with Drizzle (not supported)\n      {\n        database: \"mongodb\" as Database,\n        orm: \"drizzle\" as ORM,\n        error: \"Drizzle ORM does not support MongoDB\",\n      },\n\n      // Mongoose with non-MongoDB\n      {\n        database: \"sqlite\" as Database,\n        orm: \"mongoose\" as ORM,\n        error: \"Mongoose ORM requires MongoDB database\",\n      },\n      {\n        database: \"postgres\" as Database,\n        orm: \"mongoose\" as ORM,\n        error: \"Mongoose ORM requires MongoDB database\",\n      },\n      {\n        database: \"mysql\" as Database,\n        orm: \"mongoose\" as ORM,\n        error: \"Mongoose ORM requires MongoDB database\",\n      },\n\n      // Database without ORM\n      {\n        database: \"sqlite\" as Database,\n        orm: \"none\" as ORM,\n        error: \"Database selection requires an ORM\",\n      },\n      {\n        database: \"postgres\" as Database,\n        orm: \"none\" as ORM,\n        error: \"Database selection requires an ORM\",\n      },\n\n      // ORM without database\n      {\n        database: \"none\" as Database,\n        orm: \"drizzle\" as ORM,\n        error: \"ORM selection requires a database\",\n      },\n      {\n        database: \"none\" as Database,\n        orm: \"prisma\" as ORM,\n        error: \"ORM selection requires a database\",\n      },\n    ];\n\n    for (const { database, orm, error } of invalidCombinations) {\n      it(`should fail with ${database} + ${orm}`, async () => {\n        const result = await runTRPCTest({\n          projectName: `invalid-${database}-${orm}`,\n          database,\n          orm,\n          backend: \"hono\",\n          runtime: \"bun\",\n          frontend: [\"tanstack-router\"],\n          auth: \"none\",\n          api: \"trpc\",\n          addons: [\"none\"],\n          examples: [\"none\"],\n          dbSetup: \"none\",\n          webDeploy: \"none\",\n          serverDeploy: \"none\",\n          expectError: true,\n        });\n\n        expectError(result, error);\n      });\n    }\n  });\n\n  describe(\"Database-ORM with Authentication\", () => {\n    it(\"should work with database + auth\", async () => {\n      const result = await runTRPCTest({\n        projectName: \"db-auth\",\n        database: \"sqlite\",\n        orm: \"drizzle\",\n        auth: \"better-auth\",\n        backend: \"hono\",\n        runtime: \"bun\",\n        frontend: [\"tanstack-router\"],\n        api: \"trpc\",\n        addons: [\"none\"],\n        examples: [\"none\"],\n        dbSetup: \"none\",\n        webDeploy: \"none\",\n        serverDeploy: \"none\",\n        install: false,\n      });\n\n      expectSuccess(result);\n    });\n\n    it(\"should work with auth but no database (non-convex backend)\", async () => {\n      const result = await runTRPCTest({\n        projectName: \"auth-no-db\",\n        database: \"none\",\n        orm: \"none\",\n        auth: \"better-auth\",\n        backend: \"hono\",\n        runtime: \"bun\",\n        frontend: [\"tanstack-router\"],\n        api: \"trpc\",\n        addons: [\"none\"],\n        examples: [\"none\"],\n        dbSetup: \"none\",\n        webDeploy: \"none\",\n        serverDeploy: \"none\",\n        expectError: true,\n      });\n\n      expectSuccess(result);\n    });\n\n    it(\"should work with auth but no database (convex backend)\", async () => {\n      const result = await runTRPCTest({\n        projectName: \"convex-auth-no-db\",\n        database: \"none\",\n        orm: \"none\",\n        auth: \"none\",\n        backend: \"convex\",\n        runtime: \"none\",\n        frontend: [\"tanstack-router\"],\n        api: \"none\",\n        addons: [\"none\"],\n        examples: [\"none\"],\n        dbSetup: \"none\",\n        webDeploy: \"none\",\n        serverDeploy: \"none\",\n        install: false,\n      });\n\n      expectSuccess(result);\n    });\n  });\n\n  describe(\"All Database Types\", () => {\n    for (const database of DATABASES) {\n      if (database === \"none\") continue;\n\n      it(`should have valid ORM options for ${database}`, async () => {\n        // Test with the most compatible ORM for each database\n        const ormMap = {\n          sqlite: \"drizzle\",\n          postgres: \"drizzle\",\n          mysql: \"drizzle\",\n          mongodb: \"mongoose\",\n        };\n\n        const orm = ormMap[database as keyof typeof ormMap];\n\n        const result = await runTRPCTest({\n          projectName: `test-${database}`,\n          database: database as Database,\n          orm: orm as ORM,\n          backend: \"hono\",\n          runtime: \"bun\",\n          frontend: [\"tanstack-router\"],\n          auth: \"none\",\n          api: \"trpc\",\n          addons: [\"none\"],\n          examples: [\"none\"],\n          dbSetup: \"none\",\n          webDeploy: \"none\",\n          serverDeploy: \"none\",\n          install: false,\n        });\n\n        expectSuccess(result);\n      });\n    }\n  });\n});\n"
  },
  {
    "path": "apps/cli/test/database-setup.test.ts",
    "content": "import { describe, it } from \"bun:test\";\n\nimport { DB_SETUPS, expectError, expectSuccess, runTRPCTest, type TestConfig } from \"./test-utils\";\n\ndescribe(\"Database Setup Configurations\", () => {\n  describe(\"SQLite Database Setups\", () => {\n    it(\"should work with Turso + SQLite\", async () => {\n      const result = await runTRPCTest({\n        projectName: \"turso-sqlite\",\n        database: \"sqlite\",\n        orm: \"drizzle\",\n        dbSetup: \"turso\",\n        backend: \"hono\",\n        runtime: \"bun\",\n        auth: \"none\",\n        api: \"trpc\",\n        frontend: [\"tanstack-router\"],\n        addons: [\"none\"],\n        examples: [\"none\"],\n        webDeploy: \"none\",\n        serverDeploy: \"none\",\n        manualDb: true,\n        install: false,\n      });\n\n      expectSuccess(result);\n    });\n\n    it(\"should work with D1 + SQLite + Workers\", async () => {\n      const result = await runTRPCTest({\n        projectName: \"d1-sqlite-workers\",\n        database: \"sqlite\",\n        orm: \"drizzle\",\n        dbSetup: \"d1\",\n        backend: \"hono\",\n        runtime: \"workers\",\n        auth: \"none\",\n        api: \"trpc\",\n        frontend: [\"tanstack-router\"],\n        addons: [\"none\"],\n        examples: [\"none\"],\n        webDeploy: \"none\",\n        serverDeploy: \"cloudflare\",\n        manualDb: true,\n        install: false,\n      });\n\n      expectSuccess(result);\n    });\n\n    it(\"should fail with Turso + non-SQLite database\", async () => {\n      const result = await runTRPCTest({\n        projectName: \"turso-postgres-fail\",\n        database: \"postgres\",\n        orm: \"drizzle\",\n        dbSetup: \"turso\",\n        backend: \"hono\",\n        runtime: \"bun\",\n        auth: \"none\",\n        api: \"trpc\",\n        frontend: [\"tanstack-router\"],\n        addons: [\"none\"],\n        examples: [\"none\"],\n        webDeploy: \"none\",\n        serverDeploy: \"none\",\n        manualDb: true,\n        expectError: true,\n      });\n\n      expectError(result, \"Turso setup requires SQLite database\");\n    });\n  });\n\n  describe(\"PostgreSQL Database Setups\", () => {\n    it(\"should work with Neon + PostgreSQL\", async () => {\n      const result = await runTRPCTest({\n        projectName: \"neon-postgres\",\n        database: \"postgres\",\n        orm: \"drizzle\",\n        dbSetup: \"neon\",\n        backend: \"hono\",\n        runtime: \"bun\",\n        auth: \"none\",\n        api: \"trpc\",\n        frontend: [\"tanstack-router\"],\n        addons: [\"none\"],\n        examples: [\"none\"],\n        webDeploy: \"none\",\n        serverDeploy: \"none\",\n        manualDb: true,\n        install: false,\n      });\n\n      expectSuccess(result);\n    });\n\n    it(\"should work with Supabase + PostgreSQL\", async () => {\n      const result = await runTRPCTest({\n        projectName: \"supabase-postgres\",\n        database: \"postgres\",\n        orm: \"drizzle\",\n        dbSetup: \"supabase\",\n        backend: \"hono\",\n        runtime: \"bun\",\n        auth: \"none\",\n        api: \"trpc\",\n        frontend: [\"tanstack-router\"],\n        addons: [\"none\"],\n        examples: [\"none\"],\n        webDeploy: \"none\",\n        serverDeploy: \"none\",\n        manualDb: true,\n        install: false,\n      });\n\n      expectSuccess(result);\n    });\n\n    it(\"should work with Prisma PostgreSQL setup\", async () => {\n      const result = await runTRPCTest({\n        projectName: \"prisma-postgres-setup\",\n        database: \"postgres\",\n        orm: \"prisma\",\n        dbSetup: \"prisma-postgres\",\n        backend: \"hono\",\n        runtime: \"bun\",\n        auth: \"none\",\n        api: \"trpc\",\n        frontend: [\"tanstack-router\"],\n        addons: [\"none\"],\n        examples: [\"none\"],\n        webDeploy: \"none\",\n        serverDeploy: \"none\",\n        manualDb: true,\n        install: false,\n      });\n\n      expectSuccess(result);\n    });\n\n    it(\"should fail with Neon + non-PostgreSQL database\", async () => {\n      const result = await runTRPCTest({\n        projectName: \"neon-mysql-fail\",\n        database: \"mysql\",\n        orm: \"drizzle\",\n        dbSetup: \"neon\",\n        backend: \"hono\",\n        runtime: \"bun\",\n        auth: \"none\",\n        api: \"trpc\",\n        frontend: [\"tanstack-router\"],\n        addons: [\"none\"],\n        examples: [\"none\"],\n        webDeploy: \"none\",\n        serverDeploy: \"none\",\n        manualDb: true,\n        expectError: true,\n      });\n\n      expectError(result, \"Neon setup requires PostgreSQL database\");\n    });\n  });\n\n  describe(\"MySQL Database Setups\", () => {\n    it(\"should work with PlanetScale + MySQL\", async () => {\n      const result = await runTRPCTest({\n        projectName: \"planetscale-mysql\",\n        database: \"mysql\",\n        orm: \"drizzle\",\n        dbSetup: \"planetscale\",\n        backend: \"hono\",\n        runtime: \"bun\",\n        auth: \"none\",\n        api: \"trpc\",\n        frontend: [\"tanstack-router\"],\n        addons: [\"none\"],\n        examples: [\"none\"],\n        webDeploy: \"none\",\n        serverDeploy: \"none\",\n        manualDb: true,\n        install: false,\n      });\n\n      expectSuccess(result);\n    });\n\n    it(\"should work with PlanetScale + PostgreSQL\", async () => {\n      const result = await runTRPCTest({\n        projectName: \"planetscale-postgres\",\n        database: \"postgres\",\n        orm: \"drizzle\",\n        dbSetup: \"planetscale\",\n        backend: \"hono\",\n        runtime: \"bun\",\n        auth: \"none\",\n        api: \"trpc\",\n        frontend: [\"tanstack-router\"],\n        addons: [\"none\"],\n        examples: [\"none\"],\n        webDeploy: \"none\",\n        serverDeploy: \"none\",\n        manualDb: true,\n        install: false,\n      });\n\n      expectSuccess(result);\n    });\n  });\n\n  describe(\"MongoDB Database Setups\", () => {\n    it(\"should work with MongoDB Atlas + MongoDB\", async () => {\n      const result = await runTRPCTest({\n        projectName: \"mongodb-atlas\",\n        database: \"mongodb\",\n        orm: \"mongoose\",\n        dbSetup: \"mongodb-atlas\",\n        backend: \"hono\",\n        runtime: \"bun\",\n        auth: \"none\",\n        api: \"trpc\",\n        frontend: [\"tanstack-router\"],\n        addons: [\"none\"],\n        examples: [\"none\"],\n        webDeploy: \"none\",\n        serverDeploy: \"none\",\n        manualDb: true,\n        install: false,\n      });\n\n      expectSuccess(result);\n    });\n\n    it(\"should fail with MongoDB Atlas + non-MongoDB database\", async () => {\n      const result = await runTRPCTest({\n        projectName: \"mongodb-atlas-sqlite-fail\",\n        database: \"sqlite\",\n        orm: \"drizzle\",\n        dbSetup: \"mongodb-atlas\",\n        backend: \"hono\",\n        runtime: \"bun\",\n        auth: \"none\",\n        api: \"trpc\",\n        frontend: [\"tanstack-router\"],\n        addons: [\"none\"],\n        examples: [\"none\"],\n        webDeploy: \"none\",\n        serverDeploy: \"none\",\n        manualDb: true,\n        expectError: true,\n      });\n\n      expectError(result, \"MongoDB Atlas setup requires MongoDB database\");\n    });\n  });\n\n  describe(\"Docker Database Setup\", () => {\n    it(\"should work with Docker + PostgreSQL\", async () => {\n      const result = await runTRPCTest({\n        projectName: \"docker-postgres\",\n        database: \"postgres\",\n        orm: \"drizzle\",\n        dbSetup: \"docker\",\n        backend: \"hono\",\n        runtime: \"bun\",\n        auth: \"none\",\n        api: \"trpc\",\n        frontend: [\"tanstack-router\"],\n        addons: [\"none\"],\n        examples: [\"none\"],\n        webDeploy: \"none\",\n        serverDeploy: \"none\",\n        manualDb: true,\n        install: false,\n      });\n\n      expectSuccess(result);\n    });\n\n    it(\"should work with Docker + MySQL\", async () => {\n      const result = await runTRPCTest({\n        projectName: \"docker-mysql\",\n        database: \"mysql\",\n        orm: \"drizzle\",\n        dbSetup: \"docker\",\n        backend: \"hono\",\n        runtime: \"bun\",\n        auth: \"none\",\n        api: \"trpc\",\n        frontend: [\"tanstack-router\"],\n        addons: [\"none\"],\n        examples: [\"none\"],\n        webDeploy: \"none\",\n        serverDeploy: \"none\",\n        manualDb: true,\n        install: false,\n      });\n\n      expectSuccess(result);\n    });\n\n    it(\"should work with Docker + MongoDB\", async () => {\n      const result = await runTRPCTest({\n        projectName: \"docker-mongodb\",\n        database: \"mongodb\",\n        orm: \"mongoose\",\n        dbSetup: \"docker\",\n        backend: \"hono\",\n        runtime: \"bun\",\n        auth: \"none\",\n        api: \"trpc\",\n        frontend: [\"tanstack-router\"],\n        addons: [\"none\"],\n        examples: [\"none\"],\n        webDeploy: \"none\",\n        serverDeploy: \"none\",\n        manualDb: true,\n        install: false,\n      });\n\n      expectSuccess(result);\n    });\n\n    it(\"should fail with Docker + SQLite\", async () => {\n      const result = await runTRPCTest({\n        projectName: \"docker-sqlite-fail\",\n        database: \"sqlite\",\n        orm: \"drizzle\",\n        dbSetup: \"docker\",\n        backend: \"hono\",\n        runtime: \"bun\",\n        auth: \"none\",\n        api: \"trpc\",\n        frontend: [\"tanstack-router\"],\n        addons: [\"none\"],\n        examples: [\"none\"],\n        webDeploy: \"none\",\n        serverDeploy: \"none\",\n        manualDb: true,\n        expectError: true,\n      });\n\n      expectError(result, \"Docker setup is not compatible with SQLite database\");\n    });\n  });\n\n  describe(\"No Database Setup\", () => {\n    it(\"should work with dbSetup none\", async () => {\n      const result = await runTRPCTest({\n        projectName: \"no-db-setup\",\n        database: \"sqlite\",\n        orm: \"drizzle\",\n        dbSetup: \"none\",\n        backend: \"hono\",\n        runtime: \"bun\",\n        auth: \"none\",\n        api: \"trpc\",\n        frontend: [\"tanstack-router\"],\n        addons: [\"none\"],\n        examples: [\"none\"],\n        webDeploy: \"none\",\n        serverDeploy: \"none\",\n        install: false,\n      });\n\n      expectSuccess(result);\n    });\n\n    it(\"should fail with dbSetup but no database\", async () => {\n      const result = await runTRPCTest({\n        projectName: \"db-setup-no-db-fail\",\n        database: \"none\",\n        orm: \"none\",\n        dbSetup: \"turso\",\n        backend: \"hono\",\n        runtime: \"bun\",\n        auth: \"none\",\n        api: \"trpc\",\n        frontend: [\"tanstack-router\"],\n        addons: [\"none\"],\n        examples: [\"none\"],\n        webDeploy: \"none\",\n        serverDeploy: \"none\",\n        expectError: true,\n      });\n\n      expectError(\n        result,\n        \"Database setup requires a database. Please choose a database or set '--db-setup none'.\",\n      );\n    });\n  });\n\n  describe(\"Special Runtime Constraints\", () => {\n    it(\"should work with D1 + Workers runtime\", async () => {\n      const result = await runTRPCTest({\n        projectName: \"d1-workers-valid\",\n        database: \"sqlite\",\n        orm: \"drizzle\",\n        dbSetup: \"d1\",\n        backend: \"hono\",\n        runtime: \"workers\",\n        auth: \"none\",\n        api: \"trpc\",\n        frontend: [\"tanstack-router\"],\n        addons: [\"none\"],\n        examples: [\"none\"],\n        webDeploy: \"none\",\n        serverDeploy: \"cloudflare\",\n        install: false,\n      });\n\n      expectSuccess(result);\n    });\n\n    it(\"should work with D1 + self backend + Cloudflare web deploy\", async () => {\n      const result = await runTRPCTest({\n        projectName: \"d1-self-cloudflare-valid\",\n        database: \"sqlite\",\n        orm: \"drizzle\",\n        dbSetup: \"d1\",\n        backend: \"self\",\n        runtime: \"none\",\n        auth: \"none\",\n        api: \"trpc\",\n        frontend: [\"next\"],\n        addons: [\"none\"],\n        examples: [\"none\"],\n        webDeploy: \"cloudflare\",\n        serverDeploy: \"none\",\n        install: false,\n      });\n\n      expectSuccess(result);\n    });\n\n    it(\"should fail with D1 + non-Workers runtime\", async () => {\n      const result = await runTRPCTest({\n        projectName: \"d1-node-fail\",\n        database: \"sqlite\",\n        orm: \"drizzle\",\n        dbSetup: \"d1\",\n        backend: \"hono\",\n        runtime: \"node\",\n        auth: \"none\",\n        api: \"trpc\",\n        frontend: [\"tanstack-router\"],\n        addons: [\"none\"],\n        examples: [\"none\"],\n        webDeploy: \"none\",\n        serverDeploy: \"none\",\n        expectError: true,\n      });\n\n      expectError(\n        result,\n        \"Cloudflare D1 setup requires SQLite database and either Cloudflare Workers runtime with server deployment or backend 'self' with Cloudflare web deployment.\",\n      );\n    });\n\n    it(\"should fail with D1 + self backend without Cloudflare web deploy\", async () => {\n      const result = await runTRPCTest({\n        projectName: \"d1-self-no-cloudflare-fail\",\n        database: \"sqlite\",\n        orm: \"drizzle\",\n        dbSetup: \"d1\",\n        backend: \"self\",\n        runtime: \"none\",\n        auth: \"none\",\n        api: \"trpc\",\n        frontend: [\"next\"],\n        addons: [\"none\"],\n        examples: [\"none\"],\n        webDeploy: \"none\",\n        serverDeploy: \"none\",\n        expectError: true,\n      });\n\n      expectError(\n        result,\n        \"Cloudflare D1 setup requires SQLite database and either Cloudflare Workers runtime with server deployment or backend 'self' with Cloudflare web deployment.\",\n      );\n    });\n  });\n\n  describe(\"All Database Setup Types\", () => {\n    for (const dbSetup of DB_SETUPS) {\n      if (dbSetup === \"none\") continue;\n\n      it(`should work with ${dbSetup} in appropriate setup`, async () => {\n        const config: TestConfig = {\n          projectName: `test-${dbSetup}`,\n          dbSetup,\n          backend: \"hono\",\n          runtime: \"bun\",\n          auth: \"none\",\n          api: \"trpc\",\n          frontend: [\"tanstack-router\"],\n          addons: [\"none\"],\n          examples: [\"none\"],\n          webDeploy: \"none\",\n          serverDeploy: \"none\",\n          manualDb: true,\n          install: false,\n        };\n\n        // Set appropriate database and ORM for each setup\n        switch (dbSetup) {\n          case \"turso\":\n            config.database = \"sqlite\";\n            config.orm = \"drizzle\";\n            break;\n          case \"neon\":\n          case \"supabase\":\n          case \"prisma-postgres\":\n            config.database = \"postgres\";\n            config.orm = \"drizzle\";\n            break;\n          case \"planetscale\":\n            config.database = \"mysql\";\n            config.orm = \"drizzle\";\n            break;\n          case \"mongodb-atlas\":\n            config.database = \"mongodb\";\n            config.orm = \"mongoose\";\n            break;\n          case \"d1\":\n            config.database = \"sqlite\";\n            config.orm = \"drizzle\";\n            config.runtime = \"workers\";\n            config.serverDeploy = \"cloudflare\";\n            break;\n          case \"docker\":\n            config.database = \"postgres\";\n            config.orm = \"drizzle\";\n            break;\n        }\n\n        const result = await runTRPCTest(config);\n        expectSuccess(result);\n      });\n    }\n  });\n});\n"
  },
  {
    "path": "apps/cli/test/db-setup-mode-resolution.test.ts",
    "content": "import { describe, expect, it } from \"bun:test\";\n\nimport {\n  mergeResolvedDbSetupOptions,\n  resolveDbSetupMode,\n} from \"../src/helpers/core/db-setup-options\";\nimport { runWithContext } from \"../src/utils/context\";\n\ndescribe(\"DB setup mode resolution\", () => {\n  it(\"does not force auto mode when manualDb is explicitly false\", () => {\n    const mode = runWithContext({ silent: false }, () =>\n      resolveDbSetupMode(\"neon\", { manualDb: false }),\n    );\n\n    expect(mode).toBeUndefined();\n  });\n\n  it(\"defaults remote provisioning setups to manual in silent mode\", () => {\n    const mode = runWithContext({ silent: true }, () => resolveDbSetupMode(\"supabase\"));\n\n    expect(mode).toBe(\"manual\");\n  });\n\n  it(\"drops dbSetupOptions when dbSetup is none\", () => {\n    const merged = runWithContext({ silent: false }, () =>\n      mergeResolvedDbSetupOptions(\"none\", { mode: \"manual\" }),\n    );\n\n    expect(merged).toBeUndefined();\n  });\n});\n"
  },
  {
    "path": "apps/cli/test/db-setup-options.test.ts",
    "content": "import { beforeEach, describe, expect, it } from \"bun:test\";\nimport path from \"node:path\";\n\nimport fs from \"fs-extra\";\n\nimport { create } from \"../src/index\";\nimport { readBtsConfig } from \"../src/utils/bts-config\";\n\nconst SMOKE_DIR_PATH = path.join(import.meta.dir, \"..\", \".smoke\");\n\ndescribe(\"Database setup options\", () => {\n  beforeEach(() => {\n    process.env.BTS_SKIP_EXTERNAL_COMMANDS = \"1\";\n    process.env.BTS_TEST_MODE = \"1\";\n  });\n\n  it(\"defaults remote provider setup to manual in silent mode and uses flags when mode is representable\", async () => {\n    const projectPath = path.join(SMOKE_DIR_PATH, \"db-setup-neon-default-manual\");\n    await fs.remove(projectPath);\n\n    const result = await create(projectPath, {\n      frontend: [\"tanstack-router\"],\n      backend: \"hono\",\n      runtime: \"bun\",\n      database: \"postgres\",\n      orm: \"drizzle\",\n      auth: \"none\",\n      payments: \"none\",\n      api: \"trpc\",\n      addons: [\"none\"],\n      examples: [\"none\"],\n      dbSetup: \"neon\",\n      webDeploy: \"none\",\n      serverDeploy: \"none\",\n      install: false,\n      disableAnalytics: true,\n    });\n\n    expect(result.isOk()).toBe(true);\n    if (result.isErr()) return;\n\n    expect(result.value.projectConfig.dbSetupOptions).toEqual({ mode: \"manual\" });\n    expect(result.value.reproducibleCommand).toContain(\"--db-setup neon\");\n    expect(result.value.reproducibleCommand).toContain(\"--manual-db\");\n    expect(result.value.reproducibleCommand).not.toContain(\"create-json --input\");\n\n    const btsConfig = await readBtsConfig(projectPath);\n    expect(btsConfig?.dbSetupOptions).toEqual({ mode: \"manual\" });\n  });\n\n  it(\"uses flags when dbSetupOptions only contains auto mode\", async () => {\n    const projectPath = path.join(SMOKE_DIR_PATH, \"db-setup-neon-auto-flags\");\n    await fs.remove(projectPath);\n\n    const result = await create(projectPath, {\n      frontend: [\"react-router\"],\n      backend: \"elysia\",\n      runtime: \"node\",\n      database: \"postgres\",\n      orm: \"drizzle\",\n      auth: \"better-auth\",\n      payments: \"none\",\n      api: \"trpc\",\n      addons: [\"nx\"],\n      examples: [\"todo\"],\n      dbSetup: \"neon\",\n      webDeploy: \"none\",\n      serverDeploy: \"none\",\n      git: true,\n      packageManager: \"bun\",\n      install: true,\n      dbSetupOptions: { mode: \"auto\" },\n      disableAnalytics: true,\n    });\n\n    expect(result.isOk()).toBe(true);\n    if (result.isErr()) return;\n\n    expect(result.value.reproducibleCommand).toContain(\"--db-setup neon\");\n    expect(result.value.reproducibleCommand).not.toContain(\"create-json --input\");\n    expect(result.value.reproducibleCommand).not.toContain(\"--manual-db\");\n  });\n\n  it(\"keeps reproducible command on normal flags when dbSetupOptions include provider-specific nested options\", async () => {\n    const projectPath = path.join(SMOKE_DIR_PATH, \"db-setup-neon-structured-options\");\n    await fs.remove(projectPath);\n\n    const result = await create(projectPath, {\n      frontend: [\"tanstack-router\"],\n      backend: \"hono\",\n      runtime: \"bun\",\n      database: \"postgres\",\n      orm: \"drizzle\",\n      auth: \"none\",\n      payments: \"none\",\n      api: \"trpc\",\n      addons: [\"none\"],\n      examples: [\"none\"],\n      dbSetup: \"neon\",\n      webDeploy: \"none\",\n      serverDeploy: \"none\",\n      dryRun: true,\n      install: false,\n      dbSetupOptions: {\n        mode: \"auto\",\n        neon: {\n          method: \"get-db\",\n        },\n      },\n      disableAnalytics: true,\n    });\n\n    expect(result.isOk()).toBe(true);\n    if (result.isErr()) return;\n\n    expect(result.value.reproducibleCommand).toContain(\"--db-setup neon\");\n    expect(result.value.reproducibleCommand).not.toContain(\"create-json --input\");\n  });\n\n  it(\"does not inject manual dbSetupOptions for non-provisioning setups\", async () => {\n    const projectPath = path.join(SMOKE_DIR_PATH, \"db-setup-d1-no-manual-default\");\n    await fs.remove(projectPath);\n\n    const result = await create(projectPath, {\n      frontend: [\"tanstack-router\"],\n      backend: \"hono\",\n      runtime: \"workers\",\n      database: \"sqlite\",\n      orm: \"drizzle\",\n      auth: \"none\",\n      payments: \"none\",\n      api: \"trpc\",\n      addons: [\"none\"],\n      examples: [\"none\"],\n      dbSetup: \"d1\",\n      webDeploy: \"none\",\n      serverDeploy: \"cloudflare\",\n      install: false,\n      disableAnalytics: true,\n    });\n\n    expect(result.isOk()).toBe(true);\n    if (result.isErr()) return;\n\n    expect(result.value.projectConfig.dbSetupOptions).toBeUndefined();\n\n    const btsConfig = await readBtsConfig(projectPath);\n    expect(btsConfig?.dbSetupOptions).toBeUndefined();\n  });\n\n  it(\"does not persist dbSetupOptions or force create-json when dbSetup is none\", async () => {\n    const projectPath = path.join(SMOKE_DIR_PATH, \"db-setup-none-no-structured-options\");\n    await fs.remove(projectPath);\n\n    const result = await create(projectPath, {\n      frontend: [\"tanstack-router\"],\n      backend: \"hono\",\n      runtime: \"bun\",\n      database: \"sqlite\",\n      orm: \"drizzle\",\n      auth: \"better-auth\",\n      payments: \"none\",\n      api: \"trpc\",\n      addons: [\"turborepo\"],\n      examples: [\"todo\"],\n      dbSetup: \"none\",\n      webDeploy: \"none\",\n      serverDeploy: \"none\",\n      git: true,\n      packageManager: \"bun\",\n      install: true,\n      manualDb: false,\n      disableAnalytics: true,\n    });\n\n    expect(result.isOk()).toBe(true);\n    if (result.isErr()) return;\n\n    expect(result.value.projectConfig.dbSetupOptions).toBeUndefined();\n    expect(result.value.reproducibleCommand).not.toContain(\"create-json --input\");\n\n    const btsConfig = await readBtsConfig(projectPath);\n    expect(btsConfig?.dbSetupOptions).toBeUndefined();\n  });\n});\n"
  },
  {
    "path": "apps/cli/test/deployment.test.ts",
    "content": "import { describe, it } from \"bun:test\";\n\nimport {\n  expectError,\n  expectSuccess,\n  runTRPCTest,\n  SERVER_DEPLOYS,\n  type TestConfig,\n  WEB_DEPLOYS,\n} from \"./test-utils\";\n\ndescribe(\"Deployment Configurations\", () => {\n  describe(\"Web Deployment\", () => {\n    describe(\"Valid Web Deploy Configurations\", () => {\n      for (const webDeploy of WEB_DEPLOYS) {\n        if (webDeploy === \"none\") continue;\n\n        it(`should work with ${webDeploy} web deploy + web frontend`, async () => {\n          const result = await runTRPCTest({\n            projectName: `${webDeploy}-web-deploy`,\n            webDeploy: webDeploy,\n            serverDeploy: \"none\",\n            frontend: [\"tanstack-router\"],\n            backend: \"hono\",\n            runtime: \"bun\",\n            database: \"sqlite\",\n            orm: \"drizzle\",\n            auth: \"none\",\n            api: \"trpc\",\n            addons: [\"none\"],\n            examples: [\"none\"],\n            dbSetup: \"none\",\n            install: false,\n          });\n\n          expectSuccess(result);\n        });\n      }\n    });\n\n    it(\"should work with web deploy none\", async () => {\n      const result = await runTRPCTest({\n        projectName: \"no-web-deploy\",\n        webDeploy: \"none\",\n        serverDeploy: \"none\",\n        frontend: [\"tanstack-router\"],\n        backend: \"hono\",\n        runtime: \"bun\",\n        database: \"sqlite\",\n        orm: \"drizzle\",\n        auth: \"none\",\n        api: \"trpc\",\n        addons: [\"none\"],\n        examples: [\"none\"],\n        dbSetup: \"none\",\n        install: false,\n      });\n\n      expectSuccess(result);\n    });\n\n    it(\"should fail with web deploy but no web frontend\", async () => {\n      const result = await runTRPCTest({\n        projectName: \"web-deploy-no-web-frontend-fail\",\n        webDeploy: \"cloudflare\",\n        serverDeploy: \"none\",\n        frontend: [\"native-bare\"], // Native frontend only\n        backend: \"hono\",\n        runtime: \"bun\",\n        database: \"sqlite\",\n        orm: \"drizzle\",\n        auth: \"none\",\n        api: \"trpc\",\n        addons: [\"none\"],\n        examples: [\"none\"],\n        dbSetup: \"none\",\n        expectError: true,\n      });\n\n      expectError(result, \"'--web-deploy' requires a web frontend\");\n    });\n\n    it(\"should work with web deploy + mixed web and native frontends\", async () => {\n      const result = await runTRPCTest({\n        projectName: \"web-deploy-mixed-frontends\",\n        webDeploy: \"cloudflare\",\n        serverDeploy: \"none\",\n        frontend: [\"tanstack-router\", \"native-bare\"],\n        backend: \"hono\",\n        runtime: \"bun\",\n        database: \"sqlite\",\n        orm: \"drizzle\",\n        auth: \"none\",\n        api: \"trpc\",\n        addons: [\"none\"],\n        examples: [\"none\"],\n        dbSetup: \"none\",\n        install: false,\n      });\n\n      expectSuccess(result);\n    });\n\n    it(\"should work with web deploy + all web frontends\", async () => {\n      const webFrontends = [\n        \"tanstack-router\",\n        \"react-router\",\n        \"tanstack-start\",\n        \"next\",\n        \"nuxt\",\n        \"svelte\",\n        \"solid\",\n        \"astro\",\n      ] as const;\n\n      for (const frontend of webFrontends) {\n        const config: TestConfig = {\n          projectName: `web-deploy-${frontend}`,\n          webDeploy: \"cloudflare\",\n          serverDeploy: \"none\",\n          frontend: [frontend],\n          backend: \"hono\",\n          runtime: \"bun\",\n          database: \"sqlite\",\n          orm: \"drizzle\",\n          auth: \"none\",\n          addons: [\"none\"],\n          examples: [\"none\"],\n          dbSetup: \"none\",\n          install: false,\n        };\n\n        // Handle API compatibility\n        if ([\"nuxt\", \"svelte\", \"solid\", \"astro\"].includes(frontend)) {\n          config.api = \"orpc\";\n        } else {\n          config.api = \"trpc\";\n        }\n\n        const result = await runTRPCTest(config);\n        expectSuccess(result);\n      }\n    });\n  });\n\n  describe(\"Server Deployment\", () => {\n    describe(\"Valid Server Deploy Configurations\", () => {\n      for (const serverDeploy of SERVER_DEPLOYS) {\n        if (serverDeploy === \"none\") continue;\n\n        it(`should work with ${serverDeploy} server deploy + backend`, async () => {\n          const result = await runTRPCTest({\n            projectName: `${serverDeploy}-server-deploy`,\n            webDeploy: \"none\",\n            serverDeploy: serverDeploy,\n            backend: \"hono\",\n            runtime: serverDeploy === \"cloudflare\" ? \"workers\" : \"bun\",\n            database: \"sqlite\",\n            orm: \"drizzle\",\n            auth: \"none\",\n            api: \"trpc\",\n            frontend: [\"tanstack-router\"],\n            addons: [\"none\"],\n            examples: [\"none\"],\n            dbSetup: \"none\",\n            install: false,\n          });\n\n          expectSuccess(result);\n        });\n      }\n    });\n\n    it(\"should work with server deploy none\", async () => {\n      const result = await runTRPCTest({\n        projectName: \"no-server-deploy\",\n        webDeploy: \"none\",\n        serverDeploy: \"none\",\n        backend: \"hono\",\n        runtime: \"bun\",\n        database: \"sqlite\",\n        orm: \"drizzle\",\n        auth: \"none\",\n        api: \"trpc\",\n        frontend: [\"tanstack-router\"],\n        addons: [\"none\"],\n        examples: [\"none\"],\n        dbSetup: \"none\",\n        install: false,\n      });\n\n      expectSuccess(result);\n    });\n\n    it(\"should fail with server deploy but no backend\", async () => {\n      const result = await runTRPCTest({\n        projectName: \"server-deploy-no-backend-fail\",\n        webDeploy: \"none\",\n        serverDeploy: \"cloudflare\",\n        backend: \"none\",\n        runtime: \"none\",\n        database: \"none\",\n        orm: \"none\",\n        auth: \"none\",\n        api: \"none\",\n        frontend: [\"tanstack-router\"],\n        addons: [\"none\"],\n        examples: [\"none\"],\n        dbSetup: \"none\",\n        expectError: true,\n      });\n\n      expectError(\n        result,\n        \"Backend 'none' requires '--server-deploy none'. Please remove the --server-deploy flag or set it to 'none'.\",\n      );\n    });\n\n    it(\"should work with server deploy + all compatible backends\", async () => {\n      const backends = [\"hono\", \"express\", \"fastify\", \"elysia\"] as const;\n\n      for (const backend of backends) {\n        const config: TestConfig = {\n          projectName: `server-deploy-${backend}`,\n          webDeploy: \"none\",\n          backend,\n          database: \"sqlite\",\n          orm: \"drizzle\",\n          auth: \"none\",\n          api: \"trpc\",\n          frontend: [\"tanstack-router\"],\n          addons: [\"none\"],\n          examples: [\"none\"],\n          dbSetup: \"none\",\n          install: false,\n          runtime: \"workers\",\n        };\n\n        // Set appropriate runtime\n        if (backend === \"hono\") {\n          config.runtime = \"workers\";\n          config.serverDeploy = \"cloudflare\";\n        } else {\n          config.runtime = \"bun\";\n          config.serverDeploy = \"none\";\n        }\n\n        const result = await runTRPCTest(config);\n        expectSuccess(result);\n      }\n    });\n\n    it(\"should fail with server deploy + convex backend\", async () => {\n      const result = await runTRPCTest({\n        projectName: \"server-deploy-convex-fail\",\n        webDeploy: \"none\",\n        serverDeploy: \"cloudflare\",\n        backend: \"convex\",\n        runtime: \"none\",\n        database: \"none\",\n        orm: \"none\",\n        auth: \"clerk\",\n        api: \"none\",\n        frontend: [\"tanstack-router\"],\n        addons: [\"none\"],\n        examples: [\"none\"],\n        dbSetup: \"none\",\n        expectError: true,\n      });\n\n      expectError(result, \"Convex backend requires '--server-deploy none'\");\n    });\n  });\n\n  describe(\"Workers Runtime Deployment Constraints\", () => {\n    it(\"should work with workers runtime + server deploy\", async () => {\n      const result = await runTRPCTest({\n        projectName: \"workers-server-deploy\",\n        webDeploy: \"none\",\n        runtime: \"workers\",\n        serverDeploy: \"cloudflare\",\n        backend: \"hono\",\n        database: \"sqlite\",\n        orm: \"drizzle\",\n        auth: \"none\",\n        api: \"trpc\",\n        frontend: [\"tanstack-router\"],\n        addons: [\"none\"],\n        examples: [\"none\"],\n        dbSetup: \"d1\",\n        install: false,\n      });\n\n      expectSuccess(result);\n    });\n\n    it(\"should fail with workers runtime + no server deploy\", async () => {\n      const result = await runTRPCTest({\n        projectName: \"workers-no-server-deploy-fail\",\n        runtime: \"workers\",\n        serverDeploy: \"none\",\n        backend: \"hono\",\n        database: \"sqlite\",\n        orm: \"drizzle\",\n        auth: \"none\",\n        api: \"trpc\",\n        frontend: [\"tanstack-router\"],\n        addons: [\"none\"],\n        examples: [\"none\"],\n        dbSetup: \"none\",\n        webDeploy: \"none\",\n        expectError: true,\n      });\n\n      expectError(result, \"Cloudflare Workers runtime requires a server deployment\");\n    });\n  });\n\n  describe(\"Combined Web and Server Deployment\", () => {\n    it(\"should work with both web and server deploy\", async () => {\n      const result = await runTRPCTest({\n        projectName: \"web-server-deploy-combo\",\n        webDeploy: \"cloudflare\",\n        serverDeploy: \"cloudflare\",\n        backend: \"hono\",\n        runtime: \"workers\",\n        database: \"sqlite\",\n        orm: \"drizzle\",\n        auth: \"none\",\n        api: \"trpc\",\n        frontend: [\"tanstack-router\"],\n        addons: [\"none\"],\n        examples: [\"none\"],\n        dbSetup: \"none\",\n        install: false,\n      });\n\n      expectSuccess(result);\n    });\n\n    it(\"should work with different deploy providers\", async () => {\n      const result = await runTRPCTest({\n        projectName: \"different-deploy-providers\",\n        webDeploy: \"cloudflare\",\n        serverDeploy: \"cloudflare\",\n        backend: \"hono\",\n        runtime: \"workers\",\n        database: \"sqlite\",\n        orm: \"drizzle\",\n        auth: \"none\",\n        api: \"trpc\",\n        frontend: [\"tanstack-router\"],\n        addons: [\"none\"],\n        examples: [\"none\"],\n        dbSetup: \"none\",\n        install: false,\n      });\n\n      expectSuccess(result);\n    });\n\n    it(\"should work with web deploy only\", async () => {\n      const result = await runTRPCTest({\n        projectName: \"web-deploy-only\",\n        webDeploy: \"cloudflare\",\n        serverDeploy: \"none\",\n        backend: \"hono\",\n        runtime: \"bun\",\n        database: \"sqlite\",\n        orm: \"drizzle\",\n        auth: \"none\",\n        api: \"trpc\",\n        frontend: [\"tanstack-router\"],\n        addons: [\"none\"],\n        examples: [\"none\"],\n        dbSetup: \"none\",\n        install: false,\n      });\n\n      expectSuccess(result);\n    });\n\n    it(\"should work with server deploy only\", async () => {\n      const result = await runTRPCTest({\n        projectName: \"server-deploy-only\",\n        webDeploy: \"none\",\n        serverDeploy: \"cloudflare\",\n        backend: \"hono\",\n        runtime: \"workers\",\n        database: \"sqlite\",\n        orm: \"drizzle\",\n        auth: \"none\",\n        api: \"trpc\",\n        frontend: [\"tanstack-router\"],\n        addons: [\"none\"],\n        examples: [\"none\"],\n        dbSetup: \"none\",\n        install: false,\n      });\n\n      expectSuccess(result);\n    });\n  });\n\n  describe(\"Deployment with Special Backend Constraints\", () => {\n    it(\"should work with deployment + self backend\", async () => {\n      const result = await runTRPCTest({\n        projectName: \"deploy-self-backend\",\n        webDeploy: \"cloudflare\",\n        serverDeploy: \"none\", // Self backend doesn't use server deployment\n        backend: \"self\",\n        runtime: \"none\",\n        database: \"sqlite\",\n        orm: \"drizzle\",\n        auth: \"better-auth\",\n        api: \"trpc\",\n        frontend: [\"next\"],\n        addons: [\"none\"],\n        examples: [\"none\"],\n        dbSetup: \"none\",\n        install: false,\n      });\n\n      expectSuccess(result);\n    });\n\n    it(\"should work with deployment + fullstack setup\", async () => {\n      const result = await runTRPCTest({\n        projectName: \"deploy-fullstack\",\n        webDeploy: \"cloudflare\",\n        serverDeploy: \"cloudflare\",\n        backend: \"hono\",\n        runtime: \"workers\",\n        database: \"sqlite\",\n        orm: \"drizzle\",\n        auth: \"none\",\n        api: \"trpc\",\n        frontend: [\"tanstack-router\"],\n        addons: [\"none\"],\n        examples: [\"none\"],\n        dbSetup: \"none\",\n        install: false,\n      });\n\n      expectSuccess(result);\n    });\n  });\n\n  describe(\"All Deployment Options\", () => {\n    const deployOptions: ReadonlyArray<{\n      webDeploy: TestConfig[\"webDeploy\"];\n      serverDeploy: TestConfig[\"serverDeploy\"];\n    }> = [\n      { webDeploy: \"cloudflare\", serverDeploy: \"cloudflare\" },\n      { webDeploy: \"none\", serverDeploy: \"cloudflare\" },\n      { webDeploy: \"none\", serverDeploy: \"none\" },\n    ];\n\n    for (const { webDeploy, serverDeploy } of deployOptions) {\n      it(`should work with webDeploy: ${webDeploy}, serverDeploy: ${serverDeploy}`, async () => {\n        const config: TestConfig = {\n          projectName: `deploy-${webDeploy}-${serverDeploy}`,\n          webDeploy,\n          serverDeploy,\n          backend: \"hono\",\n          runtime: \"workers\",\n          database: \"sqlite\",\n          orm: \"drizzle\",\n          auth: \"none\",\n          api: \"trpc\",\n          frontend: [\"tanstack-router\"],\n          addons: [\"none\"],\n          examples: [\"none\"],\n          dbSetup: \"none\",\n          install: false,\n        };\n\n        // Handle special cases\n        if (\n          webDeploy !== \"none\" &&\n          !config.frontend?.some((f) =>\n            [\n              \"tanstack-router\",\n              \"react-router\",\n              \"tanstack-start\",\n              \"next\",\n              \"nuxt\",\n              \"svelte\",\n              \"solid\",\n              \"astro\",\n            ].includes(f),\n          )\n        ) {\n          config.frontend = [\"tanstack-router\"]; // Ensure web frontend for web deploy\n        }\n\n        if (serverDeploy !== \"none\" && config.backend === \"none\") {\n          config.backend = \"hono\"; // Ensure backend for server deploy\n        }\n\n        if (serverDeploy === \"none\" && webDeploy === \"none\") {\n          config.runtime = \"bun\";\n        }\n\n        const result = await runTRPCTest(config);\n        expectSuccess(result);\n      });\n    }\n  });\n\n  describe(\"Deployment Edge Cases\", () => {\n    it(\"should handle deployment with complex configurations\", async () => {\n      const result = await runTRPCTest({\n        projectName: \"complex-deployment\",\n        webDeploy: \"cloudflare\",\n        serverDeploy: \"cloudflare\",\n        backend: \"hono\",\n        runtime: \"workers\",\n        database: \"sqlite\",\n        orm: \"drizzle\",\n        auth: \"none\",\n        api: \"trpc\",\n        frontend: [\"tanstack-router\"], // Single web frontend (compatible with PWA)\n        addons: [\"pwa\", \"turborepo\"],\n        examples: [\"todo\"],\n        install: false,\n      });\n\n      expectSuccess(result);\n    });\n\n    it(\"should handle deployment constraints properly\", async () => {\n      // This should fail because we have web deploy but only native frontend\n      const result = await runTRPCTest({\n        projectName: \"deployment-constraints-fail\",\n        webDeploy: \"cloudflare\",\n        serverDeploy: \"none\",\n        backend: \"none\", // No backend but we have server deploy\n        runtime: \"none\",\n        database: \"none\",\n        orm: \"none\",\n        auth: \"none\",\n        api: \"none\",\n        frontend: [\"native-bare\"], // Only native frontend\n        addons: [\"none\"],\n        examples: [\"none\"],\n        dbSetup: \"none\",\n        expectError: true,\n      });\n\n      expectError(result, \"'--web-deploy' requires a web frontend\");\n    });\n  });\n});\n"
  },
  {
    "path": "apps/cli/test/dry-run.test.ts",
    "content": "import { describe, expect, it } from \"bun:test\";\nimport path from \"node:path\";\n\nimport fs from \"fs-extra\";\n\nimport { create } from \"../src/index\";\n\nconst SMOKE_DIR_PATH = path.join(import.meta.dir, \"..\", \".smoke\");\n\ndescribe(\"Dry run\", () => {\n  it(\"does not create project directory on dry run\", async () => {\n    const projectPath = path.join(SMOKE_DIR_PATH, \"dry-run-no-write\");\n    await fs.remove(projectPath);\n\n    const result = await create(projectPath, {\n      yes: true,\n      dryRun: true,\n      disableAnalytics: true,\n      directoryConflict: \"overwrite\",\n    });\n\n    expect(result.isOk()).toBe(true);\n    expect(await fs.pathExists(projectPath)).toBe(false);\n  });\n\n  it(\"does not clear existing directory with overwrite strategy on dry run\", async () => {\n    const projectPath = path.join(SMOKE_DIR_PATH, \"dry-run-overwrite-protected\");\n    const sentinelPath = path.join(projectPath, \"do-not-delete.txt\");\n\n    await fs.ensureDir(projectPath);\n    await fs.writeFile(sentinelPath, \"keep-me\", \"utf8\");\n\n    const result = await create(projectPath, {\n      yes: true,\n      dryRun: true,\n      disableAnalytics: true,\n      directoryConflict: \"overwrite\",\n    });\n\n    expect(result.isOk()).toBe(true);\n    expect(await fs.pathExists(sentinelPath)).toBe(true);\n    expect(await fs.readFile(sentinelPath, \"utf8\")).toBe(\"keep-me\");\n  });\n});\n"
  },
  {
    "path": "apps/cli/test/electrobun-addon.test.ts",
    "content": "import { describe, expect, it } from \"bun:test\";\nimport path from \"node:path\";\n\nimport fs from \"fs-extra\";\n\nimport { runTRPCTest } from \"./test-utils\";\n\ndescribe(\"Electrobun addon scaffolding\", () => {\n  it(\"scaffolds the desktop workspace for TanStack Router\", async () => {\n    const result = await runTRPCTest({\n      projectName: \"electrobun-files-tanstack-router-static-v2\",\n      addons: [\"electrobun\"],\n      frontend: [\"tanstack-router\"],\n      backend: \"hono\",\n      runtime: \"bun\",\n      database: \"sqlite\",\n      orm: \"drizzle\",\n      auth: \"none\",\n      api: \"trpc\",\n      examples: [\"none\"],\n      dbSetup: \"none\",\n      webDeploy: \"none\",\n      serverDeploy: \"none\",\n      install: false,\n    });\n\n    expect(result.success).toBe(true);\n    expect(result.projectDir).toBeDefined();\n    if (!result.projectDir) return;\n\n    const rootPackageJson = await fs.readJson(path.join(result.projectDir, \"package.json\"));\n    const desktopPackageJson = await fs.readJson(\n      path.join(result.projectDir, \"apps\", \"desktop\", \"package.json\"),\n    );\n    const desktopConfig = await fs.readFile(\n      path.join(result.projectDir, \"apps\", \"desktop\", \"electrobun.config.ts\"),\n      \"utf8\",\n    );\n    const desktopEntry = await fs.readFile(\n      path.join(result.projectDir, \"apps\", \"desktop\", \"src\", \"bun\", \"index.ts\"),\n      \"utf8\",\n    );\n    const fallbackHtmlExists = await fs.pathExists(\n      path.join(result.projectDir, \"apps\", \"desktop\", \"src\", \"fallback\", \"index.html\"),\n    );\n\n    expect(rootPackageJson.scripts[\"dev:desktop\"]).toBeDefined();\n    expect(rootPackageJson.scripts[\"build:desktop\"]).toBeDefined();\n    expect(rootPackageJson.scripts[\"build:desktop:canary\"]).toBeDefined();\n    expect(desktopPackageJson.scripts.start).toBeDefined();\n    expect(desktopPackageJson.scripts.dev).toBeDefined();\n    expect(desktopPackageJson.scripts[\"dev:hmr\"]).toBeDefined();\n    expect(desktopPackageJson.scripts.hmr).toContain(\"bun run --filter web dev\");\n    expect(desktopPackageJson.scripts[\"build:stable\"]).toContain(\"--env=stable\");\n    expect(desktopPackageJson.scripts[\"build:canary\"]).toContain(\"--env=canary\");\n    expect(desktopPackageJson.scripts.start).toContain(\"bun run --filter web build\");\n    expect(desktopConfig).toContain('const webBuildDir = \"../web/dist\";');\n    expect(desktopConfig).toContain('[webBuildDir]: \"views/mainview\"');\n    expect(desktopConfig).toContain(\"watchIgnore: [`${webBuildDir}/**`]\");\n    expect(desktopConfig).toContain(\"bundleCEF: true\");\n    expect(desktopConfig).toContain('defaultRenderer: \"cef\"');\n    expect(desktopEntry).toContain(\"const DEV_SERVER_PORT = 3001;\");\n    expect(desktopConfig).not.toContain(\"views/fallback\");\n    expect(fallbackHtmlExists).toBe(false);\n  });\n\n  it(\"uses the React Router client build output for packaged desktop assets\", async () => {\n    const result = await runTRPCTest({\n      projectName: \"electrobun-files-react-router-static-v2\",\n      addons: [\"electrobun\"],\n      frontend: [\"react-router\"],\n      backend: \"hono\",\n      runtime: \"bun\",\n      database: \"sqlite\",\n      orm: \"drizzle\",\n      auth: \"none\",\n      api: \"trpc\",\n      examples: [\"none\"],\n      dbSetup: \"none\",\n      webDeploy: \"none\",\n      serverDeploy: \"none\",\n      install: false,\n    });\n\n    expect(result.success).toBe(true);\n    expect(result.projectDir).toBeDefined();\n    if (!result.projectDir) return;\n\n    const desktopConfig = await fs.readFile(\n      path.join(result.projectDir, \"apps\", \"desktop\", \"electrobun.config.ts\"),\n      \"utf8\",\n    );\n    const desktopEntry = await fs.readFile(\n      path.join(result.projectDir, \"apps\", \"desktop\", \"src\", \"bun\", \"index.ts\"),\n      \"utf8\",\n    );\n\n    expect(desktopConfig).toContain('const webBuildDir = \"../web/build/client\";');\n    expect(desktopEntry).toContain(\"const DEV_SERVER_PORT = 5173;\");\n  });\n\n  it(\"maps desktop asset output and dev ports for non-Vite frontends\", async () => {\n    const cases = [\n      {\n        frontend: \"tanstack-start\",\n        api: \"trpc\",\n        expectedOutputDir: 'const webBuildDir = \"../web/dist/client\";',\n        expectedPort: \"const DEV_SERVER_PORT = 3001;\",\n      },\n      {\n        frontend: \"next\",\n        api: \"trpc\",\n        expectedOutputDir: 'const webBuildDir = \"../web/out\";',\n        expectedPort: \"const DEV_SERVER_PORT = 3001;\",\n      },\n      {\n        frontend: \"nuxt\",\n        api: \"orpc\",\n        expectedOutputDir: 'const webBuildDir = \"../web/.output/public\";',\n        expectedPort: \"const DEV_SERVER_PORT = 3001;\",\n        expectedBuildCommand: \"bun run --filter web generate\",\n      },\n      {\n        frontend: \"svelte\",\n        api: \"orpc\",\n        expectedOutputDir: 'const webBuildDir = \"../web/build\";',\n        expectedPort: \"const DEV_SERVER_PORT = 5173;\",\n      },\n      {\n        frontend: \"solid\",\n        api: \"orpc\",\n        expectedOutputDir: 'const webBuildDir = \"../web/dist\";',\n        expectedPort: \"const DEV_SERVER_PORT = 3001;\",\n      },\n      {\n        frontend: \"astro\",\n        api: \"orpc\",\n        expectedOutputDir: 'const webBuildDir = \"../web/dist\";',\n        expectedPort: \"const DEV_SERVER_PORT = 4321;\",\n      },\n    ] as const;\n\n    for (const testCase of cases) {\n      const result = await runTRPCTest({\n        projectName: `electrobun-files-${testCase.frontend}-static-v2`,\n        addons: [\"electrobun\"],\n        frontend: [testCase.frontend],\n        backend: \"hono\",\n        runtime: \"bun\",\n        database: \"sqlite\",\n        orm: \"drizzle\",\n        auth: \"none\",\n        api: testCase.api,\n        examples: [\"none\"],\n        dbSetup: \"none\",\n        webDeploy: \"none\",\n        serverDeploy: \"none\",\n        install: false,\n      });\n\n      expect(result.success).toBe(true);\n      expect(result.projectDir).toBeDefined();\n      if (!result.projectDir) continue;\n\n      const desktopConfig = await fs.readFile(\n        path.join(result.projectDir, \"apps\", \"desktop\", \"electrobun.config.ts\"),\n        \"utf8\",\n      );\n      const desktopEntry = await fs.readFile(\n        path.join(result.projectDir, \"apps\", \"desktop\", \"src\", \"bun\", \"index.ts\"),\n        \"utf8\",\n      );\n      const desktopPackageJson = await fs.readJson(\n        path.join(result.projectDir, \"apps\", \"desktop\", \"package.json\"),\n      );\n\n      expect(desktopConfig).toContain(testCase.expectedOutputDir);\n      expect(desktopEntry).toContain(testCase.expectedPort);\n      if (\"expectedBuildCommand\" in testCase) {\n        expect(desktopPackageJson.scripts.start).toContain(testCase.expectedBuildCommand);\n        expect(desktopPackageJson.scripts[\"build:stable\"]).toContain(testCase.expectedBuildCommand);\n      }\n    }\n  });\n\n  it(\"uses the configured monorepo runner for desktop web commands\", async () => {\n    const cases = [\n      {\n        projectName: \"electrobun-turbo-runner-static-v2\",\n        addons: [\"turborepo\", \"electrobun\"] as const,\n        expectedRunner: \"turbo -F web build\",\n        expectedHmr: \"turbo -F web dev\",\n      },\n      {\n        projectName: \"electrobun-nx-runner-static-v2\",\n        addons: [\"nx\", \"electrobun\"] as const,\n        expectedRunner: \"nx run-many -t build --projects=web\",\n        expectedHmr: \"nx run-many -t dev --projects=web\",\n      },\n    ];\n\n    for (const testCase of cases) {\n      const result = await runTRPCTest({\n        projectName: testCase.projectName,\n        addons: [...testCase.addons],\n        frontend: [\"tanstack-router\"],\n        backend: \"hono\",\n        runtime: \"bun\",\n        database: \"sqlite\",\n        orm: \"drizzle\",\n        auth: \"none\",\n        api: \"trpc\",\n        examples: [\"none\"],\n        dbSetup: \"none\",\n        webDeploy: \"none\",\n        serverDeploy: \"none\",\n        install: false,\n      });\n\n      expect(result.success).toBe(true);\n      expect(result.projectDir).toBeDefined();\n      if (!result.projectDir) continue;\n\n      const desktopPackageJson = await fs.readJson(\n        path.join(result.projectDir, \"apps\", \"desktop\", \"package.json\"),\n      );\n\n      expect(desktopPackageJson.scripts.start).toContain(testCase.expectedRunner);\n      expect(desktopPackageJson.scripts.hmr).toBe(testCase.expectedHmr);\n      expect(desktopPackageJson.scripts[\"build:stable\"]).toContain(testCase.expectedRunner);\n    }\n  });\n});\n"
  },
  {
    "path": "apps/cli/test/examples.test.ts",
    "content": "import { describe, it } from \"bun:test\";\n\nimport { EXAMPLES, expectError, expectSuccess, runTRPCTest, type TestConfig } from \"./test-utils\";\n\ndescribe(\"Example Configurations\", () => {\n  describe(\"Todo Example\", () => {\n    it(\"should work with todo example + database + backend\", async () => {\n      const result = await runTRPCTest({\n        projectName: \"todo-with-db\",\n        examples: [\"todo\"],\n        backend: \"hono\",\n        runtime: \"bun\",\n        database: \"sqlite\",\n        orm: \"drizzle\",\n        auth: \"none\",\n        api: \"trpc\",\n        frontend: [\"tanstack-router\"],\n        addons: [\"none\"],\n        dbSetup: \"none\",\n        webDeploy: \"none\",\n        serverDeploy: \"none\",\n        install: false,\n      });\n\n      expectSuccess(result);\n    });\n\n    it(\"should work with todo example + convex backend\", async () => {\n      const result = await runTRPCTest({\n        projectName: \"todo-convex\",\n        examples: [\"todo\"],\n        backend: \"convex\",\n        runtime: \"none\",\n        database: \"none\",\n        orm: \"none\",\n        auth: \"clerk\",\n        api: \"none\",\n        frontend: [\"tanstack-router\"],\n        addons: [\"none\"],\n        dbSetup: \"none\",\n        webDeploy: \"none\",\n        serverDeploy: \"none\",\n        install: false,\n      });\n\n      expectSuccess(result);\n    });\n\n    it(\"should work with todo example + no backend\", async () => {\n      const result = await runTRPCTest({\n        projectName: \"todo-no-backend\",\n        examples: [\"none\"],\n        backend: \"none\",\n        runtime: \"none\",\n        database: \"none\",\n        orm: \"none\",\n        auth: \"none\",\n        api: \"none\",\n        frontend: [\"tanstack-router\"],\n        addons: [\"none\"],\n        dbSetup: \"none\",\n        webDeploy: \"none\",\n        serverDeploy: \"none\",\n        install: false,\n      });\n\n      expectSuccess(result);\n    });\n\n    it(\"should fail with todo example + backend + no database\", async () => {\n      const result = await runTRPCTest({\n        projectName: \"todo-backend-no-db-fail\",\n        examples: [\"todo\"],\n        backend: \"hono\",\n        runtime: \"bun\",\n        database: \"none\",\n        orm: \"none\",\n        auth: \"none\",\n        api: \"trpc\",\n        frontend: [\"tanstack-router\"],\n        addons: [\"none\"],\n        dbSetup: \"none\",\n        webDeploy: \"none\",\n        serverDeploy: \"none\",\n        expectError: true,\n      });\n\n      expectError(result, \"The 'todo' example requires a database\");\n    });\n  });\n\n  describe(\"AI Example\", () => {\n    it(\"should work with AI example + React frontend\", async () => {\n      const result = await runTRPCTest({\n        projectName: \"ai-react\",\n        examples: [\"ai\"],\n        backend: \"hono\",\n        runtime: \"bun\",\n        database: \"sqlite\",\n        orm: \"drizzle\",\n        auth: \"none\",\n        api: \"trpc\",\n        frontend: [\"tanstack-router\"],\n        addons: [\"none\"],\n        dbSetup: \"none\",\n        webDeploy: \"none\",\n        serverDeploy: \"none\",\n        install: false,\n      });\n\n      expectSuccess(result);\n    });\n\n    it(\"should work with AI example + Next.js\", async () => {\n      const result = await runTRPCTest({\n        projectName: \"ai-next\",\n        examples: [\"ai\"],\n        backend: \"self\",\n        runtime: \"none\",\n        database: \"sqlite\",\n        orm: \"drizzle\",\n        auth: \"better-auth\",\n        api: \"trpc\",\n        frontend: [\"next\"],\n        addons: [\"none\"],\n        dbSetup: \"none\",\n        webDeploy: \"none\",\n        serverDeploy: \"none\",\n        install: false,\n      });\n\n      expectSuccess(result);\n    });\n\n    it(\"should work with AI example + Nuxt\", async () => {\n      const result = await runTRPCTest({\n        projectName: \"ai-nuxt\",\n        examples: [\"ai\"],\n        backend: \"hono\",\n        runtime: \"bun\",\n        database: \"sqlite\",\n        orm: \"drizzle\",\n        auth: \"none\",\n        api: \"orpc\", // tRPC not supported with Nuxt\n        frontend: [\"nuxt\"],\n        addons: [\"none\"],\n        dbSetup: \"none\",\n        webDeploy: \"none\",\n        serverDeploy: \"none\",\n        install: false,\n      });\n\n      expectSuccess(result);\n    });\n\n    it(\"should work with AI example + Svelte\", async () => {\n      const result = await runTRPCTest({\n        projectName: \"ai-svelte\",\n        examples: [\"ai\"],\n        backend: \"hono\",\n        runtime: \"bun\",\n        database: \"sqlite\",\n        orm: \"drizzle\",\n        auth: \"none\",\n        api: \"orpc\", // tRPC not supported with Svelte\n        frontend: [\"svelte\"],\n        addons: [\"none\"],\n        dbSetup: \"none\",\n        webDeploy: \"none\",\n        serverDeploy: \"none\",\n        install: false,\n      });\n\n      expectSuccess(result);\n    });\n\n    it(\"should fail with AI example + Solid frontend\", async () => {\n      const result = await runTRPCTest({\n        projectName: \"ai-solid-fail\",\n        examples: [\"ai\"],\n        backend: \"hono\",\n        runtime: \"bun\",\n        database: \"sqlite\",\n        orm: \"drizzle\",\n        auth: \"none\",\n        api: \"orpc\",\n        frontend: [\"solid\"],\n        addons: [\"none\"],\n        dbSetup: \"none\",\n        webDeploy: \"none\",\n        serverDeploy: \"none\",\n        expectError: true,\n      });\n\n      expectError(result, \"The 'ai' example is not compatible with the Solid frontend\");\n    });\n\n    it(\"should work with AI example + Convex + React frontend\", async () => {\n      const result = await runTRPCTest({\n        projectName: \"ai-convex-react\",\n        examples: [\"ai\"],\n        backend: \"convex\",\n        runtime: \"none\",\n        database: \"none\",\n        orm: \"none\",\n        auth: \"clerk\",\n        api: \"none\",\n        frontend: [\"tanstack-router\"],\n        addons: [\"none\"],\n        dbSetup: \"none\",\n        webDeploy: \"none\",\n        serverDeploy: \"none\",\n        install: false,\n      });\n\n      expectSuccess(result);\n    });\n\n    it(\"should work with AI example + Convex + Next.js\", async () => {\n      const result = await runTRPCTest({\n        projectName: \"ai-convex-next\",\n        examples: [\"ai\"],\n        backend: \"convex\",\n        runtime: \"none\",\n        database: \"none\",\n        orm: \"none\",\n        auth: \"better-auth\",\n        api: \"none\",\n        frontend: [\"next\"],\n        addons: [\"none\"],\n        dbSetup: \"none\",\n        webDeploy: \"none\",\n        serverDeploy: \"none\",\n        install: false,\n      });\n\n      expectSuccess(result);\n    });\n\n    it(\"should fail with AI example + Convex + Svelte\", async () => {\n      const result = await runTRPCTest({\n        projectName: \"ai-convex-svelte-fail\",\n        examples: [\"ai\"],\n        backend: \"convex\",\n        runtime: \"none\",\n        database: \"none\",\n        orm: \"none\",\n        auth: \"none\",\n        api: \"none\",\n        frontend: [\"svelte\"],\n        addons: [\"none\"],\n        dbSetup: \"none\",\n        webDeploy: \"none\",\n        serverDeploy: \"none\",\n        expectError: true,\n      });\n\n      expectError(\n        result,\n        \"The 'ai' example with Convex backend only supports React-based frontends (Next.js, TanStack Router, TanStack Start, React Router). Svelte and Nuxt are not supported with Convex AI.\",\n      );\n    });\n\n    it(\"should fail with AI example + Convex + Nuxt\", async () => {\n      const result = await runTRPCTest({\n        projectName: \"ai-convex-nuxt-fail\",\n        examples: [\"ai\"],\n        backend: \"convex\",\n        runtime: \"none\",\n        database: \"none\",\n        orm: \"none\",\n        auth: \"none\",\n        api: \"none\",\n        frontend: [\"nuxt\"],\n        addons: [\"none\"],\n        dbSetup: \"none\",\n        webDeploy: \"none\",\n        serverDeploy: \"none\",\n        expectError: true,\n      });\n\n      expectError(\n        result,\n        \"The 'ai' example with Convex backend only supports React-based frontends (Next.js, TanStack Router, TanStack Start, React Router). Svelte and Nuxt are not supported with Convex AI.\",\n      );\n    });\n\n    it(\"should fail with Convex + Solid (blocked at backend level)\", async () => {\n      const result = await runTRPCTest({\n        projectName: \"convex-solid-fail\",\n        examples: [\"none\"],\n        backend: \"convex\",\n        runtime: \"none\",\n        database: \"none\",\n        orm: \"none\",\n        auth: \"none\",\n        api: \"none\",\n        frontend: [\"solid\"],\n        addons: [\"none\"],\n        dbSetup: \"none\",\n        webDeploy: \"none\",\n        serverDeploy: \"none\",\n        expectError: true,\n      });\n\n      expectError(\n        result,\n        \"The following frontends are not compatible with '--backend convex': solid\",\n      );\n    });\n  });\n\n  describe(\"Multiple Examples\", () => {\n    it(\"should work with both todo and AI examples\", async () => {\n      const result = await runTRPCTest({\n        projectName: \"todo-ai-combo\",\n        examples: [\"todo\", \"ai\"],\n        backend: \"hono\",\n        runtime: \"bun\",\n        database: \"sqlite\",\n        orm: \"drizzle\",\n        auth: \"none\",\n        api: \"trpc\",\n        frontend: [\"tanstack-router\"],\n        addons: [\"none\"],\n        dbSetup: \"none\",\n        webDeploy: \"none\",\n        serverDeploy: \"none\",\n        install: false,\n      });\n\n      expectSuccess(result);\n    });\n\n    it(\"should fail with both examples if one is incompatible\", async () => {\n      const result = await runTRPCTest({\n        projectName: \"todo-ai-solid-fail\",\n        examples: [\"todo\", \"ai\"],\n        backend: \"hono\",\n        runtime: \"bun\",\n        database: \"sqlite\",\n        orm: \"drizzle\",\n        auth: \"none\",\n        api: \"orpc\",\n        frontend: [\"solid\"],\n        addons: [\"none\"],\n        dbSetup: \"none\",\n        webDeploy: \"none\",\n        serverDeploy: \"none\",\n        expectError: true,\n      });\n\n      expectError(result, \"The 'ai' example is not compatible with the Solid frontend\");\n    });\n  });\n\n  describe(\"Examples with None Option\", () => {\n    it(\"should work with examples none\", async () => {\n      const result = await runTRPCTest({\n        projectName: \"no-examples\",\n        examples: [\"none\"],\n        backend: \"hono\",\n        runtime: \"bun\",\n        database: \"sqlite\",\n        orm: \"drizzle\",\n        auth: \"none\",\n        api: \"trpc\",\n        frontend: [\"tanstack-router\"],\n        addons: [\"none\"],\n        dbSetup: \"none\",\n        webDeploy: \"none\",\n        serverDeploy: \"none\",\n        install: false,\n      });\n\n      expectSuccess(result);\n    });\n\n    it(\"should fail with none + other examples\", async () => {\n      const result = await runTRPCTest({\n        projectName: \"none-with-examples-fail\",\n        examples: [\"none\", \"todo\"],\n        backend: \"hono\",\n        runtime: \"bun\",\n        database: \"sqlite\",\n        orm: \"drizzle\",\n        auth: \"none\",\n        api: \"trpc\",\n        frontend: [\"tanstack-router\"],\n        addons: [\"none\"],\n        dbSetup: \"none\",\n        webDeploy: \"none\",\n        serverDeploy: \"none\",\n        expectError: true,\n      });\n\n      expectError(result, \"Cannot combine 'none' with other examples\");\n    });\n  });\n\n  describe(\"Examples with API None\", () => {\n    it(\"should fail with examples when API is none (non-convex backend)\", async () => {\n      const result = await runTRPCTest({\n        projectName: \"examples-api-none-fail\",\n        examples: [\"todo\"],\n        backend: \"hono\",\n        runtime: \"bun\",\n        database: \"sqlite\",\n        orm: \"drizzle\",\n        auth: \"none\",\n        api: \"none\",\n        frontend: [\"tanstack-router\"],\n        addons: [\"none\"],\n        dbSetup: \"none\",\n        webDeploy: \"none\",\n        serverDeploy: \"none\",\n        expectError: true,\n      });\n\n      expectError(result, \"Cannot use '--examples todo' when '--api' is set to 'none'\");\n    });\n\n    it(\"should work with examples when API is none (convex backend)\", async () => {\n      const result = await runTRPCTest({\n        projectName: \"examples-api-none-convex\",\n        examples: [\"todo\"],\n        backend: \"convex\",\n        runtime: \"none\",\n        database: \"none\",\n        orm: \"none\",\n        auth: \"clerk\",\n        api: \"none\",\n        frontend: [\"tanstack-router\"],\n        addons: [\"none\"],\n        dbSetup: \"none\",\n        webDeploy: \"none\",\n        serverDeploy: \"none\",\n        install: false,\n      });\n\n      expectSuccess(result);\n    });\n  });\n\n  describe(\"All Example Types\", () => {\n    for (const example of EXAMPLES) {\n      if (example === \"none\") continue;\n\n      it(`should work with ${example} example in appropriate setup`, async () => {\n        const config: TestConfig = {\n          projectName: `test-${example}`,\n          examples: [example],\n          backend: \"hono\",\n          runtime: \"bun\",\n          database: \"sqlite\",\n          orm: \"drizzle\",\n          auth: \"none\",\n          api: \"trpc\",\n          frontend: [\"tanstack-router\"],\n          addons: [\"none\"],\n          dbSetup: \"none\",\n          webDeploy: \"none\",\n          serverDeploy: \"none\",\n          install: false,\n        };\n\n        const result = await runTRPCTest(config);\n        expectSuccess(result);\n      });\n    }\n  });\n\n  describe(\"Example Edge Cases\", () => {\n    it(\"should work with empty examples array\", async () => {\n      const result = await runTRPCTest({\n        projectName: \"empty-examples\",\n        examples: [\"none\"],\n        backend: \"hono\",\n        runtime: \"bun\",\n        database: \"sqlite\",\n        orm: \"drizzle\",\n        auth: \"none\",\n        api: \"trpc\",\n        frontend: [\"tanstack-router\"],\n        addons: [\"none\"],\n        dbSetup: \"none\",\n        webDeploy: \"none\",\n        serverDeploy: \"none\",\n        install: false,\n      });\n\n      expectSuccess(result);\n    });\n\n    it(\"should handle complex example constraints\", async () => {\n      // Todo example with backend but no database should fail\n      const result = await runTRPCTest({\n        projectName: \"complex-example-constraints\",\n        examples: [\"todo\"],\n        backend: \"express\", // Non-convex backend\n        runtime: \"bun\",\n        database: \"none\", // No database\n        orm: \"none\",\n        auth: \"none\",\n        api: \"trpc\",\n        frontend: [\"tanstack-router\"],\n        addons: [\"none\"],\n        dbSetup: \"none\",\n        webDeploy: \"none\",\n        serverDeploy: \"none\",\n        expectError: true,\n      });\n\n      expectError(result, \"The 'todo' example requires a database\");\n    });\n  });\n});\n"
  },
  {
    "path": "apps/cli/test/external-commands.test.ts",
    "content": "import { describe, expect, it } from \"bun:test\";\nimport { mkdir, writeFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\n\nimport { setupOxlint } from \"../src/helpers/addons/oxlint-setup\";\nimport { installDependencies } from \"../src/helpers/core/install-dependencies\";\nimport { getPackageExecutionArgs } from \"../src/utils/package-runner\";\nimport { SMOKE_DIR } from \"./setup\";\n\ndescribe(\"External Command Guards\", () => {\n  it(\"should split quoted args correctly\", () => {\n    const args = getPackageExecutionArgs(\n      \"bun\",\n      `get-db@latest --yes --ref \"sbA3tIe\" --name test-db`,\n    );\n\n    expect(args).toEqual([\n      \"bunx\",\n      \"get-db@latest\",\n      \"--yes\",\n      \"--ref\",\n      \"sbA3tIe\",\n      \"--name\",\n      \"test-db\",\n    ]);\n  });\n\n  it(\"should skip dependency installation when test mode is enabled\", async () => {\n    const result = await installDependencies({\n      projectDir: SMOKE_DIR,\n      packageManager: \"bun\",\n    });\n\n    expect(result.isOk()).toBe(true);\n  });\n\n  it(\"should update package.json without running oxlint init in test mode\", async () => {\n    const projectDir = join(SMOKE_DIR, \"oxlint-skip\");\n    await mkdir(projectDir, { recursive: true });\n\n    const pkgJsonPath = join(projectDir, \"package.json\");\n    await writeFile(\n      pkgJsonPath,\n      JSON.stringify(\n        {\n          name: \"oxlint-skip\",\n          version: \"0.0.0\",\n          scripts: {},\n          devDependencies: {},\n        },\n        null,\n        2,\n      ),\n    );\n\n    const result = await setupOxlint(projectDir, \"bun\");\n    expect(result.isOk()).toBe(true);\n\n    const updated = await Bun.file(pkgJsonPath).json();\n\n    expect(updated.scripts?.check).toBe(\"oxlint && oxfmt --write\");\n    expect(updated.devDependencies?.oxlint).toBeDefined();\n    expect(updated.devDependencies?.oxfmt).toBeDefined();\n  });\n});\n"
  },
  {
    "path": "apps/cli/test/frontend.test.ts",
    "content": "import { describe, it } from \"bun:test\";\n\nimport { expectError, expectSuccess, runTRPCTest, type TestConfig } from \"./test-utils\";\n\ndescribe(\"Frontend Configurations\", () => {\n  describe(\"Single Frontend Options\", () => {\n    const singleFrontends = [\n      \"tanstack-router\",\n      \"react-router\",\n      \"tanstack-start\",\n      \"next\",\n      \"nuxt\",\n      \"native-bare\",\n      \"native-uniwind\",\n      \"native-unistyles\",\n      \"svelte\",\n      \"solid\",\n      \"astro\",\n    ] satisfies ReadonlyArray<\n      | \"tanstack-router\"\n      | \"react-router\"\n      | \"tanstack-start\"\n      | \"next\"\n      | \"nuxt\"\n      | \"native-bare\"\n      | \"native-uniwind\"\n      | \"native-unistyles\"\n      | \"svelte\"\n      | \"solid\"\n      | \"astro\"\n    >;\n\n    for (const frontend of singleFrontends) {\n      it(`should work with ${frontend}`, async () => {\n        const config: TestConfig = {\n          projectName: `${frontend}-app`,\n          frontend: [frontend],\n          install: false,\n        };\n\n        // Set compatible defaults based on frontend\n        if (frontend === \"solid\") {\n          // Solid is not compatible with Convex backend\n          config.backend = \"hono\";\n          config.runtime = \"bun\";\n          config.database = \"sqlite\";\n          config.orm = \"drizzle\";\n          config.auth = \"none\";\n          config.api = \"orpc\"; // tRPC not supported with solid\n          config.addons = [\"none\"];\n          config.examples = [\"none\"];\n          config.dbSetup = \"none\";\n          config.webDeploy = \"none\";\n          config.serverDeploy = \"none\";\n        } else if (frontend === \"next\") {\n          // Next.js can use self backend (fullstack)\n          config.backend = \"self\";\n          config.runtime = \"none\";\n          config.database = \"sqlite\";\n          config.orm = \"drizzle\";\n          config.auth = \"better-auth\";\n          config.api = \"trpc\";\n          config.addons = [\"none\"];\n          config.examples = [\"none\"];\n          config.dbSetup = \"none\";\n          config.webDeploy = \"none\";\n          config.serverDeploy = \"none\";\n        } else if ([\"nuxt\", \"svelte\"].includes(frontend)) {\n          config.backend = \"hono\";\n          config.runtime = \"bun\";\n          config.database = \"sqlite\";\n          config.orm = \"drizzle\";\n          config.auth = \"none\";\n          config.api = \"orpc\"; // tRPC not supported with nuxt/svelte\n          config.addons = [\"none\"];\n          config.examples = [\"none\"];\n          config.dbSetup = \"none\";\n          config.webDeploy = \"none\";\n          config.serverDeploy = \"none\";\n        } else if (frontend === \"astro\") {\n          // Astro uses oRPC, not Convex compatible\n          config.backend = \"hono\";\n          config.runtime = \"bun\";\n          config.database = \"sqlite\";\n          config.orm = \"drizzle\";\n          config.auth = \"none\";\n          config.api = \"orpc\"; // tRPC not supported with astro\n          config.addons = [\"none\"];\n          config.examples = [\"none\"];\n          config.dbSetup = \"none\";\n          config.webDeploy = \"none\";\n          config.serverDeploy = \"none\";\n        } else {\n          config.backend = \"hono\";\n          config.runtime = \"bun\";\n          config.database = \"sqlite\";\n          config.orm = \"drizzle\";\n          config.auth = \"none\";\n          config.api = \"trpc\";\n          config.addons = [\"none\"];\n          config.examples = [\"none\"];\n          config.dbSetup = \"none\";\n          config.webDeploy = \"none\";\n          config.serverDeploy = \"none\";\n        }\n\n        const result = await runTRPCTest(config);\n        expectSuccess(result);\n      });\n    }\n  });\n\n  describe(\"Frontend Compatibility with API\", () => {\n    it(\"should work with React frontends + tRPC\", async () => {\n      const result = await runTRPCTest({\n        projectName: \"react-trpc\",\n        frontend: [\"tanstack-router\"],\n        api: \"trpc\",\n        backend: \"hono\",\n        runtime: \"bun\",\n        database: \"sqlite\",\n        orm: \"drizzle\",\n        auth: \"none\",\n        addons: [\"none\"],\n        examples: [\"none\"],\n        dbSetup: \"none\",\n        webDeploy: \"none\",\n        serverDeploy: \"none\",\n        install: false,\n      });\n\n      expectSuccess(result);\n    });\n\n    it(\"should fail with Nuxt + tRPC\", async () => {\n      const result = await runTRPCTest({\n        projectName: \"nuxt-trpc-fail\",\n        frontend: [\"nuxt\"],\n        api: \"trpc\",\n        backend: \"hono\",\n        runtime: \"bun\",\n        database: \"sqlite\",\n        orm: \"drizzle\",\n        auth: \"none\",\n        addons: [\"none\"],\n        examples: [\"none\"],\n        dbSetup: \"none\",\n        webDeploy: \"none\",\n        serverDeploy: \"none\",\n        expectError: true,\n      });\n\n      expectError(result, \"tRPC API is not supported with 'nuxt' frontend\");\n    });\n\n    it(\"should fail with Svelte + tRPC\", async () => {\n      const result = await runTRPCTest({\n        projectName: \"svelte-trpc-fail\",\n        frontend: [\"svelte\"],\n        api: \"trpc\",\n        backend: \"hono\",\n        runtime: \"bun\",\n        database: \"sqlite\",\n        orm: \"drizzle\",\n        auth: \"none\",\n        addons: [\"none\"],\n        examples: [\"none\"],\n        dbSetup: \"none\",\n        webDeploy: \"none\",\n        serverDeploy: \"none\",\n        expectError: true,\n      });\n\n      expectError(result, \"tRPC API is not supported with 'svelte' frontend\");\n    });\n\n    it(\"should fail with Solid + tRPC\", async () => {\n      const result = await runTRPCTest({\n        projectName: \"solid-trpc-fail\",\n        frontend: [\"solid\"],\n        api: \"trpc\",\n        backend: \"hono\",\n        runtime: \"bun\",\n        database: \"sqlite\",\n        orm: \"drizzle\",\n        auth: \"none\",\n        addons: [\"none\"],\n        examples: [\"none\"],\n        dbSetup: \"none\",\n        webDeploy: \"none\",\n        serverDeploy: \"none\",\n        expectError: true,\n      });\n\n      expectError(result, \"tRPC API is not supported with 'solid' frontend\");\n    });\n\n    it(\"should fail with Astro + tRPC\", async () => {\n      const result = await runTRPCTest({\n        projectName: \"astro-trpc-fail\",\n        frontend: [\"astro\"],\n        api: \"trpc\",\n        backend: \"hono\",\n        runtime: \"bun\",\n        database: \"sqlite\",\n        orm: \"drizzle\",\n        auth: \"none\",\n        addons: [\"none\"],\n        examples: [\"none\"],\n        dbSetup: \"none\",\n        webDeploy: \"none\",\n        serverDeploy: \"none\",\n        expectError: true,\n      });\n\n      expectError(result, \"tRPC API is not supported with 'astro' frontend\");\n    });\n\n    const frontends = [\"nuxt\", \"svelte\", \"solid\", \"astro\"] as const;\n    for (const frontend of frontends) {\n      it(`should work with ${frontend} + oRPC`, async () => {\n        const result = await runTRPCTest({\n          projectName: `${frontend}-orpc`,\n          frontend: [frontend],\n          api: \"orpc\",\n          backend: \"hono\",\n          runtime: \"bun\",\n          database: \"sqlite\",\n          orm: \"drizzle\",\n          auth: \"none\",\n          addons: [\"none\"],\n          examples: [\"none\"],\n          dbSetup: \"none\",\n          webDeploy: \"none\",\n          serverDeploy: \"none\",\n          install: false,\n        });\n\n        expectSuccess(result);\n      });\n    }\n  });\n\n  describe(\"Frontend Compatibility with Backend\", () => {\n    it(\"should fail Solid + Convex\", async () => {\n      const result = await runTRPCTest({\n        projectName: \"solid-convex-fail\",\n        frontend: [\"solid\"],\n        backend: \"convex\",\n        runtime: \"none\",\n        database: \"none\",\n        orm: \"none\",\n        auth: \"none\",\n        api: \"none\",\n        addons: [\"none\"],\n        examples: [\"none\"],\n        dbSetup: \"none\",\n        webDeploy: \"none\",\n        serverDeploy: \"none\",\n        expectError: true,\n      });\n\n      expectError(\n        result,\n        \"The following frontends are not compatible with '--backend convex': solid. Please choose a different frontend or backend.\",\n      );\n    });\n\n    it(\"should fail Astro + Convex\", async () => {\n      const result = await runTRPCTest({\n        projectName: \"astro-convex-fail\",\n        frontend: [\"astro\"],\n        backend: \"convex\",\n        runtime: \"none\",\n        database: \"none\",\n        orm: \"none\",\n        auth: \"none\",\n        api: \"none\",\n        addons: [\"none\"],\n        examples: [\"none\"],\n        dbSetup: \"none\",\n        webDeploy: \"none\",\n        serverDeploy: \"none\",\n        expectError: true,\n      });\n\n      expectError(\n        result,\n        \"The following frontends are not compatible with '--backend convex': astro. Please choose a different frontend or backend.\",\n      );\n    });\n\n    it(\"should work with React frontends + Convex\", async () => {\n      const result = await runTRPCTest({\n        projectName: \"react-convex\",\n        frontend: [\"tanstack-router\"],\n        backend: \"convex\",\n        runtime: \"none\",\n        database: \"none\",\n        orm: \"none\",\n        auth: \"clerk\",\n        api: \"none\",\n        addons: [\"none\"],\n        examples: [\"none\"],\n        dbSetup: \"none\",\n        webDeploy: \"none\",\n        serverDeploy: \"none\",\n        install: false,\n      });\n\n      expectSuccess(result);\n    });\n  });\n\n  describe(\"Frontend Compatibility with Auth\", () => {\n    const incompatibleFrontends = [\"nuxt\", \"svelte\", \"solid\", \"astro\"] as const;\n    for (const frontend of incompatibleFrontends) {\n      it(`should fail incompatible ${frontend} with Clerk`, async () => {\n        const result = await runTRPCTest({\n          projectName: `${frontend}-clerk-fail`,\n          frontend: [frontend],\n          backend: \"hono\",\n          runtime: \"bun\",\n          database: \"sqlite\",\n          orm: \"drizzle\",\n          auth: \"clerk\",\n          api: \"orpc\",\n          addons: [\"none\"],\n          examples: [\"none\"],\n          dbSetup: \"none\",\n          webDeploy: \"none\",\n          serverDeploy: \"none\",\n          expectError: true,\n        });\n\n        expectError(result, \"Clerk authentication is not compatible\");\n      });\n    }\n\n    const compatibleFrontends = [\n      \"tanstack-router\",\n      \"react-router\",\n      \"tanstack-start\",\n      \"next\",\n    ] as const;\n    for (const frontend of compatibleFrontends) {\n      it(`should work with compatible ${frontend} + Clerk`, async () => {\n        const result = await runTRPCTest({\n          projectName: `${frontend}-clerk`,\n          frontend: [frontend],\n          backend: \"hono\",\n          runtime: \"bun\",\n          database: \"sqlite\",\n          orm: \"drizzle\",\n          auth: \"clerk\",\n          api: \"trpc\",\n          addons: [\"none\"],\n          examples: [\"none\"],\n          dbSetup: \"none\",\n          webDeploy: \"none\",\n          serverDeploy: \"none\",\n          install: false,\n        });\n\n        expectSuccess(result);\n      });\n    }\n  });\n\n  describe(\"Multiple Frontend Constraints\", () => {\n    it(\"should fail with multiple web frontends\", async () => {\n      const result = await runTRPCTest({\n        projectName: \"multiple-web-fail\",\n        frontend: [\"tanstack-router\", \"react-router\"],\n        backend: \"hono\",\n        runtime: \"bun\",\n        database: \"sqlite\",\n        orm: \"drizzle\",\n        auth: \"none\",\n        api: \"trpc\",\n        addons: [\"none\"],\n        examples: [\"none\"],\n        dbSetup: \"none\",\n        webDeploy: \"none\",\n        serverDeploy: \"none\",\n        expectError: true,\n      });\n\n      expectError(result, \"Cannot select multiple web frameworks\");\n    });\n\n    it(\"should fail with multiple native frontends\", async () => {\n      const result = await runTRPCTest({\n        projectName: \"multiple-native-fail\",\n        frontend: [\"native-bare\", \"native-unistyles\"],\n        backend: \"hono\",\n        runtime: \"bun\",\n        database: \"sqlite\",\n        orm: \"drizzle\",\n        auth: \"none\",\n        api: \"trpc\",\n        addons: [\"none\"],\n        examples: [\"none\"],\n        dbSetup: \"none\",\n        webDeploy: \"none\",\n        serverDeploy: \"none\",\n        expectError: true,\n      });\n\n      expectError(result, \"Cannot select multiple native frameworks\");\n    });\n\n    it(\"should work with one web + one native frontend\", async () => {\n      const result = await runTRPCTest({\n        projectName: \"web-native-combo\",\n        frontend: [\"tanstack-router\", \"native-bare\"],\n        backend: \"hono\",\n        runtime: \"bun\",\n        database: \"sqlite\",\n        orm: \"drizzle\",\n        auth: \"none\",\n        api: \"trpc\",\n        addons: [\"none\"],\n        examples: [\"none\"],\n        dbSetup: \"none\",\n        webDeploy: \"none\",\n        serverDeploy: \"none\",\n        install: false,\n      });\n\n      expectSuccess(result);\n    });\n  });\n\n  describe(\"Frontend with None Option\", () => {\n    it(\"should work with frontend none\", async () => {\n      const result = await runTRPCTest({\n        projectName: \"no-frontend\",\n        frontend: [\"none\"],\n        backend: \"hono\",\n        runtime: \"bun\",\n        database: \"sqlite\",\n        orm: \"drizzle\",\n        auth: \"none\",\n        api: \"trpc\",\n        addons: [\"none\"],\n        examples: [\"none\"],\n        dbSetup: \"none\",\n        webDeploy: \"none\",\n        serverDeploy: \"none\",\n        install: false,\n      });\n\n      expectSuccess(result);\n    });\n\n    it(\"should fail with none + other frontends\", async () => {\n      const result = await runTRPCTest({\n        projectName: \"none-with-other-fail\",\n        frontend: [\"none\", \"tanstack-router\"],\n        backend: \"hono\",\n        runtime: \"bun\",\n        database: \"sqlite\",\n        orm: \"drizzle\",\n        auth: \"none\",\n        api: \"trpc\",\n        addons: [\"none\"],\n        examples: [\"none\"],\n        dbSetup: \"none\",\n        webDeploy: \"none\",\n        serverDeploy: \"none\",\n        expectError: true,\n      });\n\n      expectError(result, \"Cannot combine 'none' with other frontend options\");\n    });\n  });\n\n  describe(\"Next.js with Self Backend\", () => {\n    it(\"should work with Next.js and self backend\", async () => {\n      const result = await runTRPCTest({\n        projectName: \"nextjs-self-backend\",\n        frontend: [\"next\"],\n        backend: \"self\",\n        runtime: \"none\",\n        database: \"sqlite\",\n        orm: \"drizzle\",\n        auth: \"better-auth\",\n        api: \"trpc\",\n        addons: [\"none\"],\n        examples: [\"none\"],\n        dbSetup: \"none\",\n        webDeploy: \"none\",\n        serverDeploy: \"none\",\n        install: false,\n      });\n\n      expectSuccess(result);\n    });\n\n    it(\"should work with Next.js and traditional backend\", async () => {\n      const result = await runTRPCTest({\n        projectName: \"nextjs-traditional-backend\",\n        frontend: [\"next\"],\n        backend: \"hono\",\n        runtime: \"bun\",\n        database: \"sqlite\",\n        orm: \"drizzle\",\n        auth: \"none\",\n        api: \"trpc\",\n        addons: [\"none\"],\n        examples: [\"none\"],\n        dbSetup: \"none\",\n        webDeploy: \"none\",\n        serverDeploy: \"none\",\n        install: false,\n      });\n\n      expectSuccess(result);\n    });\n  });\n\n  describe(\"Nuxt with Self Backend\", () => {\n    it(\"should work with Nuxt and self backend\", async () => {\n      const result = await runTRPCTest({\n        projectName: \"nuxt-self-backend\",\n        frontend: [\"nuxt\"],\n        backend: \"self\",\n        runtime: \"none\",\n        database: \"sqlite\",\n        orm: \"drizzle\",\n        auth: \"better-auth\",\n        api: \"orpc\",\n        addons: [\"none\"],\n        examples: [\"none\"],\n        dbSetup: \"none\",\n        webDeploy: \"none\",\n        serverDeploy: \"none\",\n        install: false,\n      });\n\n      expectSuccess(result);\n    });\n\n    it(\"should work with Nuxt and traditional backend\", async () => {\n      const result = await runTRPCTest({\n        projectName: \"nuxt-traditional-backend\",\n        frontend: [\"nuxt\"],\n        backend: \"hono\",\n        runtime: \"bun\",\n        database: \"sqlite\",\n        orm: \"drizzle\",\n        auth: \"none\",\n        api: \"orpc\",\n        addons: [\"none\"],\n        examples: [\"none\"],\n        dbSetup: \"none\",\n        webDeploy: \"none\",\n        serverDeploy: \"none\",\n        install: false,\n      });\n\n      expectSuccess(result);\n    });\n  });\n\n  describe(\"Astro with Self Backend\", () => {\n    it(\"should work with Astro and self backend\", async () => {\n      const result = await runTRPCTest({\n        projectName: \"astro-self-backend\",\n        frontend: [\"astro\"],\n        backend: \"self\",\n        runtime: \"none\",\n        database: \"sqlite\",\n        orm: \"drizzle\",\n        auth: \"better-auth\",\n        api: \"orpc\",\n        addons: [\"none\"],\n        examples: [\"none\"],\n        dbSetup: \"none\",\n        webDeploy: \"none\",\n        serverDeploy: \"none\",\n        install: false,\n      });\n\n      expectSuccess(result);\n    });\n\n    it(\"should work with Astro and traditional backend\", async () => {\n      const result = await runTRPCTest({\n        projectName: \"astro-traditional-backend\",\n        frontend: [\"astro\"],\n        backend: \"hono\",\n        runtime: \"bun\",\n        database: \"sqlite\",\n        orm: \"drizzle\",\n        auth: \"none\",\n        api: \"orpc\",\n        addons: [\"none\"],\n        examples: [\"none\"],\n        dbSetup: \"none\",\n        webDeploy: \"none\",\n        serverDeploy: \"none\",\n        install: false,\n      });\n\n      expectSuccess(result);\n    });\n  });\n\n  describe(\"Web Deploy Constraints\", () => {\n    it(\"should work with web frontend + web deploy\", async () => {\n      const result = await runTRPCTest({\n        projectName: \"web-deploy\",\n        frontend: [\"tanstack-router\"],\n        webDeploy: \"cloudflare\",\n        backend: \"hono\",\n        runtime: \"bun\",\n        database: \"sqlite\",\n        orm: \"drizzle\",\n        auth: \"none\",\n        api: \"trpc\",\n        addons: [\"none\"],\n        examples: [\"none\"],\n        dbSetup: \"none\",\n        serverDeploy: \"none\",\n        install: false,\n      });\n\n      expectSuccess(result);\n    });\n\n    it(\"should fail with web deploy but no web frontend\", async () => {\n      const result = await runTRPCTest({\n        projectName: \"web-deploy-no-frontend-fail\",\n        frontend: [\"native-bare\"],\n        webDeploy: \"cloudflare\",\n        backend: \"hono\",\n        runtime: \"bun\",\n        database: \"sqlite\",\n        orm: \"drizzle\",\n        auth: \"none\",\n        api: \"trpc\",\n        addons: [\"none\"],\n        examples: [\"none\"],\n        dbSetup: \"none\",\n        serverDeploy: \"none\",\n        expectError: true,\n      });\n\n      expectError(result, \"'--web-deploy' requires a web frontend\");\n    });\n  });\n});\n"
  },
  {
    "path": "apps/cli/test/index.test.ts",
    "content": "import { afterAll, beforeAll, describe, expect, it } from \"bun:test\";\n\nimport { expectSuccess, runTRPCTest } from \"./test-utils\";\n\ndescribe(\"CLI Test Suite\", () => {\n  beforeAll(async () => {\n    // Ensure CLI is built before running tests\n    console.log(\"Setting up CLI tests...\");\n  });\n\n  afterAll(async () => {\n    console.log(\"CLI tests completed.\");\n  });\n\n  describe(\"Smoke Tests\", () => {\n    it(\"should create a basic project successfully\", async () => {\n      const result = await runTRPCTest({\n        projectName: \"smoke-test-basic\",\n        yes: true,\n        install: false,\n      });\n\n      expectSuccess(result);\n    });\n\n    it(\"should handle help command\", async () => {\n      // This test would need to be implemented differently since it's not a project creation\n      // For now, we'll just test that the basic functionality works\n      expect(true).toBe(true);\n    });\n\n    it(\"should validate project name requirements\", async () => {\n      const result = await runTRPCTest({\n        projectName: \"valid-project-name\",\n        yes: true,\n        install: false,\n      });\n\n      expectSuccess(result);\n    });\n  });\n\n  describe(\"Performance Tests\", () => {\n    it(\"should complete project creation within reasonable time\", async () => {\n      const startTime = Date.now();\n\n      const result = await runTRPCTest({\n        projectName: \"performance-test\",\n        yes: true,\n        install: false,\n      });\n\n      const endTime = Date.now();\n      const duration = endTime - startTime;\n\n      expectSuccess(result);\n\n      // Should complete within 30 seconds (without installation)\n      expect(duration).toBeLessThan(30000);\n    });\n  });\n\n  describe(\"Stability Tests\", () => {\n    it(\"should handle multiple rapid project creations\", async () => {\n      const promises = [];\n\n      for (let i = 0; i < 3; i++) {\n        promises.push(\n          runTRPCTest({\n            projectName: `stability-test-${i}`,\n            yes: true,\n            install: false,\n          }),\n        );\n      }\n\n      const results = await Promise.all(promises);\n\n      for (const result of results) {\n        expectSuccess(result);\n      }\n    }, 60000);\n  });\n});\n"
  },
  {
    "path": "apps/cli/test/input-schemas.test.ts",
    "content": "import { describe, expect, it } from \"bun:test\";\n\nimport {\n  AddInputSchema,\n  BetterTStackConfigFileSchema,\n  CLIInputSchema,\n  CreateInputSchema,\n} from \"../../../packages/types/src/schemas\";\n\ndescribe(\"Input schemas\", () => {\n  it(\"rejects conflicting manualDb and dbSetupOptions.mode inputs\", () => {\n    const result = CreateInputSchema.safeParse({\n      projectName: \"app\",\n      manualDb: true,\n      dbSetupOptions: { mode: \"manual\" },\n    });\n\n    expect(result.success).toBe(false);\n  });\n\n  it(\"rejects conflicting nx and turborepo addon combinations\", () => {\n    const result = AddInputSchema.safeParse({\n      addons: [\"nx\", \"turborepo\"],\n    });\n\n    expect(result.success).toBe(false);\n  });\n\n  it(\"rejects unknown keys in JSON-first create input\", () => {\n    const result = CreateInputSchema.safeParse({\n      projectName: \"app\",\n      pakageManager: \"bun\",\n    });\n\n    expect(result.success).toBe(false);\n  });\n\n  it(\"rejects unknown keys in bts.jsonc config payloads\", () => {\n    const result = BetterTStackConfigFileSchema.safeParse({\n      version: \"0.0.0\",\n      createdAt: new Date(0).toISOString(),\n      projectName: \"app\",\n      database: \"sqlite\",\n      orm: \"drizzle\",\n      backend: \"hono\",\n      runtime: \"bun\",\n      frontend: [\"tanstack-router\"],\n      addons: [\"none\"],\n      examples: [\"none\"],\n      auth: \"none\",\n      payments: \"none\",\n      packageManager: \"bun\",\n      dbSetup: \"none\",\n      api: \"trpc\",\n      webDeploy: \"none\",\n      serverDeploy: \"none\",\n      unexpected: true,\n    });\n\n    expect(result.success).toBe(false);\n  });\n\n  it(\"rejects unknown nested addon option keys\", () => {\n    const result = CreateInputSchema.safeParse({\n      projectName: \"app\",\n      addonOptions: {\n        skills: {\n          agent: [\"cursor\"],\n        },\n      },\n    });\n\n    expect(result.success).toBe(false);\n  });\n\n  it(\"rejects unknown nested db setup option keys\", () => {\n    const result = CreateInputSchema.safeParse({\n      projectName: \"app\",\n      dbSetupOptions: {\n        neon: {\n          region: \"aws-us-east-1\",\n        },\n      },\n    });\n\n    expect(result.success).toBe(false);\n  });\n\n  it(\"accepts the evlog agent skills source in addon options\", () => {\n    const result = CreateInputSchema.safeParse({\n      projectName: \"app\",\n      addonOptions: {\n        skills: {\n          selections: [\n            {\n              source: \"https://www.evlog.dev\",\n              skills: [\"review-logging-patterns\", \"analyze-logs\"],\n            },\n          ],\n        },\n      },\n    });\n\n    expect(result.success).toBe(true);\n  });\n\n  it(\"allows CLI input parsing on top of the refined create schema\", () => {\n    const result = CLIInputSchema.safeParse({\n      projectDirectory: \".\",\n      projectName: \"app\",\n      addons: [\"biome\"],\n    });\n\n    expect(result.success).toBe(true);\n  });\n\n  it(\"imports the MCP module without schema-construction crashes\", async () => {\n    const module = await import(\"../src/mcp\");\n\n    expect(typeof module.createBtsMcpServer).toBe(\"function\");\n  });\n});\n"
  },
  {
    "path": "apps/cli/test/integration.test.ts",
    "content": "import { describe, it } from \"bun:test\";\n\nimport type { Backend, Runtime } from \"../src/types\";\nimport { expectError, expectSuccess, runTRPCTest, type TestConfig } from \"./test-utils\";\n\ndescribe(\"Integration Tests - Real World Scenarios\", () => {\n  describe(\"Complete Stack Configurations\", () => {\n    it(\"should create full-stack React app with tRPC\", async () => {\n      const result = await runTRPCTest({\n        projectName: \"fullstack-react-trpc\",\n        backend: \"hono\",\n        runtime: \"workers\",\n        database: \"postgres\",\n        orm: \"drizzle\",\n        auth: \"better-auth\",\n        api: \"trpc\",\n        frontend: [\"tanstack-router\"],\n        addons: [\"biome\", \"turborepo\"],\n        examples: [\"todo\", \"ai\"],\n        dbSetup: \"none\",\n        webDeploy: \"cloudflare\",\n        serverDeploy: \"cloudflare\",\n        install: false,\n      });\n\n      expectSuccess(result);\n    });\n\n    it(\"should create Nuxt app with oRPC\", async () => {\n      const result = await runTRPCTest({\n        projectName: \"nuxt-orpc-app\",\n        backend: \"hono\",\n        runtime: \"workers\",\n        database: \"sqlite\",\n        orm: \"drizzle\",\n        auth: \"better-auth\",\n        api: \"orpc\",\n        frontend: [\"nuxt\"],\n        addons: [\"biome\", \"husky\"],\n        examples: [\"ai\"], // AI works with Nuxt\n        dbSetup: \"none\",\n        webDeploy: \"cloudflare\",\n        serverDeploy: \"cloudflare\",\n        install: false,\n      });\n\n      expectSuccess(result);\n    });\n\n    it(\"should create Next.js fullstack app with self backend\", async () => {\n      const result = await runTRPCTest({\n        projectName: \"nextjs-fullstack-app\",\n        backend: \"self\",\n        runtime: \"none\",\n        database: \"postgres\",\n        orm: \"drizzle\",\n        auth: \"better-auth\",\n        api: \"trpc\",\n        frontend: [\"next\"],\n        addons: [\"biome\", \"turborepo\"],\n        examples: [\"todo\", \"ai\"],\n        dbSetup: \"none\",\n        webDeploy: \"cloudflare\",\n        serverDeploy: \"none\", // No server deployment for self backend\n        install: false,\n      });\n\n      expectSuccess(result);\n    });\n\n    it(\"should create Svelte app with oRPC\", async () => {\n      const result = await runTRPCTest({\n        projectName: \"svelte-orpc-app\",\n        backend: \"hono\",\n        runtime: \"bun\",\n        database: \"mysql\",\n        orm: \"prisma\",\n        auth: \"better-auth\",\n        api: \"orpc\",\n        frontend: [\"svelte\"],\n        addons: [\"turborepo\", \"oxlint\"],\n        examples: [\"todo\"], // Todo works with Svelte\n        dbSetup: \"none\",\n        webDeploy: \"none\",\n        serverDeploy: \"none\",\n        install: false,\n      });\n\n      expectSuccess(result);\n    });\n\n    it(\"should create Convex app with Clerk auth\", async () => {\n      const result = await runTRPCTest({\n        projectName: \"convex-clerk-app\",\n        backend: \"convex\",\n        runtime: \"none\",\n        database: \"none\",\n        orm: \"none\",\n        auth: \"clerk\",\n        api: \"none\",\n        frontend: [\"tanstack-router\"],\n        addons: [\"biome\", \"turborepo\"],\n        examples: [\"todo\"],\n        dbSetup: \"none\",\n        webDeploy: \"cloudflare\",\n        serverDeploy: \"none\",\n        install: false,\n      });\n\n      expectSuccess(result);\n    });\n\n    it(\"should create Hono app with Clerk auth\", async () => {\n      const result = await runTRPCTest({\n        projectName: \"hono-clerk-app\",\n        backend: \"hono\",\n        runtime: \"bun\",\n        database: \"sqlite\",\n        orm: \"drizzle\",\n        auth: \"clerk\",\n        api: \"trpc\",\n        frontend: [\"react-router\"],\n        addons: [\"biome\", \"turborepo\"],\n        examples: [\"todo\"],\n        dbSetup: \"none\",\n        webDeploy: \"none\",\n        serverDeploy: \"none\",\n        install: false,\n      });\n\n      expectSuccess(result);\n    });\n\n    it(\"should create Convex app with AI example + React frontend\", async () => {\n      const result = await runTRPCTest({\n        projectName: \"convex-ai-react-app\",\n        backend: \"convex\",\n        runtime: \"none\",\n        database: \"none\",\n        orm: \"none\",\n        auth: \"better-auth\",\n        api: \"none\",\n        frontend: [\"tanstack-router\"],\n        addons: [\"biome\"],\n        examples: [\"ai\"],\n        dbSetup: \"none\",\n        webDeploy: \"cloudflare\",\n        serverDeploy: \"none\",\n        install: false,\n      });\n\n      expectSuccess(result);\n    });\n\n    it(\"should create Convex app with AI example + Next.js\", async () => {\n      const result = await runTRPCTest({\n        projectName: \"convex-ai-next-app\",\n        backend: \"convex\",\n        runtime: \"none\",\n        database: \"none\",\n        orm: \"none\",\n        auth: \"better-auth\",\n        api: \"none\",\n        frontend: [\"next\"],\n        addons: [\"biome\"],\n        examples: [\"ai\"],\n        dbSetup: \"none\",\n        webDeploy: \"cloudflare\",\n        serverDeploy: \"none\",\n        install: false,\n      });\n\n      expectSuccess(result);\n    });\n\n    it(\"should create mobile app with React Native\", async () => {\n      const result = await runTRPCTest({\n        projectName: \"mobile-app\",\n        backend: \"hono\",\n        runtime: \"bun\",\n        database: \"postgres\",\n        orm: \"drizzle\",\n        auth: \"better-auth\",\n        api: \"trpc\",\n        frontend: [\"native-bare\"],\n        addons: [\"biome\", \"turborepo\"],\n        examples: [\"todo\"],\n        dbSetup: \"none\",\n        webDeploy: \"none\",\n        serverDeploy: \"none\",\n        install: false,\n      });\n\n      expectSuccess(result);\n    });\n\n    it(\"should create hybrid web + mobile app\", async () => {\n      const result = await runTRPCTest({\n        projectName: \"hybrid-web-mobile\",\n        backend: \"hono\",\n        runtime: \"bun\",\n        database: \"sqlite\",\n        orm: \"drizzle\",\n        auth: \"better-auth\",\n        api: \"trpc\",\n        frontend: [\"tanstack-router\", \"native-unistyles\"],\n        addons: [\"biome\", \"turborepo\"],\n        examples: [\"todo\"],\n        dbSetup: \"none\",\n        webDeploy: \"none\",\n        serverDeploy: \"none\",\n        install: false,\n      });\n\n      expectSuccess(result);\n    });\n\n    it(\"should create Cloudflare Workers app\", async () => {\n      const result = await runTRPCTest({\n        projectName: \"cloudflare-workers-app\",\n        backend: \"hono\",\n        runtime: \"workers\",\n        database: \"sqlite\",\n        orm: \"drizzle\",\n        auth: \"none\",\n        api: \"trpc\",\n        frontend: [\"tanstack-router\"],\n        addons: [\"biome\"],\n        examples: [\"todo\"],\n        dbSetup: \"none\",\n        webDeploy: \"none\",\n        serverDeploy: \"cloudflare\",\n        install: false,\n      });\n\n      expectSuccess(result);\n    });\n\n    it(\"should create MongoDB + Mongoose app\", async () => {\n      const result = await runTRPCTest({\n        projectName: \"mongodb-mongoose-app\",\n        backend: \"hono\",\n        runtime: \"node\",\n        database: \"mongodb\",\n        orm: \"mongoose\",\n        auth: \"better-auth\",\n        api: \"trpc\",\n        frontend: [\"react-router\"],\n        addons: [\"husky\", \"turborepo\"],\n        examples: [\"todo\"],\n        dbSetup: \"none\",\n        webDeploy: \"none\",\n        serverDeploy: \"none\",\n        install: false,\n      });\n\n      expectSuccess(result);\n    });\n\n    it(\"should create Next.js fullstack app\", async () => {\n      const result = await runTRPCTest({\n        projectName: \"nextjs-fullstack\",\n        backend: \"self\",\n        runtime: \"none\",\n        database: \"postgres\",\n        orm: \"prisma\",\n        auth: \"better-auth\",\n        api: \"trpc\",\n        frontend: [\"next\"],\n        addons: [\"biome\", \"turborepo\", \"pwa\"],\n        examples: [\"ai\"],\n        dbSetup: \"none\",\n        webDeploy: \"none\",\n        serverDeploy: \"none\",\n        install: false,\n      });\n\n      expectSuccess(result);\n    });\n\n    it(\"should create Solid.js app with oRPC\", async () => {\n      const result = await runTRPCTest({\n        projectName: \"solid-orpc-app\",\n        backend: \"hono\",\n        runtime: \"workers\",\n        database: \"sqlite\",\n        orm: \"drizzle\",\n        auth: \"better-auth\",\n        api: \"orpc\",\n        frontend: [\"solid\"],\n        addons: [\"biome\", \"pwa\"],\n        examples: [\"todo\"], // AI not compatible with Solid\n        dbSetup: \"none\",\n        webDeploy: \"cloudflare\",\n        serverDeploy: \"cloudflare\",\n        install: false,\n      });\n\n      expectSuccess(result);\n    });\n  });\n\n  describe(\"Frontend-only Configurations\", () => {\n    it(\"should create frontend-only React app\", async () => {\n      const result = await runTRPCTest({\n        projectName: \"frontend-only-react\",\n        backend: \"none\",\n        runtime: \"none\",\n        database: \"none\",\n        orm: \"none\",\n        auth: \"none\",\n        api: \"none\",\n        frontend: [\"tanstack-router\"],\n        addons: [\"biome\", \"pwa\"],\n        examples: [\"none\"],\n        dbSetup: \"none\",\n        webDeploy: \"cloudflare\",\n        serverDeploy: \"none\",\n        install: false,\n      });\n\n      expectSuccess(result);\n    });\n\n    it(\"should create frontend-only Nuxt app\", async () => {\n      const result = await runTRPCTest({\n        projectName: \"frontend-only-nuxt\",\n        backend: \"none\",\n        runtime: \"none\",\n        database: \"none\",\n        orm: \"none\",\n        auth: \"none\",\n        api: \"none\",\n        frontend: [\"nuxt\"],\n        addons: [\"biome\", \"husky\"],\n        examples: [\"none\"],\n        dbSetup: \"none\",\n        webDeploy: \"cloudflare\",\n        serverDeploy: \"none\",\n        install: false,\n      });\n\n      expectSuccess(result);\n    });\n  });\n\n  describe(\"Complex Error Scenarios\", () => {\n    it(\"should fail with incompatible stack combination\", async () => {\n      // MongoDB + Drizzle is not supported\n      const result = await runTRPCTest({\n        projectName: \"incompatible-stack-fail\",\n        backend: \"hono\",\n        runtime: \"bun\",\n        database: \"mongodb\",\n        orm: \"drizzle\", // Not compatible with MongoDB\n        auth: \"better-auth\",\n        api: \"trpc\",\n        frontend: [\"tanstack-router\"],\n        addons: [\"none\"],\n        examples: [\"none\"],\n        dbSetup: \"none\",\n        webDeploy: \"none\",\n        serverDeploy: \"none\",\n        expectError: true,\n      });\n\n      expectError(result, \"Drizzle ORM does not support MongoDB\");\n    });\n\n    it(\"should fail with workers + incompatible database\", async () => {\n      const result = await runTRPCTest({\n        projectName: \"workers-mongodb-fail\",\n        backend: \"hono\",\n        runtime: \"workers\",\n        database: \"mongodb\", // Not compatible with Workers\n        orm: \"mongoose\",\n        auth: \"none\",\n        api: \"trpc\",\n        frontend: [\"tanstack-router\"],\n        addons: [\"none\"],\n        examples: [\"none\"],\n        dbSetup: \"none\",\n        webDeploy: \"none\",\n        serverDeploy: \"cloudflare\",\n        expectError: true,\n      });\n\n      expectError(\n        result,\n        \"Cloudflare Workers runtime (--runtime workers) is not compatible with MongoDB database\",\n      );\n    });\n\n    it(\"should fail with tRPC + incompatible frontend\", async () => {\n      const result = await runTRPCTest({\n        projectName: \"trpc-nuxt-fail\",\n        backend: \"hono\",\n        runtime: \"bun\",\n        database: \"sqlite\",\n        orm: \"drizzle\",\n        auth: \"none\",\n        api: \"trpc\",\n        frontend: [\"nuxt\"], // tRPC not compatible with Nuxt\n        addons: [\"none\"],\n        examples: [\"none\"],\n        dbSetup: \"none\",\n        webDeploy: \"none\",\n        serverDeploy: \"none\",\n        expectError: true,\n      });\n\n      expectError(result, \"tRPC API is not supported with 'nuxt' frontend\");\n    });\n\n    it(\"should fail with Clerk + incompatible frontend\", async () => {\n      const result = await runTRPCTest({\n        projectName: \"clerk-svelte-fail\",\n        backend: \"hono\",\n        runtime: \"bun\",\n        database: \"sqlite\",\n        orm: \"drizzle\",\n        auth: \"clerk\",\n        api: \"orpc\",\n        frontend: [\"svelte\"], // Clerk is not compatible with Svelte\n        addons: [\"none\"],\n        examples: [\"none\"],\n        dbSetup: \"none\",\n        webDeploy: \"none\",\n        serverDeploy: \"none\",\n        expectError: true,\n      });\n\n      expectError(result, \"Clerk authentication is not compatible\");\n    });\n\n    it(\"should fail with addon incompatibility\", async () => {\n      const result = await runTRPCTest({\n        projectName: \"pwa-native-fail\",\n        backend: \"hono\",\n        runtime: \"bun\",\n        database: \"sqlite\",\n        orm: \"drizzle\",\n        auth: \"none\",\n        api: \"trpc\",\n        frontend: [\"native-bare\"],\n        addons: [\"pwa\"], // PWA not compatible with native-only\n        examples: [\"none\"],\n        dbSetup: \"none\",\n        webDeploy: \"none\",\n        serverDeploy: \"none\",\n        expectError: true,\n      });\n\n      expectError(result, \"pwa addon requires one of these frontends\");\n    });\n\n    it(\"should fail with example incompatibility\", async () => {\n      const result = await runTRPCTest({\n        projectName: \"ai-solid-fail\",\n        backend: \"hono\",\n        runtime: \"bun\",\n        database: \"sqlite\",\n        orm: \"drizzle\",\n        auth: \"none\",\n        api: \"orpc\",\n        frontend: [\"solid\"],\n        addons: [\"none\"],\n        examples: [\"ai\"],\n        dbSetup: \"none\",\n        webDeploy: \"none\",\n        serverDeploy: \"none\",\n        expectError: true,\n      });\n\n      expectError(result, \"The 'ai' example is not compatible with the Solid frontend\");\n    });\n\n    it(\"should fail with Convex AI example + incompatible frontend\", async () => {\n      const result = await runTRPCTest({\n        projectName: \"convex-ai-svelte-fail\",\n        backend: \"convex\",\n        runtime: \"none\",\n        database: \"none\",\n        orm: \"none\",\n        auth: \"none\",\n        api: \"none\",\n        frontend: [\"svelte\"],\n        addons: [\"none\"],\n        examples: [\"ai\"],\n        dbSetup: \"none\",\n        webDeploy: \"none\",\n        serverDeploy: \"none\",\n        expectError: true,\n      });\n\n      expectError(\n        result,\n        \"The 'ai' example with Convex backend only supports React-based frontends (Next.js, TanStack Router, TanStack Start, React Router). Svelte and Nuxt are not supported with Convex AI.\",\n      );\n    });\n\n    it(\"should fail with payments incompatibility\", async () => {\n      const result = await runTRPCTest({\n        projectName: \"polar-no-auth-fail\",\n        backend: \"hono\",\n        runtime: \"bun\",\n        database: \"none\",\n        orm: \"none\",\n        auth: \"none\",\n        payments: \"polar\",\n        api: \"trpc\",\n        frontend: [\"tanstack-router\"],\n        addons: [\"turborepo\"],\n        examples: [\"none\"],\n        dbSetup: \"none\",\n        webDeploy: \"none\",\n        serverDeploy: \"none\",\n        expectError: true,\n      });\n\n      expectError(result, \"Polar payments requires Better Auth\");\n    });\n\n    it(\"should fail with deployment constraint violation\", async () => {\n      const result = await runTRPCTest({\n        projectName: \"web-deploy-no-frontend-fail\",\n        backend: \"hono\",\n        runtime: \"bun\",\n        database: \"sqlite\",\n        orm: \"drizzle\",\n        auth: \"none\",\n        api: \"trpc\",\n        frontend: [\"native-bare\"], // Only native, no web\n        addons: [\"none\"],\n        examples: [\"none\"],\n        dbSetup: \"none\",\n        webDeploy: \"cloudflare\", // Requires web frontend\n        serverDeploy: \"none\",\n        expectError: true,\n      });\n\n      expectError(result, \"'--web-deploy' requires a web frontend\");\n    });\n  });\n\n  describe(\"Edge Case Combinations\", () => {\n    it(\"should handle maximum complexity configuration\", async () => {\n      const result = await runTRPCTest({\n        projectName: \"max-complexity\",\n        backend: \"hono\",\n        runtime: \"workers\",\n        database: \"postgres\",\n        orm: \"drizzle\",\n        auth: \"better-auth\",\n        api: \"trpc\",\n        frontend: [\"tanstack-router\", \"native-bare\"],\n        addons: [\"biome\", \"husky\", \"turborepo\"],\n        examples: [\"todo\", \"ai\"],\n        dbSetup: \"none\",\n        webDeploy: \"cloudflare\",\n        serverDeploy: \"cloudflare\",\n        install: false,\n      });\n\n      expectSuccess(result);\n    });\n\n    it(\"should handle minimal configuration\", async () => {\n      const result = await runTRPCTest({\n        projectName: \"minimal-config\",\n        backend: \"none\",\n        runtime: \"none\",\n        database: \"none\",\n        orm: \"none\",\n        auth: \"none\",\n        api: \"none\",\n        frontend: [\"tanstack-router\"],\n        addons: [\"none\"],\n        examples: [\"none\"],\n        dbSetup: \"none\",\n        webDeploy: \"none\",\n        serverDeploy: \"none\",\n        install: false,\n      });\n\n      expectSuccess(result);\n    });\n\n    const packageManagers = [\"npm\", \"pnpm\", \"bun\"];\n\n    for (const packageManager of packageManagers) {\n      it(`should handle ${packageManager} package manager`, async () => {\n        const result = await runTRPCTest({\n          projectName: `pkg-manager-${packageManager}`,\n          backend: \"hono\",\n          runtime: \"bun\",\n          database: \"sqlite\",\n          orm: \"drizzle\",\n          auth: \"none\",\n          api: \"trpc\",\n          frontend: [\"tanstack-router\"],\n          addons: [\"none\"],\n          examples: [\"none\"],\n          dbSetup: \"none\",\n          webDeploy: \"none\",\n          serverDeploy: \"none\",\n          install: false,\n        });\n\n        expectSuccess(result);\n      });\n    }\n\n    const runtimeConfigs = [\n      { runtime: \"bun\", backend: \"hono\" },\n      { runtime: \"node\", backend: \"express\" },\n      { runtime: \"workers\", backend: \"hono\" },\n      { runtime: \"none\", backend: \"convex\" },\n    ];\n\n    for (const { runtime, backend } of runtimeConfigs) {\n      it(`should handle ${runtime} runtime with ${backend} backend`, async () => {\n        const config: TestConfig = {\n          projectName: `runtime-${runtime}-${backend}`,\n          runtime: runtime as Runtime,\n          backend: backend as Backend,\n          frontend: [\"tanstack-router\"],\n          install: false,\n        };\n\n        // Set appropriate defaults\n        if (backend === \"convex\") {\n          config.database = \"none\";\n          config.orm = \"none\";\n          config.auth = \"clerk\";\n          config.api = \"none\";\n          config.addons = [\"none\"];\n          config.examples = [\"none\"];\n          config.dbSetup = \"none\";\n          config.webDeploy = \"none\";\n          config.serverDeploy = \"none\";\n        } else {\n          config.database = \"sqlite\";\n          config.orm = \"drizzle\";\n          config.auth = \"none\";\n          config.api = \"trpc\";\n          config.addons = [\"none\"];\n          config.examples = [\"none\"];\n          config.dbSetup = \"none\";\n          config.webDeploy = \"none\";\n          config.serverDeploy = \"none\";\n        }\n\n        // Handle workers runtime requirements\n        if (runtime === \"workers\") {\n          config.serverDeploy = \"cloudflare\";\n        }\n\n        const result = await runTRPCTest(config);\n        expectSuccess(result);\n      });\n    }\n  });\n});\n"
  },
  {
    "path": "apps/cli/test/mcp.test.ts",
    "content": "import { afterEach, beforeEach, describe, expect, it } from \"bun:test\";\nimport path from \"node:path\";\n\nimport { Client } from \"@modelcontextprotocol/sdk/client/index.js\";\nimport { StdioClientTransport } from \"@modelcontextprotocol/sdk/client/stdio.js\";\nimport { InMemoryTransport } from \"@modelcontextprotocol/sdk/inMemory.js\";\nimport fs from \"fs-extra\";\n\nimport { create } from \"../src/index\";\nimport { createBtsMcpServer } from \"../src/mcp\";\nimport { readBtsConfig } from \"../src/utils/bts-config\";\nimport { SMOKE_DIR } from \"./setup\";\n\nasync function connectInMemoryClient() {\n  const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();\n  const server = createBtsMcpServer();\n  await server.connect(serverTransport);\n\n  const client = new Client({ name: \"mcp-test-client\", version: \"0.0.0\" }, { capabilities: {} });\n  await client.connect(clientTransport);\n\n  return {\n    client,\n    cleanup: async () => {\n      await client.close();\n      await server.close();\n    },\n  };\n}\n\nfunction getExplicitCreateInput(projectPath: string) {\n  return {\n    projectName: projectPath,\n    frontend: [\"next\"] as const,\n    backend: \"hono\" as const,\n    runtime: \"bun\" as const,\n    database: \"sqlite\" as const,\n    orm: \"drizzle\" as const,\n    api: \"trpc\" as const,\n    auth: \"better-auth\" as const,\n    payments: \"none\" as const,\n    addons: [\"turborepo\"] as const,\n    examples: [] as const,\n    git: true,\n    packageManager: \"bun\" as const,\n    install: false,\n    dbSetup: \"none\" as const,\n    webDeploy: \"none\" as const,\n    serverDeploy: \"none\" as const,\n  };\n}\n\ndescribe(\"MCP server\", () => {\n  let cleanups: Array<() => Promise<void>> = [];\n\n  beforeEach(async () => {\n    process.env.BTS_SKIP_EXTERNAL_COMMANDS = \"1\";\n    process.env.BTS_TEST_MODE = \"1\";\n  });\n\n  afterEach(async () => {\n    for (const cleanup of cleanups.reverse()) {\n      await Promise.race([\n        cleanup(),\n        new Promise<void>((resolve) => {\n          setTimeout(resolve, 1000);\n        }),\n      ]);\n    }\n    cleanups = [];\n  });\n\n  it(\"registers the expected MCP tools\", async () => {\n    const { client, cleanup } = await connectInMemoryClient();\n    cleanups.push(cleanup);\n\n    const result = await client.listTools();\n    const toolNames = result.tools.map((tool) => tool.name).sort();\n\n    expect(toolNames).toEqual([\n      \"bts_add_addons\",\n      \"bts_create_project\",\n      \"bts_get_schema\",\n      \"bts_get_stack_guidance\",\n      \"bts_plan_addons\",\n      \"bts_plan_project\",\n    ]);\n  });\n\n  it(\"returns explicit create-contract guidance\", async () => {\n    const { client, cleanup } = await connectInMemoryClient();\n    cleanups.push(cleanup);\n\n    const result = await client.callTool({\n      name: \"bts_get_stack_guidance\",\n      arguments: {},\n    });\n\n    const payload = result.structuredContent as {\n      ok: boolean;\n      data?: {\n        createContract?: {\n          requiresExplicitFields?: string[];\n          rule?: string;\n        };\n      };\n    };\n\n    expect(payload.ok).toBe(true);\n    expect(payload.data?.createContract?.requiresExplicitFields).toEqual(\n      expect.arrayContaining([\n        \"projectName\",\n        \"frontend\",\n        \"backend\",\n        \"runtime\",\n        \"database\",\n        \"orm\",\n        \"api\",\n        \"auth\",\n        \"payments\",\n        \"addons\",\n        \"examples\",\n        \"git\",\n        \"packageManager\",\n        \"install\",\n        \"dbSetup\",\n        \"webDeploy\",\n        \"serverDeploy\",\n      ]),\n    );\n    expect(payload.data?.createContract?.rule).toContain(\"full explicit stack config\");\n  });\n\n  it(\"returns CLI and input schemas through MCP\", async () => {\n    const { client, cleanup } = await connectInMemoryClient();\n    cleanups.push(cleanup);\n\n    const result = await client.callTool({\n      name: \"bts_get_schema\",\n      arguments: { name: \"createInput\" },\n    });\n\n    const payload = result.structuredContent as {\n      ok: boolean;\n      data?: { type?: string; properties?: Record<string, unknown> };\n    };\n\n    expect(payload.ok).toBe(true);\n    expect(payload.data?.type).toBe(\"object\");\n    expect(payload.data?.properties).toHaveProperty(\"frontend\");\n    expect(payload.data?.properties).toHaveProperty(\"backend\");\n  });\n\n  it(\"rejects partial project payloads before planning\", async () => {\n    const { client, cleanup } = await connectInMemoryClient();\n    cleanups.push(cleanup);\n\n    const result = await client.callTool({\n      name: \"bts_plan_project\",\n      arguments: {\n        projectName: \"partial-app\",\n        frontend: [\"next\"],\n        git: true,\n        install: true,\n      },\n    });\n\n    expect(result.isError).toBe(true);\n    const text = result.content\n      .filter((item): item is { type: \"text\"; text: string } => item.type === \"text\")\n      .map((item) => item.text)\n      .join(\"\\n\");\n\n    expect(text).toContain(\"Input validation error\");\n    expect(text).toContain(\"database\");\n    expect(text).toContain(\"backend\");\n    expect(text).toContain(\"packageManager\");\n  });\n\n  it(\"plans projects without writing files\", async () => {\n    const { client, cleanup } = await connectInMemoryClient();\n    cleanups.push(cleanup);\n\n    const projectPath = path.join(SMOKE_DIR, \"mcp-plan-project\");\n    await fs.remove(projectPath);\n\n    const result = await client.callTool({\n      name: \"bts_plan_project\",\n      arguments: getExplicitCreateInput(projectPath),\n    });\n\n    const payload = result.structuredContent as {\n      ok: boolean;\n      data?: { projectDirectory?: string; success?: boolean };\n    };\n\n    expect(payload.ok).toBe(true);\n    expect(payload.data?.success).toBe(true);\n    expect(payload.data?.projectDirectory).toBe(projectPath);\n    expect(await fs.pathExists(projectPath)).toBe(false);\n  });\n\n  it(\"warns during planning when install=true would be slow for MCP execution\", async () => {\n    const { client, cleanup } = await connectInMemoryClient();\n    cleanups.push(cleanup);\n\n    const projectPath = path.join(SMOKE_DIR, \"mcp-plan-install-warning\");\n    await fs.remove(projectPath);\n\n    const result = await client.callTool({\n      name: \"bts_plan_project\",\n      arguments: {\n        ...getExplicitCreateInput(projectPath),\n        install: true,\n      },\n    });\n\n    const payload = result.structuredContent as {\n      ok: boolean;\n      data?: {\n        warnings?: string[];\n        recommendedMcpExecution?: { install?: boolean };\n      };\n    };\n\n    expect(payload.ok).toBe(true);\n    expect(payload.data?.warnings?.[0]).toContain(\"install: false\");\n    expect(payload.data?.recommendedMcpExecution?.install).toBe(false);\n  });\n\n  it(\"creates projects on disk from explicit MCP input\", async () => {\n    const { client, cleanup } = await connectInMemoryClient();\n    cleanups.push(cleanup);\n\n    const projectPath = path.join(SMOKE_DIR, \"mcp-create-project\");\n    await fs.remove(projectPath);\n\n    const result = await client.callTool({\n      name: \"bts_create_project\",\n      arguments: getExplicitCreateInput(projectPath),\n    });\n\n    const payload = result.structuredContent as {\n      ok: boolean;\n      data?: { success?: boolean; projectDirectory?: string };\n    };\n\n    expect(payload.ok).toBe(true);\n    expect(payload.data?.success).toBe(true);\n    expect(payload.data?.projectDirectory).toBe(projectPath);\n    expect(await fs.pathExists(projectPath)).toBe(true);\n\n    const btsConfig = await readBtsConfig(projectPath);\n    expect(btsConfig?.frontend).toEqual([\"next\"]);\n  });\n\n  it(\"rejects install=true during MCP project creation with an actionable error\", async () => {\n    const { client, cleanup } = await connectInMemoryClient();\n    cleanups.push(cleanup);\n\n    const projectPath = path.join(SMOKE_DIR, \"mcp-create-install-rejected\");\n    await fs.remove(projectPath);\n\n    const result = await client.callTool({\n      name: \"bts_create_project\",\n      arguments: {\n        ...getExplicitCreateInput(projectPath),\n        install: true,\n      },\n    });\n\n    const payload = result.structuredContent as { ok: boolean; error?: string };\n\n    expect(result.isError).toBe(true);\n    expect(payload.ok).toBe(false);\n    expect(payload.error).toContain(\"install: false\");\n    expect(await fs.pathExists(projectPath)).toBe(false);\n  });\n\n  it(\"returns an MCP tool error when creating into a non-empty directory\", async () => {\n    const { client, cleanup } = await connectInMemoryClient();\n    cleanups.push(cleanup);\n\n    const projectPath = path.join(SMOKE_DIR, \"mcp-existing-directory\");\n    await fs.ensureDir(projectPath);\n    await fs.writeFile(path.join(projectPath, \"existing.txt\"), \"hello\");\n\n    const result = await client.callTool({\n      name: \"bts_create_project\",\n      arguments: getExplicitCreateInput(projectPath),\n    });\n\n    const payload = result.structuredContent as { ok: boolean; error?: string };\n    expect(result.isError).toBe(true);\n    expect(payload.ok).toBe(false);\n    expect(payload.error).toContain(\"already exists and is not empty\");\n  });\n\n  it(\"plans addon installation without mutating bts.jsonc\", async () => {\n    const { client, cleanup } = await connectInMemoryClient();\n    cleanups.push(cleanup);\n\n    const projectPath = path.join(SMOKE_DIR, \"mcp-plan-addons\");\n    await fs.remove(projectPath);\n    const createResult = await create(projectPath, {\n      frontend: [\"tanstack-router\"],\n      backend: \"hono\",\n      runtime: \"bun\",\n      database: \"sqlite\",\n      orm: \"drizzle\",\n      api: \"trpc\",\n      auth: \"none\",\n      payments: \"none\",\n      addons: [\"turborepo\"],\n      examples: [\"none\"],\n      dbSetup: \"none\",\n      webDeploy: \"none\",\n      serverDeploy: \"none\",\n      install: false,\n      git: true,\n      packageManager: \"bun\",\n      disableAnalytics: true,\n    });\n    expect(createResult.isOk()).toBe(true);\n\n    const before = await readBtsConfig(projectPath);\n\n    const result = await client.callTool({\n      name: \"bts_plan_addons\",\n      arguments: {\n        projectDir: projectPath,\n        addons: [\"biome\"],\n        packageManager: \"bun\",\n        install: false,\n      },\n    });\n\n    const payload = result.structuredContent as {\n      ok: boolean;\n      data?: { success?: boolean; dryRun?: boolean; addedAddons?: string[] };\n    };\n\n    expect(payload.ok).toBe(true);\n    expect(payload.data?.success).toBe(true);\n    expect(payload.data?.dryRun).toBe(true);\n    expect(payload.data?.addedAddons).toEqual([\"biome\"]);\n\n    const after = await readBtsConfig(projectPath);\n    expect(after).toEqual(before);\n  });\n\n  it(\"adds addons through MCP and persists them to bts.jsonc\", async () => {\n    const { client, cleanup } = await connectInMemoryClient();\n    cleanups.push(cleanup);\n\n    const projectPath = path.join(SMOKE_DIR, \"mcp-add-addons\");\n    await fs.remove(projectPath);\n    const createResult = await create(projectPath, {\n      frontend: [\"tanstack-router\"],\n      backend: \"hono\",\n      runtime: \"bun\",\n      database: \"sqlite\",\n      orm: \"drizzle\",\n      api: \"trpc\",\n      auth: \"none\",\n      payments: \"none\",\n      addons: [\"turborepo\"],\n      examples: [\"none\"],\n      dbSetup: \"none\",\n      webDeploy: \"none\",\n      serverDeploy: \"none\",\n      install: false,\n      git: true,\n      packageManager: \"bun\",\n      disableAnalytics: true,\n    });\n    expect(createResult.isOk()).toBe(true);\n\n    const result = await client.callTool({\n      name: \"bts_add_addons\",\n      arguments: {\n        projectDir: projectPath,\n        addons: [\"biome\"],\n        packageManager: \"bun\",\n        install: false,\n      },\n    });\n\n    const payload = result.structuredContent as {\n      ok: boolean;\n      data?: { success?: boolean; addedAddons?: string[] };\n    };\n\n    expect(payload.ok).toBe(true);\n    expect(payload.data?.success).toBe(true);\n    expect(payload.data?.addedAddons).toEqual([\"biome\"]);\n\n    const after = await readBtsConfig(projectPath);\n    expect(after?.addons).toEqual(expect.arrayContaining([\"turborepo\", \"biome\"]));\n  });\n\n  it(\"starts over stdio through the CLI entrypoint\", async () => {\n    const cliRoot = path.join(import.meta.dir, \"..\");\n    const cliEntrypoint = path.join(cliRoot, \"src\", \"cli.ts\");\n    const client = new Client({ name: \"mcp-stdio-test\", version: \"0.0.0\" }, { capabilities: {} });\n\n    const transport = new StdioClientTransport({\n      command: \"bun\",\n      args: [cliEntrypoint, \"mcp\"],\n      cwd: cliRoot,\n      env: {\n        BTS_SKIP_EXTERNAL_COMMANDS: \"1\",\n        BTS_TEST_MODE: \"1\",\n      },\n    });\n\n    await client.connect(transport);\n    cleanups.push(async () => {\n      await transport.close();\n      await client.close();\n    });\n\n    const tools = await client.listTools();\n    expect(tools.tools.map((tool) => tool.name)).toEqual(\n      expect.arrayContaining([\"bts_get_stack_guidance\", \"bts_plan_project\", \"bts_add_addons\"]),\n    );\n\n    const guidance = await client.callTool({\n      name: \"bts_get_stack_guidance\",\n      arguments: {},\n    });\n\n    const payload = guidance.structuredContent as { ok: boolean };\n    expect(payload.ok).toBe(true);\n  });\n});\n"
  },
  {
    "path": "apps/cli/test/project-name-validation.test.ts",
    "content": "import { describe, expect, it } from \"bun:test\";\nimport path from \"node:path\";\n\nimport fs from \"fs-extra\";\n\nimport { create } from \"../src/index\";\nimport {\n  extractAndValidateProjectName,\n  validateProjectName,\n} from \"../src/utils/project-name-validation\";\n\nconst SMOKE_DIR_PATH = path.join(import.meta.dir, \"..\", \".smoke\");\n\ndescribe(\"Project name validation hardening\", () => {\n  it(\"rejects control characters\", () => {\n    const result = validateProjectName(\"bad\\nname\");\n\n    expect(result.isErr()).toBe(true);\n    if (result.isErr()) {\n      expect(result.error.message).toContain(\"control characters\");\n    }\n  });\n\n  it(\"rejects query-like characters in project input\", () => {\n    const result = extractAndValidateProjectName(\"my-app?fields=id,name\");\n\n    expect(result.isErr()).toBe(true);\n    if (result.isErr()) {\n      expect(result.error.message).toContain(\"Invalid project name\");\n    }\n  });\n\n  it(\"allows percent characters in filesystem paths\", () => {\n    const result = extractAndValidateProjectName(\"my-app%23docs\");\n\n    expect(result.isOk()).toBe(true);\n  });\n\n  it(\"allows hash characters in filesystem paths\", () => {\n    const result = extractAndValidateProjectName(\"my-app#docs\");\n\n    expect(result.isOk()).toBe(true);\n  });\n\n  it(\"fails invalid names before creating the project directory\", async () => {\n    const projectPath = path.join(SMOKE_DIR_PATH, \"invalid?name\");\n    await fs.remove(projectPath);\n\n    const result = await create(projectPath, {\n      yes: true,\n      install: false,\n      disableAnalytics: true,\n      directoryConflict: \"overwrite\",\n    });\n\n    expect(result.isErr()).toBe(true);\n    expect(await fs.pathExists(projectPath)).toBe(false);\n  });\n});\n"
  },
  {
    "path": "apps/cli/test/readme.test.ts",
    "content": "import { describe, expect, it } from \"bun:test\";\n\nimport { createVirtual } from \"../src/index\";\nimport { collectFiles } from \"./setup\";\n\nasync function generateReadme(config: Parameters<typeof createVirtual>[0]): Promise<string> {\n  const result = await createVirtual({\n    projectName: \"readme-check\",\n    frontend: [\"tanstack-router\"],\n    backend: \"hono\",\n    runtime: \"bun\",\n    database: \"sqlite\",\n    orm: \"drizzle\",\n    auth: \"clerk\",\n    api: \"trpc\",\n    addons: [\"turborepo\"],\n    examples: [\"todo\"],\n    dbSetup: \"none\",\n    webDeploy: \"none\",\n    serverDeploy: \"none\",\n    install: false,\n    git: false,\n    packageManager: \"bun\",\n    payments: \"none\",\n    ...config,\n  });\n\n  expect(result.isOk()).toBe(true);\n\n  if (result.isErr()) {\n    throw result.error;\n  }\n\n  const files = collectFiles(result.value.root, result.value.root.path);\n  return files.get(\"README.md\") ?? \"\";\n}\n\ndescribe(\"README generation\", () => {\n  it(\"documents Clerk env setup for next + express\", async () => {\n    const readme = await generateReadme({\n      frontend: [\"next\"],\n      backend: \"express\",\n    });\n\n    expect(readme).toContain(\"`NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY` in `apps/web/.env`\");\n    expect(readme).toContain(\"`CLERK_SECRET_KEY` in `apps/web/.env` for Clerk server middleware\");\n    expect(readme).toContain(\"`CLERK_SECRET_KEY` in `apps/server/.env` for server-side Clerk auth\");\n    expect(readme).toContain(\n      \"`CLERK_PUBLISHABLE_KEY` in `apps/server/.env` for Clerk backend middleware\",\n    );\n  });\n\n  it(\"documents Clerk request verification for self backends\", async () => {\n    const readme = await generateReadme({\n      frontend: [\"next\"],\n      backend: \"self\",\n      runtime: \"none\",\n    });\n\n    expect(readme).toContain(\"`NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY` in `apps/web/.env`\");\n    expect(readme).toContain(\n      \"`CLERK_SECRET_KEY` in `apps/web/.env` for Clerk server middleware and server-side Clerk auth\",\n    );\n    expect(readme).toContain(\n      \"`CLERK_PUBLISHABLE_KEY` in `apps/web/.env` for server-side Clerk request verification\",\n    );\n  });\n\n  it(\"documents Clerk native env setup for standalone backends\", async () => {\n    const readme = await generateReadme({\n      frontend: [\"native-uniwind\"],\n      backend: \"hono\",\n      api: \"trpc\",\n    });\n\n    expect(readme).toContain(\"`EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY` in `apps/native/.env`\");\n    expect(readme).toContain(\"`CLERK_SECRET_KEY` in `apps/server/.env` for server-side Clerk auth\");\n    expect(readme).toContain(\n      \"`CLERK_PUBLISHABLE_KEY` in `apps/server/.env` for server-side Clerk request verification\",\n    );\n    expect(readme).not.toContain(\"Open [http://localhost:3001]\");\n    expect(readme).not.toContain(\"web/         # Frontend application\");\n  });\n});\n"
  },
  {
    "path": "apps/cli/test/schema-command.test.ts",
    "content": "import { describe, expect, it } from \"bun:test\";\n\nimport { initTRPC } from \"@trpc/server\";\n\nimport { router } from \"../src/index\";\n\nconst caller = initTRPC.create().createCallerFactory(router)({});\n\ndescribe(\"Schema command\", () => {\n  it(\"returns full schema payload for 'all'\", async () => {\n    const result = await caller.schema({ name: \"all\" });\n\n    expect(result).toHaveProperty(\"cli\");\n    expect(result).toHaveProperty(\"schemas\");\n    expect(result.schemas).toHaveProperty(\"createInput\");\n    expect(result.schemas).toHaveProperty(\"addInput\");\n    expect(result.schemas).toHaveProperty(\"addonOptions\");\n    expect(result.schemas).toHaveProperty(\"dbSetupOptions\");\n    expect(Array.isArray(result.cli.commands)).toBe(true);\n  });\n\n  it(\"returns a specific schema payload\", async () => {\n    const result = await caller.schema({ name: \"createInput\" });\n\n    expect(result).toHaveProperty(\"type\", \"object\");\n    expect(result).toHaveProperty(\"properties\");\n  });\n\n  it(\"includes agent-focused commands in CLI introspection\", async () => {\n    const result = await caller.schema({ name: \"cli\" });\n    const commandNames = result.commands.map((command) => command.name);\n\n    expect(commandNames).toContain(\"create-json\");\n    expect(commandNames).toContain(\"add-json\");\n    expect(commandNames).toContain(\"schema\");\n  });\n});\n"
  },
  {
    "path": "apps/cli/test/setup.ts",
    "content": "import { afterAll, beforeAll } from \"bun:test\";\nimport { mkdir, rm } from \"node:fs/promises\";\nimport { join } from \"node:path\";\n\nexport const SMOKE_DIR = join(import.meta.dir, \"..\", \".smoke\");\n\ntype VirtualFileNode = {\n  type: \"file\";\n  path: string;\n  content: string;\n};\n\ntype VirtualDirectoryNode = {\n  type: \"directory\";\n  path: string;\n  children: VirtualNode[];\n};\n\nexport type VirtualNode = VirtualFileNode | VirtualDirectoryNode;\n\nexport async function ensureSmokeDirectory() {\n  await mkdir(SMOKE_DIR, { recursive: true });\n}\n\nexport async function cleanupSmokeDirectory() {\n  await rm(SMOKE_DIR, { recursive: true, force: true });\n}\n\nexport function collectFiles(\n  node: VirtualNode,\n  rootPath: string,\n  files = new Map<string, string>(),\n) {\n  if (node.type === \"file\") {\n    const relativePath = node.path.startsWith(`${rootPath}/`)\n      ? node.path.slice(rootPath.length + 1)\n      : node.path;\n    files.set(relativePath, node.content);\n    return files;\n  }\n\n  for (const child of node.children) {\n    collectFiles(child, rootPath, files);\n  }\n\n  return files;\n}\n\n// Global setup - runs once before all tests\nbeforeAll(async () => {\n  try {\n    process.env.BTS_SKIP_EXTERNAL_COMMANDS = \"1\";\n    process.env.BTS_TEST_MODE = \"1\";\n    await cleanupSmokeDirectory();\n    await ensureSmokeDirectory();\n  } catch (error) {\n    console.error(\"Failed to setup smoke directory:\", error);\n    throw error;\n  }\n});\n\n// Global teardown - runs once after all tests\nafterAll(async () => {\n  try {\n    await cleanupSmokeDirectory();\n  } catch {\n    // Ignore cleanup errors on teardown\n  }\n});\n"
  },
  {
    "path": "apps/cli/test/silent-create-output.test.ts",
    "content": "import { describe, expect, it } from \"bun:test\";\nimport path from \"node:path\";\n\nimport { execa } from \"execa\";\nimport fs from \"fs-extra\";\n\nimport { SMOKE_DIR } from \"./setup\";\n\nconst CLI_INDEX_PATH = path.join(import.meta.dir, \"..\", \"src\", \"index.ts\");\n\ntype SilentCreateCase = {\n  name: string;\n  projectName: string;\n  options: Record<string, unknown>;\n};\n\nasync function runSilentCreate(testCase: SilentCreateCase) {\n  const projectPath = path.join(SMOKE_DIR, testCase.projectName);\n  await fs.remove(projectPath);\n\n  const script = `\n    import { create } from ${JSON.stringify(CLI_INDEX_PATH)};\n\n    const result = await create(${JSON.stringify(testCase.projectName)}, {\n      ...${JSON.stringify(testCase.options)},\n      disableAnalytics: true,\n    });\n\n    if (result.isErr()) {\n      console.error(result.error.message);\n      process.exit(1);\n    }\n  `;\n\n  const result = await execa(\"bun\", [\"-e\", script], {\n    cwd: SMOKE_DIR,\n    env: {\n      BTS_TELEMETRY_DISABLED: \"1\",\n    },\n    reject: false,\n  });\n\n  return {\n    ...result,\n    projectPath,\n  };\n}\n\ndescribe(\"silent create output\", () => {\n  const cases: SilentCreateCase[] = [\n    {\n      name: \"stays quiet for oxlint addon setup\",\n      projectName: \"silent-addon-oxlint\",\n      options: {\n        frontend: [\"next\"],\n        backend: \"hono\",\n        runtime: \"node\",\n        database: \"none\",\n        orm: \"none\",\n        api: \"none\",\n        auth: \"none\",\n        payments: \"none\",\n        addons: [\"nx\", \"oxlint\"],\n        examples: [],\n        git: true,\n        packageManager: \"pnpm\",\n        install: false,\n        dbSetup: \"none\",\n        webDeploy: \"none\",\n        serverDeploy: \"none\",\n      },\n    },\n    {\n      name: \"stays quiet for manual neon setup\",\n      projectName: \"silent-db-neon\",\n      options: {\n        frontend: [\"next\"],\n        backend: \"hono\",\n        runtime: \"node\",\n        database: \"postgres\",\n        orm: \"drizzle\",\n        api: \"trpc\",\n        auth: \"none\",\n        payments: \"none\",\n        addons: [],\n        examples: [],\n        git: true,\n        packageManager: \"pnpm\",\n        install: false,\n        dbSetup: \"neon\",\n        dbSetupOptions: { mode: \"manual\" },\n        webDeploy: \"none\",\n        serverDeploy: \"none\",\n      },\n    },\n    {\n      name: \"stays quiet for manual prisma-postgres setup\",\n      projectName: \"silent-db-prisma-postgres\",\n      options: {\n        frontend: [\"next\"],\n        backend: \"hono\",\n        runtime: \"node\",\n        database: \"postgres\",\n        orm: \"prisma\",\n        api: \"trpc\",\n        auth: \"none\",\n        payments: \"none\",\n        addons: [],\n        examples: [],\n        git: true,\n        packageManager: \"pnpm\",\n        install: false,\n        dbSetup: \"prisma-postgres\",\n        dbSetupOptions: { mode: \"manual\" },\n        webDeploy: \"none\",\n        serverDeploy: \"none\",\n      },\n    },\n    {\n      name: \"stays quiet for manual turso setup\",\n      projectName: \"silent-db-turso\",\n      options: {\n        frontend: [\"next\"],\n        backend: \"hono\",\n        runtime: \"node\",\n        database: \"sqlite\",\n        orm: \"drizzle\",\n        api: \"trpc\",\n        auth: \"none\",\n        payments: \"none\",\n        addons: [],\n        examples: [],\n        git: true,\n        packageManager: \"pnpm\",\n        install: false,\n        dbSetup: \"turso\",\n        dbSetupOptions: { mode: \"manual\" },\n        webDeploy: \"none\",\n        serverDeploy: \"none\",\n      },\n    },\n    {\n      name: \"stays quiet for manual supabase setup\",\n      projectName: \"silent-db-supabase\",\n      options: {\n        frontend: [\"next\"],\n        backend: \"hono\",\n        runtime: \"node\",\n        database: \"postgres\",\n        orm: \"drizzle\",\n        api: \"trpc\",\n        auth: \"none\",\n        payments: \"none\",\n        addons: [],\n        examples: [],\n        git: true,\n        packageManager: \"pnpm\",\n        install: false,\n        dbSetup: \"supabase\",\n        dbSetupOptions: { mode: \"manual\" },\n        webDeploy: \"none\",\n        serverDeploy: \"none\",\n      },\n    },\n    {\n      name: \"stays quiet for manual mongodb atlas setup\",\n      projectName: \"silent-db-mongodb-atlas\",\n      options: {\n        frontend: [\"next\"],\n        backend: \"hono\",\n        runtime: \"node\",\n        database: \"mongodb\",\n        orm: \"mongoose\",\n        api: \"none\",\n        auth: \"none\",\n        payments: \"none\",\n        addons: [],\n        examples: [],\n        git: true,\n        packageManager: \"pnpm\",\n        install: false,\n        dbSetup: \"mongodb-atlas\",\n        dbSetupOptions: { mode: \"manual\" },\n        webDeploy: \"none\",\n        serverDeploy: \"none\",\n      },\n    },\n  ];\n\n  for (const testCase of cases) {\n    it(testCase.name, async () => {\n      const result = await runSilentCreate(testCase);\n\n      expect(result.exitCode).toBe(0);\n      expect(result.stdout).toBe(\"\");\n      expect(result.stderr).toBe(\"\");\n      expect(await fs.pathExists(result.projectPath)).toBe(true);\n    });\n  }\n});\n"
  },
  {
    "path": "apps/cli/test/sponsors.test.ts",
    "content": "import { describe, expect, it } from \"bun:test\";\n\nimport type { SponsorEntry } from \"../src/utils/sponsors\";\nimport { formatPostInstallSpecialSponsorsSection } from \"../src/utils/sponsors\";\n\nfunction createSponsorsFixture(): SponsorEntry {\n  return {\n    generated_at: \"2026-02-25T00:00:00.000Z\",\n    summary: {\n      total_sponsors: 3,\n      total_lifetime_amount: 300,\n      total_current_monthly: 100,\n      special_sponsors: 3,\n      current_sponsors: 3,\n      past_sponsors: 0,\n      backers: 0,\n      top_sponsor: {\n        name: \"Ada\",\n        amount: 100,\n      },\n    },\n    specialSponsors: [\n      {\n        githubId: \"ada\",\n        githubUrl: \"https://github.com/ada\",\n        avatarUrl: \"https://example.com/ada.png\",\n        tierName: \"Pro\",\n        sinceWhen: \"2025-01\",\n        transactionCount: 8,\n        name: \"Ada\",\n      },\n      {\n        githubId: \"grace\",\n        githubUrl: \"https://github.com/grace\",\n        avatarUrl: \"https://example.com/grace.png\",\n        tierName: \"Starter\",\n        sinceWhen: \"2025-02\",\n        transactionCount: 5,\n        name: \"Grace\",\n      },\n      {\n        githubId: \"linus\",\n        githubUrl: \"https://github.com/linus\",\n        avatarUrl: \"https://example.com/linus.png\",\n        sinceWhen: \"2025-03\",\n        transactionCount: 3,\n        name: \"Linus\",\n      },\n    ],\n    sponsors: [],\n    pastSponsors: [],\n    backers: [],\n  };\n}\n\ndescribe(\"formatPostInstallSpecialSponsorsSection\", () => {\n  it(\"returns empty output when no special sponsors exist\", () => {\n    const fixture = createSponsorsFixture();\n    fixture.specialSponsors = [];\n\n    const output = formatPostInstallSpecialSponsorsSection(fixture);\n    expect(output).toBe(\"\");\n  });\n\n  it(\"renders all special sponsors without tier details or sponsor link\", () => {\n    const fixture = createSponsorsFixture();\n\n    const output = formatPostInstallSpecialSponsorsSection(fixture);\n    expect(output).toContain(\"Special sponsors\");\n    expect(output).toContain(\"Ada\");\n    expect(output).toContain(\"Grace\");\n    expect(output).toContain(\"Linus\");\n    expect(output).toContain(\"• Ada\");\n    expect(output).not.toContain(\"Pro\");\n    expect(output).not.toContain(\"Starter\");\n    expect(output).not.toContain(\"Become a sponsor\");\n  });\n});\n"
  },
  {
    "path": "apps/cli/test/tauri-setup.test.ts",
    "content": "import { describe, expect, it } from \"bun:test\";\n\nimport { buildTauriInitArgs } from \"../src/helpers/addons/tauri-setup\";\n\ndescribe(\"Tauri setup\", () => {\n  it(\"builds init args with frontend-specific dev urls and static output paths\", () => {\n    const cases = [\n      {\n        frontend: [\"tanstack-start\"],\n        expectedDist: \"../dist/client\",\n        expectedUrl: \"http://localhost:3001\",\n        expectedBuildCommand: \"bun run build\",\n      },\n      {\n        frontend: [\"next\"],\n        expectedDist: \"../out\",\n        expectedUrl: \"http://localhost:3001\",\n        expectedBuildCommand: \"bun run build\",\n      },\n      {\n        frontend: [\"nuxt\"],\n        expectedDist: \"../.output/public\",\n        expectedUrl: \"http://localhost:3001\",\n        expectedBuildCommand: \"bun run generate\",\n      },\n      {\n        frontend: [\"astro\"],\n        expectedDist: \"../dist\",\n        expectedUrl: \"http://localhost:4321\",\n        expectedBuildCommand: \"bun run build\",\n      },\n      {\n        frontend: [\"react-router\"],\n        expectedDist: \"../build/client\",\n        expectedUrl: \"http://localhost:5173\",\n        expectedBuildCommand: \"bun run build\",\n      },\n      {\n        frontend: [\"solid\"],\n        expectedDist: \"../dist\",\n        expectedUrl: \"http://localhost:3001\",\n        expectedBuildCommand: \"bun run build\",\n      },\n    ] as const;\n\n    for (const testCase of cases) {\n      const args = buildTauriInitArgs({\n        packageManager: \"bun\",\n        frontend: [...testCase.frontend],\n        projectDir: \"/tmp/my app\",\n      });\n\n      expect(args).toContain(\"@tauri-apps/cli@latest\");\n      expect(args).toContain(\"--app-name\");\n      expect(args).toContain(\"my app\");\n      expect(args).toContain(\"--frontend-dist\");\n      expect(args).toContain(testCase.expectedDist);\n      expect(args).toContain(\"--dev-url\");\n      expect(args).toContain(testCase.expectedUrl);\n      expect(args).toContain(\"--before-dev-command\");\n      expect(args).toContain(\"bun run dev\");\n      expect(args).toContain(\"--before-build-command\");\n      expect(args).toContain(testCase.expectedBuildCommand);\n      expect(args.some((arg) => arg.startsWith(\"--app-name=\"))).toBe(false);\n      expect(args.some((arg) => arg.startsWith(\"--before-dev-command=\"))).toBe(false);\n      expect(args.some((arg) => arg.startsWith(\"--before-build-command=\"))).toBe(false);\n    }\n  });\n});\n"
  },
  {
    "path": "apps/cli/test/test-utils.ts",
    "content": "import { expect } from \"bun:test\";\nimport { mkdir } from \"node:fs/promises\";\nimport { join } from \"node:path\";\n\nimport { create, UserCancelledError, CLIError, ProjectCreationError } from \"../src/index\";\nimport type {\n  CreateInput,\n  InitResult,\n  Database,\n  ORM,\n  Backend,\n  Runtime,\n  Frontend,\n  Addons,\n  Examples,\n  Auth,\n  Payments,\n  API,\n  WebDeploy,\n  ServerDeploy,\n  DatabaseSetup,\n} from \"../src/types\";\nimport {\n  AddonsSchema,\n  APISchema,\n  AuthSchema,\n  BackendSchema,\n  DatabaseSchema,\n  DatabaseSetupSchema,\n  ExamplesSchema,\n  FrontendSchema,\n  ORMSchema,\n  PackageManagerSchema,\n  PaymentsSchema,\n  RuntimeSchema,\n  ServerDeploySchema,\n  WebDeploySchema,\n} from \"../src/types\";\n\n// Smoke directory path - use the same as setup.ts\nconst SMOKE_DIR_PATH = join(import.meta.dir, \"..\", \".smoke\");\n\nexport interface TestResult {\n  success: boolean;\n  result?: InitResult;\n  error?: string;\n  projectDir?: string;\n  config: TestConfig;\n}\n\nexport interface TestConfig extends CreateInput {\n  projectName?: string;\n  expectError?: boolean;\n  expectedErrorMessage?: string;\n}\n\n/**\n * Run test using the programmatic create() API instead of the router.\n * The create() API runs in silent mode and returns JSON instead of calling process.exit().\n */\nexport async function runTRPCTest(config: TestConfig): Promise<TestResult> {\n  // Ensure smoke directory exists (may be called before global setup in some cases)\n  try {\n    await mkdir(SMOKE_DIR_PATH, { recursive: true });\n  } catch {\n    // Directory may already exist\n  }\n\n  const projectName = config.projectName || \"default-app\";\n  const projectPath = join(SMOKE_DIR_PATH, projectName);\n\n  // Determine if we should use --yes or not\n  // Only core stack flags conflict with --yes flag (from CLI error message)\n  const coreStackFlags: (keyof TestConfig)[] = [\n    \"database\",\n    \"orm\",\n    \"backend\",\n    \"runtime\",\n    \"frontend\",\n    \"addons\",\n    \"examples\",\n    \"auth\",\n    \"payments\",\n    \"dbSetup\",\n    \"api\",\n    \"webDeploy\",\n    \"serverDeploy\",\n  ];\n  const hasSpecificCoreConfig = coreStackFlags.some((flag) => config[flag] !== undefined);\n\n  // Only use --yes if no core stack flags are provided and not explicitly disabled\n  const willUseYesFlag = config.yes !== undefined ? config.yes : !hasSpecificCoreConfig;\n\n  // Provide defaults for missing core stack options to avoid prompts\n  // But don't provide core stack defaults when yes: true is explicitly set\n  const coreStackDefaults = willUseYesFlag\n    ? {}\n    : {\n        frontend: [\"tanstack-router\"] as Frontend[],\n        backend: \"hono\" as Backend,\n        runtime: \"bun\" as Runtime,\n        api: \"trpc\" as API,\n        database: \"sqlite\" as Database,\n        orm: \"drizzle\" as ORM,\n        auth: \"none\" as Auth,\n        payments: \"none\" as Payments,\n        addons: [\"none\"] as Addons[],\n        examples: [\"none\"] as Examples[],\n        dbSetup: \"none\" as DatabaseSetup,\n        webDeploy: \"none\" as WebDeploy,\n        serverDeploy: \"none\" as ServerDeploy,\n      };\n\n  // Build options object - let the CLI handle all validation\n  // Remove test-specific properties before passing to create()\n  const { projectName: _, expectError: __, expectedErrorMessage: ___, ...restConfig } = config;\n\n  const options: Partial<CreateInput> = {\n    install: config.install ?? false,\n    git: config.git ?? true,\n    packageManager: config.packageManager ?? \"bun\",\n    directoryConflict: \"overwrite\",\n    disableAnalytics: true,\n    yes: willUseYesFlag,\n    ...coreStackDefaults,\n    ...restConfig,\n  };\n\n  // Use the programmatic create() API which runs in silent mode\n  // and returns a Result type instead of calling process.exit()\n  const result = await create(projectPath, options);\n\n  // Handle the Result type from better-result\n  if (result.isOk()) {\n    const initResult = result.value;\n    return {\n      success: true,\n      result: initResult,\n      error: undefined,\n      projectDir: initResult.projectDirectory,\n      config,\n    };\n  }\n\n  // Handle error case - extract error message based on error type\n  const error = result.error;\n  let errorMessage: string;\n  if (UserCancelledError.is(error)) {\n    errorMessage = error.message || \"User cancelled\";\n  } else if (CLIError.is(error)) {\n    errorMessage = error.message;\n  } else if (ProjectCreationError.is(error)) {\n    errorMessage = error.message;\n  } else {\n    errorMessage = String(error);\n  }\n\n  return {\n    success: false,\n    result: undefined,\n    error: errorMessage,\n    projectDir: undefined,\n    config,\n  };\n}\n\nexport function expectSuccess(result: TestResult) {\n  if (!result.success) {\n    console.error(\"Test failed:\");\n    console.error(\"Error:\", result.error);\n    if (result.result) {\n      console.error(\"Result:\", result.result);\n    }\n  }\n  expect(result.success).toBe(true);\n  expect(result.result).toBeDefined();\n}\n\nexport function expectError(result: TestResult, expectedMessage?: string) {\n  expect(result.success).toBe(false);\n  if (expectedMessage) {\n    expect(result.error).toContain(expectedMessage);\n  }\n}\n\n// Helper function to create properly typed test configs\nexport function createTestConfig(\n  config: Partial<TestConfig> & { projectName: string },\n): TestConfig {\n  return config as TestConfig;\n}\n\n/**\n * Extract enum values from a Zod enum schema\n */\nfunction extractEnumValues<T extends string>(schema: { options: readonly T[] }): readonly T[] {\n  return schema.options;\n}\n\n// Test data generators inferred from Zod schemas\nexport const PACKAGE_MANAGERS = extractEnumValues(PackageManagerSchema);\nexport const DATABASES = extractEnumValues(DatabaseSchema);\nexport const ORMS = extractEnumValues(ORMSchema);\nexport const BACKENDS = extractEnumValues(BackendSchema);\nexport const RUNTIMES = extractEnumValues(RuntimeSchema);\nexport const FRONTENDS = extractEnumValues(FrontendSchema);\nexport const ADDONS = extractEnumValues(AddonsSchema);\nexport const EXAMPLES = extractEnumValues(ExamplesSchema);\nexport const AUTH_PROVIDERS = extractEnumValues(AuthSchema);\nexport const PAYMENTS_PROVIDERS = extractEnumValues(PaymentsSchema);\nexport const API_TYPES = extractEnumValues(APISchema);\nexport const WEB_DEPLOYS = extractEnumValues(WebDeploySchema);\nexport const SERVER_DEPLOYS = extractEnumValues(ServerDeploySchema);\nexport const DB_SETUPS = extractEnumValues(DatabaseSetupSchema);\n\n// Convenience functions for common test patterns\nexport function createBasicConfig(overrides: Partial<TestConfig> = {}): TestConfig {\n  return {\n    projectName: \"test-app\",\n    yes: true, // Use defaults\n    install: false,\n    git: true,\n    ...overrides,\n  };\n}\n\nexport function createCustomConfig(config: Partial<TestConfig>): TestConfig {\n  return {\n    projectName: \"test-app\",\n    install: false,\n    git: true,\n    ...config,\n  };\n}\n"
  },
  {
    "path": "apps/cli/test/tui-setup.test.ts",
    "content": "import { describe, expect, it } from \"bun:test\";\nimport { join } from \"node:path\";\n\nimport fs from \"fs-extra\";\n\nimport { postProcessTuiWorkspace, resolveTuiTemplate } from \"../src/helpers/addons/tui-setup\";\nimport type { ProjectConfig } from \"../src/types\";\nimport { runWithContextAsync } from \"../src/utils/context\";\nimport { SMOKE_DIR } from \"./setup\";\n\nfunction createTuiConfig(overrides: Partial<ProjectConfig> = {}): ProjectConfig {\n  return {\n    projectName: \"test-app\",\n    projectDir: SMOKE_DIR,\n    relativePath: \"test-app\",\n    database: \"sqlite\",\n    orm: \"drizzle\",\n    backend: \"hono\",\n    runtime: \"bun\",\n    frontend: [\"tanstack-router\"],\n    addons: [\"opentui\"],\n    examples: [\"none\"],\n    auth: \"none\",\n    payments: \"none\",\n    git: true,\n    packageManager: \"bun\",\n    install: false,\n    dbSetup: \"none\",\n    api: \"trpc\",\n    webDeploy: \"none\",\n    serverDeploy: \"none\",\n    ...overrides,\n  };\n}\n\ndescribe(\"OpenTUI setup\", () => {\n  it(\"defaults to the core template in silent mode\", async () => {\n    const template = await runWithContextAsync({ silent: true }, async () =>\n      resolveTuiTemplate(createTuiConfig()),\n    );\n\n    expect(template).toBe(\"core\");\n  });\n\n  it(\"uses persisted addon options before falling back to the silent default\", async () => {\n    const template = await runWithContextAsync({ silent: true }, async () =>\n      resolveTuiTemplate(\n        createTuiConfig({\n          addonOptions: {\n            opentui: {\n              template: \"react\",\n            },\n          },\n        }),\n      ),\n    );\n\n    expect(template).toBe(\"react\");\n  });\n\n  it(\"injects check-types and removes nested lockfiles during post-processing\", async () => {\n    const tuiDir = join(SMOKE_DIR, \"tui-post-process\");\n    await fs.ensureDir(tuiDir);\n    await fs.writeJson(join(tuiDir, \"package.json\"), {\n      name: \"tui\",\n      scripts: {\n        dev: \"opentui dev\",\n      },\n    });\n\n    await fs.writeFile(join(tuiDir, \"bun.lock\"), \"\");\n    await fs.writeFile(join(tuiDir, \"pnpm-lock.yaml\"), \"\");\n\n    const result = await postProcessTuiWorkspace(tuiDir);\n\n    expect(result.isOk()).toBe(true);\n\n    const packageJson = await fs.readJson(join(tuiDir, \"package.json\"));\n    expect(packageJson.scripts[\"check-types\"]).toBe(\"tsc --noEmit\");\n    expect(await fs.pathExists(join(tuiDir, \"bun.lock\"))).toBe(false);\n    expect(await fs.pathExists(join(tuiDir, \"pnpm-lock.yaml\"))).toBe(false);\n  });\n});\n"
  },
  {
    "path": "apps/cli/test/ultracite-setup.test.ts",
    "content": "import { describe, expect, it } from \"bun:test\";\n\nimport { buildUltraciteInitArgs } from \"../src/helpers/addons/ultracite-setup\";\n\ndescribe(\"Ultracite setup\", () => {\n  it(\"omits optional flags when editors, agents, and hooks are empty\", () => {\n    const args = buildUltraciteInitArgs({\n      packageManager: \"bun\",\n      linter: \"biome\",\n      frameworks: [\"react\"],\n      editors: [],\n      agents: [],\n      hooks: [],\n      gitHooks: [],\n    });\n\n    expect(args).toContain(\"--frameworks\");\n    expect(args).not.toContain(\"--editors\");\n    expect(args).not.toContain(\"--agents\");\n    expect(args).not.toContain(\"--hooks\");\n    expect(args).toContain(\"--skip-install\");\n    expect(args).toContain(\"--quiet\");\n  });\n\n  it(\"passes integrations as separate values without implicit additions\", () => {\n    const args = buildUltraciteInitArgs({\n      packageManager: \"bun\",\n      linter: \"biome\",\n      frameworks: [\"react\"],\n      editors: [],\n      agents: [],\n      hooks: [],\n      gitHooks: [\"husky\", \"lefthook\"],\n    });\n\n    const integrationsIndex = args.indexOf(\"--integrations\");\n\n    expect(integrationsIndex).toBeGreaterThan(-1);\n    expect(args.slice(integrationsIndex + 1, integrationsIndex + 3)).toEqual([\"husky\", \"lefthook\"]);\n    // Matches upstream: lint-staged is only added when explicitly selected.\n    expect(args).not.toContain(\"lint-staged\");\n    expect(args).not.toContain(\"husky lefthook\");\n  });\n});\n"
  },
  {
    "path": "apps/cli/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ES2022\",\n    \"module\": \"ESNext\",\n    \"moduleResolution\": \"bundler\",\n    \"esModuleInterop\": true,\n    \"verbatimModuleSyntax\": true,\n    \"strict\": true,\n    \"skipLibCheck\": true,\n    \"outDir\": \"dist\",\n    \"types\": [\"node\"]\n  },\n  \"include\": [\"src/**/*.ts\"],\n  \"exclude\": [\"node_modules\", \"**/*.test.ts\", \"**/*.spec.ts\"]\n}\n"
  },
  {
    "path": "apps/cli/tsdown.config.ts",
    "content": "import { defineConfig } from \"tsdown\";\n\nexport default defineConfig({\n  entry: [\"src/index.ts\", \"src/cli.ts\", \"src/virtual.ts\"],\n  format: [\"esm\"],\n  clean: true,\n  shims: true,\n  outDir: \"dist\",\n  dts: true,\n  outputOptions: {\n    banner: \"#!/usr/bin/env node\",\n  },\n  env: {\n    BTS_TELEMETRY: process.env.BTS_TELEMETRY || \"0\",\n    CONVEX_INGEST_URL: process.env.CONVEX_INGEST_URL || \"\",\n  },\n});\n"
  },
  {
    "path": "apps/web/.eslintrc.json",
    "content": "{\n  \"extends\": [\"next/core-web-vitals\", \"next/typescript\"]\n}\n"
  },
  {
    "path": "apps/web/.gitignore",
    "content": "# deps\n/node_modules\n\n# generated content\n.contentlayer\n.content-collections\n.source\npublic/analytics-minimal.json\n\n# test & build\n/coverage\n/.next/\n/out/\n/build\n*.tsbuildinfo\n/.open-next/\n/.wrangler/\n.alchemy\n\n# misc\n.DS_Store\n*.pem\n/.pnp\n.pnp.js\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\n\n# others\n.env*.local\n.vercel\nnext-env.d.ts\n.dev.vars\n.dev.vars.prod\n"
  },
  {
    "path": "apps/web/.vercelignore",
    "content": "apps/web/scripts/"
  },
  {
    "path": "apps/web/README.md",
    "content": "# Better-T-Stack Website\n\nThis is the official documentation website for Better-T-Stack, built with Next.js and Fumadocs.\n\n## Getting Started\n\nTo run the development server:\n\n```bash\n# Install dependencies\nnpm install\n# or\npnpm install\n# or\nbun install\n\n# Start development server\nnpm run dev\n# or\npnpm dev\n# or\nbun dev\n```\n\nOpen [http://localhost:3333](http://localhost:3333) with your browser to see the site.\n\n## Project Structure\n\n- `/src/app` - Next.js application routes\n- `/content/docs` - Documentation content in MDX format\n- `/public` - Static assets\n\n## Contributing to Documentation\n\nTo add or modify documentation:\n\n1. Edit the appropriate MDX files in the `content/docs` directory\n2. Run the development server to preview your changes\n3. Submit a pull request with your updates\n\n## Learn More\n\nTo learn more about the technologies used in this website:\n\n- [Next.js Documentation](https://nextjs.org/docs) - Next.js features and API\n- [Fumadocs](https://fumadocs.vercel.app) - The documentation framework used\n- [Better-T-Stack](https://better-t-stack.dev) - Main project site\n"
  },
  {
    "path": "apps/web/cli.json",
    "content": "{\n  \"uiLibrary\": \"base-ui\"\n}\n"
  },
  {
    "path": "apps/web/components.json",
    "content": "{\n  \"$schema\": \"https://ui.shadcn.com/schema.json\",\n  \"style\": \"base-lyra\",\n  \"rsc\": true,\n  \"tsx\": true,\n  \"tailwind\": {\n    \"config\": \"\",\n    \"css\": \"src/app/global.css\",\n    \"baseColor\": \"neutral\",\n    \"cssVariables\": true,\n    \"prefix\": \"\"\n  },\n  \"iconLibrary\": \"lucide\",\n  \"menuColor\": \"default\",\n  \"menuAccent\": \"subtle\",\n  \"aliases\": {\n    \"components\": \"@/components\",\n    \"utils\": \"@/lib/utils\",\n    \"ui\": \"@/components/ui\",\n    \"lib\": \"@/lib\",\n    \"hooks\": \"@/hooks\"\n  },\n  \"registries\": {\n    \"@kibo-ui\": \"https://www.kibo-ui.com/r/{name}.json\",\n    \"@magicui\": \"https://magicui.design/r/{name}.json\",\n    \"@evilcharts\": \"https://evilcharts.com/r/{name}.json\"\n  }\n}\n"
  },
  {
    "path": "apps/web/content/docs/analytics.mdx",
    "content": "---\ntitle: Analytics & Telemetry\ndescription: What we collect, how to disable it, and where to view aggregated insights\n---\n\n## What is collected\n\nOn successful project creation, the CLI sends a single event (`project_created`) with:\n\n- Selected options (stack choices), including: `frontend`, `backend`, `runtime`, `database`, `orm`, `api`, `auth`, `payments`, `addons`, `examples`, `dbSetup`, `webDeploy`, `serverDeploy`, `packageManager`, `git`, `install`\n- Environment data: `cli_version`, `node_version`, `platform`\n\nNot collected:\n\n- Project name, path, or file contents (explicitly omitted)\n- Secrets or environment variables from your machine\n\n## Disable telemetry\n\nTelemetry is enabled by default. To disable:\n\n```bash\nBTS_TELEMETRY_DISABLED=1 npx create-better-t-stack@latest\n```\n\n<Callout title=\"Note\">The above command disables it for a single run.</Callout>\n\nAdd `export BTS_TELEMETRY_DISABLED=1` to your shell profile to make it permanent.\n\n## Where to view analytics\n\n- Charts: [`/analytics`](/analytics)\n- Shared website analytics: [Umami dashboard](https://umami.amanv.cloud/share/pHvqHleyOl9PBfaK) (self-hosted on a Hostinger VPS)\n- Raw JSON snapshot: `https://r2.better-t-stack.dev/analytics-data.json`\n- CSV export: `https://r2.better-t-stack.dev/export.csv`\n\nNotes:\n\n- Aggregates are periodically regenerated from incoming events\n- Raw data is not publicly exposed; the `/analytics` page presents only summary statistics\n\n## Full transparency\n\nSingle event per scaffold; no IP or project identifiers. See source code below.\n\nIf in doubt, set `BTS_TELEMETRY_DISABLED=1` and proceed. You can still use all CLI features.\n\n## Source code\n\n- CLI event sender: [`apps/cli/src/utils/analytics.ts`](https://github.com/AmanVarshney01/create-better-t-stack/blob/main/apps/cli/src/utils/analytics.ts)\n- Telemetry toggle logic: [`apps/cli/src/utils/telemetry.ts`](https://github.com/AmanVarshney01/create-better-t-stack/blob/main/apps/cli/src/utils/telemetry.ts)\n- Ingest endpoint: [`packages/backend/convex/http.ts`](https://github.com/AmanVarshney01/create-better-t-stack/blob/main/packages/backend/convex/http.ts)\n- Analytics backend (ingest + aggregation): [`packages/backend/convex/analytics.ts`](https://github.com/AmanVarshney01/create-better-t-stack/blob/main/packages/backend/convex/analytics.ts)\n"
  },
  {
    "path": "apps/web/content/docs/bts-config.mdx",
    "content": "---\ntitle: bts.jsonc\ndescription: What bts.jsonc does and why it matters\n---\n\n## What is it?\n\n`bts.jsonc` is a small config file written to your project root when you create a project. It captures the stack choices you selected (frontend, backend, API, DB/ORM, auth, addons, etc.). The file uses JSONC (JSON with comments) and includes a schema for editor hints.\n\nWhere: `./bts.jsonc`\n\n## Why it exists\n\n- Required for the `add` command to detect your current stack\n- Helps validate compatibility and pre‑fill sensible defaults\n\nIf `bts.jsonc` is missing, the `add` command cannot run because the project cannot be detected.\n\n## Safe to delete (with a caveat)\n\nIt’s safe to delete for normal development; the generated code in `apps/*` and `packages/*` remains the source of truth. However, if you plan to use the `add` command later, you must keep `bts.jsonc` (or recreate it) so the CLI can detect your project.\n\n## Format\n\nThe file is JSONC with comments enabled and includes a `$schema` URL for tooling.\n\n```jsonc\n// Better-T-Stack configuration file\n// safe to delete\n{\n  \"$schema\": \"https://r2.better-t-stack.dev/schema.json\",\n  \"version\": \"x.y.z\",\n  \"createdAt\": \"2025-01-01T00:00:00.000Z\",\n  \"reproducibleCommand\": \"bun create better-t-stack@latest create-json --input '{...}'\",\n  \"frontend\": [\"tanstack-router\"],\n  \"backend\": \"hono\",\n  \"runtime\": \"bun\",\n  \"database\": \"sqlite\",\n  \"orm\": \"drizzle\",\n  \"api\": \"trpc\",\n  \"auth\": \"better-auth\",\n  \"addons\": [\"turborepo\"],\n  \"addonOptions\": {\n    \"wxt\": {\n      \"template\": \"react\",\n    },\n  },\n  \"examples\": [],\n  \"dbSetup\": \"none\",\n  \"dbSetupOptions\": {\n    \"mode\": \"manual\",\n  },\n  \"webDeploy\": \"none\",\n  \"serverDeploy\": \"none\",\n  \"packageManager\": \"bun\",\n}\n```\n\nNotes:\n\n- Values mirror what you selected during project creation\n- `reproducibleCommand` contains the exact command to recreate this project\n- When structured options are present, the reproducible command uses `create-json`\n- `addonOptions` stores nested configuration for prompt-driven addons\n- `dbSetupOptions` stores structured database setup behavior for automation\n- The file may be updated when you run `add` (addons and addon-specific config)\n\nSee also: [`add` command](/docs/cli#add)\n"
  },
  {
    "path": "apps/web/content/docs/cli/agent-workflows.mdx",
    "content": "---\ntitle: Agent Workflows\ndescription: JSON-first and schema-driven workflows for agents, scripts, and automation\n---\n\n## Overview\n\nBetter-T-Stack now supports a fully JSON-first workflow for agents and automation:\n\n- Raw JSON project creation with `create-json`\n- Raw JSON addon installation with `add-json`\n- Runtime schema introspection with `schema`\n- Local stdio MCP server with `mcp`\n- Structured addon configuration with `addonOptions`\n- Structured database setup configuration with `dbSetupOptions`\n- Safe planning with `--dry-run`\n\nIf you are automating the CLI from an LLM, CI job, or another tool, start here instead of the interactive prompt flow.\n\n## `create-json`\n\nCreate a project from a single JSON payload instead of many flags.\n\n```bash\ncreate-better-t-stack create-json --input '{\n  \"projectName\": \"my-app\",\n  \"frontend\": [\"tanstack-router\"],\n  \"backend\": \"hono\",\n  \"runtime\": \"bun\",\n  \"database\": \"postgres\",\n  \"orm\": \"drizzle\",\n  \"api\": \"trpc\",\n  \"auth\": \"none\",\n  \"addons\": [\"wxt\", \"mcp\"],\n  \"addonOptions\": {\n    \"wxt\": { \"template\": \"react\", \"devPort\": 5555 },\n    \"mcp\": {\n      \"scope\": \"project\",\n      \"servers\": [\"context7\"],\n      \"agents\": [\"cursor\"]\n    }\n  },\n  \"install\": false\n}'\n```\n\nThis is the best path when:\n\n- Your input already exists as structured data\n- You want to avoid shell flag expansion issues\n- You need nested addon or database setup options\n\n## `add-json`\n\nAdd addons to an existing project with a single JSON payload.\n\n```bash\ncreate-better-t-stack add-json --input '{\n  \"projectDir\": \"./my-app\",\n  \"addons\": [\"skills\", \"ultracite\"],\n  \"addonOptions\": {\n    \"skills\": {\n      \"scope\": \"project\",\n      \"agents\": [\"cursor\", \"codex\"],\n      \"selections\": [\n        {\n          \"source\": \"vercel-labs/agent-skills\",\n          \"skills\": [\"web-design-guidelines\"]\n        },\n        {\n          \"source\": \"https://www.evlog.dev\",\n          \"skills\": [\"review-logging-patterns\", \"analyze-logs\"]\n        }\n      ]\n    },\n    \"ultracite\": {\n      \"linter\": \"biome\",\n      \"editors\": [\"vscode\", \"cursor\"],\n      \"agents\": [\"claude\", \"codex\"]\n    }\n  },\n  \"dryRun\": true\n}'\n```\n\n## Runtime Schema Introspection\n\nUse the CLI itself as the source of truth for current input shapes.\n\n```bash\ncreate-better-t-stack schema --name all\ncreate-better-t-stack schema --name cli\ncreate-better-t-stack schema --name createInput\ncreate-better-t-stack schema --name addInput\ncreate-better-t-stack schema --name addonOptions\ncreate-better-t-stack schema --name dbSetupOptions\n```\n\nUseful patterns:\n\n- `schema --name cli`: inspect available commands\n- `schema --name createInput`: inspect the full create payload\n- `schema --name addonOptions`: inspect nested addon configuration\n- `schema --name dbSetupOptions`: inspect structured database setup behavior\n\n## `mcp`\n\nRun Better-T-Stack itself as a local MCP server over stdio:\n\n```bash\nnpx create-better-t-stack@latest mcp\n```\n\nInstall it into supported agent configs with `add-mcp`:\n\n```bash\nnpx -y add-mcp@latest \"npx -y create-better-t-stack@latest mcp\"\n```\n\nExposed tools:\n\n- `bts_get_stack_guidance`\n- `bts_get_schema`\n- `bts_plan_project`\n- `bts_create_project`\n- `bts_plan_addons`\n- `bts_add_addons`\n\nThis is the best path when an MCP-capable client should scaffold or modify Better-T-Stack projects without shelling out to a prompt-driven CLI flow.\n\nRecommended MCP workflow:\n\n1. Call `bts_get_stack_guidance` if the user's request is ambiguous.\n2. Call `bts_get_schema` for the exact input shape you need.\n3. Build a full explicit stack config.\n4. Call `bts_plan_project` before `bts_create_project`.\n5. For MCP execution, use `install: false` when creating projects.\n6. Call `bts_plan_addons` before `bts_add_addons`.\n\nFor project creation, the MCP tools now expect a full explicit config, not a partial payload. That means the agent should provide all major stack choices such as:\n\n- `frontend`\n- `backend`\n- `runtime`\n- `database`\n- `orm`\n- `api`\n- `auth`\n- `payments`\n- `addons`\n- `examples`\n- `dbSetup`\n- `webDeploy`\n- `serverDeploy`\n- `git`\n- `packageManager`\n- `install`\n\nImportant field rules:\n\n- `frontend` means app surfaces, not styling choices.\n- `addons` must be an explicit array. Use `[]` when none are requested.\n- `examples` must be an explicit array. Use `[]` when none are requested.\n- `dbSetup`, `webDeploy`, and `serverDeploy` should be explicit even when the answer is `none`.\n- `git`, `install`, and `packageManager` should always be set explicitly.\n- `install` should usually be `false` for `bts_create_project`, because dependency installation can exceed common MCP client timeouts. Run `npm install`, `pnpm install`, or `bun install` separately after scaffolding.\n- If a request is still ambiguous after reading the guidance, the agent should resolve that ambiguity before calling `bts_plan_project`.\n\nIf you scaffold a project with the `mcp` addon, Better-T-Stack itself is now one of the recommended MCP servers. The addon installs it through `add-mcp` with a package runner command so the generated config does not rely on a globally installed CLI:\n\n```bash\nnpx -y add-mcp@latest \"npx -y create-better-t-stack@latest mcp\"\n```\n\nFor Bun projects, the generated config uses the equivalent `bunx create-better-t-stack@latest mcp` server command inside `add-mcp`. That means MCP-capable agents inside the generated project can talk back to Better-T-Stack without any extra global install, alongside other recommended docs, framework, and database MCP servers.\n\n## Structured Addon Options\n\nPrompt-driven addons can be configured up front through `addonOptions`.\n\nSupported structured addon surfaces include:\n\n- `wxt`\n- `fumadocs`\n- `opentui`\n- `mcp`\n- `skills`\n- `ultracite`\n\nWhen `skills` is used with the `evlog` addon, Better-T-Stack recommends Evlog's agent skills from `https://www.evlog.dev`: `review-logging-patterns` and `analyze-logs`.\n\nExample:\n\n```json\n{\n  \"addons\": [\"wxt\"],\n  \"addonOptions\": {\n    \"wxt\": {\n      \"template\": \"react\",\n      \"devPort\": 5555\n    }\n  }\n}\n```\n\nStructured addon options are still persisted in `bts.jsonc`, but the printed reproducible command now stays on normal CLI flags for consistency. That means deeply nested addon or provider-specific setup options are not fully encoded in the one-line replay command.\n\n## Structured Database Setup Options\n\n`dbSetupOptions` controls how database provisioning behaves in automation.\n\nExample:\n\n```bash\ncreate-better-t-stack create-json --input '{\n  \"projectName\": \"db-app\",\n  \"database\": \"postgres\",\n  \"orm\": \"drizzle\",\n  \"backend\": \"hono\",\n  \"runtime\": \"bun\",\n  \"api\": \"trpc\",\n  \"frontend\": [\"tanstack-router\"],\n  \"dbSetup\": \"neon\",\n  \"dbSetupOptions\": {\n    \"mode\": \"manual\"\n  }\n}'\n```\n\nCurrent behavior:\n\n- Providers with automatic cloud provisioning default to `manual` in silent/agent flows:\n  - `turso`\n  - `neon`\n  - `prisma-postgres`\n  - `supabase`\n  - `mongodb-atlas`\n- Providers that only write local/manual config today are not forced to `manual`:\n  - `d1`\n  - `docker`\n  - `planetscale`\n\nThis keeps agents from accidentally creating cloud resources unless automation explicitly opts into `auto`, while leaving local or manual-only setups unchanged.\n\nProvider-specific options are intentionally small today:\n\n- `neon.method`, `neon.projectName`, `neon.regionId`\n- `prismaPostgres.regionId`\n- `turso.databaseName`, `turso.groupName`, `turso.installCli`\n\n## Dry Runs\n\nUse `--dry-run` to validate inputs and target directories without writing files.\n\n```bash\ncreate-better-t-stack --yes --dry-run\ncreate-better-t-stack create-json --input '{\"projectName\":\"my-app\",\"yes\":true,\"dryRun\":true}'\ncreate-better-t-stack add-json --input '{\"projectDir\":\"./my-app\",\"addons\":[\"mcp\"],\"dryRun\":true}'\n```\n\nThis is especially useful for:\n\n- Planning actions before mutation\n- CI validation\n- Agent reasoning loops\n\n## Programmatic API\n\nThe exported `create()` and `add()` APIs run in silent mode by default, so they benefit from the same agent-safe behavior as the JSON commands.\n\nSee [Programmatic API](/docs/cli/programmatic-api) for TypeScript examples.\n\n## Stored in `bts.jsonc`\n\nBetter-T-Stack persists structured configuration in `bts.jsonc`, including:\n\n- `reproducibleCommand`\n- `addonOptions`\n- `dbSetupOptions`\n\nSee [`bts.jsonc`](/docs/bts-config) for the file format.\n"
  },
  {
    "path": "apps/web/content/docs/cli/compatibility.mdx",
    "content": "---\ntitle: Compatibility Rules\ndescription: Understanding compatibility rules and restrictions between different CLI options\n---\n\n## Overview\n\nThe CLI validates option combinations to ensure generated projects work correctly. Here are the key compatibility rules and restrictions.\n\n## Database & ORM Compatibility\n\n### Required Combinations\n\n| Database   | Compatible ORMs      | Notes                                     |\n| ---------- | -------------------- | ----------------------------------------- |\n| `sqlite`   | `drizzle`, `prisma`  | Lightweight, file-based database          |\n| `postgres` | `drizzle`, `prisma`  | Advanced relational database              |\n| `mysql`    | `drizzle`, `prisma`  | Traditional relational database           |\n| `mongodb`  | `mongoose`, `prisma` | Document database, requires specific ORMs |\n| `none`     | `none`               | No database setup                         |\n\n### Restrictions\n\n- **MongoDB + Drizzle**: ❌ Not supported - Drizzle doesn't support MongoDB\n- **Database without ORM**: ❌ Not supported - Database requires an ORM for code generation\n- **ORM without Database**: ❌ Not supported - ORM requires a database target\n\n```bash\n# ❌ Invalid - MongoDB with Drizzle\ncreate-better-t-stack --database mongodb --orm drizzle\n\n# ✅ Valid - MongoDB with Mongoose\ncreate-better-t-stack --database mongodb --orm mongoose\n```\n\n## Backend & Runtime Compatibility\n\n### Cloudflare Workers Restrictions\n\nCloudflare Workers has specific compatibility requirements:\n\n| Component      | Requirement                              | Reason                                                         |\n| -------------- | ---------------------------------------- | -------------------------------------------------------------- |\n| Backend        | Must be `hono`                           | Only Hono supports Workers runtime                             |\n| ORM            | If DB is used, use `drizzle` or `prisma` | Mongoose is MongoDB-only and MongoDB is not workers-compatible |\n| Database       | Cannot be `mongodb`                      | MongoDB is not compatible with Workers runtime                 |\n| Database Setup | Cannot be `docker`                       | Workers is serverless, no Docker support                       |\n\n```bash\n# ❌ Invalid - Workers with Express\ncreate-better-t-stack --runtime workers --backend express\n\n# ✅ Valid - Workers with Hono\ncreate-better-t-stack --runtime workers --backend hono --database sqlite --orm drizzle --db-setup d1\n\n# ✅ Also valid - Workers with Prisma (D1)\ncreate-better-t-stack --runtime workers --backend hono --database sqlite --orm prisma --db-setup d1\n```\n\n### Backend Presets\n\n#### Convex Backend\n\nWhen using `--backend convex`, these constraints apply:\n\n- `--runtime none`\n- `--database none` (Convex provides database)\n- `--orm none` (Convex provides data layer)\n- `--api none` (Convex provides API)\n- `--db-setup none` (Convex manages hosting)\n- `--server-deploy none`\n- Auth can be `better-auth`, `clerk`, or `none` depending frontend compatibility\n\n**Note:** Convex supports Clerk authentication with compatible frontends (React frameworks, Next.js, TanStack Start, and native frameworks). Nuxt, Svelte, Solid, and Astro are not compatible with Clerk.\n\n**Better Auth with Convex:** supported frontends are `react-router`, `tanstack-router`, `tanstack-start`, `next`, `native-bare`, `native-uniwind`, and `native-unistyles`. Nuxt, Svelte, Solid, and Astro are not supported with Convex Better Auth.\n\n#### No Backend\n\nWhen using `--backend none`, the following options are automatically set:\n\n- `--auth none` (No backend for auth)\n- `--database none` (No backend for database)\n- `--orm none` (No database)\n- `--api none` (No backend for API)\n- `--runtime none` (No backend to run)\n- `--db-setup none` (No database to host)\n- `--examples none` (Examples require backend)\n\n## Frontend & API Compatibility\n\n### API Framework Support\n\n| Frontend          | tRPC Support | oRPC Support | Notes              |\n| ----------------- | ------------ | ------------ | ------------------ |\n| `tanstack-router` | ✅           | ✅           | Full support       |\n| `react-router`    | ✅           | ✅           | Full support       |\n| `tanstack-start`  | ✅           | ✅           | Full support       |\n| `next`            | ✅           | ✅           | Full support       |\n| `nuxt`            | ❌           | ✅           | tRPC not supported |\n| `svelte`          | ❌           | ✅           | tRPC not supported |\n| `solid`           | ❌           | ✅           | tRPC not supported |\n| `astro`           | ❌           | ✅           | tRPC not supported |\n| Native frameworks | ✅           | ✅           | Full support       |\n\n```bash\n# ❌ Invalid - Nuxt with tRPC\ncreate-better-t-stack --frontend nuxt --api trpc\n\n# ✅ Valid - Nuxt with oRPC\ncreate-better-t-stack --frontend nuxt --api orpc\n```\n\n### Frontend Restrictions\n\n- **Multiple Web Frontends**: ❌ Only one web framework allowed\n- **Multiple Native Frontends**: ❌ Only one native framework allowed\n- **Web + Native**: ✅ One web and one native framework allowed\n\n```bash\n# ❌ Invalid - Multiple web frontends\ncreate-better-t-stack --frontend next tanstack-router\n\n# ✅ Valid - Web + native\ncreate-better-t-stack --frontend next native-uniwind\n```\n\n## Database Setup Compatibility\n\n### Provider Requirements\n\n| Setup Provider    | Required Database              | Notes                                                                                                            |\n| ----------------- | ------------------------------ | ---------------------------------------------------------------------------------------------------------------- |\n| `turso`           | `sqlite`                       | Distributed SQLite; works with Drizzle and Prisma                                                                |\n| `d1`              | `sqlite`                       | Cloudflare D1; works with Drizzle and Prisma on Cloudflare Workers or supported self-hosted Cloudflare frontends |\n| `neon`            | `postgres`                     | Serverless PostgreSQL                                                                                            |\n| `supabase`        | `postgres`                     | PostgreSQL with additional features                                                                              |\n| `prisma-postgres` | `postgres`                     | Managed PostgreSQL via Prisma                                                                                    |\n| `planetscale`     | `mysql`, `postgres`            | PlanetScale serverless database                                                                                  |\n| `mongodb-atlas`   | `mongodb`                      | Managed MongoDB                                                                                                  |\n| `docker`          | `postgres`, `mysql`, `mongodb` | Not compatible with `sqlite` or Workers                                                                          |\n\n### Special Cases\n\n#### Cloudflare D1\n\n- Requires `--database sqlite`\n- Requires one of these Cloudflare deployment targets:\n  - `--backend hono --runtime workers --server-deploy cloudflare`\n  - `--backend self --web-deploy cloudflare`\n- With `--backend self`, D1 is supported on `next`, `tanstack-start`, `nuxt`, `svelte`, and `astro`\n- With `--backend self`, frontend API compatibility still applies: `nuxt`, `svelte`, and `astro` require `--api orpc` or `--api none`\n\n#### Docker Setup\n\n- Cannot be used with `sqlite` (file-based database)\n- Cannot be used with `workers` runtime (serverless environment)\n\n## Addon Compatibility\n\n### PWA Support\n\n- Requires web frontend\n- Compatible frontends: `tanstack-router`, `react-router`, `next`, `solid`\n- Not compatible with native-only projects\n\n### Tauri (Desktop Apps)\n\n- Requires web frontend\n- Compatible frontends: `tanstack-router`, `react-router`, `tanstack-start`, `next`, `nuxt`, `svelte`, `solid`, `astro`\n- Desktop builds package static web output, so `tanstack-start`, `next`, `nuxt`, `svelte`, and `astro` need static/export configuration before packaging\n- Cannot be combined with native frameworks\n\n### Electrobun (Desktop Apps)\n\n- Requires web frontend\n- Compatible frontends: `tanstack-router`, `react-router`, `tanstack-start`, `next`, `nuxt`, `svelte`, `solid`, `astro`\n- Uses a generated `apps/desktop` shell that loads `apps/web` during development and bundles its static build output for distribution\n- Desktop builds package static web output, so `tanstack-start`, `next`, `nuxt`, `svelte`, and `astro` need static/export configuration before packaging\n\n### Web Deployment\n\n- `--web-deploy cloudflare` requires a web frontend\n- Cannot be used with native-only projects\n\n## Authentication Requirements\n\n### Better-Auth Requirements\n\nBetter-Auth authentication requires:\n\n- A backend framework (cannot be `none`)\n- With database: Requires an ORM\n- Without database: Works with Convex backend or custom configuration\n- With `--backend convex`: requires `react-router`, `tanstack-router`, `tanstack-start`, `next`, or a native Expo frontend\n\n### Clerk Requirements\n\nClerk authentication requires:\n\n- Compatible frontends (React frameworks, Next.js, TanStack Start, native frameworks)\n- Supported backends: Convex, Hono, Express, Fastify, and Elysia\n- Fullstack (`--backend self`) support with Next.js or TanStack Start\n- Not compatible with Nuxt, Svelte, Solid, or Astro\n\n### Payments Requirements\n\nPolar payments require:\n\n- Better-Auth authentication\n- A web frontend (or no frontend selected)\n\n```bash\n# ✅ Valid - Better-Auth without database\ncreate-better-t-stack --auth better-auth --database none\n\n# ✅ Valid - Better-Auth with full stack\ncreate-better-t-stack --auth better-auth --database postgres --orm drizzle --backend hono\n\n# ✅ Valid - Clerk with Hono\ncreate-better-t-stack --auth clerk --backend hono --frontend tanstack-router\n\n# ✅ Valid - Clerk with fullstack Next.js\ncreate-better-t-stack --auth clerk --backend self --frontend next\n\n# ❌ Invalid - Clerk with Astro\ncreate-better-t-stack --auth clerk --backend self --frontend astro\n```\n\n## Example Compatibility\n\n### Todo Example\n\n- Requires a database when backend is present (except Convex)\n- Requires an API layer (`trpc` or `orpc`) for non-Convex backends\n- Cannot be used with `--backend none`\n\n### AI Example\n\n- Not compatible with `--frontend solid`\n- Not compatible with `--frontend astro`\n- With `--backend convex`, Nuxt and Svelte frontends are not supported\n\n## Common Error Messages\n\n### \"Mongoose ORM requires MongoDB database\"\n\n```bash\n# Fix by using MongoDB\ncreate-better-t-stack --database mongodb --orm mongoose\n```\n\n### \"Cloudflare Workers runtime is only supported with Hono backend\"\n\n```bash\n# Fix by using Hono\ncreate-better-t-stack --runtime workers --backend hono\n```\n\n### \"Cannot select multiple web frameworks\"\n\n```bash\n# Fix by choosing one web framework\ncreate-better-t-stack --frontend tanstack-router\n```\n\n### \"Polar payments requires Better Auth\"\n\n```bash\n# Fix by using Better-Auth\ncreate-better-t-stack --payments polar --auth better-auth\n\n# Or use Clerk without Polar payments\ncreate-better-t-stack --auth clerk --backend hono --frontend tanstack-router\n```\n\n## Validation Strategy\n\nThe CLI validates compatibility in this order:\n\n1. **Basic validation**: Required parameters, valid enum values\n2. **Combination validation**: Database + ORM, Backend + Runtime compatibility\n3. **Feature validation**: Auth requirements, addon compatibility\n4. **Example validation**: Example + stack compatibility\n\nUnderstanding these rules helps you create valid configurations and troubleshoot issues when the CLI reports compatibility errors.\n"
  },
  {
    "path": "apps/web/content/docs/cli/index.mdx",
    "content": "---\ntitle: Commands\ndescription: Complete reference for all CLI commands\n---\n\n## Overview\n\nThe Better-T-Stack CLI provides several commands to manage your TypeScript projects.\n\n## `create` (Default Command)\n\nCreates a new Better-T-Stack project.\n\n```bash\ncreate-better-t-stack [project-directory] [options]\n```\n\n### Parameters\n\n- `project-directory` (optional): Name or path for your project directory\n\n### Key Options\n\n- `--yes, -y`: Use default configuration (skips prompts)\n- `--dry-run`: Validate configuration and target directory without writing files\n- `--verbose`: Show detailed result information as JSON\n- `--yolo`: Bypass validations and compatibility checks\n- `--package-manager <pm>`: `npm`, `pnpm`, `bun`\n- `--install / --no-install`: Install dependencies after creation\n- `--git / --no-git`: Initialize Git repository\n- `--frontend <types...>`: Web and/or native frameworks (see [Options](/docs/cli/options#frontend))\n- `--backend <framework>`: `hono`, `express`, `fastify`, `elysia`, `convex`, `self`, `none`\n- `--runtime <runtime>`: `bun`, `node`, `workers` (`none` only with `--backend convex`, `--backend none`, or `--backend self`)\n- `--database <type>`: `none`, `sqlite`, `postgres`, `mysql`, `mongodb`\n- `--orm <type>`: `none`, `drizzle`, `prisma`, `mongoose`\n- `--api <type>`: `none`, `trpc`, `orpc`\n- `--auth <provider>`: `better-auth`, `clerk`, `none` (see [Options](/docs/cli/options#authentication))\n- `--payments <provider>`: `polar`, `none`\n- `--db-setup <setup>`: `none`, `turso`, `d1`, `neon`, `supabase`, `prisma-postgres`, `planetscale`, `mongodb-atlas`, `docker`\n- `--examples <types...>`: `none`, `todo`, `ai`\n- `--web-deploy <setup>`: `none`, `cloudflare`\n- `--server-deploy <setup>`: `none`, `cloudflare`\n- `--template <type>`: `none`, `mern`, `pern`, `t3`, `uniwind`\n- `--directory-conflict <strategy>`: `merge`, `overwrite`, `increment`, `error`\n- `--render-title / --no-render-title`: Show/hide ASCII art title\n- `--disable-analytics / --no-disable-analytics`: Control analytics collection\n- `--manual-db`: Skip automatic database setup prompts\n\nSee the full reference in [Options](/docs/cli/options).\n\nFor JSON-first automation and agent usage, see [Agent Workflows](/docs/cli/agent-workflows).\n\n### Examples\n\n```bash\n# Default setup with prompts\ncreate-better-t-stack\n\n# Quick setup with defaults\ncreate-better-t-stack --yes\n\n# Specific configuration\ncreate-better-t-stack --database postgres --backend hono --frontend tanstack-router\n\n# Validate without writing files\ncreate-better-t-stack my-app --yes --dry-run\n```\n\n## `add`\n\nAdds addons to an existing Better-T-Stack project.\n\n```bash\ncreate-better-t-stack add [options]\n```\n\n### Options\n\n- `--addons <types...>`: Addons to add (see [Addons](/docs/cli/options#addons))\n- `--project-dir <path>`: Project directory (defaults to current directory)\n- `--install / --no-install`: Install dependencies after adding\n- `--package-manager <pm>`: Package manager to use (`npm`, `pnpm`, `bun`)\n\n### Examples\n\n```bash\n# Add addons interactively\ncreate-better-t-stack add\n\n# Add specific addons\ncreate-better-t-stack add --addons pwa tauri --install\n\n# Add addons in a specific project directory\ncreate-better-t-stack add --project-dir ./my-app --addons mcp skills\n```\n\n## `create-json`\n\nCreate a project from a raw JSON payload.\n\n```bash\ncreate-better-t-stack create-json --input '{\"projectName\":\"my-app\",\"yes\":true,\"dryRun\":true}'\n```\n\nUse this when you want a single machine-readable input object instead of many flags.\n\n## `add-json`\n\nAdd addons from a raw JSON payload.\n\n```bash\ncreate-better-t-stack add-json --input '{\"projectDir\":\"./my-app\",\"addons\":[\"wxt\"],\"addonOptions\":{\"wxt\":{\"template\":\"react\"}}}'\n```\n\n## `schema`\n\nPrint runtime CLI and input schemas as JSON.\n\n```bash\ncreate-better-t-stack schema --name all\ncreate-better-t-stack schema --name createInput\ncreate-better-t-stack schema --name addonOptions\ncreate-better-t-stack schema --name dbSetupOptions\n```\n\nThis is the best way for scripts and agents to discover the current input contract at runtime.\n\n## `sponsors`\n\nDisplays Better-T-Stack sponsors.\n\n```bash\ncreate-better-t-stack sponsors\n```\n\nShows a list of project sponsors and supporters.\n\n## `docs`\n\nOpens the Better-T-Stack documentation in your default browser.\n\n```bash\ncreate-better-t-stack docs\n```\n\nOpens `https://better-t-stack.dev/docs` in your browser.\n\n## `builder`\n\nOpens the web-based stack builder in your default browser.\n\n```bash\ncreate-better-t-stack builder\n```\n\nOpens `https://better-t-stack.dev/new` where you can configure your stack visually.\n\n## `history`\n\nShows project creation history stored on your local machine.\n\n```bash\ncreate-better-t-stack history [options]\n```\n\n### Options\n\n- `--limit <number>`: Number of entries to show (default: 10)\n- `--json`: Output history as JSON\n- `--clear`: Clear all history entries\n\n### Examples\n\n```bash\n# Show latest entries\ncreate-better-t-stack history\n\n# Show 5 entries\ncreate-better-t-stack history --limit 5\n\n# JSON output\ncreate-better-t-stack history --json\n\n# Clear history\ncreate-better-t-stack history --clear\n```\n\n## Global Options\n\nThese options work with any command:\n\n- `--help, -h`: Display help information\n- `--version, -V`: Display CLI version\n\n## Command Examples\n\n### Create a Full-Stack App\n\n```bash\ncreate-better-t-stack \\\n  --database postgres \\\n  --orm drizzle \\\n  --backend hono \\\n  --frontend tanstack-router \\\n  --auth better-auth \\\n  --addons pwa biome\n```\n\n### Create a Backend-Only Project\n\n```bash\ncreate-better-t-stack api-server \\\n  --frontend none \\\n  --backend hono \\\n  --database postgres \\\n  --orm drizzle \\\n  --api trpc\n```\n\n### Add Features to Existing Project\n\n```bash\ncd my-existing-project\ncreate-better-t-stack add --addons tauri starlight --install\n```\n\n### Show Local History\n\n```bash\ncreate-better-t-stack history --limit 20\n```\n\n## Programmatic Usage\n\nFor advanced use cases, automation, or integration with other tools, you can use the [Programmatic API](/docs/cli/programmatic-api) to create projects from Node.js code:\n\n```typescript\nimport { create } from \"create-better-t-stack\";\n\nconst result = await create(\"my-app\", {\n  frontend: [\"tanstack-router\"],\n  backend: \"hono\",\n  database: \"sqlite\",\n  orm: \"drizzle\",\n});\n\nresult.match({\n  ok: (data) => console.log(`Project created at: ${data.projectDirectory}`),\n  err: (error) => console.error(`Failed: ${error.message}`),\n});\n```\n\nThis is useful for:\n\n- **Build tools and generators** - Create projects from templates\n- **CI/CD pipelines** - Generate test projects automatically\n- **Development workflows** - Batch create related projects\n- **Custom tooling** - Integrate with your existing development setup\n\nSee the [Programmatic API documentation](/docs/cli/programmatic-api) for complete examples and API reference.\n"
  },
  {
    "path": "apps/web/content/docs/cli/meta.json",
    "content": "{\n  \"title\": \"CLI\",\n  \"defaultOpen\": true,\n  \"pages\": [\"index\", \"agent-workflows\", \"programmatic-api\", \"options\", \"prompts\", \"compatibility\"]\n}\n"
  },
  {
    "path": "apps/web/content/docs/cli/options.mdx",
    "content": "---\ntitle: Options Reference\ndescription: Complete reference for all CLI options and flags\n---\n\n## General Options\n\n### `--yes, -y`\n\nUse default configuration and skip interactive prompts.\n\n```bash\ncreate-better-t-stack --yes\n```\n\n### `--template <type>`\n\nUse a predefined project template:\n\n- `none`: No template (default)\n- `mern`: MongoDB, Express, React, Node.js stack\n- `pern`: PostgreSQL, Express, React, Node.js stack\n- `t3`: T3 stack configuration\n- `uniwind`: UniWind React Native template\n\n```bash\ncreate-better-t-stack --template t3\n```\n\n### `--manual-db`\n\nSkip automatic database setup prompts and use manual database configuration.\n\n```bash\ncreate-better-t-stack --manual-db\n```\n\n### `--dry-run`\n\nValidate configuration, compatibility, and directory handling without writing files.\n\n```bash\ncreate-better-t-stack my-app --yes --dry-run\n```\n\n### `--package-manager <pm>`\n\nChoose package manager: `npm`, `pnpm`, or `bun`.\n\n```bash\ncreate-better-t-stack --package-manager bun\n```\n\n### `--install / --no-install`\n\nControl dependency installation after project creation.\n\n```bash\ncreate-better-t-stack --no-install\n```\n\n### `--git / --no-git`\n\nControl Git repository initialization.\n\n```bash\ncreate-better-t-stack --no-git\n```\n\n### `--yolo`\n\nBypass validations and compatibility checks. Not recommended for normal use.\n\n```bash\ncreate-better-t-stack --yolo\n```\n\n### `--verbose`\n\nShow detailed result information in JSON format after project creation.\n\n```bash\ncreate-better-t-stack --verbose\n```\n\n### `--render-title / --no-render-title`\n\nControl whether the ASCII art title is shown. Enabled by default.\n\n```bash\n# Hide the title (useful in CI)\ncreate-better-t-stack --no-render-title\n```\n\n### `--directory-conflict <strategy>`\n\nHow to handle existing, non-empty target directories:\n\n- `merge`: Keep existing files and merge new ones\n- `overwrite`: Clear the directory before scaffolding\n- `increment`: Create a suffixed directory (e.g., `my-app-1`)\n- `error`: Fail instead of prompting\n\n```bash\n# Overwrite an existing directory without prompting\ncreate-better-t-stack my-app --yes --directory-conflict overwrite\n\n# Safely create a new directory name if it exists\ncreate-better-t-stack my-app --yes --directory-conflict increment\n```\n\n### `--disable-analytics / --no-disable-analytics`\n\nControl whether analytics and telemetry data is collected.\n\n```bash\n# Disable analytics collection\ncreate-better-t-stack --disable-analytics\n\n# Enable analytics collection (default)\ncreate-better-t-stack --no-disable-analytics\n```\n\nAnalytics help improve Better-T-Stack by providing insights into usage patterns. When disabled, no data is collected or transmitted.\n\nFor JSON-first automation, runtime schemas, and nested structured options, see [Agent Workflows](/docs/cli/agent-workflows).\n\n## Database Options\n\n### `--database <type>`\n\nDatabase type to use:\n\n- `none`: No database\n- `sqlite`: SQLite database\n- `postgres`: PostgreSQL database\n- `mysql`: MySQL database\n- `mongodb`: MongoDB database\n\n```bash\ncreate-better-t-stack --database postgres\n```\n\n### `--orm <type>`\n\nORM to use with your database:\n\n- `none`: No ORM\n- `drizzle`: Drizzle ORM (TypeScript-first)\n- `prisma`: Prisma ORM (feature-rich)\n- `mongoose`: Mongoose ODM (for MongoDB)\n\n```bash\ncreate-better-t-stack --database postgres --orm drizzle\n```\n\n### `--db-setup <setup>`\n\nDatabase hosting/setup provider:\n\n- `none`: Manual setup\n- `turso`: Turso (SQLite)\n- `d1`: Cloudflare D1 (SQLite; requires either Cloudflare Workers server deployment or `backend self` with Cloudflare web deployment)\n- `neon`: Neon (PostgreSQL)\n- `supabase`: Supabase (PostgreSQL)\n- `prisma-postgres`: Prisma Postgres\n- `planetscale`: PlanetScale (MySQL/PostgreSQL)\n- `mongodb-atlas`: MongoDB Atlas\n- `docker`: Local Docker containers\n\n```bash\ncreate-better-t-stack --database postgres --db-setup neon\n```\n\nIf you need structured control over database provisioning behavior, use `dbSetupOptions` with `create-json` or the programmatic API. See [Agent Workflows](/docs/cli/agent-workflows).\n\n## Backend Options\n\n### `--backend <framework>`\n\nBackend framework to use:\n\n- `none`: No backend\n- `hono`: Hono (fast, lightweight)\n- `express`: Express.js (popular, mature)\n- `fastify`: Fastify (fast, plugin-based)\n- `elysia`: Elysia (Bun-native)\n- `convex`: Convex backend\n- `self`: Self-hosted/custom backend\n\n```bash\ncreate-better-t-stack --backend hono\n```\n\n### `--runtime <runtime>`\n\nRuntime environment:\n\n- `none`: No specific runtime (only with `convex`, `none`, or `self` backend)\n- `bun`: Bun runtime\n- `node`: Node.js runtime\n- `workers`: Cloudflare Workers\n\n```bash\ncreate-better-t-stack --backend hono --runtime bun\n```\n\n### `--api <type>`\n\nAPI layer type:\n\n- `none`: No API layer\n- `trpc`: tRPC (type-safe)\n- `orpc`: oRPC (OpenAPI-compatible)\n\n```bash\ncreate-better-t-stack --api trpc\n```\n\n## Frontend Options\n\n### `--frontend <types...>`\n\nFrontend frameworks (can specify multiple):\n\n**Web Frameworks:**\n\n- `tanstack-router`: React with TanStack Router\n- `react-router`: React with React Router\n- `tanstack-start`: React with TanStack Start (SSR)\n- `next`: Next.js\n- `nuxt`: Nuxt (Vue)\n- `svelte`: SvelteKit\n- `solid`: SolidJS\n- `astro`: Astro\n\n**Native Frameworks:**\n\n- `native-bare`: React Native (bare setup)\n- `native-uniwind`: React Native with UniWind (NativeWind alternative)\n- `native-unistyles`: React Native with Unistyles\n\n**No Frontend:**\n\n- `none`: Backend-only project\n\n```bash\n# Single web frontend\ncreate-better-t-stack --frontend tanstack-router\n\n# Web + native frontend\ncreate-better-t-stack --frontend next native-uniwind\n\n# Backend-only\ncreate-better-t-stack --frontend none\n```\n\n## Authentication\n\n### `--auth <provider>`\n\nChoose authentication provider:\n\n- `better-auth`: Better-Auth authentication (default)\n- `clerk`: Clerk authentication\n- `none`: No authentication\n\n```bash\ncreate-better-t-stack --auth better-auth\ncreate-better-t-stack --auth clerk\ncreate-better-t-stack --auth none\n```\n\n**Note:**\n\n- `better-auth` requires a backend framework (cannot be `none`)\n- if you choose a database, you should also choose an ORM\n- with `--backend convex`, `better-auth` supports `react-router`, `tanstack-router`, `tanstack-start`, `next`, and native Expo frontends\n- `clerk` requires a compatible frontend\n- Supported Clerk backends: `convex`, `hono`, `express`, `fastify`, `elysia`, and `self` with Next.js or TanStack Start\n- Authentication is automatically set to `none` when using `--backend none`\n\n## Payments\n\n### `--payments <provider>`\n\nPayments provider:\n\n- `none`: No payments integration\n- `polar`: Polar payments integration\n\n```bash\ncreate-better-t-stack --payments polar --auth better-auth\n```\n\n**Note:** Polar payments requires Better-Auth authentication.\n\n## Addons\n\n### `--addons <types...>`\n\nAdditional features to include:\n\n- `none`: No addons\n- `pwa`: Progressive Web App support\n- `tauri`: Desktop app support\n- `electrobun`: Lightweight desktop shell for web frontends\n- `starlight`: Starlight documentation site\n- `fumadocs`: Fumadocs documentation site\n- `biome`: Biome linting and formatting\n- `lefthook`: Git hooks with Lefthook\n- `husky`: Git hooks with Husky\n- `turborepo`: Turborepo monorepo setup\n- `nx`: Nx monorepo setup\n- `ultracite`: Ultracite configuration\n- `oxlint`: Oxlint + Oxfmt (linting & formatting)\n- `mcp`: Install MCP servers, including Better T Stack itself, with add-mcp\n- `opentui`: OpenTUI components\n- `wxt`: WXT browser extension framework\n- `skills`: Install AI agent skills for coding assistants (Cursor, Claude Code, GitHub Copilot, etc.)\n- `evlog`: Structured request logging for Hono, Express, Fastify, Elysia, or fullstack web backends\n\n```bash\ncreate-better-t-stack --addons pwa biome husky\n```\n\n## Examples\n\n### `--examples <types...>`\n\nExample implementations to include:\n\n- `none`: No examples\n- `todo`: Todo app example\n- `ai`: AI chat interface example\n\n```bash\ncreate-better-t-stack --examples todo ai\n```\n\n## Deployment\n\n### `--web-deploy <setup>`\n\nWeb deployment configuration:\n\n- `none`: No deployment setup\n- `cloudflare`: Cloudflare Workers deployment (via Alchemy infrastructure as code)\n\n```bash\ncreate-better-t-stack --web-deploy cloudflare\n```\n\n**Note:** Alchemy uses TypeScript to define infrastructure programmatically. See the [Deploying to Cloudflare with Alchemy Guide](/docs/guides/cloudflare-alchemy) for details.\n\n## Automation Surfaces\n\nThese are part of the CLI contract for agents and scripts even though they are commands or JSON fields instead of traditional flags:\n\n- `create-json`\n- `add-json`\n- `schema --name <schema>`\n- `mcp`\n- `addonOptions`\n- `dbSetupOptions`\n\nSee [Agent Workflows](/docs/cli/agent-workflows) for examples.\n\n### `--server-deploy <setup>`\n\nServer deployment configuration:\n\n- `none`: No deployment setup\n- `cloudflare`: Cloudflare Workers deployment (when runtime is workers, via Alchemy infrastructure as code)\n\n```bash\ncreate-better-t-stack --server-deploy cloudflare\n```\n\n**Note:** Alchemy uses TypeScript to define infrastructure programmatically. See the [Deploying to Cloudflare with Alchemy Guide](/docs/guides/cloudflare-alchemy) for details.\n\n## History\n\n### `history`\n\nView your project creation history. Projects are tracked locally using platform-specific directories:\n\n- **macOS**: `~/Library/Application Support/better-t-stack/history.json`\n- **Linux**: `~/.local/share/better-t-stack/history.json`\n- **Windows**: `%LOCALAPPDATA%\\better-t-stack\\Data\\history.json`\n\n```bash\n# Show last 10 projects\ncreate-better-t-stack history\n\n# Show last 5 projects\ncreate-better-t-stack history --limit 5\n\n# Output as JSON\ncreate-better-t-stack history --json\n\n# Clear all history\ncreate-better-t-stack history --clear\n```\n\n**Options:**\n\n- `--limit <number>`: Number of entries to show (default: 10)\n- `--clear`: Clear all project history\n- `--json`: Output history as JSON\n\n## Option Validation\n\nThe CLI validates option combinations and will show errors for incompatible selections. See the [Compatibility](/docs/cli/compatibility) page for detailed rules.\n\n## Examples\n\n### Full Configuration\n\n```bash\ncreate-better-t-stack \\\n  --database postgres \\\n  --orm drizzle \\\n  --backend hono \\\n  --runtime bun \\\n  --frontend tanstack-router \\\n  --api trpc \\\n  --auth better-auth \\\n  --addons pwa biome \\\n  --examples todo \\\n  --package-manager bun \\\n  --web-deploy cloudflare \\\n  --server-deploy cloudflare \\\n  --install\n```\n\n### Minimal Setup\n\n```bash\ncreate-better-t-stack \\\n  --backend none \\\n  --frontend tanstack-router \\\n  --addons none \\\n  --examples none\n```\n"
  },
  {
    "path": "apps/web/content/docs/cli/programmatic-api.mdx",
    "content": "---\ntitle: Programmatic API\ndescription: Use Better-T-Stack programmatically in your Node.js applications\n---\n\n## Overview\n\nYou can call Better-T-Stack directly from TypeScript/JavaScript without shelling out to the CLI.\n\nThe programmatic API is exported from `create-better-t-stack` and is designed for automation tools, internal generators, and scripted workflows.\n\nBecause it runs in silent mode by default, it also benefits from the same agent-safe behavior as `create-json`, including structured addon and database setup options.\n\n## Installation\n\n```npm\nnpm i create-better-t-stack\n```\n\n## Quick Start\n\n```typescript\nimport { create } from \"create-better-t-stack\";\n\nconst result = await create(\"my-app\", {\n  frontend: [\"tanstack-router\"],\n  backend: \"hono\",\n  database: \"sqlite\",\n  orm: \"drizzle\",\n  auth: \"better-auth\",\n  packageManager: \"bun\",\n  install: false,\n  dryRun: true,\n});\n\nresult.match({\n  ok: (data) => {\n    console.log(`Project created at: ${data.projectDirectory}`);\n    console.log(`Reproducible command: ${data.reproducibleCommand}`);\n  },\n  err: (error) => {\n    console.error(`Failed: ${error.message}`);\n  },\n});\n```\n\n## API Reference\n\n### `create(projectName?, options?)`\n\nCreate a new project.\n\n```typescript\nfunction create(\n  projectName?: string,\n  options?: Partial<CreateInput>,\n): Promise<Result<InitResult, CreateError>>;\n```\n\nNotes:\n\n- Uses the same option model as the CLI `create` command (`frontend`, `backend`, `database`, `orm`, `api`, `auth`, `addons`, etc.).\n- Supports structured `addonOptions` and `dbSetupOptions`.\n- Supports `dryRun` for validation-only automation.\n- Runs in silent mode (no interactive prompts / no CLI UI output).\n- Returns a `Result` (`ok`/`err`) instead of exiting the process.\n\n### `add(options?)`\n\nAdd addons to an existing Better-T-Stack project.\n\n```typescript\nfunction add(options?: {\n  addons?: Addons[];\n  addonOptions?: AddonOptions;\n  install?: boolean;\n  packageManager?: PackageManager;\n  projectDir?: string;\n  dryRun?: boolean;\n}): Promise<AddResult | undefined>;\n```\n\nExample:\n\n```typescript\nimport { add } from \"create-better-t-stack\";\n\nconst result = await add({\n  projectDir: \"./my-app\",\n  addons: [\"biome\", \"mcp\"],\n  addonOptions: {\n    mcp: {\n      scope: \"project\",\n      servers: [\"context7\"],\n      agents: [\"cursor\"],\n    },\n  },\n  install: true,\n});\n\nif (result?.success) {\n  console.log(`Added: ${result.addedAddons.join(\", \")}`);\n} else {\n  console.error(result?.error ?? \"Failed to add addons\");\n}\n```\n\n### `createVirtual(options)`\n\nGenerate a project in memory without writing to disk.\n\n```typescript\nimport { createVirtual } from \"create-better-t-stack\";\n\nconst result = await createVirtual({\n  frontend: [\"tanstack-router\"],\n  backend: \"hono\",\n  database: \"sqlite\",\n  orm: \"drizzle\",\n  addonOptions: {\n    wxt: {\n      template: \"react\",\n    },\n  },\n});\n```\n\nThis is useful for previews, tests, and web-based builders.\n\n### `sponsors()`\n\nShow sponsors (same behavior as CLI command).\n\n### `docs()`\n\nOpen docs URL (same behavior as CLI command).\n\n### `builder()`\n\nOpen the web stack builder (same behavior as CLI command).\n\n## Result Types\n\n### `InitResult` (from `create` on `ok`)\n\n```typescript\ntype InitResult = {\n  success: boolean;\n  projectConfig: ProjectConfig;\n  reproducibleCommand: string;\n  timeScaffolded: string;\n  elapsedTimeMs: number;\n  projectDirectory: string;\n  relativePath: string;\n  error?: string;\n};\n```\n\n### `AddResult` (from `add`)\n\n```typescript\ntype AddResult = {\n  success: boolean;\n  addedAddons: Addons[];\n  projectDir: string;\n  dryRun?: boolean;\n  plannedFileCount?: number;\n  error?: string;\n};\n```\n\n### `CreateError`\n\n`create()` can return these error types in `Result.err(...)`:\n\n- `UserCancelledError`\n- `CLIError`\n- `ProjectCreationError`\n\n## Error Handling Pattern\n\n```typescript\nimport { create } from \"create-better-t-stack\";\n\nconst result = await create(\"existing-dir\", {\n  directoryConflict: \"error\",\n});\n\nif (result.isErr()) {\n  console.error(result.error.message);\n  process.exit(1);\n}\n\nconsole.log(result.value.projectDirectory);\n```\n\n## Mapping CLI to Programmatic\n\nCLI:\n\n```bash\ncreate-better-t-stack my-app \\\n  --frontend tanstack-router \\\n  --backend hono \\\n  --database postgres \\\n  --orm drizzle \\\n  --auth better-auth\n```\n\nProgrammatic:\n\n```typescript\nconst result = await create(\"my-app\", {\n  frontend: [\"tanstack-router\"],\n  backend: \"hono\",\n  database: \"postgres\",\n  orm: \"drizzle\",\n  auth: \"better-auth\",\n  addonOptions: {\n    wxt: { template: \"react\" },\n  },\n  dbSetupOptions: {\n    mode: \"manual\",\n  },\n});\n```\n\nFor the CLI-side JSON equivalents, see [Agent Workflows](/docs/cli/agent-workflows).\n"
  },
  {
    "path": "apps/web/content/docs/cli/prompts.mdx",
    "content": "---\ntitle: Using the interactive prompts\ndescription: How to navigate and answer the CLI's interactive questions\n---\n\n## Overview\n\nThe CLI uses `@clack/prompts` for interactive questions. These prompts work well in most terminals and are fully keyboard-driven.\n\n## Core keys\n\n- **Navigate**: Up/Down arrow keys\n- **Confirm/continue**: Enter\n- **Cancel**: Ctrl+C\n\n## Prompt types you’ll see\n\n### Single select (choose one)\n\n- **Move** with Up/Down, **Enter** to choose the highlighted option.\n\nTypical places: choosing a web or native framework, picking a runtime or API.\n\n### Multi-select (choose many)\n\n- **Move** with Up/Down.\n- **Space** toggles the highlighted option on/off.\n- **Enter** confirms your selection(s).\n- Some prompts allow selecting none (you can press Enter without toggling anything).\n\nTypical places: selecting project types (web/native), choosing example apps.\n\n### Grouped multi-select (addons)\n\n- Options are organized under group headings.\n- **Move** with Up/Down, **Space** to toggle an option, **Enter** to confirm.\n- Group headings are informational; toggle the items within groups.\n\nUsed when selecting addons like Biome, PWA, Turborepo, etc.\n\n### Confirm (yes/no)\n\n- Use Left/Right or Up/Down to highlight Yes/No, then **Enter**.\n\nTypical places: installing dependencies, initializing Git.\n\n### Text input\n\n- Type your answer and press **Enter**.\n- If validation fails, a short message will explain what to fix; edit and press **Enter** again.\n\nTypical places: project name/path, database URLs, provider-specific inputs.\n\n## Tips\n\n- You can skip all prompts with `--yes` if you want the defaults. See the [Options](/docs/cli/options#yes--y) page.\n- If you accidentally start the wrong flow, press **Ctrl+C** to cancel safely.\n"
  },
  {
    "path": "apps/web/content/docs/contributing.mdx",
    "content": "---\ntitle: Contributing\ndescription: How to set up your environment and contribute changes\n---\n\n<Callout title=\"Important\" type=\"warn\">\n  Before starting work on any new features or major changes, **please open an issue first to discuss\n  your proposal and get approval.** We don't want you to **waste time** on work that might not align\n  with the project's direction or get merged.\n</Callout>\n## Overview\n\nThis project is a monorepo with two main apps:\n\n- CLI: `apps/cli`\n- Documentation site: `apps/web`\n\n## Setup\n\n### Prerequisites\n\n- Node.js (lts)\n- Bun (recommended)\n- Git\n\n### Install\n\n```bash\ngit clone https://github.com/AmanVarshney01/create-better-t-stack.git\ncd create-better-t-stack\nbun install\n```\n\n## Develop the CLI\n\n```bash\ncd apps/cli\n# optional global link for testing anywhere\nbun link\n# run in watch mode (runs tsdown build in watch mode)\nbun dev\n```\n\nNow go to anywhere else in your system (maybe like a test folder) and run:\n\n```bash\ncreate-better-t-stack\n```\n\nThis will run the locally installed CLI.\n\n## Develop the Docs\n\n```bash\n# from repo root\nbun install\ncd packages/backend\nbun dev:setup  # you can choose local development too in prompts\n```\n\nCopy the Convex URL from `packages/backend/.env.local` to `apps/web/.env`:\n\n```\nNEXT_PUBLIC_CONVEX_URL=http://127.0.0.1:3210/\n```\n\nNow run `bun dev` in the root. It will complain about GitHub token, so run this in `packages/backend`:\n\n```bash\nnpx convex env set GITHUB_ACCESS_TOKEN=xxxxx\nnpx convex env set GITHUB_WEBHOOK_SECRET=xxxxx\n```\n\n## Contribution Flow\n\n1. Open an issue/discussion before starting major work\n2. Fork the repository\n3. Create a feature branch\n4. Make changes following existing code style\n5. Update docs as needed\n6. Test and format\n\n```bash\n# CLI\ncd apps/cli && bun dev\ncd apps/cli && bun run test\n\n# Web\nbun dev\n\n# Lint + format checks\nbun check\n```\n\n7. Commit and push\n\n```bash\ngit add .\ngit commit -m \"feat(web): ...\" # or fix(cli): ...\ngit push origin <your-branch>\n```\n\n8. Open a Pull Request and link any related issues\n\n## Commit Conventions\n\nUse conventional commit messages with the appropriate scope:\n\n- `feat(cli): add new CLI feature`\n- `fix(cli): fix CLI bug`\n- `feat(web): add new web feature`\n- `fix(web): fix web bug`\n- `chore(web): update dependencies`\n- `docs: update documentation`\n\n## Help\n\n- Issues and Discussions on GitHub\n- Discord: https://discord.gg/ZYsbjpDaM5\n\nSee full contributor guide in the repository: `.github/CONTRIBUTING.md`.\n"
  },
  {
    "path": "apps/web/content/docs/faq.mdx",
    "content": "---\ntitle: Frequently Asked Questions\ndescription: Short answers to common beginner questions\n---\n\nimport { Accordion, Accordions } from \"fumadocs-ui/components/accordion\";\n\n## General\n\n<Accordions type=\"single\">\n  <Accordion\n    id=\"faq-1\"\n    title=\"What is Better‑T‑Stack?\">\n    An opinionated CLI that scaffolds full‑stack TypeScript projects (frontend, backend, API, DB/ORM, auth, addons) with a clean monorepo. See the Quick Start on the docs home.\n  </Accordion>\n\n<Accordion id=\"faq-2\" title=\"Do I need to install anything globally?\">\n  No. Run the CLI directly with your package manager. See Quick Start and the per‑command pages\n  under CLI.\n</Accordion>\n\n<Accordion id=\"faq-3\" title=\"Which package manager can I use?\">\n  `npm`, `pnpm`, or `bun` (all supported).\n</Accordion>\n\n<Accordion id=\"faq-4\" title=\"What Node.js version is required?\">\n  Node.js 20+ (LTS recommended).\n</Accordion>\n\n<Accordion id=\"faq-5\" title=\"Can I use this with an existing project?\">\n  The CLI is for new projects. You can migrate gradually or use `add` to extend a Better‑T‑Stack\n  project.\n</Accordion>\n\n <Accordion \n    id=\"faq-6\"\n    title=\"Where do generated files live?\">\n    See Project Structure for high‑level layouts (server‑based vs. Convex, optional web/native)..\n </Accordion>\n</Accordions>\n\n## Choosing options\n\n<Accordions type=\"single\">\n <Accordion \n    id=\"faq-7\"\n    title=\"Does the CLI recommend a stack?\">\n    No. Pick what fits your needs. The CLI validates compatibility. See CLI (per command) and Compatibility for rules.\n </Accordion>\n\n <Accordion \n    id=\"faq-8\"\n    title=\"I’m unsure between tRPC and oRPC / Drizzle and Prisma\">\n    See Compatibility for guidance and constraints. Both pairs work well; choose based on team and hosting needs.\n </Accordion>\n</Accordions>\n\n## Common issues\n\n<Accordions type=\"single\">\n <Accordion \n    id=\"faq-9\"\n    title=\"My mobile app can’t connect to the backend (Expo)\">\n    Set `EXPO_PUBLIC_SERVER_URL` in `apps/native/.env` to your machine IP (not `localhost`), check firewall, or try `npx expo start --tunnel`.\n </Accordion>\n\n <Accordion \n    id=\"faq-10\"\n    title=\"How do I disable telemetry?\">\n    Set `BTS_TELEMETRY_DISABLED=1` (shell env). For one run, prefix the command; to make it permanent, export it in your shell profile.\n </Accordion>\n</Accordions>\n\n## Getting help\n\n- Docs: Quick Start, CLI, Project Structure, Compatibility\n- Ask/Report: GitHub Issues & Discussions\n- Community: Discord\n"
  },
  {
    "path": "apps/web/content/docs/guides/cloudflare-alchemy.mdx",
    "content": "---\ntitle: Deploying to Cloudflare with Alchemy\ndescription: Learn how to deploy your Better-T-Stack app to Cloudflare Workers using Alchemy infrastructure-as-code\nauthor:\n  name: Oscar Gabriel\n  url: https://github.com/oscabriel\ndate: Dec 31, 2025\n---\n\n## Overview\n\nThis guide explains how Better-T-Stack uses [Alchemy](https://alchemy.run) to deploy your applications to [Cloudflare Workers](https://developers.cloudflare.com/workers/). You'll learn:\n\n- What Cloudflare Workers and Alchemy are\n- How to deploy web apps, server apps, or both\n- How environment variables and secrets are managed\n- How to work with D1 databases\n- How to manage multiple stages (dev, prod, staging, etc)\n- How type-safe bindings work with the `packages/env` package\n\n## What is Cloudflare Workers?\n\n**Cloudflare Workers** is a serverless platform that runs your code on Cloudflare's edge network across 300+ data centers worldwide. Unlike traditional serverless (AWS Lambda, Google Cloud Functions), Workers use **V8 isolates** instead of containers. This architecture provides near-zero cold starts, global distribution, native TypeScript type definitions generated by [workerd](https://github.com/cloudflare/workerd), and full-stack framework support for React Router, TanStack Start, SvelteKit, and more.\n\nWorkers integrate seamlessly with Cloudflare's developer platform, including D1 databases, R2 object storage, KV stores, Durable Objects, etc; all accessible via typed bindings, all with very generous free tiers.\n\n## What is Alchemy?\n\n**Alchemy** is an Infrastructure-as-Code (IaC) library. Unlike Terraform or Pulumi, Alchemy is pure TypeScript, resource-based, AI-friendly, and runs anywhere JS runs. You define what you want via normal async functions and Alchemy handles the creation, updating, and deletion of everything for you.\n\nWhen you scaffold a project with Cloudflare deployment enabled, Better-T-Stack generates an `alchemy.run.ts` file that defines your entire infrastructure as code.\n\n## Enabling Cloudflare Deployment\n\nWhen creating a project:\n\n**Combined deployment (web + server):**\n\n```npm\nnpm create better-t-stack@latest my-app \\\n  --frontend tanstack-router \\\n  --backend hono \\\n  --runtime workers \\\n  --web-deploy cloudflare \\\n  --server-deploy cloudflare\n```\n\n**Web-only (e.g., with Convex backend):**\n\n```npm\nnpm create better-t-stack@latest my-app \\\n  --frontend tanstack-start \\\n  --backend convex \\\n  --web-deploy cloudflare\n```\n\n**Server-only:**\n\n```npm\nnpm create better-t-stack@latest my-app \\\n  --frontend none \\\n  --backend hono \\\n  --runtime workers \\\n  --server-deploy cloudflare\n```\n\n## Understanding alchemy.run.ts\n\nThe `alchemy.run.ts` file is the heart of your deployment configuration. Here's a simplified example for a combined web + server deployment:\n\n```typescript\n// packages/infra/alchemy.run.ts\n\nimport alchemy from \"alchemy\";\nimport { TanStackStart } from \"alchemy/cloudflare\";\nimport { Worker } from \"alchemy/cloudflare\";\nimport { D1Database } from \"alchemy/cloudflare\";\nimport { config } from \"dotenv\";\n\n// Load environment variables from multiple .env files\nconfig({ path: \"./.env\" });\nconfig({ path: \"../../apps/web/.env\" });\nconfig({ path: \"../../apps/server/.env\" });\n\n// Initialize the Alchemy app\nconst app = await alchemy(\"my-app\");\n\n// Create D1 database (if using D1)\nconst db = await D1Database(\"database\", {\n  migrationsDir: \"../../packages/db/src/migrations\",\n});\n\n// Deploy web frontend\nexport const web = await TanStackStart(\"web\", {\n  cwd: \"../../apps/web\",\n  bindings: {\n    VITE_SERVER_URL: alchemy.env.VITE_SERVER_URL!,\n  },\n});\n\n// Deploy server backend\nexport const server = await Worker(\"server\", {\n  cwd: \"../../apps/server\",\n  entrypoint: \"src/index.ts\",\n  compatibility: \"node\",\n  bindings: {\n    DB: db,\n    CORS_ORIGIN: alchemy.env.CORS_ORIGIN!,\n    BETTER_AUTH_SECRET: alchemy.secret.env.BETTER_AUTH_SECRET!,\n    BETTER_AUTH_URL: alchemy.env.BETTER_AUTH_URL!,\n    DATABASE_URL: alchemy.secret.env.DATABASE_URL!,\n  },\n  dev: {\n    port: 3000,\n  },\n});\n\n// Log deployment URLs\nconsole.log(`Web    -> ${web.url}`);\nconsole.log(`Server -> ${server.url}`);\n\n// Finalize (triggers cleanup of orphaned resources)\nawait app.finalize();\n```\n\n### Framework-Specific Deployments\n\nAlchemy provides optimized deployment resources for each frontend framework:\n\n| Framework       | Alchemy Resource | Notes                                |\n| --------------- | ---------------- | ------------------------------------ |\n| Next.js         | `Nextjs`         | Uses OpenNext adapter                |\n| Nuxt            | `Nuxt`           | Uses Nitro Cloudflare preset         |\n| SvelteKit       | `SvelteKit`      | Uses Alchemy SvelteKit adapter       |\n| TanStack Start  | `TanStackStart`  | Full SSR support                     |\n| React Router    | `ReactRouter`    | Uses React Router Cloudflare adapter |\n| TanStack Router | `Vite`           | Static site with assets              |\n| SolidJS         | `Vite`           | Static site with assets              |\n\n## Environment Variables and Secrets\n\n### Loading Environment Variables\n\nThe generated `alchemy.run.ts` loads environment variables using dotenv:\n\n```typescript\nimport { config } from \"dotenv\";\n\n// Load from multiple locations (order matters - later files override)\nconfig({ path: \"./.env\" }); // packages/infra/.env\nconfig({ path: \"../../apps/web/.env\" }); // apps/web/.env\nconfig({ path: \"../../apps/server/.env\" }); // apps/server/.env\n```\n\nThis allows you to:\n\n- Keep shared variables in `packages/infra/.env`\n- Keep web-specific variables in `apps/web/.env`\n- Keep server-specific variables in `apps/server/.env`\n\n### alchemy.env vs alchemy.secret.env\n\nAlchemy provides two ways to access environment variables for bindings:\n\n#### Public Variables\n\nUse `alchemy.env` for non-sensitive configuration values. These are stored as plaintext in Alchemy's state files and are visible in logs:\n\n```typescript\nbindings: {\n  CORS_ORIGIN: alchemy.env.CORS_ORIGIN!,\n  VITE_SERVER_URL: alchemy.env.VITE_SERVER_URL!,\n  STAGE: alchemy.env.STAGE!,\n  VERSION: \"1.0.0\",\n}\n```\n\n**Use for:**\n\n- URLs and endpoints\n- Feature flags\n- Stage/environment identifiers\n- Public configuration\n\n#### Encrypted Secrets\n\nUse `alchemy.secret.env` for sensitive values. These are **encrypted** in Alchemy's state files using AES-256-GCM:\n\n```typescript\nbindings: {\n  BETTER_AUTH_SECRET: alchemy.secret.env.BETTER_AUTH_SECRET!,\n  DATABASE_URL: alchemy.secret.env.DATABASE_URL!,\n  API_KEY: alchemy.secret.env.API_KEY!,\n  STRIPE_SECRET_KEY: alchemy.secret.env.STRIPE_SECRET_KEY!,\n}\n```\n\n**Use for:**\n\n- API keys and tokens\n- Database credentials\n- Auth secrets\n- Any sensitive data\n\n### Alchemy Password\n\nAlchemy uses a password to encrypt and decrypt secrets. After creating a project, make sure to update the password variable in `packages/infra/.env`.\n\n**For CI/CD (GitHub Actions):**\n\n```yaml\nenv:\n  ALCHEMY_PASSWORD: ${{ secrets.ALCHEMY_PASSWORD }}\n```\n\n<Callout type=\"info\">Generate a strong password: `openssl rand -base64 32`</Callout>\n\nWithout `ALCHEMY_PASSWORD`, any operation involving secrets will fail. Store this password securely and never commit it to source control.\n\nNote that for apps using Convex as their backend, you should instead set your secrets directly in the Convex dashboard manually or with `convex env set` from `packages/backend`.\n\n## Multi-Stage Deployments\n\nAlchemy supports deploying to multiple stages (environments) like development, production, staging, and so on. Each stage has isolated state and resources.\n\n**CLI Argument:**\n\n```bash\n# Development (default)\nbun run deploy\n\n# Staging\nbun run deploy --stage staging\n\n# Production\nbun run deploy --stage prod\n```\n\n**Environment Variables:**\n\n```bash\n# packages/infra/.env.prod\nALCHEMY_STAGE=prod\n\nbun run deploy --env-file .env.prod\n```\n\n**Default Stage Resolution:**\n\n1. `--stage` CLI argument\n2. `ALCHEMY_STAGE` environment variable\n3. `STAGE` environment variable\n4. Current username (`$USER`)\n5. `\"dev\"` as fallback\n\n### Stage-Isolated State\n\nEach stage stores its state in a separate directory:\n\n<Files>\n  <Folder name=\".alchemy\" defaultOpen>\n    <Folder name=\"dev\" defaultOpen>\n      <File name=\"web.json\" />\n      <File name=\"server.json\" />\n    </Folder>\n    <Folder name=\"staging\">\n      <File name=\"web.json\" />\n      <File name=\"server.json\" />\n    </Folder>\n    <Folder name=\"prod\">\n      <File name=\"web.json\" />\n      <File name=\"server.json\" />\n    </Folder>\n  </Folder>\n</Files>\n\nThis ensures complete isolation between environments.\n\n### Stage-Based Resource Naming\n\nUse `app.stage` to create unique resource names per environment:\n\n```typescript\nconst app = await alchemy(\"my-app\");\n\n// Resources include stage in their names\nexport const server = await Worker(\"server\", {\n  name: `${app.name}-${app.stage}-server`, // e.g., \"my-app-prod-server\"\n  // ...\n});\n\nexport const db = await D1Database(\"database\", {\n  name: `${app.name}-${app.stage}-db`, // e.g., \"my-app-dev-db\"\n  // ...\n});\n```\n\n### Environment-Specific Configuration\n\n```typescript\nconst stage = process.env.STAGE || \"dev\";\nconst app = await alchemy(\"my-app\", { stage });\n\n// Stage-specific settings\nconst isProd = app.stage === \"prod\";\n\nexport const server = await Worker(\"server\", {\n  // Production gets custom domain, others get workers.dev URLs\n  url: !isProd,\n  domains: isProd ? [\"api.myapp.com\"] : undefined,\n\n  bindings: {\n    // Different URLs per environment\n    CORS_ORIGIN: isProd ? \"https://myapp.com\" : `https://${app.stage}.myapp.com`,\n  },\n});\n```\n\n## Type-Safe Bindings\n\n### How Bindings Work\n\nWhen you define bindings in `alchemy.run.ts`, they become available in your Worker code at runtime. Alchemy provides type inference so you get full TypeScript support.\n\n### The env.d.ts Pattern\n\nBetter-T-Stack generates a `packages/env/env.d.ts` file that connects your Alchemy bindings to TypeScript:\n\n```typescript\nimport { type server } from \"@my-app/infra/alchemy.run\";\n\n// Infer types from the Worker's bindings\nexport type CloudflareEnv = typeof server.Env;\n\ndeclare global {\n  type Env = CloudflareEnv;\n}\n\ndeclare module \"cloudflare:workers\" {\n  namespace Cloudflare {\n    export interface Env extends CloudflareEnv {}\n  }\n}\n```\n\nThis enables type-safe access to bindings in your server code.\n\n### Accessing Bindings in Code\n\n**With Hono:**\n\n```typescript\nimport { Hono } from \"hono\";\nimport { env } from \"cloudflare:workers\";\n\n// Access bindings via cloudflare:workers module\nconst app = new Hono()\n  .get(\"/users\", async (c) => {\n    // Type-safe access to bindings\n    const db = drizzle(env.DB);\n    const users = await db.select().from(usersTable);\n    return c.json(users);\n  })\n  .get(\"/config\", (c) => {\n    // Access env vars and secrets\n    return c.json({\n      corsOrigin: env.CORS_ORIGIN,\n      stage: env.STAGE,\n    });\n  });\n```\n\n**With Request Handler:**\n\n```typescript\nimport type { server } from \"@my-app/infra/alchemy.run\";\n\nexport default {\n  async fetch(request: Request, env: typeof server.Env) {\n    // Type-safe access to all bindings\n    const value = await env.KV.get(\"key\");\n    const apiKey = env.API_KEY;\n    return new Response(`Value: ${value}`);\n  },\n};\n```\n\n## Integration with packages/env\n\nBetter-T-Stack uses the `packages/env` package for type-safe environment variables. The setup differs between Cloudflare Workers and traditional runtimes.\n\n### For Cloudflare Workers (server.ts)\n\nWhen deploying to Cloudflare, server environment variables come from Worker bindings, not `process.env`:\n\n```typescript\n// packages/env/src/server.ts (Cloudflare Workers)\n\n/// <reference path=\"../env.d.ts\" />\n\n// Re-export env from cloudflare:workers module\n// Types are defined in env.d.ts based on your alchemy.run.ts bindings\nexport { env } from \"cloudflare:workers\";\n```\n\nThis means:\n\n- **No t3-env validation** for server env (bindings are already type-safe)\n- **Types come from Alchemy** via the `env.d.ts` file\n- **Runtime values** are injected by Cloudflare Workers\n\nFor traditional backend runtimes, server environment variables come from `process.env` and do make use of t3-env validation.\n\n### For Web/Client (web.ts)\n\nClient-side environment variables always use t3-env (Cloudflare or not):\n\n```typescript\n// packages/env/src/web.ts\n\nimport { createEnv } from \"@t3-oss/env-core\";\nimport { z } from \"zod\";\n\nexport const env = createEnv({\n  clientPrefix: \"VITE_\",\n  client: {\n    VITE_SERVER_URL: z.url(),\n  },\n  runtimeEnv: import.meta.env,\n  emptyStringAsUndefined: true,\n});\n```\n\n## Local Resources\n\nDuring development, Alchemy emulates your local environment using Miniflare. Running `bun run dev` creates a local SQLite databases that mimic your resources' real production behavior, so you can develop without deploying.\n\nYou can find these emulated resources in `.alchemy/miniflare/v3/`.\n\n## Deployment Commands\n\n### Root-Level Commands\n\nBetter-T-Stack adds these scripts to your root `package.json`:\n\n```json\n{\n  \"scripts\": {\n    \"dev\": \"...\",\n    \"deploy\": \"turbo -F @my-app/infra deploy\",\n    \"destroy\": \"turbo -F @my-app/infra destroy\"\n  }\n}\n```\n\n### Deploy Your Application\n\n```bash\n# Deploy to default stage (your username or \"dev\")\nbun run deploy\n\n# Deploy to a specific stage\nbun run deploy --stage prod\n\n# Or from the infra package directly\ncd packages/infra && bun run deploy\n```\n\nOn first deploy, Alchemy will:\n\n1. Create Cloudflare Workers for your web and/or server\n2. Create D1 database (if configured)\n3. Apply database migrations\n4. Upload your code and assets\n5. Log the deployment URLs\n\n### Development Mode\n\n```bash\n# Runs web and/or server with Alchemy's local emulation\nbun run dev\n```\n\nIn dev mode, Alchemy:\n\n- Emulates D1 locally using Miniflare\n- Provides local URLs for testing\n- Hot-reloads on changes\n\n### Destroy Resources\n\n```bash\n# Tear down all deployed resources for current stage\nbun run destroy\n\n# Destroy a specific stage\nbun run destroy --stage staging\n```\n\nThis removes:\n\n- Cloudflare Workers\n- D1 databases (unless `delete: false` is set)\n- All associated bindings\n\n<Callout type=\"warn\">\n  **Warning**: `destroy` permanently deletes your deployed resources. Database data will be lost\n  unless you've configured `delete: false` or exported backups.\n</Callout>\n\n## Continous Integration\n\n<Callout type=\"warn\">\n  **Important**: By default, Alchemy uses local file-based state storage. This can cause issues in\n  CI/CD where the filesystem is ephemeral.\n</Callout>\n\nFor CI/CD, you should either:\n\n1. **Commit state files** to your repository (secrets are encrypted)\n2. **Use a remote state store** via the CloudflareStateStore resource\n\nSee the alchemy docs to configure a [state store](https://alchemy.run/guides/cloudflare-state-store/) and set up a [CI/CD pipeline](https://alchemy.run/guides/ci/).\n\n## Cross-Domain Considerations\n\nWhen web and server are deployed as separate Workers, they have different domains:\n\n```\nWeb:    https://my-app-web.your-subdomain.workers.dev\nServer: https://my-app-server.your-subdomain.workers.dev\n```\n\n### Updating Environment Variables for Production\n\nBefore deploying, update your environment variables to use your production Worker URLs:\n\n```bash\n# apps/web/.env\nVITE_SERVER_URL=https://my-app-server.your-subdomain.workers.dev\n\n# apps/server/.env\nCORS_ORIGIN=https://my-app-web.your-subdomain.workers.dev\nBETTER_AUTH_URL=https://my-app-server.your-subdomain.workers.dev\n```\n\nReplace `your-subdomain` with your actual Cloudflare Workers subdomain (found in the Cloudflare dashboard under Workers & Pages).\n\n### Cookie Configuration for Auth\n\nWhen using Better-Auth with separate web and server Workers, cookies need special configuration to work across subdomains. In `packages/auth/src/auth.ts`, configure these settings:\n\n```typescript\n// packages/auth/src/auth.ts\nexport const auth = betterAuth({\n  // ... other config\n  session: {\n    cookieCache: {\n      enabled: true,\n      maxAge: 5 * 60, // 5 minutes\n    },\n  },\n  advanced: {\n    crossSubDomainCookies: {\n      enabled: true,\n      domain: \".workers.dev\", // Shared domain for cookies\n    },\n  },\n});\n```\n\n<Callout type=\"info\">\n  The generated auth configuration includes these settings commented out. Uncomment them and replace\n  the domain with your actual workers subdomain (e.g., `.your-subdomain.workers.dev`) when deploying\n  to production.\n</Callout>\n\nPay careful attention to CORS settings when moving between stages. A configuration that works locally may fail in production (and vice versa) if CORS origins or cookie domains don't match your actual deployment URLs.\n\n## Troubleshooting\n\n### \"Environment variable X is undefined\"\n\n1. Check that the variable exists in the correct `.env` file\n2. Verify the dotenv `config()` call loads that file\n3. For secrets, use `alchemy.secret.env.X` not `alchemy.env.X`\n\n### \"Secret cannot be decrypted\" or \"Password required\"\n\n1. Ensure `ALCHEMY_PASSWORD` is set in your environment\n2. Use the same password that was used to encrypt the secrets\n3. Check that the password hasn't been changed since last deployment\n\n### \"D1 migrations failed\"\n\n1. Ensure migrations directory path is correct in `alchemy.run.ts`\n2. Run migrations locally first: `bun run db:push`\n3. Check migration files are valid SQL\n\n### \"Worker size too large\"\n\n1. Check your bundle size with `bun run build`\n2. Enable minification in your build config\n3. Review dependencies - some packages aren't edge-compatible\n\n### \"CORS errors in browser\"\n\n1. Verify `CORS_ORIGIN` matches your web Worker URL exactly\n2. Check that preflight requests are handled\n3. Ensure cookies have correct `SameSite` settings\n"
  },
  {
    "path": "apps/web/content/docs/guides/index.mdx",
    "content": "---\ntitle: Guides\ndescription: Practical guides for common setups\n---\n\n## Guides\n\nCurated, task-focused guides for working with Better-T-Stack projects.\n\n### Available Guides\n\n<Cards>\n  <Card href=\"/docs/guides/cloudflare-alchemy\" title=\"Deploying to Cloudflare with Alchemy\">\n    Deploy your app to Cloudflare Workers using Alchemy infrastructure-as-code. Covers web + server\n    deployments, D1 databases, and environment management.\n  </Card>\n</Cards>\n\n### Coming Soon\n\nMore guides are planned:\n\n- Using Better-Auth with Convex\n- Workers + D1 setup with Hono and Drizzle\n- Adding PWA to React Router or Next.js\n- Using Prisma with Neon or Supabase\n- Monorepo tips with Turborepo\n\n### Other Resources\n\n- [CLI Reference](/docs/cli) - Command options and usage\n- [Compatibility](/docs/cli/compatibility) - Valid stack combinations\n- [Project Structure](/docs/project-structure) - Understanding generated projects\n"
  },
  {
    "path": "apps/web/content/docs/guides/meta.json",
    "content": "{\n  \"title\": \"Guides\",\n  \"defaultOpen\": true,\n  \"pages\": [\"index\", \"cloudflare-alchemy\"]\n}\n"
  },
  {
    "path": "apps/web/content/docs/index.mdx",
    "content": "---\ntitle: Quick Start\ndescription: Create your first Better-T-Stack project in minutes\n---\n\n## Philosophy\n\n- Roll your own stack: pick only what you need, nothing extra.\n- Minimal templates: bare-bones scaffolds with zero bloat.\n- Latest dependencies: always current and stable by default.\n- Free and open source: forever.\n\n## Get Started\n\n### Prerequisites\n\n- **Node.js LTS** - [Download from nodejs.org](https://nodejs.org/)\n- **Git** (optional) - [Download from git-scm.com](https://git-scm.com/) - if you want to initialize a git repository\n- **Bun** (optional) - [Download from bun.com](https://bun.com/) - if you want to use Bun as your package manager\n\n### CLI (prompts)\n\n```npm\nnpm create better-t-stack@latest\n```\n\nFollow the interactive prompts to choose your frontend, backend, database, ORM, API layer, and addons.\n\nSkip prompts and use the default stack:\n\n```npm\nnpm create better-t-stack@latest --yes\n```\n\n### Stack Builder (UI)\n\n- Visit [/new](/new) to pick your stack and copy the generated command\n- Or open it via:\n\n```npm\nnpm create better-t-stack@latest builder\n```\n\n## Common Setups\n\n### Default Stack\n\n```npm\nnpm create better-t-stack@latest my-webapp \\\n  --frontend tanstack-router \\\n  --backend hono \\\n  --database sqlite \\\n  --orm drizzle \\\n  --auth better-auth \\\n  --addons turborepo\n```\n\n### Convex + React + Clerk\n\n```npm\nnpm create better-t-stack@latest my-api \\\n  --frontend tanstack-router \\\n  --backend convex \\\n  --auth clerk\n```\n\n### Mobile App (Expo)\n\n```npm\nnpm create better-t-stack@latest my-native \\\n  --frontend native-uniwind \\\n  --backend hono \\\n  --database sqlite \\\n  --orm drizzle \\\n  --auth better-auth\n```\n\n### Empty Monorepo\n\n```npm\nnpm create better-t-stack@latest my-workspace \\\n  --frontend none \\\n  --backend none\n```\n\n## Flags Cheat Sheet\n\nSee the full list in the [CLI Reference](/docs/cli). Key flags:\n\n- `--frontend`: tanstack-router, react-router, tanstack-start, next, nuxt, svelte, solid, astro, native-bare, native-uniwind, native-unistyles, none\n- `--backend`: hono, express, fastify, elysia, convex, self, none\n- `--runtime`: bun, node, workers, none\n- `--database`: sqlite, postgres, mysql, mongodb, none\n- `--orm`: drizzle, prisma, mongoose, none\n- `--api`: trpc, orpc, none\n- `--auth`: better-auth, clerk, none\n- `--payments`: polar, none\n- `--addons`: turborepo, nx, pwa, tauri, electrobun, biome, lefthook, husky, starlight, fumadocs, ultracite, oxlint, mcp, opentui, wxt, skills, evlog, none\n- `--examples`: todo, ai, none\n\n## Next Steps\n\n<Cards>\n  <Card href=\"/docs/cli\" title=\"CLI (per-command)\">\n    Flags, usage, and examples for each command\n  </Card>\n  <Card href=\"/docs/project-structure\" title=\"Project Structure\">\n    See how web/server/native and Convex layouts are generated\n  </Card>\n  <Card href=\"/docs/cli/compatibility\" title=\"Compatibility\">\n    Valid combinations for backend, runtime, database, ORM, API\n  </Card>\n  <Card href=\"/docs/bts-config\" title=\"bts.jsonc\">\n    Required for the add command; safe to delete if you don't use add\n  </Card>\n  <Card href=\"/docs/contributing\" title=\"Contributing\">\n    Dev setup and contribution flow\n  </Card>\n</Cards>\n"
  },
  {
    "path": "apps/web/content/docs/meta.json",
    "content": "{\n  \"pages\": [\n    \"index\",\n    \"cli\",\n    \"guides\",\n    \"project-structure\",\n    \"bts-config\",\n    \"analytics\",\n    \"contributing\",\n    \"faq\"\n  ]\n}\n"
  },
  {
    "path": "apps/web/content/docs/project-structure.mdx",
    "content": "---\ntitle: Project Structure\ndescription: Understanding the structure of projects created by Better-T-Stack CLI\n---\n\n## Overview\n\nBetter-T-Stack CLI scaffolds a monorepo with `apps/*` and `packages/*`. `packages/config` is always present; other packages and apps appear based on your choices (frontend, backend, API, database/ORM, auth, addons, runtime, deploy). This page mirrors what the CLI actually writes.\n\n## Root layout\n\nAt the repository root you will see:\n\n<Files>\n  <Folder name=\"/\" defaultOpen>\n    <Folder name=\"apps\">\n      <File name=\"...\" />\n    </Folder>\n    <Folder name=\"packages\">\n      <File name=\"...\" />\n    </Folder>\n    <File name=\"package.json\" />\n    <File name=\"bts.jsonc\" />\n    <File name=\"turbo.json\" />\n    <File name=\"nx.json\" />\n    <File name=\"pnpm-workspace.yaml\" />\n    <File name=\"bunfig.toml\" />\n    <File name=\".npmrc\" />\n    <File name=\"README.md\" />\n  </Folder>\n</Files>\n\nNotes:\n\n- `bts.jsonc` lets the CLI detect and enhance your project later; keep it if you plan to use `create-better-t-stack add`.\n- `turbo.json` exists only if you picked the Turborepo addon.\n- `nx.json` exists only if you picked the Nx addon.\n- `pnpm-workspace.yaml`, `bunfig.toml`, and `.npmrc` are added based on the package manager you choose.\n- `packages/infra` is created only when Cloudflare deployment is enabled.\n\n## Monorepo structure by backend\n\n### Server backends (hono, express, fastify, elysia)\n\n<Files>\n  <Folder name=\"my-app\" defaultOpen>\n    <Folder name=\"apps\" defaultOpen>\n      <Folder name=\"web\">\n        <File name=\"...\" />\n      </Folder>\n      <Folder name=\"desktop\">\n        <File name=\"...\" />\n      </Folder>\n      <Folder name=\"server\">\n        <File name=\"...\" />\n      </Folder>\n      <Folder name=\"native\">\n        <File name=\"...\" />\n      </Folder>\n      <Folder name=\"docs\">\n        <File name=\"...\" />\n      </Folder>\n    </Folder>\n    <Folder name=\"packages\">\n      <File name=\"...\" />\n    </Folder>\n  </Folder>\n</Files>\n\nNotes:\n\n- `apps/desktop` is created only with the Electrobun addon.\n- `apps/docs` is created only with the Starlight addon.\n- `apps/fumadocs` is created only with the Fumadocs addon.\n\n### Self backend (fullstack)\n\nWhen `--backend self` is used, API routes live inside `apps/web` (no `apps/server`).\n\n<Files>\n  <Folder name=\"my-app\" defaultOpen>\n    <Folder name=\"apps\" defaultOpen>\n      <Folder name=\"web\">\n        <File name=\"...\" />\n      </Folder>\n      <Folder name=\"desktop\">\n        <File name=\"...\" />\n      </Folder>\n      <Folder name=\"native\">\n        <File name=\"...\" />\n      </Folder>\n      <Folder name=\"docs\">\n        <File name=\"...\" />\n      </Folder>\n    </Folder>\n    <Folder name=\"packages\">\n      <File name=\"...\" />\n    </Folder>\n  </Folder>\n</Files>\n\n### Convex backend\n\n<Files>\n  <Folder name=\"my-app\" defaultOpen>\n    <Folder name=\"apps\" defaultOpen>\n      <Folder name=\"web\">\n        <File name=\"...\" />\n      </Folder>\n      <Folder name=\"native\">\n        <File name=\"...\" />\n      </Folder>\n      <Folder name=\"docs\">\n        <File name=\"...\" />\n      </Folder>\n    </Folder>\n    <Folder name=\"packages\" defaultOpen>\n      <Folder name=\"backend\">\n        <File name=\"...\" />\n      </Folder>\n      <Folder name=\"config\">\n        <File name=\"...\" />\n      </Folder>\n      <Folder name=\"env\">\n        <File name=\"...\" />\n      </Folder>\n    </Folder>\n  </Folder>\n</Files>\n\n## Frontend Structure (apps/web)\n\nThe structure varies by framework. Items marked \"(auth)\" or \"(API)\" appear only when those options are enabled.\n\n### React with TanStack Router\n\n<Files>\n  <Folder name=\"apps/web\" defaultOpen>\n    <Folder name=\"src\" defaultOpen>\n      <Folder name=\"components\" defaultOpen>\n        <File name=\"header.tsx\" />\n        <File name=\"loader.tsx\" />\n        <File name=\"mode-toggle.tsx\" />\n        <File name=\"theme-provider.tsx\" />\n      </Folder>\n      <Folder name=\"routes\">\n        <File name=\"__root.tsx\" />\n        <File name=\"index.tsx\" />\n      </Folder>\n      <Folder name=\"utils\">\n        <File name=\"trpc.ts\" />\n      </Folder>\n      <File name=\"main.tsx\" />\n      <File name=\"index.css\" />\n    </Folder>\n    <File name=\"index.html\" />\n    <File name=\"components.json\" />\n    <File name=\"tsconfig.json\" />\n    <File name=\"vite.config.ts\" />\n    <File name=\"package.json\" />\n  </Folder>\n  <Folder name=\"packages/ui\">\n    <Folder name=\"src\">\n      <Folder name=\"components\">\n        <File name=\"button.tsx\" />\n        <File name=\"card.tsx\" />\n        <File name=\"checkbox.tsx\" />\n        <File name=\"dropdown-menu.tsx\" />\n        <File name=\"input.tsx\" />\n        <File name=\"label.tsx\" />\n        <File name=\"skeleton.tsx\" />\n        <File name=\"sonner.tsx\" />\n      </Folder>\n      <Folder name=\"lib\">\n        <File name=\"utils.ts\" />\n      </Folder>\n      <Folder name=\"styles\">\n        <File name=\"globals.css\" />\n      </Folder>\n    </Folder>\n    <File name=\"components.json\" />\n    <File name=\"package.json\" />\n    <File name=\"tsconfig.json\" />\n  </Folder>\n</Files>\n\n### Next.js\n\n<Files>\n  <Folder name=\"apps/web\" defaultOpen>\n    <Folder name=\"src\" defaultOpen>\n      <Folder name=\"app\">\n        <File name=\"layout.tsx\" />\n        <File name=\"page.tsx\" />\n      </Folder>\n      <Folder name=\"components\">\n        <File name=\"mode-toggle.tsx\" />\n        <File name=\"providers.tsx\" />\n        <File name=\"theme-provider.tsx\" />\n      </Folder>\n    </Folder>\n    <File name=\"components.json\" />\n    <File name=\"next.config.ts\" />\n    <File name=\"postcss.config.mjs\" />\n    <File name=\"tsconfig.json\" />\n    <File name=\"package.json\" />\n  </Folder>\n  <Folder name=\"packages/ui\">\n    <Folder name=\"src\">\n      <Folder name=\"components\">\n        <File name=\"button.tsx\" />\n        <File name=\"card.tsx\" />\n        <File name=\"checkbox.tsx\" />\n        <File name=\"dropdown-menu.tsx\" />\n        <File name=\"input.tsx\" />\n        <File name=\"label.tsx\" />\n        <File name=\"skeleton.tsx\" />\n        <File name=\"sonner.tsx\" />\n      </Folder>\n      <Folder name=\"lib\">\n        <File name=\"utils.ts\" />\n      </Folder>\n      <Folder name=\"styles\">\n        <File name=\"globals.css\" />\n      </Folder>\n    </Folder>\n    <File name=\"components.json\" />\n    <File name=\"package.json\" />\n    <File name=\"tsconfig.json\" />\n  </Folder>\n</Files>\n\nNotes:\n\n- If you choose `--backend self` with Next.js, TanStack Start, Nuxt, SvelteKit, or Astro, API routes live inside `apps/web` (no `apps/server`).\n- (auth) adds `src/app/login/*` and `src/app/dashboard/*` plus sign-in components.\n\n## React UI Customization\n\nReact web apps (`tanstack-router`, `react-router`, `tanstack-start`, and `next`) share shadcn/ui primitives through `packages/ui`.\n\n- Change design tokens and global styles in `packages/ui/src/styles/globals.css`\n- Update shared primitives in `packages/ui/src/components/*`\n- Adjust shadcn aliases or style config in `packages/ui/components.json` and `apps/web/components.json`\n\n### Add more shared components\n\nRun this from the project root to add more primitives to the shared UI package:\n\n```bash\nnpx shadcn@latest add accordion dialog popover sheet table -c packages/ui\n```\n\nImport shared components like this:\n\n```tsx\nimport { Button } from \"@your-project/ui/components/button\";\n```\n\n### Add app-specific blocks\n\nIf you want to add app-specific shadcn blocks instead of shared primitives, run the shadcn CLI from `apps/web`.\n\n## Backend Structure (apps/server)\n\nThe server structure depends on your backend choice:\n\n### Hono backend\n\n<Files>\n  <Folder name=\"apps/server\" defaultOpen>\n    <Folder name=\"src\" defaultOpen>\n      <Folder name=\"routers\">\n        <File name=\"index.ts\" />\n      </Folder>\n      <File name=\"index.ts\" />\n    </Folder>\n    <File name=\"package.json\" />\n    <File name=\"tsconfig.json\" />\n  </Folder>\n</Files>\n\n### Express / Fastify / Elysia\n\n<Files>\n  <Folder name=\"apps/server\" defaultOpen>\n    <Folder name=\"src\" defaultOpen>\n      <File name=\"index.ts\" />\n    </Folder>\n    <File name=\"package.json\" />\n    <File name=\"tsconfig.json\" />\n  </Folder>\n</Files>\n\n### Workers runtime (optional)\n\nWhen `runtime=workers`, the server targets Cloudflare Workers. If you also choose Cloudflare deployment, you'll get `packages/infra/alchemy.run.ts` and related infra files.\n\nAPI and auth scaffolding (conditional):\n\n- API=trpc: `src/lib/trpc.ts`, `src/lib/context.ts`\n- API=orpc: `src/lib/orpc.ts`, `src/lib/context.ts`\n- Auth: `src/lib/auth.ts`\n\n## Database Configuration\n\nAdded only when you selected a database and ORM:\n\n### Drizzle ORM\n\n<Files>\n  <Folder name=\"apps/server\" defaultOpen>\n    <Folder name=\"src\">\n      <Folder name=\"db\">\n        <File name=\"index.ts\" />\n      </Folder>\n    </Folder>\n    <File name=\"drizzle.config.ts\" />\n    <Folder name=\"drizzle\">\n      <File name=\"...\" />\n    </Folder>\n  </Folder>\n</Files>\n\n### Prisma ORM\n\n<Files>\n  <Folder name=\"apps/server\" defaultOpen>\n    <Folder name=\"prisma\" defaultOpen>\n      <File name=\"schema.prisma\" />\n      <Folder name=\"migrations\">\n        <File name=\"...\" />\n      </Folder>\n    </Folder>\n  </Folder>\n</Files>\n\n### Mongoose (MongoDB)\n\n<Files>\n  <Folder name=\"apps/server\" defaultOpen>\n    <Folder name=\"src\">\n      <Folder name=\"db\">\n        <File name=\"index.ts\" />\n      </Folder>\n    </Folder>\n  </Folder>\n</Files>\n\n### Auth + DB\n\nIf you selected auth, additional files are added for your ORM:\n\n- Drizzle: `src/db/schema/auth.ts`\n- Prisma: `prisma/schema/auth.prisma`\n- Mongoose: `src/db/models/auth.model.ts`\n\n### Docker compose (optional)\n\nIf `dbSetup=docker`, a `docker-compose.yml` is added in `apps/server/` for your database.\n\n## Native App Structure (apps/native)\n\nCreated only when you include React Native (NativeWind or Unistyles):\n\n<Files>\n  <Folder name=\"apps/native\" defaultOpen>\n    <Folder name=\"app\" defaultOpen>\n      <Folder name=\"(tabs)\">\n        <File name=\"...\" />\n      </Folder>\n      <File name=\"_layout.tsx\" />\n      <File name=\"index.tsx\" />\n    </Folder>\n    <Folder name=\"components\">\n      <File name=\"...\" />\n    </Folder>\n    <Folder name=\"lib\">\n      <File name=\"...\" />\n    </Folder>\n    <Folder name=\"assets\">\n      <File name=\"...\" />\n    </Folder>\n    <File name=\"app.json\" />\n    <File name=\"package.json\" />\n    <File name=\"tsconfig.json\" />\n  </Folder>\n</Files>\n\nIf an API is selected, a client utility is added:\n\n- API=trpc: `utils/trpc.ts`\n- API=orpc: `utils/orpc.ts`\n\n## Documentation Structure\n\n### Starlight (apps/docs)\n\n<Files>\n  <Folder name=\"apps/docs\" defaultOpen>\n    <Folder name=\"src\" defaultOpen>\n      <Folder name=\"content\">\n        <Folder name=\"docs\">\n          <File name=\"...\" />\n        </Folder>\n      </Folder>\n      <Folder name=\"pages\">\n        <File name=\"...\" />\n      </Folder>\n    </Folder>\n    <File name=\"astro.config.mjs\" />\n    <File name=\"package.json\" />\n    <File name=\"tsconfig.json\" />\n  </Folder>\n</Files>\n\n### Fumadocs (apps/fumadocs)\n\nFumadocs is generated by `create-fumadocs-app` inside `apps/fumadocs`. The exact structure depends on the template you choose (MDX vs static).\n\n### Electrobun (apps/desktop)\n\nElectrobun adds an `apps/desktop` workspace with its own `package.json`, `electrobun.config.ts`, and `src/bun/index.ts`. The desktop shell reuses `apps/web` during development and bundles its built static output for release builds. If your frontend is SSR-first, switch it to a static/export build before packaging the desktop app.\n\n## Configuration Files\n\n### Better-T-Stack Config (bts.jsonc)\n\n```json\n{\n  \"$schema\": \"https://r2.better-t-stack.dev/schema.json\",\n  \"version\": \"<cli-version>\",\n  \"createdAt\": \"<timestamp>\",\n  \"database\": \"<none|sqlite|postgres|mysql|mongodb>\",\n  \"orm\": \"<none|drizzle|prisma|mongoose>\",\n  \"backend\": \"<none|hono|express|fastify|elysia|convex|self>\",\n  \"runtime\": \"<bun|node|workers|none>\",\n  \"frontend\": [\"<tanstack-router|react-router|tanstack-start|next|nuxt|svelte|solid|astro|native-bare|native-uniwind|native-unistyles|none>\"] ,\n  \"addons\": [\"<pwa|tauri|electrobun|starlight|fumadocs|biome|lefthook|husky|mcp|turborepo|nx|ultracite|oxlint|opentui|wxt|skills|evlog|none>\"] ,\n  \"examples\": [\"<ai|todo|none>\"] ,\n  \"auth\": <\"better-auth\"|\"clerk\"|\"none\">,\n  \"packageManager\": \"<bun|pnpm|npm>\",\n  \"dbSetup\": \"<turso|neon|prisma-postgres|planetscale|mongodb-atlas|supabase|d1|docker|none>\",\n  \"api\": \"<none|trpc|orpc>\",\n  \"webDeploy\": \"<cloudflare|none>\",\n  \"serverDeploy\": \"<cloudflare|none>\"\n}\n```\n\n### Turborepo Config (turbo.json)\n\nGenerated only if you chose the Turborepo addon.\n\n### Nx Config (nx.json)\n\nGenerated only if you chose the Nx addon.\n\n```json\n{\n  \"$schema\": \"https://turbo.build/schema.json\",\n  \"tasks\": {\n    \"build\": {\n      \"dependsOn\": [\"^build\"],\n      \"outputs\": [\"dist/**\", \".next/**\"]\n    },\n    \"dev\": {\n      \"cache\": false,\n      \"persistent\": true\n    }\n  }\n}\n```\n\n## Shared packages\n\nBetter-T-Stack always creates `packages/config`. Other packages are added based on your selections:\n\n- `packages/env`: when any frontend is selected or the backend is not `none`\n- `packages/api`: when `--api` is not `none` (non-Convex)\n- `packages/auth`: when `--auth` is not `none` (non-Convex)\n- `packages/db`: when both `--database` and `--orm` are selected (non-Convex)\n- `packages/backend`: Convex backend only\n- `packages/infra`: Cloudflare deployment only\n\n## Development Scripts\n\nScripts are adjusted based on your package manager and whether the Turborepo or Nx addon is selected.\n\nWith Turborepo:\n\n```json\n{\n  \"scripts\": {\n    \"dev\": \"turbo dev\",\n    \"build\": \"turbo build\",\n    \"check-types\": \"turbo check-types\",\n    \"dev:web\": \"turbo -F web dev\",\n    \"dev:server\": \"turbo -F server dev\",\n    \"db:push\": \"turbo -F server db:push\",\n    \"db:studio\": \"turbo -F server db:studio\"\n  }\n}\n```\n\nWith Nx:\n\n```json\n{\n  \"scripts\": {\n    \"dev\": \"nx run-many -t dev\",\n    \"build\": \"nx run-many -t build\",\n    \"check-types\": \"nx run-many -t check-types\",\n    \"dev:web\": \"nx run-many -t dev --projects=web\",\n    \"dev:server\": \"nx run-many -t dev --projects=server\"\n  }\n}\n```\n\nWithout Turborepo or Nx (example for Bun):\n\n```json\n{\n  \"scripts\": {\n    \"dev\": \"bun run --filter '*' dev\",\n    \"build\": \"bun run --filter '*' build\",\n    \"check-types\": \"bun run --filter '*' check-types\",\n    \"dev:web\": \"bun run --filter web dev\",\n    \"dev:server\": \"bun run --filter server dev\"\n  }\n}\n```\n\nNotes:\n\n- Convex adds `dev:setup` for initial backend configuration.\n- Database scripts (`db:*`) are added only when a database + ORM are selected (Drizzle/Prisma). D1 + Cloudflare omits `db:studio`.\n\n## Key Details\n\n- **Monorepo**: `apps/*` and `packages/*` are created only when relevant (except `packages/config`, which is always present)\n- **React web base**: app-specific files stay in `apps/web`, while shared shadcn/ui primitives live in `packages/ui`\n- **API clients**: `src/utils/trpc.ts` or `src/utils/orpc.ts` added to web/native when selected\n- **Auth**: Adds authentication setup based on provider:\n  - `better-auth`: `src/lib/auth.ts` on server and login/dashboard pages on web app\n  - `clerk`: Clerk provider setup and authentication components\n- **ORM/DB**: Drizzle/Prisma/Mongoose files added only when selected\n- **Extras**: `pnpm-workspace.yaml`, `bunfig.toml`, or `.npmrc` added based on package manager and choices\n- **Deploy**: Cloudflare deployment adds `packages/infra/alchemy.run.ts` and related infra files\n\nThis reflects the actual files written by the CLI so new projects match what's documented here.\n"
  },
  {
    "path": "apps/web/next.config.ts",
    "content": "import { createMDX } from \"fumadocs-mdx/next\";\nimport type { NextConfig } from \"next\";\n\nconst withMDX = createMDX();\n\nconst config: NextConfig = {\n  reactCompiler: true,\n  reactStrictMode: true,\n  images: {\n    remotePatterns: [\n      { protocol: \"https\", hostname: \"pbs.twimg.com\" },\n      { protocol: \"https\", hostname: \"abs.twimg.com\" },\n      { protocol: \"https\", hostname: \"r2.better-t-stack.dev\" },\n      { protocol: \"https\", hostname: \"avatars.githubusercontent.com\" },\n    ],\n  },\n  outputFileTracingExcludes: {\n    \"*\": [\"./**/*.js.map\", \"./**/*.mjs.map\", \"./**/*.cjs.map\"],\n  },\n  async rewrites() {\n    return [\n      {\n        source: \"/docs/:path*.mdx\",\n        destination: \"/llms.mdx/:path*\",\n      },\n    ];\n  },\n  experimental: {\n    turbopackFileSystemCacheForDev: true,\n  },\n  serverExternalPackages: [\"create-better-t-stack\", \"fs-extra\", \"tinyglobby\", \"handlebars\"],\n};\n\nexport default withMDX(config);\n"
  },
  {
    "path": "apps/web/package.json",
    "content": "{\n  \"name\": \"web\",\n  \"version\": \"0.0.0\",\n  \"private\": true,\n  \"scripts\": {\n    \"prebuild\": \"(cd ../../packages/types && bun run build) && (cd ../../packages/template-generator && bun run build)\",\n    \"build\": \"next build\",\n    \"dev\": \"next dev --port 3333\",\n    \"start\": \"next start\",\n    \"postinstall\": \"fumadocs-mdx\",\n    \"generate-schema\": \"bun scripts/generate-schema.ts\"\n  },\n  \"dependencies\": {\n    \"@base-ui/react\": \"^1.4.1\",\n    \"@base-ui/utils\": \"^0.2.8\",\n    \"@better-t-stack/backend\": \"workspace:*\",\n    \"@better-t-stack/template-generator\": \"workspace:*\",\n    \"@better-t-stack/types\": \"workspace:*\",\n    \"@erquhart/convex-oss-stats\": \"catalog:\",\n    \"@number-flow/react\": \"^0.6.0\",\n    \"@orama/orama\": \"^3.1.18\",\n    \"@shikijs/transformers\": \"^4.0.2\",\n    \"babel-plugin-react-compiler\": \"^1.0.0\",\n    \"class-variance-authority\": \"^0.7.1\",\n    \"clsx\": \"^2.1.1\",\n    \"convex\": \"catalog:\",\n    \"convex-helpers\": \"catalog:\",\n    \"create-better-t-stack\": \"workspace:*\",\n    \"culori\": \"^4.0.2\",\n    \"date-fns\": \"^4.1.0\",\n    \"fumadocs-core\": \"16.8.1\",\n    \"fumadocs-mdx\": \"14.3.1\",\n    \"fumadocs-ui\": \"npm:@fumadocs/base-ui@16.8.1\",\n    \"lucide-react\": \"^1.8.0\",\n    \"motion\": \"^12.38.0\",\n    \"next\": \"^16.2.4\",\n    \"next-themes\": \"^0.4.6\",\n    \"nuqs\": \"^2.8.8\",\n    \"papaparse\": \"^5.5.3\",\n    \"qrcode\": \"^1.5.4\",\n    \"radix-ui\": \"^1.4.3\",\n    \"react\": \"^19.2.5\",\n    \"react-dom\": \"^19.2.5\",\n    \"react-icons\": \"^5.5.0\",\n    \"react-tweet\": \"^3.3.0\",\n    \"recharts\": \"^3.8.1\",\n    \"remark\": \"^15.0.1\",\n    \"remark-gfm\": \"^4.0.1\",\n    \"remark-mdx\": \"^3.1.1\",\n    \"shiki\": \"^4.0.2\",\n    \"sonner\": \"^2.0.7\",\n    \"tailwind-merge\": \"^3.4.0\",\n    \"zod\": \"catalog:\"\n  },\n  \"devDependencies\": {\n    \"@tailwindcss/postcss\": \"^4.2.4\",\n    \"@types/culori\": \"^4.0.1\",\n    \"@types/mdx\": \"^2.0.13\",\n    \"@types/node\": \"catalog:\",\n    \"@types/papaparse\": \"^5.5.2\",\n    \"@types/qrcode\": \"^1.5.6\",\n    \"@types/react\": \"19.2.14\",\n    \"@types/react-dom\": \"19.2.3\",\n    \"eslint\": \"^10.2.1\",\n    \"eslint-config-next\": \"16.2.4\",\n    \"postcss\": \"^8.5.10\",\n    \"tailwindcss\": \"^4.2.4\",\n    \"tw-animate-css\": \"^1.4.0\",\n    \"typescript\": \"catalog:\"\n  }\n}\n"
  },
  {
    "path": "apps/web/postcss.config.mjs",
    "content": "export default {\n  plugins: {\n    \"@tailwindcss/postcss\": {},\n  },\n};\n"
  },
  {
    "path": "apps/web/public/_headers",
    "content": "/_next/static/*\n  Cache-Control: public,max-age=31536000,immutable\n"
  },
  {
    "path": "apps/web/public/favicon/site.webmanifest",
    "content": "{\n  \"name\": \"Better T Stack\",\n  \"short_name\": \"Better T Stack\",\n  \"icons\": [\n    {\n      \"src\": \"/web-app-manifest-192x192.png\",\n      \"sizes\": \"192x192\",\n      \"type\": \"image/png\",\n      \"purpose\": \"maskable\"\n    },\n    {\n      \"src\": \"/web-app-manifest-512x512.png\",\n      \"sizes\": \"512x512\",\n      \"type\": \"image/png\",\n      \"purpose\": \"maskable\"\n    }\n  ],\n  \"theme_color\": \"#ffffff\",\n  \"background_color\": \"#ffffff\",\n  \"display\": \"standalone\"\n}\n"
  },
  {
    "path": "apps/web/public/robots.txt",
    "content": "User-agent: *\nAllow: /\n\nSitemap: https://better-t-stack.dev/sitemap.xml\n"
  },
  {
    "path": "apps/web/scripts/generate-schema.ts",
    "content": "import { execSync } from \"node:child_process\";\nimport { writeFileSync } from \"node:fs\";\nimport { tmpdir } from \"node:os\";\nimport { join } from \"node:path\";\n\nimport { BetterTStackConfigFileSchema } from \"@better-t-stack/types\";\nimport { z } from \"zod\";\n\nconst schema = z.toJSONSchema(BetterTStackConfigFileSchema, { target: \"draft-7\" });\nconst tempPath = join(tmpdir(), \"bts-schema.json\");\n\nwriteFileSync(tempPath, JSON.stringify(schema, null, 2));\nexecSync(`npx wrangler r2 object put \"bucket/schema.json\" --file=\"${tempPath}\" --remote`, {\n  stdio: \"inherit\",\n});\n\nconsole.log(\"Uploaded schema.json to R2\");\n"
  },
  {
    "path": "apps/web/source.config.ts",
    "content": "import { defineConfig, defineDocs, frontmatterSchema, metaSchema } from \"fumadocs-mdx/config\";\nimport { z } from \"zod\";\n\nexport const docs = defineDocs({\n  dir: \"content/docs\",\n  docs: {\n    postprocess: {\n      includeProcessedMarkdown: true,\n    },\n    schema: frontmatterSchema.extend({\n      author: z\n        .object({\n          name: z.string(),\n          url: z.string().url().optional(),\n        })\n        .optional(),\n      date: z.string().optional(),\n    }),\n  },\n  meta: {\n    schema: metaSchema,\n  },\n});\n\nexport default defineConfig({\n  mdxOptions: {\n    remarkNpmOptions: {\n      persist: {\n        id: \"package-manager\",\n      },\n    },\n  },\n});\n"
  },
  {
    "path": "apps/web/src/app/(home)/_components/FeatureCard.tsx",
    "content": "\"use client\";\n\nimport { motion } from \"motion/react\";\nimport { useTheme } from \"next-themes\";\nimport Image from \"next/image\";\n\nimport { ScrollArea } from \"@/components/ui/scroll-area\";\nimport { cn } from \"@/lib/utils\";\n\ntype TechOption = {\n  id: string;\n  name: string;\n  icon: string;\n};\n\ntype FeatureCardProps = {\n  title: string;\n  description?: string;\n  options: TechOption[];\n  className?: string;\n};\n\nfunction TechIcon({ icon, name, className }: { icon: string; name: string; className?: string }) {\n  const { theme } = useTheme();\n\n  if (!icon) return null;\n\n  if (!icon.startsWith(\"https://\")) {\n    return (\n      <span className={cn(\"flex h-6 w-6 items-center justify-center text-2xl\", className)}>\n        {icon}\n      </span>\n    );\n  }\n\n  let iconSrc = icon;\n  if (\n    theme === \"light\" &&\n    (icon.includes(\"drizzle\") ||\n      icon.includes(\"prisma\") ||\n      icon.includes(\"express\") ||\n      icon.includes(\"astro\"))\n  ) {\n    iconSrc = icon.replace(\".svg\", \"-light.svg\");\n  }\n\n  return (\n    <Image\n      src={iconSrc}\n      alt={`${name} icon`}\n      width={24}\n      height={24}\n      className={cn(\"h-6 w-6 object-contain\", className)}\n      unoptimized\n    />\n  );\n}\n\nexport default function FeatureCard({ title, options, className }: FeatureCardProps) {\n  return (\n    <motion.div\n      className={cn(\n        \"relative flex h-36 flex-col overflow-hidden rounded border border-border bg-fd-background p-2 shadow-sm\",\n        className,\n      )}\n      layout\n    >\n      <div>\n        <h4 className=\"pb-2 text-center font-semibold text-foreground text-sm\">{title}</h4>\n      </div>\n      <div className=\"flex-1 overflow-hidden\">\n        <ScrollArea className=\"h-full w-full\">\n          <ul className=\"grid grid-cols-3 gap-2 p-1\">\n            {options.map((option) => (\n              <li key={option.id} title={option.name} className=\"flex items-center justify-center\">\n                {/* {option.icon.startsWith(\"/\") ? (\n\t\t\t\t\t\t\t\t\t<Image\n\t\t\t\t\t\t\t\t\t\tsrc={option.icon}\n\t\t\t\t\t\t\t\t\t\talt={option.name}\n\t\t\t\t\t\t\t\t\t\twidth={24}\n\t\t\t\t\t\t\t\t\t\theight={24}\n\t\t\t\t\t\t\t\t\t\tclassName=\"h-6 w-6 object-contain\"\n\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t) : (\n\t\t\t\t\t\t\t\t\t<span className=\"flex h-6 w-6 items-center justify-center text-2xl\">\n\t\t\t\t\t\t\t\t\t\t{option.icon}\n\t\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t\t)} */}\n                <TechIcon\n                  icon={option.icon}\n                  name={option.name}\n                  className=\"h-6 w-6 object-contain\"\n                />\n              </li>\n            ))}\n          </ul>\n        </ScrollArea>\n      </div>\n    </motion.div>\n  );\n}\n"
  },
  {
    "path": "apps/web/src/app/(home)/_components/code-container.tsx",
    "content": "\"use client\";\nimport { Check, ClipboardCopy } from \"lucide-react\";\nimport { AnimatePresence, motion } from \"motion/react\";\nimport { useState } from \"react\";\n\nimport { cn } from \"@/lib/utils\";\n\nimport PackageIcon from \"./icons\";\n\nconst CodeContainer = () => {\n  const [selectedPM, setSelectedPM] = useState<\"npm\" | \"pnpm\" | \"bun\">(\"bun\");\n  const [copied, setCopied] = useState(false);\n\n  const commands = {\n    npm: \"npx create-better-t-stack@latest\",\n    pnpm: \"pnpm create better-t-stack@latest\",\n    bun: \"bun create better-t-stack@latest\",\n  };\n\n  const copyToClipboard = async () => {\n    if (copied) return;\n    await navigator.clipboard.writeText(commands[selectedPM]);\n    setCopied(true);\n    setTimeout(() => setCopied(false), 2000);\n  };\n\n  const packageManagers: Array<\"npm\" | \"pnpm\" | \"bun\"> = [\"bun\", \"pnpm\", \"npm\"];\n\n  return (\n    <div className=\"mx-auto mt-6 w-full max-w-3xl px-2 md:px-0\">\n      <div className=\"overflow-hidden rounded-lg border border-border bg-muted/30 shadow-sm\">\n        <div className=\"flex items-center justify-between border-border border-b bg-muted/50 px-4 py-2\">\n          <span className=\"text-muted-foreground text-xs\">Package manager:</span>\n          <div className=\"flex items-center rounded-md border border-border p-0.5\">\n            {packageManagers.map((pm) => (\n              <button\n                type=\"button\"\n                key={pm}\n                onClick={() => setSelectedPM(pm)}\n                className={cn(\n                  \"flex items-center gap-1.5 rounded-[5px] px-2.5 py-1 text-xs transition-colors duration-150\",\n                  selectedPM === pm\n                    ? \"bg-primary/10 text-primary shadow-sm\"\n                    : \"text-muted-foreground hover:text-foreground\",\n                )}\n              >\n                <PackageIcon pm={pm} className=\"size-3.5\" />\n                {pm}\n              </button>\n            ))}\n          </div>\n        </div>\n\n        <div className=\"relative p-4 text-sm\">\n          <div className=\"flex items-center gap-2 overflow-x-auto pb-1\">\n            <span className=\"select-none text-muted-foreground\">$</span>\n            <code className=\"whitespace-pre text-foreground\">{commands[selectedPM]}</code>\n          </div>\n\n          <div className=\"absolute top-3 right-3\">\n            <motion.button\n              type=\"button\"\n              onClick={copyToClipboard}\n              className={cn(\n                \"flex h-7 w-7 items-center justify-center rounded border text-muted-foreground transition-all duration-150 hover:border-border hover:bg-muted hover:text-foreground\",\n                copied ? \"border-chart-4/50 bg-chart-4/10 text-chart-4\" : \"border-border\",\n              )}\n              aria-label={copied ? \"Copied\" : \"Copy command\"}\n              whileTap={{ scale: 0.9 }}\n            >\n              <AnimatePresence mode=\"wait\" initial={false}>\n                {copied ? (\n                  <motion.div\n                    key=\"check\"\n                    initial={{ scale: 0.5, opacity: 0 }}\n                    animate={{ scale: 1, opacity: 1 }}\n                    exit={{ scale: 0.5, opacity: 0 }}\n                    transition={{ duration: 0.15 }}\n                  >\n                    <Check className=\"size-4\" />\n                  </motion.div>\n                ) : (\n                  <motion.div\n                    key=\"copy\"\n                    initial={{ scale: 0.5, opacity: 0 }}\n                    animate={{ scale: 1, opacity: 1 }}\n                    exit={{ scale: 0.5, opacity: 0 }}\n                    transition={{ duration: 0.15 }}\n                  >\n                    <ClipboardCopy className=\"size-4\" />\n                  </motion.div>\n                )}\n              </AnimatePresence>\n            </motion.button>\n          </div>\n        </div>\n      </div>\n    </div>\n  );\n};\n\nexport default CodeContainer;\n"
  },
  {
    "path": "apps/web/src/app/(home)/_components/command-section.tsx",
    "content": "\"use client\";\nimport { Check, ChevronDown, ChevronRight, Copy, Terminal, Zap } from \"lucide-react\";\nimport Link from \"next/link\";\nimport { useState } from \"react\";\n\nimport {\n  DropdownMenu,\n  DropdownMenuContent,\n  DropdownMenuItem,\n  DropdownMenuTrigger,\n} from \"@/components/ui/dropdown-menu\";\nimport { cn } from \"@/lib/utils\";\n\nimport PackageIcon from \"./icons\";\n\nexport default function CommandSection() {\n  const [copiedCommand, setCopiedCommand] = useState<string | null>(null);\n  const [selectedPM, setSelectedPM] = useState<\"npm\" | \"pnpm\" | \"bun\">(\"bun\");\n\n  const commands = {\n    npm: \"npx create-better-t-stack@latest\",\n    pnpm: \"pnpm create better-t-stack@latest\",\n    bun: \"bun create better-t-stack@latest\",\n  };\n\n  const copyCommand = (command: string, packageManager: string) => {\n    navigator.clipboard.writeText(command);\n    setCopiedCommand(packageManager);\n    setTimeout(() => setCopiedCommand(null), 2000);\n  };\n\n  return (\n    <div className=\"grid grid-cols-1 gap-4 lg:grid-cols-2\">\n      <div className=\"flex h-full flex-col justify-between rounded-2xl bg-fd-background/75 p-4 transition-colors hover:bg-muted/10\">\n        <div className=\"mb-4 flex items-center justify-between\">\n          <div className=\"flex items-center gap-2\">\n            <Terminal className=\"h-4 w-4 text-primary\" />\n            <span className=\"font-bold font-mono text-lg sm:text-xl\">CLI_COMMAND</span>\n          </div>\n          <DropdownMenu>\n            <DropdownMenuTrigger\n              render={\n                <button\n                  type=\"button\"\n                  className=\"flex items-center gap-2 rounded-md bg-muted/20 px-3 py-1.5 font-mono text-xs transition-colors hover:bg-muted/35\"\n                />\n              }\n            >\n              <PackageIcon pm={selectedPM} className=\"h-3 w-3\" />\n              <span>{selectedPM.toUpperCase()}</span>\n              <ChevronDown className=\"h-3 w-3\" />\n            </DropdownMenuTrigger>\n            <DropdownMenuContent align=\"end\">\n              {([\"bun\", \"pnpm\", \"npm\"] as const).map((pm) => (\n                <DropdownMenuItem\n                  key={pm}\n                  onClick={() => setSelectedPM(pm)}\n                  className={cn(\n                    \"flex items-center gap-2\",\n                    selectedPM === pm && \"bg-accent text-background\",\n                  )}\n                >\n                  <PackageIcon pm={pm} className=\"h-3 w-3\" />\n                  <span>{pm.toUpperCase()}</span>\n                  {selectedPM === pm && <Check className=\"ml-auto h-3 w-3 text-background\" />}\n                </DropdownMenuItem>\n              ))}\n            </DropdownMenuContent>\n          </DropdownMenu>\n        </div>\n\n        <div className=\"space-y-3\">\n          <div\n            role=\"button\"\n            tabIndex={0}\n            className=\"builder-focus-ring flex cursor-pointer items-center justify-between rounded-xl bg-muted/20 p-3\"\n            onClick={() => copyCommand(commands[selectedPM], selectedPM)}\n            onKeyDown={(event) => {\n              if (event.key === \"Enter\" || event.key === \" \") {\n                event.preventDefault();\n                copyCommand(commands[selectedPM], selectedPM);\n              }\n            }}\n            aria-label={`Copy ${selectedPM} command`}\n            title=\"Click to copy command\"\n          >\n            <div className=\"flex items-center gap-2 font-mono text-sm\">\n              <span className=\"text-primary\">$</span>\n              <span className=\"text-foreground\">{commands[selectedPM]}</span>\n            </div>\n            <span className=\"flex items-center gap-1 rounded-md bg-muted/20 px-2 py-1 font-mono text-xs transition-colors group-hover:bg-muted/35\">\n              {copiedCommand === selectedPM ? (\n                <Check className=\"h-3 w-3 text-primary\" />\n              ) : (\n                <Copy className=\"h-3 w-3\" />\n              )}\n              {copiedCommand === selectedPM ? \"COPIED!\" : \"COPY\"}\n            </span>\n          </div>\n        </div>\n      </div>\n\n      <Link href=\"/new\">\n        <div className=\"group flex h-full cursor-pointer flex-col justify-between rounded-2xl bg-fd-background/75 p-4 transition-colors hover:bg-muted/10\">\n          <div className=\"mb-4 flex items-center justify-between\">\n            <div className=\"flex items-center gap-2\">\n              <ChevronRight className=\"h-4 w-4 text-primary transition-transform group-hover:translate-x-1\" />\n              <span className=\"font-bold font-mono text-lg sm:text-xl\">STACK_BUILDER</span>\n            </div>\n            <div className=\"rounded-md bg-primary/15 px-2 py-1 font-mono text-xs text-primary\">\n              INTERACTIVE\n            </div>\n          </div>\n\n          <div className=\"space-y-3\">\n            <div className=\"flex items-center justify-between rounded-xl bg-muted/20 p-3\">\n              <div className=\"flex items-center gap-2 text-sm\">\n                <Zap className=\"h-4 w-4 text-primary\" />\n                <span className=\"text-foreground\">Interactive configuration wizard</span>\n              </div>\n              <div className=\"rounded-md bg-primary px-2 py-1 font-mono text-primary-foreground text-xs transition-colors group-hover:bg-primary/90\">\n                START\n              </div>\n            </div>\n          </div>\n        </div>\n      </Link>\n    </div>\n  );\n}\n"
  },
  {
    "path": "apps/web/src/app/(home)/_components/footer.tsx",
    "content": "import { Terminal } from \"lucide-react\";\nimport Image from \"next/image\";\nimport Link from \"next/link\";\nimport { FaGithub } from \"react-icons/fa6\";\n\nimport npmIcon from \"@/public/icon/npm.svg\";\n\nconst Footer = () => {\n  return (\n    <footer className=\"relative w-full border-border border-t\">\n      <div className=\"container mx-auto px-4 py-8 sm:px-6 sm:py-12 lg:px-8\">\n        <div className=\"mb-8 grid gap-8 sm:mb-12 sm:grid-cols-2 lg:grid-cols-3 lg:gap-12\">\n          <div className=\"sm:col-span-2 lg:col-span-1\">\n            <h3 className=\"mb-3 flex items-center gap-2 font-semibold font-mono text-base text-foreground sm:mb-4\">\n              <Terminal className=\"h-4 w-4 text-primary\" />\n              <span>BETTER_T_STACK.INFO</span>\n            </h3>\n            <p className=\"mb-4 font-mono text-muted-foreground text-sm leading-relaxed sm:mb-6 sm:text-base lg:pr-4\">\n              Type-safe, modern TypeScript scaffolding for full-stack web development\n            </p>\n            <div className=\"flex gap-2\">\n              <Link\n                href=\"https://github.com/AmanVarshney01/create-better-t-stack\"\n                target=\"_blank\"\n                className=\"inline-flex items-center justify-center rounded border border-border p-2 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground focus:outline-none focus:ring-2 focus:ring-primary focus:ring-offset-2\"\n                aria-label=\"GitHub Repository\"\n              >\n                <FaGithub size={20} />\n              </Link>\n              <Link\n                href=\"https://www.npmjs.com/package/create-better-t-stack\"\n                target=\"_blank\"\n                className=\"inline-flex items-center justify-center rounded border border-border p-2 text-muted-foreground invert-0 transition-colors hover:bg-muted hover:text-foreground focus:outline-none focus:ring-2 focus:ring-primary focus:ring-offset-2 dark:invert\"\n                aria-label=\"NPM Package\"\n              >\n                <Image src={npmIcon} alt=\"NPM\" width={20} height={20} />\n              </Link>\n            </div>\n          </div>\n\n          <div>\n            <h3 className=\"mb-3 font-semibold font-mono text-base text-foreground sm:mb-4\">\n              RESOURCES.LIST\n            </h3>\n            <ul className=\"space-y-2 font-mono text-muted-foreground text-sm sm:space-y-3 sm:text-base\">\n              <li>\n                <Link\n                  target=\"_blank\"\n                  href=\"https://github.com/AmanVarshney01/create-better-t-stack\"\n                  className=\"inline-block transition-colors hover:text-primary focus:text-primary focus:outline-none\"\n                >\n                  GitHub Repository\n                </Link>\n              </li>\n              <li>\n                <Link\n                  target=\"_blank\"\n                  href=\"https://www.npmjs.com/package/create-better-t-stack\"\n                  className=\"inline-block transition-colors hover:text-primary focus:text-primary focus:outline-none\"\n                >\n                  NPM Package\n                </Link>\n              </li>\n              <li>\n                <Link\n                  target=\"_blank\"\n                  href=\"https://my-better-t-app-client.pages.dev/\"\n                  className=\"inline-block transition-colors hover:text-primary focus:text-primary focus:outline-none\"\n                >\n                  Demo Application\n                </Link>\n              </li>\n            </ul>\n          </div>\n\n          <div>\n            <h3 className=\"mb-3 font-semibold font-mono text-base text-foreground sm:mb-4\">\n              CONTACT.ENV\n            </h3>\n            <div className=\"space-y-3 font-mono text-muted-foreground text-sm sm:space-y-4 sm:text-base\">\n              <div className=\"flex flex-col gap-2 sm:flex-row sm:items-center\">\n                <span className=\"inline-flex w-fit rounded bg-muted px-2 py-1 font-mono text-xs sm:text-sm\">\n                  $\n                </span>\n                <span className=\"break-all sm:break-normal\">amanvarshney.work@gmail.com</span>\n              </div>\n              <p className=\"text-sm leading-relaxed sm:text-base\">\n                Have questions or feedback? Feel free to reach out or open an issue on GitHub.\n              </p>\n            </div>\n          </div>\n        </div>\n\n        <div className=\"flex flex-col items-center justify-between gap-4 border-border border-t pt-6 sm:flex-row sm:gap-6 sm:pt-8\">\n          <p className=\"text-center font-mono text-muted-foreground text-xs sm:text-left sm:text-sm\">\n            © {new Date().getFullYear()} Better-T-Stack. All rights reserved.\n          </p>\n          <p className=\"flex items-center gap-1.5 font-mono text-muted-foreground text-xs sm:text-sm\">\n            <span className=\"text-primary\">$</span> Built with{\" \"}\n            <span className=\"bg-linear-to-r from-primary to-primary/80 bg-clip-text font-medium text-transparent\">\n              TypeScript\n            </span>\n          </p>\n        </div>\n      </div>\n    </footer>\n  );\n};\n\nexport default Footer;\n"
  },
  {
    "path": "apps/web/src/app/(home)/_components/hero-section.tsx",
    "content": "import NpmPackage from \"./npm-package\";\n\nexport default function HeroSection() {\n  return (\n    <section className=\"rounded-2xl bg-fd-background px-4 py-6 sm:px-6 sm:py-8\">\n      <div className=\"relative mb-6 flex items-center justify-center rounded-2xl bg-fd-background px-3 py-4 sm:px-4 sm:py-5\">\n        <div className=\"flex flex-wrap items-center justify-center gap-2 sm:gap-4 md:gap-6\">\n          <pre className=\"ascii-art text-primary text-xs leading-tight sm:text-sm\">\n            {`\n██████╗  ██████╗ ██╗     ██╗\n██╔══██╗██╔═══██╗██║     ██║\n██████╔╝██║   ██║██║     ██║\n██╔══██╗██║   ██║██║     ██║\n██║  ██║╚██████╔╝███████╗███████╗\n╚═╝  ╚═╝ ╚═════╝ ╚══════╝╚══════╝`}\n          </pre>\n\n          <pre className=\"ascii-art text-primary text-xs leading-tight sm:text-sm\">\n            {`\n██╗   ██╗ ██████╗ ██╗   ██╗██████╗\n╚██╗ ██╔╝██╔═══██╗██║   ██║██╔══██╗\n ╚████╔╝ ██║   ██║██║   ██║██████╔╝\n  ╚██╔╝  ██║   ██║██║   ██║██╔══██╗\n   ██║   ╚██████╔╝╚██████╔╝██║  ██║\n   ╚═╝    ╚═════╝  ╚═════╝ ╚═╝  ╚═╝`}\n          </pre>\n\n          <pre className=\"ascii-art text-primary text-xs leading-tight sm:text-sm\">\n            {`\n ██████╗ ██╗    ██╗███╗   ██╗\n██╔═══██╗██║    ██║████╗  ██║\n██║   ██║██║ █╗ ██║██╔██╗ ██║\n██║   ██║██║███╗██║██║╚██╗██║\n╚██████╔╝╚███╔███╔╝██║ ╚████║\n ╚═════╝  ╚══╝╚══╝ ╚═╝  ╚═══╝`}\n          </pre>\n\n          <pre className=\"ascii-art text-primary text-xs leading-tight sm:text-sm\">\n            {`\n███████╗████████╗ █████╗  ██████╗██╗  ██╗\n██╔════╝╚══██╔══╝██╔══██╗██╔════╝██║ ██╔╝\n███████╗   ██║   ███████║██║     █████╔╝\n╚════██║   ██║   ██╔══██║██║     ██╔═██╗\n███████║   ██║   ██║  ██║╚██████╗██║  ██╗\n╚══════╝   ╚═╝   ╚═╝  ╚═╝ ╚═════╝╚═╝  ╚═╝`}\n          </pre>\n        </div>\n      </div>\n\n      <div className=\"text-center\">\n        <p className=\"mx-auto max-w-3xl font-mono text-base text-muted-foreground sm:text-lg\">\n          Modern CLI for scaffolding end-to-end type-safe TypeScript projects\n        </p>\n        <NpmPackage />\n      </div>\n    </section>\n  );\n}\n"
  },
  {
    "path": "apps/web/src/app/(home)/_components/icons.tsx",
    "content": "const PackageIcon = ({ pm, className }: { pm: string; className?: string }) => {\n  switch (pm) {\n    case \"npm\":\n      return (\n        <svg className={className} viewBox=\"0 0 24 24\" fill=\"currentColor\">\n          <title>npm</title>\n          <path d=\"M1.763 0C.786 0 0 .786 0 1.763v20.474C0 23.214.786 24 1.763 24h20.474c.977 0 1.763-.786 1.763-1.763V1.763C24 .786 23.214 0 22.237 0zM5.13 5.323l13.837.019-.009 13.836h-3.464l.01-10.382h-3.456L12.04 19.17H5.113z\" />\n        </svg>\n      );\n    case \"pnpm\":\n      return (\n        <svg\n          className={className}\n          width=\"800px\"\n          height=\"800px\"\n          viewBox=\"0 0 32 32\"\n          xmlns=\"http://www.w3.org/2000/svg\"\n        >\n          <title>pnpm</title>\n          <path d=\"M30,10.75H21.251V2H30Z\" style={{ fill: \"#f9ad00\" }} />\n          <path d=\"M20.374,10.75h-8.75V2h8.75Z\" style={{ fill: \"#f9ad00\" }} />\n          <path d=\"M10.749,10.75H2V2h8.749Z\" style={{ fill: \"#f9ad00\" }} />\n          <path d=\"M30,20.375H21.251v-8.75H30Z\" style={{ fill: \"#f9ad00\" }} />\n          <path d=\"M20.374,20.375h-8.75v-8.75h8.75Z\" style={{ fill: \"#fff\" }} />\n          <path d=\"M20.374,30h-8.75V21.25h8.75Z\" style={{ fill: \"#fff\" }} />\n          <path d=\"M30,30H21.251V21.25H30Z\" style={{ fill: \"#fff\" }} />\n          <path d=\"M10.749,30H2V21.25h8.749Z\" style={{ fill: \"#fff\" }} />\n        </svg>\n      );\n    case \"bun\":\n      return (\n        <svg\n          className={className}\n          xmlns=\"http://www.w3.org/2000/svg\"\n          fill=\"none\"\n          viewBox=\"0 0 100 100\"\n        >\n          <title>bun</title>\n          <path\n            fill=\"#000\"\n            d=\"M89.237 32.3c-.2-.213-.412-.425-.625-.625-.212-.2-.412-.425-.625-.625-.212-.2-.412-.425-.625-.625-.212-.2-.412-.425-.625-.625-.212-.2-.412-.425-.625-.625-.212-.2-.412-.425-.625-.625-.212-.2-.412-.425-.625-.625A33.08 33.08 0 0 1 94.75 51c0 20.712-21.025 37.562-46.875 37.562-14.475 0-27.425-5.287-36.038-13.575l.625.625.625.625.625.625.625.625.625.625.625.625.625.625c8.6 8.638 21.838 14.2 36.663 14.2 25.85 0 46.875-16.85 46.875-37.5 0-8.825-3.8-17.187-10.513-23.762\"\n          />\n          <path\n            fill=\"#FBF0DF\"\n            d=\"M91.625 51c0 19.012-19.588 34.425-43.75 34.425S4.125 70.012 4.125 51c0-11.788 7.5-22.2 19.025-28.375s18.7-12.5 24.725-12.5 11.175 5.162 24.725 12.5C84.125 28.8 91.625 39.212 91.625 51\"\n          />\n          <path\n            fill=\"#F6DECE\"\n            d=\"M91.625 51a27 27 0 0 0-1-7.225C87.213 85.4 36.438 87.4 16.475 74.95a50 50 0 0 0 31.4 10.475C72 85.425 91.625 69.987 91.625 51\"\n          />\n          <path\n            fill=\"#FFFEFC\"\n            d=\"M31.038 20.337c5.587-3.35 13.012-9.637 20.312-9.65a11.6 11.6 0 0 0-3.475-.562c-3.025 0-6.25 1.562-10.312 3.912-1.413.825-2.876 1.738-4.425 2.688-2.913 1.8-6.25 3.837-10 5.875C11.237 29.037 4.124 39.65 4.124 51v1.487c7.575-26.762 21.338-28.8 26.913-32.15\"\n          />\n          <path\n            fill=\"#CCBEA7\"\n            fillRule=\"evenodd\"\n            d=\"M44.275 13.287a20.51 20.51 0 0 1-7.037 15.588c-.35.312-.075.912.375.737 4.212-1.637 9.9-6.537 7.5-16.425-.1-.562-.838-.412-.838.1m2.838 0a20.3 20.3 0 0 1 2.012 16.838c-.15.437.388.812.688.45 2.737-3.5 5.125-10.45-2.025-17.95-.363-.325-.925.175-.675.612zm3.45-.212a20.52 20.52 0 0 1 8.562 14.7.412.412 0 0 0 .813.137c1.15-4.362.5-11.8-8.963-15.662-.5-.2-.825.475-.412.775zm-23.075 13a21.18 21.18 0 0 0 13.087-11.25c.225-.45.938-.275.825.225-2.162 10-9.4 12.087-13.9 11.812-.475.013-.462-.65-.012-.787\"\n            clipRule=\"evenodd\"\n          />\n          <path\n            fill=\"#000\"\n            d=\"M47.875 88.562C22.025 88.562 1 71.712 1 51c0-12.5 7.725-24.163 20.663-31.15 3.75-2 6.962-4.013 9.825-5.775a262 262 0 0 1 4.5-2.738C40.375 8.737 44.125 7 47.875 7S54.9 8.5 59 10.925c1.25.712 2.5 1.487 3.837 2.337 3.113 1.925 6.626 4.1 11.25 6.588C87.026 26.837 94.75 38.487 94.75 51c0 20.712-21.025 37.562-46.875 37.562m0-78.437c-3.025 0-6.25 1.562-10.312 3.912-1.413.825-2.876 1.738-4.425 2.688-2.913 1.8-6.25 3.837-10 5.875C11.237 29.037 4.124 39.65 4.124 51c0 18.987 19.625 34.437 43.75 34.437S91.625 69.987 91.625 51c0-11.35-7.112-21.963-19.025-28.375-4.725-2.5-8.412-4.85-11.4-6.7-1.363-.838-2.613-1.613-3.75-2.3-3.788-2.25-6.55-3.5-9.575-3.5\"\n          />\n          <path\n            fill=\"#B71422\"\n            d=\"M56.688 60.125a11.16 11.16 0 0 1-3.65 5.887 8.5 8.5 0 0 1-5 2.35 8.55 8.55 0 0 1-5.163-2.35 11.16 11.16 0 0 1-3.6-5.887.9.9 0 0 1 1-1.013H55.7a.9.9 0 0 1 .987 1.013\"\n          />\n          <path\n            fill=\"#FF6164\"\n            d=\"M42.875 66.112a8.64 8.64 0 0 0 5.15 2.375 8.64 8.64 0 0 0 5.137-2.375q.672-.625 1.25-1.337a8.54 8.54 0 0 0-6.125-2.888 7.69 7.69 0 0 0-6.25 3.475c.288.263.538.513.838.75\"\n          />\n          <path\n            fill=\"#000\"\n            d=\"M43.075 65.125a6.7 6.7 0 0 1 5.237-2.6 7.5 7.5 0 0 1 5 2.112c.288-.312.563-.637.825-.962a8.75 8.75 0 0 0-5.887-2.413 7.95 7.95 0 0 0-6.112 2.95q.443.482.937.913\"\n          />\n          <path\n            fill=\"#000\"\n            d=\"M47.987 69.112a9.28 9.28 0 0 1-5.562-2.5 11.9 11.9 0 0 1-3.888-6.312 1.5 1.5 0 0 1 .325-1.25 1.76 1.76 0 0 1 1.413-.638H55.7a1.8 1.8 0 0 1 1.412.638 1.49 1.49 0 0 1 .313 1.25 11.9 11.9 0 0 1-3.888 6.312 9.27 9.27 0 0 1-5.55 2.5m-7.712-9.25c-.2 0-.25.088-.263.113a10.36 10.36 0 0 0 3.413 5.462 7.8 7.8 0 0 0 4.562 2.188 7.85 7.85 0 0 0 4.563-2.163A10.38 10.38 0 0 0 55.95 60a.26.26 0 0 0-.25-.113z\"\n          />\n          <path\n            fill=\"#FEBBD0\"\n            d=\"M66.9 60.9c4.038 0 7.312-1.926 7.312-4.3 0-2.375-3.273-4.3-7.312-4.3s-7.313 1.925-7.313 4.3 3.274 4.3 7.313 4.3m-37.837 0c4.038 0 7.312-1.926 7.312-4.3 0-2.375-3.274-4.3-7.312-4.3-4.04 0-7.313 1.925-7.313 4.3s3.274 4.3 7.313 4.3\"\n          />\n          <path\n            fill=\"#000\"\n            fillRule=\"evenodd\"\n            d=\"M32.5 54.875a6.888 6.888 0 1 0 .025-13.775 6.888 6.888 0 0 0-.025 13.775m30.963 0a6.887 6.887 0 1 0-6.838-6.888 6.875 6.875 0 0 0 6.837 6.888\"\n            clipRule=\"evenodd\"\n          />\n          <path\n            fill=\"#fff\"\n            fillRule=\"evenodd\"\n            d=\"M30.375 48.425a2.588 2.588 0 1 0 .025-5.176 2.588 2.588 0 0 0-.025 5.176m30.963 0a2.588 2.588 0 1 0-.026 0z\"\n            clipRule=\"evenodd\"\n          />\n        </svg>\n      );\n\n    case \"github\":\n      return (\n        <svg className={className} xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 97.63 96.03\">\n          <title>Github</title>\n          <path\n            fill=\"#f0f6fc\"\n            fillRule=\"evenodd\"\n            d=\"M48.854 0C21.839 0 0 22 0 49.217c0 21.756 13.993 40.172 33.405 46.69 2.427.49 3.316-1.059 3.316-2.362 0-1.141-.08-5.052-.08-9.127-13.59 2.934-16.42-5.867-16.42-5.867-2.184-5.704-5.42-7.17-5.42-7.17-4.448-3.015.324-3.015.324-3.015 4.934.326 7.523 5.052 7.523 5.052 4.367 7.496 11.404 5.378 14.235 4.074.404-3.178 1.699-5.378 3.074-6.6-10.839-1.141-22.243-5.378-22.243-24.283 0-5.378 1.94-9.778 5.014-13.2-.485-1.222-2.184-6.275.486-13.038 0 0 4.125-1.304 13.426 5.052a47 47 0 0 1 12.214-1.63c4.125 0 8.33.571 12.213 1.63 9.302-6.356 13.427-5.052 13.427-5.052 2.67 6.763.97 11.816.485 13.038 3.155 3.422 5.015 7.822 5.015 13.2 0 18.905-11.404 23.06-22.324 24.283 1.78 1.548 3.316 4.481 3.316 9.126 0 6.6-.08 11.897-.08 13.526 0 1.304.89 2.853 3.316 2.364 19.412-6.52 33.405-24.935 33.405-46.691C97.707 22 75.788 0 48.854 0\"\n            clipRule=\"evenodd\"\n          />\n        </svg>\n      );\n    default:\n      return null;\n  }\n};\n\nexport default PackageIcon;\n"
  },
  {
    "path": "apps/web/src/app/(home)/_components/npm-package.tsx",
    "content": "\"use client\";\n\nimport { useEffect, useState } from \"react\";\n\nconst NpmPackage = () => {\n  const [version, setVersion] = useState(\"0.0.0\");\n\n  useEffect(() => {\n    const getLatestVersion = async () => {\n      try {\n        const res = await fetch(\"https://registry.npmjs.org/create-better-t-stack/latest\");\n        if (!res.ok) throw new Error(\"Failed to fetch version\");\n        const data = await res.json();\n        const latestVersion =\n          typeof data?.version === \"string\" && data.version.trim().length > 0\n            ? data.version\n            : \"latest\";\n        setVersion(latestVersion);\n      } catch (error) {\n        console.error(\"Error fetching NPM version:\", error);\n        setVersion(\"latest\");\n      }\n    };\n    getLatestVersion();\n  }, []);\n\n  return (\n    <div className=\"mt-2 flex items-center justify-center\">\n      <span className=\"mr-2 inline-block h-5 w-3 bg-primary\" />\n      <span className=\"text-muted-foreground text-xl\">[v{version}]</span>\n    </div>\n  );\n};\n\nexport default NpmPackage;\n"
  },
  {
    "path": "apps/web/src/app/(home)/_components/shiny-text.tsx",
    "content": "interface ShinyTextProps {\n  text: string;\n  disabled?: boolean;\n  speed?: number;\n  className?: string;\n}\n\nconst ShinyText = ({ text, disabled = false, speed = 5, className = \"\" }: ShinyTextProps) => {\n  const animationDuration = `${speed}s`;\n\n  return (\n    <div\n      className={`shiny-text ${disabled ? \"disabled\" : \"\"} ${className}`}\n      style={{ animationDuration }}\n    >\n      {text}\n    </div>\n  );\n};\n\nexport default ShinyText;\n"
  },
  {
    "path": "apps/web/src/app/(home)/_components/sponsors-section.tsx",
    "content": "\"use client\";\nimport { ChevronDown, ChevronUp, Globe, Heart, Star, Terminal } from \"lucide-react\";\nimport Image from \"next/image\";\nimport { useState } from \"react\";\nimport { FaGithub } from \"react-icons/fa6\";\n\nimport {\n  formatSponsorUrl,\n  getSponsorUrl,\n  isLifetimeSpecialSponsor,\n  shouldShowLifetimeTotal,\n} from \"@/lib/sponsor-utils\";\nimport type { SponsorsData } from \"@/lib/types\";\n\nexport default function SponsorsSection({ sponsorsData }: { sponsorsData: SponsorsData }) {\n  const [showPastSponsors, setShowPastSponsors] = useState(false);\n\n  const specialSponsors = sponsorsData.specialSponsors;\n  const regularSponsors = sponsorsData.sponsors;\n  const pastSponsors = sponsorsData.pastSponsors;\n\n  const totalCurrentSponsors = specialSponsors.length + regularSponsors.length;\n\n  return (\n    <div className=\"\">\n      <div className=\"mb-6 flex flex-wrap items-center justify-between gap-2 sm:flex-nowrap\">\n        <div className=\"flex items-center gap-2\">\n          <Terminal className=\"h-5 w-5 text-primary\" />\n          <span className=\"font-bold font-mono text-lg sm:text-xl\">SPONSORS_DATABASE.JSON</span>\n        </div>\n        <div className=\"hidden h-px flex-1 bg-border sm:block\" />\n        <span className=\"w-full text-right font-mono text-muted-foreground text-xs sm:w-auto sm:text-left\">\n          [{totalCurrentSponsors} RECORDS]\n        </span>\n      </div>\n      {totalCurrentSponsors === 0 ? (\n        <div className=\"space-y-4\">\n          <div className=\"rounded border border-border p-8\">\n            <div className=\"text-center\">\n              <div className=\"mb-4 flex items-center justify-center gap-2\">\n                <span className=\"font-mono text-muted-foreground\">NO_SPONSORS_FOUND.NULL</span>\n              </div>\n              <div className=\"flex items-center justify-center gap-2 text-sm\">\n                <span className=\"text-primary\">$</span>\n                <span className=\"text-muted-foreground\">Be the first to support this project!</span>\n              </div>\n            </div>\n          </div>\n          <div className=\"rounded border border-border p-4\">\n            <a\n              href=\"https://github.com/sponsors/AmanVarshney01\"\n              target=\"_blank\"\n              rel=\"noopener noreferrer\"\n              className=\"flex items-center justify-center gap-2 text-primary transition-colors hover:text-accent\"\n            >\n              <Heart className=\"h-4 w-4\" />\n              <span>BECOME_SPONSOR.SH</span>\n            </a>\n          </div>\n        </div>\n      ) : (\n        <div className=\"space-y-8\">\n          {specialSponsors.length > 0 && (\n            <div className=\"space-y-4\">\n              <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4\">\n                {specialSponsors.map((entry, index) => {\n                  const sponsorUrl = getSponsorUrl(entry);\n\n                  return (\n                    <div\n                      key={entry.githubId}\n                      className=\"rounded border border-border\"\n                      style={{ animationDelay: `${index * 50}ms` }}\n                    >\n                      <div className=\"border-border border-b px-3 py-2\">\n                        <div className=\"flex items-center gap-2\">\n                          <Star className=\"h-4 w-4 text-yellow-500/90\" />\n                          <div className=\"ml-auto flex items-center gap-2 text-muted-foreground text-xs\">\n                            <span>SPECIAL</span>\n                            <span>•</span>\n                            <span>{entry.sinceWhen.toUpperCase()}</span>\n                          </div>\n                        </div>\n                      </div>\n                      <div className=\"p-4\">\n                        <div className=\"flex gap-4\">\n                          <div className=\"flex-shrink-0\">\n                            <Image\n                              src={entry.avatarUrl}\n                              alt={entry.name}\n                              width={100}\n                              height={100}\n                              className=\"rounded border border-border transition-colors duration-300\"\n                              unoptimized\n                            />\n                          </div>\n                          <div className=\"grid grid-cols-1 grid-rows-[1fr_auto] justify-between py-2\">\n                            <div>\n                              <h3 className=\"truncate font-semibold text-foreground text-sm\">\n                                {entry.name}\n                              </h3>\n                              {shouldShowLifetimeTotal(entry) ? (\n                                <>\n                                  {entry.tierName && (\n                                    <p className=\"text-primary text-xs\">{entry.tierName}</p>\n                                  )}\n                                  <p className=\"text-muted-foreground text-xs\">\n                                    Total: {entry.formattedAmount}\n                                  </p>\n                                </>\n                              ) : (\n                                <p className=\"text-primary text-xs\">{entry.tierName}</p>\n                              )}\n                            </div>\n                            <div className=\"flex flex-col\">\n                              <a\n                                href={entry.githubUrl}\n                                target=\"_blank\"\n                                rel=\"noopener noreferrer\"\n                                className=\"group flex items-center gap-2 text-muted-foreground text-xs transition-colors hover:text-primary\"\n                              >\n                                <FaGithub className=\"size-3\" />\n                                <span className=\"truncate\">{entry.githubId}</span>\n                              </a>\n                              {entry.websiteUrl && (\n                                <a\n                                  href={sponsorUrl}\n                                  target=\"_blank\"\n                                  rel=\"noopener noreferrer\"\n                                  className=\"group flex items-center gap-2 text-muted-foreground text-xs transition-colors hover:text-primary\"\n                                >\n                                  <Globe className=\"size-3\" />\n                                  <span className=\"truncate\">{formatSponsorUrl(sponsorUrl)}</span>\n                                </a>\n                              )}\n                            </div>\n                          </div>\n                        </div>\n                      </div>\n                    </div>\n                  );\n                })}\n              </div>\n            </div>\n          )}\n\n          {regularSponsors.length > 0 && (\n            <div className=\"space-y-4\">\n              <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4\">\n                {regularSponsors.map((entry, index) => {\n                  const sponsorUrl = getSponsorUrl(entry);\n                  return (\n                    <div\n                      key={entry.githubId}\n                      className=\"rounded border border-border\"\n                      style={{ animationDelay: `${index * 50}ms` }}\n                    >\n                      <div className=\"border-border border-b px-3 py-2\">\n                        <div className=\"flex items-center gap-2\">\n                          <span className=\"text-primary text-xs\">▶</span>\n                          <div className=\"ml-auto flex items-center gap-2 text-muted-foreground text-xs\">\n                            <span>{entry.sinceWhen.toUpperCase()}</span>\n                          </div>\n                        </div>\n                      </div>\n                      <div className=\"p-4\">\n                        <div className=\"flex gap-4\">\n                          <div className=\"flex-shrink-0\">\n                            <Image\n                              src={entry.avatarUrl}\n                              alt={entry.name}\n                              width={100}\n                              height={100}\n                              className=\"rounded border border-border transition-colors duration-300\"\n                              unoptimized\n                            />\n                          </div>\n                          <div className=\"grid grid-cols-1 grid-rows-[1fr_auto] justify-between py-2\">\n                            <div>\n                              <h3 className=\"truncate font-semibold text-foreground text-sm\">\n                                {entry.name}\n                              </h3>\n                              {shouldShowLifetimeTotal(entry) ? (\n                                <>\n                                  {entry.tierName && (\n                                    <p className=\"text-primary text-xs\">{entry.tierName}</p>\n                                  )}\n                                  <p className=\"text-muted-foreground text-xs\">\n                                    Total: {entry.formattedAmount}\n                                  </p>\n                                </>\n                              ) : (\n                                <p className=\"text-primary text-xs\">{entry.tierName}</p>\n                              )}\n                            </div>\n                            <div className=\"flex flex-col\">\n                              <a\n                                href={entry.githubUrl}\n                                target=\"_blank\"\n                                rel=\"noopener noreferrer\"\n                                className=\"group flex items-center gap-2 text-muted-foreground text-xs transition-colors hover:text-primary\"\n                              >\n                                <FaGithub className=\"size-3\" />\n                                <span className=\"truncate\">{entry.githubId}</span>\n                              </a>\n                              {entry.websiteUrl && (\n                                <a\n                                  href={sponsorUrl}\n                                  target=\"_blank\"\n                                  rel=\"noopener noreferrer\"\n                                  className=\"group flex items-center gap-2 text-muted-foreground text-xs transition-colors hover:text-primary\"\n                                >\n                                  <Globe className=\"size-3\" />\n                                  <span className=\"truncate\">{formatSponsorUrl(sponsorUrl)}</span>\n                                </a>\n                              )}\n                            </div>\n                          </div>\n                        </div>\n                      </div>\n                    </div>\n                  );\n                })}\n              </div>\n            </div>\n          )}\n\n          {pastSponsors.length > 0 && (\n            <div className=\"space-y-4\">\n              <button\n                type=\"button\"\n                onClick={() => setShowPastSponsors(!showPastSponsors)}\n                className=\"flex w-full items-center gap-2 rounded border border-muted p-2 text-left transition-colors hover:bg-muted\"\n              >\n                {showPastSponsors ? (\n                  <ChevronUp className=\"h-4 w-4 text-muted-foreground\" />\n                ) : (\n                  <ChevronDown className=\"h-4 w-4 text-muted-foreground\" />\n                )}\n                <span className=\"font-semibold font-mono text-muted-foreground text-sm\">\n                  PAST_SPONSORS.ARCHIVE\n                </span>\n                <span className=\"text-muted-foreground text-xs\">({pastSponsors.length})</span>\n                <div className=\"mx-2 h-px flex-1 bg-border\" />\n                <span className=\"text-muted-foreground text-xs\">\n                  {showPastSponsors ? \"HIDE\" : \"SHOW\"}\n                </span>\n              </button>\n\n              {showPastSponsors && (\n                <div className=\"slide-in-from-top-2 grid animate-in grid-cols-1 gap-4 duration-300 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4\">\n                  {pastSponsors.map((entry, index) => {\n                    const wasSpecial = isLifetimeSpecialSponsor(entry);\n                    const sponsorUrl = getSponsorUrl(entry);\n\n                    return (\n                      <div\n                        key={entry.githubId}\n                        className=\"rounded border border-border/70 bg-muted/20\"\n                        style={{ animationDelay: `${index * 50}ms` }}\n                      >\n                        <div className=\"border-border/70 border-b px-3 py-2\">\n                          <div className=\"flex items-center gap-2\">\n                            {wasSpecial ? (\n                              <Star className=\"h-4 w-4 text-yellow-500/60\" />\n                            ) : (\n                              <span className=\"text-muted-foreground text-xs\">◆</span>\n                            )}\n                            <div className=\"ml-auto flex items-center gap-2 text-muted-foreground text-xs\">\n                              {wasSpecial && <span>SPECIAL</span>}\n                              {wasSpecial && <span>•</span>}\n                              <span>{entry.sinceWhen.toUpperCase()}</span>\n                            </div>\n                          </div>\n                        </div>\n                        <div className=\"p-4\">\n                          <div className=\"flex gap-4\">\n                            <div className=\"flex-shrink-0\">\n                              <Image\n                                src={entry.avatarUrl}\n                                alt={entry.name}\n                                width={80}\n                                height={80}\n                                className=\"rounded border border-border/70\"\n                                unoptimized\n                              />\n                            </div>\n                            <div className=\"grid grid-cols-1 grid-rows-[1fr_auto] justify-between\">\n                              <div>\n                                <h3 className=\"truncate font-semibold font-mono text-muted-foreground text-sm\">\n                                  {entry.name}\n                                </h3>\n                                {shouldShowLifetimeTotal(entry) ? (\n                                  <>\n                                    {entry.tierName && (\n                                      <p className=\"text-muted-foreground/70 text-xs\">\n                                        {entry.tierName}\n                                      </p>\n                                    )}\n                                    <p className=\"text-muted-foreground/50 text-xs\">\n                                      Total: {entry.formattedAmount}\n                                    </p>\n                                  </>\n                                ) : (\n                                  <p className=\"text-muted-foreground/70 text-xs\">\n                                    {entry.tierName}\n                                  </p>\n                                )}\n                              </div>\n                              <div className=\"flex flex-col\">\n                                <a\n                                  href={entry.githubUrl}\n                                  target=\"_blank\"\n                                  rel=\"noopener noreferrer\"\n                                  className=\"group flex items-center gap-2 text-muted-foreground/70 text-xs transition-colors hover:text-muted-foreground\"\n                                >\n                                  <FaGithub className=\"size-3\" />\n                                  <span className=\"truncate\">{entry.githubId}</span>\n                                </a>\n                                {entry.websiteUrl && (\n                                  <a\n                                    href={sponsorUrl}\n                                    target=\"_blank\"\n                                    rel=\"noopener noreferrer\"\n                                    className=\"group flex items-center gap-2 text-muted-foreground/70 text-xs transition-colors hover:text-muted-foreground\"\n                                  >\n                                    <Globe className=\"size-3\" />\n                                    <span className=\"truncate\">{formatSponsorUrl(sponsorUrl)}</span>\n                                  </a>\n                                )}\n                              </div>\n                            </div>\n                          </div>\n                        </div>\n                      </div>\n                    );\n                  })}\n                </div>\n              )}\n            </div>\n          )}\n\n          <div className=\"rounded border border-border p-4\">\n            <a\n              href=\"https://github.com/sponsors/AmanVarshney01\"\n              target=\"_blank\"\n              rel=\"noopener noreferrer\"\n              className=\"flex items-center justify-center gap-2 text-primary transition-colors hover:text-accent\"\n            >\n              <Heart className=\"h-4 w-4\" />\n              <span>SUPPORT_PROJECT.SH</span>\n            </a>\n          </div>\n        </div>\n      )}\n    </div>\n  );\n}\n"
  },
  {
    "path": "apps/web/src/app/(home)/_components/stats-section.tsx",
    "content": "\"use client\";\nimport { api } from \"@better-t-stack/backend/convex/_generated/api\";\nimport { useNpmDownloadCounter } from \"@erquhart/convex-oss-stats/react\";\nimport NumberFlow, { continuous } from \"@number-flow/react\";\nimport { useQuery } from \"convex/react\";\nimport { BarChart3, Package, Star, Terminal, TrendingUp, Users } from \"lucide-react\";\nimport Link from \"next/link\";\nimport { FaGithub } from \"react-icons/fa6\";\n\nexport default function StatsSection() {\n  const stats = useQuery(api.analytics.getStats, {});\n  const dailyStats = useQuery(api.analytics.getDailyStats, { days: 30 });\n  const githubRepo = useQuery(api.stats.getGithubRepo, {\n    name: \"AmanVarshney01/create-better-t-stack\",\n  });\n  const npmPackages = useQuery(api.stats.getNpmPackages, {\n    names: [\"create-better-t-stack\"],\n  });\n\n  const liveNpmDownloadCount = useNpmDownloadCounter(npmPackages);\n\n  const totalProjects = stats?.totalProjects ?? 0;\n  const avgProjectsPerDay =\n    dailyStats && dailyStats.length > 0 ? (totalProjects / dailyStats.length).toFixed(2) : \"0\";\n  const lastUpdated = stats?.lastEventTime\n    ? new Date(stats.lastEventTime).toLocaleDateString(\"en-US\", {\n        month: \"short\",\n        day: \"numeric\",\n        year: \"numeric\",\n      })\n    : null;\n\n  return (\n    <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3\">\n      <Link href=\"/analytics\">\n        <div className=\"group cursor-pointer rounded-2xl bg-fd-background/75 p-4 transition-colors hover:bg-muted/10\">\n          <div className=\"mb-3 flex items-center gap-2\">\n            <Terminal className=\"h-4 w-4 text-primary\" />\n            <span className=\"font-bold font-mono text-lg sm:text-xl\">CLI_ANALYTICS.JSON</span>\n          </div>\n\n          <div className=\"space-y-3\">\n            <div className=\"flex items-center justify-between\">\n              <span className=\"flex items-center gap-1 font-mono text-muted-foreground text-xs uppercase tracking-wide\">\n                <BarChart3 className=\"h-3 w-3\" />\n                Total Projects\n              </span>\n              <NumberFlow\n                value={totalProjects}\n                className=\"font-bold font-mono text-lg text-primary tabular-nums\"\n                transformTiming={{\n                  duration: 1000,\n                  easing: \"ease-out\",\n                }}\n                trend={1}\n                willChange\n                isolate\n              />\n            </div>\n\n            <div className=\"flex items-center justify-between\">\n              <span className=\"flex items-center gap-1 font-mono text-muted-foreground text-xs uppercase tracking-wide\">\n                <TrendingUp className=\"h-3 w-3\" />\n                Avg/Day\n              </span>\n              <span className=\"font-mono text-foreground text-sm\">{avgProjectsPerDay}</span>\n            </div>\n\n            <div className=\"rounded-lg bg-muted/15 px-2.5 py-2\">\n              <div className=\"flex items-center justify-between gap-2 text-xs\">\n                <span className=\"font-mono text-muted-foreground\">Last Updated</span>\n                <span className=\"truncate font-mono text-accent\">\n                  {lastUpdated ||\n                    new Date().toLocaleDateString(\"en-US\", {\n                      month: \"short\",\n                      day: \"numeric\",\n                      year: \"numeric\",\n                    })}\n                </span>\n              </div>\n            </div>\n          </div>\n        </div>\n      </Link>\n\n      <Link\n        href=\"https://github.com/AmanVarshney01/create-better-t-stack\"\n        target=\"_blank\"\n        rel=\"noopener noreferrer\"\n      >\n        <div className=\"group cursor-pointer rounded-2xl bg-fd-background/75 p-4 transition-colors hover:bg-muted/10\">\n          <div className=\"mb-3 flex items-center gap-2\">\n            <FaGithub className=\"h-4 w-4 text-primary\" />\n            <span className=\"font-bold font-mono text-lg sm:text-xl\">GITHUB_REPO.GIT</span>\n          </div>\n\n          <div className=\"space-y-3\">\n            <div className=\"flex items-center justify-between\">\n              <span className=\"flex items-center gap-1 font-mono text-muted-foreground text-xs uppercase tracking-wide\">\n                <Star className=\"h-3 w-3\" />\n                Stars\n              </span>\n              <NumberFlow\n                value={githubRepo?.starCount || 0}\n                className=\"font-bold font-mono text-lg text-primary tabular-nums\"\n                transformTiming={{\n                  duration: 800,\n                  easing: \"ease-out\",\n                }}\n                trend={1}\n                willChange\n                isolate\n              />\n            </div>\n\n            <div className=\"flex items-center justify-between\">\n              <span className=\"flex items-center gap-1 font-mono text-muted-foreground text-xs uppercase tracking-wide\">\n                <Users className=\"h-3 w-3\" />\n                Contributors\n              </span>\n              <span className=\"font-mono text-foreground text-sm\">\n                {githubRepo?.contributorCount || \"—\"}\n              </span>\n            </div>\n\n            <div className=\"rounded-lg bg-muted/15 px-2.5 py-2\">\n              <div className=\"flex items-center justify-between gap-2 text-xs\">\n                <span className=\"font-mono text-muted-foreground\">Repository</span>\n                <span className=\"truncate font-mono text-accent\">\n                  AmanVarshney01/create-better-t-stack\n                </span>\n              </div>\n            </div>\n          </div>\n        </div>\n      </Link>\n\n      <Link\n        href=\"https://www.npmjs.com/package/create-better-t-stack\"\n        target=\"_blank\"\n        rel=\"noopener noreferrer\"\n      >\n        <div className=\"group cursor-pointer rounded-2xl bg-fd-background/75 p-4 transition-colors hover:bg-muted/10\">\n          <div className=\"mb-3 flex items-center gap-2\">\n            <Terminal className=\"h-4 w-4 text-primary\" />\n            <span className=\"font-bold font-mono text-lg sm:text-xl\">NPM_PACKAGE.JS</span>\n          </div>\n\n          <div className=\"space-y-3\">\n            <div className=\"flex items-center justify-between\">\n              <span className=\"flex items-center gap-1 font-mono text-muted-foreground text-xs uppercase tracking-wide\">\n                <Package className=\"h-3 w-3\" />\n                Downloads\n              </span>\n              <NumberFlow\n                value={liveNpmDownloadCount?.count || 0}\n                className=\"font-bold font-mono text-lg text-primary tabular-nums\"\n                transformTiming={{\n                  duration: liveNpmDownloadCount?.intervalMs || 1000,\n                  easing: \"linear\",\n                }}\n                trend={1}\n                willChange\n                plugins={[continuous]}\n                isolate\n              />\n            </div>\n\n            <div className=\"flex items-center justify-between\">\n              <span className=\"flex items-center gap-1 font-mono text-muted-foreground text-xs uppercase tracking-wide\">\n                <TrendingUp className=\"h-3 w-3\" />\n                Avg/Day\n              </span>\n              <span className=\"font-mono text-foreground text-sm\">\n                {npmPackages?.dayOfWeekAverages\n                  ? Math.round(\n                      npmPackages.dayOfWeekAverages.reduce((a: number, b: number) => a + b, 0) / 7,\n                    )\n                  : \"—\"}\n              </span>\n            </div>\n\n            <div className=\"rounded-lg bg-muted/15 px-2.5 py-2\">\n              <div className=\"flex items-center justify-between gap-2 text-xs\">\n                <span className=\"font-mono text-muted-foreground\">Package</span>\n                <span className=\"truncate font-mono text-accent\">create-better-t-stack</span>\n              </div>\n            </div>\n          </div>\n        </div>\n      </Link>\n    </div>\n  );\n}\n"
  },
  {
    "path": "apps/web/src/app/(home)/_components/testimonials.tsx",
    "content": "\"use client\";\n\nimport { ChevronDown, ChevronUp, Play, Terminal } from \"lucide-react\";\nimport { motion } from \"motion/react\";\nimport Image from \"next/image\";\nimport { useState } from \"react\";\nimport { Tweet, type TwitterComponents } from \"react-tweet\";\n\nexport const components: TwitterComponents = {\n  AvatarImg: (props) => {\n    if (!props.src || props.src === \"\") {\n      return <div className=\"flex h-10 w-10 items-center justify-center rounded-full bg-muted\" />;\n    }\n    return <Image {...props} alt={props.alt || \"User avatar\"} unoptimized />;\n  },\n  MediaImg: (props) => {\n    if (!props.src || props.src === \"\") {\n      return <div className=\"flex h-32 w-full items-center justify-center rounded bg-muted\" />;\n    }\n    return <Image {...props} alt={props.alt || \"Media content\"} fill unoptimized />;\n  },\n};\n\nconst sectionHeaderClass = \"mb-6 flex flex-wrap items-center justify-between gap-2 sm:flex-nowrap\";\nconst sectionTitleClass = \"flex items-center gap-2\";\nconst sectionTitleTextClass = \"font-bold font-mono text-lg sm:text-xl\";\nconst sectionCountClass =\n  \"w-full text-right font-mono text-muted-foreground text-xs sm:w-auto sm:text-left\";\n\nconst cardHeaderClass =\n  \"sticky top-0 z-10 flex items-center gap-2 border-border border-b px-3 py-2\";\n\nfunction ArchiveToggleButton({\n  expanded,\n  count,\n  onToggle,\n}: {\n  expanded: boolean;\n  count: number;\n  onToggle: () => void;\n}) {\n  return (\n    <button\n      type=\"button\"\n      onClick={onToggle}\n      className=\"flex w-full items-center gap-2 rounded border border-border p-2 text-left font-mono transition-colors hover:bg-muted/10\"\n    >\n      {expanded ? (\n        <ChevronUp className=\"h-4 w-4 text-primary\" />\n      ) : (\n        <ChevronDown className=\"h-4 w-4 text-primary\" />\n      )}\n      <span className=\"font-semibold text-muted-foreground text-sm\">\n        TWEET_TESTIMONIALS.ARCHIVE\n      </span>\n      <span className=\"text-muted-foreground text-xs\">({count})</span>\n      <div className=\"mx-2 h-px flex-1 bg-border\" />\n      <span className=\"text-muted-foreground text-xs\">{expanded ? \"HIDE\" : \"SHOW\"}</span>\n    </button>\n  );\n}\n\nconst VideoCard = ({\n  video,\n  index,\n}: {\n  video: { embedId: string; title: string };\n  index: number;\n}) => (\n  <motion.div\n    className=\"w-full min-w-0\"\n    initial={{ opacity: 0, y: 20, scale: 0.95 }}\n    animate={{ opacity: 1, y: 0, scale: 1 }}\n    transition={{\n      delay: index * 0.1,\n      duration: 0.4,\n      ease: \"easeOut\",\n    }}\n  >\n    <div className=\"w-full min-w-0 overflow-hidden rounded border border-border bg-fd-background\">\n      <div className={cardHeaderClass}>\n        <Play className=\"h-3 w-3 text-primary\" />\n        <span className=\"font-semibold font-mono text-xs\">\n          [VIDEO_{String(index + 1).padStart(3, \"0\")}]\n        </span>\n      </div>\n      <div className=\"w-full min-w-0 overflow-hidden\">\n        <div className=\"relative aspect-video w-full\">\n          <iframe\n            src={`https://www.youtube.com/embed/${video.embedId}`}\n            title={video.title}\n            allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture\"\n            allowFullScreen\n            className=\"absolute inset-0 h-full w-full\"\n          />\n        </div>\n      </div>\n    </div>\n  </motion.div>\n);\n\nconst TweetCard = ({ tweetId, index }: { tweetId: string; index: number }) => (\n  <motion.div\n    className=\"w-full min-w-0\"\n    initial={{ opacity: 0, y: 20, scale: 0.95 }}\n    animate={{ opacity: 1, y: 0, scale: 1 }}\n    transition={{\n      delay: index * 0.05,\n      duration: 0.4,\n      ease: \"easeOut\",\n    }}\n  >\n    <div className=\"w-full min-w-0 overflow-hidden rounded border border-border bg-fd-background\">\n      <div className={cardHeaderClass}>\n        <span className=\"text-primary text-xs\">▶</span>\n        <span className=\"font-semibold font-mono text-xs\">\n          [TWEET_{String(index + 1).padStart(3, \"0\")}]\n        </span>\n      </div>\n      <div className=\"w-full min-w-0 overflow-hidden\">\n        <div style={{ width: \"100%\", minWidth: 0, maxWidth: \"100%\" }}>\n          <Tweet id={tweetId} components={components} />\n        </div>\n      </div>\n    </div>\n  </motion.div>\n);\n\nexport default function Testimonials({\n  videos,\n  tweets,\n}: {\n  videos: Array<{ embedId: string; title: string }>;\n  tweets: Array<{ tweetId: string }>;\n}) {\n  const videosReversed = [...videos].reverse();\n  const [showAllTweets, setShowAllTweets] = useState(false);\n\n  const getResponsiveColumns = (numCols: number) => {\n    const columns: string[][] = Array(numCols)\n      .fill(null)\n      .map(() => []);\n\n    tweets.forEach((tweet, index) => {\n      const colIndex = index % numCols;\n      columns[colIndex].push(tweet.tweetId);\n    });\n\n    return columns;\n  };\n\n  const containerVariants = {\n    hidden: { opacity: 0 },\n    visible: {\n      opacity: 1,\n      transition: { staggerChildren: 0.1, delayChildren: 0.1 },\n    },\n  };\n\n  const columnVariants = {\n    hidden: { opacity: 0 },\n    visible: {\n      opacity: 1,\n      transition: { staggerChildren: 0.05 },\n    },\n  };\n\n  return (\n    <div className=\"w-full max-w-full overflow-hidden px-4\">\n      <div className=\"mb-8\">\n        <div className={sectionHeaderClass}>\n          <div className={sectionTitleClass}>\n            <Play className=\"h-5 w-5 text-primary\" />\n            <span className={sectionTitleTextClass}>VIDEO_TESTIMONIALS.LOG</span>\n          </div>\n          <div className=\"hidden h-px flex-1 bg-border sm:block\" />\n          <span className={sectionCountClass}>[{videosReversed.length} ENTRIES]</span>\n        </div>\n\n        <div className=\"block sm:hidden\">\n          <motion.div\n            className=\"flex flex-col gap-4\"\n            variants={containerVariants}\n            initial=\"hidden\"\n            animate=\"visible\"\n          >\n            {videosReversed.map((video, index) => (\n              <VideoCard key={`video-${video.embedId}`} video={video} index={index} />\n            ))}\n          </motion.div>\n        </div>\n\n        <div className=\"hidden sm:block\">\n          <motion.div\n            className=\"grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-3\"\n            variants={containerVariants}\n            initial=\"hidden\"\n            animate=\"visible\"\n          >\n            {videosReversed.map((video, index) => (\n              <VideoCard key={`video-${video.embedId}`} video={video} index={index} />\n            ))}\n          </motion.div>\n        </div>\n      </div>\n\n      <div>\n        <div className={sectionHeaderClass}>\n          <div className={sectionTitleClass}>\n            <Terminal className=\"h-5 w-5 text-primary\" />\n            <span className={sectionTitleTextClass}>DEVELOPER_TESTIMONIALS.LOG</span>\n          </div>\n          <div className=\"hidden h-px flex-1 bg-border sm:block\" />\n          <span className={sectionCountClass}>[{tweets.length} ENTRIES]</span>\n        </div>\n\n        <div className=\"block sm:hidden\">\n          <div className=\"relative\">\n            <motion.div\n              className={`flex flex-col gap-4 overflow-hidden transition-all duration-500 ease-in-out ${\n                showAllTweets ? \"h-auto\" : \"h-[700px]\"\n              }`}\n              variants={containerVariants}\n              initial=\"hidden\"\n              animate=\"visible\"\n            >\n              {tweets.map((tweet, index) => (\n                <TweetCard key={tweet.tweetId} tweetId={tweet.tweetId} index={index} />\n              ))}\n            </motion.div>\n\n            {!showAllTweets && (\n              <div className=\"pointer-events-none absolute right-0 bottom-10 left-0 h-32 bg-linear-to-t from-background via-background/80 to-transparent\" />\n            )}\n\n            <div className=\"my-4\">\n              <ArchiveToggleButton\n                expanded={showAllTweets}\n                count={tweets.length}\n                onToggle={() => setShowAllTweets(!showAllTweets)}\n              />\n            </div>\n          </div>\n        </div>\n\n        <div className=\"hidden sm:block lg:hidden\">\n          <div className=\"relative\">\n            <motion.div\n              className={`grid grid-cols-2 gap-4 overflow-hidden transition-all duration-500 ease-in-out ${\n                showAllTweets ? \"h-auto\" : \"h-[650px]\"\n              }`}\n              variants={containerVariants}\n              initial=\"hidden\"\n              animate=\"visible\"\n            >\n              {getResponsiveColumns(2).map((column, colIndex) => (\n                <motion.div\n                  key={`col-2-${column.length > 0 ? column[0] : `empty-${colIndex}`}`}\n                  className=\"flex min-w-0 flex-col gap-4\"\n                  variants={columnVariants}\n                >\n                  {column.map((tweetId, tweetIndex) => {\n                    const globalIndex = colIndex + tweetIndex * 2;\n                    return <TweetCard key={tweetId} tweetId={tweetId} index={globalIndex} />;\n                  })}\n                </motion.div>\n              ))}\n            </motion.div>\n\n            {!showAllTweets && (\n              <div className=\"pointer-events-none absolute right-0 bottom-10 left-0 h-32 bg-linear-to-t from-background via-background/80 to-transparent\" />\n            )}\n\n            <div className=\"my-4\">\n              <ArchiveToggleButton\n                expanded={showAllTweets}\n                count={tweets.length}\n                onToggle={() => setShowAllTweets(!showAllTweets)}\n              />\n            </div>\n          </div>\n        </div>\n\n        <div className=\"hidden lg:block\">\n          <div className=\"relative\">\n            <motion.div\n              className={`grid grid-cols-3 gap-4 overflow-hidden transition-all duration-500 ease-in-out ${\n                showAllTweets ? \"h-auto\" : \"h-[600px]\"\n              }`}\n              variants={containerVariants}\n              initial=\"hidden\"\n              animate=\"visible\"\n            >\n              {getResponsiveColumns(3).map((column, colIndex) => (\n                <motion.div\n                  key={`col-3-${column.length > 0 ? column[0] : `empty-${colIndex}`}`}\n                  className=\"flex min-w-0 flex-col gap-4\"\n                  variants={columnVariants}\n                >\n                  {column.map((tweetId, tweetIndex) => {\n                    const globalIndex = colIndex + tweetIndex * 3;\n                    return <TweetCard key={tweetId} tweetId={tweetId} index={globalIndex} />;\n                  })}\n                </motion.div>\n              ))}\n            </motion.div>\n\n            {!showAllTweets && (\n              <div className=\"pointer-events-none absolute right-0 bottom-10 left-0 h-32 bg-linear-to-t from-background via-background/80 to-transparent\" />\n            )}\n\n            <div className=\"my-4\">\n              <ArchiveToggleButton\n                expanded={showAllTweets}\n                count={tweets.length}\n                onToggle={() => setShowAllTweets(!showAllTweets)}\n              />\n            </div>\n          </div>\n        </div>\n      </div>\n    </div>\n  );\n}\n"
  },
  {
    "path": "apps/web/src/app/(home)/analytics/_components/analytics-header.tsx",
    "content": "import { Activity, DatabaseZap, Terminal } from \"lucide-react\";\n\nimport { cn } from \"@/lib/utils\";\n\nimport { formatCompactNumber } from \"./analytics-helpers\";\n\nconst utcDateTimeFormatter = new Intl.DateTimeFormat(\"en-US\", {\n  month: \"short\",\n  day: \"numeric\",\n  year: \"numeric\",\n  hour: \"2-digit\",\n  minute: \"2-digit\",\n  hour12: false,\n  timeZone: \"UTC\",\n});\n\nconst utcDateFormatter = new Intl.DateTimeFormat(\"en-US\", {\n  month: \"short\",\n  day: \"numeric\",\n  year: \"numeric\",\n  timeZone: \"UTC\",\n});\n\nfunction formatUtcDateTime(value: string) {\n  return `${utcDateTimeFormatter.format(new Date(value))} UTC`;\n}\n\nfunction formatUtcDate(value: string) {\n  return utcDateFormatter.format(new Date(value));\n}\n\nfunction HeaderStat({\n  label,\n  value,\n  detail,\n}: {\n  label: string;\n  value: string | number;\n  detail: string;\n}) {\n  return (\n    <div className=\"rounded border border-border bg-fd-background p-4\">\n      <div className=\"font-mono text-[11px] text-muted-foreground uppercase tracking-wide\">\n        {label}\n      </div>\n      <div className=\"mt-3 font-semibold text-2xl\">{value}</div>\n      <p className=\"mt-2 text-muted-foreground text-xs leading-5\">{detail}</p>\n    </div>\n  );\n}\n\nexport function AnalyticsHeader({\n  lastUpdated,\n  liveTotal,\n  trackingDays,\n  legacy,\n  connectionStatus,\n}: {\n  lastUpdated: string | null;\n  liveTotal: number;\n  trackingDays: number;\n  legacy: {\n    total: number;\n    avgPerDay: number;\n    lastUpdatedIso: string;\n    source: string;\n  };\n  connectionStatus: \"online\" | \"connecting\" | \"reconnecting\" | \"offline\";\n}) {\n  const formattedDate = lastUpdated ? formatUtcDateTime(lastUpdated) : null;\n  const legacyDate = formatUtcDate(legacy.lastUpdatedIso);\n  const statusMeta = {\n    online: {\n      label: \"Streaming\",\n      textClass: \"text-primary\",\n      dotClass: \"bg-primary\",\n    },\n    connecting: {\n      label: \"Connecting\",\n      textClass: \"text-muted-foreground\",\n      dotClass: \"bg-muted-foreground\",\n    },\n    reconnecting: {\n      label: \"Reconnecting\",\n      textClass: \"text-chart-3\",\n      dotClass: \"bg-chart-3\",\n    },\n    offline: {\n      label: \"Offline\",\n      textClass: \"text-destructive\",\n      dotClass: \"bg-destructive\",\n    },\n  }[connectionStatus];\n\n  return (\n    <section className=\"space-y-5\">\n      <div className=\"mb-2 flex flex-wrap items-center justify-between gap-3 sm:flex-nowrap\">\n        <div className=\"flex min-w-0 items-center gap-2\">\n          <Terminal className=\"h-5 w-5 shrink-0 text-primary\" />\n          <div className=\"min-w-0\">\n            <h1 className=\"font-bold font-mono text-lg sm:text-xl\">ANALYTICS.SH</h1>\n            <p className=\"text-muted-foreground text-sm\">\n              Aggregate CLI telemetry for create-better-t-stack.\n            </p>\n          </div>\n        </div>\n        <div className=\"hidden h-px flex-1 bg-border sm:block\" />\n        <div className=\"flex items-center gap-2 rounded border border-border bg-fd-background px-3 py-2 font-mono text-xs\">\n          <Activity className={cn(\"h-3.5 w-3.5\", statusMeta.textClass)} />\n          <span className={cn(\"h-2 w-2 rounded-full\", statusMeta.dotClass)} />\n          <span className={statusMeta.textClass}>{statusMeta.label}</span>\n        </div>\n      </div>\n\n      <div className=\"grid gap-4 md:grid-cols-2 xl:grid-cols-4\">\n        <HeaderStat\n          label=\"Live projects\"\n          value={formatCompactNumber(liveTotal)}\n          detail=\"Project creations tracked in the current Convex stream.\"\n        />\n        <HeaderStat\n          label=\"Tracked span\"\n          value={trackingDays}\n          detail=\"Calendar days represented in the live telemetry dataset.\"\n        />\n        <HeaderStat\n          label=\"Archive total\"\n          value={formatCompactNumber(legacy.total)}\n          detail={`Historical creations from the pre-Convex archive through ${legacyDate}.`}\n        />\n        <HeaderStat\n          label=\"Archive pace\"\n          value={legacy.avgPerDay.toFixed(1)}\n          detail=\"Average project creations per day in the historical snapshot.\"\n        />\n      </div>\n\n      <div className=\"rounded border border-border bg-fd-background p-4\">\n        <div className=\"grid gap-3 text-sm md:grid-cols-4\">\n          <div className=\"flex items-center gap-2\">\n            <DatabaseZap className=\"h-4 w-4 text-primary\" />\n            <span className=\"font-mono text-xs text-muted-foreground uppercase\">Telemetry</span>\n          </div>\n          <div className=\"flex items-center justify-between gap-3 md:block\">\n            <span className=\"text-muted-foreground\">Latest event</span>\n            <div className=\"font-medium md:mt-1\">{formattedDate ?? \"Waiting\"}</div>\n          </div>\n          <div className=\"flex items-center justify-between gap-3 md:block\">\n            <span className=\"text-muted-foreground\">Archive snapshot</span>\n            <div className=\"font-medium md:mt-1\">{legacyDate}</div>\n          </div>\n          <div className=\"flex items-center justify-between gap-3 md:block\">\n            <span className=\"text-muted-foreground\">Source</span>\n            <div className=\"font-medium md:mt-1\">{legacy.source}</div>\n          </div>\n        </div>\n      </div>\n    </section>\n  );\n}\n"
  },
  {
    "path": "apps/web/src/app/(home)/analytics/_components/analytics-helpers.ts",
    "content": "import { format } from \"date-fns\";\n\nimport type {\n  ComboMatrix,\n  Distribution,\n  ShareDistributionItem,\n  TimeSeriesPoint,\n  VersionDistribution,\n  WeekdayPoint,\n} from \"./types\";\n\nconst compactNumberFormatter = new Intl.NumberFormat(\"en\", {\n  notation: \"compact\",\n  maximumFractionDigits: 1,\n});\n\nconst countFormatter = new Intl.NumberFormat(\"en\");\n\nconst percentFormatter = new Intl.NumberFormat(\"en\", {\n  style: \"percent\",\n  maximumFractionDigits: 0,\n});\n\nconst precisePercentFormatter = new Intl.NumberFormat(\"en\", {\n  style: \"percent\",\n  maximumFractionDigits: 1,\n});\n\nconst MILLISECONDS_PER_DAY = 24 * 60 * 60 * 1000;\n\nexport function formatCompactNumber(value: number): string {\n  return compactNumberFormatter.format(value);\n}\n\nexport function formatCount(value: number): string {\n  return countFormatter.format(value);\n}\n\nexport function formatPercent(value: number, precise = value > 0 && value < 0.1): string {\n  return (precise ? precisePercentFormatter : percentFormatter).format(value);\n}\n\nexport function formatDelta(value: number | null): string {\n  if (value === null) return \"new\";\n  const formatted = `${Math.abs(value * 100).toFixed(Math.abs(value) >= 0.1 ? 0 : 1)}%`;\n  if (value === 0) return \"0%\";\n  return `${value > 0 ? \"+\" : \"-\"}${formatted}`;\n}\n\nexport function formatDateLabel(date: string): string {\n  return format(new Date(`${date}T00:00:00`), \"MMM d\");\n}\n\nexport function formatMonthLabel(month: string, pattern = \"MMM yyyy\"): string {\n  return format(new Date(`${month}-01T00:00:00`), pattern);\n}\n\nexport function formatHourLabel(hour: string): string {\n  return hour.replace(\":00\", \"\");\n}\n\nexport function shortenLabel(value: string, maxLength = 18): string {\n  return value.length > maxLength ? `${value.slice(0, maxLength - 1)}…` : value;\n}\n\nexport function shortenMiddleLabel(value: string, maxLength = 18): string {\n  if (value.length <= maxLength) return value;\n  if (maxLength <= 5) return `${value.slice(0, Math.max(1, maxLength - 1))}…`;\n\n  const suffixLength = Math.max(3, Math.floor((maxLength - 1) / 3));\n  const prefixLength = Math.max(3, maxLength - suffixLength - 1);\n\n  return `${value.slice(0, prefixLength)}…${value.slice(-suffixLength)}`;\n}\n\nexport function buildCompactCategoryLabels(values: string[], maxLength = 12): string[] {\n  const used = new Set<string>();\n\n  return values.map((value) => {\n    const attempts = [\n      shortenMiddleLabel(value, maxLength),\n      shortenMiddleLabel(value, maxLength + 2),\n      shortenMiddleLabel(value, maxLength + 4),\n      value,\n    ];\n\n    for (const candidate of attempts) {\n      if (!used.has(candidate)) {\n        used.add(candidate);\n        return candidate;\n      }\n    }\n\n    const base = shortenMiddleLabel(value, Math.max(6, maxLength - 2));\n    let suffix = 2;\n    let candidate = `${base}-${suffix}`;\n\n    while (used.has(candidate)) {\n      suffix += 1;\n      candidate = `${base}-${suffix}`;\n    }\n\n    used.add(candidate);\n    return candidate;\n  });\n}\n\nexport function withShare(items: Distribution, total?: number): ShareDistributionItem[] {\n  const resolvedTotal = total ?? items.reduce((sum, item) => sum + item.value, 0);\n  if (resolvedTotal <= 0) {\n    return items.map((item) => ({ ...item, share: 0 }));\n  }\n\n  return items.map((item) => ({\n    ...item,\n    share: item.value / resolvedTotal,\n  }));\n}\n\nexport function versionWithShare(\n  items: Array<{ version: string; count: number }>,\n  total?: number,\n): VersionDistribution {\n  const resolvedTotal = total ?? items.reduce((sum, item) => sum + item.count, 0);\n  if (resolvedTotal <= 0) {\n    return items.map((item) => ({ ...item, share: 0 }));\n  }\n\n  return items.map((item) => ({\n    ...item,\n    share: item.count / resolvedTotal,\n  }));\n}\n\nexport function buildWeekdayDistribution(timeSeries: TimeSeriesPoint[]): WeekdayPoint[] {\n  const seed = [\n    { weekday: \"Monday\", shortLabel: \"Mon\", dayIndex: 1 },\n    { weekday: \"Tuesday\", shortLabel: \"Tue\", dayIndex: 2 },\n    { weekday: \"Wednesday\", shortLabel: \"Wed\", dayIndex: 3 },\n    { weekday: \"Thursday\", shortLabel: \"Thu\", dayIndex: 4 },\n    { weekday: \"Friday\", shortLabel: \"Fri\", dayIndex: 5 },\n    { weekday: \"Saturday\", shortLabel: \"Sat\", dayIndex: 6 },\n    { weekday: \"Sunday\", shortLabel: \"Sun\", dayIndex: 0 },\n  ].map((day) => ({ ...day, count: 0, dayCount: 0 }));\n\n  if (timeSeries.length === 0) {\n    return seed.map(({ dayCount: _dayCount, ...day }) => ({\n      ...day,\n      averageDailyProjects: 0,\n    }));\n  }\n\n  const countsByDate = new Map(timeSeries.map((point) => [point.date, point.count]));\n  const sortedDates = Array.from(countsByDate.keys()).sort((a, b) => a.localeCompare(b));\n  const start = Date.parse(`${sortedDates[0]}T00:00:00Z`);\n  const end = Date.parse(`${sortedDates.at(-1)}T00:00:00Z`);\n\n  if (Number.isNaN(start) || Number.isNaN(end) || end < start) {\n    return seed.map(({ dayCount: _dayCount, ...day }) => ({\n      ...day,\n      averageDailyProjects: 0,\n    }));\n  }\n\n  for (let timestamp = start; timestamp <= end; timestamp += MILLISECONDS_PER_DAY) {\n    const date = new Date(timestamp).toISOString().slice(0, 10);\n    const dayIndex = new Date(timestamp).getUTCDay();\n    const target = seed.find((day) => day.dayIndex === dayIndex);\n    if (!target) continue;\n    target.count += countsByDate.get(date) || 0;\n    target.dayCount += 1;\n  }\n\n  return seed.map(({ dayCount, ...day }) => ({\n    ...day,\n    averageDailyProjects: dayCount > 0 ? day.count / dayCount : 0,\n  }));\n}\n\ntype MatrixOptions = {\n  distribution: ShareDistributionItem[];\n  total: number;\n  xFromLabel: (name: string) => string;\n  yFromLabel: (name: string) => string;\n  xLimit?: number;\n  yLimit?: number;\n};\n\nexport function buildComboMatrix({\n  distribution,\n  total,\n  xFromLabel,\n  yFromLabel,\n  xLimit,\n  yLimit,\n}: MatrixOptions): ComboMatrix {\n  const pairs = distribution\n    .map((item) => ({\n      x: xFromLabel(item.name),\n      y: yFromLabel(item.name),\n      count: item.value,\n    }))\n    .filter((item) => item.x && item.y);\n\n  const xTotals = new Map<string, number>();\n  const yTotals = new Map<string, number>();\n  const counts = new Map<string, number>();\n\n  for (const pair of pairs) {\n    xTotals.set(pair.x, (xTotals.get(pair.x) || 0) + pair.count);\n    yTotals.set(pair.y, (yTotals.get(pair.y) || 0) + pair.count);\n    counts.set(`${pair.x}:::${pair.y}`, (counts.get(`${pair.x}:::${pair.y}`) || 0) + pair.count);\n  }\n\n  const rankedXDomain = Array.from(xTotals.entries())\n    .sort((a, b) => b[1] - a[1])\n    .map(([name]) => name);\n\n  const rankedYDomain = Array.from(yTotals.entries())\n    .sort((a, b) => b[1] - a[1])\n    .map(([name]) => name);\n\n  const xDomain = typeof xLimit === \"number\" ? rankedXDomain.slice(0, xLimit) : rankedXDomain;\n  const yDomain = typeof yLimit === \"number\" ? rankedYDomain.slice(0, yLimit) : rankedYDomain;\n\n  const data = yDomain.flatMap((y) =>\n    xDomain.map((x) => {\n      const count = counts.get(`${x}:::${y}`) || 0;\n      return {\n        x,\n        y,\n        count,\n        share: total > 0 ? count / total : 0,\n      };\n    }),\n  );\n\n  return {\n    data,\n    xDomain,\n    yDomain,\n    maxValue: Math.max(...data.map((item) => item.count), 0),\n  };\n}\n\nexport function splitComboLabel(value: string): [string, string] {\n  const [left = \"none\", ...rest] = value.split(\" + \");\n  return [left.trim(), rest.join(\" + \").trim() || \"none\"];\n}\n\nexport function getTrendTone(deltaPercentage: number | null): \"up\" | \"down\" | \"flat\" {\n  if (deltaPercentage === null) return \"up\";\n  if (deltaPercentage > 0.02) return \"up\";\n  if (deltaPercentage < -0.02) return \"down\";\n  return \"flat\";\n}\n"
  },
  {
    "path": "apps/web/src/app/(home)/analytics/_components/analytics-page.tsx",
    "content": "\"use client\";\n\nimport Footer from \"../../_components/footer\";\nimport { AnalyticsHeader } from \"./analytics-header\";\nimport { AnalyticsSources } from \"./analytics-sources\";\nimport { DevToolsSection } from \"./dev-environment-charts\";\nimport { LiveLogs } from \"./live-logs\";\nimport { MetricsCards } from \"./metrics-cards\";\nimport { StackSection } from \"./stack-configuration-charts\";\nimport { TimelineSection } from \"./timeline-charts\";\nimport type { AggregatedAnalyticsData } from \"./types\";\n\nexport default function AnalyticsPage({\n  data,\n  legacy,\n  connectionStatus,\n}: {\n  data: AggregatedAnalyticsData;\n  legacy: {\n    total: number;\n    avgPerDay: number;\n    lastUpdatedIso: string;\n    source: string;\n  };\n  connectionStatus: \"online\" | \"connecting\" | \"reconnecting\" | \"offline\";\n}) {\n  return (\n    <main className=\"container mx-auto min-h-svh\">\n      <div className=\"mx-auto flex flex-col gap-8 px-4 pt-12\">\n        <AnalyticsHeader\n          lastUpdated={data.lastUpdated}\n          liveTotal={data.totalProjects}\n          trackingDays={data.momentum.trackingDays}\n          legacy={legacy}\n          connectionStatus={connectionStatus}\n        />\n\n        <LiveLogs />\n\n        <MetricsCards data={data} />\n\n        <TimelineSection data={data} />\n\n        <StackSection data={data} />\n\n        <DevToolsSection data={data} />\n\n        <div className=\"max-w-xl\">\n          <AnalyticsSources />\n        </div>\n      </div>\n      <Footer />\n    </main>\n  );\n}\n"
  },
  {
    "path": "apps/web/src/app/(home)/analytics/_components/analytics-sources.tsx",
    "content": "import { ArrowUpRight } from \"lucide-react\";\nimport Link from \"next/link\";\n\nexport function AnalyticsSources() {\n  return (\n    <div className=\"rounded border border-border bg-fd-background p-4\">\n      <div className=\"font-mono text-[11px] text-muted-foreground uppercase tracking-wide\">\n        Source map\n      </div>\n\n      <div className=\"mt-4 space-y-3 text-sm\">\n        <Link\n          href=\"https://github.com/AmanVarshney01/create-better-t-stack/blob/main/apps/cli/src/utils/analytics.ts\"\n          target=\"_blank\"\n          rel=\"noopener noreferrer\"\n          className=\"group flex items-center justify-between rounded border border-border px-3 py-2.5 transition-colors hover:border-muted-foreground/30\"\n        >\n          <span className=\"min-w-0\">\n            <span className=\"block text-muted-foreground text-xs\">CLI sender</span>\n            <span className=\"block truncate\">apps/cli/src/utils/analytics.ts</span>\n          </span>\n          <ArrowUpRight className=\"h-4 w-4 shrink-0 text-muted-foreground transition-transform group-hover:-translate-y-0.5 group-hover:translate-x-0.5 group-hover:text-primary\" />\n        </Link>\n\n        <Link\n          href=\"https://github.com/AmanVarshney01/create-better-t-stack/blob/main/packages/backend/convex/analytics.ts\"\n          target=\"_blank\"\n          rel=\"noopener noreferrer\"\n          className=\"group flex items-center justify-between rounded border border-border px-3 py-2.5 transition-colors hover:border-muted-foreground/30\"\n        >\n          <span className=\"min-w-0\">\n            <span className=\"block text-muted-foreground text-xs\">Aggregator</span>\n            <span className=\"block truncate\">packages/backend/convex/analytics.ts</span>\n          </span>\n          <ArrowUpRight className=\"h-4 w-4 shrink-0 text-muted-foreground transition-transform group-hover:-translate-y-0.5 group-hover:translate-x-0.5 group-hover:text-primary\" />\n        </Link>\n\n        <Link\n          href=\"https://umami.amanv.cloud/share/pHvqHleyOl9PBfaK\"\n          target=\"_blank\"\n          rel=\"noopener noreferrer\"\n          className=\"group flex items-center justify-between rounded border border-border px-3 py-2.5 transition-colors hover:border-muted-foreground/30\"\n        >\n          <span className=\"min-w-0\">\n            <span className=\"block text-muted-foreground text-xs\">Website analytics</span>\n            <span className=\"block truncate\">Umami dashboard</span>\n          </span>\n          <ArrowUpRight className=\"h-4 w-4 shrink-0 text-muted-foreground transition-transform group-hover:-translate-y-0.5 group-hover:translate-x-0.5 group-hover:text-primary\" />\n        </Link>\n      </div>\n\n      <p className=\"mt-4 text-muted-foreground text-xs leading-5\">\n        No personal data is collected here. The page only surfaces aggregate CLI usage and a small\n        live event feed for recent anonymous activity.\n      </p>\n    </div>\n  );\n}\n"
  },
  {
    "path": "apps/web/src/app/(home)/analytics/_components/chart-card.tsx",
    "content": "import type { ReactNode } from \"react\";\n\nimport { cn } from \"@/lib/utils\";\n\nexport function ChartCard({\n  title,\n  description,\n  aside,\n  children,\n  footer,\n  className,\n  contentClassName,\n}: {\n  title: string;\n  description: string;\n  aside?: ReactNode;\n  children: ReactNode;\n  footer?: ReactNode;\n  className?: string;\n  contentClassName?: string;\n}) {\n  return (\n    <section\n      className={cn(\n        \"min-w-0 overflow-hidden rounded border border-border bg-fd-background transition-colors duration-200 hover:border-muted-foreground/30\",\n        className,\n      )}\n    >\n      <div className=\"space-y-5 p-4 sm:p-5\">\n        <div className=\"flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between\">\n          <div className=\"max-w-2xl space-y-1.5\">\n            <h3 className=\"font-semibold text-sm sm:text-base\">{title}</h3>\n            <p className=\"max-w-xl text-muted-foreground text-sm leading-6\">{description}</p>\n          </div>\n          {aside ? <div className=\"shrink-0\">{aside}</div> : null}\n        </div>\n\n        <div className={cn(\"min-w-0 space-y-4\", contentClassName)}>{children}</div>\n\n        {footer ? (\n          <div className=\"border-border/35 border-t pt-4 text-muted-foreground text-xs leading-5\">\n            {footer}\n          </div>\n        ) : null}\n      </div>\n    </section>\n  );\n}\n"
  },
  {
    "path": "apps/web/src/app/(home)/analytics/_components/dev-environment-charts.tsx",
    "content": "\"use client\";\n\nimport { EvilBarChart } from \"@/components/evilcharts/charts/bar-chart\";\nimport { cn } from \"@/lib/utils\";\n\nimport { formatCompactNumber, formatCount, formatPercent } from \"./analytics-helpers\";\nimport { ChartCard } from \"./chart-card\";\nimport { seriesConfig } from \"./evil-chart-utils\";\nimport { PreferenceChartCard } from \"./preference-chart-card\";\nimport { SectionHeader } from \"./section-header\";\nimport type { AggregatedAnalyticsData, ShareDistributionItem } from \"./types\";\n\nfunction SplitMeterCard({\n  title,\n  description,\n  data,\n}: {\n  title: string;\n  description: string;\n  data: ShareDistributionItem[];\n}) {\n  const yesShare = data.find((item) => item.name === \"Yes\")?.share ?? 0;\n  const noShare = data.find((item) => item.name === \"No\")?.share ?? 0;\n  const yesCount = data.find((item) => item.name === \"Yes\")?.value ?? 0;\n  const noCount = data.find((item) => item.name === \"No\")?.value ?? 0;\n  const chartData = data.map((item) => ({\n    label: item.name,\n    value: item.value,\n    share: item.share,\n  }));\n  const chartConfig = seriesConfig(\"value\", \"Tracked setups\", \"teal\");\n\n  return (\n    <ChartCard title={title} description={description}>\n      <div className=\"grid gap-4 sm:grid-cols-[minmax(0,0.9fr)_minmax(180px,0.7fr)] sm:items-center\">\n        <div className=\"grid gap-3 sm:grid-cols-2\">\n          <div className=\"rounded border border-primary/25 bg-fd-background p-4\">\n            <div className=\"font-mono text-[11px] text-muted-foreground uppercase tracking-wide\">\n              Yes\n            </div>\n            <div className=\"mt-2 font-semibold text-2xl\">{formatCount(yesCount)}</div>\n            <div className=\"mt-1 text-muted-foreground text-xs\">{formatPercent(yesShare)}</div>\n          </div>\n          <div className=\"rounded border border-border bg-fd-background p-4\">\n            <div className=\"font-mono text-[11px] text-muted-foreground uppercase tracking-wide\">\n              No\n            </div>\n            <div className=\"mt-2 font-semibold text-2xl\">{formatCount(noCount)}</div>\n            <div className=\"mt-1 text-muted-foreground text-xs\">{formatPercent(noShare)}</div>\n          </div>\n        </div>\n        <EvilBarChart\n          className=\"h-[210px] w-full p-1\"\n          data={chartData}\n          xDataKey=\"label\"\n          yDataKey=\"value\"\n          chartConfig={chartConfig}\n          layout=\"horizontal\"\n          barVariant=\"default\"\n          barRadius={5}\n          hideLegend\n          tooltipVariant=\"frosted-glass\"\n          tooltipRoundness=\"md\"\n          backgroundVariant=\"dots\"\n          xAxisProps={{\n            tickFormatter: (value) => formatCompactNumber(Number(value)),\n          }}\n        />\n      </div>\n    </ChartCard>\n  );\n}\n\nexport function DevToolsSection({ data }: { data: AggregatedAnalyticsData }) {\n  const webDeployOptions = data.webDeployDistribution;\n  const serverDeployOptions = data.serverDeployDistribution;\n  const hasDeploymentOptions = webDeployOptions.length > 0 || serverDeployOptions.length > 0;\n  const nodeVersionPreferences = data.nodeVersionDistribution.map((item) => ({\n    name: item.version,\n    value: item.count,\n    share: item.share,\n  }));\n  const cliVersionPreferences = data.cliVersionDistribution.map((item) => ({\n    name: item.version,\n    value: item.count,\n    share: item.share,\n  }));\n\n  return (\n    <div className=\"space-y-6\">\n      <SectionHeader\n        label=\"Environment\"\n        title=\"Package manager, setup, deployment, and addon choices\"\n        description=\"The environment choices that shape generated projects after the stack options are selected.\"\n        aside={\n          <div className=\"rounded border border-border bg-fd-background px-3 py-1.5 font-mono text-muted-foreground text-xs\">\n            packages {data.summary.mostPopularPackageManager} • runtime{\" \"}\n            {data.summary.mostPopularRuntime}\n          </div>\n        }\n      />\n\n      <div className=\"grid gap-4 xl:grid-cols-[minmax(0,1.3fr)_minmax(0,0.7fr)]\">\n        <PreferenceChartCard\n          title=\"Database setup\"\n          description=\"How often each database setup option was selected.\"\n          data={data.dbSetupDistribution}\n          colorKey=\"chart4\"\n          className=\"xl:min-h-full\"\n          chartClassName=\"min-h-[290px]\"\n        />\n        <PreferenceChartCard\n          title=\"Package manager\"\n          description=\"How often each package manager was selected.\"\n          data={data.packageManagerDistribution}\n          colorKey=\"chart1\"\n        />\n      </div>\n\n      <div\n        className={cn(\n          \"grid gap-4\",\n          data.paymentsDistribution.length > 0 ? \"xl:grid-cols-2\" : \"grid-cols-1\",\n        )}\n      >\n        <PreferenceChartCard\n          title=\"Platform\"\n          description=\"How many tracked CLI runs came from each platform.\"\n          data={data.platformDistribution}\n          colorKey=\"chart2\"\n        />\n        {data.paymentsDistribution.length > 0 ? (\n          <PreferenceChartCard\n            title=\"Payments\"\n            description=\"How often each payments option was selected, including none.\"\n            data={data.paymentsDistribution}\n            colorKey=\"chart3\"\n          />\n        ) : null}\n      </div>\n\n      {hasDeploymentOptions ? (\n        <div className=\"grid gap-4 xl:grid-cols-2\">\n          {webDeployOptions.length > 0 ? (\n            <PreferenceChartCard\n              title=\"Web deployment\"\n              description=\"How often each web deployment option was selected, including none.\"\n              data={webDeployOptions}\n              colorKey=\"chart3\"\n            />\n          ) : null}\n\n          {serverDeployOptions.length > 0 ? (\n            <PreferenceChartCard\n              title=\"Server deployment\"\n              description=\"How often each server deployment option was selected, including none.\"\n              data={serverDeployOptions}\n              colorKey=\"chart2\"\n            />\n          ) : null}\n        </div>\n      ) : null}\n\n      <div className=\"grid gap-4 xl:grid-cols-2\">\n        <SplitMeterCard\n          title=\"Git initialization\"\n          description=\"Share of tracked setups where Git was initialized during project creation.\"\n          data={data.gitDistribution}\n        />\n        <SplitMeterCard\n          title=\"Install dependencies\"\n          description=\"Share of tracked setups where dependencies were installed during project creation.\"\n          data={data.installDistribution}\n        />\n      </div>\n\n      <div\n        className={cn(\n          \"grid gap-4\",\n          data.examplesDistribution.length > 0 ? \"xl:grid-cols-2\" : \"grid-cols-1\",\n        )}\n      >\n        <PreferenceChartCard\n          title=\"Node versions\"\n          description=\"How many tracked CLI runs reported each Node major version.\"\n          data={nodeVersionPreferences}\n          colorKey=\"chart5\"\n          layout=\"vertical\"\n          chartClassName=\"min-h-[280px]\"\n        />\n\n        {data.examplesDistribution.length > 0 ? (\n          <PreferenceChartCard\n            title=\"Examples\"\n            description=\"How often each example was included.\"\n            data={data.examplesDistribution}\n            colorKey=\"chart4\"\n            layout=\"vertical\"\n            chartClassName=\"min-h-[280px]\"\n          />\n        ) : null}\n      </div>\n\n      {data.addonsDistribution.length > 0 ? (\n        <PreferenceChartCard\n          title=\"Addons\"\n          description=\"How often each addon was selected.\"\n          data={data.addonsDistribution}\n          colorKey=\"chart1\"\n          columnCount={2}\n          chartClassName=\"min-h-[280px]\"\n        />\n      ) : null}\n\n      {cliVersionPreferences.length > 0 ? (\n        <PreferenceChartCard\n          title=\"CLI versions\"\n          description=\"How many tracked setups were created with each CLI version.\"\n          data={cliVersionPreferences}\n          colorKey=\"chart4\"\n          columnCount={4}\n          columnGridClassName=\"xl:grid-cols-2\"\n          chartClassName=\"min-h-[300px]\"\n        />\n      ) : null}\n    </div>\n  );\n}\n"
  },
  {
    "path": "apps/web/src/app/(home)/analytics/_components/evil-chart-utils.ts",
    "content": "import type { ChartConfig } from \"@/components/evilcharts/ui/chart\";\n\nimport type { ShareDistributionItem } from \"./types\";\n\ntype EvilTone = \"blue\" | \"teal\" | \"amber\" | \"rose\" | \"violet\" | \"slate\";\n\nconst toneColors: Record<EvilTone, NonNullable<ChartConfig[string][\"colors\"]>> = {\n  blue: {\n    light: [\"#1d4ed8\", \"#60a5fa\"],\n    dark: [\"#60a5fa\", \"#2563eb\"],\n  },\n  teal: {\n    light: [\"#0f766e\", \"#2dd4bf\"],\n    dark: [\"#2dd4bf\", \"#0f766e\"],\n  },\n  amber: {\n    light: [\"#ca8a04\", \"#facc15\"],\n    dark: [\"#facc15\", \"#ca8a04\"],\n  },\n  rose: {\n    light: [\"#be123c\", \"#fb7185\"],\n    dark: [\"#fb7185\", \"#be123c\"],\n  },\n  violet: {\n    light: [\"#7c3aed\", \"#c084fc\"],\n    dark: [\"#c084fc\", \"#7c3aed\"],\n  },\n  slate: {\n    light: [\"#475569\", \"#94a3b8\"],\n    dark: [\"#cbd5e1\", \"#64748b\"],\n  },\n};\n\nconst toneOrder: EvilTone[] = [\"blue\", \"teal\", \"amber\", \"rose\", \"violet\", \"slate\"];\n\nexport function seriesConfig<TKey extends string>(\n  key: TKey,\n  label: string,\n  tone: EvilTone,\n): Record<TKey, ChartConfig[string]> {\n  return {\n    [key]: {\n      label,\n      colors: toneColors[tone],\n    },\n  } as Record<TKey, ChartConfig[string]>;\n}\n\nexport function multiSeriesConfig<const TKey extends string>(\n  entries: Array<{ key: TKey; label: string; tone: EvilTone }>,\n): Record<TKey, ChartConfig[string]> {\n  return Object.fromEntries(\n    entries.map((entry) => [\n      entry.key,\n      {\n        label: entry.label,\n        colors: toneColors[entry.tone],\n      },\n    ]),\n  ) as Record<TKey, ChartConfig[string]>;\n}\n\nfunction slugify(value: string): string {\n  const slug = value\n    .toLowerCase()\n    .replace(/[^a-z0-9]+/g, \"-\")\n    .replace(/^-|-$/g, \"\");\n\n  return slug || \"none\";\n}\n\nexport type KeyedShareItem = ShareDistributionItem & {\n  key: string;\n};\n\nexport function toKeyedShareItems(\n  items: ShareDistributionItem[],\n  prefix: string,\n): KeyedShareItem[] {\n  const seen = new Map<string, number>();\n\n  return items.map((item) => {\n    const base = `${prefix}-${slugify(item.name)}`;\n    const count = seen.get(base) ?? 0;\n    seen.set(base, count + 1);\n\n    return {\n      ...item,\n      key: count === 0 ? base : `${base}-${count + 1}`,\n    };\n  });\n}\n\nexport function categoryConfig(items: KeyedShareItem[], toneOffset = 0): ChartConfig {\n  return Object.fromEntries(\n    items.map((item, index) => {\n      const tone = toneOrder[(index + toneOffset) % toneOrder.length] ?? \"blue\";\n\n      return [\n        item.key,\n        {\n          label: item.name,\n          colors: toneColors[tone],\n        },\n      ];\n    }),\n  );\n}\n\nexport function getTone(index: number): EvilTone {\n  return toneOrder[index % toneOrder.length] ?? \"blue\";\n}\n"
  },
  {
    "path": "apps/web/src/app/(home)/analytics/_components/live-logs.tsx",
    "content": "\"use client\";\n\nimport { api } from \"@better-t-stack/backend/convex/_generated/api\";\nimport { useQuery } from \"convex/react\";\nimport { Activity, ChevronRight, Radio } from \"lucide-react\";\nimport { AnimatePresence, motion } from \"motion/react\";\nimport { useState } from \"react\";\n\nimport { Button } from \"@/components/ui/button\";\nimport { ScrollArea } from \"@/components/ui/scroll-area\";\nimport { cn } from \"@/lib/utils\";\n\nconst LOG_FIELD_ORDER = [\n  \"frontend\",\n  \"backend\",\n  \"database\",\n  \"orm\",\n  \"api\",\n  \"runtime\",\n  \"packageManager\",\n  \"auth\",\n  \"payments\",\n  \"dbSetup\",\n  \"webDeploy\",\n  \"serverDeploy\",\n  \"addons\",\n  \"examples\",\n  \"cli_version\",\n  \"node_version\",\n  \"platform\",\n  \"git\",\n  \"install\",\n] as const;\n\nconst eventTimeFormatter = new Intl.DateTimeFormat(\"en-US\", {\n  month: \"short\",\n  day: \"numeric\",\n  hour: \"2-digit\",\n  minute: \"2-digit\",\n  hour12: false,\n});\n\nfunction formatValue(value: unknown): string {\n  if (Array.isArray(value)) return value.length > 0 ? value.join(\",\") : \"none\";\n  if (typeof value === \"boolean\") return value ? \"true\" : \"false\";\n  if (typeof value === \"string\") return value;\n  return String(value);\n}\n\nfunction hasLogValue(value: unknown): boolean {\n  if (value === undefined || value === null) return false;\n  if (Array.isArray(value)) return value.length > 0;\n  return true;\n}\n\nfunction formatStackSummary(event: Record<string, unknown>) {\n  const frontend = Array.isArray(event.frontend)\n    ? event.frontend.join(\"+\")\n    : (event.frontend as string | undefined);\n  const backend = typeof event.backend === \"string\" ? event.backend : \"none\";\n  const database = typeof event.database === \"string\" ? event.database : \"none\";\n  const orm = typeof event.orm === \"string\" ? event.orm : \"none\";\n  const packageManager =\n    typeof event.packageManager === \"string\" ? event.packageManager : \"unknown package manager\";\n\n  return `${frontend || \"none\"} / ${backend} -> ${database} + ${orm} via ${packageManager}`;\n}\n\nexport function LiveLogs() {\n  const [isOpen, setIsOpen] = useState(false);\n  const events = useQuery(api.analytics.getRecentEvents, isOpen ? { limit: 25 } : \"skip\");\n\n  return (\n    <div className=\"overflow-hidden rounded border border-border bg-fd-background\">\n      <Button\n        variant=\"ghost\"\n        className=\"group h-auto w-full rounded-none border-border border-b px-4 py-3 transition-colors hover:bg-muted/20\"\n        onClick={() => setIsOpen(!isOpen)}\n      >\n        <div className=\"flex w-full items-center justify-between gap-4\">\n          <div className=\"flex items-center gap-2.5\">\n            <ChevronRight\n              className={cn(\n                \"h-3.5 w-3.5 text-muted-foreground/70 transition-transform duration-200\",\n                isOpen && \"rotate-90\",\n              )}\n            />\n            <Activity className=\"h-3.5 w-3.5 text-primary\" />\n            <span className=\"font-mono font-medium text-[12px] text-foreground/90 uppercase tracking-wide\">\n              Recent project starts\n            </span>\n          </div>\n          <span className=\"font-medium text-muted-foreground text-xs transition-colors group-hover:text-foreground/80\">\n            {isOpen ? \"Hide feed\" : \"Show feed\"}\n          </span>\n        </div>\n      </Button>\n\n      <AnimatePresence initial={false}>\n        {isOpen && (\n          <motion.div\n            initial={{ height: 0, opacity: 0 }}\n            animate={{ height: \"auto\", opacity: 1 }}\n            exit={{ height: 0, opacity: 0 }}\n            transition={{ duration: 0.25, ease: [0.32, 0.72, 0, 1] }}\n            style={{ overflow: \"hidden\" }}\n          >\n            {events === undefined ? (\n              <div className=\"flex h-[220px] flex-col items-center justify-center border-border/10 border-t\">\n                <div className=\"flex items-center gap-2 font-mono text-muted-foreground text-xs\">\n                  <Activity className=\"h-3.5 w-3.5 animate-pulse text-primary\" />\n                  Loading latest starts\n                </div>\n              </div>\n            ) : events.length === 0 ? (\n              <div className=\"flex h-[300px] flex-col items-center justify-center border-border/10 border-t\">\n                <div className=\"flex flex-col items-center gap-3 opacity-70\">\n                  <div className=\"rounded border border-border bg-muted/20 p-3\">\n                    <Radio className=\"h-4 w-4 text-muted-foreground\" />\n                  </div>\n                  <div className=\"space-y-1 text-center\">\n                    <p className=\"font-medium text-muted-foreground text-sm\">No recent activity</p>\n                    <p className=\"text-muted-foreground/70 text-xs\">\n                      The feed will populate as new anonymous CLI events arrive.\n                    </p>\n                  </div>\n                </div>\n              </div>\n            ) : (\n              <ScrollArea className=\"h-[400px] border-border/10 border-t\">\n                <div className=\"border-border/60 border-b bg-muted/10 px-4 py-2\">\n                  <div className=\"flex items-center justify-between gap-3 font-mono text-[11px]\">\n                    <span className=\"text-muted-foreground uppercase tracking-wide\">\n                      stream: project.starts\n                    </span>\n                    <span className=\"text-muted-foreground\">{events.length} events</span>\n                  </div>\n                </div>\n                <div className=\"divide-y divide-border/35\">\n                  <AnimatePresence initial={false} mode=\"popLayout\">\n                    {events.map((event, index) => {\n                      const time = eventTimeFormatter.format(new Date(event._creationTime));\n                      const eventRecord = event as Record<string, unknown>;\n                      const logFields = LOG_FIELD_ORDER.flatMap((key) =>\n                        hasLogValue(eventRecord[key])\n                          ? [{ key, value: formatValue(eventRecord[key]) }]\n                          : [],\n                      );\n                      const eventId = String(event._id).slice(-6);\n\n                      return (\n                        <motion.div\n                          key={event._id}\n                          initial={{ opacity: 0, y: 6 }}\n                          animate={{ opacity: 1, y: 0 }}\n                          transition={{ duration: 0.2, delay: Math.min(index * 0.035, 0.35) }}\n                          className=\"grid gap-3 px-4 py-3 transition-colors hover:bg-muted/20 sm:grid-cols-[104px_minmax(0,1fr)]\"\n                        >\n                          <div className=\"flex items-start gap-2 sm:block\">\n                            <span\n                              suppressHydrationWarning\n                              className=\"font-mono text-muted-foreground text-[11px] tabular-nums\"\n                            >\n                              {time}\n                            </span>\n                            <span className=\"hidden font-mono text-muted-foreground/60 text-[10px] sm:mt-1 sm:block\">\n                              #{String(events.length - index).padStart(2, \"0\")}\n                            </span>\n                          </div>\n\n                          <div className=\"min-w-0 space-y-2\">\n                            <div className=\"flex min-w-0 flex-wrap items-center gap-2\">\n                              <span className=\"rounded border border-primary/30 bg-primary/10 px-2 py-0.5 font-mono text-[10px] text-primary uppercase tracking-wide\">\n                                project.start\n                              </span>\n                              <span className=\"min-w-0 truncate font-mono text-[12px] text-foreground\">\n                                {formatStackSummary(eventRecord)}\n                              </span>\n                              <span className=\"font-mono text-muted-foreground/70 text-[10px]\">\n                                id={eventId}\n                              </span>\n                            </div>\n\n                            <div className=\"flex min-w-0 flex-wrap gap-x-3 gap-y-1.5\">\n                              {logFields.map(({ key, value }) => (\n                                <code\n                                  key={key}\n                                  className=\"max-w-full rounded border border-border/70 bg-muted/15 px-1.5 py-0.5 font-mono text-[11px] leading-5\"\n                                >\n                                  <span className=\"text-muted-foreground\">{key}=</span>\n                                  <span className=\"break-all text-foreground/90\">{value}</span>\n                                </code>\n                              ))}\n                            </div>\n                          </div>\n                        </motion.div>\n                      );\n                    })}\n                  </AnimatePresence>\n                </div>\n              </ScrollArea>\n            )}\n          </motion.div>\n        )}\n      </AnimatePresence>\n    </div>\n  );\n}\n"
  },
  {
    "path": "apps/web/src/app/(home)/analytics/_components/metrics-cards.tsx",
    "content": "\"use client\";\n\nimport NumberFlow from \"@number-flow/react\";\nimport { AreaChart, Flame, Gauge, Radar, Sparkles, Sunrise } from \"lucide-react\";\n\nimport { EvilAreaChart } from \"@/components/evilcharts/charts/area-chart\";\nimport { EvilBarChart } from \"@/components/evilcharts/charts/bar-chart\";\nimport { cn } from \"@/lib/utils\";\n\nimport {\n  formatCompactNumber,\n  formatDateLabel,\n  formatDelta,\n  getTrendTone,\n  shortenLabel,\n} from \"./analytics-helpers\";\nimport { multiSeriesConfig, seriesConfig } from \"./evil-chart-utils\";\nimport type { AggregatedAnalyticsData } from \"./types\";\n\nfunction MetricTile({\n  label,\n  value,\n  detail,\n  icon,\n  tone = \"default\",\n}: {\n  label: string;\n  value: string;\n  detail: string;\n  icon: React.ReactNode;\n  tone?: \"default\" | \"success\" | \"warning\";\n}) {\n  return (\n    <div\n      className={cn(\n        \"min-w-0 rounded border border-border bg-fd-background p-4\",\n        tone === \"success\" && \"border-primary/25\",\n        tone === \"warning\" && \"border-chart-3/25\",\n      )}\n    >\n      <div className=\"flex items-center justify-between gap-3\">\n        <span className=\"font-mono text-[11px] text-muted-foreground uppercase tracking-wide\">\n          {label}\n        </span>\n        <span className=\"text-muted-foreground/80\">{icon}</span>\n      </div>\n      <div className=\"mt-3 font-semibold text-2xl\">{value}</div>\n      <p className=\"mt-2 text-muted-foreground text-xs leading-5\">{detail}</p>\n    </div>\n  );\n}\n\nexport function MetricsCards({ data }: { data: AggregatedAnalyticsData }) {\n  const momentumTone = getTrendTone(data.momentum.deltaPercentage);\n  const sparklineData = (\n    data.timeSeries.length > 0\n      ? data.timeSeries\n      : [\n          {\n            dateValue: new Date(),\n            count: 0,\n            rollingAverage: 0,\n            cumulativeProjects: 0,\n            date: new Date().toISOString().slice(0, 10),\n          },\n        ]\n  ).map((point) => ({\n    day: formatDateLabel(point.date),\n    projects: point.count,\n    average: Number(point.rollingAverage.toFixed(2)),\n  }));\n\n  const leadingChoices = [\n    { category: \"Frontend\", item: data.frontendDistribution[0] },\n    { category: \"Backend\", item: data.backendDistribution[0] },\n    { category: \"Database\", item: data.databaseDistribution[0] },\n    { category: \"ORM\", item: data.ormDistribution[0] },\n    { category: \"Runtime\", item: data.runtimeDistribution[0] },\n    {\n      category: \"Packages\",\n      item: data.packageManagerDistribution[0],\n    },\n  ].map(({ category, item }) => ({\n    choice: `${category} · ${shortenLabel(item?.name ?? \"n/a\", 18)}`,\n    setups: item?.value ?? 0,\n  }));\n  const momentumComparison = [\n    { window: \"Last 7 days\", projects: data.momentum.last7Days },\n    { window: \"Previous 7 days\", projects: data.momentum.previous7Days },\n  ];\n\n  const trendConfig = multiSeriesConfig([\n    { key: \"projects\", label: \"Projects\", tone: \"blue\" },\n    { key: \"average\", label: \"7 day average\", tone: \"teal\" },\n  ]);\n  const leadingChoicesConfig = seriesConfig(\"setups\", \"Tracked setups\", \"violet\");\n  const momentumComparisonConfig = seriesConfig(\"projects\", \"Projects\", \"amber\");\n\n  return (\n    <div className=\"grid min-w-0 gap-4 xl:grid-cols-[minmax(0,1.3fr)_minmax(320px,0.7fr)] xl:items-start\">\n      <div className=\"grid min-w-0 gap-4\">\n        <section className=\"min-w-0 overflow-hidden rounded border border-border bg-fd-background p-4 sm:p-5\">\n          <div className=\"grid min-w-0 gap-6 xl:grid-cols-[minmax(220px,0.36fr)_minmax(0,0.64fr)] xl:items-center\">\n            <div className=\"min-w-0 space-y-5\">\n              <div className=\"space-y-2\">\n                <div className=\"font-mono text-[11px] text-muted-foreground uppercase tracking-wide\">\n                  Convex total\n                </div>\n                <NumberFlow\n                  value={data.totalProjects}\n                  className=\"block font-semibold text-4xl sm:text-5xl\"\n                  transformTiming={{ duration: 850, easing: \"ease-out\" }}\n                  willChange\n                  isolate\n                />\n                <p className=\"max-w-md text-muted-foreground text-sm leading-6\">\n                  Live project starts in the current telemetry dataset.\n                </p>\n              </div>\n\n              <div className=\"grid min-w-0 gap-3 sm:grid-cols-2 xl:grid-cols-1 2xl:grid-cols-2\">\n                <div className=\"min-w-0 rounded border border-border p-3\">\n                  <div className=\"font-mono text-[11px] text-muted-foreground uppercase tracking-wide\">\n                    Average per day\n                  </div>\n                  <div className=\"mt-2 font-semibold text-2xl\">\n                    {data.avgProjectsPerDay.toFixed(1)}\n                  </div>\n                </div>\n                <div className=\"min-w-0 rounded border border-border p-3\">\n                  <div className=\"font-mono text-[11px] text-muted-foreground uppercase tracking-wide\">\n                    Leading pair\n                  </div>\n                  <div className=\"mt-2 font-medium text-base\">\n                    {shortenLabel(data.summary.topStack, 24)}\n                  </div>\n                </div>\n              </div>\n            </div>\n\n            <EvilAreaChart\n              className=\"h-[310px] min-w-0 w-full p-1\"\n              xDataKey=\"day\"\n              yDataKey=\"projects\"\n              data={sparklineData}\n              chartConfig={trendConfig}\n              curveType=\"monotone\"\n              areaVariant=\"gradient\"\n              strokeVariant=\"animated-dashed\"\n              activeDotVariant=\"colored-border\"\n              dotVariant=\"border\"\n              legendVariant=\"horizontal-bar\"\n              tooltipVariant=\"frosted-glass\"\n              tooltipRoundness=\"md\"\n              backgroundVariant=\"grid\"\n              xAxisProps={{\n                interval: \"preserveStartEnd\",\n                tickFormatter: (value) => String(value),\n              }}\n              yAxisProps={{\n                tickFormatter: (value) => formatCompactNumber(Number(value)),\n              }}\n            />\n          </div>\n        </section>\n\n        <div className=\"grid min-w-0 gap-4 md:grid-cols-2\">\n          <MetricTile\n            label=\"7 day momentum\"\n            value={formatDelta(data.momentum.deltaPercentage)}\n            detail={`${formatCompactNumber(data.momentum.last7Days)} projects in the last 7 days versus ${formatCompactNumber(data.momentum.previous7Days)} in the previous window.`}\n            icon={<Gauge className=\"h-4 w-4\" />}\n            tone={\n              momentumTone === \"up\" ? \"success\" : momentumTone === \"down\" ? \"warning\" : \"default\"\n            }\n          />\n\n          <MetricTile\n            label=\"Active days\"\n            value={`${data.momentum.activeDaysLast30}/30`}\n            detail=\"Days in the last month with at least one tracked project creation.\"\n            icon={<AreaChart className=\"h-4 w-4\" />}\n          />\n\n          <MetricTile\n            label=\"Peak day\"\n            value={data.momentum.peakDay ? formatCompactNumber(data.momentum.peakDay.count) : \"0\"}\n            detail={\n              data.momentum.peakDay\n                ? `Highest daily volume landed on ${formatDateLabel(data.momentum.peakDay.date)}.`\n                : \"Waiting for enough activity to identify a peak.\"\n            }\n            icon={<Flame className=\"h-4 w-4\" />}\n            tone=\"warning\"\n          />\n\n          <MetricTile\n            label=\"Busiest hour\"\n            value={data.momentum.busiestHour?.hour.replace(\":00\", \"\") ?? \"--\"}\n            detail={\n              data.momentum.busiestHour\n                ? `${formatCompactNumber(data.momentum.busiestHour.count)} projects kicked off during this UTC hour.`\n                : \"Hour-of-day activity appears once events begin arriving.\"\n            }\n            icon={<Sunrise className=\"h-4 w-4\" />}\n          />\n\n          <MetricTile\n            label=\"Leading choices\"\n            value={shortenLabel(\n              `${data.summary.mostPopularFrontend} / ${data.summary.mostPopularBackend}`,\n              24,\n            )}\n            detail={`${data.summary.mostPopularDatabase} leads database choices, and ${data.summary.mostPopularORM} leads ORM picks.`}\n            icon={<Sparkles className=\"h-4 w-4\" />}\n          />\n\n          <MetricTile\n            label=\"Runtime + package\"\n            value={shortenLabel(\n              `${data.summary.mostPopularRuntime} / ${data.summary.mostPopularPackageManager}`,\n              24,\n            )}\n            detail=\"Top runtime and package-manager choices across tracked project setups.\"\n            icon={<Radar className=\"h-4 w-4\" />}\n          />\n        </div>\n      </div>\n\n      <div className=\"grid min-w-0 gap-4\">\n        <section className=\"min-w-0 overflow-hidden rounded border border-border bg-fd-background p-4 sm:p-5\">\n          <div className=\"space-y-1.5\">\n            <h3 className=\"font-semibold text-sm sm:text-base\">Leading choices</h3>\n            <p className=\"text-muted-foreground text-sm leading-6\">\n              The top selected option in each major category, shown by tracked setup count.\n            </p>\n          </div>\n          <EvilBarChart\n            className=\"mt-3 h-[290px] min-w-0 w-full p-1\"\n            data={leadingChoices}\n            xDataKey=\"choice\"\n            yDataKey=\"setups\"\n            chartConfig={leadingChoicesConfig}\n            layout=\"horizontal\"\n            barVariant=\"default\"\n            barRadius={5}\n            hideLegend\n            enableHoverHighlight\n            tooltipVariant=\"frosted-glass\"\n            tooltipRoundness=\"md\"\n            backgroundVariant=\"grid\"\n            xAxisProps={{\n              tickFormatter: (value) => formatCompactNumber(Number(value)),\n            }}\n          />\n        </section>\n\n        <section className=\"min-w-0 overflow-hidden rounded border border-border bg-fd-background p-4 sm:p-5\">\n          <div className=\"space-y-1.5\">\n            <h3 className=\"font-semibold text-sm sm:text-base\">7 day comparison</h3>\n            <p className=\"text-muted-foreground text-sm leading-6\">\n              Recent project starts compared with the previous 7 day window.\n            </p>\n          </div>\n          <EvilBarChart\n            className=\"mt-3 h-[220px] min-w-0 w-full p-1\"\n            data={momentumComparison}\n            xDataKey=\"window\"\n            yDataKey=\"projects\"\n            chartConfig={momentumComparisonConfig}\n            barVariant=\"default\"\n            barRadius={5}\n            hideLegend\n            enableHoverHighlight\n            tooltipVariant=\"frosted-glass\"\n            tooltipRoundness=\"md\"\n            backgroundVariant=\"grid\"\n            yAxisProps={{\n              tickFormatter: (value) => formatCompactNumber(Number(value)),\n            }}\n          />\n        </section>\n      </div>\n    </div>\n  );\n}\n"
  },
  {
    "path": "apps/web/src/app/(home)/analytics/_components/preference-chart-card.tsx",
    "content": "\"use client\";\n\nimport { EvilBarChart } from \"@/components/evilcharts/charts/bar-chart\";\nimport { cn } from \"@/lib/utils\";\n\nimport {\n  buildCompactCategoryLabels,\n  formatCompactNumber,\n  formatCount,\n  formatPercent,\n} from \"./analytics-helpers\";\nimport { ChartCard } from \"./chart-card\";\nimport { getTone, seriesConfig } from \"./evil-chart-utils\";\nimport type { ShareDistributionItem } from \"./types\";\n\nfunction chunkItems<T>(items: T[], chunkCount: number) {\n  if (chunkCount <= 1 || items.length === 0) return [items];\n  const chunks = Array.from({ length: Math.min(chunkCount, items.length) }, () => [] as T[]);\n\n  items.forEach((item, index) => {\n    chunks[index % chunks.length]?.push(item);\n  });\n\n  return chunks.filter((chunk) => chunk.length > 0);\n}\n\ntype PreferenceChartCardProps = {\n  title: string;\n  description: string;\n  data: ShareDistributionItem[];\n  colorKey?: \"chart1\" | \"chart2\" | \"chart3\" | \"chart4\" | \"chart5\";\n  maxItems?: number;\n  className?: string;\n  chartClassName?: string;\n  layout?: \"horizontal\" | \"vertical\";\n  columnCount?: number;\n  columnGridClassName?: string;\n};\n\nconst colorToneOffset = {\n  chart1: 0,\n  chart2: 1,\n  chart3: 2,\n  chart4: 3,\n  chart5: 4,\n};\n\nexport function PreferenceChartCard({\n  title,\n  description,\n  data,\n  colorKey = \"chart1\",\n  maxItems,\n  className,\n  chartClassName,\n  layout = \"horizontal\",\n  columnCount = 1,\n  columnGridClassName,\n}: PreferenceChartCardProps) {\n  const ranking = typeof maxItems === \"number\" ? data.slice(0, maxItems) : data;\n  const chunks = columnCount > 1 ? chunkItems(ranking, columnCount) : [ranking];\n  const toneOffset = colorToneOffset[colorKey];\n\n  return (\n    <ChartCard\n      title={title}\n      description={description}\n      className={className}\n      contentClassName={\n        chunks.length > 1\n          ? cn(\n              \"grid min-w-0 gap-4\",\n              columnGridClassName ??\n                (layout === \"horizontal\"\n                  ? columnCount >= 3\n                    ? \"xl:grid-cols-2 2xl:grid-cols-3\"\n                    : \"xl:grid-cols-2\"\n                  : \"xl:grid-cols-2\"),\n            )\n          : undefined\n      }\n    >\n      {chunks.map((chunk, index) => {\n        const labels = buildCompactCategoryLabels(\n          chunk.map((item) => item.name),\n          layout === \"horizontal\" ? 22 : 12,\n        );\n        const chartData = chunk.map((item, itemIndex) => ({\n          label: labels[itemIndex] ?? item.name,\n          value: item.value,\n          share: item.share,\n          fullName: item.name,\n          formatted: `${formatCount(item.value)} (${formatPercent(item.share)})`,\n        }));\n        const tone = getTone(toneOffset + index);\n        const chartConfig = seriesConfig(\"value\", \"Tracked setups\", tone);\n        const height =\n          layout === \"horizontal\"\n            ? Math.max(240, chartData.length * 38 + 84)\n            : Math.max(260, chartData.length * 18 + 240);\n\n        return (\n          <div\n            key={`${title}-${index}`}\n            className={cn(\"min-h-[220px] min-w-0 overflow-hidden\", chartClassName)}\n            style={{ height }}\n          >\n            <EvilBarChart\n              className=\"h-full min-w-0 w-full p-1\"\n              data={chartData}\n              chartConfig={chartConfig}\n              xDataKey=\"label\"\n              yDataKey=\"value\"\n              layout={layout === \"horizontal\" ? \"horizontal\" : \"vertical\"}\n              barVariant=\"default\"\n              barRadius={5}\n              enableHoverHighlight\n              glowingBars={[\"value\"]}\n              hideLegend\n              tooltipVariant=\"frosted-glass\"\n              tooltipRoundness=\"md\"\n              backgroundVariant={layout === \"horizontal\" ? \"dots\" : \"grid\"}\n              xAxisProps={{\n                tickFormatter: (value) =>\n                  layout === \"horizontal\" ? formatCompactNumber(Number(value)) : String(value),\n              }}\n              yAxisProps={{\n                tickFormatter: (value) =>\n                  layout === \"horizontal\" ? String(value) : formatCompactNumber(Number(value)),\n              }}\n            />\n          </div>\n        );\n      })}\n    </ChartCard>\n  );\n}\n"
  },
  {
    "path": "apps/web/src/app/(home)/analytics/_components/section-header.tsx",
    "content": "import type { ReactNode } from \"react\";\n\nexport function SectionHeader({\n  label,\n  title,\n  description,\n  aside,\n}: {\n  label: string;\n  title: string;\n  description: string;\n  aside?: ReactNode;\n}) {\n  return (\n    <div className=\"space-y-3\">\n      <div className=\"flex flex-wrap items-center gap-3\">\n        <span className=\"font-mono font-semibold text-foreground text-sm\">{label}</span>\n        <div className=\"hidden h-px flex-1 bg-border sm:block\" />\n        {aside}\n      </div>\n      <div className=\"space-y-1.5\">\n        <h2 className=\"max-w-3xl font-semibold text-lg sm:text-xl\">{title}</h2>\n        <p className=\"max-w-3xl text-muted-foreground text-sm leading-6\">{description}</p>\n      </div>\n    </div>\n  );\n}\n"
  },
  {
    "path": "apps/web/src/app/(home)/analytics/_components/stack-configuration-charts.tsx",
    "content": "\"use client\";\n\nimport { shortenLabel } from \"./analytics-helpers\";\nimport { PreferenceChartCard } from \"./preference-chart-card\";\nimport { SectionHeader } from \"./section-header\";\nimport type { AggregatedAnalyticsData } from \"./types\";\n\nexport function StackSection({ data }: { data: AggregatedAnalyticsData }) {\n  return (\n    <div className=\"space-y-6\">\n      <SectionHeader\n        label=\"Stack choices\"\n        title=\"Frameworks, runtimes, data layers, and auth\"\n        description=\"The most common stack decisions and pairings selected during project creation.\"\n        aside={\n          <div className=\"rounded border border-border bg-fd-background px-3 py-1.5 font-mono text-muted-foreground text-xs\">\n            top stack {shortenLabel(data.summary.topStack, 28)}\n          </div>\n        }\n      />\n\n      <div className=\"grid gap-4 xl:grid-cols-2\">\n        <PreferenceChartCard\n          title=\"Frontend and backend pairings\"\n          description=\"The combinations that appear most often across tracked setups.\"\n          data={data.stackCombinationDistribution}\n          colorKey=\"chart1\"\n          maxItems={10}\n          chartClassName=\"min-h-[420px]\"\n        />\n        <PreferenceChartCard\n          title=\"Database and ORM pairings\"\n          description=\"Which persistence choices tend to be selected together.\"\n          data={data.databaseORMCombinationDistribution}\n          colorKey=\"chart4\"\n          maxItems={10}\n          chartClassName=\"min-h-[420px]\"\n        />\n      </div>\n\n      <div className=\"grid gap-4 xl:grid-cols-2\">\n        <PreferenceChartCard\n          title=\"Frontend\"\n          description=\"How often each frontend was selected.\"\n          data={data.frontendDistribution}\n          colorKey=\"chart1\"\n        />\n        <PreferenceChartCard\n          title=\"Backend\"\n          description=\"How often each backend was selected.\"\n          data={data.backendDistribution}\n          colorKey=\"chart2\"\n        />\n      </div>\n\n      <div className=\"grid gap-4 xl:grid-cols-[minmax(0,1.2fr)_minmax(0,0.8fr)]\">\n        <PreferenceChartCard\n          title=\"Database\"\n          description=\"How often each database was selected.\"\n          data={data.databaseDistribution}\n          colorKey=\"chart4\"\n          chartClassName=\"min-h-[320px]\"\n        />\n        <PreferenceChartCard\n          title=\"ORM\"\n          description=\"How often each ORM was selected.\"\n          data={data.ormDistribution}\n          colorKey=\"chart5\"\n        />\n      </div>\n\n      <div className=\"grid gap-4 xl:grid-cols-2 2xl:grid-cols-3\">\n        <PreferenceChartCard\n          title=\"API\"\n          description=\"How often each API type was selected.\"\n          data={data.apiDistribution}\n          colorKey=\"chart3\"\n        />\n        <PreferenceChartCard\n          title=\"Authentication provider\"\n          description=\"How often each authentication provider was selected.\"\n          data={data.authDistribution}\n          colorKey=\"chart1\"\n        />\n        <PreferenceChartCard\n          title=\"Runtime\"\n          description=\"How often each runtime was selected.\"\n          data={data.runtimeDistribution}\n          colorKey=\"chart2\"\n        />\n      </div>\n    </div>\n  );\n}\n"
  },
  {
    "path": "apps/web/src/app/(home)/analytics/_components/timeline-charts.tsx",
    "content": "\"use client\";\n\nimport { EvilAreaChart } from \"@/components/evilcharts/charts/area-chart\";\nimport { EvilBarChart } from \"@/components/evilcharts/charts/bar-chart\";\n\nimport {\n  formatCompactNumber,\n  formatDateLabel,\n  formatHourLabel,\n  formatMonthLabel,\n} from \"./analytics-helpers\";\nimport { ChartCard } from \"./chart-card\";\nimport { multiSeriesConfig, seriesConfig } from \"./evil-chart-utils\";\nimport { SectionHeader } from \"./section-header\";\nimport type { AggregatedAnalyticsData } from \"./types\";\n\nexport function TimelineSection({ data }: { data: AggregatedAnalyticsData }) {\n  const peakDayLabel = data.momentum.peakDay ? formatDateLabel(data.momentum.peakDay.date) : \"n/a\";\n  const busiestHourLabel = data.momentum.busiestHour\n    ? `${formatHourLabel(data.momentum.busiestHour.hour)} UTC`\n    : \"n/a\";\n  const dailyData = data.timeSeries.map((point) => ({\n    day: formatDateLabel(point.date),\n    projects: point.count,\n    average: Number(point.rollingAverage.toFixed(2)),\n  }));\n  const monthlyData = data.monthlyTimeSeries.map((point) => ({\n    month: formatMonthLabel(point.month, \"MMM yy\"),\n    projects: point.totalProjects,\n  }));\n  const weekdayData = data.weekdayDistribution.map((point) => ({\n    weekday: point.shortLabel,\n    average: Number(point.averageDailyProjects.toFixed(2)),\n    total: point.count,\n  }));\n  const hourlyData = data.hourlyDistribution.map((point) => ({\n    hour: point.label,\n    projects: point.count,\n  }));\n  const dailyConfig = multiSeriesConfig([\n    { key: \"projects\", label: \"Daily starts\", tone: \"blue\" },\n    { key: \"average\", label: \"7 day average\", tone: \"teal\" },\n  ]);\n  const monthlyConfig = seriesConfig(\"projects\", \"Monthly starts\", \"rose\");\n  const weekdayConfig = seriesConfig(\"average\", \"Average starts\", \"amber\");\n  const hourlyConfig = seriesConfig(\"projects\", \"Project starts\", \"violet\");\n\n  return (\n    <div className=\"space-y-6\">\n      <SectionHeader\n        label=\"Activity\"\n        title=\"Project creation volume over time\"\n        description=\"Recent momentum, monthly totals, weekday averages, and UTC-hour concentration from live CLI telemetry.\"\n        aside={\n          <div className=\"rounded border border-border bg-fd-background px-3 py-1.5 font-mono text-muted-foreground text-xs\">\n            peak {peakDayLabel} · hot hour {busiestHourLabel}\n          </div>\n        }\n      />\n\n      <div className=\"grid gap-4 xl:grid-cols-[1.35fr_0.95fr]\">\n        <ChartCard\n          title=\"Daily project starts\"\n          description=\"Raw daily activity with the rolling average layered on top.\"\n          footer={\n            <>\n              Last 7 days:{\" \"}\n              <span className=\"text-foreground\">\n                {formatCompactNumber(data.momentum.last7Days)}\n              </span>\n              {\" · \"}\n              Previous 7 days:{\" \"}\n              <span className=\"text-foreground\">\n                {formatCompactNumber(data.momentum.previous7Days)}\n              </span>\n            </>\n          }\n        >\n          <EvilAreaChart\n            className=\"h-[340px] w-full p-1\"\n            xDataKey=\"day\"\n            yDataKey=\"projects\"\n            data={dailyData}\n            chartConfig={dailyConfig}\n            curveType=\"monotone\"\n            areaVariant=\"gradient\"\n            strokeVariant=\"animated-dashed\"\n            dotVariant=\"border\"\n            activeDotVariant=\"colored-border\"\n            legendVariant=\"horizontal-bar\"\n            tooltipVariant=\"frosted-glass\"\n            tooltipRoundness=\"md\"\n            backgroundVariant=\"grid\"\n            xAxisProps={{ interval: \"preserveStartEnd\" }}\n            yAxisProps={{\n              tickFormatter: (value) => formatCompactNumber(Number(value)),\n            }}\n          />\n        </ChartCard>\n\n        <ChartCard\n          title=\"Monthly starts\"\n          description=\"The longer view of when the tracked history accumulated; the latest month may still be in progress.\"\n          footer={\n            <>\n              Live total:{\" \"}\n              <span className=\"text-foreground\">{data.totalProjects.toLocaleString()}</span>\n            </>\n          }\n        >\n          <EvilBarChart\n            className=\"h-[340px] w-full p-1\"\n            xDataKey=\"month\"\n            yDataKey=\"projects\"\n            data={monthlyData}\n            chartConfig={monthlyConfig}\n            barVariant=\"default\"\n            barRadius={5}\n            enableBufferBar\n            glowingBars={[\"projects\"]}\n            hideLegend\n            tooltipVariant=\"frosted-glass\"\n            tooltipRoundness=\"md\"\n            backgroundVariant=\"diagonal-lines\"\n            xAxisProps={{ interval: \"preserveStartEnd\" }}\n            yAxisProps={{\n              tickFormatter: (value) => formatCompactNumber(Number(value)),\n            }}\n          />\n        </ChartCard>\n      </div>\n\n      <div className=\"grid gap-4 lg:grid-cols-2\">\n        <ChartCard\n          title=\"Weekday average\"\n          description=\"Average project starts for each weekday across the last month.\"\n          footer={\n            <>\n              Active days in the last 30:{\" \"}\n              <span className=\"text-foreground\">{data.momentum.activeDaysLast30}</span>\n            </>\n          }\n        >\n          <EvilBarChart\n            className=\"h-[260px] w-full p-1\"\n            xDataKey=\"weekday\"\n            yDataKey=\"average\"\n            data={weekdayData}\n            chartConfig={weekdayConfig}\n            barVariant=\"default\"\n            barRadius={5}\n            enableHoverHighlight\n            hideLegend\n            tooltipVariant=\"frosted-glass\"\n            tooltipRoundness=\"md\"\n            backgroundVariant=\"tiny-checkers\"\n            yAxisProps={{\n              tickFormatter: (value) => formatCompactNumber(Number(value)),\n            }}\n          />\n        </ChartCard>\n\n        <ChartCard\n          title=\"UTC hour distribution\"\n          description=\"When project starts cluster during the day.\"\n          footer={\n            <>\n              Busiest hour:{\" \"}\n              <span className=\"text-foreground\">\n                {data.momentum.busiestHour\n                  ? `${formatHourLabel(data.momentum.busiestHour.hour)} UTC`\n                  : \"n/a\"}\n              </span>\n            </>\n          }\n        >\n          <EvilBarChart\n            className=\"h-[260px] w-full p-1\"\n            xDataKey=\"hour\"\n            yDataKey=\"projects\"\n            data={hourlyData}\n            chartConfig={hourlyConfig}\n            barVariant=\"default\"\n            barRadius={4}\n            enableHoverHighlight\n            hideLegend\n            tooltipVariant=\"frosted-glass\"\n            tooltipRoundness=\"md\"\n            backgroundVariant=\"dots\"\n            xAxisProps={{\n              interval: 1,\n              tickFormatter: (value) => String(value),\n            }}\n            yAxisProps={{\n              tickFormatter: (value) => formatCompactNumber(Number(value)),\n            }}\n          />\n        </ChartCard>\n      </div>\n    </div>\n  );\n}\n"
  },
  {
    "path": "apps/web/src/app/(home)/analytics/_components/types.ts",
    "content": "export type DistributionItem = { name: string; value: number };\nexport type Distribution = DistributionItem[];\n\nexport type ShareDistributionItem = DistributionItem & {\n  share: number;\n};\n\nexport type VersionDistributionItem = {\n  version: string;\n  count: number;\n  share: number;\n};\n\nexport type VersionDistribution = VersionDistributionItem[];\n\nexport type TimeSeriesPoint = {\n  date: string;\n  dateValue: Date;\n  count: number;\n  rollingAverage: number;\n  cumulativeProjects: number;\n};\n\nexport type MonthlyPoint = {\n  month: string;\n  monthDate: Date;\n  totalProjects: number;\n  cumulativeProjects: number;\n};\n\nexport type HourlyPoint = {\n  hour: string;\n  hourValue: number;\n  label: string;\n  count: number;\n};\n\nexport type WeekdayPoint = {\n  weekday: string;\n  shortLabel: string;\n  dayIndex: number;\n  count: number;\n  averageDailyProjects: number;\n};\n\nexport type ComboMatrixPoint = {\n  x: string;\n  y: string;\n  count: number;\n  share: number;\n};\n\nexport type ComboMatrix = {\n  data: ComboMatrixPoint[];\n  xDomain: string[];\n  yDomain: string[];\n  maxValue: number;\n};\n\nexport type MomentumSnapshot = {\n  trackingDays: number;\n  last7Days: number;\n  previous7Days: number;\n  delta: number;\n  deltaPercentage: number | null;\n  activeDaysLast30: number;\n  peakDay: { date: string; count: number } | null;\n  busiestHour: { hour: string; count: number } | null;\n};\n\nexport type AggregatedAnalyticsData = {\n  lastUpdated: string | null;\n  totalProjects: number;\n  avgProjectsPerDay: number;\n  timeSeries: TimeSeriesPoint[];\n  monthlyTimeSeries: MonthlyPoint[];\n  hourlyDistribution: HourlyPoint[];\n  weekdayDistribution: WeekdayPoint[];\n  platformDistribution: ShareDistributionItem[];\n  packageManagerDistribution: ShareDistributionItem[];\n  backendDistribution: ShareDistributionItem[];\n  databaseDistribution: ShareDistributionItem[];\n  ormDistribution: ShareDistributionItem[];\n  dbSetupDistribution: ShareDistributionItem[];\n  apiDistribution: ShareDistributionItem[];\n  frontendDistribution: ShareDistributionItem[];\n  authDistribution: ShareDistributionItem[];\n  runtimeDistribution: ShareDistributionItem[];\n  addonsDistribution: ShareDistributionItem[];\n  examplesDistribution: ShareDistributionItem[];\n  gitDistribution: ShareDistributionItem[];\n  installDistribution: ShareDistributionItem[];\n  webDeployDistribution: ShareDistributionItem[];\n  serverDeployDistribution: ShareDistributionItem[];\n  paymentsDistribution: ShareDistributionItem[];\n  nodeVersionDistribution: VersionDistribution;\n  cliVersionDistribution: VersionDistribution;\n  stackCombinationDistribution: ShareDistributionItem[];\n  databaseORMCombinationDistribution: ShareDistributionItem[];\n  stackMatrix: ComboMatrix;\n  databaseOrmMatrix: ComboMatrix;\n  summary: {\n    mostPopularFrontend: string;\n    mostPopularBackend: string;\n    mostPopularDatabase: string;\n    mostPopularORM: string;\n    mostPopularAPI: string;\n    mostPopularAuth: string;\n    mostPopularPackageManager: string;\n    mostPopularRuntime: string;\n    topStack: string;\n    topDatabasePair: string;\n  };\n  momentum: MomentumSnapshot;\n};\n"
  },
  {
    "path": "apps/web/src/app/(home)/analytics/analytics-client.tsx",
    "content": "\"use client\";\n\nimport { api } from \"@better-t-stack/backend/convex/_generated/api\";\nimport { type Preloaded, useConvexConnectionState, usePreloadedQuery } from \"convex/react\";\nimport { useEffect, useState } from \"react\";\n\nimport {\n  buildComboMatrix,\n  buildWeekdayDistribution,\n  splitComboLabel,\n  versionWithShare,\n  withShare,\n} from \"./_components/analytics-helpers\";\nimport AnalyticsPage from \"./_components/analytics-page\";\nimport type { AggregatedAnalyticsData, Distribution, TimeSeriesPoint } from \"./_components/types\";\n\ntype PrecomputedStats = {\n  totalProjects: number;\n  lastEventTime: number;\n  backend: Record<string, number>;\n  frontend: Record<string, number>;\n  database: Record<string, number>;\n  orm: Record<string, number>;\n  api: Record<string, number>;\n  auth: Record<string, number>;\n  runtime: Record<string, number>;\n  packageManager: Record<string, number>;\n  platform: Record<string, number>;\n  addons: Record<string, number>;\n  examples: Record<string, number>;\n  dbSetup: Record<string, number>;\n  webDeploy: Record<string, number>;\n  serverDeploy: Record<string, number>;\n  payments: Record<string, number>;\n  git: Record<string, number>;\n  install: Record<string, number>;\n  nodeVersion: Record<string, number>;\n  cliVersion: Record<string, number>;\n  hourlyDistribution: Record<string, number>;\n  stackCombinations: Record<string, number>;\n  dbOrmCombinations: Record<string, number>;\n};\n\ntype DailyStats = { date: string; count: number };\ntype MonthlyStats = {\n  monthly: Array<{ month: string; totalProjects: number }>;\n  firstDate: string | null;\n  lastDate: string | null;\n};\ntype ConnectionStatus = \"online\" | \"connecting\" | \"reconnecting\" | \"offline\";\n\nconst MILLISECONDS_PER_DAY = 24 * 60 * 60 * 1000;\n\nfunction getConnectionStatus({\n  isWebSocketConnected,\n  hasEverConnected,\n  connectionRetries,\n}: {\n  isWebSocketConnected: boolean;\n  hasEverConnected: boolean;\n  connectionRetries: number;\n}): ConnectionStatus {\n  if (isWebSocketConnected) return \"online\";\n  if (hasEverConnected) return \"reconnecting\";\n  if (connectionRetries > 0) return \"offline\";\n  return \"connecting\";\n}\n\nfunction recordToDistribution(record: Record<string, number>): Distribution {\n  return Object.entries(record)\n    .map(([name, value]) => ({ name, value }))\n    .sort((a, b) => b.value - a.value);\n}\n\nfunction getMostPopular(dist: Distribution) {\n  return dist.length > 0 ? dist[0].name : \"none\";\n}\n\nfunction getCalendarDaySpan(timeSeries: DailyStats[]): number {\n  if (timeSeries.length === 0) return 1;\n\n  const firstDate = timeSeries[0]?.date;\n  const lastDate = timeSeries[timeSeries.length - 1]?.date;\n  if (!firstDate || !lastDate) return 1;\n\n  const start = Date.parse(`${firstDate}T00:00:00Z`);\n  const end = Date.parse(`${lastDate}T00:00:00Z`);\n\n  if (Number.isNaN(start) || Number.isNaN(end) || end < start) {\n    return Math.max(timeSeries.length, 1);\n  }\n\n  return Math.floor((end - start) / MILLISECONDS_PER_DAY) + 1;\n}\n\nfunction getCalendarDaySpanFromRange(\n  firstDate: string | null,\n  lastDate: string | null,\n  fallbackSeries: DailyStats[],\n): number {\n  if (!firstDate || !lastDate) {\n    return getCalendarDaySpan(fallbackSeries);\n  }\n\n  const start = Date.parse(`${firstDate}T00:00:00Z`);\n  const end = Date.parse(`${lastDate}T00:00:00Z`);\n\n  if (Number.isNaN(start) || Number.isNaN(end) || end < start) {\n    return getCalendarDaySpan(fallbackSeries);\n  }\n\n  return Math.floor((end - start) / MILLISECONDS_PER_DAY) + 1;\n}\n\nfunction buildTimeSeries(dailyStats: DailyStats[]): TimeSeriesPoint[] {\n  const sorted = [...dailyStats].sort((a, b) => a.date.localeCompare(b.date));\n  let cumulativeProjects = 0;\n\n  return sorted.map((day, index) => {\n    cumulativeProjects += day.count;\n    const trailingWindow = sorted.slice(Math.max(0, index - 6), index + 1);\n    const rollingAverage =\n      trailingWindow.reduce((sum, point) => sum + point.count, 0) / trailingWindow.length;\n\n    return {\n      date: day.date,\n      dateValue: new Date(`${day.date}T00:00:00`),\n      count: day.count,\n      rollingAverage,\n      cumulativeProjects,\n    };\n  });\n}\n\nfunction buildMonthlyTimeSeries(monthlyStats: MonthlyStats[\"monthly\"]) {\n  let cumulativeProjects = 0;\n\n  return [...monthlyStats]\n    .sort((a, b) => a.month.localeCompare(b.month))\n    .map((month) => {\n      cumulativeProjects += month.totalProjects;\n      return {\n        month: month.month,\n        monthDate: new Date(`${month.month}-01T00:00:00`),\n        totalProjects: month.totalProjects,\n        cumulativeProjects,\n      };\n    });\n}\n\nfunction buildFromPrecomputed(\n  stats: PrecomputedStats,\n  dailyStats: DailyStats[],\n  monthlyStats: MonthlyStats,\n): AggregatedAnalyticsData {\n  const totalProjects = stats.totalProjects;\n  const backendDistribution = recordToDistribution(stats.backend);\n  const frontendDistribution = recordToDistribution(stats.frontend);\n  const databaseDistribution = recordToDistribution(stats.database);\n  const ormDistribution = recordToDistribution(stats.orm);\n  const apiDistribution = recordToDistribution(stats.api);\n  const authDistribution = recordToDistribution(stats.auth);\n  const runtimeDistribution = recordToDistribution(stats.runtime);\n  const packageManagerDistribution = recordToDistribution(stats.packageManager);\n  const platformDistribution = recordToDistribution(stats.platform);\n  const addonsDistribution = recordToDistribution(stats.addons);\n  const examplesDistribution = recordToDistribution(stats.examples);\n  const dbSetupDistribution = recordToDistribution(stats.dbSetup);\n  const webDeployDistribution = recordToDistribution(stats.webDeploy);\n  const serverDeployDistribution = recordToDistribution(stats.serverDeploy);\n  const paymentsDistribution = recordToDistribution(stats.payments);\n  const gitDistribution = recordToDistribution(stats.git);\n  const installDistribution = recordToDistribution(stats.install);\n  const stackCombinationDistribution = withShare(\n    recordToDistribution(stats.stackCombinations),\n    totalProjects,\n  );\n  const databaseORMCombinationDistribution = withShare(\n    recordToDistribution(stats.dbOrmCombinations),\n    totalProjects,\n  );\n\n  const timeSeries = buildTimeSeries(dailyStats);\n  const monthlyTimeSeries = buildMonthlyTimeSeries(monthlyStats.monthly);\n  const calendarDaySpan = getCalendarDaySpanFromRange(\n    monthlyStats.firstDate,\n    monthlyStats.lastDate,\n    dailyStats,\n  );\n\n  const hourlyDistribution = Array.from({ length: 24 }, (_, hourValue) => {\n    const hour = String(hourValue).padStart(2, \"0\");\n    return {\n      hour: `${hour}:00`,\n      hourValue,\n      label: hour,\n      count: stats.hourlyDistribution[hour] || 0,\n    };\n  });\n\n  const weekdayDistribution = buildWeekdayDistribution(timeSeries);\n  const nodeVersionDistribution = versionWithShare(\n    recordToDistribution(stats.nodeVersion).map((item) => ({\n      version: item.name,\n      count: item.value,\n    })),\n    totalProjects,\n  );\n  const cliVersionDistribution = versionWithShare(\n    recordToDistribution(stats.cliVersion)\n      .filter((item) => item.name !== \"unknown\")\n      .map((item) => ({\n        version: item.name,\n        count: item.value,\n      })),\n    totalProjects,\n  );\n\n  const recent7Days = timeSeries.slice(-7).reduce((sum, point) => sum + point.count, 0);\n  const previous7Days = timeSeries.slice(-14, -7).reduce((sum, point) => sum + point.count, 0);\n  const delta = recent7Days - previous7Days;\n  const deltaPercentage = previous7Days > 0 ? delta / previous7Days : recent7Days > 0 ? null : 0;\n  const peakDay = timeSeries.reduce<TimeSeriesPoint | null>(\n    (max, point) => (max && max.count >= point.count ? max : point),\n    null,\n  );\n  const busiestHourCandidate = hourlyDistribution.reduce<\n    (typeof hourlyDistribution)[number] | null\n  >((max, point) => (max && max.count >= point.count ? max : point), null);\n  const busiestHour =\n    busiestHourCandidate && busiestHourCandidate.count > 0 ? busiestHourCandidate : null;\n\n  return {\n    lastUpdated: new Date(stats.lastEventTime).toISOString(),\n    totalProjects,\n    avgProjectsPerDay: totalProjects / Math.max(calendarDaySpan, 1),\n    timeSeries,\n    monthlyTimeSeries,\n    hourlyDistribution,\n    weekdayDistribution,\n    platformDistribution: withShare(platformDistribution, totalProjects),\n    packageManagerDistribution: withShare(packageManagerDistribution, totalProjects),\n    backendDistribution: withShare(backendDistribution, totalProjects),\n    databaseDistribution: withShare(databaseDistribution, totalProjects),\n    ormDistribution: withShare(ormDistribution, totalProjects),\n    dbSetupDistribution: withShare(dbSetupDistribution, totalProjects),\n    apiDistribution: withShare(apiDistribution, totalProjects),\n    frontendDistribution: withShare(frontendDistribution, totalProjects),\n    authDistribution: withShare(authDistribution, totalProjects),\n    runtimeDistribution: withShare(runtimeDistribution, totalProjects),\n    addonsDistribution: withShare(addonsDistribution, totalProjects),\n    examplesDistribution: withShare(examplesDistribution, totalProjects),\n    gitDistribution: withShare(gitDistribution, totalProjects),\n    installDistribution: withShare(installDistribution, totalProjects),\n    webDeployDistribution: withShare(webDeployDistribution, totalProjects),\n    serverDeployDistribution: withShare(serverDeployDistribution, totalProjects),\n    paymentsDistribution: withShare(paymentsDistribution, totalProjects),\n    nodeVersionDistribution,\n    cliVersionDistribution,\n    stackCombinationDistribution,\n    databaseORMCombinationDistribution,\n    stackMatrix: buildComboMatrix({\n      distribution: stackCombinationDistribution,\n      total: totalProjects,\n      xFromLabel: (name) => splitComboLabel(name)[1],\n      yFromLabel: (name) => splitComboLabel(name)[0],\n    }),\n    databaseOrmMatrix: buildComboMatrix({\n      distribution: databaseORMCombinationDistribution,\n      total: totalProjects,\n      xFromLabel: (name) => splitComboLabel(name)[1],\n      yFromLabel: (name) => splitComboLabel(name)[0],\n    }),\n    summary: {\n      mostPopularFrontend: getMostPopular(frontendDistribution),\n      mostPopularBackend: getMostPopular(backendDistribution),\n      mostPopularDatabase: getMostPopular(databaseDistribution),\n      mostPopularORM: getMostPopular(ormDistribution),\n      mostPopularAPI: getMostPopular(apiDistribution),\n      mostPopularAuth: getMostPopular(authDistribution),\n      mostPopularPackageManager: getMostPopular(packageManagerDistribution),\n      mostPopularRuntime: getMostPopular(runtimeDistribution),\n      topStack: stackCombinationDistribution[0]?.name ?? \"none\",\n      topDatabasePair: databaseORMCombinationDistribution[0]?.name ?? \"none\",\n    },\n    momentum: {\n      trackingDays: calendarDaySpan,\n      last7Days: recent7Days,\n      previous7Days,\n      delta,\n      deltaPercentage,\n      activeDaysLast30: timeSeries.filter((point) => point.count > 0).length,\n      peakDay: peakDay ? { date: peakDay.date, count: peakDay.count } : null,\n      busiestHour: busiestHour ? { hour: busiestHour.hour, count: busiestHour.count } : null,\n    },\n  };\n}\n\nconst emptyData: AggregatedAnalyticsData = {\n  lastUpdated: null,\n  totalProjects: 0,\n  avgProjectsPerDay: 0,\n  timeSeries: [],\n  monthlyTimeSeries: [],\n  hourlyDistribution: [],\n  weekdayDistribution: [],\n  platformDistribution: [],\n  packageManagerDistribution: [],\n  backendDistribution: [],\n  databaseDistribution: [],\n  ormDistribution: [],\n  dbSetupDistribution: [],\n  apiDistribution: [],\n  frontendDistribution: [],\n  authDistribution: [],\n  runtimeDistribution: [],\n  addonsDistribution: [],\n  examplesDistribution: [],\n  gitDistribution: [],\n  installDistribution: [],\n  webDeployDistribution: [],\n  serverDeployDistribution: [],\n  paymentsDistribution: [],\n  nodeVersionDistribution: [],\n  cliVersionDistribution: [],\n  stackCombinationDistribution: [],\n  databaseORMCombinationDistribution: [],\n  stackMatrix: { data: [], xDomain: [], yDomain: [], maxValue: 0 },\n  databaseOrmMatrix: { data: [], xDomain: [], yDomain: [], maxValue: 0 },\n  summary: {\n    mostPopularFrontend: \"none\",\n    mostPopularBackend: \"none\",\n    mostPopularDatabase: \"none\",\n    mostPopularORM: \"none\",\n    mostPopularAPI: \"none\",\n    mostPopularAuth: \"none\",\n    mostPopularPackageManager: \"none\",\n    mostPopularRuntime: \"none\",\n    topStack: \"none\",\n    topDatabasePair: \"none\",\n  },\n  momentum: {\n    trackingDays: 0,\n    last7Days: 0,\n    previous7Days: 0,\n    delta: 0,\n    deltaPercentage: 0,\n    activeDaysLast30: 0,\n    peakDay: null,\n    busiestHour: null,\n  },\n};\n\nexport function AnalyticsClient({\n  preloadedStats,\n  preloadedDailyStats,\n  preloadedMonthlyStats,\n}: {\n  preloadedStats: Preloaded<typeof api.analytics.getStats>;\n  preloadedDailyStats: Preloaded<typeof api.analytics.getDailyStats>;\n  preloadedMonthlyStats: Preloaded<typeof api.analytics.getMonthlyStats>;\n}) {\n  const stats = usePreloadedQuery(preloadedStats);\n  const dailyStats = usePreloadedQuery(preloadedDailyStats);\n  const monthlyStats = usePreloadedQuery(preloadedMonthlyStats);\n  const connectionState = useConvexConnectionState();\n  const [hasHydrated, setHasHydrated] = useState(false);\n\n  useEffect(() => {\n    setHasHydrated(true);\n  }, []);\n\n  const connectionStatus = hasHydrated ? getConnectionStatus(connectionState) : \"connecting\";\n\n  const data = stats ? buildFromPrecomputed(stats, dailyStats, monthlyStats) : emptyData;\n\n  const legacy = {\n    total: 55434,\n    avgPerDay: 326.1,\n    lastUpdatedIso: \"2025-11-13T10:10:00.000Z\",\n    source: \"Legacy analytics (pre-Convex)\",\n  };\n\n  return <AnalyticsPage data={data} legacy={legacy} connectionStatus={connectionStatus} />;\n}\n"
  },
  {
    "path": "apps/web/src/app/(home)/analytics/page.tsx",
    "content": "import { api } from \"@better-t-stack/backend/convex/_generated/api\";\nimport { preloadQuery } from \"convex/nextjs\";\nimport type { Metadata } from \"next\";\n\nimport { AnalyticsClient } from \"./analytics-client\";\n\nexport const metadata: Metadata = {\n  title: \"Analytics - Better-T-Stack\",\n  description: \"Convex-backed project creation analytics for Better-T-Stack.\",\n  openGraph: {\n    title: \"Analytics - Better-T-Stack\",\n    description: \"Convex-backed project creation analytics for Better-T-Stack.\",\n    url: \"https://better-t-stack.dev/analytics\",\n    images: [\n      {\n        url: \"https://r2.better-t-stack.dev/og.png\",\n        width: 1200,\n        height: 630,\n        alt: \"Better-T-Stack Convex Analytics\",\n      },\n    ],\n  },\n  twitter: {\n    card: \"summary_large_image\",\n    title: \"Analytics - Better-T-Stack\",\n    description: \"Convex-backed project creation analytics for Better-T-Stack.\",\n    images: [\"https://r2.better-t-stack.dev/og.png\"],\n  },\n};\n\nexport default async function Analytics() {\n  const [preloadedStats, preloadedDailyStats, preloadedMonthlyStats] = await Promise.all([\n    preloadQuery(api.analytics.getStats, {}),\n    preloadQuery(api.analytics.getDailyStats, { days: 30 }),\n    preloadQuery(api.analytics.getMonthlyStats, {}),\n  ]);\n\n  return (\n    <AnalyticsClient\n      preloadedStats={preloadedStats}\n      preloadedDailyStats={preloadedDailyStats}\n      preloadedMonthlyStats={preloadedMonthlyStats}\n    />\n  );\n}\n"
  },
  {
    "path": "apps/web/src/app/(home)/layout.tsx",
    "content": "\"use client\";\n\nimport { HomeLayout } from \"fumadocs-ui/layouts/home\";\nimport type { ReactNode } from \"react\";\n\nimport { baseOptions } from \"@/app/layout.config\";\n\nexport default function Layout({ children }: { children: ReactNode }) {\n  return (\n    <HomeLayout\n      {...baseOptions}\n      style={\n        {\n          \"--fd-layout-width\": \"100%\",\n        } as object\n      }\n    >\n      <main className=\"h-full w-full\">{children}</main>\n    </HomeLayout>\n  );\n}\n"
  },
  {
    "path": "apps/web/src/app/(home)/new/_components/action-buttons.tsx",
    "content": "\"use client\";\n\nimport { RefreshCw, Settings, Shuffle, Star } from \"lucide-react\";\n\ntype ActionButtonsProps = {\n  onReset: () => void;\n  onRandom: () => void;\n  onSave: () => void;\n  onLoad: () => void;\n  hasSavedStack: boolean;\n};\n\nexport function ActionButtons({\n  onReset,\n  onRandom,\n  onSave,\n  onLoad,\n  hasSavedStack,\n}: ActionButtonsProps) {\n  return (\n    <div className=\"space-y-2\">\n      <p className=\"font-mono text-[11px] text-muted-foreground uppercase tracking-wide\">Actions</p>\n      <div className=\"grid grid-cols-2 gap-1.5\">\n        <button\n          type=\"button\"\n          onClick={onRandom}\n          className=\"builder-focus-ring flex items-center justify-center gap-1.5 rounded-md bg-primary/15 px-2 py-1.5 font-mono font-medium text-primary text-xs transition-colors hover:bg-primary/22\"\n          title=\"Generate a random stack\"\n        >\n          <Shuffle className=\"h-3 w-3\" />\n          Randomize\n        </button>\n        <button\n          type=\"button\"\n          onClick={onSave}\n          className=\"builder-focus-ring flex items-center justify-center gap-1.5 rounded-md bg-primary/15 px-2 py-1.5 font-mono font-medium text-primary text-xs transition-colors hover:bg-primary/22\"\n          title=\"Save current preferences\"\n        >\n          <Star className=\"h-3 w-3\" />\n          Save\n        </button>\n      </div>\n      <div className=\"grid grid-cols-2 gap-1.5\">\n        <button\n          type=\"button\"\n          onClick={onReset}\n          className=\"builder-focus-ring flex items-center justify-center gap-1.5 rounded-md bg-muted/20 px-2 py-1.5 font-mono font-medium text-muted-foreground text-xs transition-colors hover:bg-muted/35 hover:text-foreground\"\n          title=\"Reset to defaults\"\n        >\n          <RefreshCw className=\"h-3 w-3\" />\n          Reset\n        </button>\n        {hasSavedStack ? (\n          <button\n            type=\"button\"\n            onClick={onLoad}\n            className=\"builder-focus-ring flex items-center justify-center gap-1.5 rounded-md bg-muted/20 px-2 py-1.5 font-mono font-medium text-muted-foreground text-xs transition-colors hover:bg-muted/35 hover:text-foreground\"\n            title=\"Load saved preferences\"\n          >\n            <Settings className=\"h-3 w-3\" />\n            Load\n          </button>\n        ) : (\n          <div className=\"rounded-md bg-muted/15 px-2 py-1.5 text-center font-mono text-[11px] text-muted-foreground\">\n            No saved stack\n          </div>\n        )}\n      </div>\n    </div>\n  );\n}\n"
  },
  {
    "path": "apps/web/src/app/(home)/new/_components/code-viewer.tsx",
    "content": "\"use client\";\n\nimport { memo, useMemo } from \"react\";\n\nimport type { BundledLanguage } from \"@/components/ui/kibo-ui/code-block\";\nimport {\n  CodeBlock,\n  CodeBlockBody,\n  CodeBlockContent,\n  CodeBlockCopyButton,\n  CodeBlockFilename,\n  CodeBlockFiles,\n  CodeBlockHeader,\n  CodeBlockItem,\n} from \"@/components/ui/kibo-ui/code-block\";\n\ninterface CodeViewerProps {\n  filePath: string;\n  content: string;\n  extension: string;\n}\n\n// Map file extensions to Shiki language IDs\nfunction getLanguage(extension: string): BundledLanguage {\n  const languageMap: Record<string, BundledLanguage> = {\n    ts: \"typescript\",\n    tsx: \"tsx\",\n    js: \"javascript\",\n    jsx: \"jsx\",\n    json: \"json\",\n    md: \"markdown\",\n    mdx: \"mdx\",\n    css: \"css\",\n    scss: \"scss\",\n    html: \"html\",\n    vue: \"vue\",\n    svelte: \"svelte\",\n    astro: \"astro\",\n    yaml: \"yaml\",\n    yml: \"yaml\",\n    toml: \"toml\",\n    sql: \"sql\",\n    prisma: \"prisma\",\n    graphql: \"graphql\",\n    sh: \"bash\",\n    bash: \"bash\",\n    zsh: \"bash\",\n    fish: \"bash\",\n    dockerfile: \"dockerfile\",\n    env: \"shellscript\",\n    hbs: \"handlebars\",\n  };\n  return languageMap[extension.toLowerCase()] || \"text\";\n}\n\nexport const CodeViewer = memo(function CodeViewer({\n  filePath,\n  content,\n  extension,\n}: CodeViewerProps) {\n  const language = useMemo(() => getLanguage(extension), [extension]);\n  const filename = useMemo(() => filePath.split(\"/\").pop() || filePath, [filePath]);\n\n  const codeData = useMemo(\n    () => [\n      {\n        language,\n        filename,\n        code: content,\n      },\n    ],\n    [language, filename, content],\n  );\n\n  return (\n    <div className=\"flex h-full flex-col overflow-hidden bg-fd-background\">\n      <CodeBlock\n        key={filePath}\n        data={codeData}\n        defaultValue={language}\n        className=\"flex flex-col h-full bg-fd-background\"\n      >\n        <CodeBlockHeader>\n          <CodeBlockFiles>\n            {(item) => (\n              <CodeBlockFilename key={item.language} value={item.language}>\n                {filePath}\n              </CodeBlockFilename>\n            )}\n          </CodeBlockFiles>\n          <CodeBlockCopyButton />\n        </CodeBlockHeader>\n        <CodeBlockBody className=\"flex-1 overflow-auto [&_.shiki]:bg-fd-background! dark:[&_.shiki]:bg-fd-background! bg-fd-background\">\n          {(item) => (\n            <CodeBlockItem key={item.language} value={item.language}>\n              <CodeBlockContent\n                language={item.language as BundledLanguage}\n                themes={{\n                  light: \"catppuccin-latte\",\n                  dark: \"catppuccin-mocha\",\n                }}\n                className=\"bg-fd-background\"\n              >\n                {item.code}\n              </CodeBlockContent>\n            </CodeBlockItem>\n          )}\n        </CodeBlockBody>\n      </CodeBlock>\n    </div>\n  );\n});\n\ninterface EmptyStateProps {\n  message?: string;\n}\n\nexport function CodeViewerEmpty({\n  message = \"Select a file to view its content\",\n}: EmptyStateProps) {\n  return (\n    <div className=\"flex h-full items-center justify-center text-muted-foreground\">\n      <p className=\"text-sm\">{message}</p>\n    </div>\n  );\n}\n"
  },
  {
    "path": "apps/web/src/app/(home)/new/_components/file-explorer.tsx",
    "content": "\"use client\";\n\nimport { useMemo } from \"react\";\n\nimport { Tree, Folder, File } from \"@/components/ui/file-tree\";\n\nexport interface VirtualFile {\n  type: \"file\";\n  path: string;\n  name: string;\n  content: string;\n  extension: string;\n}\n\nexport interface VirtualDirectory {\n  type: \"directory\";\n  path: string;\n  name: string;\n  children: VirtualNode[];\n}\n\nexport type VirtualNode = VirtualFile | VirtualDirectory;\n\ninterface FileExplorerProps {\n  root: VirtualDirectory;\n  selectedPath: string | null;\n  onSelectFile: (file: VirtualFile) => void;\n}\n\nfunction collectInitialExpandedItems(node: VirtualDirectory, depth: number = 0): string[] {\n  const result: string[] = [];\n  if (depth < 2) {\n    result.push(node.path);\n  }\n  for (const child of node.children) {\n    if (child.type === \"directory\") {\n      result.push(...collectInitialExpandedItems(child, depth + 1));\n    }\n  }\n  return result;\n}\n\nexport function FileExplorer({ root, selectedPath, onSelectFile }: FileExplorerProps) {\n  const initialExpandedItems = useMemo(() => collectInitialExpandedItems(root), [root]);\n\n  return (\n    <div className=\"h-full overflow-auto text-sm\">\n      <Tree\n        initialExpandedItems={initialExpandedItems}\n        initialSelectedId={selectedPath ?? undefined}\n        indicator={false}\n        className=\"p-2\"\n      >\n        <DirectoryContents node={root} selectedPath={selectedPath} onSelectFile={onSelectFile} />\n      </Tree>\n    </div>\n  );\n}\n\ninterface DirectoryContentsProps {\n  node: VirtualDirectory;\n  selectedPath: string | null;\n  onSelectFile: (file: VirtualFile) => void;\n}\n\nfunction DirectoryContents({ node, selectedPath, onSelectFile }: DirectoryContentsProps) {\n  return (\n    <Folder element={node.name} value={node.path}>\n      {node.children.map((child) =>\n        child.type === \"directory\" ? (\n          <DirectoryContents\n            key={child.path}\n            node={child}\n            selectedPath={selectedPath}\n            onSelectFile={onSelectFile}\n          />\n        ) : (\n          <File\n            key={child.path}\n            value={child.path}\n            isSelect={selectedPath === child.path}\n            onClick={() => onSelectFile(child)}\n          >\n            <span className=\"truncate\">{child.name}</span>\n          </File>\n        ),\n      )}\n    </Folder>\n  );\n}\n"
  },
  {
    "path": "apps/web/src/app/(home)/new/_components/get-badge-color.ts",
    "content": "export const getBadgeColors = (category: string): string => {\n  switch (category) {\n    case \"webFrontend\":\n    case \"nativeFrontend\":\n      return \"border-primary/30 bg-primary/10 text-primary\";\n    case \"runtime\":\n      return \"border-accent/30 bg-accent/10 text-accent\";\n    case \"backend\":\n      return \"border-primary/40 bg-primary/15 text-primary\";\n    case \"api\":\n      return \"border-primary/50 bg-primary/20 text-primary\";\n    case \"database\":\n      return \"border-accent/40 bg-accent/15 text-accent\";\n    case \"orm\":\n      return \"border-primary/35 bg-primary/12 text-primary\";\n    case \"auth\":\n      return \"border-accent/35 bg-accent/12 text-accent\";\n    case \"dbSetup\":\n      return \"border-primary/30 bg-primary/10 text-primary\";\n    case \"addons\":\n      return \"border-accent/50 bg-accent/20 text-accent\";\n    case \"examples\":\n      return \"border-primary/40 bg-primary/15 text-primary\";\n    case \"packageManager\":\n      return \"border-muted-foreground/30 bg-muted text-muted-foreground\";\n    case \"git\":\n    case \"webDeploy\":\n    case \"serverDeploy\":\n    case \"install\":\n      return \"border-muted-foreground/30 bg-muted text-muted-foreground\";\n    default:\n      return \"border-muted-foreground/30 bg-muted text-muted-foreground\";\n  }\n};\n"
  },
  {
    "path": "apps/web/src/app/(home)/new/_components/preset-dropdown.tsx",
    "content": "\"use client\";\n\nimport { ChevronDown, Zap } from \"lucide-react\";\n\nimport {\n  DropdownMenu,\n  DropdownMenuContent,\n  DropdownMenuItem,\n  DropdownMenuTrigger,\n} from \"@/components/ui/dropdown-menu\";\nimport { PRESET_TEMPLATES } from \"@/lib/constant\";\nimport { generateStackSummary } from \"@/lib/stack-utils\";\n\ntype PresetDropdownProps = {\n  onApplyPreset: (presetId: string) => void;\n};\n\nexport function PresetDropdown({ onApplyPreset }: PresetDropdownProps) {\n  return (\n    <DropdownMenu>\n      <DropdownMenuTrigger\n        render={\n          <button\n            type=\"button\"\n            className=\"builder-focus-ring flex flex-1 items-center justify-center gap-1.5 rounded-md bg-muted/20 px-2 py-1.5 font-mono font-medium text-muted-foreground text-xs transition-colors hover:bg-muted/35 hover:text-foreground\"\n          />\n        }\n      >\n        <Zap className=\"h-3 w-3\" />\n        Presets\n        <ChevronDown className=\"ml-auto h-3 w-3\" />\n      </DropdownMenuTrigger>\n      <DropdownMenuContent align=\"end\" className=\"w-72 bg-fd-background\">\n        {PRESET_TEMPLATES.map((preset) => (\n          <DropdownMenuItem\n            key={preset.id}\n            onClick={() => onApplyPreset(preset.id)}\n            className=\"flex flex-col items-start gap-1 p-3\"\n          >\n            <div className=\"flex w-full items-center justify-between gap-2\">\n              <div className=\"font-medium text-sm\">{preset.name}</div>\n              <span className=\"rounded border border-border bg-muted/30 px-1.5 py-0.5 font-mono text-[10px] text-muted-foreground uppercase\">\n                Preset\n              </span>\n            </div>\n            <div className=\"line-clamp-2 text-xs text-muted-foreground\">{preset.description}</div>\n            <div className=\"line-clamp-1 w-full font-mono text-[10px] text-primary uppercase tracking-wide\">\n              {generateStackSummary(preset.stack)}\n            </div>\n          </DropdownMenuItem>\n        ))}\n      </DropdownMenuContent>\n    </DropdownMenu>\n  );\n}\n"
  },
  {
    "path": "apps/web/src/app/(home)/new/_components/preview-panel.tsx",
    "content": "\"use client\";\n\nimport { Loader2, FolderTree, FileCode2, Info, ChevronLeft } from \"lucide-react\";\nimport { useEffect, useState, useCallback, useRef } from \"react\";\n\nimport { Tooltip, TooltipTrigger, TooltipContent } from \"@/components/ui/tooltip\";\nimport type { StackState } from \"@/lib/constant\";\nimport { cn } from \"@/lib/utils\";\n\nimport { CodeViewer, CodeViewerEmpty } from \"./code-viewer\";\nimport { FileExplorer, type VirtualFile, type VirtualDirectory } from \"./file-explorer\";\n\ninterface PreviewPanelProps {\n  stack: StackState;\n  selectedFilePath: string | null;\n  onSelectFile: (filePath: string | null) => void;\n}\n\ninterface PreviewResponse {\n  success: boolean;\n  tree?: {\n    root: VirtualDirectory;\n    fileCount: number;\n    directoryCount: number;\n  };\n  error?: string;\n}\n\nexport function PreviewPanel({ stack, selectedFilePath, onSelectFile }: PreviewPanelProps) {\n  const [tree, setTree] = useState<VirtualDirectory | null>(null);\n  const [fileCount, setFileCount] = useState(0);\n  const [directoryCount, setDirectoryCount] = useState(0);\n  const [selectedFile, setSelectedFile] = useState<VirtualFile | null>(null);\n  const [isLoading, setIsLoading] = useState(false);\n  const [error, setError] = useState<string | null>(null);\n  // On mobile, track whether we're viewing the file tree or the code\n  const [mobileView, setMobileView] = useState<\"tree\" | \"code\">(\"tree\");\n  const requestIdRef = useRef(0);\n  const abortRef = useRef<AbortController | null>(null);\n  const selectedFilePathRef = useRef<string | null>(selectedFilePath);\n  const onSelectFileRef = useRef(onSelectFile);\n\n  useEffect(() => {\n    selectedFilePathRef.current = selectedFilePath;\n  }, [selectedFilePath]);\n\n  useEffect(() => {\n    onSelectFileRef.current = onSelectFile;\n  }, [onSelectFile]);\n\n  const fetchPreview = useCallback(async () => {\n    const requestId = requestIdRef.current + 1;\n    requestIdRef.current = requestId;\n    abortRef.current?.abort();\n    const controller = new AbortController();\n    abortRef.current = controller;\n\n    setIsLoading(true);\n    setError(null);\n\n    try {\n      const response = await fetch(\"/api/preview\", {\n        method: \"POST\",\n        headers: { \"Content-Type\": \"application/json\" },\n        body: JSON.stringify(stack),\n        signal: controller.signal,\n      });\n\n      if (requestId !== requestIdRef.current) return;\n\n      const data: PreviewResponse = await response.json();\n\n      if (requestId !== requestIdRef.current) return;\n\n      if (data.success && data.tree) {\n        setTree(data.tree.root);\n        setFileCount(data.tree.fileCount);\n        setDirectoryCount(data.tree.directoryCount);\n\n        // Restore selected file from query state if it exists\n        const currentSelectedFilePath = selectedFilePathRef.current;\n        if (currentSelectedFilePath) {\n          const file = findFileByPath(data.tree.root, currentSelectedFilePath);\n          if (file) {\n            setSelectedFile(file);\n            setMobileView(\"code\");\n          } else {\n            setSelectedFile(null);\n            onSelectFileRef.current(null);\n            setMobileView(\"tree\");\n          }\n        } else {\n          setSelectedFile(null);\n          setMobileView(\"tree\");\n        }\n      } else {\n        setError(data.error || \"Failed to generate preview\");\n      }\n    } catch (err) {\n      if (controller.signal.aborted) return;\n      if (requestId !== requestIdRef.current) return;\n      setError(err instanceof Error ? err.message : \"Failed to fetch preview\");\n    } finally {\n      if (requestId === requestIdRef.current) {\n        setIsLoading(false);\n      }\n    }\n  }, [stack]);\n\n  // Debounced fetch on stack change\n  useEffect(() => {\n    const timeoutId = setTimeout(fetchPreview, 300);\n    return () => {\n      clearTimeout(timeoutId);\n      abortRef.current?.abort();\n    };\n  }, [fetchPreview]);\n\n  const handleSelectFile = (file: VirtualFile) => {\n    setSelectedFile(file);\n    onSelectFile(file.path);\n    setMobileView(\"code\");\n  };\n\n  const handleBackToTree = () => {\n    setMobileView(\"tree\");\n  };\n\n  // Helper function to find a file by path in the tree\n  function findFileByPath(node: VirtualDirectory, path: string): VirtualFile | null {\n    for (const child of node.children) {\n      if (child.type === \"file\" && child.path === path) {\n        return child;\n      }\n      if (child.type === \"directory\") {\n        const found = findFileByPath(child, path);\n        if (found) return found;\n      }\n    }\n    return null;\n  }\n\n  if (isLoading && !tree) {\n    return (\n      <div className=\"flex h-full items-center justify-center rounded-lg border border-border bg-fd-background\">\n        <div className=\"flex items-center gap-2 rounded border border-border bg-muted/20 px-3 py-2 font-mono text-muted-foreground text-xs\">\n          <Loader2 className=\"h-4 w-4 animate-spin\" />\n          Rendering file tree\n        </div>\n      </div>\n    );\n  }\n\n  if (error && !tree) {\n    return (\n      <div className=\"flex h-full items-center justify-center rounded-lg border border-border bg-fd-background\">\n        <p className=\"rounded border border-destructive/30 bg-destructive/10 px-3 py-2 font-mono text-destructive text-sm\">\n          {error}\n        </p>\n      </div>\n    );\n  }\n\n  if (!tree) {\n    return (\n      <div className=\"flex h-full items-center justify-center rounded-lg border border-border bg-fd-background text-muted-foreground\">\n        <p className=\"font-mono text-sm\">Generating preview...</p>\n      </div>\n    );\n  }\n\n  return (\n    <div className=\"flex h-full flex-col overflow-hidden rounded-lg border border-border/80 bg-fd-background\">\n      {/* Stats bar */}\n      <div className=\"flex items-center gap-2 border-border border-b bg-muted/20 px-3 py-2 sm:gap-4\">\n        {/* Mobile back button when viewing code */}\n        {mobileView === \"code\" && selectedFile && (\n          <button\n            type=\"button\"\n            onClick={handleBackToTree}\n            className=\"builder-focus-ring flex items-center gap-1 rounded px-1 py-0.5 font-mono text-xs text-muted-foreground hover:text-foreground sm:hidden\"\n          >\n            <ChevronLeft className=\"h-4 w-4\" />\n            <span>Files</span>\n          </button>\n        )}\n        <div\n          className={cn(\n            \"flex items-center gap-1.5 text-xs text-muted-foreground\",\n            mobileView === \"code\" && \"hidden sm:flex\",\n          )}\n        >\n          <FolderTree className=\"h-3.5 w-3.5\" />\n          <span>{directoryCount} folders</span>\n        </div>\n        <div\n          className={cn(\n            \"flex items-center gap-1.5 text-xs text-muted-foreground\",\n            mobileView === \"code\" && \"hidden sm:flex\",\n          )}\n        >\n          <FileCode2 className=\"h-3.5 w-3.5\" />\n          <span>{fileCount} files</span>\n        </div>\n        <span\n          className={cn(\n            \"hidden rounded border border-border/70 bg-fd-background px-1.5 py-0.5 font-mono text-[10px] text-muted-foreground uppercase tracking-wide sm:inline-flex\",\n          )}\n        >\n          {mobileView === \"code\" ? \"Code view\" : \"Tree view\"}\n        </span>\n        {/* Show current file name on mobile */}\n        {mobileView === \"code\" && selectedFile && (\n          <span className=\"truncate font-mono text-xs text-foreground sm:hidden\">\n            {selectedFile.path.split(\"/\").pop()}\n          </span>\n        )}\n        <div className=\"ml-auto flex items-center gap-2\">\n          <Tooltip>\n            <TooltipTrigger className=\"flex items-center gap-1 text-xs text-muted-foreground hover:text-foreground\">\n              <Info className=\"h-3.5 w-3.5\" />\n              <span className=\"hidden sm:inline\">Preview info</span>\n            </TooltipTrigger>\n            <TooltipContent side=\"bottom\" className=\"max-w-xs\">\n              <p>\n                This is a static template preview. Files are not formatted. Some features like\n                database provider setup (Turso, Neon, Supabase, etc.) and certain addons (Fumadocs,\n                Starlight, Tauri, etc.) require CLI execution and are not shown here.\n              </p>\n            </TooltipContent>\n          </Tooltip>\n          {isLoading && <Loader2 className=\"h-3.5 w-3.5 animate-spin text-muted-foreground\" />}\n        </div>\n      </div>\n\n      {/* Split view - side by side on desktop, toggle on mobile */}\n      <div className=\"flex flex-1 overflow-hidden\">\n        {/* File explorer - full width on mobile when tree view, hidden when code view */}\n        <div\n          className={cn(\n            \"shrink-0 overflow-hidden border-border sm:border-r\",\n            \"w-full sm:w-48 md:w-56 lg:w-64\",\n            mobileView === \"code\" ? \"hidden sm:block\" : \"block\",\n          )}\n        >\n          <FileExplorer\n            root={tree}\n            selectedPath={selectedFile?.path || selectedFilePath || null}\n            onSelectFile={handleSelectFile}\n          />\n        </div>\n\n        {/* Code viewer - full width on mobile when code view, hidden when tree view */}\n        <div\n          className={cn(\n            \"flex-1 overflow-hidden bg-fd-background/80\",\n            mobileView === \"tree\" ? \"hidden sm:block\" : \"block\",\n          )}\n        >\n          {selectedFile ? (\n            <CodeViewer\n              filePath={selectedFile.path}\n              content={selectedFile.content}\n              extension={selectedFile.extension}\n            />\n          ) : (\n            <CodeViewerEmpty />\n          )}\n        </div>\n      </div>\n    </div>\n  );\n}\n"
  },
  {
    "path": "apps/web/src/app/(home)/new/_components/share-button.tsx",
    "content": "\"use client\";\n\nimport { Share2 } from \"lucide-react\";\n\nimport { ShareDialog } from \"@/components/ui/share-dialog\";\nimport type { StackState } from \"@/lib/constant\";\n\ninterface ShareButtonProps {\n  stackUrl: string;\n  stackState: StackState;\n}\n\nexport function ShareButton({ stackUrl, stackState }: ShareButtonProps) {\n  return (\n    <ShareDialog stackUrl={stackUrl} stackState={stackState}>\n      <button\n        type=\"button\"\n        className=\"builder-focus-ring flex flex-1 items-center justify-center gap-1.5 rounded-md bg-muted/20 px-2 py-1.5 font-mono font-medium text-muted-foreground text-xs transition-colors hover:bg-muted/35 hover:text-foreground\"\n        title=\"Share your stack\"\n      >\n        <Share2 className=\"h-3 w-3\" />\n        Share\n      </button>\n    </ShareDialog>\n  );\n}\n"
  },
  {
    "path": "apps/web/src/app/(home)/new/_components/special-sponsors-panel.tsx",
    "content": "\"use client\";\n\nimport { Globe, Plus, Star } from \"lucide-react\";\nimport Image from \"next/image\";\nimport { FaGithub } from \"react-icons/fa6\";\n\nimport { HoverCard, HoverCardContent, HoverCardTrigger } from \"@/components/ui/hover-card\";\nimport { formatSponsorUrl, getSponsorUrl, shouldShowLifetimeTotal } from \"@/lib/sponsor-utils\";\nimport type { Sponsor } from \"@/lib/types\";\nimport { cn } from \"@/lib/utils\";\n\ntype SpecialSponsorsPanelProps = {\n  sponsors: Sponsor[];\n  compact?: boolean;\n};\n\nconst SPONSOR_ME_URL = \"https://github.com/sponsors/AmanVarshney01\";\n\nexport function SpecialSponsorsPanel({ sponsors, compact = false }: SpecialSponsorsPanelProps) {\n  if (!sponsors.length) {\n    return null;\n  }\n\n  return (\n    <div className=\"space-y-2\">\n      <div className=\"flex items-center justify-between gap-2\">\n        <p className=\"flex items-center gap-1.5 font-mono text-[11px] text-muted-foreground uppercase tracking-wide\">\n          <Star className=\"h-3.5 w-3.5 text-yellow-500/90\" />\n          Special sponsors\n        </p>\n        <span className=\"rounded-md bg-muted/20 px-1.5 py-0.5 font-mono text-[10px] text-muted-foreground uppercase\">\n          {sponsors.length}\n        </span>\n      </div>\n\n      <div className=\"no-scrollbar flex items-center gap-2 overflow-x-auto pb-1\">\n        {sponsors.map((entry) => {\n          const sponsorUrl = getSponsorUrl(entry);\n\n          return (\n            <HoverCard key={entry.githubId}>\n              <HoverCardTrigger\n                render={\n                  <a\n                    href={sponsorUrl}\n                    target=\"_blank\"\n                    rel=\"noopener noreferrer\"\n                    aria-label={entry.name}\n                    className=\"inline-flex rounded-md\"\n                  />\n                }\n              >\n                <Image\n                  src={entry.avatarUrl}\n                  alt={entry.name}\n                  width={64}\n                  height={64}\n                  className={cn(\n                    \"rounded-md border border-border/70 transition-colors hover:border-primary/40\",\n                    compact ? \"h-9 w-9\" : \"h-10 w-10\",\n                  )}\n                  unoptimized\n                />\n              </HoverCardTrigger>\n\n              <HoverCardContent align=\"start\" sideOffset={8} className=\"bg-fd-background\">\n                <div className=\"space-y-2.5\">\n                  <div className=\"flex items-center gap-2\">\n                    <Image\n                      src={entry.avatarUrl}\n                      alt={entry.name}\n                      width={48}\n                      height={48}\n                      className=\"h-11 w-11 rounded border border-border\"\n                      unoptimized\n                    />\n                    <div className=\"min-w-0\">\n                      <h3 className=\"truncate font-semibold text-sm\">{entry.name}</h3>\n                      {shouldShowLifetimeTotal(entry) ? (\n                        <>\n                          {entry.tierName && (\n                            <p className=\"text-primary text-xs\">{entry.tierName}</p>\n                          )}\n                          <p className=\"text-muted-foreground text-xs\">\n                            Total: {entry.formattedAmount}\n                          </p>\n                        </>\n                      ) : (\n                        <p className=\"text-primary text-xs\">{entry.tierName}</p>\n                      )}\n                    </div>\n                  </div>\n\n                  <div className=\"space-y-1\">\n                    <a\n                      href={entry.githubUrl}\n                      target=\"_blank\"\n                      rel=\"noopener noreferrer\"\n                      className=\"group flex items-center gap-2 text-muted-foreground text-xs transition-colors hover:text-primary\"\n                    >\n                      <FaGithub className=\"h-3.5 w-3.5\" />\n                      <span className=\"truncate\">{entry.githubId}</span>\n                    </a>\n                    {entry.websiteUrl ? (\n                      <a\n                        href={sponsorUrl}\n                        target=\"_blank\"\n                        rel=\"noopener noreferrer\"\n                        className=\"group flex items-center gap-2 text-muted-foreground text-xs transition-colors hover:text-primary\"\n                      >\n                        <Globe className=\"h-3.5 w-3.5\" />\n                        <span className=\"truncate\">{formatSponsorUrl(sponsorUrl)}</span>\n                      </a>\n                    ) : null}\n                  </div>\n                </div>\n              </HoverCardContent>\n            </HoverCard>\n          );\n        })}\n\n        <HoverCard>\n          <HoverCardTrigger\n            render={\n              <a\n                href={SPONSOR_ME_URL}\n                target=\"_blank\"\n                rel=\"noopener noreferrer\"\n                aria-label=\"Become a sponsor\"\n                className={cn(\n                  \"builder-focus-ring inline-flex items-center justify-center rounded-md border border-dashed border-primary/40 bg-primary/10 text-primary transition-colors hover:border-primary/60 hover:bg-primary/16\",\n                  compact ? \"h-9 w-9\" : \"h-10 w-10\",\n                )}\n              />\n            }\n          >\n            <Plus className=\"h-4 w-4\" />\n          </HoverCardTrigger>\n          <HoverCardContent align=\"start\" sideOffset={8} className=\"bg-fd-background\">\n            <div className=\"space-y-1.5\">\n              <p className=\"font-semibold text-sm\">Become a sponsor</p>\n              <p className=\"text-muted-foreground text-xs\">\n                Support the project and get featured in this special sponsors list.\n              </p>\n            </div>\n          </HoverCardContent>\n        </HoverCard>\n      </div>\n    </div>\n  );\n}\n"
  },
  {
    "path": "apps/web/src/app/(home)/new/_components/stack-builder/index.tsx",
    "content": "\"use client\";\n\nimport {\n  AlertTriangle,\n  Check,\n  ChevronDown,\n  ClipboardCopy,\n  FolderTree,\n  Settings,\n  Terminal,\n} from \"lucide-react\";\nimport { startTransition } from \"react\";\n\nimport {\n  DropdownMenu,\n  DropdownMenuContent,\n  DropdownMenuTrigger,\n} from \"@/components/ui/dropdown-menu\";\nimport { Input } from \"@/components/ui/input\";\nimport { ScrollArea } from \"@/components/ui/scroll-area\";\nimport { TooltipProvider } from \"@/components/ui/tooltip\";\nimport { getDesktopBuildNote } from \"@/lib/stack-utils\";\nimport type { Sponsor } from \"@/lib/types\";\nimport { cn } from \"@/lib/utils\";\n\nimport { ActionButtons } from \"../action-buttons\";\nimport { PresetDropdown } from \"../preset-dropdown\";\nimport { PreviewPanel } from \"../preview-panel\";\nimport { ShareButton } from \"../share-button\";\nimport { SpecialSponsorsPanel } from \"../special-sponsors-panel\";\nimport { YoloToggle } from \"../yolo-toggle\";\nimport { SelectedStackBadges } from \"./selected-stack-badges\";\nimport { TechCategories } from \"./tech-categories\";\nimport { useStackBuilder } from \"./use-stack-builder\";\n\ntype StackBuilderProps = {\n  specialSponsors?: Sponsor[];\n};\n\nexport function StackBuilder({ specialSponsors = [] }: StackBuilderProps) {\n  const {\n    applyPreset,\n    command,\n    compatibilityAnalysis,\n    copied,\n    copyToClipboard,\n    getRandomStack,\n    getStackUrl,\n    handleTechSelect,\n    lastSavedStack,\n    loadSavedStack,\n    mobileTab,\n    projectNameError,\n    removeSelectedTech,\n    resetStack,\n    saveCurrentStack,\n    scrollAreaRef,\n    selectedCount,\n    selectedFile,\n    setMobileTab,\n    setSelectedFile,\n    setStack,\n    setViewMode,\n    stack,\n    viewMode,\n  } = useStackBuilder();\n  const effectiveStack = compatibilityAnalysis.adjustedStack || stack;\n  const desktopBuildNote = getDesktopBuildNote(effectiveStack);\n\n  return (\n    <TooltipProvider>\n      <div className=\"flex h-full w-full flex-col overflow-hidden bg-fd-background text-foreground\">\n        <div className=\"sticky top-0 z-20 border-border border-b bg-fd-background/95 px-3 py-2 backdrop-blur-sm sm:hidden\">\n          <div className=\"flex items-center gap-2\">\n            <div className=\"flex items-center gap-1 rounded-md bg-muted/20 p-1\">\n              <button\n                type=\"button\"\n                onClick={() => setMobileTab(\"build\")}\n                className={cn(\n                  \"builder-focus-ring rounded px-2 py-1 font-mono text-[11px] uppercase\",\n                  mobileTab === \"build\"\n                    ? \"bg-primary/12 text-primary\"\n                    : \"text-muted-foreground hover:bg-muted/30\",\n                )}\n              >\n                Build\n              </button>\n              <button\n                type=\"button\"\n                onClick={() => setMobileTab(\"preview\")}\n                className={cn(\n                  \"builder-focus-ring rounded px-2 py-1 font-mono text-[11px] uppercase\",\n                  mobileTab === \"preview\"\n                    ? \"bg-primary/12 text-primary\"\n                    : \"text-muted-foreground hover:bg-muted/30\",\n                )}\n              >\n                Preview\n              </button>\n            </div>\n          </div>\n        </div>\n\n        <div className=\"hidden h-full flex-1 grid-cols-[24rem_minmax(0,1fr)] overflow-hidden border-border sm:grid\">\n          <aside className=\"flex min-h-0 flex-col overflow-hidden border-border/50 border-r bg-fd-background\">\n            <ScrollArea className=\"min-h-0 flex-1\">\n              <div className=\"p-2\">\n                <div className=\"overflow-hidden rounded-2xl bg-fd-background/80\">\n                  <section className=\"space-y-2 border-b border-border/20 px-3 py-3\">\n                    <label className=\"flex flex-col\">\n                      <span className=\"mb-1 font-mono text-[11px] text-muted-foreground uppercase tracking-wide\">\n                        Project Name\n                      </span>\n                      <Input\n                        type=\"text\"\n                        value={stack.projectName || \"\"}\n                        onChange={(event) => {\n                          setStack({ projectName: event.target.value });\n                        }}\n                        aria-invalid={!!projectNameError}\n                        className={cn(\n                          \"builder-focus-ring w-full rounded-lg px-2.5 py-1.5 font-mono text-sm focus:outline-none\",\n                          projectNameError\n                            ? \"border-destructive bg-destructive/10 text-destructive-foreground\"\n                            : \"border-border/60 focus:border-primary\",\n                        )}\n                        placeholder=\"my-better-t-app\"\n                      />\n                      {projectNameError && (\n                        <p className=\"mt-1 text-destructive text-xs\">{projectNameError}</p>\n                      )}\n                    </label>\n                  </section>\n\n                  <section className=\"space-y-2 border-border/20 border-b px-3 py-3\">\n                    <div className=\"flex items-center justify-between\">\n                      <p className=\"font-mono text-[11px] text-muted-foreground uppercase tracking-wide\">\n                        CLI Command\n                      </p>\n                      <button\n                        type=\"button\"\n                        onClick={copyToClipboard}\n                        className={cn(\n                          \"builder-focus-ring flex items-center gap-1 rounded-md px-2 py-1 font-mono text-[11px] uppercase transition-colors\",\n                          copied\n                            ? \"bg-primary/14 text-primary\"\n                            : \"bg-muted/20 text-muted-foreground hover:bg-muted/35 hover:text-foreground\",\n                        )}\n                        title={copied ? \"Copied!\" : \"Copy command\"}\n                      >\n                        {copied ? (\n                          <Check className=\"h-3 w-3 shrink-0\" />\n                        ) : (\n                          <ClipboardCopy className=\"h-3 w-3 shrink-0\" />\n                        )}\n                        {copied ? \"Copied\" : \"Copy\"}\n                      </button>\n                    </div>\n                    <div\n                      role=\"button\"\n                      tabIndex={0}\n                      onClick={copyToClipboard}\n                      onKeyDown={(event) => {\n                        if (event.key === \"Enter\" || event.key === \" \") {\n                          event.preventDefault();\n                          copyToClipboard();\n                        }\n                      }}\n                      aria-label=\"Copy CLI command\"\n                      title=\"Click to copy command\"\n                      className=\"builder-focus-ring cursor-pointer rounded-lg bg-muted/20 px-2.5 py-2\"\n                    >\n                      <div className=\"flex items-start gap-2\">\n                        <code className=\"block break-all font-mono text-muted-foreground text-xs\">\n                          {command}\n                        </code>\n                      </div>\n                    </div>\n                  </section>\n\n                  <section className=\"space-y-2 px-3 py-3\">\n                    <div className=\"flex items-center justify-between\">\n                      <p className=\"font-mono text-[11px] text-muted-foreground uppercase tracking-wide\">\n                        Selected stack\n                      </p>\n                      <span className=\"font-mono text-[11px] text-muted-foreground uppercase\">\n                        {selectedCount} picks\n                      </span>\n                    </div>\n                    <SelectedStackBadges stack={stack} onRemove={removeSelectedTech} />\n                  </section>\n\n                  {compatibilityAnalysis.changes.length > 0 && (\n                    <section className=\"space-y-2 border-border/20 border-t px-3 py-3\">\n                      <p className=\"font-mono text-[11px] text-primary uppercase tracking-wide\">\n                        Compatibility Log\n                      </p>\n                      <ul className=\"space-y-1 rounded-lg bg-primary/7 px-2.5 py-2\">\n                        {compatibilityAnalysis.changes.slice(0, 4).map((change, index) => (\n                          <li\n                            key={`${change.category}-${change.message}-${index}`}\n                            className=\"text-muted-foreground text-xs\"\n                          >\n                            • {change.message}\n                          </li>\n                        ))}\n                      </ul>\n                    </section>\n                  )}\n\n                  {desktopBuildNote && (\n                    <section className=\"space-y-2 border-border/20 border-t px-3 py-3\">\n                      <div className=\"flex items-center gap-1.5 font-mono text-[11px] text-amber-600 uppercase tracking-wide dark:text-amber-400\">\n                        <AlertTriangle className=\"h-3.5 w-3.5\" />\n                        Desktop Build Note\n                      </div>\n                      <div className=\"rounded-lg border border-amber-500/20 bg-amber-500/10 px-2.5 py-2 text-muted-foreground text-xs\">\n                        {desktopBuildNote}\n                      </div>\n                    </section>\n                  )}\n                </div>\n              </div>\n            </ScrollArea>\n\n            <div className=\"border-border/35 border-t bg-fd-background/95 p-2\">\n              <div className=\"rounded-2xl bg-fd-background/80 p-2\">\n                <SpecialSponsorsPanel sponsors={specialSponsors} />\n                {specialSponsors.length > 0 ? <div className=\"my-2 h-px bg-border/25\" /> : null}\n                <ActionButtons\n                  onReset={resetStack}\n                  onRandom={getRandomStack}\n                  onSave={saveCurrentStack}\n                  onLoad={loadSavedStack}\n                  hasSavedStack={!!lastSavedStack}\n                />\n\n                <div className=\"mt-2 grid grid-cols-3 gap-1.5\">\n                  <ShareButton stackUrl={getStackUrl()} stackState={effectiveStack} />\n                  <PresetDropdown onApplyPreset={applyPreset} />\n                  <DropdownMenu>\n                    <DropdownMenuTrigger\n                      render={\n                        <button\n                          type=\"button\"\n                          className=\"builder-focus-ring flex items-center justify-center gap-1.5 rounded-md bg-muted/20 px-2 py-1.5 font-mono font-medium text-muted-foreground text-xs transition-colors hover:bg-muted/35 hover:text-foreground\"\n                        />\n                      }\n                    >\n                      <Settings className=\"h-3 w-3\" />\n                      <span className=\"sr-only\">Settings</span>\n                      <ChevronDown className=\"h-3 w-3\" />\n                    </DropdownMenuTrigger>\n                    <DropdownMenuContent align=\"end\" className=\"w-64 bg-fd-background\">\n                      <YoloToggle stack={stack} onToggle={(yolo) => setStack({ yolo })} />\n                    </DropdownMenuContent>\n                  </DropdownMenu>\n                </div>\n              </div>\n            </div>\n          </aside>\n\n          <section className=\"flex min-h-0 flex-col overflow-hidden\">\n            <div className=\"sticky top-0 z-10 flex items-center gap-2 border-border border-b bg-fd-background px-3 py-2\">\n              <div className=\"flex items-center gap-1 rounded-md bg-muted/20 p-1\">\n                <button\n                  type=\"button\"\n                  onClick={() => {\n                    startTransition(() => {\n                      setViewMode(\"command\");\n                    });\n                  }}\n                  className={cn(\n                    \"builder-focus-ring flex items-center gap-1.5 rounded px-2 py-1 font-mono text-[11px] uppercase tracking-wide\",\n                    viewMode === \"command\"\n                      ? \"bg-primary/12 text-primary\"\n                      : \"text-muted-foreground hover:bg-muted/30\",\n                  )}\n                >\n                  <Terminal className=\"h-3 w-3\" />\n                  Configure\n                </button>\n                <button\n                  type=\"button\"\n                  onClick={() => {\n                    startTransition(() => {\n                      setViewMode(\"preview\");\n                    });\n                  }}\n                  className={cn(\n                    \"builder-focus-ring flex items-center gap-1.5 rounded px-2 py-1 font-mono text-[11px] uppercase tracking-wide\",\n                    viewMode === \"preview\"\n                      ? \"bg-primary/12 text-primary\"\n                      : \"text-muted-foreground hover:bg-muted/30\",\n                  )}\n                >\n                  <FolderTree className=\"h-3 w-3\" />\n                  Preview\n                </button>\n              </div>\n            </div>\n\n            {viewMode === \"command\" ? (\n              <div ref={scrollAreaRef} className=\"h-full\">\n                <ScrollArea className=\"h-full overflow-hidden scroll-smooth\">\n                  <main className=\"p-2 sm:p-4\">\n                    <TechCategories\n                      mode=\"desktop\"\n                      stack={stack}\n                      compatibilityNotes={compatibilityAnalysis.notes}\n                      onSelect={handleTechSelect}\n                      showAllCategories\n                    />\n                  </main>\n                </ScrollArea>\n              </div>\n            ) : (\n              <PreviewPanel\n                stack={effectiveStack}\n                selectedFilePath={selectedFile}\n                onSelectFile={setSelectedFile}\n              />\n            )}\n          </section>\n        </div>\n\n        <div className=\"flex flex-1 flex-col overflow-hidden sm:hidden\">\n          {mobileTab === \"build\" && (\n            <div className=\"flex min-h-0 flex-1 flex-col\">\n              <ScrollArea className=\"h-full overflow-hidden scroll-smooth\">\n                <main className=\"p-2 pb-6\">\n                  <div className=\"mb-4 space-y-2 rounded-xl bg-muted/10 p-2\">\n                    <label className=\"flex flex-col\">\n                      <span className=\"mb-1 font-mono text-[11px] text-muted-foreground uppercase tracking-wide\">\n                        Project Name\n                      </span>\n                      <Input\n                        type=\"text\"\n                        value={stack.projectName || \"\"}\n                        onChange={(event) => {\n                          setStack({ projectName: event.target.value });\n                        }}\n                        aria-invalid={!!projectNameError}\n                        className={cn(\n                          \"builder-focus-ring w-full rounded-lg border bg-background/75 px-2.5 py-1.5 font-mono text-sm focus:outline-none\",\n                          projectNameError\n                            ? \"border-destructive bg-destructive/10 text-destructive-foreground\"\n                            : \"border-border/60 focus:border-primary\",\n                        )}\n                        placeholder=\"my-better-t-app\"\n                      />\n                      {projectNameError && (\n                        <p className=\"mt-1 text-destructive text-xs\">{projectNameError}</p>\n                      )}\n                    </label>\n\n                    <div className=\"space-y-1\">\n                      <div className=\"flex items-center justify-between gap-2\">\n                        <span className=\"font-mono text-[10px] text-muted-foreground uppercase tracking-wide\">\n                          CLI Command\n                        </span>\n                        <span\n                          className={cn(\n                            \"flex items-center gap-1 rounded-md px-2 py-1 font-mono text-[11px] uppercase\",\n                            copied\n                              ? \"bg-primary/14 text-primary\"\n                              : \"bg-muted/20 text-muted-foreground\",\n                          )}\n                        >\n                          {copied ? (\n                            <Check className=\"h-3 w-3 shrink-0\" />\n                          ) : (\n                            <ClipboardCopy className=\"h-3 w-3 shrink-0\" />\n                          )}\n                          {copied ? \"Copied\" : \"Tap to copy\"}\n                        </span>\n                      </div>\n                      <div\n                        role=\"button\"\n                        tabIndex={0}\n                        onClick={copyToClipboard}\n                        onKeyDown={(event) => {\n                          if (event.key === \"Enter\" || event.key === \" \") {\n                            event.preventDefault();\n                            copyToClipboard();\n                          }\n                        }}\n                        className={cn(\n                          \"builder-focus-ring rounded-lg bg-background/75 px-2.5 py-2 font-mono text-xs text-muted-foreground ring-1\",\n                          copied ? \"ring-primary/40\" : \"ring-border/45\",\n                        )}\n                        aria-label=\"Copy command\"\n                        title=\"Click to copy command\"\n                      >\n                        <div className=\"flex items-start gap-1.5\">\n                          <span className=\"mt-0.5 text-chart-4\">$</span>\n                          <code className=\"break-all\">{command}</code>\n                        </div>\n                      </div>\n                    </div>\n\n                    {desktopBuildNote && (\n                      <div className=\"rounded-lg border border-amber-500/20 bg-amber-500/10 px-2.5 py-2\">\n                        <div className=\"mb-1 flex items-center gap-1.5 font-mono text-[10px] text-amber-600 uppercase tracking-wide dark:text-amber-400\">\n                          <AlertTriangle className=\"h-3 w-3\" />\n                          Desktop Build Note\n                        </div>\n                        <p className=\"text-muted-foreground text-xs\">{desktopBuildNote}</p>\n                      </div>\n                    )}\n                  </div>\n\n                  <TechCategories\n                    mode=\"mobile\"\n                    stack={stack}\n                    compatibilityNotes={compatibilityAnalysis.notes}\n                    onSelect={handleTechSelect}\n                    showAllCategories\n                  />\n                </main>\n              </ScrollArea>\n\n              <div className=\"border-border/35 border-t bg-fd-background/95 p-2 backdrop-blur-sm\">\n                <div className=\"rounded-xl bg-fd-background/80 p-2\">\n                  <SpecialSponsorsPanel sponsors={specialSponsors} compact />\n                  {specialSponsors.length > 0 ? <div className=\"my-2 h-px bg-border/25\" /> : null}\n                  <ActionButtons\n                    onReset={resetStack}\n                    onRandom={getRandomStack}\n                    onSave={saveCurrentStack}\n                    onLoad={loadSavedStack}\n                    hasSavedStack={!!lastSavedStack}\n                  />\n\n                  <div className=\"mt-2 grid grid-cols-3 gap-1.5\">\n                    <ShareButton stackUrl={getStackUrl()} stackState={effectiveStack} />\n                    <PresetDropdown onApplyPreset={applyPreset} />\n                    <DropdownMenu>\n                      <DropdownMenuTrigger\n                        render={\n                          <button\n                            type=\"button\"\n                            className=\"builder-focus-ring flex items-center justify-center gap-1.5 rounded-md bg-muted/20 px-2 py-1.5 font-mono font-medium text-muted-foreground text-xs transition-colors hover:bg-muted/35 hover:text-foreground\"\n                          />\n                        }\n                      >\n                        <Settings className=\"h-3 w-3\" />\n                        <span className=\"sr-only\">Settings</span>\n                        <ChevronDown className=\"h-3 w-3\" />\n                      </DropdownMenuTrigger>\n                      <DropdownMenuContent align=\"end\" className=\"w-64 bg-fd-background\">\n                        <YoloToggle stack={stack} onToggle={(yolo) => setStack({ yolo })} />\n                      </DropdownMenuContent>\n                    </DropdownMenu>\n                  </div>\n                </div>\n              </div>\n            </div>\n          )}\n\n          {mobileTab === \"preview\" && (\n            <PreviewPanel\n              stack={effectiveStack}\n              selectedFilePath={selectedFile}\n              onSelectFile={setSelectedFile}\n            />\n          )}\n        </div>\n      </div>\n    </TooltipProvider>\n  );\n}\n"
  },
  {
    "path": "apps/web/src/app/(home)/new/_components/stack-builder/selected-stack-badges.tsx",
    "content": "import { X } from \"lucide-react\";\n\nimport type { StackState } from \"@/lib/constant\";\nimport { TECH_OPTIONS } from \"@/lib/constant\";\nimport { CATEGORY_ORDER } from \"@/lib/stack-utils\";\nimport type { TechCategory } from \"@/lib/types\";\nimport { cn } from \"@/lib/utils\";\n\nimport { getBadgeColors } from \"../get-badge-color\";\nimport { TechIcon } from \"../tech-icon\";\nimport { getCategoryDisplayName } from \"../utils\";\n\ntype SelectedStackBadgesProps = {\n  stack: StackState;\n  onRemove?: (category: TechCategory, techId: string) => void;\n};\n\nexport function SelectedStackBadges({ stack, onRemove }: SelectedStackBadgesProps) {\n  const groupedSelections = CATEGORY_ORDER.map((category) => {\n    const categoryKey = category as keyof StackState;\n    const options = TECH_OPTIONS[category as keyof typeof TECH_OPTIONS];\n    const selectedValue = stack[categoryKey];\n\n    if (!options) {\n      return null;\n    }\n\n    if (Array.isArray(selectedValue)) {\n      const selectedTechs = selectedValue\n        .filter((id) => id !== \"none\")\n        .map((id) => options.find((opt) => opt.id === id))\n        .filter(Boolean);\n\n      if (selectedTechs.length === 0) {\n        return null;\n      }\n\n      return {\n        category: category as TechCategory,\n        label: getCategoryDisplayName(category),\n        techs: selectedTechs,\n      };\n    }\n\n    const tech = options.find((opt) => opt.id === selectedValue);\n    if (\n      !tech ||\n      tech.id === \"none\" ||\n      tech.id === \"false\" ||\n      ((category === \"git\" || category === \"install\" || category === \"auth\") && tech.id === \"true\")\n    ) {\n      return null;\n    }\n\n    return {\n      category: category as TechCategory,\n      label: getCategoryDisplayName(category),\n      techs: [tech],\n    };\n  }).filter(Boolean);\n\n  if (groupedSelections.length === 0) {\n    return <p className=\"font-mono text-muted-foreground text-xs\">No selections yet</p>;\n  }\n\n  return (\n    <div className=\"space-y-2\">\n      {groupedSelections.map((group) => {\n        if (!group) {\n          return null;\n        }\n\n        return (\n          <div key={group.category} className=\"space-y-1\">\n            <p className=\"font-mono text-[11px] text-muted-foreground uppercase tracking-wide\">\n              {group.label}\n            </p>\n            <div className=\"flex flex-wrap gap-1.5\">\n              {group.techs.map((tech) => {\n                if (!tech) {\n                  return null;\n                }\n\n                const canRemove = typeof onRemove === \"function\";\n                const chipClasses = cn(\n                  \"inline-flex items-center gap-1.5 rounded-full px-2 py-0.5 text-xs\",\n                  getBadgeColors(group.category),\n                  canRemove && \"builder-focus-ring cursor-pointer pr-1 hover:brightness-95\",\n                );\n\n                if (!canRemove) {\n                  return (\n                    <span key={`${group.category}-${tech.id}`} className={chipClasses}>\n                      {tech.icon !== \"\" && (\n                        <TechIcon\n                          icon={tech.icon}\n                          name={tech.name}\n                          className={cn(\"h-3 w-3\", tech.className)}\n                        />\n                      )}\n                      {tech.name}\n                    </span>\n                  );\n                }\n\n                return (\n                  <button\n                    key={`${group.category}-${tech.id}`}\n                    type=\"button\"\n                    className={chipClasses}\n                    onClick={() => onRemove(group.category, tech.id)}\n                    aria-label={`Remove ${tech.name} from ${group.label}`}\n                  >\n                    {tech.icon !== \"\" && (\n                      <TechIcon\n                        icon={tech.icon}\n                        name={tech.name}\n                        className={cn(\"h-3 w-3\", tech.className)}\n                      />\n                    )}\n                    {tech.name}\n                    <span className=\"rounded-full p-0.5 hover:bg-black/10 dark:hover:bg-white/10\">\n                      <X className=\"h-2.5 w-2.5\" />\n                    </span>\n                  </button>\n                );\n              })}\n            </div>\n          </div>\n        );\n      })}\n    </div>\n  );\n}\n"
  },
  {
    "path": "apps/web/src/app/(home)/new/_components/stack-builder/tech-categories.tsx",
    "content": "import { CheckCircle2, InfoIcon, Terminal } from \"lucide-react\";\nimport { motion } from \"motion/react\";\n\nimport { Tooltip, TooltipContent, TooltipTrigger } from \"@/components/ui/tooltip\";\nimport type { StackState } from \"@/lib/constant\";\nimport { TECH_OPTIONS } from \"@/lib/constant\";\nimport { CATEGORY_ORDER } from \"@/lib/stack-utils\";\nimport type { TechCategory } from \"@/lib/types\";\nimport { cn } from \"@/lib/utils\";\n\nimport { TechIcon } from \"../tech-icon\";\nimport { getCategoryDisplayName, getDisabledReason, isOptionCompatible } from \"../utils\";\n\ntype TechCategoriesProps = {\n  mode: \"desktop\" | \"mobile\";\n  stack: StackState;\n  compatibilityNotes: Record<string, { hasIssue: boolean; notes: string[] }>;\n  onSelect: (category: keyof typeof TECH_OPTIONS, techId: string) => void;\n  showAllCategories?: boolean;\n};\n\nfunction getIsSelected(stack: StackState, category: keyof StackState, techId: string) {\n  const currentValue = stack[category];\n\n  if (\n    category === \"addons\" ||\n    category === \"examples\" ||\n    category === \"webFrontend\" ||\n    category === \"nativeFrontend\"\n  ) {\n    return ((currentValue as string[]) || []).includes(techId);\n  }\n\n  return currentValue === techId;\n}\n\nexport function TechCategories({\n  mode,\n  stack,\n  compatibilityNotes,\n  onSelect,\n  showAllCategories = false,\n}: TechCategoriesProps) {\n  const isDesktop = mode === \"desktop\";\n  const categories = showAllCategories ? CATEGORY_ORDER : [CATEGORY_ORDER[0]];\n\n  return (\n    <>\n      {categories.map((categoryKey) => {\n        const categoryOptions = TECH_OPTIONS[categoryKey as keyof typeof TECH_OPTIONS] || [];\n        const categoryDisplayName = getCategoryDisplayName(categoryKey);\n\n        if (categoryOptions.length === 0) return null;\n\n        return (\n          <section\n            key={`${mode}-${categoryKey}`}\n            id={isDesktop ? `section-${categoryKey}` : `section-mobile-${categoryKey}`}\n            className={cn(\"mb-6 scroll-mt-4\", isDesktop && \"sm:mb-8\")}\n          >\n            <div className=\"mb-3 flex items-center border-border border-b pb-2 text-muted-foreground\">\n              <Terminal\n                className={cn(\"mr-2 h-4 w-4 shrink-0 text-primary\", isDesktop && \"sm:h-5 sm:w-5\")}\n              />\n              <h2\n                className={cn(\n                  \"font-semibold font-mono text-foreground text-sm\",\n                  isDesktop && \"sm:text-base\",\n                )}\n              >\n                {categoryDisplayName.toUpperCase()}\n              </h2>\n              {compatibilityNotes[categoryKey]?.hasIssue && (\n                <Tooltip delay={100}>\n                  <TooltipTrigger\n                    render={\n                      <InfoIcon className=\"ml-2 h-4 w-4 shrink-0 cursor-help text-muted-foreground\" />\n                    }\n                  />\n                  <TooltipContent side=\"top\" align=\"start\">\n                    <ul className=\"list-disc space-y-1 pl-4 text-xs\">\n                      {compatibilityNotes[categoryKey].notes.map((note) => (\n                        <li key={note}>{note}</li>\n                      ))}\n                    </ul>\n                  </TooltipContent>\n                </Tooltip>\n              )}\n            </div>\n\n            <div\n              className={cn(\n                \"grid gap-2\",\n                isDesktop ? \"grid-cols-1 sm:grid-cols-2 xl:grid-cols-3\" : \"grid-cols-1\",\n                isDesktop && \"auto-rows-fr\",\n              )}\n            >\n              {categoryOptions.map((tech) => {\n                const category = categoryKey as keyof StackState;\n                const isSelected = getIsSelected(stack, category, tech.id);\n                const isDisabled = !isOptionCompatible(stack, categoryKey as TechCategory, tech.id);\n                const disabledReason = getDisabledReason(\n                  stack,\n                  categoryKey as TechCategory,\n                  tech.id,\n                );\n\n                const card = (\n                  <motion.button\n                    type=\"button\"\n                    disabled={isDisabled}\n                    aria-disabled={isDisabled}\n                    aria-pressed={isSelected}\n                    aria-label={`${tech.name}${isDisabled && disabledReason ? `. ${disabledReason}` : \"\"}`}\n                    className={cn(\n                      \"builder-focus-ring relative h-full w-full text-left rounded-lg p-3 transition-colors\",\n                      isDesktop && \"p-2 sm:p-3\",\n                      isSelected\n                        ? \"bg-primary/12 ring-1 ring-primary\"\n                        : isDisabled\n                          ? \"cursor-not-allowed bg-destructive/6 ring-1 ring-destructive/25 opacity-80\"\n                          : \"bg-muted/15 hover:bg-muted/25\",\n                    )}\n                    whileHover={isDesktop && !isDisabled ? { scale: 1.01 } : undefined}\n                    whileTap={!isDisabled ? { scale: 0.98 } : undefined}\n                    onClick={() => {\n                      if (isDisabled) {\n                        return;\n                      }\n                      onSelect(categoryKey as keyof typeof TECH_OPTIONS, tech.id);\n                    }}\n                  >\n                    <div className=\"flex items-start\">\n                      <div className=\"grow\">\n                        <div className=\"flex items-center justify-between gap-2\">\n                          <div className=\"flex items-center\">\n                            {tech.icon !== \"\" && (\n                              <TechIcon\n                                icon={tech.icon}\n                                name={tech.name}\n                                className={cn(\n                                  \"mr-1.5 h-4 w-4\",\n                                  isDesktop && \"h-3 w-3 sm:h-4 sm:w-4\",\n                                  tech.className,\n                                )}\n                              />\n                            )}\n                            <span\n                              className={cn(\n                                \"font-medium text-sm\",\n                                isDesktop && \"text-xs sm:text-sm\",\n                                isSelected ? \"text-primary\" : \"text-foreground\",\n                              )}\n                            >\n                              {tech.name}\n                            </span>\n                          </div>\n                          {isSelected && <CheckCircle2 className=\"h-4 w-4 text-primary\" />}\n                        </div>\n                        <p className=\"mt-0.5 text-muted-foreground text-xs\">{tech.description}</p>\n                        {isDesktop ? (\n                          <div className=\"mt-2 h-7\">\n                            {isDisabled && disabledReason ? (\n                              <p className=\"h-full px-0.5 py-1 text-[11px] text-destructive/90 leading-tight line-clamp-1\">\n                                {disabledReason}\n                              </p>\n                            ) : (\n                              <div aria-hidden className=\"h-full\" />\n                            )}\n                          </div>\n                        ) : (\n                          isDisabled &&\n                          disabledReason && (\n                            <p className=\"mt-2 px-0.5 py-1 text-[11px] text-destructive/90\">\n                              {disabledReason}\n                            </p>\n                          )\n                        )}\n                      </div>\n                    </div>\n                    {tech.default && !isSelected && (\n                      <span className=\"absolute top-1 right-1 ml-2 shrink-0 rounded bg-muted px-1 py-0.5 text-[10px] text-muted-foreground\">\n                        Default\n                      </span>\n                    )}\n                  </motion.button>\n                );\n\n                if (isDesktop && disabledReason) {\n                  return (\n                    <Tooltip key={`${mode}-${categoryKey}-${tech.id}`} delay={100}>\n                      <TooltipTrigger render={card} />\n                      <TooltipContent side=\"top\" align=\"center\" className=\"max-w-xs\">\n                        <p className=\"text-xs\">{disabledReason}</p>\n                      </TooltipContent>\n                    </Tooltip>\n                  );\n                }\n\n                return (\n                  <div key={`${mode}-${categoryKey}-${tech.id}`} className=\"h-full\">\n                    {card}\n                  </div>\n                );\n              })}\n            </div>\n          </section>\n        );\n      })}\n      <div className=\"h-10\" />\n    </>\n  );\n}\n"
  },
  {
    "path": "apps/web/src/app/(home)/new/_components/stack-builder/use-stack-builder.ts",
    "content": "import { startTransition, useEffect, useMemo, useRef, useState } from \"react\";\nimport { toast } from \"sonner\";\n\nimport { DEFAULT_STACK, PRESET_TEMPLATES, type StackState, TECH_OPTIONS } from \"@/lib/constant\";\nimport { sanitizeStackState } from \"@/lib/sanitize-stack-addons\";\nimport { useStackState } from \"@/lib/stack-url-state.client\";\nimport { CATEGORY_ORDER, generateStackCommand, generateStackSharingUrl } from \"@/lib/stack-utils\";\nimport type { TechCategory } from \"@/lib/types\";\n\nimport { analyzeStackCompatibility, isOptionCompatible, validateProjectName } from \"../utils\";\n\nexport type MobileTab = \"build\" | \"preview\";\n\ntype CategoryProgressItem = {\n  category: TechCategory;\n  selected: number;\n  total: number;\n  done: boolean;\n};\n\nconst CATEGORY_LIST = CATEGORY_ORDER as TechCategory[];\n\nfunction formatProjectName(name: string) {\n  return name.replace(/\\s+/g, \"-\");\n}\n\nfunction withFormattedProjectName(stack: StackState) {\n  const projectName = stack.projectName || \"my-better-t-app\";\n  return {\n    ...stack,\n    projectName: formatProjectName(projectName),\n  };\n}\n\nexport function getCompatibilityAdjustmentKey(stack: StackState, adjustedStack: StackState) {\n  return `${JSON.stringify(stack)}=>${JSON.stringify(adjustedStack)}`;\n}\n\nexport function getCompatibilityAdjustmentState(\n  lastAppliedAdjustmentKey: string,\n  stack: StackState,\n  adjustedStack: StackState | null,\n) {\n  if (!adjustedStack) {\n    return {\n      adjustmentKey: \"\",\n      shouldApply: false,\n    };\n  }\n\n  const adjustmentKey = getCompatibilityAdjustmentKey(stack, adjustedStack);\n  return {\n    adjustmentKey,\n    shouldApply: lastAppliedAdjustmentKey !== adjustmentKey,\n  };\n}\n\nexport function useStackBuilder() {\n  const [stack, setStack, viewMode, setViewMode, selectedFile, setSelectedFile] = useStackState();\n\n  const [command, setCommand] = useState(\"\");\n  const [copied, setCopied] = useState(false);\n  const [lastSavedStack, setLastSavedStack] = useState<StackState | null>(null);\n  const [, setLastChanges] = useState<Array<{ category: string; message: string }>>([]);\n  const [mobileTab, setMobileTab] = useState<MobileTab>(\"build\");\n\n  const contentRef = useRef<HTMLDivElement | null>(null);\n  const scrollAreaRef = useRef<HTMLDivElement | null>(null);\n  const lastAppliedAdjustmentKey = useRef<string>(\"\");\n\n  useEffect(() => {\n    if (scrollAreaRef.current) {\n      const viewport = scrollAreaRef.current.querySelector<HTMLDivElement>(\n        '[data-slot=\"scroll-area-viewport\"]',\n      );\n      if (viewport) {\n        contentRef.current = viewport;\n      }\n    }\n  }, [viewMode]);\n\n  const compatibilityAnalysis = analyzeStackCompatibility(stack);\n  const projectNameError = validateProjectName(stack.projectName || \"\");\n\n  useEffect(() => {\n    const savedStack = localStorage.getItem(\"betterTStackPreference\");\n    if (!savedStack) {\n      return;\n    }\n\n    try {\n      const parsedStack = sanitizeStackState(JSON.parse(savedStack) as StackState);\n      setLastSavedStack(parsedStack);\n    } catch (error) {\n      console.error(\"Failed to parse saved stack\", error);\n      localStorage.removeItem(\"betterTStackPreference\");\n    }\n  }, []);\n\n  useEffect(() => {\n    const adjustedStack = compatibilityAnalysis.adjustedStack;\n    const { adjustmentKey, shouldApply } = getCompatibilityAdjustmentState(\n      lastAppliedAdjustmentKey.current,\n      stack,\n      adjustedStack,\n    );\n\n    if (!shouldApply) {\n      lastAppliedAdjustmentKey.current = adjustmentKey;\n      return;\n    }\n\n    startTransition(() => {\n      if (compatibilityAnalysis.changes.length === 1) {\n        toast.info(compatibilityAnalysis.changes[0].message, { duration: 4000 });\n      }\n\n      if (compatibilityAnalysis.changes.length > 1) {\n        const message = `${compatibilityAnalysis.changes.length} compatibility adjustments made:\\n${compatibilityAnalysis.changes\n          .map((change) => `• ${change.message}`)\n          .join(\"\\n\")}`;\n\n        toast.info(message, { duration: 5000 });\n      }\n\n      setLastChanges(compatibilityAnalysis.changes);\n      setStack(adjustedStack!);\n      lastAppliedAdjustmentKey.current = adjustmentKey;\n    });\n  }, [stack, compatibilityAnalysis.adjustedStack, compatibilityAnalysis.changes, setStack]);\n\n  useEffect(() => {\n    const stackToUse = compatibilityAnalysis.adjustedStack || stack;\n    setCommand(generateStackCommand(withFormattedProjectName(stackToUse)));\n  }, [stack, compatibilityAnalysis.adjustedStack]);\n\n  const categoryProgress = useMemo<Array<CategoryProgressItem>>(() => {\n    return CATEGORY_LIST.map((category) => {\n      const options = TECH_OPTIONS[category] || [];\n      const selectedValue = stack[category as keyof StackState];\n      const realOptionCount = options.filter((option) => option.id !== \"none\").length;\n\n      if (Array.isArray(selectedValue)) {\n        const selectedReal = selectedValue.filter(\n          (id) => id !== \"none\" && options.some((option) => option.id === id),\n        );\n        const selectedCount = selectedReal.length;\n        return {\n          category,\n          selected: selectedCount,\n          total: Math.max(realOptionCount, 1),\n          done: selectedCount > 0,\n        };\n      }\n\n      const isSelectedReal =\n        selectedValue !== \"none\" &&\n        selectedValue !== \"false\" &&\n        options.some((option) => option.id === selectedValue);\n\n      return {\n        category,\n        selected: isSelectedReal ? 1 : 0,\n        total: 1,\n        done: isSelectedReal,\n      };\n    });\n  }, [stack]);\n\n  const selectedCount = useMemo(() => {\n    return categoryProgress.reduce((total, entry) => total + entry.selected, 0);\n  }, [categoryProgress]);\n\n  function getStackUrl() {\n    const stackToUse = compatibilityAnalysis.adjustedStack || stack;\n    return generateStackSharingUrl(withFormattedProjectName(stackToUse));\n  }\n\n  function getRandomStack() {\n    const randomStack: Partial<StackState> = {};\n\n    for (const category of CATEGORY_LIST) {\n      const options = TECH_OPTIONS[category as keyof typeof TECH_OPTIONS] || [];\n      if (options.length === 0) {\n        continue;\n      }\n\n      const catKey = category as keyof StackState;\n      if (\n        catKey === \"webFrontend\" ||\n        catKey === \"nativeFrontend\" ||\n        catKey === \"addons\" ||\n        catKey === \"examples\"\n      ) {\n        if (catKey === \"webFrontend\" || catKey === \"nativeFrontend\") {\n          const selectedOption = options[Math.floor(Math.random() * options.length)]?.id;\n          if (selectedOption) {\n            randomStack[catKey as \"webFrontend\" | \"nativeFrontend\"] = [selectedOption];\n          }\n          continue;\n        }\n\n        const numToPick = Math.floor(Math.random() * Math.min(options.length, 4));\n        if (numToPick === 0) {\n          randomStack[catKey as \"addons\" | \"examples\"] = [\"none\"];\n          continue;\n        }\n\n        const shuffledOptions = [...options]\n          .filter((opt) => opt.id !== \"none\")\n          .sort(() => 0.5 - Math.random())\n          .slice(0, numToPick);\n\n        randomStack[catKey as \"addons\" | \"examples\"] = shuffledOptions.map((opt) => opt.id);\n        continue;\n      }\n\n      const selectedOption = options[Math.floor(Math.random() * options.length)]?.id;\n      if (selectedOption) {\n        randomStack[catKey] = selectedOption as never;\n      }\n    }\n\n    startTransition(() => {\n      setStack({\n        ...(randomStack as StackState),\n        projectName: stack.projectName || \"my-better-t-app\",\n      });\n    });\n\n    contentRef.current?.scrollTo(0, 0);\n  }\n\n  function handleTechSelect(category: keyof typeof TECH_OPTIONS, techId: string) {\n    if (!isOptionCompatible(stack, category, techId)) {\n      return;\n    }\n\n    startTransition(() => {\n      setStack((currentStack: StackState) => {\n        const catKey = category as keyof StackState;\n        const update: Partial<StackState> = {};\n        const currentValue = currentStack[catKey];\n\n        if (\n          catKey === \"webFrontend\" ||\n          catKey === \"nativeFrontend\" ||\n          catKey === \"addons\" ||\n          catKey === \"examples\"\n        ) {\n          const currentArray = Array.isArray(currentValue) ? [...currentValue] : [];\n          let nextArray = [...currentArray];\n          const isSelected = currentArray.includes(techId);\n\n          if (catKey === \"webFrontend\") {\n            if (techId === \"none\") {\n              nextArray = [\"none\"];\n            } else if (isSelected) {\n              nextArray =\n                currentArray.length > 1 ? nextArray.filter((id) => id !== techId) : [\"none\"];\n            } else {\n              nextArray = [techId];\n            }\n          } else if (catKey === \"nativeFrontend\") {\n            if (techId === \"none\" || isSelected) {\n              nextArray = [\"none\"];\n            } else {\n              nextArray = [techId];\n            }\n          } else {\n            nextArray = isSelected\n              ? nextArray.filter((id) => id !== techId)\n              : [...nextArray, techId];\n\n            if (catKey === \"addons\" && !isSelected) {\n              if (techId === \"nx\") {\n                nextArray = nextArray.filter((id) => id !== \"turborepo\");\n              }\n              if (techId === \"turborepo\") {\n                nextArray = nextArray.filter((id) => id !== \"nx\");\n              }\n            }\n\n            if (nextArray.length > 1) {\n              nextArray = nextArray.filter((id) => id !== \"none\");\n            }\n\n            if (nextArray.length === 0 && catKey !== \"addons\" && catKey !== \"examples\") {\n              nextArray = [\"none\"];\n            }\n          }\n\n          const uniqueNext = [...new Set(nextArray)].sort();\n          const uniqueCurrent = [...new Set(currentArray)].sort();\n\n          if (JSON.stringify(uniqueNext) !== JSON.stringify(uniqueCurrent)) {\n            update[catKey] = uniqueNext as never;\n          }\n        } else if (currentValue !== techId) {\n          update[catKey] = techId as never;\n        } else if ((category === \"git\" || category === \"install\") && techId === \"false\") {\n          update[catKey] = \"true\" as never;\n        } else if ((category === \"git\" || category === \"install\") && techId === \"true\") {\n          update[catKey] = \"false\" as never;\n        }\n\n        return Object.keys(update).length > 0 ? update : {};\n      });\n    });\n  }\n\n  function removeSelectedTech(category: TechCategory, techId: string) {\n    const categoryKey = category as keyof StackState;\n    const value = stack[categoryKey];\n    const options = TECH_OPTIONS[category] || [];\n    const hasNoneOption = options.some((option) => option.id === \"none\");\n    const forceNoneFallback = category === \"addons\" || category === \"examples\";\n\n    if (Array.isArray(value)) {\n      const next = value.filter((id) => id !== techId);\n      const fallback = next.length === 0 && (hasNoneOption || forceNoneFallback) ? [\"none\"] : next;\n      startTransition(() => {\n        setStack({ [categoryKey]: fallback } as Partial<StackState>);\n      });\n      return;\n    }\n\n    if (value === techId && hasNoneOption) {\n      startTransition(() => {\n        setStack({ [categoryKey]: \"none\" } as Partial<StackState>);\n      });\n    }\n  }\n\n  async function copyToClipboard() {\n    try {\n      await navigator.clipboard.writeText(command);\n      setCopied(true);\n      setTimeout(() => setCopied(false), 2000);\n    } catch {\n      toast.error(\"Unable to copy command. Please copy it manually.\");\n    }\n  }\n\n  function resetStack() {\n    startTransition(() => {\n      setStack(DEFAULT_STACK);\n    });\n    contentRef.current?.scrollTo(0, 0);\n  }\n\n  function saveCurrentStack() {\n    const stackToSave = withFormattedProjectName(compatibilityAnalysis.adjustedStack || stack);\n    localStorage.setItem(\"betterTStackPreference\", JSON.stringify(stackToSave));\n    setLastSavedStack(stackToSave);\n    toast.success(\"Your stack configuration has been saved\");\n  }\n\n  function loadSavedStack() {\n    if (!lastSavedStack) {\n      return;\n    }\n\n    startTransition(() => {\n      setStack(lastSavedStack);\n    });\n\n    contentRef.current?.scrollTo(0, 0);\n    toast.success(\"Saved configuration loaded\");\n  }\n\n  function applyPreset(presetId: string) {\n    const preset = PRESET_TEMPLATES.find((template) => template.id === presetId);\n    if (!preset) {\n      return;\n    }\n\n    startTransition(() => {\n      setStack(preset.stack);\n    });\n\n    contentRef.current?.scrollTo(0, 0);\n    toast.success(`Applied preset: ${preset.name}`);\n  }\n\n  return {\n    applyPreset,\n    command,\n    compatibilityAnalysis,\n    copied,\n    copyToClipboard,\n    getRandomStack,\n    getStackUrl,\n    handleTechSelect,\n    lastSavedStack,\n    loadSavedStack,\n    mobileTab,\n    projectNameError,\n    removeSelectedTech,\n    resetStack,\n    saveCurrentStack,\n    scrollAreaRef,\n    selectedCount,\n    selectedFile,\n    setMobileTab,\n    setSelectedFile,\n    setStack,\n    setViewMode,\n    stack,\n    viewMode,\n  };\n}\n"
  },
  {
    "path": "apps/web/src/app/(home)/new/_components/tech-icon.tsx",
    "content": "import { useTheme } from \"next-themes\";\nimport Image from \"next/image\";\n\nimport { cn } from \"@/lib/utils\";\n\nexport function TechIcon({\n  icon,\n  name,\n  className,\n}: {\n  icon: string;\n  name: string;\n  className?: string;\n}) {\n  const { theme } = useTheme();\n\n  if (!icon) return null;\n\n  if (!icon.startsWith(\"https://\")) {\n    return <span className={cn(\"inline-flex items-center text-lg\", className)}>{icon}</span>;\n  }\n\n  let iconSrc = icon;\n  if (\n    theme === \"light\" &&\n    (icon.includes(\"drizzle\") ||\n      icon.includes(\"prisma\") ||\n      icon.includes(\"express\") ||\n      icon.includes(\"clerk\") ||\n      icon.includes(\"planetscale\") ||\n      icon.includes(\"nx\") ||\n      icon.includes(\"polar\") ||\n      icon.includes(\"astro\"))\n  ) {\n    iconSrc = icon.replace(\".svg\", \"-light.svg\");\n  }\n\n  return (\n    <Image\n      suppressHydrationWarning\n      src={iconSrc}\n      alt={`${name} icon`}\n      width={20}\n      height={20}\n      className={cn(\"inline-block\", className)}\n      unoptimized\n    />\n  );\n}\n"
  },
  {
    "path": "apps/web/src/app/(home)/new/_components/utils.ts",
    "content": "import { desktopWebFrontends } from \"@better-t-stack/types\";\n\nimport { DEFAULT_STACK, type StackState, type TECH_OPTIONS } from \"@/lib/constant\";\nimport { CATEGORY_ORDER } from \"@/lib/stack-utils\";\n\nexport function validateProjectName(name: string): string | undefined {\n  const INVALID_CHARS = [\"<\", \">\", \":\", '\"', \"|\", \"?\", \"*\"];\n  const MAX_LENGTH = 255;\n\n  if (name === \".\") return undefined;\n\n  if (!name) return \"Project name cannot be empty\";\n  if (name.length > MAX_LENGTH) {\n    return `Project name must be less than ${MAX_LENGTH} characters`;\n  }\n  if (INVALID_CHARS.some((char) => name.includes(char))) {\n    return \"Project name contains invalid characters\";\n  }\n  if (name.startsWith(\".\") || name.startsWith(\"-\")) {\n    return \"Project name cannot start with a dot or dash\";\n  }\n  if (name.toLowerCase() === \"node_modules\" || name.toLowerCase() === \"favicon.ico\") {\n    return \"Project name is reserved\";\n  }\n  return undefined;\n}\n\nexport const hasPWACompatibleFrontend = (webFrontend: string[]) =>\n  webFrontend.some((f) => [\"tanstack-router\", \"react-router\", \"solid\", \"next\"].includes(f));\n\nexport const hasTauriCompatibleFrontend = (webFrontend: string[]) =>\n  webFrontend.some((f) => (desktopWebFrontends as readonly string[]).includes(f));\n\nexport const hasElectrobunCompatibleFrontend = (webFrontend: string[]) =>\n  webFrontend.some((f) => (desktopWebFrontends as readonly string[]).includes(f));\n\nconst hasWebFrontend = (webFrontend: string[]) => webFrontend.some((f) => f !== \"none\");\nconst hasNativeFrontend = (nativeFrontend: string[]) => nativeFrontend.some((f) => f !== \"none\");\n\nconst clerkSupportedBackends = [\n  \"convex\",\n  \"hono\",\n  \"express\",\n  \"fastify\",\n  \"elysia\",\n  \"self-next\",\n  \"self-tanstack-start\",\n] as const;\n\nconst selfHostedFullstackBackends = [\n  \"self-next\",\n  \"self-tanstack-start\",\n  \"self-nuxt\",\n  \"self-svelte\",\n  \"self-astro\",\n] as const;\n\nconst clerkBackendRequirementMessage =\n  \"Clerk requires Convex, Hono, Express, Fastify, Elysia, or Next.js/TanStack Start fullstack backend\";\nconst clerkFrontendRequirementMessage =\n  \"Clerk requires React Router, TanStack Router, TanStack Start, Next.js, or React Native\";\nconst convexBetterAuthSupportedWebFrontends = [\n  \"react-router\",\n  \"tanstack-router\",\n  \"tanstack-start\",\n  \"next\",\n] as const;\nconst convexBetterAuthSupportedNativeFrontends = [\n  \"native-bare\",\n  \"native-uniwind\",\n  \"native-unistyles\",\n] as const;\n\nconst hasConvexBetterAuthCompatibleFrontend = (webFrontend: string[], nativeFrontend: string[]) =>\n  webFrontend.some((f) =>\n    convexBetterAuthSupportedWebFrontends.includes(\n      f as (typeof convexBetterAuthSupportedWebFrontends)[number],\n    ),\n  ) ||\n  nativeFrontend.some((f) =>\n    convexBetterAuthSupportedNativeFrontends.includes(\n      f as (typeof convexBetterAuthSupportedNativeFrontends)[number],\n    ),\n  );\n\nconst convexBetterAuthFrontendRequirementMessage =\n  \"Better-Auth with Convex requires React Router, TanStack Router, TanStack Start, Next.js, or React Native\";\n\nexport const hasClerkCompatibleFrontend = (webFrontend: string[], nativeFrontend: string[]) =>\n  webFrontend.some((f) =>\n    [\"react-router\", \"tanstack-router\", \"tanstack-start\", \"next\"].includes(f),\n  ) ||\n  nativeFrontend.some((f) => [\"native-bare\", \"native-uniwind\", \"native-unistyles\"].includes(f));\n\nexport const hasClerkCompatibleBackend = (backend: string) =>\n  clerkSupportedBackends.includes(backend as (typeof clerkSupportedBackends)[number]);\n\nconst isSelfHostedFullstackBackend = (backend: string) =>\n  selfHostedFullstackBackends.includes(backend as (typeof selfHostedFullstackBackends)[number]);\n\nexport const hasEvlogCompatibleBackend = (backend: string) =>\n  [\"hono\", \"express\", \"fastify\", \"elysia\", ...selfHostedFullstackBackends].includes(backend);\n\nexport const getCategoryDisplayName = (categoryKey: string): string => {\n  const result = categoryKey.replace(/([A-Z])/g, \" $1\");\n  return result.charAt(0).toUpperCase() + result.slice(1);\n};\n\ninterface CompatibilityResult {\n  adjustedStack: StackState | null;\n  notes: Record<string, { notes: string[]; hasIssue: boolean }>;\n  changes: Array<{ category: string; message: string }>;\n}\n\n/**\n * Analyzes the stack and auto-adjusts incompatible selections.\n * This follows the CLI approach: when you make a selection, dependent items adjust automatically.\n * The flow is: frontend -> backend -> runtime -> database -> orm -> api -> auth -> etc.\n */\nexport const analyzeStackCompatibility = (stack: StackState): CompatibilityResult => {\n  // Skip all validation if YOLO mode is enabled\n  if (stack.yolo === \"true\") {\n    return {\n      adjustedStack: null,\n      notes: {},\n      changes: [],\n    };\n  }\n\n  const nextStack = { ...stack };\n  let changed = false;\n  const notes: CompatibilityResult[\"notes\"] = {};\n  const changes: Array<{ category: string; message: string }> = [];\n\n  for (const cat of CATEGORY_ORDER) {\n    notes[cat] = { notes: [], hasIssue: false };\n  }\n\n  // ============================================\n  // BACKEND CONSTRAINTS\n  // ============================================\n\n  if (nextStack.backend === \"convex\") {\n    // Convex handles its own runtime, database, orm, api, dbSetup\n    const convexOverrides: Partial<StackState> = {\n      runtime: \"none\",\n      database: \"none\",\n      orm: \"none\",\n      api: \"none\",\n      dbSetup: \"none\",\n      serverDeploy: \"none\",\n    };\n\n    for (const [key, value] of Object.entries(convexOverrides)) {\n      const catKey = key as keyof StackState;\n      if (nextStack[catKey] !== value) {\n        nextStack[catKey] = value as never;\n        changed = true;\n        changes.push({\n          category: \"backend\",\n          message: `${getCategoryDisplayName(catKey)} set to '${value}' (Convex provides this)`,\n        });\n      }\n    }\n\n    // Remove incompatible frontends\n    if (nextStack.webFrontend.includes(\"solid\") || nextStack.webFrontend.includes(\"astro\")) {\n      nextStack.webFrontend = nextStack.webFrontend.filter((f) => f !== \"solid\" && f !== \"astro\");\n      if (nextStack.webFrontend.length === 0) nextStack.webFrontend = [\"none\"];\n      changed = true;\n      changes.push({ category: \"backend\", message: \"Removed Solid (incompatible with Convex)\" });\n    }\n\n    // Remove AI example if incompatible frontends are selected (Convex AI only supports React-based frontends)\n    if (nextStack.examples.includes(\"ai\")) {\n      const hasIncompatibleFrontend = nextStack.webFrontend.some((f) =>\n        [\"solid\", \"svelte\", \"nuxt\"].includes(f),\n      );\n      if (hasIncompatibleFrontend) {\n        nextStack.examples = nextStack.examples.filter((e) => e !== \"ai\");\n        if (nextStack.examples.length === 0) nextStack.examples = [\"none\"];\n        changed = true;\n        changes.push({\n          category: \"examples\",\n          message: \"AI example removed (Convex AI only supports React-based frontends)\",\n        });\n      }\n    }\n\n    // Auth constraints for Convex\n    if (nextStack.auth === \"clerk\") {\n      if (!hasClerkCompatibleFrontend(nextStack.webFrontend, nextStack.nativeFrontend)) {\n        nextStack.auth = \"none\";\n        changed = true;\n        changes.push({\n          category: \"auth\",\n          message: `Auth set to 'None' (${clerkFrontendRequirementMessage})`,\n        });\n      }\n    }\n\n    if (nextStack.auth === \"better-auth\") {\n      if (!hasConvexBetterAuthCompatibleFrontend(nextStack.webFrontend, nextStack.nativeFrontend)) {\n        nextStack.auth = \"none\";\n        changed = true;\n        changes.push({\n          category: \"auth\",\n          message: \"Auth set to 'None' (Better-Auth with Convex requires compatible frontend)\",\n        });\n      }\n    }\n  }\n\n  if (nextStack.backend === \"none\") {\n    // No backend means no runtime, database, orm, api, auth, dbSetup, serverDeploy\n    const noneOverrides: Partial<StackState> = {\n      runtime: \"none\",\n      database: \"none\",\n      orm: \"none\",\n      api: \"none\",\n      auth: \"none\",\n      dbSetup: \"none\",\n      serverDeploy: \"none\",\n      payments: \"none\",\n    };\n\n    for (const [key, value] of Object.entries(noneOverrides)) {\n      const catKey = key as keyof StackState;\n      if (nextStack[catKey] !== value) {\n        nextStack[catKey] = value as never;\n        changed = true;\n        changes.push({\n          category: \"backend\",\n          message: `${getCategoryDisplayName(catKey)} set to '${value}' (no backend)`,\n        });\n      }\n    }\n\n    // Clear examples\n    if (\n      nextStack.examples.length > 0 &&\n      !(nextStack.examples.length === 1 && nextStack.examples[0] === \"none\")\n    ) {\n      nextStack.examples = [\"none\"];\n      changed = true;\n      changes.push({ category: \"backend\", message: \"Examples cleared (no backend)\" });\n    }\n  }\n\n  // Self (fullstack) backend constraints\n  if (isSelfHostedFullstackBackend(nextStack.backend)) {\n    // Fullstack uses frontend's API routes, no separate runtime needed\n    if (nextStack.runtime !== \"none\") {\n      nextStack.runtime = \"none\";\n      changed = true;\n      changes.push({\n        category: \"backend\",\n        message: \"Runtime set to 'None' (fullstack uses frontend's API routes)\",\n      });\n    }\n    if (nextStack.serverDeploy !== \"none\") {\n      nextStack.serverDeploy = \"none\";\n      changed = true;\n      changes.push({\n        category: \"backend\",\n        message: \"Server deploy set to 'None' (fullstack uses frontend deployment)\",\n      });\n    }\n\n    // Ensure correct frontend is selected\n    if (nextStack.backend === \"self-next\" && !nextStack.webFrontend.includes(\"next\")) {\n      nextStack.webFrontend = [\"next\"];\n      changed = true;\n      changes.push({\n        category: \"backend\",\n        message: \"Frontend set to 'Next.js' (required for Next.js fullstack)\",\n      });\n    }\n    if (\n      nextStack.backend === \"self-tanstack-start\" &&\n      !nextStack.webFrontend.includes(\"tanstack-start\")\n    ) {\n      nextStack.webFrontend = [\"tanstack-start\"];\n      changed = true;\n      changes.push({\n        category: \"backend\",\n        message: \"Frontend set to 'TanStack Start' (required for TanStack Start fullstack)\",\n      });\n    }\n    if (nextStack.backend === \"self-nuxt\" && !nextStack.webFrontend.includes(\"nuxt\")) {\n      nextStack.webFrontend = [\"nuxt\"];\n      changed = true;\n      changes.push({\n        category: \"backend\",\n        message: \"Frontend set to 'Nuxt' (required for Nuxt fullstack)\",\n      });\n    }\n    if (nextStack.backend === \"self-svelte\" && !nextStack.webFrontend.includes(\"svelte\")) {\n      nextStack.webFrontend = [\"svelte\"];\n      changed = true;\n      changes.push({\n        category: \"backend\",\n        message: \"Frontend set to 'SvelteKit' (required for SvelteKit fullstack)\",\n      });\n    }\n    if (nextStack.backend === \"self-astro\" && !nextStack.webFrontend.includes(\"astro\")) {\n      nextStack.webFrontend = [\"astro\"];\n      changed = true;\n      changes.push({\n        category: \"backend\",\n        message: \"Frontend set to 'Astro' (required for Astro fullstack)\",\n      });\n    }\n  }\n\n  // ============================================\n  // RUNTIME CONSTRAINTS\n  // ============================================\n\n  // Workers runtime requires Hono backend\n  if (nextStack.runtime === \"workers\" && nextStack.backend !== \"hono\") {\n    nextStack.backend = \"hono\";\n    changed = true;\n    changes.push({ category: \"runtime\", message: \"Backend set to 'Hono' (required for Workers)\" });\n  }\n\n  // Workers runtime requires server deployment\n  if (nextStack.runtime === \"workers\" && nextStack.serverDeploy === \"none\") {\n    nextStack.serverDeploy = \"cloudflare\";\n    changed = true;\n    changes.push({\n      category: \"runtime\",\n      message: \"Server deploy set to 'Cloudflare' (required for Workers)\",\n    });\n  }\n\n  // Workers runtime is incompatible with MongoDB\n  if (nextStack.runtime === \"workers\" && nextStack.database === \"mongodb\") {\n    nextStack.database = \"sqlite\";\n    nextStack.orm = \"drizzle\";\n    nextStack.dbSetup = \"d1\";\n    changed = true;\n    changes.push({\n      category: \"runtime\",\n      message: \"Database changed to SQLite with D1 (MongoDB incompatible with Workers)\",\n    });\n  }\n\n  // Runtime \"none\" only for Convex, no backend, or self-hosted fullstack backends.\n  if (\n    nextStack.runtime === \"none\" &&\n    nextStack.backend !== \"convex\" &&\n    nextStack.backend !== \"none\" &&\n    !isSelfHostedFullstackBackend(nextStack.backend)\n  ) {\n    nextStack.runtime = DEFAULT_STACK.runtime;\n    changed = true;\n    changes.push({\n      category: \"runtime\",\n      message: `Runtime set to '${DEFAULT_STACK.runtime}' (required for this backend)`,\n    });\n  }\n\n  // ============================================\n  // DATABASE & ORM CONSTRAINTS (CLI-like flow)\n  // ============================================\n\n  // Skip if backend doesn't use database\n  if (nextStack.backend !== \"convex\" && nextStack.backend !== \"none\") {\n    // If database is none, ORM and dbSetup must be none\n    if (nextStack.database === \"none\") {\n      if (nextStack.orm !== \"none\") {\n        nextStack.orm = \"none\";\n        changed = true;\n        changes.push({ category: \"database\", message: \"ORM set to 'None' (no database selected)\" });\n      }\n      if (nextStack.dbSetup !== \"none\") {\n        nextStack.dbSetup = \"none\";\n        changed = true;\n        changes.push({\n          category: \"database\",\n          message: \"DB Setup set to 'None' (no database selected)\",\n        });\n      }\n    }\n\n    // MongoDB requires Prisma or Mongoose\n    if (nextStack.database === \"mongodb\") {\n      if (nextStack.orm !== \"prisma\" && nextStack.orm !== \"mongoose\") {\n        nextStack.orm = \"prisma\";\n        changed = true;\n        changes.push({\n          category: \"database\",\n          message: \"ORM set to 'Prisma' (required for MongoDB)\",\n        });\n      }\n      // MongoDB only works with mongodb-atlas or none for dbSetup\n      if (\n        nextStack.dbSetup !== \"mongodb-atlas\" &&\n        nextStack.dbSetup !== \"none\" &&\n        nextStack.dbSetup !== \"docker\"\n      ) {\n        nextStack.dbSetup = \"none\";\n        changed = true;\n        changes.push({\n          category: \"database\",\n          message: \"DB Setup set to 'None' (incompatible with MongoDB)\",\n        });\n      }\n    }\n\n    // Relational databases (sqlite, postgres, mysql) need Drizzle or Prisma\n    if ([\"sqlite\", \"postgres\", \"mysql\"].includes(nextStack.database)) {\n      if (nextStack.orm === \"none\") {\n        nextStack.orm = \"drizzle\";\n        changed = true;\n        changes.push({\n          category: \"database\",\n          message: \"ORM set to 'Drizzle' (required for database)\",\n        });\n      }\n      if (nextStack.orm === \"mongoose\") {\n        nextStack.orm = \"drizzle\";\n        changed = true;\n        changes.push({\n          category: \"database\",\n          message: \"ORM set to 'Drizzle' (Mongoose only works with MongoDB)\",\n        });\n      }\n    }\n\n    // ORM selected but no database - select appropriate database\n    if (nextStack.orm !== \"none\" && nextStack.database === \"none\") {\n      if (nextStack.orm === \"mongoose\") {\n        nextStack.database = \"mongodb\";\n        changed = true;\n        changes.push({\n          category: \"orm\",\n          message: \"Database set to 'MongoDB' (required for Mongoose)\",\n        });\n      } else {\n        nextStack.database = \"sqlite\";\n        changed = true;\n        changes.push({ category: \"orm\", message: \"Database set to 'SQLite' (required for ORM)\" });\n      }\n    }\n\n    // DB Setup constraints\n    if (nextStack.dbSetup === \"turso\" && nextStack.database !== \"sqlite\") {\n      nextStack.database = \"sqlite\";\n      changed = true;\n      changes.push({\n        category: \"dbSetup\",\n        message: \"Database set to 'SQLite' (required for Turso)\",\n      });\n    }\n    if (nextStack.dbSetup === \"d1\") {\n      if (nextStack.database !== \"sqlite\") {\n        nextStack.database = \"sqlite\";\n        changed = true;\n        changes.push({\n          category: \"dbSetup\",\n          message: \"Database set to 'SQLite' (required for D1)\",\n        });\n      }\n      if (isSelfHostedFullstackBackend(nextStack.backend)) {\n        if (nextStack.webDeploy !== \"cloudflare\") {\n          nextStack.webDeploy = \"cloudflare\";\n          changed = true;\n          changes.push({\n            category: \"dbSetup\",\n            message: \"Web deploy set to 'Cloudflare' (required for D1 with fullstack backend)\",\n          });\n        }\n      } else {\n        if (nextStack.runtime !== \"workers\" || nextStack.backend !== \"hono\") {\n          nextStack.runtime = \"workers\";\n          nextStack.backend = \"hono\";\n          changed = true;\n          changes.push({\n            category: \"dbSetup\",\n            message: \"Runtime set to 'Workers' with 'Hono' (required for D1)\",\n          });\n        }\n        if (nextStack.serverDeploy !== \"cloudflare\") {\n          nextStack.serverDeploy = \"cloudflare\";\n          changed = true;\n          changes.push({\n            category: \"dbSetup\",\n            message: \"Server deploy set to 'Cloudflare' (required for D1 with Workers)\",\n          });\n        }\n      }\n    }\n    if (nextStack.dbSetup === \"neon\" && nextStack.database !== \"postgres\") {\n      nextStack.database = \"postgres\";\n      changed = true;\n      changes.push({\n        category: \"dbSetup\",\n        message: \"Database set to 'PostgreSQL' (required for Neon)\",\n      });\n    }\n    if (nextStack.dbSetup === \"supabase\" && nextStack.database !== \"postgres\") {\n      nextStack.database = \"postgres\";\n      changed = true;\n      changes.push({\n        category: \"dbSetup\",\n        message: \"Database set to 'PostgreSQL' (required for Supabase)\",\n      });\n    }\n    if (nextStack.dbSetup === \"prisma-postgres\" && nextStack.database !== \"postgres\") {\n      nextStack.database = \"postgres\";\n      changed = true;\n      changes.push({\n        category: \"dbSetup\",\n        message: \"Database set to 'PostgreSQL' (required for Prisma Postgres)\",\n      });\n    }\n    if (nextStack.dbSetup === \"mongodb-atlas\" && nextStack.database !== \"mongodb\") {\n      nextStack.database = \"mongodb\";\n      if (nextStack.orm !== \"prisma\" && nextStack.orm !== \"mongoose\") {\n        nextStack.orm = \"prisma\";\n      }\n      changed = true;\n      changes.push({\n        category: \"dbSetup\",\n        message: \"Database set to 'MongoDB' (required for MongoDB Atlas)\",\n      });\n    }\n    if (\n      nextStack.dbSetup === \"planetscale\" &&\n      nextStack.database !== \"postgres\" &&\n      nextStack.database !== \"mysql\"\n    ) {\n      nextStack.database = \"postgres\";\n      changed = true;\n      changes.push({\n        category: \"dbSetup\",\n        message: \"Database set to 'PostgreSQL' (required for PlanetScale)\",\n      });\n    }\n    if (nextStack.dbSetup === \"docker\") {\n      if (nextStack.database === \"sqlite\") {\n        nextStack.dbSetup = \"none\";\n        changed = true;\n        changes.push({\n          category: \"dbSetup\",\n          message: \"DB Setup set to 'None' (SQLite doesn't need Docker)\",\n        });\n      }\n      if (nextStack.runtime === \"workers\") {\n        nextStack.dbSetup = \"d1\";\n        changed = true;\n        changes.push({\n          category: \"dbSetup\",\n          message: \"DB Setup set to 'D1' (Docker incompatible with Workers)\",\n        });\n      }\n    }\n  }\n\n  // ============================================\n  // API CONSTRAINTS\n  // ============================================\n\n  if (nextStack.backend !== \"convex\" && nextStack.backend !== \"none\") {\n    // Nuxt, Svelte, Solid, Astro require oRPC (not tRPC)\n    const needsOrpc = nextStack.webFrontend.some((f) =>\n      [\"nuxt\", \"svelte\", \"solid\", \"astro\"].includes(f),\n    );\n    if (needsOrpc && nextStack.api === \"trpc\") {\n      nextStack.api = \"orpc\";\n      changed = true;\n      changes.push({ category: \"api\", message: \"API set to 'oRPC' (required for this frontend)\" });\n    }\n  }\n\n  // ============================================\n  // AUTH CONSTRAINTS\n  // ============================================\n\n  if (nextStack.auth === \"clerk\") {\n    if (!hasClerkCompatibleBackend(nextStack.backend)) {\n      nextStack.auth = \"none\";\n      changed = true;\n      changes.push({\n        category: \"auth\",\n        message: `Auth set to 'None' (${clerkBackendRequirementMessage})`,\n      });\n    } else if (!hasClerkCompatibleFrontend(nextStack.webFrontend, nextStack.nativeFrontend)) {\n      nextStack.auth = \"none\";\n      changed = true;\n      changes.push({\n        category: \"auth\",\n        message: `Auth set to 'None' (${clerkFrontendRequirementMessage})`,\n      });\n    }\n  }\n\n  // ============================================\n  // PAYMENTS CONSTRAINTS\n  // ============================================\n\n  if (nextStack.payments === \"polar\") {\n    if (nextStack.auth !== \"better-auth\") {\n      nextStack.payments = \"none\";\n      changed = true;\n      changes.push({\n        category: \"payments\",\n        message: \"Payments set to 'None' (Polar requires Better Auth)\",\n      });\n    }\n    if (nextStack.backend === \"convex\") {\n      nextStack.payments = \"none\";\n      changed = true;\n      changes.push({\n        category: \"payments\",\n        message: \"Payments set to 'None' (Polar incompatible with Convex)\",\n      });\n    }\n    const hasAnyFrontend =\n      hasWebFrontend(nextStack.webFrontend) || hasNativeFrontend(nextStack.nativeFrontend);\n    if (!hasWebFrontend(nextStack.webFrontend) && hasAnyFrontend) {\n      nextStack.payments = \"none\";\n      changed = true;\n      changes.push({\n        category: \"payments\",\n        message: \"Payments set to 'None' (Polar requires a web frontend or no frontend)\",\n      });\n    }\n  }\n\n  // ============================================\n  // ADDONS CONSTRAINTS\n  // ============================================\n\n  const pwaCompat = hasPWACompatibleFrontend(nextStack.webFrontend);\n  const tauriCompat = hasTauriCompatibleFrontend(nextStack.webFrontend);\n  const electrobunCompat = hasElectrobunCompatibleFrontend(nextStack.webFrontend);\n  const evlogCompat = hasEvlogCompatibleBackend(nextStack.backend);\n\n  if (!pwaCompat && nextStack.addons.includes(\"pwa\")) {\n    nextStack.addons = nextStack.addons.filter((a) => a !== \"pwa\");\n    if (nextStack.addons.length === 0) nextStack.addons = [\"none\"];\n    changed = true;\n    changes.push({ category: \"addons\", message: \"PWA removed (requires compatible frontend)\" });\n  }\n  if (!tauriCompat && nextStack.addons.includes(\"tauri\")) {\n    nextStack.addons = nextStack.addons.filter((a) => a !== \"tauri\");\n    if (nextStack.addons.length === 0) nextStack.addons = [\"none\"];\n    changed = true;\n    changes.push({ category: \"addons\", message: \"Tauri removed (requires compatible frontend)\" });\n  }\n  if (!electrobunCompat && nextStack.addons.includes(\"electrobun\")) {\n    nextStack.addons = nextStack.addons.filter((a) => a !== \"electrobun\");\n    if (nextStack.addons.length === 0) nextStack.addons = [\"none\"];\n    changed = true;\n    changes.push({\n      category: \"addons\",\n      message: \"Electrobun removed (requires compatible frontend)\",\n    });\n  }\n  if (!evlogCompat && nextStack.addons.includes(\"evlog\")) {\n    nextStack.addons = nextStack.addons.filter((a) => a !== \"evlog\");\n    if (nextStack.addons.length === 0) nextStack.addons = [\"none\"];\n    changed = true;\n    changes.push({\n      category: \"addons\",\n      message: \"evlog removed (requires a server or fullstack backend)\",\n    });\n  }\n\n  // ============================================\n  // EXAMPLES CONSTRAINTS\n  // ============================================\n\n  // Todo example requires database AND API (unless Convex)\n  if (nextStack.examples.includes(\"todo\") && nextStack.backend !== \"convex\") {\n    const needsRemoval = nextStack.database === \"none\" || nextStack.api === \"none\";\n    if (needsRemoval) {\n      const reason = nextStack.database === \"none\" ? \"requires database\" : \"requires API layer\";\n      nextStack.examples = nextStack.examples.filter((e) => e !== \"todo\");\n      if (nextStack.examples.length === 0) nextStack.examples = [\"none\"];\n      changed = true;\n      changes.push({ category: \"examples\", message: `Todo removed (${reason})` });\n    }\n  }\n\n  // AI example constraints\n  if (nextStack.examples.includes(\"ai\")) {\n    // Solid and Astro frontends are incompatible with the AI example\n    if (nextStack.webFrontend.includes(\"solid\") || nextStack.webFrontend.includes(\"astro\")) {\n      nextStack.examples = nextStack.examples.filter((e) => e !== \"ai\");\n      if (nextStack.examples.length === 0) nextStack.examples = [\"none\"];\n      changed = true;\n      changes.push({\n        category: \"examples\",\n        message: \"AI removed (not compatible with Solid or Astro frontend)\",\n      });\n    }\n    // Convex AI only supports React-based frontends (not Svelte/Nuxt)\n    if (nextStack.backend === \"convex\") {\n      const hasIncompatibleFrontend = nextStack.webFrontend.some((f) =>\n        [\"svelte\", \"nuxt\"].includes(f),\n      );\n      if (hasIncompatibleFrontend) {\n        nextStack.examples = nextStack.examples.filter((e) => e !== \"ai\");\n        if (nextStack.examples.length === 0) nextStack.examples = [\"none\"];\n        changed = true;\n        changes.push({\n          category: \"examples\",\n          message: \"AI removed (Convex AI only supports React-based frontends)\",\n        });\n      }\n    }\n  }\n\n  // ============================================\n  // DEPLOYMENT CONSTRAINTS\n  // ============================================\n\n  // Web deploy requires web frontend\n  if (nextStack.webDeploy !== \"none\" && !nextStack.webFrontend.some((f) => f !== \"none\")) {\n    nextStack.webDeploy = \"none\";\n    changed = true;\n    changes.push({ category: \"webDeploy\", message: \"Web deploy set to 'None' (no web frontend)\" });\n  }\n\n  // Server deploy constraints\n  if (nextStack.serverDeploy === \"cloudflare\") {\n    if (nextStack.runtime !== \"workers\" || nextStack.backend !== \"hono\") {\n      nextStack.serverDeploy = \"none\";\n      changed = true;\n      changes.push({\n        category: \"serverDeploy\",\n        message: \"Server deploy set to 'None' (Cloudflare requires Workers + Hono)\",\n      });\n    }\n  }\n\n  if (\n    nextStack.serverDeploy !== \"none\" &&\n    ([\"none\", \"convex\"].includes(nextStack.backend) ||\n      isSelfHostedFullstackBackend(nextStack.backend))\n  ) {\n    nextStack.serverDeploy = \"none\";\n    changed = true;\n    changes.push({\n      category: \"serverDeploy\",\n      message: \"Server deploy set to 'None' (not needed for this backend)\",\n    });\n  }\n\n  return {\n    adjustedStack: changed ? nextStack : null,\n    notes,\n    changes,\n  };\n};\n\n/**\n * Returns a reason why an option is disabled, or null if it's enabled.\n *\n * PHILOSOPHY: Only disable options that are TRULY incompatible.\n * - Don't create circular dependencies\n * - Allow users to select options that will trigger auto-adjustments\n * - Follow CLI behavior: filter options based on UPSTREAM selections only\n */\nexport const getDisabledReason = (\n  currentStack: StackState,\n  category: keyof typeof TECH_OPTIONS,\n  optionId: string,\n): string | null => {\n  // ============================================\n  // CONVEX BACKEND - locks down many options\n  // ============================================\n  if (currentStack.backend === \"convex\") {\n    if (category === \"runtime\" && optionId !== \"none\") {\n      return \"Convex provides its own runtime\";\n    }\n    if (category === \"database\" && optionId !== \"none\") {\n      return \"Convex provides its own database\";\n    }\n    if (category === \"orm\" && optionId !== \"none\") {\n      return \"Convex has built-in data access\";\n    }\n    if (category === \"api\" && optionId !== \"none\") {\n      return \"Convex provides its own API layer\";\n    }\n    if (category === \"dbSetup\" && optionId !== \"none\") {\n      return \"Convex handles database setup\";\n    }\n    if (category === \"serverDeploy\" && optionId !== \"none\") {\n      return \"Convex has its own deployment\";\n    }\n    if (category === \"auth\" && optionId === \"better-auth\") {\n      if (\n        !hasConvexBetterAuthCompatibleFrontend(\n          currentStack.webFrontend,\n          currentStack.nativeFrontend,\n        )\n      ) {\n        return convexBetterAuthFrontendRequirementMessage;\n      }\n    }\n    if (category === \"webFrontend\" && (optionId === \"solid\" || optionId === \"astro\")) {\n      return `${optionId.charAt(0).toUpperCase() + optionId.slice(1)} is not compatible with Convex`;\n    }\n    if (category === \"examples\" && optionId === \"ai\") {\n      const hasIncompatibleFrontend = currentStack.webFrontend.some((f) =>\n        [\"solid\", \"svelte\", \"nuxt\"].includes(f),\n      );\n      if (hasIncompatibleFrontend) {\n        const frontendName = currentStack.webFrontend.find((f) =>\n          [\"solid\", \"svelte\", \"nuxt\"].includes(f),\n        );\n        return `Convex AI example only supports React-based frontends (not ${frontendName})`;\n      }\n    }\n    if (category === \"payments\" && optionId === \"polar\") {\n      return \"Polar is not compatible with Convex\";\n    }\n  }\n\n  // ============================================\n  // NO BACKEND - locks down backend-dependent options\n  // ============================================\n  if (currentStack.backend === \"none\") {\n    if (category === \"runtime\" && optionId !== \"none\") {\n      return \"No backend selected\";\n    }\n    if (category === \"database\" && optionId !== \"none\") {\n      return \"No backend selected\";\n    }\n    if (category === \"orm\" && optionId !== \"none\") {\n      return \"No backend selected\";\n    }\n    if (category === \"api\" && optionId !== \"none\") {\n      return \"No backend selected\";\n    }\n    if (category === \"auth\" && optionId !== \"none\") {\n      return \"No backend selected\";\n    }\n    if (category === \"dbSetup\" && optionId !== \"none\") {\n      return \"No backend selected\";\n    }\n    if (category === \"serverDeploy\" && optionId !== \"none\") {\n      return \"No backend selected\";\n    }\n    if (category === \"payments\" && optionId !== \"none\") {\n      return \"No backend selected\";\n    }\n    if (category === \"examples\" && optionId !== \"none\") {\n      return \"No backend selected\";\n    }\n  }\n\n  // ============================================\n  // FULLSTACK BACKEND CONSTRAINTS\n  // ============================================\n  if (currentStack.backend === \"self-next\") {\n    if (category === \"runtime\" && optionId !== \"none\") {\n      return \"Next.js fullstack uses built-in API routes\";\n    }\n    if (category === \"webFrontend\" && optionId !== \"next\") {\n      return \"Next.js fullstack requires Next.js frontend\";\n    }\n    if (category === \"serverDeploy\" && optionId !== \"none\") {\n      return \"Fullstack uses frontend deployment\";\n    }\n  }\n\n  if (currentStack.backend === \"self-nuxt\") {\n    if (category === \"runtime\" && optionId !== \"none\") {\n      return \"Nuxt fullstack uses built-in server routes\";\n    }\n    if (category === \"webFrontend\" && optionId !== \"nuxt\") {\n      return \"Nuxt fullstack requires Nuxt frontend\";\n    }\n    if (category === \"serverDeploy\" && optionId !== \"none\") {\n      return \"Fullstack uses frontend deployment\";\n    }\n    if (category === \"api\" && optionId === \"trpc\") {\n      return \"tRPC is not compatible with Nuxt (use oRPC)\";\n    }\n  }\n\n  if (currentStack.backend === \"self-svelte\") {\n    if (category === \"runtime\" && optionId !== \"none\") {\n      return \"SvelteKit fullstack uses built-in server routes\";\n    }\n    if (category === \"webFrontend\" && optionId !== \"svelte\") {\n      return \"SvelteKit fullstack requires SvelteKit frontend\";\n    }\n    if (category === \"serverDeploy\" && optionId !== \"none\") {\n      return \"Fullstack uses frontend deployment\";\n    }\n    if (category === \"api\" && optionId === \"trpc\") {\n      return \"tRPC is not compatible with SvelteKit (use oRPC)\";\n    }\n  }\n\n  if (currentStack.backend === \"self-tanstack-start\") {\n    if (category === \"runtime\" && optionId !== \"none\") {\n      return \"TanStack Start fullstack uses built-in API routes\";\n    }\n    if (category === \"webFrontend\" && optionId !== \"tanstack-start\") {\n      return \"TanStack Start fullstack requires TanStack Start frontend\";\n    }\n    if (category === \"serverDeploy\" && optionId !== \"none\") {\n      return \"Fullstack uses frontend deployment\";\n    }\n  }\n\n  if (currentStack.backend === \"self-astro\") {\n    if (category === \"runtime\" && optionId !== \"none\") {\n      return \"Astro fullstack uses built-in API routes\";\n    }\n    if (category === \"webFrontend\" && optionId !== \"astro\") {\n      return \"Astro fullstack requires Astro frontend\";\n    }\n    if (category === \"serverDeploy\" && optionId !== \"none\") {\n      return \"Fullstack uses frontend deployment\";\n    }\n    if (category === \"api\" && optionId === \"trpc\") {\n      return \"tRPC is not compatible with Astro (use oRPC)\";\n    }\n  }\n\n  // ============================================\n  // BACKEND SELECTION CONSTRAINTS\n  // ============================================\n  if (category === \"backend\") {\n    if (optionId === \"self-next\" && !currentStack.webFrontend.includes(\"next\")) {\n      return \"Requires Next.js frontend\";\n    }\n    if (\n      optionId === \"self-tanstack-start\" &&\n      !currentStack.webFrontend.includes(\"tanstack-start\")\n    ) {\n      return \"Requires TanStack Start frontend\";\n    }\n    if (optionId === \"self-nuxt\" && !currentStack.webFrontend.includes(\"nuxt\")) {\n      return \"Requires Nuxt frontend\";\n    }\n    if (optionId === \"self-svelte\" && !currentStack.webFrontend.includes(\"svelte\")) {\n      return \"Requires SvelteKit frontend\";\n    }\n    if (optionId === \"self-astro\" && !currentStack.webFrontend.includes(\"astro\")) {\n      return \"Requires Astro frontend\";\n    }\n    if (\n      optionId === \"convex\" &&\n      (currentStack.webFrontend.includes(\"solid\") || currentStack.webFrontend.includes(\"astro\"))\n    ) {\n      const incompatible = currentStack.webFrontend.includes(\"solid\") ? \"Solid\" : \"Astro\";\n      return `Convex is not compatible with ${incompatible}`;\n    }\n    // Workers runtime only works with Hono backend\n    if (currentStack.runtime === \"workers\" && optionId !== \"hono\" && optionId !== \"none\") {\n      return \"Workers runtime only works with Hono\";\n    }\n  }\n\n  // ============================================\n  // RUNTIME CONSTRAINTS\n  // ============================================\n  if (category === \"runtime\") {\n    if (optionId === \"workers\" && currentStack.backend !== \"hono\") {\n      return \"Workers requires Hono backend\";\n    }\n    if (optionId === \"none\") {\n      if (\n        currentStack.backend !== \"convex\" &&\n        currentStack.backend !== \"none\" &&\n        !isSelfHostedFullstackBackend(currentStack.backend)\n      ) {\n        return \"Runtime 'None' only for Convex or fullstack backends\";\n      }\n    }\n  }\n\n  // ============================================\n  // DATABASE CONSTRAINTS\n  // ============================================\n  if (category === \"database\") {\n    if (optionId === \"mongodb\" && currentStack.runtime === \"workers\") {\n      return \"MongoDB is not compatible with Workers runtime\";\n    }\n    // Allow all databases when ORM is none - system will auto-select ORM\n  }\n\n  // ============================================\n  // ORM CONSTRAINTS\n  // ============================================\n  if (category === \"orm\") {\n    if (optionId === \"mongoose\") {\n      if (currentStack.runtime === \"workers\") {\n        return \"Mongoose requires MongoDB, which is incompatible with Workers\";\n      }\n      // Only block if a non-MongoDB database is EXPLICITLY selected\n      if (currentStack.database !== \"none\" && currentStack.database !== \"mongodb\") {\n        return \"Mongoose only works with MongoDB\";\n      }\n      // Allow when database is \"none\" - system will auto-select MongoDB\n    }\n    if (optionId === \"drizzle\" && currentStack.database === \"mongodb\") {\n      return \"Drizzle does not support MongoDB\";\n    }\n    if (optionId === \"none\" && currentStack.database !== \"none\") {\n      return \"Database requires an ORM\";\n    }\n  }\n\n  // ============================================\n  // DB SETUP CONSTRAINTS\n  // ============================================\n  if (category === \"dbSetup\" && optionId !== \"none\") {\n    if (currentStack.database === \"none\") {\n      return \"Select a database first\";\n    }\n\n    // Database-specific setups\n    if (optionId === \"turso\" && currentStack.database !== \"sqlite\") {\n      return \"Turso requires SQLite\";\n    }\n    if (optionId === \"d1\") {\n      if (currentStack.database !== \"sqlite\") return \"D1 requires SQLite\";\n      if (\n        currentStack.runtime !== \"workers\" &&\n        !isSelfHostedFullstackBackend(currentStack.backend)\n      ) {\n        return \"D1 requires Cloudflare Workers runtime or a self fullstack backend\";\n      }\n    }\n    if (optionId === \"neon\" && currentStack.database !== \"postgres\") {\n      return \"Neon requires PostgreSQL\";\n    }\n    if (optionId === \"supabase\" && currentStack.database !== \"postgres\") {\n      return \"Supabase requires PostgreSQL\";\n    }\n    if (optionId === \"prisma-postgres\" && currentStack.database !== \"postgres\") {\n      return \"Prisma Postgres requires PostgreSQL\";\n    }\n    if (optionId === \"mongodb-atlas\" && currentStack.database !== \"mongodb\") {\n      return \"MongoDB Atlas requires MongoDB\";\n    }\n    if (\n      optionId === \"planetscale\" &&\n      currentStack.database !== \"postgres\" &&\n      currentStack.database !== \"mysql\"\n    ) {\n      return \"PlanetScale requires PostgreSQL or MySQL\";\n    }\n    if (optionId === \"docker\") {\n      if (currentStack.database === \"sqlite\") return \"SQLite doesn't need Docker\";\n      if (currentStack.runtime === \"workers\") return \"Docker is incompatible with Workers\";\n    }\n  }\n\n  // ============================================\n  // API CONSTRAINTS\n  // ============================================\n  if (category === \"api\" && optionId === \"trpc\") {\n    const needsOrpc = currentStack.webFrontend.some((f) =>\n      [\"nuxt\", \"svelte\", \"solid\", \"astro\"].includes(f),\n    );\n    if (needsOrpc) {\n      const frontendName = currentStack.webFrontend.find((f) =>\n        [\"nuxt\", \"svelte\", \"solid\", \"astro\"].includes(f),\n      );\n      return `${frontendName} requires oRPC, not tRPC`;\n    }\n  }\n\n  // ============================================\n  // AUTH CONSTRAINTS\n  // ============================================\n  if (category === \"auth\") {\n    if (optionId === \"clerk\") {\n      if (!hasClerkCompatibleBackend(currentStack.backend)) {\n        return clerkBackendRequirementMessage;\n      }\n      if (!hasClerkCompatibleFrontend(currentStack.webFrontend, currentStack.nativeFrontend)) {\n        return clerkFrontendRequirementMessage;\n      }\n    }\n  }\n\n  // ============================================\n  // PAYMENTS CONSTRAINTS\n  // ============================================\n  if (category === \"payments\" && optionId === \"polar\") {\n    if (currentStack.auth !== \"better-auth\") {\n      return \"Polar requires Better Auth\";\n    }\n    const hasAnyFrontend =\n      hasWebFrontend(currentStack.webFrontend) || hasNativeFrontend(currentStack.nativeFrontend);\n    if (!hasWebFrontend(currentStack.webFrontend) && hasAnyFrontend) {\n      return \"Polar requires a web frontend or no frontend\";\n    }\n  }\n\n  // ============================================\n  // ADDONS CONSTRAINTS\n  // ============================================\n  if (category === \"addons\") {\n    if (optionId === \"pwa\" && !hasPWACompatibleFrontend(currentStack.webFrontend)) {\n      return \"PWA requires TanStack Router, React Router, Solid, or Next.js\";\n    }\n    if (optionId === \"tauri\" && !hasTauriCompatibleFrontend(currentStack.webFrontend)) {\n      return \"Tauri requires a web frontend\";\n    }\n    if (optionId === \"electrobun\" && !hasElectrobunCompatibleFrontend(currentStack.webFrontend)) {\n      return \"Electrobun requires a web frontend\";\n    }\n    if (optionId === \"evlog\" && !hasEvlogCompatibleBackend(currentStack.backend)) {\n      return \"evlog requires Hono, Express, Fastify, Elysia, or a fullstack backend\";\n    }\n    if (optionId === \"nx\" && currentStack.addons.includes(\"turborepo\")) {\n      return \"Nx and Turborepo cannot be used together\";\n    }\n    if (optionId === \"turborepo\" && currentStack.addons.includes(\"nx\")) {\n      return \"Nx and Turborepo cannot be used together\";\n    }\n  }\n\n  // ============================================\n  // EXAMPLES CONSTRAINTS\n  // ============================================\n  if (category === \"examples\") {\n    if (optionId === \"todo\" && currentStack.backend !== \"convex\") {\n      if (currentStack.database === \"none\") {\n        return \"Todo example requires a database\";\n      }\n      if (currentStack.api === \"none\") {\n        return \"Todo example requires an API layer (tRPC or oRPC)\";\n      }\n    }\n    if (optionId === \"ai\") {\n      if (\n        currentStack.webFrontend.includes(\"solid\") ||\n        currentStack.webFrontend.includes(\"astro\")\n      ) {\n        return \"AI example not compatible with Solid or Astro frontend\";\n      }\n      if (currentStack.backend === \"convex\") {\n        const hasIncompatibleFrontend = currentStack.webFrontend.some((f) =>\n          [\"svelte\", \"nuxt\"].includes(f),\n        );\n        if (hasIncompatibleFrontend) {\n          const frontendName = currentStack.webFrontend.find((f) => [\"svelte\", \"nuxt\"].includes(f));\n          return `Convex AI example only supports React-based frontends (not ${frontendName})`;\n        }\n      }\n    }\n  }\n\n  // ============================================\n  // DEPLOYMENT CONSTRAINTS\n  // ============================================\n  if (category === \"webDeploy\" && optionId !== \"none\") {\n    if (!currentStack.webFrontend.some((f) => f !== \"none\")) {\n      return \"Web deployment requires a web frontend\";\n    }\n  }\n\n  if (\n    category === \"webDeploy\" &&\n    currentStack.dbSetup === \"d1\" &&\n    isSelfHostedFullstackBackend(currentStack.backend) &&\n    optionId !== \"cloudflare\"\n  ) {\n    return \"D1 with a self fullstack backend requires Cloudflare web deployment\";\n  }\n\n  if (category === \"serverDeploy\") {\n    if (optionId === \"cloudflare\") {\n      if (currentStack.runtime !== \"workers\") return \"Cloudflare requires Workers runtime\";\n      if (currentStack.backend !== \"hono\") return \"Cloudflare requires Hono backend\";\n    }\n    if (optionId !== \"none\") {\n      if (\n        currentStack.backend === \"none\" ||\n        currentStack.backend === \"convex\" ||\n        isSelfHostedFullstackBackend(currentStack.backend)\n      ) {\n        return \"Server deployment not needed for this backend\";\n      }\n    }\n    if (optionId === \"none\" && currentStack.runtime === \"workers\") {\n      return \"Workers requires server deployment\";\n    }\n  }\n\n  return null;\n};\n\nexport const isOptionCompatible = (\n  currentStack: StackState,\n  category: keyof typeof TECH_OPTIONS,\n  optionId: string,\n): boolean => {\n  if (currentStack.yolo === \"true\") {\n    return true;\n  }\n  return getDisabledReason(currentStack, category, optionId) === null;\n};\n"
  },
  {
    "path": "apps/web/src/app/(home)/new/_components/yolo-toggle.tsx",
    "content": "\"use client\";\n\nimport { AlertTriangle } from \"lucide-react\";\n\nimport { Switch } from \"@/components/ui/switch\";\nimport { Tooltip, TooltipContent, TooltipTrigger } from \"@/components/ui/tooltip\";\nimport type { StackState } from \"@/lib/constant\";\nimport { cn } from \"@/lib/utils\";\n\ninterface YoloToggleProps {\n  stack: StackState;\n  onToggle: (yolo: string) => void;\n}\n\nexport function YoloToggle({ stack, onToggle }: YoloToggleProps) {\n  const isYoloEnabled = stack.yolo === \"true\";\n\n  return (\n    <Tooltip delay={100}>\n      <TooltipTrigger render={<div className=\"flex w-full items-center gap-3 p-3\" />}>\n        <AlertTriangle className=\"h-4 w-4 shrink-0\" />\n        <div className=\"flex flex-1 flex-col items-start\">\n          <div className=\"font-medium text-sm\">YOLO Mode</div>\n          <div className=\"text-muted-foreground text-xs\">\n            {isYoloEnabled ? \"Enabled\" : \"Disabled\"}\n          </div>\n        </div>\n        <Switch\n          checked={isYoloEnabled}\n          onCheckedChange={(checked) => onToggle(checked ? \"true\" : \"false\")}\n          className={cn(isYoloEnabled && \"data-[state=checked]:bg-destructive\")}\n        />\n      </TooltipTrigger>\n      <TooltipContent side=\"top\" align=\"start\" className=\"max-w-xs\">\n        <p className=\"text-xs\">\n          Disables all validation and adds --yolo flag to the command. Use at your own risk!\n        </p>\n      </TooltipContent>\n    </Tooltip>\n  );\n}\n"
  },
  {
    "path": "apps/web/src/app/(home)/new/page.tsx",
    "content": "import type { Metadata } from \"next\";\nimport { Suspense } from \"react\";\n\nimport { fetchSponsors } from \"@/lib/sponsors\";\n\nimport { StackBuilder } from \"./_components/stack-builder\";\n\nexport const metadata: Metadata = {\n  title: \"Stack Builder - Better-T-Stack\",\n  description: \"Interactive Ui to roll your own stack\",\n  openGraph: {\n    title: \"Stack Builder - Better-T-Stack\",\n    description: \"Interactive Ui to roll your own stack\",\n    url: \"https://better-t-stack.dev/new\",\n    images: [\n      {\n        url: \"https://r2.better-t-stack.dev/og.png\",\n        width: 1200,\n        height: 630,\n        alt: \"Better-T-Stack Stack Builder\",\n      },\n    ],\n  },\n  twitter: {\n    card: \"summary_large_image\",\n    title: \"Stack Builder - Better-T-Stack\",\n    description: \"Interactive Ui to roll your own stack\",\n    images: [\"https://r2.better-t-stack.dev/og.png\"],\n  },\n};\n\nexport default async function FullScreenStackBuilder() {\n  const sponsorsData = await fetchSponsors();\n\n  return (\n    <Suspense>\n      <div className=\"grid h-[calc(100vh-64px)] w-full flex-1 grid-cols-1 overflow-hidden\">\n        <StackBuilder specialSponsors={sponsorsData.specialSponsors} />\n      </div>\n    </Suspense>\n  );\n}\n"
  },
  {
    "path": "apps/web/src/app/(home)/page.tsx",
    "content": "export const dynamic = \"force-static\";\n\nimport { api } from \"@better-t-stack/backend/convex/_generated/api\";\nimport { fetchQuery } from \"convex/nextjs\";\n\nimport { fetchSponsors } from \"@/lib/sponsors\";\n\nimport CommandSection from \"./_components/command-section\";\nimport Footer from \"./_components/footer\";\nimport HeroSection from \"./_components/hero-section\";\nimport SponsorsSection from \"./_components/sponsors-section\";\nimport StatsSection from \"./_components/stats-section\";\nimport Testimonials from \"./_components/testimonials\";\n\nexport default async function HomePage() {\n  const sponsorsData = await fetchSponsors();\n  const fetchedTweets = await fetchQuery(api.testimonials.getTweets);\n  const fetchedVideos = await fetchQuery(api.testimonials.getVideos);\n  const videos = fetchedVideos.map((v) => ({\n    embedId: v.embedId,\n    title: v.title,\n  }));\n  const tweets = fetchedTweets.map((t) => ({ tweetId: t.tweetId }));\n\n  return (\n    <main className=\"container mx-auto min-h-svh\">\n      <div className=\"mx-auto flex flex-col gap-8 px-4 pt-12\">\n        <HeroSection />\n        <CommandSection />\n        <StatsSection />\n        <SponsorsSection sponsorsData={sponsorsData} />\n        <Testimonials tweets={tweets} videos={videos} />\n      </div>\n      <Footer />\n    </main>\n  );\n}\n"
  },
  {
    "path": "apps/web/src/app/(home)/showcase/_components/ShowcaseItem.tsx",
    "content": "\"use client\";\n\nimport { ExternalLink, File, Monitor } from \"lucide-react\";\nimport Image from \"next/image\";\nimport Link from \"next/link\";\nimport { FaGithub } from \"react-icons/fa6\";\n\nexport interface ShowcaseItemProps {\n  title: string;\n  description: string;\n  imageUrl: string;\n  liveUrl?: string;\n  sourceUrl?: string;\n  tags: string[];\n  index?: number;\n}\n\nexport default function ShowcaseItem({\n  title,\n  description,\n  imageUrl,\n  liveUrl,\n  sourceUrl,\n  tags,\n  index = 0,\n}: ShowcaseItemProps) {\n  const projectId = `PROJECT_${String(index + 1).padStart(3, \"0\")}`;\n\n  return (\n    <div className=\"group flex h-full flex-col overflow-hidden rounded-xl bg-fd-background/85 ring-1 ring-border/35 transition-all duration-200 hover:-translate-y-0.5 hover:ring-primary/35\">\n      <div className=\"px-4 py-3\">\n        <div className=\"flex items-center gap-2\">\n          <File className=\"h-3 w-3 text-primary\" />\n          <span className=\"font-semibold font-mono text-foreground text-xs\">\n            {projectId}.PROJECT\n          </span>\n          <div className=\"ml-auto flex items-center gap-2 text-muted-foreground text-xs\">\n            <span>•</span>\n            <span className=\"font-mono\">{tags.length} DEPS</span>\n          </div>\n        </div>\n      </div>\n\n      <div className=\"relative aspect-video w-full overflow-hidden bg-muted/10\">\n        <Image\n          src={imageUrl}\n          alt={title}\n          fill\n          className=\"object-cover transition-all duration-300 ease-in-out group-hover:scale-[1.03]\"\n          unoptimized\n        />\n        <div className=\"absolute inset-0 bg-gradient-to-t from-background/45 via-transparent to-transparent\" />\n      </div>\n\n      <div className=\"flex flex-1 flex-col p-4 pt-4\">\n        <h3 className=\"mb-2 font-bold text-lg text-primary\">{title}</h3>\n\n        <p className=\"mb-4 flex-grow text-muted-foreground text-sm leading-relaxed\">\n          {description}\n        </p>\n\n        <div className=\"mb-4\">\n          <div className=\"mb-2 flex items-center gap-2\">\n            <span className=\"text-muted-foreground text-xs\">DEPENDENCIES:</span>\n          </div>\n          <div className=\"flex flex-wrap gap-1.5\">\n            {tags.map((tag) => (\n              <span\n                key={tag}\n                className=\"rounded-md bg-muted/30 px-2 py-1 text-foreground text-xs transition-colors hover:bg-muted/45\"\n              >\n                {tag}\n              </span>\n            ))}\n          </div>\n        </div>\n\n        <div className=\"mt-auto grid gap-2\">\n          {liveUrl && (\n            <Link\n              href={liveUrl}\n              target=\"_blank\"\n              rel=\"noopener noreferrer\"\n              className=\"flex items-center gap-2 rounded-md bg-primary/12 px-3 py-2 text-primary text-sm transition-colors hover:bg-primary/20\"\n            >\n              <Monitor className=\"h-3 w-3\" />\n              <span>LAUNCH_DEMO.SH</span>\n              <ExternalLink className=\"ml-auto h-3 w-3\" />\n            </Link>\n          )}\n          {sourceUrl && (\n            <Link\n              href={sourceUrl}\n              target=\"_blank\"\n              rel=\"noopener noreferrer\"\n              className=\"flex items-center gap-2 rounded-md bg-muted/30 px-3 py-2 text-muted-foreground text-sm transition-colors hover:bg-muted/45 hover:text-foreground\"\n            >\n              <FaGithub className=\"h-3 w-3\" />\n              <span>VIEW_SOURCE.GIT</span>\n              <ExternalLink className=\"ml-auto h-3 w-3\" />\n            </Link>\n          )}\n        </div>\n      </div>\n    </div>\n  );\n}\n"
  },
  {
    "path": "apps/web/src/app/(home)/showcase/_components/showcase-page.tsx",
    "content": "\"use client\";\n\nimport { Terminal } from \"lucide-react\";\n\nimport Footer from \"../../_components/footer\";\nimport ShowcaseItem from \"../_components/ShowcaseItem\";\n\ntype ShowcaseProject = {\n  _id: string;\n  _creationTime: number;\n  title: string;\n  description: string;\n  imageUrl: string;\n  liveUrl: string;\n  tags: string[];\n};\n\nexport function ShowcasePage({ showcaseProjects }: { showcaseProjects: Array<ShowcaseProject> }) {\n  return (\n    <main className=\"min-h-svh bg-fd-background\">\n      <div className=\"container mx-auto space-y-8 px-4 py-8 pt-16\">\n        <div className=\"mb-4 space-y-4\">\n          <div className=\"flex flex-wrap items-end justify-between gap-4\">\n            <div className=\"flex flex-col gap-1\">\n              <div className=\"flex items-center gap-2 text-primary\">\n                <Terminal className=\"h-5 w-5\" />\n                <h1 className=\"font-bold font-mono text-xl sm:text-2xl\">PROJECT_SHOWCASE.SH</h1>\n              </div>\n              <p className=\"text-muted-foreground text-sm\">\n                Community projects built with create-better-t-stack\n              </p>\n            </div>\n          </div>\n        </div>\n\n        {showcaseProjects.length === 0 ? (\n          <div className=\"rounded-xl bg-fd-background/80 p-8 text-center ring-1 ring-border/35\">\n            <div className=\"mb-4 flex items-center justify-center gap-2\">\n              <span className=\"font-mono text-muted-foreground\">\n                NO_SHOWCASE_PROJECTS_FOUND.NULL\n              </span>\n            </div>\n            <div className=\"flex items-center justify-center gap-2 text-sm\">\n              <span className=\"text-primary\">$</span>\n              <span className=\"text-muted-foreground\">Be the first to showcase your project!</span>\n            </div>\n          </div>\n        ) : (\n          <div className=\"grid grid-cols-1 gap-6 md:grid-cols-2 xl:grid-cols-3\">\n            {showcaseProjects.map((project, index) => (\n              <ShowcaseItem key={project._id} {...project} index={index} />\n            ))}\n          </div>\n        )}\n\n        <div className=\"rounded-xl bg-fd-background/80 p-4 ring-1 ring-border/35\">\n          <div className=\"flex items-center gap-2 text-sm\">\n            <span className=\"text-primary\">$</span>\n            <span className=\"text-muted-foreground\">\n              Want to showcase your project? Submit via{\" \"}\n              <a\n                href=\"https://github.com/AmanVarshney01/create-better-t-stack/issues/new/choose\"\n                target=\"_blank\"\n                rel=\"noreferrer\"\n                className=\"underline decoration-border underline-offset-4 transition-colors hover:text-foreground\"\n              >\n                GitHub issues\n              </a>\n            </span>\n          </div>\n        </div>\n      </div>\n      <Footer />\n    </main>\n  );\n}\n"
  },
  {
    "path": "apps/web/src/app/(home)/showcase/page.tsx",
    "content": "export const dynamic = \"force-static\";\n\nimport { api } from \"@better-t-stack/backend/convex/_generated/api\";\nimport { fetchQuery } from \"convex/nextjs\";\nimport type { Metadata } from \"next\";\n\nimport { ShowcasePage } from \"./_components/showcase-page\";\n\nexport const metadata: Metadata = {\n  title: \"Showcase - Better-T-Stack\",\n  description: \"Projects created with Better-T-Stack\",\n  openGraph: {\n    title: \"Showcase - Better-T-Stack\",\n    description: \"Projects created with Better-T-Stack\",\n    url: \"https://better-t-stack.dev/showcase\",\n    images: [\n      {\n        url: \"https://r2.better-t-stack.dev/og.png\",\n        width: 1200,\n        height: 630,\n        alt: \"Better-T-Stack Showcase\",\n      },\n    ],\n  },\n  twitter: {\n    card: \"summary_large_image\",\n    title: \"Showcase - Better-T-Stack\",\n    description: \"Projects created with Better-T-Stack\",\n    images: [\"https://r2.better-t-stack.dev/og.png\"],\n  },\n};\n\nexport default async function Showcase() {\n  const showcaseProjects = await fetchQuery(api.showcase.getShowcaseProjects);\n  return <ShowcasePage showcaseProjects={showcaseProjects} />;\n}\n"
  },
  {
    "path": "apps/web/src/app/(home)/stack/_components/stack-display.tsx",
    "content": "\"use client\";\n\nimport { Check, Copy, Edit, Share2, Terminal } from \"lucide-react\";\nimport Link from \"next/link\";\nimport { useEffect, useState } from \"react\";\nimport { toast } from \"sonner\";\n\nimport { ShareDialog } from \"@/components/ui/share-dialog\";\nimport { TechBadge } from \"@/components/ui/tech-badge\";\nimport { type StackState, TECH_OPTIONS } from \"@/lib/constant\";\nimport type { LoadedStackState } from \"@/lib/stack-url-state\";\nimport {\n  CATEGORY_ORDER,\n  generateStackCommand,\n  generateStackSharingUrl,\n  generateStackSummary,\n  generateStackUrlFromState,\n} from \"@/lib/stack-utils\";\n\ntype StackDisplayProps = {\n  stackState: LoadedStackState;\n};\n\nexport function StackDisplay({ stackState }: StackDisplayProps) {\n  const [copied, setCopied] = useState(false);\n  const [stackUrl, setStackUrl] = useState<string>(\"\");\n  const [editUrl, setEditUrl] = useState<string>(\"\");\n\n  useEffect(() => {\n    if (typeof window !== \"undefined\") {\n      setStackUrl(generateStackSharingUrl(stackState, window.location.origin));\n      setEditUrl(generateStackUrlFromState(stackState, window.location.origin));\n    }\n  }, [stackState]);\n\n  const stack = stackState;\n  const stackSummary = generateStackSummary(stack);\n\n  const command = generateStackCommand(stackState);\n\n  const techBadges = (() => {\n    const badges: React.ReactNode[] = [];\n    for (const category of CATEGORY_ORDER) {\n      const categoryKey = category as keyof StackState;\n      const options = TECH_OPTIONS[category as keyof typeof TECH_OPTIONS];\n      const selectedValue = stack[categoryKey];\n\n      if (!options) continue;\n\n      if (Array.isArray(selectedValue)) {\n        if (\n          selectedValue.length === 0 ||\n          (selectedValue.length === 1 && selectedValue[0] === \"none\")\n        ) {\n          continue;\n        }\n\n        for (const id of selectedValue) {\n          if (id === \"none\") continue;\n          const tech = options.find((opt) => opt.id === id);\n          if (tech) {\n            badges.push(\n              <TechBadge\n                key={`${category}-${tech.id}`}\n                icon={tech.icon}\n                name={tech.name}\n                category={category}\n              />,\n            );\n          }\n        }\n      } else {\n        const tech = options.find((opt) => opt.id === selectedValue);\n        if (\n          !tech ||\n          tech.id === \"none\" ||\n          tech.id === \"false\" ||\n          ((category === \"git\" || category === \"install\" || category === \"auth\") &&\n            tech.id === \"true\")\n        ) {\n          continue;\n        }\n        badges.push(\n          <TechBadge\n            key={`${category}-${tech.id}`}\n            icon={tech.icon}\n            name={tech.name}\n            category={category}\n          />,\n        );\n      }\n    }\n    return badges;\n  })();\n\n  const copyCommand = async () => {\n    try {\n      await navigator.clipboard.writeText(command);\n      setCopied(true);\n      toast.success(\"Command copied to clipboard!\");\n      setTimeout(() => setCopied(false), 2000);\n    } catch {\n      toast.error(\"Failed to copy command\");\n    }\n  };\n\n  return (\n    <main className=\"container mx-auto min-h-svh\">\n      <div className=\"mx-auto flex flex-col gap-8 px-4 pt-12\">\n        <div className=\"mb-6 flex flex-wrap items-center justify-between gap-2 sm:flex-nowrap\">\n          <div className=\"flex items-center gap-2\">\n            <Terminal className=\"h-5 w-5 text-primary\" />\n            <span className=\"font-bold font-mono text-lg sm:text-xl\">STACK_DISPLAY.SH</span>\n          </div>\n          <div className=\"hidden h-px flex-1 bg-border sm:block\" />\n          <span className=\"w-full text-right text-muted-foreground text-xs sm:w-auto sm:text-left\">\n            [{techBadges.length} DEPENDENCIES]\n          </span>\n        </div>\n\n        <div className=\"space-y-2 rounded border border-border bg-fd-background p-4\">\n          <div className=\"flex items-center gap-2 text-sm\">\n            <span className=\"text-primary\">$</span>\n            <span className=\"text-foreground\">./display_stack --summary</span>\n          </div>\n          <div className=\"flex items-center gap-2 text-sm\">\n            <span className=\"text-primary\">&gt;</span>\n            <span className=\"text-muted-foreground\">{stackSummary}</span>\n          </div>\n          <div className=\"flex items-center gap-2 text-sm\">\n            <span className=\"text-primary\">$</span>\n            <span className=\"text-muted-foreground\">Stack loaded successfully</span>\n          </div>\n        </div>\n\n        <div className=\"flex items-center gap-3\">\n          <Link href={editUrl}>\n            <button\n              type=\"button\"\n              className=\"flex items-center gap-2 rounded border border-border bg-fd-background px-3 py-2 font-mono text-muted-foreground text-xs transition-all hover:border-muted-foreground/30 hover:bg-muted hover:text-foreground\"\n            >\n              <Edit className=\"h-3 w-3\" />\n              <span>./edit --stack</span>\n            </button>\n          </Link>\n\n          <ShareDialog stackUrl={stackUrl} stackState={stackState}>\n            <button\n              type=\"button\"\n              className=\"flex items-center gap-2 rounded border border-border bg-fd-background px-3 py-2 font-mono text-muted-foreground text-xs transition-all hover:border-muted-foreground/30 hover:bg-muted hover:text-foreground\"\n            >\n              <Share2 className=\"h-3 w-3\" />\n              <span>./share --config</span>\n            </button>\n          </ShareDialog>\n        </div>\n\n        <div className=\"space-y-4\">\n          <div className=\"flex items-center gap-2\">\n            <span className=\"text-primary text-xs\">▶</span>\n            <span className=\"font-mono font-semibold text-foreground text-sm\">\n              GENERATE_COMMAND\n            </span>\n          </div>\n\n          <div\n            role=\"button\"\n            tabIndex={0}\n            className=\"flex cursor-pointer items-center justify-between rounded border border-border bg-fd-background p-3\"\n            onClick={copyCommand}\n            onKeyDown={(event) => {\n              if (event.key === \"Enter\" || event.key === \" \") {\n                event.preventDefault();\n                copyCommand();\n              }\n            }}\n            aria-label=\"Copy generated command\"\n            title=\"Click to copy command\"\n          >\n            <div className=\"flex items-center gap-2 font-mono text-sm\">\n              <span className=\"text-primary\">$</span>\n              <span className=\"text-foreground\">{command}</span>\n            </div>\n            <span\n              className={\n                copied\n                  ? \"flex items-center gap-1 rounded border border-green-500/20 bg-green-500/10 px-2 py-1 font-mono text-green-600 text-xs transition-colors dark:text-green-400\"\n                  : \"flex items-center gap-1 rounded border border-border px-2 py-1 font-mono text-xs transition-colors\"\n              }\n            >\n              {copied ? <Check className=\"h-3 w-3\" /> : <Copy className=\"h-3 w-3\" />}\n              {copied ? \"COPIED!\" : \"COPY\"}\n            </span>\n          </div>\n        </div>\n\n        <div className=\"space-y-4\">\n          <div className=\"flex items-center gap-2\">\n            <span className=\"text-primary text-xs\">▶</span>\n            <span className=\"font-mono font-semibold text-foreground text-sm\">\n              DEPENDENCIES ({techBadges.length})\n            </span>\n          </div>\n\n          {techBadges.length > 0 ? (\n            <div className=\"flex flex-wrap gap-3\">{techBadges}</div>\n          ) : (\n            <div className=\"flex items-center gap-2 text-muted-foreground\">\n              <span className=\"text-primary\">$</span>\n              <span>No technologies selected</span>\n            </div>\n          )}\n        </div>\n      </div>\n    </main>\n  );\n}\n"
  },
  {
    "path": "apps/web/src/app/(home)/stack/page.tsx",
    "content": "import type { Metadata } from \"next\";\nimport { Suspense } from \"react\";\n\nimport { loadStackParams, serializeStackParams } from \"@/lib/stack-url-state\";\n\nimport { StackDisplay } from \"./_components/stack-display\";\n\ninterface StackPageProps {\n  searchParams: Promise<{ [key: string]: string | string[] | undefined }>;\n}\n\nexport async function generateMetadata({ searchParams }: StackPageProps): Promise<Metadata> {\n  const params = await loadStackParams(searchParams);\n  const projectName = params.projectName || \"my-better-t-app\";\n  const title = `${projectName} – Better-T-Stack`;\n  return {\n    title,\n    description: \"View and share your custom tech stack configuration\",\n    alternates: {\n      canonical: serializeStackParams(\"/stack\", params),\n    },\n    openGraph: {\n      title,\n      description: \"View and share your custom tech stack configuration\",\n      url: \"https://better-t-stack.dev/stack\",\n      images: [\n        {\n          url: \"https://r2.better-t-stack.dev/og.png\",\n          width: 1200,\n          height: 630,\n          alt: \"Better-T-Stack Tech Stack\",\n        },\n      ],\n    },\n    twitter: {\n      card: \"summary_large_image\",\n      title,\n      description: \"View and share your custom tech stack configuration\",\n      images: [\"https://r2.better-t-stack.dev/og.png\"],\n    },\n  };\n}\n\nexport default async function StackPage({ searchParams }: StackPageProps) {\n  const stackState = await loadStackParams(searchParams);\n\n  return (\n    <Suspense>\n      <StackDisplay stackState={stackState} />\n    </Suspense>\n  );\n}\n"
  },
  {
    "path": "apps/web/src/app/api/preview/route.ts",
    "content": "import { generate, type VirtualNode } from \"@better-t-stack/template-generator\";\nimport { EMBEDDED_TEMPLATES } from \"@better-t-stack/template-generator\";\nimport type { ProjectConfig } from \"@better-t-stack/types\";\nimport { NextResponse } from \"next/server\";\n\nimport type { StackState } from \"@/lib/constant\";\nimport { sanitizeStackState } from \"@/lib/sanitize-stack-addons\";\n\nexport async function POST(request: Request) {\n  try {\n    const body = sanitizeStackState((await request.json()) as StackState);\n\n    // Convert StackState from web to CLI options format\n    const config = stackStateToConfig(body);\n\n    // Generate project to virtual filesystem using Result-based API\n    const result = await generate({\n      config,\n      templates: EMBEDDED_TEMPLATES,\n    });\n\n    // Handle Result type\n    if (result.isErr()) {\n      throw new Error(result.error.message);\n    }\n\n    const tree = result.value;\n\n    // Transform VirtualFileTree to web's expected format\n    const transformedRoot = transformTree(tree.root);\n\n    return NextResponse.json({\n      success: true,\n      tree: {\n        root: transformedRoot,\n        fileCount: tree.fileCount,\n        directoryCount: tree.directoryCount,\n      },\n    });\n  } catch (error) {\n    console.error(\"Preview generation error:\", error);\n    return NextResponse.json(\n      {\n        success: false,\n        error: error instanceof Error ? error.message : \"Unknown error\",\n      },\n      { status: 500 },\n    );\n  }\n}\n\n/**\n * Transform VirtualFileTree format to web's expected tree format\n */\nfunction transformTree(node: VirtualNode): Record<string, unknown> {\n  if (node.type === \"file\") {\n    return {\n      name: node.name,\n      path: node.path,\n      type: \"file\" as const,\n      content: node.content,\n      extension: node.extension,\n    };\n  }\n\n  return {\n    name: node.name,\n    path: node.path,\n    type: \"directory\" as const,\n    children: node.children.map(transformTree),\n  };\n}\n\nfunction normalizeBoolean(value: boolean | string | undefined, fallback: boolean): boolean {\n  if (typeof value === \"boolean\") return value;\n  if (typeof value === \"string\") return value === \"true\";\n  return fallback;\n}\n\nfunction normalizeBackend(value?: string): ProjectConfig[\"backend\"] {\n  if (!value) return \"hono\";\n  if (value.startsWith(\"self-\")) return \"self\";\n  return value as ProjectConfig[\"backend\"];\n}\n\nfunction stackStateToConfig(state: StackState): ProjectConfig {\n  // Convert web StackState format to ProjectConfig format\n  const webFrontend = state.webFrontend || [];\n  const nativeFrontend = state.nativeFrontend || [];\n\n  // Combine frontends, filtering out \"none\"\n  const frontend = [\n    ...webFrontend.filter((f) => f !== \"none\"),\n    ...nativeFrontend.filter((f) => f !== \"none\"),\n  ] as ProjectConfig[\"frontend\"];\n\n  const backend = normalizeBackend(state.backend);\n\n  const git = normalizeBoolean(state.git, false);\n\n  return {\n    projectName: state.projectName || \"my-better-t-app\",\n    projectDir: \"/virtual\",\n    relativePath: \"./virtual\",\n    database: (state.database || \"none\") as ProjectConfig[\"database\"],\n    orm: (state.orm || \"none\") as ProjectConfig[\"orm\"],\n    backend,\n    runtime: (state.runtime || \"bun\") as ProjectConfig[\"runtime\"],\n    frontend: frontend.length > 0 ? frontend : [\"none\"],\n    addons: (state.addons || []).filter((a) => a !== \"none\") as ProjectConfig[\"addons\"],\n    examples: (state.examples || []).filter((e) => e !== \"none\") as ProjectConfig[\"examples\"],\n    auth: (state.auth || \"none\") as ProjectConfig[\"auth\"],\n    payments: (state.payments || \"none\") as ProjectConfig[\"payments\"],\n    git,\n    packageManager: (state.packageManager || \"bun\") as ProjectConfig[\"packageManager\"],\n    install: false,\n    dbSetup: (state.dbSetup || \"none\") as ProjectConfig[\"dbSetup\"],\n    api: (state.api || \"trpc\") as ProjectConfig[\"api\"],\n    webDeploy: (state.webDeploy || \"none\") as ProjectConfig[\"webDeploy\"],\n    serverDeploy: (state.serverDeploy || \"none\") as ProjectConfig[\"serverDeploy\"],\n  };\n}\n"
  },
  {
    "path": "apps/web/src/app/api/search/route.ts",
    "content": "import { createFromSource } from \"fumadocs-core/search/server\";\n\nimport { source } from \"@/lib/source\";\n\nexport const revalidate = false;\n\nexport const { staticGET: GET } = createFromSource(source);\n"
  },
  {
    "path": "apps/web/src/app/docs/[[...slug]]/page.tsx",
    "content": "import * as FilesComponents from \"fumadocs-ui/components/files\";\nimport * as TabsComponents from \"fumadocs-ui/components/tabs\";\nimport { DocsBody, DocsDescription, DocsPage, DocsTitle } from \"fumadocs-ui/layouts/notebook/page\";\nimport defaultMdxComponents from \"fumadocs-ui/mdx\";\nimport type { Metadata } from \"next\";\nimport Link from \"next/link\";\nimport { notFound } from \"next/navigation\";\n\nimport { LLMCopyButton, ViewOptions } from \"@/components/ai/page-actions\";\nimport { getPageImage, source } from \"@/lib/source\";\n\nexport default async function Page(props: PageProps<\"/docs/[[...slug]]\">) {\n  const params = await props.params;\n  const page = source.getPage(params.slug);\n  if (!page) notFound();\n\n  const MDX = page.data.body;\n\n  return (\n    <DocsPage toc={page.data.toc} tableOfContent={{ style: \"clerk\" }} full={page.data.full}>\n      <DocsTitle>{page.data.title}</DocsTitle>\n      <DocsDescription>{page.data.description}</DocsDescription>\n      {page.data.author && (\n        <div className=\"flex items-center gap-2 text-sm text-muted-foreground\">\n          <span>by</span>\n          {page.data.author.url ? (\n            <Link\n              href={page.data.author.url}\n              target=\"_blank\"\n              rel=\"noopener noreferrer\"\n              className=\"font-medium text-fd-primary hover:underline\"\n            >\n              {page.data.author.name}\n            </Link>\n          ) : (\n            <span className=\"font-medium\">{page.data.author.name}</span>\n          )}\n          {page.data.date && (\n            <>\n              <span>·</span>\n              <time dateTime={page.data.date}>{page.data.date}</time>\n            </>\n          )}\n        </div>\n      )}\n      <div className=\"flex flex-row items-center gap-2 border-b pt-2 pb-6\">\n        <LLMCopyButton markdownUrl={`${page.url}.mdx`} />\n        <ViewOptions\n          markdownUrl={`${page.url}.mdx`}\n          githubUrl={`https://github.com/AmanVarshney01/create-better-t-stack/blob/main/apps/web/content/docs/${page.path}`}\n        />\n      </div>\n      <DocsBody>\n        <MDX components={{ ...defaultMdxComponents, ...TabsComponents, ...FilesComponents }} />\n      </DocsBody>\n    </DocsPage>\n  );\n}\n\nexport async function generateStaticParams() {\n  return source.generateParams();\n}\n\nexport async function generateMetadata({\n  params,\n}: PageProps<\"/docs/[[...slug]]\">): Promise<Metadata> {\n  const { slug = [] } = await params;\n  const page = source.getPage(slug);\n  if (!page) notFound();\n\n  const image = getPageImage(page);\n\n  return {\n    title: page.data.title,\n    description: page.data.description,\n    openGraph: {\n      title: page.data.title,\n      description: page.data.description,\n      type: \"article\",\n      images: image.url,\n    },\n    twitter: {\n      card: \"summary_large_image\",\n      title: page.data.title,\n      description: page.data.description,\n      images: image.url,\n    },\n  };\n}\n"
  },
  {
    "path": "apps/web/src/app/docs/layout.tsx",
    "content": "import { DocsLayout, type DocsLayoutProps } from \"fumadocs-ui/layouts/notebook\";\nimport type { ReactNode } from \"react\";\n\nimport { baseOptions } from \"@/app/layout.config\";\nimport { SpecialSponsorBanner } from \"@/components/special-sponsor-banner\";\nimport { source } from \"@/lib/source\";\n\nconst docsOptions: DocsLayoutProps = {\n  ...baseOptions,\n  tree: source.pageTree,\n  // links: [],\n  sidebar: {\n    banner: <SpecialSponsorBanner />,\n  },\n};\n\nexport default function Layout({ children }: { children: ReactNode }) {\n  return (\n    <DocsLayout {...docsOptions} nav={{ ...baseOptions.nav, mode: \"top\" }}>\n      {children}\n    </DocsLayout>\n  );\n}\n"
  },
  {
    "path": "apps/web/src/app/global.css",
    "content": "@import \"tailwindcss\";\n@import \"fumadocs-ui/css/neutral.css\";\n@import \"fumadocs-ui/css/preset.css\";\n@import \"tw-animate-css\";\n\n@custom-variant dark (&:where(.dark, .dark *));\n\n.react-tweet-theme {\n  --tweet-container-margin: 0 !important;\n  @apply bg-fd-background! border-none! h-full! border-transparent! w-full!;\n  max-width: 100% !important;\n  min-width: 0 !important;\n}\n\n.react-tweet-theme > * {\n  max-width: 100% !important;\n  width: 100% !important;\n  min-width: 0 !important;\n}\n\n.react-tweet-theme img,\n.react-tweet-theme video {\n  max-width: 100% !important;\n  height: auto !important;\n}\n\n.react-tweet-theme * {\n  word-wrap: break-word !important;\n  word-break: break-word !important;\n  overflow-wrap: break-word !important;\n}\n\n.shiny-text {\n  background: linear-gradient(\n    120deg,\n    rgba(255, 255, 255, 0) 40%,\n    rgba(255, 255, 255, 0.8) 50%,\n    rgba(255, 255, 255, 0) 60%\n  );\n  background-size: 200% 100%;\n  -webkit-background-clip: text;\n  background-clip: text;\n  display: inline-block;\n  animation: shine 5s linear infinite;\n}\n\n@keyframes shine {\n  0% {\n    background-position: 100%;\n  }\n\n  100% {\n    background-position: -100%;\n  }\n}\n\n.shiny-text.disabled {\n  animation: none;\n}\n\n@keyframes fadeInUp {\n  from {\n    opacity: 0;\n    transform: translateY(20px);\n  }\n  to {\n    opacity: 1;\n    transform: translateY(0);\n  }\n}\n\n.animate-fadeIn {\n  opacity: 0;\n  animation: fadeInUp 0.5s ease-out forwards;\n}\n\n.border-beam {\n  animation: border-beam 3s linear infinite;\n}\n\n@keyframes border-beam {\n  0% {\n    background-position: 0% 50%;\n  }\n  100% {\n    background-position: 200% 50%;\n  }\n}\n\n.no-scrollbar::-webkit-scrollbar {\n  display: none;\n}\n.no-scrollbar {\n  -ms-overflow-style: none;\n  scrollbar-width: none;\n}\n\n@theme inline {\n  --font-sans: var(--font-geist);\n  --font-mono: var(--font-geist-mono);\n  --radius-sm: calc(var(--radius) - 4px);\n  --radius-md: calc(var(--radius) - 2px);\n  --radius-lg: var(--radius);\n  --radius-xl: calc(var(--radius) + 4px);\n  --color-background: var(--background);\n  --color-foreground: var(--foreground);\n  --color-card: var(--card);\n  --color-card-foreground: var(--card-foreground);\n  --color-popover: var(--popover);\n  --color-popover-foreground: var(--popover-foreground);\n  --color-primary: var(--primary);\n  --color-primary-foreground: var(--primary-foreground);\n  --color-secondary: var(--secondary);\n  --color-secondary-foreground: var(--secondary-foreground);\n  --color-muted: var(--muted);\n  --color-muted-foreground: var(--muted-foreground);\n  --color-accent: var(--accent);\n  --color-accent-foreground: var(--accent-foreground);\n  --color-destructive: var(--destructive);\n  --color-border: var(--border);\n  --color-input: var(--input);\n  --color-ring: var(--ring);\n  --color-chart-1: var(--chart-1);\n  --color-chart-2: var(--chart-2);\n  --color-chart-3: var(--chart-3);\n  --color-chart-4: var(--chart-4);\n  --color-chart-5: var(--chart-5);\n  --color-sidebar: var(--sidebar);\n  --color-sidebar-foreground: var(--sidebar-foreground);\n  --color-sidebar-primary: var(--sidebar-primary);\n  --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);\n  --color-sidebar-accent: var(--sidebar-accent);\n  --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);\n  --color-sidebar-border: var(--sidebar-border);\n  --color-sidebar-ring: var(--sidebar-ring);\n  --radius: 0.35rem;\n  --tracking-tighter: calc(var(--tracking-normal) - 0.05em);\n  --tracking-tight: calc(var(--tracking-normal) - 0.025em);\n  --tracking-wide: calc(var(--tracking-normal) + 0.025em);\n  --tracking-wider: calc(var(--tracking-normal) + 0.05em);\n  --tracking-widest: calc(var(--tracking-normal) + 0.1em);\n  --tracking-normal: var(--tracking-normal);\n  --shadow-2xl: var(--shadow-2xl);\n  --shadow-xl: var(--shadow-xl);\n  --shadow-lg: var(--shadow-lg);\n  --shadow-md: var(--shadow-md);\n  --shadow: var(--shadow);\n  --shadow-sm: var(--shadow-sm);\n  --shadow-xs: var(--shadow-xs);\n  --shadow-2xs: var(--shadow-2xs);\n  --spacing: var(--spacing);\n  --letter-spacing: var(--letter-spacing);\n  --shadow-offset-y: var(--shadow-offset-y);\n  --shadow-offset-x: var(--shadow-offset-x);\n  --shadow-spread: var(--shadow-spread);\n  --shadow-blur: var(--shadow-blur);\n  --shadow-opacity: var(--shadow-opacity);\n  --color-shadow-color: var(--shadow-color);\n  --color-destructive-foreground: var(--destructive-foreground);\n}\n\n:root {\n  --color-fd-primary: #8839ef;\n  --color-fd-primary-foreground: #ffffff;\n  --background: oklch(1 0 0);\n  --foreground: oklch(0.44 0.04 279.33);\n  --card: oklch(1 0 0);\n  --card-foreground: oklch(0.44 0.04 279.33);\n  --popover: oklch(0.86 0.01 268.48);\n  --popover-foreground: oklch(0.44 0.04 279.33);\n  --primary: #8839ef;\n  --primary-foreground: #ffffff;\n  --secondary: oklch(0.86 0.01 268.48);\n  --secondary-foreground: oklch(0.44 0.04 279.33);\n  --muted: oklch(0.91 0.01 264.51);\n  --muted-foreground: oklch(0.55 0.03 279.08);\n  --accent: #9353d3;\n  --accent-foreground: #ffffff;\n  --destructive: #d20f39;\n  --border: oklch(0.81 0.02 271.2);\n  --input: oklch(0.86 0.01 268.48);\n  --ring: #8839ef;\n  --chart-1: oklch(0.646 0.222 41.116);\n  --chart-2: oklch(0.6 0.118 184.704);\n  --chart-3: oklch(0.398 0.07 227.392);\n  --chart-4: oklch(0.828 0.189 84.429);\n  --chart-5: oklch(0.769 0.188 70.08);\n  --sidebar: oklch(0.93 0.01 264.52);\n  --sidebar-foreground: oklch(0.44 0.04 279.33);\n  --sidebar-primary: #8839ef;\n  --sidebar-primary-foreground: #ffffff;\n  --sidebar-accent: #9353d3;\n  --sidebar-accent-foreground: #ffffff;\n  --sidebar-border: oklch(0.81 0.02 271.2);\n  --sidebar-ring: #8839ef;\n  --destructive-foreground: oklch(1 0 0);\n  --radius: 0.35rem;\n  --shadow-color: hsl(240 30% 25%);\n  --shadow-opacity: 0.12;\n  --shadow-blur: 6px;\n  --shadow-spread: 0px;\n  --shadow-offset-x: 0px;\n  --shadow-offset-y: 4px;\n  --letter-spacing: 0em;\n  --spacing: 0.25rem;\n  --shadow-2xs: 0px 4px 6px 0px hsl(240 30% 25% / 0.06);\n  --shadow-xs: 0px 4px 6px 0px hsl(240 30% 25% / 0.06);\n  --shadow-sm: 0px 4px 6px 0px hsl(240 30% 25% / 0.12), 0px 1px 2px -1px hsl(240 30% 25% / 0.12);\n  --shadow: 0px 4px 6px 0px hsl(240 30% 25% / 0.12), 0px 1px 2px -1px hsl(240 30% 25% / 0.12);\n  --shadow-md: 0px 4px 6px 0px hsl(240 30% 25% / 0.12), 0px 2px 4px -1px hsl(240 30% 25% / 0.12);\n  --shadow-lg: 0px 4px 6px 0px hsl(240 30% 25% / 0.12), 0px 4px 6px -1px hsl(240 30% 25% / 0.12);\n  --shadow-xl: 0px 4px 6px 0px hsl(240 30% 25% / 0.12), 0px 8px 10px -1px hsl(240 30% 25% / 0.12);\n  --shadow-2xl: 0px 4px 6px 0px hsl(240 30% 25% / 0.3);\n  --tracking-normal: 0em;\n}\n\n.dark {\n  --color-fd-primary: #cba6f7;\n  --color-fd-primary-foreground: #11111b;\n  --color-fd-background: #0d0d0d;\n  --background: #11111b;\n  --foreground: #cdd6f4;\n  --card: #11111b;\n  --card-foreground: #cdd6f4;\n  --popover: #181825;\n  --popover-foreground: #cdd6f4;\n  --primary: #cba6f7;\n  --primary-foreground: #11111b;\n  --secondary: #313244;\n  --secondary-foreground: #cdd6f4;\n  --muted: #313244;\n  --muted-foreground: #a6adc8;\n  --accent: #b4befe;\n  --accent-foreground: #11111b;\n  --destructive: #f38ba8;\n  --border: #45475a;\n  --input: #313244;\n  --ring: #cba6f7;\n  --chart-1: oklch(0.488 0.243 264.376);\n  --chart-2: oklch(0.696 0.17 162.48);\n  --chart-3: oklch(0.769 0.188 70.08);\n  --chart-4: oklch(0.627 0.265 303.9);\n  --chart-5: oklch(0.645 0.246 16.439);\n  --sidebar: #11111b;\n  --sidebar-foreground: #cdd6f4;\n  --sidebar-primary: #cba6f7;\n  --sidebar-primary-foreground: #11111b;\n  --sidebar-accent: #b4befe;\n  --sidebar-accent-foreground: #11111b;\n  --sidebar-border: #45475a;\n  --sidebar-ring: #cba6f7;\n  --destructive-foreground: #11111b;\n  --radius: 0.35rem;\n  --shadow-color: hsl(240 30% 5%);\n  --shadow-opacity: 0.25;\n  --shadow-blur: 8px;\n  --shadow-spread: 0px;\n  --shadow-offset-x: 0px;\n  --shadow-offset-y: 4px;\n  --letter-spacing: 0em;\n  --spacing: 0.25rem;\n  --shadow-2xs: 0px 2px 4px 0px hsl(240 30% 5% / 0.15);\n  --shadow-xs: 0px 2px 4px 0px hsl(240 30% 5% / 0.15);\n  --shadow-sm: 0px 4px 8px 0px hsl(240 30% 5% / 0.2), 0px 1px 2px -1px hsl(240 30% 5% / 0.15);\n  --shadow: 0px 4px 8px 0px hsl(240 30% 5% / 0.2), 0px 1px 2px -1px hsl(240 30% 5% / 0.15);\n  --shadow-md: 0px 6px 12px 0px hsl(240 30% 5% / 0.25), 0px 2px 4px -1px hsl(240 30% 5% / 0.2);\n  --shadow-lg: 0px 8px 16px 0px hsl(240 30% 5% / 0.3), 0px 4px 6px -1px hsl(240 30% 5% / 0.25);\n  --shadow-xl: 0px 12px 24px 0px hsl(240 30% 5% / 0.35), 0px 8px 10px -1px hsl(240 30% 5% / 0.3);\n  --shadow-2xl: 0px 16px 32px 0px hsl(240 30% 5% / 0.4);\n}\n\n/* @layer base {\n\t* {\n\t\t@apply border-border outline-ring/50;\n\t}\n\tbody {\n\t\t@apply text-foreground;\n\t\tletter-spacing: var(--tracking-normal);\n\t}\n} */\n\n.terminal-cursor {\n  animation: blink 1s infinite;\n  border-right: 2px solid currentColor;\n  padding-right: 2px;\n}\n\n.builder-surface {\n  background: color-mix(in srgb, var(--background) 85%, transparent);\n  border: 1px solid var(--border);\n}\n\n.builder-focus-ring:focus-visible {\n  outline: 2px solid var(--primary);\n  outline-offset: 2px;\n}\n\n@keyframes blink {\n  0%,\n  50% {\n    border-color: transparent;\n  }\n  51%,\n  100% {\n    border-color: currentColor;\n  }\n}\n\n.terminal-scanlines {\n  position: relative;\n}\n\n.terminal-scanlines::after {\n  content: \"\";\n  position: absolute;\n  top: 0;\n  left: 0;\n  width: 100%;\n  height: 100%;\n  background: repeating-linear-gradient(\n    0deg,\n    transparent,\n    transparent 2px,\n    rgba(255, 255, 255, 0.03) 2px,\n    rgba(255, 255, 255, 0.03) 4px\n  );\n  pointer-events: none;\n}\n\n.terminal-block-hover:hover {\n  border-color: var(--primary);\n  box-shadow: 0 0 10px rgba(136, 57, 239, 0.3);\n  transform: translateY(-1px);\n}\n\n.ascii-art {\n  font-family: \"Courier New\", monospace;\n  line-height: 1;\n  letter-spacing: 0;\n}\n\n.terminal-prompt::before {\n  content: \"> \";\n  color: var(--primary);\n  font-weight: bold;\n}\n\n.file-browser-item {\n  transition: all 0.15s ease;\n  position: relative;\n}\n\n.file-browser-item:hover {\n  background-color: var(--accent);\n  color: var(--accent-foreground);\n  transform: translateX(2px);\n}\n\n.file-browser-item:hover .file-icon {\n  filter: brightness(1.2);\n}\n\n.directory-header {\n  position: relative;\n  cursor: pointer;\n}\n\n.directory-header:hover {\n  background-color: var(--muted);\n}\n\n@keyframes file-load {\n  0% {\n    opacity: 0;\n    transform: translateX(-10px);\n  }\n  100% {\n    opacity: 1;\n    transform: translateX(0);\n  }\n}\n\n.file-load-animation {\n  animation: file-load 0.3s ease-out forwards;\n}\n"
  },
  {
    "path": "apps/web/src/app/layout.config.tsx",
    "content": "import type { BaseLayoutProps, LinkItemType } from \"fumadocs-ui/layouts/shared\";\nimport Image from \"next/image\";\n\nimport discordLogo from \"@/public/icon/discord.svg\";\nimport npmLogo from \"@/public/icon/npm.svg\";\nimport xLogo from \"@/public/icon/x.svg\";\nimport mainLogoDark from \"@/public/logo-dark.svg\";\nimport mainLogoLight from \"@/public/logo-light.svg\";\n\nexport const logo = (\n  <>\n    <Image alt=\"better-t-stack\" src={mainLogoLight} className=\"w-8 dark:hidden\" />\n    <Image alt=\"better-t-stack\" src={mainLogoDark} className=\"hidden w-8 dark:block\" />\n  </>\n);\n\nexport const links: LinkItemType[] = [\n  {\n    text: \"Docs\",\n    url: \"/docs\",\n    active: \"nested-url\",\n  },\n  {\n    text: \"Builder\",\n    url: \"/new\",\n  },\n  {\n    text: \"Analytics\",\n    url: \"/analytics\",\n  },\n  {\n    text: \"Showcase\",\n    url: \"/showcase\",\n  },\n  {\n    text: \"Demo\",\n    url: \"https://my-better-t-app.amanv.cloud/\",\n    external: true,\n  },\n  {\n    text: \"NPM\",\n    icon: <Image src={npmLogo} alt=\"npm\" className=\"size-4 invert-0 dark:invert\" />,\n    label: \"NPM\",\n    type: \"icon\",\n    url: \"https://www.npmjs.com/package/create-better-t-stack\",\n    external: true,\n    secondary: true,\n  },\n  {\n    text: \"X\",\n    icon: <Image src={xLogo} alt=\"x\" className=\"size-4 invert dark:invert-0\" />,\n    label: \"X\",\n    type: \"icon\",\n    url: \"https://x.com/amanvarshney01\",\n    external: true,\n    secondary: true,\n  },\n  {\n    text: \"Discord\",\n    icon: <Image src={discordLogo} alt=\"discord\" className=\"size-5 invert-0 dark:invert\" />,\n    label: \"Discord\",\n    type: \"icon\",\n    url: \"https://discord.gg/ZYsbjpDaM5\",\n    external: true,\n    secondary: true,\n  },\n];\n\nexport const baseOptions: BaseLayoutProps = {\n  nav: {\n    title: (\n      <>\n        {logo}\n        <span className=\"font-medium font-mono text-md tracking-tighter\">Better T Stack</span>\n      </>\n    ),\n  },\n  links: links,\n  githubUrl: \"https://github.com/AmanVarshney01/create-better-t-stack\",\n};\n"
  },
  {
    "path": "apps/web/src/app/layout.tsx",
    "content": "import { RootProvider } from \"fumadocs-ui/provider/next\";\nimport type { Metadata, Viewport } from \"next\";\nimport { Geist, Geist_Mono } from \"next/font/google\";\nimport Script from \"next/script\";\nimport type { ReactNode } from \"react\";\n\nimport Providers from \"@/components/providers\";\n\nimport \"./global.css\";\nimport { cn } from \"@/lib/utils\";\n\nconst geist = Geist({\n  subsets: [\"latin\"],\n  weight: [\"400\", \"500\", \"600\", \"700\"],\n  variable: \"--font-geist\",\n});\n\nconst geistMono = Geist_Mono({\n  subsets: [\"latin\"],\n  weight: [\"400\", \"500\", \"600\", \"700\"],\n  variable: \"--font-geist-mono\",\n});\n\nconst ogImage = \"https://r2.better-t-stack.dev/og.png\";\n\nexport const metadata: Metadata = {\n  title: \"Better-T-Stack\",\n  description:\n    \"A modern CLI tool for scaffolding end-to-end type-safe TypeScript projects with best practices and customizable configurations\",\n  keywords: [\n    \"TypeScript\",\n    \"project scaffolding\",\n    \"boilerplate\",\n    \"type safety\",\n    \"Drizzle\",\n    \"Prisma\",\n    \"hono\",\n    \"elysia\",\n    \"turborepo\",\n    \"trpc\",\n    \"orpc\",\n    \"turso\",\n    \"neon\",\n    \"Better-Auth\",\n    \"convex\",\n    \"monorepo\",\n    \"Better-T-Stack\",\n    \"create-better-t-stack\",\n  ],\n  authors: [{ name: \"Better-T-Stack Team\" }],\n  creator: \"Better-T-Stack\",\n  publisher: \"Better-T-Stack\",\n  formatDetection: {\n    email: false,\n    telephone: false,\n  },\n  metadataBase: new URL(\"https://better-t-stack.dev\"),\n  alternates: {\n    canonical: \"/\",\n  },\n  openGraph: {\n    title: \"Better-T-Stack\",\n    description:\n      \"A modern CLI tool for scaffolding end-to-end type-safe TypeScript projects with best practices and customizable configurations\",\n    url: \"https://better-t-stack.dev\",\n    siteName: \"Better-T-Stack\",\n    images: [\n      {\n        url: ogImage,\n        width: 1200,\n        height: 630,\n        alt: \"Better-T-Stack\",\n      },\n    ],\n    locale: \"en_US\",\n    type: \"website\",\n  },\n  twitter: {\n    card: \"summary_large_image\",\n    title: \"Better-T-Stack\",\n    description:\n      \"A modern CLI tool for scaffolding end-to-end type-safe TypeScript projects with best practices and customizable configurations\",\n    images: [ogImage],\n  },\n  robots: {\n    index: true,\n    follow: true,\n    googleBot: {\n      index: true,\n      follow: true,\n      \"max-image-preview\": \"large\",\n      \"max-video-preview\": -1,\n      \"max-snippet\": -1,\n    },\n  },\n  category: \"Technology\",\n  icons: {\n    icon: [\n      { url: \"/favicon/favicon.svg\", type: \"image/svg+xml\" },\n      { url: \"/logo-light.svg\", media: \"(prefers-color-scheme: light)\", type: \"image/svg+xml\" },\n      { url: \"/logo-dark.svg\", media: \"(prefers-color-scheme: dark)\", type: \"image/svg+xml\" },\n    ],\n    shortcut: \"/favicon/favicon.svg\",\n    apple: \"/favicon/apple-touch-icon.png\",\n  },\n};\n\nexport const viewport: Viewport = {\n  width: \"device-width\",\n  initialScale: 1.0,\n};\n\nexport default function Layout({ children }: { children: ReactNode }) {\n  return (\n    <html\n      lang=\"en\"\n      className={cn(geist.variable, geistMono.variable, \"font-sans\")}\n      suppressHydrationWarning\n    >\n      <body>\n        <Script\n          src=\"https://umami.amanv.cloud/script.js\"\n          data-website-id=\"3fe218f9-a51b-40c3-ab37-d65e6963d686\"\n          strategy=\"afterInteractive\"\n        />\n        <RootProvider\n          search={{\n            options: {\n              type: \"static\",\n            },\n          }}\n          theme={{\n            enableSystem: true,\n            defaultTheme: \"system\",\n          }}\n        >\n          <Providers>{children}</Providers>\n        </RootProvider>\n      </body>\n    </html>\n  );\n}\n"
  },
  {
    "path": "apps/web/src/app/llms-full.txt/route.ts",
    "content": "import { getLLMText } from \"@/lib/get-llm-text\";\nimport { source } from \"@/lib/source\";\n\n// cached forever\nexport const revalidate = false;\n\nexport async function GET() {\n  const scan = source.getPages().map(getLLMText);\n  const scanned = await Promise.all(scan);\n\n  return new Response(scanned.join(\"\\n\\n\"));\n}\n"
  },
  {
    "path": "apps/web/src/app/llms.mdx/[[...slug]]/route.ts",
    "content": "import { notFound } from \"next/navigation\";\n\nimport { getLLMText } from \"@/lib/get-llm-text\";\nimport { source } from \"@/lib/source\";\n\nexport const revalidate = false;\n\nexport async function GET(_req: Request, { params }: RouteContext<\"/llms.mdx/[[...slug]]\">) {\n  const { slug } = await params;\n  const page = source.getPage(slug);\n  if (!page) notFound();\n\n  return new Response(await getLLMText(page), {\n    headers: {\n      \"Content-Type\": \"text/markdown\",\n    },\n  });\n}\n\nexport function generateStaticParams() {\n  return source.generateParams();\n}\n"
  },
  {
    "path": "apps/web/src/app/manifest.ts",
    "content": "import type { MetadataRoute } from \"next\";\n\nexport const dynamic = \"force-static\";\n\nexport default function manifest(): MetadataRoute.Manifest {\n  return {\n    name: \"Better T Stack\",\n    short_name: \"Better T Stack\",\n    description:\n      \"A modern CLI tool for scaffolding end-to-end type-safe TypeScript projects with best practices and customizable configurations\",\n    start_url: \"/new\",\n    display: \"standalone\",\n    background_color: \"#ffffff\",\n    theme_color: \"#000000\",\n    icons: [\n      {\n        src: \"/favicon/web-app-manifest-192x192.png\",\n        sizes: \"192x192\",\n        type: \"image/png\",\n      },\n      {\n        src: \"/favicon/web-app-manifest-512x512.png\",\n        sizes: \"512x512\",\n        type: \"image/png\",\n      },\n    ],\n  };\n}\n"
  },
  {
    "path": "apps/web/src/app/not-found.tsx",
    "content": "\"use client\";\n\nimport { FileX, Home, Terminal } from \"lucide-react\";\nimport Link from \"next/link\";\n\nexport default function NotFound() {\n  return (\n    <div className=\"container mx-auto mx-auto min-h-svh\">\n      <main className=\"mx-auto px-4 pt-12\">\n        <div className=\"mb-8\">\n          <div className=\"mb-6 flex flex-wrap items-center justify-between gap-2 sm:flex-nowrap\">\n            <div className=\"flex items-center gap-2\">\n              <Terminal className=\"h-4 w-4 text-primary\" />\n              <span className=\"font-bold font-mono text-lg sm:text-xl\">ERROR_404.TXT</span>\n            </div>\n            <div className=\"h-px flex-1 bg-border\" />\n            <span className=\"text-muted-foreground text-xs\">[PAGE NOT FOUND]</span>\n          </div>\n        </div>\n\n        <div className=\"mb-8 grid grid-cols-1 gap-4 lg:grid-cols-2\">\n          <div className=\"flex h-full flex-col justify-between rounded border border-border bg-fd-background p-4\">\n            <div className=\"mb-4 flex items-center justify-between\">\n              <div className=\"flex items-center gap-2\">\n                <FileX className=\"h-4 w-4 text-primary\" />\n                <span className=\"font-semibold font-mono text-sm\">ERROR_DETAILS</span>\n              </div>\n              <div className=\"rounded border border-border bg-muted/30 px-2 py-1 text-xs\">404</div>\n            </div>\n\n            <div className=\"space-y-3\">\n              <div className=\"flex items-center justify-between rounded border border-border bg-fd-background p-3\">\n                <div className=\"flex items-center gap-2 font-mono text-sm\">\n                  <span className=\"text-primary\">$</span>\n                  <span className=\"text-foreground\">Page not found in directory</span>\n                </div>\n                <div className=\"rounded border border-border bg-destructive/10 px-2 py-1 text-destructive text-xs\">\n                  ERROR\n                </div>\n              </div>\n            </div>\n          </div>\n\n          <Link href=\"/\">\n            <div className=\"group flex h-full cursor-pointer flex-col justify-between rounded border border-border bg-fd-background p-4 transition-colors hover:bg-muted/10\">\n              <div className=\"mb-4 flex items-center justify-between\">\n                <div className=\"flex items-center gap-2\">\n                  <Home className=\"h-4 w-4 text-primary transition-transform group-hover:scale-110\" />\n                  <span className=\"font-semibold font-mono text-sm\">GO_HOME</span>\n                </div>\n                <div className=\"rounded border border-border bg-muted/30 px-2 py-1 text-xs\">\n                  SAFE\n                </div>\n              </div>\n\n              <div className=\"space-y-3\">\n                <div className=\"flex items-center justify-between rounded border border-border bg-fd-background p-3\">\n                  <div className=\"flex items-center gap-2 text-sm\">\n                    <Home className=\"h-4 w-4 text-primary\" />\n                    <span className=\"text-foreground\">Return to homepage</span>\n                  </div>\n                  <div className=\"rounded border border-border bg-muted/30 px-2 py-1 text-xs\">\n                    NAVIGATE\n                  </div>\n                </div>\n              </div>\n            </div>\n          </Link>\n        </div>\n      </main>\n    </div>\n  );\n}\n"
  },
  {
    "path": "apps/web/src/app/og/docs/[...slug]/route.tsx",
    "content": "import { notFound } from \"next/navigation\";\nimport { ImageResponse } from \"next/og\";\n\nimport { getPageImage, source } from \"@/lib/source\";\n\nexport const revalidate = false;\n\nexport async function GET(_req: Request, { params }: RouteContext<\"/og/docs/[...slug]\">) {\n  const { slug } = await params;\n  const page = source.getPage(slug.slice(0, -1));\n  if (!page) notFound();\n\n  return new ImageResponse(\n    <div\n      style={{\n        height: \"100%\",\n        width: \"100%\",\n        display: \"flex\",\n        flexDirection: \"column\",\n        background: \"#0a0a0a\",\n        fontFamily: \"system-ui, sans-serif\",\n        position: \"relative\",\n      }}\n    >\n      <div\n        style={{\n          display: \"flex\",\n          flexDirection: \"column\",\n          margin: \"40px\",\n          flex: 1,\n          border: \"1px solid #313244\",\n          borderRadius: \"8px\",\n          overflow: \"hidden\",\n          background: \"#11111b\",\n        }}\n      >\n        <div\n          style={{\n            display: \"flex\",\n            alignItems: \"center\",\n            padding: \"14px 20px\",\n            background: \"#181825\",\n            borderBottom: \"1px solid #313244\",\n            gap: \"8px\",\n          }}\n        >\n          <div style={{ display: \"flex\", gap: \"8px\" }}>\n            <div\n              style={{\n                width: \"12px\",\n                height: \"12px\",\n                borderRadius: \"50%\",\n                background: \"#f38ba8\",\n                display: \"flex\",\n              }}\n            />\n            <div\n              style={{\n                width: \"12px\",\n                height: \"12px\",\n                borderRadius: \"50%\",\n                background: \"#f9e2af\",\n                display: \"flex\",\n              }}\n            />\n            <div\n              style={{\n                width: \"12px\",\n                height: \"12px\",\n                borderRadius: \"50%\",\n                background: \"#a6e3a1\",\n                display: \"flex\",\n              }}\n            />\n          </div>\n          <div\n            style={{\n              flex: 1,\n              textAlign: \"center\",\n              color: \"#585b70\",\n              fontSize: \"14px\",\n              fontFamily: \"monospace\",\n              display: \"flex\",\n              justifyContent: \"center\",\n            }}\n          >\n            ~/docs/{page.slugs.join(\"/\")}\n          </div>\n        </div>\n\n        <div\n          style={{\n            display: \"flex\",\n            flexDirection: \"column\",\n            padding: \"48px 56px\",\n            flex: 1,\n            justifyContent: \"center\",\n            gap: \"16px\",\n          }}\n        >\n          <div\n            style={{\n              fontSize: \"56px\",\n              fontWeight: 700,\n              color: \"#cdd6f4\",\n              lineHeight: 1.15,\n              letterSpacing: \"-0.025em\",\n              display: \"flex\",\n              flexWrap: \"wrap\",\n            }}\n          >\n            {page.data.title}\n          </div>\n\n          {page.data.description && (\n            <div\n              style={{\n                fontSize: \"24px\",\n                color: \"#6c7086\",\n                lineHeight: 1.5,\n                display: \"flex\",\n                flexWrap: \"wrap\",\n              }}\n            >\n              {page.data.description}\n            </div>\n          )}\n        </div>\n\n        <div\n          style={{\n            display: \"flex\",\n            justifyContent: \"space-between\",\n            alignItems: \"center\",\n            padding: \"16px 56px\",\n            borderTop: \"1px solid #313244\",\n            background: \"#181825\",\n          }}\n        >\n          <div style={{ display: \"flex\", alignItems: \"center\", gap: \"10px\" }}>\n            <span\n              style={{\n                color: \"#cba6f7\",\n                fontSize: \"18px\",\n                fontWeight: 600,\n                display: \"flex\",\n              }}\n            >\n              Better-T Stack\n            </span>\n            <span style={{ color: \"#313244\", fontSize: \"18px\", display: \"flex\" }}>/</span>\n            <span style={{ color: \"#585b70\", fontSize: \"16px\", display: \"flex\" }}>docs</span>\n          </div>\n          <div\n            style={{\n              color: \"#45475a\",\n              fontSize: \"14px\",\n              fontFamily: \"monospace\",\n              display: \"flex\",\n            }}\n          >\n            better-t-stack.dev\n          </div>\n        </div>\n      </div>\n    </div>,\n    {\n      width: 1200,\n      height: 630,\n    },\n  );\n}\n\nexport function generateStaticParams() {\n  return source.getPages().map((page) => ({\n    slug: getPageImage(page).segments,\n  }));\n}\n"
  },
  {
    "path": "apps/web/src/app/sitemap.ts",
    "content": "export const dynamic = \"force-static\";\n\nimport type { MetadataRoute } from \"next\";\n\nexport default function sitemap(): MetadataRoute.Sitemap {\n  return [\n    {\n      url: \"https://better-t-stack.dev/\",\n      lastModified: new Date(),\n      changeFrequency: \"weekly\",\n      priority: 1,\n    },\n    {\n      url: \"https://better-t-stack.dev/new\",\n      lastModified: new Date(),\n      changeFrequency: \"weekly\",\n      priority: 0.8,\n    },\n    {\n      url: \"https://better-t-stack.dev/docs\",\n      lastModified: new Date(),\n      changeFrequency: \"weekly\",\n      priority: 0.5,\n    },\n  ];\n}\n"
  },
  {
    "path": "apps/web/src/components/ai/page-actions.tsx",
    "content": "\"use client\";\nimport { cva } from \"class-variance-authority\";\nimport { buttonVariants } from \"fumadocs-ui/components/ui/button\";\nimport { Popover, PopoverContent, PopoverTrigger } from \"fumadocs-ui/components/ui/popover\";\nimport { useCopyButton } from \"fumadocs-ui/utils/use-copy-button\";\nimport { Check, ChevronDown, Copy, ExternalLinkIcon, MessageCircleIcon } from \"lucide-react\";\nimport { useMemo, useState } from \"react\";\n\nimport { cn } from \"@/lib/utils\";\n\nconst cache = new Map<string, string>();\n\nexport function LLMCopyButton({\n  /**\n   * A URL to fetch the raw Markdown/MDX content of page\n   */\n  markdownUrl,\n}: {\n  markdownUrl: string;\n}) {\n  const [isLoading, setLoading] = useState(false);\n  const [checked, onClick] = useCopyButton(async () => {\n    const cached = cache.get(markdownUrl);\n    if (cached) return navigator.clipboard.writeText(cached);\n\n    setLoading(true);\n\n    try {\n      await navigator.clipboard.write([\n        new ClipboardItem({\n          \"text/plain\": fetch(markdownUrl).then(async (res) => {\n            const content = await res.text();\n            cache.set(markdownUrl, content);\n\n            return content;\n          }),\n        }),\n      ]);\n    } finally {\n      setLoading(false);\n    }\n  });\n\n  return (\n    <button\n      type=\"button\"\n      disabled={isLoading}\n      className={cn(\n        buttonVariants({\n          color: \"secondary\",\n          size: \"sm\",\n          className: \"gap-2 [&_svg]:size-3.5 [&_svg]:text-fd-muted-foreground\",\n        }),\n      )}\n      onClick={onClick}\n    >\n      {checked ? <Check /> : <Copy />}\n      Copy Markdown\n    </button>\n  );\n}\n\nconst optionVariants = cva(\n  \"inline-flex items-center gap-2 rounded-lg p-2 text-sm hover:bg-fd-accent hover:text-fd-accent-foreground [&_svg]:size-4\",\n);\n\nexport function ViewOptions({\n  markdownUrl,\n  githubUrl,\n}: {\n  /**\n   * A URL to the raw Markdown/MDX content of page\n   */\n  markdownUrl: string;\n\n  /**\n   * Source file URL on GitHub\n   */\n  githubUrl: string;\n}) {\n  const items = useMemo(() => {\n    const fullMarkdownUrl =\n      typeof window !== \"undefined\" ? new URL(markdownUrl, window.location.origin) : \"loading\";\n    const q = `Read ${fullMarkdownUrl}, I want to ask questions about it.`;\n\n    return [\n      {\n        title: \"Open in GitHub\",\n        href: githubUrl,\n        icon: (\n          <svg fill=\"currentColor\" role=\"img\" viewBox=\"0 0 24 24\">\n            <title>GitHub</title>\n            <path d=\"M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12\" />\n          </svg>\n        ),\n      },\n      {\n        title: \"Open in Scira AI\",\n        href: `https://scira.ai/?${new URLSearchParams({\n          q,\n        })}`,\n        icon: (\n          <svg\n            width=\"910\"\n            height=\"934\"\n            viewBox=\"0 0 910 934\"\n            fill=\"none\"\n            xmlns=\"http://www.w3.org/2000/svg\"\n          >\n            <title>Scira AI Logo</title>\n            <path\n              d=\"M647.664 197.775C569.13 189.049 525.5 145.419 516.774 66.8849C508.048 145.419 464.418 189.049 385.884 197.775C464.418 206.501 508.048 250.131 516.774 328.665C525.5 250.131 569.13 206.501 647.664 197.775Z\"\n              fill=\"currentColor\"\n              stroke=\"currentColor\"\n              strokeWidth=\"8\"\n              strokeLinejoin=\"round\"\n            />\n            <path\n              d=\"M516.774 304.217C510.299 275.491 498.208 252.087 480.335 234.214C462.462 216.341 439.058 204.251 410.333 197.775C439.059 191.3 462.462 179.209 480.335 161.336C498.208 143.463 510.299 120.06 516.774 91.334C523.25 120.059 535.34 143.463 553.213 161.336C571.086 179.209 594.49 191.3 623.216 197.775C594.49 204.251 571.086 216.341 553.213 234.214C535.34 252.087 523.25 275.491 516.774 304.217Z\"\n              fill=\"currentColor\"\n              stroke=\"currentColor\"\n              strokeWidth=\"8\"\n              strokeLinejoin=\"round\"\n            />\n            <path\n              d=\"M857.5 508.116C763.259 497.644 710.903 445.288 700.432 351.047C689.961 445.288 637.605 497.644 543.364 508.116C637.605 518.587 689.961 570.943 700.432 665.184C710.903 570.943 763.259 518.587 857.5 508.116Z\"\n              stroke=\"currentColor\"\n              strokeWidth=\"20\"\n              strokeLinejoin=\"round\"\n            />\n            <path\n              d=\"M700.432 615.957C691.848 589.05 678.575 566.357 660.383 548.165C642.191 529.973 619.499 516.7 592.593 508.116C619.499 499.533 642.191 486.258 660.383 468.066C678.575 449.874 691.848 427.181 700.432 400.274C709.015 427.181 722.289 449.874 740.481 468.066C758.673 486.258 781.365 499.533 808.271 508.116C781.365 516.7 758.673 529.973 740.481 548.165C722.289 566.357 709.015 589.05 700.432 615.957Z\"\n              stroke=\"currentColor\"\n              strokeWidth=\"20\"\n              strokeLinejoin=\"round\"\n            />\n            <path\n              d=\"M889.949 121.237C831.049 114.692 798.326 81.9698 791.782 23.0692C785.237 81.9698 752.515 114.692 693.614 121.237C752.515 127.781 785.237 160.504 791.782 219.404C798.326 160.504 831.049 127.781 889.949 121.237Z\"\n              fill=\"currentColor\"\n              stroke=\"currentColor\"\n              strokeWidth=\"8\"\n              strokeLinejoin=\"round\"\n            />\n            <path\n              d=\"M791.782 196.795C786.697 176.937 777.869 160.567 765.16 147.858C752.452 135.15 736.082 126.322 716.226 121.237C736.082 116.152 752.452 107.324 765.16 94.6152C777.869 81.9065 786.697 65.5368 791.782 45.6797C796.867 65.5367 805.695 81.9066 818.403 94.6152C831.112 107.324 847.481 116.152 867.338 121.237C847.481 126.322 831.112 135.15 818.403 147.858C805.694 160.567 796.867 176.937 791.782 196.795Z\"\n              fill=\"currentColor\"\n              stroke=\"currentColor\"\n              strokeWidth=\"8\"\n              strokeLinejoin=\"round\"\n            />\n            <path\n              d=\"M760.632 764.337C720.719 814.616 669.835 855.1 611.872 882.692C553.91 910.285 490.404 924.255 426.213 923.533C362.022 922.812 298.846 907.419 241.518 878.531C184.19 849.643 134.228 808.026 95.4548 756.863C56.6815 705.7 30.1238 646.346 17.8129 583.343C5.50207 520.339 7.76433 455.354 24.4266 393.359C41.089 331.364 71.7099 274.001 113.947 225.658C156.184 177.315 208.919 139.273 268.117 114.442\"\n              stroke=\"currentColor\"\n              strokeWidth=\"20\"\n              strokeLinecap=\"round\"\n              strokeLinejoin=\"round\"\n            />\n          </svg>\n        ),\n      },\n      {\n        title: \"Open in ChatGPT\",\n        href: `https://chatgpt.com/?${new URLSearchParams({\n          hints: \"search\",\n          q,\n        })}`,\n        icon: (\n          <svg\n            role=\"img\"\n            viewBox=\"0 0 24 24\"\n            fill=\"currentColor\"\n            xmlns=\"http://www.w3.org/2000/svg\"\n          >\n            <title>OpenAI</title>\n            <path d=\"M22.2819 9.8211a5.9847 5.9847 0 0 0-.5157-4.9108 6.0462 6.0462 0 0 0-6.5098-2.9A6.0651 6.0651 0 0 0 4.9807 4.1818a5.9847 5.9847 0 0 0-3.9977 2.9 6.0462 6.0462 0 0 0 .7427 7.0966 5.98 5.98 0 0 0 .511 4.9107 6.051 6.051 0 0 0 6.5146 2.9001A5.9847 5.9847 0 0 0 13.2599 24a6.0557 6.0557 0 0 0 5.7718-4.2058 5.9894 5.9894 0 0 0 3.9977-2.9001 6.0557 6.0557 0 0 0-.7475-7.0729zm-9.022 12.6081a4.4755 4.4755 0 0 1-2.8764-1.0408l.1419-.0804 4.7783-2.7582a.7948.7948 0 0 0 .3927-.6813v-6.7369l2.02 1.1686a.071.071 0 0 1 .038.052v5.5826a4.504 4.504 0 0 1-4.4945 4.4944zm-9.6607-4.1254a4.4708 4.4708 0 0 1-.5346-3.0137l.142.0852 4.783 2.7582a.7712.7712 0 0 0 .7806 0l5.8428-3.3685v2.3324a.0804.0804 0 0 1-.0332.0615L9.74 19.9502a4.4992 4.4992 0 0 1-6.1408-1.6464zM2.3408 7.8956a4.485 4.485 0 0 1 2.3655-1.9728V11.6a.7664.7664 0 0 0 .3879.6765l5.8144 3.3543-2.0201 1.1685a.0757.0757 0 0 1-.071 0l-4.8303-2.7865A4.504 4.504 0 0 1 2.3408 7.872zm16.5963 3.8558L13.1038 8.364 15.1192 7.2a.0757.0757 0 0 1 .071 0l4.8303 2.7913a4.4944 4.4944 0 0 1-.6765 8.1042v-5.6772a.79.79 0 0 0-.407-.667zm2.0107-3.0231l-.142-.0852-4.7735-2.7818a.7759.7759 0 0 0-.7854 0L9.409 9.2297V6.8974a.0662.0662 0 0 1 .0284-.0615l4.8303-2.7866a4.4992 4.4992 0 0 1 6.6802 4.66zM8.3065 12.863l-2.02-1.1638a.0804.0804 0 0 1-.038-.0567V6.0742a4.4992 4.4992 0 0 1 7.3757-3.4537l-.142.0805L8.704 5.459a.7948.7948 0 0 0-.3927.6813zm1.0976-2.3654l2.602-1.4998 2.6069 1.4998v2.9994l-2.5974 1.4997-2.6067-1.4997Z\" />\n          </svg>\n        ),\n      },\n      {\n        title: \"Open in Claude\",\n        href: `https://claude.ai/new?${new URLSearchParams({\n          q,\n        })}`,\n        icon: (\n          <svg\n            fill=\"currentColor\"\n            role=\"img\"\n            viewBox=\"0 0 24 24\"\n            xmlns=\"http://www.w3.org/2000/svg\"\n          >\n            <title>Anthropic</title>\n            <path d=\"M17.3041 3.541h-3.6718l6.696 16.918H24Zm-10.6082 0L0 20.459h3.7442l1.3693-3.5527h7.0052l1.3693 3.5528h3.7442L10.5363 3.5409Zm-.3712 10.2232 2.2914-5.9456 2.2914 5.9456Z\" />\n          </svg>\n        ),\n      },\n      {\n        title: \"Open in T3 Chat\",\n        href: `https://t3.chat/new?${new URLSearchParams({\n          q,\n        })}`,\n        icon: <MessageCircleIcon />,\n      },\n    ];\n  }, [githubUrl, markdownUrl]);\n\n  return (\n    <Popover>\n      <PopoverTrigger\n        className={cn(\n          buttonVariants({\n            color: \"secondary\",\n            size: \"sm\",\n            className: \"gap-2\",\n          }),\n        )}\n      >\n        Open\n        <ChevronDown className=\"size-3.5 text-fd-muted-foreground\" />\n      </PopoverTrigger>\n      <PopoverContent className=\"flex flex-col overflow-auto\">\n        {items.map((item) => (\n          <a\n            key={item.href}\n            href={item.href}\n            rel=\"noreferrer noopener\"\n            target=\"_blank\"\n            className={cn(optionVariants())}\n          >\n            {item.icon}\n            {item.title}\n            <ExternalLinkIcon className=\"ms-auto size-3.5 text-fd-muted-foreground\" />\n          </a>\n        ))}\n      </PopoverContent>\n    </Popover>\n  );\n}\n"
  },
  {
    "path": "apps/web/src/components/evilcharts/charts/area-chart.tsx",
    "content": "\"use client\";\n\nimport { motion } from \"motion/react\";\nimport { useCallback, useId, useMemo, useRef, useState, type ComponentProps } from \"react\";\nimport { Area, AreaChart, CartesianGrid, ReferenceLine, XAxis, YAxis } from \"recharts\";\n\nimport { ChartBackground, type BackgroundVariant } from \"@/components/evilcharts/ui/background\";\nimport {\n  axisValueToPercentFormatter,\n  type ChartConfig,\n  ChartContainer,\n  getColorsCount,\n  getLoadingData,\n  LoadingIndicator,\n} from \"@/components/evilcharts/ui/chart\";\nimport { ChartDot, DotVariant } from \"@/components/evilcharts/ui/dot\";\nimport {\n  EvilBrush,\n  useEvilBrush,\n  type EvilBrushRange,\n} from \"@/components/evilcharts/ui/evil-brush\";\nimport {\n  ChartLegend,\n  ChartLegendContent,\n  type ChartLegendVariant,\n} from \"@/components/evilcharts/ui/legend\";\nimport {\n  ChartTooltip,\n  ChartTooltipContent,\n  type TooltipRoundness,\n  type TooltipVariant,\n} from \"@/components/evilcharts/ui/tooltip\";\n\n// Constants\nconst STROKE_WIDTH = 0.8;\nconst LOADING_AREA_DATA_KEY = \"loading\";\nconst LOADING_ANIMATION_DURATION = 2000; // in milliseconds\n\ntype ChartProps = ComponentProps<typeof AreaChart>;\ntype XAxisProps = ComponentProps<typeof XAxis>;\ntype YAxisProps = ComponentProps<typeof YAxis>;\ntype AreaType = ComponentProps<typeof Area>[\"type\"];\ntype AreaVariant = \"gradient\" | \"gradient-reverse\" | \"solid\" | \"dotted\" | \"lines\" | \"hatched\";\ntype StrokeVariant = \"solid\" | \"dashed\" | \"animated-dashed\";\ntype StackType = \"default\" | \"expanded\" | \"stacked\";\n\n// Validating Tyes to make sure user have provided valid data according to chartConfig\ntype ValidateConfigKeys<TData, TConfig> = {\n  [K in keyof TConfig]: K extends keyof TData ? ChartConfig[string] : never;\n};\n\ntype BaseEvilAreaChartProps<\n  TData extends Record<string, unknown>,\n  TConfig extends Record<string, ChartConfig[string]>,\n> = {\n  chartConfig: TConfig & ValidateConfigKeys<TData, TConfig>;\n  data: TData[];\n  xDataKey?: keyof TData & string;\n  yDataKey?: keyof TData & string;\n  className?: string;\n  chartProps?: ChartProps;\n  xAxisProps?: XAxisProps;\n  yAxisProps?: YAxisProps;\n  defaultSelectedDataKey?: string | null;\n  curveType?: AreaType;\n  areaVariant?: AreaVariant;\n  strokeVariant?: StrokeVariant;\n  stackType?: StackType;\n  dotVariant?: DotVariant;\n  activeDotVariant?: DotVariant;\n  legendVariant?: ChartLegendVariant;\n  connectNulls?: boolean;\n  tickGap?: number;\n  // Hide Stuffs\n  hideTooltip?: boolean;\n  hideCartesianGrid?: boolean;\n  hideLegend?: boolean;\n  hideCursorLine?: boolean;\n  // Tooltip\n  tooltipRoundness?: TooltipRoundness;\n  tooltipVariant?: TooltipVariant;\n  tooltipDefaultIndex?: number;\n  isLoading?: boolean;\n  loadingPoints?: number;\n  // Brush\n  showBrush?: boolean;\n  brushHeight?: number;\n  brushFormatLabel?: (value: unknown, index: number) => string;\n  onBrushChange?: (range: EvilBrushRange) => void;\n  // Background\n  backgroundVariant?: BackgroundVariant;\n};\n\ntype EvilAreaChartClickable = {\n  isClickable: true;\n  onSelectionChange?: (selectedDataKey: string | null) => void;\n};\n\ntype EvilAreaChartNotClickable = {\n  isClickable?: false;\n  onSelectionChange?: never;\n};\n\ntype EvilAreaChartProps<\n  TData extends Record<string, unknown>,\n  TConfig extends Record<string, ChartConfig[string]>,\n> = BaseEvilAreaChartProps<TData, TConfig> & (EvilAreaChartClickable | EvilAreaChartNotClickable);\n\nexport function EvilAreaChart<\n  TData extends Record<string, unknown>,\n  TConfig extends Record<string, ChartConfig[string]>,\n>({\n  chartConfig,\n  data,\n  xDataKey,\n  yDataKey,\n  className,\n  chartProps,\n  xAxisProps,\n  yAxisProps,\n  defaultSelectedDataKey = null,\n  curveType = \"linear\",\n  areaVariant = \"gradient\",\n  strokeVariant = \"dashed\",\n  stackType = \"default\",\n  dotVariant,\n  activeDotVariant,\n  legendVariant,\n  connectNulls = false,\n  tickGap = 8,\n  hideTooltip = false,\n  hideCartesianGrid = false,\n  hideLegend = false,\n  hideCursorLine = false,\n  tooltipRoundness,\n  tooltipVariant,\n  tooltipDefaultIndex,\n  isClickable = false,\n  isLoading = false,\n  loadingPoints,\n  showBrush = false,\n  brushHeight,\n  brushFormatLabel,\n  onBrushChange,\n  onSelectionChange,\n  backgroundVariant,\n}: EvilAreaChartProps<TData, TConfig>) {\n  const [selectedDataKey, setSelectedDataKey] = useState<string | null>(defaultSelectedDataKey);\n  const { loadingData, onShimmerExit } = useLoadingData(isLoading, loadingPoints);\n  const chartId = useId().replace(/:/g, \"\"); // Remove colons for valid CSS selectors\n\n  // ── Zoom state ──────────────────────────────────────────────────────────\n  const { visibleData, brushProps } = useEvilBrush({ data });\n  const displayData = showBrush && !isLoading ? visibleData : data;\n\n  // Wrapper function to update state and call parent callback\n  // Only call callback when isClickable is true\n  const handleSelectionChange = useCallback(\n    (newSelectedDataKey: string | null) => {\n      setSelectedDataKey(newSelectedDataKey);\n      if (isClickable && onSelectionChange) {\n        onSelectionChange(newSelectedDataKey);\n      }\n    },\n    [onSelectionChange, isClickable],\n  );\n\n  const isExpanded = stackType === \"expanded\";\n  const isStacked = stackType === \"stacked\" || stackType === \"expanded\";\n\n  return (\n    <ChartContainer\n      className={className}\n      config={chartConfig}\n      footer={\n        showBrush &&\n        !isLoading && (\n          <EvilBrush\n            data={data}\n            chartConfig={chartConfig}\n            xDataKey={xDataKey}\n            variant=\"area\"\n            curveType={curveType}\n            strokeVariant={strokeVariant}\n            connectNulls={connectNulls}\n            height={brushHeight}\n            formatLabel={brushFormatLabel}\n            stacked={isStacked}\n            skipStyle\n            className=\"mt-1\"\n            {...brushProps}\n            onChange={(range) => {\n              brushProps.onChange(range);\n              onBrushChange?.(range);\n            }}\n          />\n        )\n      }\n    >\n      <LoadingIndicator isLoading={isLoading} />\n      <AreaChart\n        id=\"evil-charts-area-chart\"\n        accessibilityLayer\n        stackOffset={isExpanded ? \"expand\" : undefined}\n        data={isLoading ? loadingData : displayData}\n        {...chartProps}\n      >\n        {backgroundVariant && <ChartBackground variant={backgroundVariant} />}\n        <ReferenceLine color=\"white\" />\n        {!hideCartesianGrid && !backgroundVariant && (\n          <CartesianGrid vertical={false} strokeDasharray=\"3 3\" />\n        )}\n        {!hideLegend && (\n          <ChartLegend\n            verticalAlign=\"top\"\n            align=\"right\"\n            content={\n              <ChartLegendContent\n                selected={selectedDataKey}\n                onSelectChange={handleSelectionChange}\n                isClickable={isClickable}\n                variant={legendVariant}\n              />\n            }\n          />\n        )}\n        {xDataKey && !isLoading && (\n          <XAxis\n            dataKey={xDataKey}\n            tickLine={false}\n            axisLine={false}\n            tickMargin={8}\n            minTickGap={tickGap}\n            {...xAxisProps}\n          />\n        )}\n        {yDataKey && !isLoading && (\n          <YAxis\n            dataKey={yDataKey}\n            tickLine={false}\n            axisLine={false}\n            tickMargin={8}\n            minTickGap={tickGap}\n            width=\"auto\"\n            tickFormatter={\n              stackType === \"expanded\" ? axisValueToPercentFormatter : yAxisProps?.tickFormatter\n            }\n            {...yAxisProps}\n          />\n        )}\n        {!hideTooltip && !isLoading && (\n          <ChartTooltip\n            defaultIndex={tooltipDefaultIndex}\n            cursor={\n              hideCursorLine\n                ? false\n                : {\n                    strokeDasharray:\n                      strokeVariant === \"dashed\" || strokeVariant === \"animated-dashed\"\n                        ? \"3 3\"\n                        : undefined,\n                    strokeWidth: STROKE_WIDTH,\n                  }\n            }\n            content={\n              <ChartTooltipContent\n                selected={selectedDataKey}\n                roundness={tooltipRoundness}\n                variant={tooltipVariant}\n              />\n            }\n          />\n        )}\n        {!isLoading &&\n          Object.keys(chartConfig).map((dataKey) => {\n            const _opacity = getOpacity(isClickable, selectedDataKey, dataKey);\n            const isSelected = selectedDataKey === dataKey;\n            const hasSelection = selectedDataKey !== null;\n\n            // Get fill pattern based on variant and selection state\n            const fillPattern = getFillPattern(\n              areaVariant,\n              isClickable,\n              hasSelection,\n              isSelected,\n              dataKey,\n              chartId,\n            );\n\n            const dot = dotVariant ? (\n              <ChartDot\n                fillOpacity={_opacity.dot}\n                type={dotVariant}\n                dataKey={dataKey}\n                chartId={chartId}\n              />\n            ) : (\n              false\n            );\n            const activeDot = activeDotVariant ? (\n              <ChartDot\n                fillOpacity={_opacity.dot}\n                type={activeDotVariant}\n                dataKey={dataKey}\n                chartId={chartId}\n              />\n            ) : (\n              false\n            );\n\n            return (\n              <Area\n                type={curveType}\n                key={dataKey}\n                dataKey={dataKey}\n                connectNulls={connectNulls}\n                fillOpacity={_opacity.fill}\n                strokeOpacity={_opacity.stroke}\n                fill={fillPattern}\n                stroke={\n                  areaVariant === \"solid\"\n                    ? getSeriesColor(dataKey)\n                    : `url(#${chartId}-colors-${dataKey})`\n                }\n                stackId={isStacked ? \"evil-stacked\" : undefined}\n                dot={dot}\n                activeDot={activeDot}\n                strokeWidth={STROKE_WIDTH}\n                strokeDasharray={\n                  strokeVariant === \"dashed\"\n                    ? \"3 3\"\n                    : strokeVariant === \"animated-dashed\"\n                      ? \"3 3\"\n                      : undefined\n                }\n                style={isClickable ? { cursor: \"pointer\" } : undefined}\n                onClick={() => {\n                  if (!isClickable) return;\n                  // Toggle: if already selected, unselect; otherwise select\n                  handleSelectionChange(selectedDataKey === dataKey ? null : dataKey);\n                }}\n              >\n                {strokeVariant === \"animated-dashed\" && !hasSelection && <AnimatedDashedStyle />}\n              </Area>\n            );\n          })}\n        {/* ======== LOADING AREA ======== */}\n        {isLoading && (\n          <Area\n            type={curveType}\n            dataKey={LOADING_AREA_DATA_KEY}\n            fillOpacity={0.05}\n            min={0}\n            max={100}\n            fill=\"currentColor\"\n            stroke=\"currentColor\"\n            strokeOpacity={0.5}\n            isAnimationActive={false}\n            legendType=\"none\"\n            tooltipType=\"none\"\n            activeDot={false}\n            dot={false}\n            style={{ mask: `url(#${chartId}-loading-mask)` }}\n          />\n        )}\n        {/* ======== CHART STYLES ======== */}\n        <defs>\n          {isLoading && <LoadingAreaPatternStyle chartId={chartId} onShimmerExit={onShimmerExit} />}\n          {/* Shared horizontal color gradient for textured or gradient area variants */}\n          {areaVariant !== \"solid\" && (\n            <HorizontalColorGradientStyle\n              chartConfig={chartConfig}\n              chartId={chartId}\n              isExpanded={isExpanded}\n            />\n          )}\n          {/* Variant-specific styles */}\n          {areaVariant === \"gradient\" && (\n            <LinearGradientStyle chartConfig={chartConfig} chartId={chartId} />\n          )}\n          {areaVariant === \"gradient-reverse\" && (\n            <ReverseGradientStyle chartConfig={chartConfig} chartId={chartId} />\n          )}\n          {areaVariant === \"lines\" && (\n            <LinesPatternStyle chartConfig={chartConfig} chartId={chartId} />\n          )}\n          {areaVariant === \"dotted\" && (\n            <DottedPatternStyle chartConfig={chartConfig} chartId={chartId} />\n          )}\n          {areaVariant === \"hatched\" && (\n            <HatchedPatternStyle chartConfig={chartConfig} chartId={chartId} />\n          )}\n          {areaVariant !== \"solid\" && (\n            <UnselectedDiagonalPatternStyle\n              chartConfig={chartConfig}\n              chartId={chartId}\n              selectedDataKey={selectedDataKey}\n              isClickable={isClickable}\n            />\n          )}\n        </defs>\n      </AreaChart>\n    </ChartContainer>\n  );\n}\n\nconst getSeriesColor = (dataKey: string) => `var(--color-${dataKey}-0)`;\n\n// Returns opacity object for both fill and stroke, same values for both\nconst getOpacity = (isClickable: boolean, selectedDataKey: string | null, dataKey: string) => {\n  if (!isClickable || selectedDataKey === null) {\n    return { fill: 0.8, stroke: 0.8, dot: 1 };\n  }\n  return selectedDataKey === dataKey\n    ? { fill: 0.8, stroke: 0.8, dot: 1 }\n    : { fill: 0.2, stroke: 0.3, dot: 0.3 };\n};\n\n// Returns the appropriate fill pattern based on variant and selection state\nconst getFillPattern = (\n  variant: AreaVariant,\n  isClickable: boolean,\n  hasSelection: boolean,\n  isSelected: boolean,\n  dataKey: string,\n  chartId: string,\n): string => {\n  // If clickable and there's a selection but this item is not selected, use unselected diagonal pattern\n  if (isClickable && hasSelection && !isSelected) {\n    return `url(#${chartId}-unselected-${dataKey})`;\n  }\n\n  // Otherwise, use the variant-specific pattern\n  switch (variant) {\n    case \"gradient\":\n      return `url(#${chartId}-gradient-${dataKey})`;\n    case \"gradient-reverse\":\n      return `url(#${chartId}-gradient-reverse-${dataKey})`;\n    case \"solid\":\n      return getSeriesColor(dataKey);\n    case \"dotted\":\n      return `url(#${chartId}-dotted-${dataKey})`;\n    case \"lines\":\n      return `url(#${chartId}-lines-${dataKey})`;\n    case \"hatched\":\n      return `url(#${chartId}-hatched-pattern-${dataKey})`;\n    default:\n      return `url(#${chartId}-${dataKey})`;\n  }\n};\n\n// Animated dashed-stroke style for the area chart\nconst AnimatedDashedStyle = () => {\n  return (\n    <>\n      <animate\n        attributeName=\"stroke-dasharray\"\n        values=\"3 3; 0 3; 3 3\"\n        dur=\"1s\"\n        repeatCount=\"indefinite\"\n        keyTimes=\"0;0.5;1\"\n      />\n      <animate\n        attributeName=\"stroke-dashoffset\"\n        values=\"0; -6\"\n        dur=\"1s\"\n        repeatCount=\"indefinite\"\n        keyTimes=\"0;1\"\n      />\n    </>\n  );\n};\n\n// Shared horizontal color gradient (left to right) - used by all variants and stroke\n// This is ALWAYS rendered so colors are available for any variant\nconst HorizontalColorGradientStyle = ({\n  chartConfig,\n  chartId,\n  isExpanded = false,\n}: {\n  chartConfig: ChartConfig;\n  chartId: string;\n  isExpanded?: boolean;\n}) => {\n  return (\n    <>\n      {Object.entries(chartConfig).map(([dataKey, config]) => {\n        const colorsCount = getColorsCount(config);\n\n        return (\n          <linearGradient\n            key={`${chartId}-colors-${dataKey}`}\n            id={`${chartId}-colors-${dataKey}`}\n            x1=\"0\"\n            y1=\"0\"\n            x2=\"1\"\n            y2=\"0\"\n            gradientUnits={isExpanded ? \"userSpaceOnUse\" : \"objectBoundingBox\"}\n          >\n            {colorsCount === 1 ? (\n              // Single color: same color at start and end\n              <>\n                <stop offset=\"0%\" stopColor={`var(--color-${dataKey}-0)`} />\n                <stop offset=\"100%\" stopColor={`var(--color-${dataKey}-0)`} />\n              </>\n            ) : (\n              // Multiple colors: distribute evenly\n              // Fallback to first color if index doesn't exist in current theme\n              Array.from({ length: colorsCount }, (_, index) => (\n                <stop\n                  key={index}\n                  offset={`${(index / (colorsCount - 1)) * 100}%`}\n                  stopColor={`var(--color-${dataKey}-${index}, var(--color-${dataKey}-0))`}\n                />\n              ))\n            )}\n          </linearGradient>\n        );\n      })}\n    </>\n  );\n};\n\n// Linear gradient variant - adds vertical fade mask on top of the shared color gradient\nconst LinearGradientStyle = ({\n  chartConfig,\n  chartId,\n}: {\n  chartConfig: ChartConfig;\n  chartId: string;\n}) => {\n  return (\n    <>\n      {/* Vertical fade gradient for mask */}\n      <linearGradient id={`${chartId}-vertical-fade`} x1=\"0\" y1=\"0\" x2=\"0\" y2=\"1\">\n        <stop offset=\"0%\" stopColor=\"white\" stopOpacity={0.1} />\n        <stop offset=\"100%\" stopColor=\"white\" stopOpacity={0} />\n      </linearGradient>\n\n      {Object.keys(chartConfig).map((dataKey) => (\n        <g key={`${chartId}-gradient-group-${dataKey}`}>\n          {/* Mask for vertical fade (top visible, bottom transparent) */}\n          <mask id={`${chartId}-gradient-mask-${dataKey}`}>\n            <rect width=\"100%\" height=\"100%\" fill={`url(#${chartId}-vertical-fade)`} />\n          </mask>\n\n          {/* Pattern combining shared color gradient + vertical mask */}\n          <pattern\n            id={`${chartId}-gradient-${dataKey}`}\n            patternUnits=\"userSpaceOnUse\"\n            width=\"100%\"\n            height=\"100%\"\n          >\n            <rect\n              width=\"100%\"\n              height=\"100%\"\n              fill={`url(#${chartId}-colors-${dataKey})`}\n              mask={`url(#${chartId}-gradient-mask-${dataKey})`}\n            />\n          </pattern>\n        </g>\n      ))}\n    </>\n  );\n};\n\n// Reverse gradient for the area chart - vertical fade (top transparent, bottom visible)\nconst ReverseGradientStyle = ({\n  chartConfig,\n  chartId,\n}: {\n  chartConfig: ChartConfig;\n  chartId: string;\n}) => {\n  return (\n    <>\n      {/* Vertical reverse fade gradient for mask */}\n      <linearGradient id={`${chartId}-vertical-fade-reverse`} x1=\"0\" y1=\"0\" x2=\"0\" y2=\"1\">\n        <stop offset=\"0%\" stopColor=\"white\" stopOpacity={0} />\n        <stop offset=\"100%\" stopColor=\"white\" stopOpacity={0.1} />\n      </linearGradient>\n\n      {Object.keys(chartConfig).map((dataKey) => (\n        <g key={`${chartId}-gradient-reverse-group-${dataKey}`}>\n          {/* Mask for reverse vertical fade */}\n          <mask id={`${chartId}-gradient-reverse-mask-${dataKey}`}>\n            <rect width=\"100%\" height=\"100%\" fill={`url(#${chartId}-vertical-fade-reverse)`} />\n          </mask>\n\n          {/* Pattern: horizontal gradient + reverse vertical mask */}\n          <pattern\n            id={`${chartId}-gradient-reverse-${dataKey}`}\n            patternUnits=\"userSpaceOnUse\"\n            width=\"100%\"\n            height=\"100%\"\n          >\n            <rect\n              width=\"100%\"\n              height=\"100%\"\n              fill={`url(#${chartId}-colors-${dataKey})`}\n              mask={`url(#${chartId}-gradient-reverse-mask-${dataKey})`}\n            />\n          </pattern>\n        </g>\n      ))}\n    </>\n  );\n};\n\n// Lines pattern for the area chart - diagonal lines with gradient\nconst LinesPatternStyle = ({\n  chartConfig,\n  chartId,\n}: {\n  chartConfig: ChartConfig;\n  chartId: string;\n}) => {\n  return (\n    <>\n      {/* Shared diagonal lines pattern for mask */}\n      <pattern\n        id={`${chartId}-lines-mask-pattern`}\n        patternUnits=\"userSpaceOnUse\"\n        width=\"5\"\n        height=\"5\"\n        patternTransform=\"rotate(45)\"\n      >\n        <line x1=\"0\" y1=\"0\" x2=\"0\" y2=\"5\" stroke=\"white\" strokeWidth=\"1\" />\n      </pattern>\n\n      {Object.keys(chartConfig).map((dataKey) => (\n        <g key={`${chartId}-lines-group-${dataKey}`}>\n          {/* Mask using diagonal lines */}\n          <mask id={`${chartId}-lines-mask-${dataKey}`}>\n            <rect\n              width=\"100%\"\n              height=\"100%\"\n              fill={`url(#${chartId}-lines-mask-pattern)`}\n              fillOpacity=\"0.3\"\n            />\n          </mask>\n\n          {/* Pattern: gradient fill masked by diagonal lines */}\n          <pattern\n            id={`${chartId}-lines-${dataKey}`}\n            patternUnits=\"userSpaceOnUse\"\n            width=\"100%\"\n            height=\"100%\"\n          >\n            <rect\n              width=\"100%\"\n              height=\"100%\"\n              fill={`url(#${chartId}-colors-${dataKey})`}\n              mask={`url(#${chartId}-lines-mask-${dataKey})`}\n            />\n          </pattern>\n        </g>\n      ))}\n    </>\n  );\n};\n\n// Dotted pattern for the area chart - dots with gradient\nconst DottedPatternStyle = ({\n  chartConfig,\n  chartId,\n}: {\n  chartConfig: ChartConfig;\n  chartId: string;\n}) => {\n  return (\n    <>\n      {/* Shared dots pattern for mask */}\n      <pattern\n        id={`${chartId}-dotted-mask-pattern`}\n        x=\"0\"\n        y=\"0\"\n        width=\"6\"\n        height=\"6\"\n        patternUnits=\"userSpaceOnUse\"\n      >\n        <circle cx=\"4\" cy=\"4\" r=\"0.5\" fill=\"white\" />\n      </pattern>\n\n      {Object.keys(chartConfig).map((dataKey) => (\n        <g key={`${chartId}-dotted-group-${dataKey}`}>\n          {/* Mask using dots pattern */}\n          <mask id={`${chartId}-dotted-mask-${dataKey}`}>\n            <rect\n              width=\"100%\"\n              height=\"100%\"\n              fill={`url(#${chartId}-dotted-mask-pattern)`}\n              fillOpacity=\"0.5\"\n            />\n          </mask>\n\n          {/* Pattern: gradient fill masked by dots */}\n          <pattern\n            id={`${chartId}-dotted-${dataKey}`}\n            patternUnits=\"userSpaceOnUse\"\n            width=\"100%\"\n            height=\"100%\"\n          >\n            <rect\n              width=\"100%\"\n              height=\"100%\"\n              fill={`url(#${chartId}-colors-${dataKey})`}\n              mask={`url(#${chartId}-dotted-mask-${dataKey})`}\n            />\n          </pattern>\n        </g>\n      ))}\n    </>\n  );\n};\n\n// Diagonal lines pattern for non-selected areas\nconst UnselectedDiagonalPatternStyle = ({\n  chartConfig,\n  chartId,\n  selectedDataKey,\n  isClickable,\n}: {\n  chartConfig: ChartConfig;\n  chartId: string;\n  selectedDataKey: string | null;\n  isClickable: boolean;\n}) => {\n  if (!isClickable || selectedDataKey === null) return null;\n\n  return (\n    <>\n      {/* Shared diagonal lines pattern for mask (white lines) */}\n      <pattern\n        id={`${chartId}-unselected-lines-mask-pattern`}\n        patternUnits=\"userSpaceOnUse\"\n        width=\"5\"\n        height=\"5\"\n        patternTransform=\"rotate(45)\"\n      >\n        <line x1=\"0\" y1=\"0\" x2=\"0\" y2=\"5\" stroke=\"white\" strokeWidth=\"1\" />\n      </pattern>\n\n      {Object.keys(chartConfig).map((dataKey) => {\n        const isSelected = selectedDataKey === dataKey;\n        if (isSelected) return null;\n\n        return (\n          <g key={`${chartId}-unselected-group-${dataKey}`}>\n            {/* Mask using diagonal lines pattern */}\n            <mask id={`${chartId}-unselected-mask-${dataKey}`}>\n              <rect\n                width=\"100%\"\n                height=\"100%\"\n                fill={`url(#${chartId}-unselected-lines-mask-pattern)`}\n                fillOpacity=\"0.3\"\n              />\n            </mask>\n\n            {/* Pattern: gradient fill masked by diagonal lines */}\n            <pattern\n              id={`${chartId}-unselected-${dataKey}`}\n              patternUnits=\"userSpaceOnUse\"\n              width=\"100%\"\n              height=\"100%\"\n            >\n              <rect\n                width=\"100%\"\n                height=\"100%\"\n                fill={`url(#${chartId}-colors-${dataKey})`}\n                mask={`url(#${chartId}-unselected-mask-${dataKey})`}\n              />\n            </pattern>\n          </g>\n        );\n      })}\n    </>\n  );\n};\n\n// Hatched pattern with striped gradient effect\nconst HatchedPatternStyle = ({\n  chartConfig,\n  chartId,\n}: {\n  chartConfig: ChartConfig;\n  chartId: string;\n}) => {\n  return (\n    <>\n      {/* Shared hatched stripes mask pattern */}\n      <linearGradient id={`${chartId}-hatched-stripe-gradient`} x1=\"0\" y1=\"0\" x2=\"1\" y2=\"0\">\n        <stop offset=\"50%\" stopColor=\"white\" stopOpacity={0.2} />\n        <stop offset=\"50%\" stopColor=\"white\" stopOpacity={1} />\n      </linearGradient>\n      <pattern\n        id={`${chartId}-hatched-mask-pattern`}\n        x=\"0\"\n        y=\"0\"\n        width=\"20\"\n        height=\"10\"\n        patternUnits=\"userSpaceOnUse\"\n        overflow=\"visible\"\n        patternTransform=\"rotate(20)\"\n      >\n        <rect width=\"20\" height=\"10\" fill={`url(#${chartId}-hatched-stripe-gradient)`} />\n      </pattern>\n\n      {Object.keys(chartConfig).map((dataKey) => (\n        <g key={`${chartId}-hatched-group-${dataKey}`}>\n          {/* Mask using hatched stripes */}\n          <mask id={`${chartId}-hatched-mask-${dataKey}`}>\n            <rect\n              width=\"100%\"\n              height=\"100%\"\n              fill={`url(#${chartId}-hatched-mask-pattern)`}\n              fillOpacity=\"0.2\"\n            />\n          </mask>\n\n          {/* Pattern: gradient fill masked by hatched stripes */}\n          <pattern\n            id={`${chartId}-hatched-pattern-${dataKey}`}\n            patternUnits=\"userSpaceOnUse\"\n            width=\"100%\"\n            height=\"100%\"\n          >\n            <rect\n              width=\"100%\"\n              height=\"100%\"\n              fill={`url(#${chartId}-colors-${dataKey})`}\n              mask={`url(#${chartId}-hatched-mask-${dataKey})`}\n            />\n          </pattern>\n        </g>\n      ))}\n    </>\n  );\n};\n\n// Generate gradient stops with smooth easing for loading animation\nconst generateEasedGradientStops = (\n  steps: number = 17,\n  minOpacity: number = 0.05,\n  maxOpacity: number = 0.9,\n) => {\n  return Array.from({ length: steps }, (_, i) => {\n    const t = i / (steps - 1); // 0 to 1\n    // Sine-based bell curve easing: peaks at center (t=0.5), smooth falloff at edges\n    const eased = Math.sin(t * Math.PI) ** 2;\n    const opacity = minOpacity + eased * (maxOpacity - minOpacity);\n    return { offset: `${(t * 100).toFixed(0)}%`, opacity: Number(opacity.toFixed(3)) };\n  });\n};\n\n/**\n * Hook to manage loading data with pixel-perfect shimmer synchronization.\n *\n * Uses motion.dev's onAnimationComplete callback to ensure chart data\n * is only regenerated when the shimmer has completely exited the visible area.\n * This eliminates timing drift issues from setTimeout/setInterval.\n *\n * The shimmer pattern has 200-300% width so that when the visible shimmer\n * exits the chart container (at the 100% point), we can safely swap data\n * while the invisible portion continues animating.\n */\nexport function useLoadingData(isLoading: boolean, loadingPoints: number = 14) {\n  const [loadingDataKey, setLoadingDataKey] = useState(false);\n\n  // Callback fired by motion.dev when shimmer exits visible area\n  const onShimmerExit = useCallback(() => {\n    if (isLoading) {\n      setLoadingDataKey((prev) => !prev);\n    }\n  }, [isLoading]);\n\n  const loadingData = useMemo(\n    () => getLoadingData(loadingPoints),\n    // loadingDataKey toggle triggers re-computation when shimmer exits\n    // eslint-disable-next-line react-hooks/exhaustive-deps\n    [loadingPoints, loadingDataKey],\n  );\n\n  return { loadingData, onShimmerExit };\n}\n\n/**\n * Loading area pattern with animated skeleton effect using motion.dev\n *\n * Key design for pixel-perfect sync:\n * - Visible chart area is normalized to 0-1 in objectBoundingBox units\n * - Shimmer gradient has width=1 (same as visible area)\n * - Pattern width is 3x (300%) to provide buffer on both sides\n * - Animation: x goes from -1 (off-screen left) to 2 (off-screen right)\n * - At x=-1: shimmer is completely outside left edge\n * - At x=0: shimmer starts entering from left\n * - At x=1: shimmer has fully exited right edge\n * - At x=2: shimmer is in the right buffer zone\n * - onShimmerExit fires when x crosses 1 (shimmer fully exited visible area)\n * - Data swaps happen while shimmer is outside visible area (x >= 1)\n * - Loop continues infinitely\n */\nconst LoadingAreaPatternStyle = ({\n  chartId,\n  onShimmerExit,\n}: {\n  chartId: string;\n  onShimmerExit: () => void;\n}) => {\n  const gradientStops = generateEasedGradientStops();\n\n  // Pattern width needs to accommodate: 1 (left buffer) + 1 (visible) + 1 (right buffer) = 3\n  const patternWidth = 3;\n\n  // Animation goes from -1 (left of visible) to 2 (right of visible)\n  // Total travel distance = 3, matching pattern width\n  const startX = -1;\n  const endX = 2;\n\n  // Track last x value to detect threshold crossing\n  const lastXRef = useRef(startX);\n\n  return (\n    <>\n      {/* Gradient for smooth fade: edges dim, middle bright for sweep effect */}\n      <linearGradient id={`${chartId}-loading-mask-gradient`} x1=\"0\" y1=\"0\" x2=\"1\" y2=\"0\">\n        {gradientStops.map(({ offset, opacity }) => (\n          <stop key={offset} offset={offset} stopColor=\"white\" stopOpacity={opacity} />\n        ))}\n      </linearGradient>\n      <pattern\n        id={`${chartId}-loading-mask-pattern`}\n        patternUnits=\"objectBoundingBox\"\n        patternContentUnits=\"objectBoundingBox\"\n        patternTransform=\"rotate(25)\"\n        width={patternWidth}\n        height=\"1\"\n        x=\"0\"\n        y=\"0\"\n      >\n        {/* Use motion.rect with keyframe animation for precise timing */}\n        <motion.rect\n          y=\"0\"\n          width=\"1\"\n          height=\"1\"\n          fill={`url(#${chartId}-loading-mask-gradient)`}\n          initial={{ x: startX }}\n          animate={{ x: endX }}\n          transition={{\n            duration: LOADING_ANIMATION_DURATION / 1000,\n            ease: \"linear\",\n            repeat: Infinity,\n            repeatType: \"loop\",\n          }}\n          // Use onUpdate to fire callback at precise exit point\n          onUpdate={(latest) => {\n            const xValue = typeof latest.x === \"number\" ? latest.x : startX;\n            const lastX = lastXRef.current;\n\n            // Fire when crossing the exit threshold (x >= 1 means shimmer fully exited right)\n            if (xValue >= 1 && lastX < 1) {\n              onShimmerExit();\n            }\n\n            // Update tracked value\n            lastXRef.current = xValue;\n          }}\n        />\n      </pattern>\n      {/* Masking */}\n      <mask id={`${chartId}-loading-mask`} maskUnits=\"userSpaceOnUse\">\n        <rect width=\"100%\" height=\"100%\" fill={`url(#${chartId}-loading-mask-pattern)`} />\n      </mask>\n    </>\n  );\n};\n"
  },
  {
    "path": "apps/web/src/components/evilcharts/charts/bar-chart.tsx",
    "content": "\"use client\";\n\nimport { motion } from \"motion/react\";\nimport { useCallback, useId, useMemo, useRef, useState, type ComponentProps } from \"react\";\nimport { Bar, BarChart, CartesianGrid, Rectangle, ReferenceLine, XAxis, YAxis } from \"recharts\";\nimport { RectRadius } from \"recharts/types/shape/Rectangle\";\n\nimport { ChartBackground, type BackgroundVariant } from \"@/components/evilcharts/ui/background\";\nimport {\n  type ChartConfig,\n  ChartContainer,\n  getColorsCount,\n  getLoadingData,\n  LoadingIndicator,\n} from \"@/components/evilcharts/ui/chart\";\nimport {\n  EvilBrush,\n  useEvilBrush,\n  type EvilBrushRange,\n} from \"@/components/evilcharts/ui/evil-brush\";\nimport {\n  ChartLegend,\n  ChartLegendContent,\n  type ChartLegendVariant,\n} from \"@/components/evilcharts/ui/legend\";\nimport {\n  ChartTooltip,\n  ChartTooltipContent,\n  type TooltipRoundness,\n  type TooltipVariant,\n} from \"@/components/evilcharts/ui/tooltip\";\n\n// Constants\nconst DEFAULT_BAR_RADIUS = 2;\nconst LOADING_BAR_DATA_KEY = \"loading\";\nconst LOADING_ANIMATION_DURATION = 2000; // in milliseconds\n\ntype ChartProps = ComponentProps<typeof BarChart>;\ntype XAxisProps = ComponentProps<typeof XAxis>;\ntype YAxisProps = ComponentProps<typeof YAxis>;\ntype BarVariant = \"default\" | \"hatched\" | \"duotone\" | \"duotone-reverse\" | \"gradient\" | \"stripped\";\ntype StackType = \"default\" | \"stacked\" | \"percent\";\ntype BarLayout = \"vertical\" | \"horizontal\";\n\n// Validating Types to make sure user have provided valid data according to chartConfig\ntype ValidateConfigKeys<TData, TConfig> = {\n  [K in keyof TConfig]: K extends keyof TData ? ChartConfig[string] : never;\n};\n\n// Extract only keys from TData where the value is a number (not string, boolean, etc.)\ntype NumericDataKeys<T> = {\n  [K in keyof T]: T[K] extends number ? K : never;\n}[keyof T];\n\ntype EvilBarChartProps<\n  TData extends Record<string, unknown>,\n  TConfig extends Record<string, ChartConfig[string]>,\n> = {\n  chartConfig: TConfig & ValidateConfigKeys<TData, TConfig>;\n  data: TData[];\n  xDataKey?: keyof TData & string;\n  yDataKey?: keyof TData & string;\n  className?: string;\n  chartProps?: ChartProps;\n  xAxisProps?: XAxisProps;\n  yAxisProps?: YAxisProps;\n  defaultSelectedDataKey?: string | null;\n  barVariant?: BarVariant;\n  stackType?: StackType;\n  layout?: BarLayout;\n  barRadius?: number;\n  barGap?: number;\n  barCategoryGap?: number;\n  tickGap?: number;\n  legendVariant?: ChartLegendVariant;\n  // Hide Stuffs\n  hideTooltip?: boolean;\n  hideCartesianGrid?: boolean;\n  hideLegend?: boolean;\n  // Tooltip\n  tooltipRoundness?: TooltipRoundness;\n  tooltipVariant?: TooltipVariant;\n  tooltipDefaultIndex?: number;\n  // Interactive Stuffs\n  enableHoverHighlight?: boolean;\n  isLoading?: boolean;\n  loadingBars?: number;\n  // Glow Effects\n  glowingBars?: NumericDataKeys<TData>[];\n  // Brush\n  showBrush?: boolean;\n  brushHeight?: number;\n  brushFormatLabel?: (value: unknown, index: number) => string;\n  onBrushChange?: (range: EvilBrushRange) => void;\n  // Background\n  backgroundVariant?: BackgroundVariant;\n  // Buffer Bar - renders last data point bars as hatched/lines style\n  enableBufferBar?: boolean;\n};\n\ntype EvilBarChartClickable = {\n  isClickable: true;\n  onSelectionChange?: (selectedDataKey: string | null) => void;\n};\n\ntype EvilBarChartNotClickable = {\n  isClickable?: false;\n  onSelectionChange?: never;\n};\n\ntype EvilBarChartPropsWithCallback<\n  TData extends Record<string, unknown>,\n  TConfig extends Record<string, ChartConfig[string]>,\n> = EvilBarChartProps<TData, TConfig> & (EvilBarChartClickable | EvilBarChartNotClickable);\n\nexport function EvilBarChart<\n  TData extends Record<string, unknown>,\n  TConfig extends Record<string, ChartConfig[string]>,\n>({\n  chartConfig,\n  data,\n  xDataKey,\n  yDataKey,\n  className,\n  chartProps,\n  xAxisProps,\n  yAxisProps,\n  defaultSelectedDataKey = null,\n  barVariant = \"default\",\n  stackType = \"default\",\n  layout = \"vertical\",\n  barRadius = DEFAULT_BAR_RADIUS,\n  barGap,\n  barCategoryGap,\n  tickGap = 8,\n  legendVariant,\n  hideTooltip = false,\n  hideCartesianGrid = false,\n  hideLegend = false,\n  tooltipRoundness,\n  tooltipVariant,\n  tooltipDefaultIndex,\n  isClickable = false,\n  enableHoverHighlight = false,\n  isLoading = false,\n  loadingBars,\n  glowingBars = [],\n  showBrush = false,\n  brushHeight,\n  brushFormatLabel,\n  onBrushChange,\n  onSelectionChange,\n  backgroundVariant,\n  enableBufferBar = false,\n}: EvilBarChartPropsWithCallback<TData, TConfig>) {\n  const [selectedDataKey, setSelectedDataKey] = useState<string | null>(defaultSelectedDataKey);\n  const [isMouseInChart, setIsMouseInChart] = useState(false);\n  const { loadingData, onShimmerExit } = useLoadingData(isLoading, loadingBars);\n  const chartId = useId().replace(/:/g, \"\"); // Remove colons for valid CSS selectors\n\n  // ── Zoom state ──────────────────────────────────────────────────────────\n  const { visibleData, brushProps } = useEvilBrush({ data });\n  const displayData = showBrush && !isLoading ? visibleData : data;\n\n  // Wrapper function to update state and call parent callback\n  const handleSelectionChange = useCallback(\n    (newSelectedDataKey: string | null) => {\n      setSelectedDataKey(newSelectedDataKey);\n      if (isClickable && onSelectionChange) {\n        onSelectionChange(newSelectedDataKey);\n      }\n    },\n    [onSelectionChange, isClickable],\n  );\n\n  const isStacked = stackType === \"stacked\" || stackType === \"percent\";\n  const isHorizontal = layout === \"horizontal\";\n  const hasMultiColorSeries = Object.values(chartConfig).some(\n    (config) => getColorsCount(config) > 1,\n  );\n  const useSolidDefaultBars = barVariant === \"default\" && !enableBufferBar && !hasMultiColorSeries;\n\n  return (\n    <ChartContainer\n      className={className}\n      config={chartConfig}\n      footer={\n        showBrush &&\n        !isLoading && (\n          <EvilBrush\n            data={data}\n            chartConfig={chartConfig}\n            xDataKey={xDataKey}\n            variant=\"bar\"\n            barRadius={barRadius}\n            height={brushHeight}\n            formatLabel={brushFormatLabel}\n            stacked={isStacked}\n            skipStyle\n            className=\"mt-1\"\n            {...brushProps}\n            onChange={(range) => {\n              brushProps.onChange(range);\n              onBrushChange?.(range);\n            }}\n          />\n        )\n      }\n    >\n      <LoadingIndicator isLoading={isLoading} />\n      <BarChart\n        id=\"evil-charts-bar-chart\"\n        accessibilityLayer\n        layout={isHorizontal ? \"vertical\" : \"horizontal\"}\n        data={isLoading ? loadingData : displayData}\n        barGap={barGap}\n        barCategoryGap={barCategoryGap}\n        stackOffset={stackType === \"percent\" ? \"expand\" : undefined}\n        onMouseEnter={() => setIsMouseInChart(true)}\n        onMouseLeave={() => setIsMouseInChart(false)}\n        {...chartProps}\n      >\n        {backgroundVariant && <ChartBackground variant={backgroundVariant} />}\n        <ReferenceLine color=\"white\" />\n        {!hideCartesianGrid && !backgroundVariant && (\n          <CartesianGrid vertical={isHorizontal} horizontal={!isHorizontal} strokeDasharray=\"3 3\" />\n        )}\n        {!hideLegend && (\n          <ChartLegend\n            verticalAlign=\"top\"\n            align=\"right\"\n            content={\n              <ChartLegendContent\n                selected={selectedDataKey}\n                onSelectChange={handleSelectionChange}\n                isClickable={isClickable}\n                variant={legendVariant}\n              />\n            }\n          />\n        )}\n        {xDataKey && !isLoading && (\n          <XAxis\n            dataKey={isHorizontal ? undefined : xDataKey}\n            type={isHorizontal ? \"number\" : \"category\"}\n            tickLine={false}\n            axisLine={false}\n            tickMargin={8}\n            minTickGap={tickGap}\n            {...xAxisProps}\n          />\n        )}\n        {(isHorizontal ? xDataKey : yDataKey) && !isLoading && (\n          <YAxis\n            dataKey={isHorizontal ? xDataKey : yDataKey}\n            type={isHorizontal ? \"category\" : \"number\"}\n            tickLine={false}\n            axisLine={false}\n            tickMargin={8}\n            minTickGap={tickGap}\n            width=\"auto\"\n            {...yAxisProps}\n          />\n        )}\n        {!hideTooltip && !isLoading && (\n          <ChartTooltip\n            cursor={false}\n            defaultIndex={tooltipDefaultIndex}\n            content={\n              <ChartTooltipContent\n                selected={selectedDataKey}\n                roundness={tooltipRoundness}\n                variant={tooltipVariant}\n              />\n            }\n          />\n        )}\n        {!isLoading &&\n          Object.keys(chartConfig).map((dataKey) => {\n            const isGlowing = glowingBars.includes(dataKey as NumericDataKeys<TData>);\n            const filter = isGlowing ? `url(#${chartId}-bar-glow-${dataKey})` : undefined;\n\n            // Shared props for both shape and activeBar\n            const customBarProps = {\n              chartId,\n              dataKey,\n              barVariant,\n              barRadius,\n              filter,\n              isClickable,\n              enableHoverHighlight,\n              isMouseInChart,\n              selectedDataKey,\n              useSolidFill: useSolidDefaultBars,\n              enableBufferBar,\n              dataLength: displayData.length,\n              onClick: () => {\n                if (!isClickable) return;\n                handleSelectionChange(selectedDataKey === dataKey ? null : dataKey);\n              },\n            };\n\n            return (\n              <Bar\n                key={dataKey}\n                dataKey={dataKey}\n                stackId={isStacked ? \"evil-stacked\" : undefined}\n                fill={\n                  useSolidDefaultBars\n                    ? getSeriesColor(dataKey)\n                    : `url(#${chartId}-colors-${dataKey})`\n                }\n                radius={barRadius}\n                style={isClickable || enableHoverHighlight ? { cursor: \"pointer\" } : undefined}\n                shape={(props: unknown) => (\n                  <CustomBar {...(props as BarShapeProps)} {...customBarProps} />\n                )}\n                activeBar={(props: unknown) => (\n                  <CustomBar {...(props as BarShapeProps)} {...customBarProps} />\n                )}\n              />\n            );\n          })}\n        {/* ======== LOADING BAR ======== */}\n        {isLoading && (\n          <Bar\n            dataKey={LOADING_BAR_DATA_KEY}\n            fill=\"currentColor\"\n            fillOpacity={0.15}\n            radius={barRadius}\n            isAnimationActive={false}\n            legendType=\"none\"\n            style={{ mask: `url(#${chartId}-loading-mask)` }}\n          />\n        )}\n        {/* ======== CHART STYLES ======== */}\n        <defs>\n          {isLoading && <LoadingBarPatternStyle chartId={chartId} onShimmerExit={onShimmerExit} />}\n          {/* Shared vertical color gradient for textured, buffer, or multi-color bar variants */}\n          {!useSolidDefaultBars && (\n            <VerticalColorGradientStyle chartConfig={chartConfig} chartId={chartId} />\n          )}\n          {/* Variant-specific styles */}\n          {barVariant === \"hatched\" && (\n            <HatchedPatternStyle chartConfig={chartConfig} chartId={chartId} />\n          )}\n          {barVariant === \"duotone\" && (\n            <DuotonePatternStyle chartConfig={chartConfig} chartId={chartId} />\n          )}\n          {barVariant === \"duotone-reverse\" && (\n            <DuotoneReversePatternStyle chartConfig={chartConfig} chartId={chartId} />\n          )}\n          {barVariant === \"gradient\" && (\n            <GradientPatternStyle chartConfig={chartConfig} chartId={chartId} />\n          )}\n          {barVariant === \"stripped\" && (\n            <StrippedPatternStyle chartConfig={chartConfig} chartId={chartId} />\n          )}\n          {/* Buffer bar hatched pattern - always rendered when enableBufferBar */}\n          {enableBufferBar && (\n            <BufferHatchedPatternStyle chartConfig={chartConfig} chartId={chartId} />\n          )}\n          {/* Glow filter for glowing bars */}\n          {glowingBars.length > 0 && (\n            <GlowFilterStyle chartId={chartId} glowingBars={glowingBars as string[]} />\n          )}\n        </defs>\n      </BarChart>\n    </ChartContainer>\n  );\n}\n\n// Types for custom bar shape\ntype BarShapeProps = {\n  x?: number;\n  y?: number;\n  width?: number;\n  height?: number;\n  fill?: string;\n  fillOpacity?: number;\n  dataKey?: string;\n  index?: number;\n  [key: string]: unknown;\n};\n\ntype CustomBarProps = {\n  chartId: string;\n  dataKey: string;\n  barVariant: BarVariant;\n  barRadius: number;\n  filter?: string;\n  isClickable?: boolean;\n  enableHoverHighlight?: boolean;\n  isMouseInChart?: boolean;\n  selectedDataKey?: string | null;\n  useSolidFill?: boolean;\n  isActive?: boolean;\n  enableBufferBar?: boolean;\n  dataLength?: number;\n  onClick?: () => void;\n} & BarShapeProps;\n\n// Custom bar shape component for different variants\nconst CustomBar = (props: CustomBarProps) => {\n  const {\n    x = 0,\n    y = 0,\n    width = 0,\n    height = 0,\n    chartId,\n    dataKey,\n    barVariant,\n    barRadius,\n    filter,\n    isClickable,\n    enableHoverHighlight,\n    isMouseInChart,\n    selectedDataKey,\n    useSolidFill,\n    isActive,\n    enableBufferBar,\n    dataLength = 0,\n    onClick,\n  } = props;\n\n  const index = typeof props.index === \"number\" ? props.index : -1;\n  const isLastBar = enableBufferBar && dataLength > 0 && index === dataLength - 1;\n  const isStripped = barVariant === \"stripped\";\n\n  const getFill = () => {\n    // Buffer bar: last bar always uses hatched pattern\n    if (isLastBar) {\n      return `url(#${chartId}-buffer-hatched-${dataKey})`;\n    }\n\n    switch (barVariant) {\n      case \"hatched\":\n        return `url(#${chartId}-hatched-${dataKey})`;\n      case \"duotone\":\n        return `url(#${chartId}-duotone-${dataKey})`;\n      case \"duotone-reverse\":\n        return `url(#${chartId}-duotone-reverse-${dataKey})`;\n      case \"gradient\":\n        return `url(#${chartId}-gradient-${dataKey})`;\n      case \"stripped\":\n        return `url(#${chartId}-stripped-${dataKey})`;\n      default:\n        return useSolidFill ? getSeriesColor(dataKey) : `url(#${chartId}-colors-${dataKey})`;\n    }\n  };\n\n  const fillOpacity = getBarOpacity({\n    isClickable,\n    selectedDataKey,\n    dataKey,\n    enableHoverHighlight,\n    isMouseInChart,\n    isActive,\n  });\n  const cursorStyle = isClickable || enableHoverHighlight ? { cursor: \"pointer\" } : undefined;\n\n  // For stripped: top corners rounded, bottom flat [topLeft, topRight, bottomRight, bottomLeft]\n  // For others: all corners rounded\n  const radius: RectRadius = isStripped ? [barRadius, barRadius, 0, 0] : barRadius;\n\n  return (\n    <g style={cursorStyle} onClick={onClick}>\n      {/* Transparent rectangle for full column hit area */}\n      <Rectangle {...props} fill=\"transparent\" />\n      {/* Visible bar with animated opacity */}\n      <Rectangle\n        x={x}\n        y={y}\n        width={width}\n        opacity={fillOpacity}\n        height={Math.max(0, height - 3)}\n        radius={radius}\n        fill={getFill()}\n        filter={filter}\n        stroke={\n          isLastBar\n            ? useSolidFill\n              ? getSeriesColor(dataKey)\n              : `url(#${chartId}-colors-${dataKey})`\n            : undefined\n        }\n        strokeWidth={isLastBar ? 1 : undefined}\n      />\n      {/* Top border strip for stripped variant */}\n      {isStripped && (\n        <Rectangle\n          x={x}\n          y={y - 4}\n          width={width}\n          height={2}\n          radius={1}\n          fill={useSolidFill ? getSeriesColor(dataKey) : `url(#${chartId}-colors-${dataKey})`}\n        />\n      )}\n    </g>\n  );\n};\n\nconst getSeriesColor = (dataKey: string) => `var(--color-${dataKey}-0)`;\n\n// Shared vertical color gradient (top to bottom) - used for bar fill\nconst VerticalColorGradientStyle = ({\n  chartConfig,\n  chartId,\n}: {\n  chartConfig: ChartConfig;\n  chartId: string;\n}) => {\n  return (\n    <>\n      {Object.entries(chartConfig).map(([dataKey, config]) => {\n        const colorsCount = getColorsCount(config);\n\n        return (\n          <linearGradient\n            key={`${chartId}-colors-${dataKey}`}\n            id={`${chartId}-colors-${dataKey}`}\n            x1=\"0\"\n            y1=\"0\"\n            x2=\"0\"\n            y2=\"1\"\n          >\n            {colorsCount === 1 ? (\n              <>\n                <stop offset=\"0%\" stopColor={`var(--color-${dataKey}-0)`} />\n                <stop offset=\"100%\" stopColor={`var(--color-${dataKey}-0)`} />\n              </>\n            ) : (\n              Array.from({ length: colorsCount }, (_, index) => (\n                <stop\n                  key={index}\n                  offset={`${(index / (colorsCount - 1)) * 100}%`}\n                  stopColor={`var(--color-${dataKey}-${index}, var(--color-${dataKey}-0))`}\n                />\n              ))\n            )}\n          </linearGradient>\n        );\n      })}\n    </>\n  );\n};\n\n// Hatched pattern style for bars - uses mask to preserve gradient colors\nconst HatchedPatternStyle = ({\n  chartConfig,\n  chartId,\n}: {\n  chartConfig: ChartConfig;\n  chartId: string;\n}) => {\n  return (\n    <>\n      {/* Shared hatched stripes mask pattern */}\n      <pattern\n        id={`${chartId}-hatched-mask-pattern`}\n        x=\"0\"\n        y=\"0\"\n        width=\"5\"\n        height=\"5\"\n        patternUnits=\"userSpaceOnUse\"\n        patternTransform=\"rotate(-45)\"\n      >\n        <rect width=\"5\" height=\"5\" fill=\"white\" fillOpacity={0.3} />\n        <rect width=\"1.5\" height=\"5\" fill=\"white\" fillOpacity={1} />\n      </pattern>\n\n      {Object.keys(chartConfig).map((dataKey) => (\n        <g key={`${chartId}-hatched-group-${dataKey}`}>\n          {/* Mask using hatched stripes */}\n          <mask id={`${chartId}-hatched-mask-${dataKey}`}>\n            <rect width=\"100%\" height=\"100%\" fill={`url(#${chartId}-hatched-mask-pattern)`} />\n          </mask>\n\n          {/* Pattern: gradient fill masked by hatched stripes */}\n          <pattern\n            id={`${chartId}-hatched-${dataKey}`}\n            patternUnits=\"userSpaceOnUse\"\n            width=\"100%\"\n            height=\"100%\"\n          >\n            <rect\n              width=\"100%\"\n              height=\"100%\"\n              fill={`url(#${chartId}-colors-${dataKey})`}\n              mask={`url(#${chartId}-hatched-mask-${dataKey})`}\n            />\n          </pattern>\n        </g>\n      ))}\n    </>\n  );\n};\n\n// Buffer hatched pattern style - diagonal lines only (no background fill), used for the last bar when enableBufferBar is true\nconst BufferHatchedPatternStyle = ({\n  chartConfig,\n  chartId,\n}: {\n  chartConfig: ChartConfig;\n  chartId: string;\n}) => {\n  return (\n    <>\n      {/* Shared buffer hatched stripes mask pattern - lines only, no background */}\n      <pattern\n        id={`${chartId}-buffer-hatched-mask-pattern`}\n        x=\"0\"\n        y=\"0\"\n        width=\"5\"\n        height=\"5\"\n        patternUnits=\"userSpaceOnUse\"\n        patternTransform=\"rotate(-45)\"\n      >\n        <rect width=\"5\" height=\"5\" fill=\"black\" fillOpacity={0} />\n        <rect width=\"1\" height=\"5\" fill=\"white\" fillOpacity={1} />\n      </pattern>\n\n      {Object.keys(chartConfig).map((dataKey) => (\n        <g key={`${chartId}-buffer-hatched-group-${dataKey}`}>\n          {/* Mask using buffer hatched stripes */}\n          <mask id={`${chartId}-buffer-hatched-mask-${dataKey}`}>\n            <rect\n              width=\"100%\"\n              height=\"100%\"\n              fill={`url(#${chartId}-buffer-hatched-mask-pattern)`}\n            />\n          </mask>\n\n          {/* Pattern: gradient fill masked by buffer hatched stripes - lines only */}\n          <pattern\n            id={`${chartId}-buffer-hatched-${dataKey}`}\n            patternUnits=\"userSpaceOnUse\"\n            width=\"100%\"\n            height=\"100%\"\n          >\n            <rect\n              width=\"100%\"\n              height=\"100%\"\n              fill={`url(#${chartId}-colors-${dataKey})`}\n              mask={`url(#${chartId}-buffer-hatched-mask-${dataKey})`}\n            />\n          </pattern>\n        </g>\n      ))}\n    </>\n  );\n};\n\n// Duotone pattern style for bars (half opacity, half full) - uses objectBoundingBox for per-bar effect\nconst DuotonePatternStyle = ({\n  chartConfig,\n  chartId,\n}: {\n  chartConfig: ChartConfig;\n  chartId: string;\n}) => {\n  return (\n    <>\n      {Object.entries(chartConfig).map(([dataKey, config]) => {\n        const colorsCount = getColorsCount(config);\n\n        return (\n          <g key={`${chartId}-duotone-group-${dataKey}`}>\n            {/* Duotone mask gradient - applies to each bar's bounding box */}\n            <linearGradient\n              id={`${chartId}-duotone-mask-gradient-${dataKey}`}\n              gradientUnits=\"objectBoundingBox\"\n              x1=\"0\"\n              y1=\"0\"\n              x2=\"1\"\n              y2=\"0\"\n            >\n              <stop offset=\"50%\" stopColor=\"white\" stopOpacity={0.4} />\n              <stop offset=\"50%\" stopColor=\"white\" stopOpacity={1} />\n            </linearGradient>\n\n            {/* Color gradient for this dataKey - applies to each bar's bounding box */}\n            <linearGradient\n              id={`${chartId}-duotone-colors-${dataKey}`}\n              gradientUnits=\"objectBoundingBox\"\n              x1=\"0\"\n              y1=\"0\"\n              x2=\"0\"\n              y2=\"1\"\n            >\n              {colorsCount === 1 ? (\n                <>\n                  <stop offset=\"0%\" stopColor={`var(--color-${dataKey}-0)`} />\n                  <stop offset=\"100%\" stopColor={`var(--color-${dataKey}-0)`} />\n                </>\n              ) : (\n                Array.from({ length: colorsCount }, (_, index) => (\n                  <stop\n                    key={index}\n                    offset={`${(index / (colorsCount - 1)) * 100}%`}\n                    stopColor={`var(--color-${dataKey}-${index}, var(--color-${dataKey}-0))`}\n                  />\n                ))\n              )}\n            </linearGradient>\n\n            {/* Mask for duotone effect */}\n            <mask id={`${chartId}-duotone-mask-${dataKey}`} maskContentUnits=\"objectBoundingBox\">\n              <rect\n                x=\"0\"\n                y=\"0\"\n                width=\"1\"\n                height=\"1\"\n                fill={`url(#${chartId}-duotone-mask-gradient-${dataKey})`}\n              />\n            </mask>\n\n            {/* Pattern: gradient fill with duotone mask */}\n            <pattern\n              id={`${chartId}-duotone-${dataKey}`}\n              patternUnits=\"objectBoundingBox\"\n              patternContentUnits=\"objectBoundingBox\"\n              width=\"1\"\n              height=\"1\"\n            >\n              <rect\n                x=\"0\"\n                y=\"0\"\n                width=\"1\"\n                height=\"1\"\n                fill={`url(#${chartId}-duotone-colors-${dataKey})`}\n                mask={`url(#${chartId}-duotone-mask-${dataKey})`}\n              />\n            </pattern>\n          </g>\n        );\n      })}\n    </>\n  );\n};\n\n// Duotone reverse pattern style for bars (full opacity first, then half) - uses objectBoundingBox for per-bar effect\nconst DuotoneReversePatternStyle = ({\n  chartConfig,\n  chartId,\n}: {\n  chartConfig: ChartConfig;\n  chartId: string;\n}) => {\n  return (\n    <>\n      {Object.entries(chartConfig).map(([dataKey, config]) => {\n        const colorsCount = getColorsCount(config);\n\n        return (\n          <g key={`${chartId}-duotone-reverse-group-${dataKey}`}>\n            {/* Duotone reverse mask gradient - applies to each bar's bounding box */}\n            <linearGradient\n              id={`${chartId}-duotone-reverse-mask-gradient-${dataKey}`}\n              gradientUnits=\"objectBoundingBox\"\n              x1=\"0\"\n              y1=\"0\"\n              x2=\"1\"\n              y2=\"0\"\n            >\n              <stop offset=\"50%\" stopColor=\"white\" stopOpacity={1} />\n              <stop offset=\"50%\" stopColor=\"white\" stopOpacity={0.4} />\n            </linearGradient>\n\n            {/* Color gradient for this dataKey - applies to each bar's bounding box */}\n            <linearGradient\n              id={`${chartId}-duotone-reverse-colors-${dataKey}`}\n              gradientUnits=\"objectBoundingBox\"\n              x1=\"0\"\n              y1=\"0\"\n              x2=\"0\"\n              y2=\"1\"\n            >\n              {colorsCount === 1 ? (\n                <>\n                  <stop offset=\"0%\" stopColor={`var(--color-${dataKey}-0)`} />\n                  <stop offset=\"100%\" stopColor={`var(--color-${dataKey}-0)`} />\n                </>\n              ) : (\n                Array.from({ length: colorsCount }, (_, index) => (\n                  <stop\n                    key={index}\n                    offset={`${(index / (colorsCount - 1)) * 100}%`}\n                    stopColor={`var(--color-${dataKey}-${index}, var(--color-${dataKey}-0))`}\n                  />\n                ))\n              )}\n            </linearGradient>\n\n            {/* Mask for duotone reverse effect */}\n            <mask\n              id={`${chartId}-duotone-reverse-mask-${dataKey}`}\n              maskContentUnits=\"objectBoundingBox\"\n            >\n              <rect\n                x=\"0\"\n                y=\"0\"\n                width=\"1\"\n                height=\"1\"\n                fill={`url(#${chartId}-duotone-reverse-mask-gradient-${dataKey})`}\n              />\n            </mask>\n\n            {/* Pattern: gradient fill with duotone reverse mask */}\n            <pattern\n              id={`${chartId}-duotone-reverse-${dataKey}`}\n              patternUnits=\"objectBoundingBox\"\n              patternContentUnits=\"objectBoundingBox\"\n              width=\"1\"\n              height=\"1\"\n            >\n              <rect\n                x=\"0\"\n                y=\"0\"\n                width=\"1\"\n                height=\"1\"\n                fill={`url(#${chartId}-duotone-reverse-colors-${dataKey})`}\n                mask={`url(#${chartId}-duotone-reverse-mask-${dataKey})`}\n              />\n            </pattern>\n          </g>\n        );\n      })}\n    </>\n  );\n};\n\n// Gradient pattern style for bars (top to bottom fade) - uses mask to preserve gradient colors\nconst GradientPatternStyle = ({\n  chartConfig,\n  chartId,\n}: {\n  chartConfig: ChartConfig;\n  chartId: string;\n}) => {\n  return (\n    <>\n      {/* Shared vertical fade gradient for mask */}\n      <linearGradient id={`${chartId}-gradient-mask-gradient`} x1=\"0\" y1=\"0\" x2=\"0\" y2=\"1\">\n        <stop offset=\"20%\" stopColor=\"white\" stopOpacity={1} />\n        <stop offset=\"90%\" stopColor=\"white\" stopOpacity={0} />\n      </linearGradient>\n\n      {Object.keys(chartConfig).map((dataKey) => (\n        <g key={`${chartId}-gradient-group-${dataKey}`}>\n          {/* Mask for vertical fade */}\n          <mask id={`${chartId}-gradient-mask-${dataKey}`}>\n            <rect width=\"100%\" height=\"100%\" fill={`url(#${chartId}-gradient-mask-gradient)`} />\n          </mask>\n\n          {/* Pattern: gradient fill with vertical fade mask */}\n          <pattern\n            id={`${chartId}-gradient-${dataKey}`}\n            patternUnits=\"userSpaceOnUse\"\n            width=\"100%\"\n            height=\"100%\"\n          >\n            <rect\n              width=\"100%\"\n              height=\"100%\"\n              fill={`url(#${chartId}-colors-${dataKey})`}\n              mask={`url(#${chartId}-gradient-mask-${dataKey})`}\n            />\n          </pattern>\n        </g>\n      ))}\n    </>\n  );\n};\n\n// Stripped pattern style for bars (low opacity body with full opacity top) - uses mask to preserve gradient colors\nconst StrippedPatternStyle = ({\n  chartConfig,\n  chartId,\n}: {\n  chartConfig: ChartConfig;\n  chartId: string;\n}) => {\n  return (\n    <>\n      {/* Shared stripped fade gradient for mask */}\n      <linearGradient id={`${chartId}-stripped-mask-gradient`} x1=\"0\" y1=\"0\" x2=\"0\" y2=\"1\">\n        <stop offset=\"0%\" stopColor=\"white\" stopOpacity={0.2} />\n        <stop offset=\"100%\" stopColor=\"white\" stopOpacity={0.2} />\n      </linearGradient>\n\n      {Object.keys(chartConfig).map((dataKey) => (\n        <g key={`${chartId}-stripped-group-${dataKey}`}>\n          {/* Mask for stripped fade */}\n          <mask id={`${chartId}-stripped-mask-${dataKey}`}>\n            <rect width=\"100%\" height=\"100%\" fill={`url(#${chartId}-stripped-mask-gradient)`} />\n          </mask>\n\n          {/* Pattern: gradient fill with stripped fade mask */}\n          <pattern\n            id={`${chartId}-stripped-${dataKey}`}\n            patternUnits=\"userSpaceOnUse\"\n            width=\"100%\"\n            height=\"100%\"\n          >\n            <rect\n              width=\"100%\"\n              height=\"100%\"\n              fill={`url(#${chartId}-colors-${dataKey})`}\n              mask={`url(#${chartId}-stripped-mask-${dataKey})`}\n            />\n          </pattern>\n        </g>\n      ))}\n    </>\n  );\n};\n\n// Glow filter style for glowing bars\nconst GlowFilterStyle = ({ chartId, glowingBars }: { chartId: string; glowingBars: string[] }) => {\n  return (\n    <>\n      {glowingBars.map((dataKey) => (\n        <filter\n          key={`${chartId}-bar-glow-${dataKey}`}\n          id={`${chartId}-bar-glow-${dataKey}`}\n          x=\"-100%\"\n          y=\"-100%\"\n          width=\"300%\"\n          height=\"300%\"\n        >\n          <feGaussianBlur in=\"SourceGraphic\" stdDeviation=\"8\" result=\"blur\" />\n          <feColorMatrix\n            in=\"blur\"\n            type=\"matrix\"\n            values=\"1 0 0 0 0\n                    0 1 0 0 0\n                    0 0 1 0 0\n                    0 0 0 0.5 0\"\n            result=\"glow\"\n          />\n          <feMerge>\n            <feMergeNode in=\"glow\" />\n            <feMergeNode in=\"SourceGraphic\" />\n          </feMerge>\n        </filter>\n      ))}\n    </>\n  );\n};\n\n// Generate gradient stops with smooth easing for loading animation\nconst generateEasedGradientStops = (\n  steps: number = 17,\n  minOpacity: number = 0.05,\n  maxOpacity: number = 0.9,\n) => {\n  return Array.from({ length: steps }, (_, i) => {\n    const t = i / (steps - 1);\n    const eased = Math.sin(t * Math.PI) ** 2;\n    const opacity = minOpacity + eased * (maxOpacity - minOpacity);\n    return { offset: `${(t * 100).toFixed(0)}%`, opacity: Number(opacity.toFixed(3)) };\n  });\n};\n\n/**\n * Hook to manage loading data with pixel-perfect shimmer synchronization.\n */\nexport function useLoadingData(isLoading: boolean, loadingBars: number = 12) {\n  const [loadingDataKey, setLoadingDataKey] = useState(false);\n\n  const onShimmerExit = useCallback(() => {\n    if (isLoading) {\n      setLoadingDataKey((prev) => !prev);\n    }\n  }, [isLoading]);\n\n  const loadingData = useMemo(\n    () => getLoadingData(loadingBars, 20, 80),\n    // eslint-disable-next-line react-hooks/exhaustive-deps\n    [loadingBars, loadingDataKey],\n  );\n\n  return { loadingData, onShimmerExit };\n}\n\n/**\n * Loading bar pattern with animated skeleton effect\n */\nconst LoadingBarPatternStyle = ({\n  chartId,\n  onShimmerExit,\n}: {\n  chartId: string;\n  onShimmerExit: () => void;\n}) => {\n  const gradientStops = generateEasedGradientStops();\n  const patternWidth = 3;\n  const startX = -1;\n  const endX = 2;\n  const lastXRef = useRef(startX);\n\n  return (\n    <>\n      <linearGradient id={`${chartId}-loading-mask-gradient`} x1=\"0\" y1=\"0\" x2=\"1\" y2=\"0\">\n        {gradientStops.map(({ offset, opacity }) => (\n          <stop key={offset} offset={offset} stopColor=\"white\" stopOpacity={opacity} />\n        ))}\n      </linearGradient>\n      <pattern\n        id={`${chartId}-loading-mask-pattern`}\n        patternUnits=\"objectBoundingBox\"\n        patternContentUnits=\"objectBoundingBox\"\n        patternTransform=\"rotate(25)\"\n        width={patternWidth}\n        height=\"1\"\n        x=\"0\"\n        y=\"0\"\n      >\n        <motion.rect\n          y=\"0\"\n          width=\"1\"\n          height=\"1\"\n          fill={`url(#${chartId}-loading-mask-gradient)`}\n          initial={{ x: startX }}\n          animate={{ x: endX }}\n          transition={{\n            duration: LOADING_ANIMATION_DURATION / 1000,\n            ease: \"linear\",\n            repeat: Infinity,\n            repeatType: \"loop\",\n          }}\n          onUpdate={(latest) => {\n            const xValue = typeof latest.x === \"number\" ? latest.x : startX;\n            const lastX = lastXRef.current;\n            if (xValue >= 1 && lastX < 1) {\n              onShimmerExit();\n            }\n            lastXRef.current = xValue;\n          }}\n        />\n      </pattern>\n      <mask id={`${chartId}-loading-mask`} maskUnits=\"userSpaceOnUse\">\n        <rect width=\"100%\" height=\"100%\" fill={`url(#${chartId}-loading-mask-pattern)`} />\n      </mask>\n    </>\n  );\n};\n\n/**\n * Calculate bar opacity based on click selection and hover highlight state\n */\nconst getBarOpacity = ({\n  isClickable,\n  selectedDataKey,\n  dataKey,\n  enableHoverHighlight,\n  isMouseInChart,\n  isActive,\n}: {\n  isClickable?: boolean;\n  selectedDataKey?: string | null;\n  dataKey: string;\n  enableHoverHighlight?: boolean;\n  isMouseInChart?: boolean;\n  isActive?: boolean;\n}) => {\n  // Check if this dataKey is selected (for click selection)\n  const isSelectedDataKey = selectedDataKey === null || selectedDataKey === dataKey;\n\n  // Base opacity from click selection\n  const clickOpacity = isClickable && selectedDataKey !== null ? (isSelectedDataKey ? 1 : 0.3) : 1;\n\n  // If hover highlight is enabled and mouse is in chart\n  if (enableHoverHighlight && isMouseInChart) {\n    // Combine: if this bar is active/hovered, show full opacity (respecting click selection)\n    // If not hovered, dim it further\n    return isActive ? clickOpacity : clickOpacity * 0.3;\n  }\n\n  return clickOpacity;\n};\n"
  },
  {
    "path": "apps/web/src/components/evilcharts/charts/line-chart.tsx",
    "content": "\"use client\";\n\nimport { motion } from \"motion/react\";\nimport { useCallback, useId, useMemo, useRef, useState, type ComponentProps } from \"react\";\nimport {\n  CartesianGrid,\n  Curve,\n  Line,\n  LineChart,\n  ReferenceLine,\n  XAxis,\n  YAxis,\n  type CurveProps,\n} from \"recharts\";\n\nimport { ChartBackground, type BackgroundVariant } from \"@/components/evilcharts/ui/background\";\nimport {\n  type ChartConfig,\n  ChartContainer,\n  getColorsCount,\n  getLoadingData,\n  LoadingIndicator,\n} from \"@/components/evilcharts/ui/chart\";\nimport { ChartDot, DotVariant } from \"@/components/evilcharts/ui/dot\";\nimport {\n  EvilBrush,\n  useEvilBrush,\n  type EvilBrushRange,\n} from \"@/components/evilcharts/ui/evil-brush\";\nimport {\n  ChartLegend,\n  ChartLegendContent,\n  type ChartLegendVariant,\n} from \"@/components/evilcharts/ui/legend\";\nimport {\n  ChartTooltip,\n  ChartTooltipContent,\n  type TooltipRoundness,\n  type TooltipVariant,\n} from \"@/components/evilcharts/ui/tooltip\";\n\n// Constants\nconst STROKE_WIDTH = 1;\nconst LOADING_LINE_DATA_KEY = \"loading\";\nconst LOADING_ANIMATION_DURATION = 2000; // in milliseconds\n\ntype ChartProps = ComponentProps<typeof LineChart>;\ntype XAxisProps = ComponentProps<typeof XAxis>;\ntype YAxisProps = ComponentProps<typeof YAxis>;\ntype LineType = ComponentProps<typeof Line>[\"type\"];\ntype StrokeVariant = \"solid\" | \"dashed\" | \"animated-dashed\";\n\n// Validating Types to make sure user have provided valid data according to chartConfig\ntype ValidateConfigKeys<TData, TConfig> = {\n  [K in keyof TConfig]: K extends keyof TData ? ChartConfig[string] : never;\n};\n\n// Extract only keys from TData where the value is a number (not string, boolean, etc.)\ntype NumericDataKeys<T> = {\n  [K in keyof T]: T[K] extends number ? K : never;\n}[keyof T];\n\ntype EvilLineChartProps<\n  TData extends Record<string, unknown>,\n  TConfig extends Record<string, ChartConfig[string]>,\n> = {\n  chartConfig: TConfig & ValidateConfigKeys<TData, TConfig>;\n  data: TData[];\n  xDataKey?: keyof TData & string;\n  yDataKey?: keyof TData & string;\n  className?: string;\n  chartProps?: ChartProps;\n  xAxisProps?: XAxisProps;\n  yAxisProps?: YAxisProps;\n  defaultSelectedDataKey?: string | null;\n  curveType?: LineType;\n  strokeVariant?: StrokeVariant;\n  dotVariant?: DotVariant;\n  activeDotVariant?: DotVariant;\n  legendVariant?: ChartLegendVariant;\n  connectNulls?: boolean;\n  tickGap?: number;\n  // Hide Stuffs\n  hideTooltip?: boolean;\n  hideCartesianGrid?: boolean;\n  hideLegend?: boolean;\n  hideCursorLine?: boolean;\n  // Tooltip\n  tooltipRoundness?: TooltipRoundness;\n  tooltipVariant?: TooltipVariant;\n  tooltipDefaultIndex?: number;\n  // Interactive Stuffs\n  isLoading?: boolean;\n  loadingPoints?: number;\n  // Glow Effect\n  glowingLines?: NumericDataKeys<TData>[];\n  // Brush\n  showBrush?: boolean;\n  brushHeight?: number;\n  brushFormatLabel?: (value: unknown, index: number) => string;\n  onBrushChange?: (range: EvilBrushRange) => void;\n  // Background\n  backgroundVariant?: BackgroundVariant;\n  // Buffer Line - renders last segment as dashed/dotted\n  enableBufferLine?: boolean;\n};\n\ntype EvilLineChartClickable = {\n  isClickable: true;\n  onSelectionChange?: (selectedDataKey: string | null) => void;\n};\n\ntype EvilLineChartNotClickable = {\n  isClickable?: false;\n  onSelectionChange?: never;\n};\n\ntype EvilLineChartPropsWithCallback<\n  TData extends Record<string, unknown>,\n  TConfig extends Record<string, ChartConfig[string]>,\n> = EvilLineChartProps<TData, TConfig> & (EvilLineChartClickable | EvilLineChartNotClickable);\n\nexport function EvilLineChart<\n  TData extends Record<string, unknown>,\n  TConfig extends Record<string, ChartConfig[string]>,\n>({\n  chartConfig,\n  data,\n  xDataKey,\n  yDataKey,\n  className,\n  chartProps,\n  xAxisProps,\n  yAxisProps,\n  defaultSelectedDataKey = null,\n  curveType = \"linear\",\n  strokeVariant = \"solid\",\n  dotVariant,\n  activeDotVariant,\n  legendVariant,\n  connectNulls = false,\n  tickGap = 8,\n  hideTooltip = false,\n  hideCartesianGrid = false,\n  hideLegend = false,\n  hideCursorLine = false,\n  tooltipRoundness,\n  tooltipVariant,\n  tooltipDefaultIndex,\n  isClickable = false,\n  isLoading = false,\n  loadingPoints,\n  glowingLines = [],\n  showBrush = false,\n  brushHeight,\n  brushFormatLabel,\n  onBrushChange,\n  onSelectionChange,\n  backgroundVariant,\n  enableBufferLine = false,\n}: EvilLineChartPropsWithCallback<TData, TConfig>) {\n  const [selectedDataKey, setSelectedDataKey] = useState<string | null>(defaultSelectedDataKey);\n  const { loadingData, onShimmerExit } = useLoadingData(isLoading, loadingPoints);\n  const chartId = useId().replace(/:/g, \"\"); // Remove colons for valid CSS selectors\n\n  // ── Zoom state ──────────────────────────────────────────────────────────\n  const { visibleData, brushProps } = useEvilBrush({ data });\n  const displayData = showBrush && !isLoading ? visibleData : data;\n\n  // Wrapper function to update state and call parent callback\n  const handleSelectionChange = useCallback(\n    (newSelectedDataKey: string | null) => {\n      setSelectedDataKey(newSelectedDataKey);\n      if (isClickable && onSelectionChange) {\n        onSelectionChange(newSelectedDataKey);\n      }\n    },\n    [onSelectionChange, isClickable],\n  );\n\n  return (\n    <ChartContainer\n      className={className}\n      config={chartConfig}\n      footer={\n        showBrush &&\n        !isLoading && (\n          <EvilBrush\n            data={data}\n            chartConfig={chartConfig}\n            xDataKey={xDataKey}\n            variant=\"line\"\n            curveType={curveType}\n            strokeVariant={strokeVariant}\n            connectNulls={connectNulls}\n            height={brushHeight}\n            formatLabel={brushFormatLabel}\n            skipStyle\n            className=\"mt-1\"\n            {...brushProps}\n            onChange={(range) => {\n              brushProps.onChange(range);\n              onBrushChange?.(range);\n            }}\n          />\n        )\n      }\n    >\n      <LoadingIndicator isLoading={isLoading} />\n      <LineChart\n        id=\"evil-charts-line-chart\"\n        accessibilityLayer\n        data={isLoading ? loadingData : displayData}\n        {...chartProps}\n      >\n        {backgroundVariant && <ChartBackground variant={backgroundVariant} />}\n        <ReferenceLine color=\"white\" />\n        {!hideCartesianGrid && !backgroundVariant && (\n          <CartesianGrid vertical={false} strokeDasharray=\"3 3\" />\n        )}\n        {!hideLegend && (\n          <ChartLegend\n            verticalAlign=\"top\"\n            align=\"right\"\n            content={\n              <ChartLegendContent\n                selected={selectedDataKey}\n                onSelectChange={handleSelectionChange}\n                isClickable={isClickable}\n                variant={legendVariant}\n              />\n            }\n          />\n        )}\n        {xDataKey && !isLoading && (\n          <XAxis\n            dataKey={xDataKey}\n            tickLine={false}\n            axisLine={false}\n            tickMargin={8}\n            minTickGap={tickGap}\n            {...xAxisProps}\n          />\n        )}\n        {yDataKey && !isLoading && (\n          <YAxis\n            dataKey={yDataKey}\n            tickLine={false}\n            axisLine={false}\n            tickMargin={8}\n            minTickGap={tickGap}\n            width=\"auto\"\n            tickFormatter={yAxisProps?.tickFormatter}\n            {...yAxisProps}\n          />\n        )}\n        {!hideTooltip && !isLoading && (\n          <ChartTooltip\n            defaultIndex={tooltipDefaultIndex}\n            cursor={\n              hideCursorLine\n                ? false\n                : {\n                    strokeDasharray:\n                      strokeVariant === \"dashed\" || strokeVariant === \"animated-dashed\"\n                        ? \"3 3\"\n                        : undefined,\n                    strokeWidth: STROKE_WIDTH,\n                  }\n            }\n            content={\n              <ChartTooltipContent\n                selected={selectedDataKey}\n                roundness={tooltipRoundness}\n                variant={tooltipVariant}\n              />\n            }\n          />\n        )}\n        {!isLoading &&\n          Object.keys(chartConfig).map((dataKey) => {\n            const _opacity = getOpacity(isClickable, selectedDataKey, dataKey);\n            const hasSelection = selectedDataKey !== null;\n            const isGlowing = glowingLines.includes(dataKey as NumericDataKeys<TData>);\n            const filter = isGlowing ? `url(#${chartId}-line-glow-${dataKey})` : undefined;\n\n            const dot = dotVariant ? (\n              <ChartDot\n                fillOpacity={_opacity.dot}\n                type={dotVariant}\n                dataKey={dataKey}\n                chartId={chartId}\n              />\n            ) : (\n              false\n            );\n            const activeDot = activeDotVariant ? (\n              <ChartDot\n                fillOpacity={_opacity.dot}\n                type={activeDotVariant}\n                dataKey={dataKey}\n                chartId={chartId}\n              />\n            ) : (\n              false\n            );\n\n            return (\n              <g key={dataKey}>\n                {/* Transparent hit area for easier clicking */}\n                {isClickable && (\n                  <Line\n                    type={curveType}\n                    dataKey={dataKey}\n                    connectNulls={connectNulls}\n                    stroke=\"transparent\"\n                    strokeWidth={15}\n                    dot={false}\n                    activeDot={false}\n                    legendType=\"none\"\n                    tooltipType=\"none\"\n                    style={{ cursor: \"pointer\" }}\n                    onClick={() => {\n                      handleSelectionChange(selectedDataKey === dataKey ? null : dataKey);\n                    }}\n                  />\n                )}\n                {/* Visible line */}\n                <Line\n                  type={curveType}\n                  dataKey={dataKey}\n                  connectNulls={connectNulls}\n                  strokeOpacity={_opacity.stroke}\n                  stroke={`url(#${chartId}-colors-${dataKey})`}\n                  filter={filter}\n                  dot={dot}\n                  activeDot={activeDot}\n                  strokeWidth={STROKE_WIDTH}\n                  strokeDasharray={getStrokeDasharray(enableBufferLine, strokeVariant)}\n                  shape={enableBufferLine ? bufferLineShape : undefined}\n                  style={isClickable ? { cursor: \"pointer\" } : undefined}\n                  onClick={() => {\n                    if (!isClickable) return;\n\n                    // Toggle: if already selected, unselect; otherwise select\n                    setSelectedDataKey(selectedDataKey === dataKey ? null : dataKey);\n                  }}\n                >\n                  {strokeVariant === \"animated-dashed\" && !hasSelection && <AnimatedDashedStyle />}\n                </Line>\n              </g>\n            );\n          })}\n        {/* ======== LOADING LINE ======== */}\n        {isLoading && (\n          <Line\n            type={curveType}\n            dataKey={LOADING_LINE_DATA_KEY}\n            min={0}\n            max={100}\n            stroke=\"currentColor\"\n            strokeOpacity={0.5}\n            isAnimationActive={false}\n            legendType=\"none\"\n            tooltipType=\"none\"\n            activeDot={false}\n            dot={false}\n            strokeWidth={STROKE_WIDTH}\n            style={{ mask: `url(#${chartId}-loading-mask)` }}\n          />\n        )}\n        {/* ======== CHART STYLES ======== */}\n        <defs>\n          {isLoading && <LoadingLinePatternStyle chartId={chartId} onShimmerExit={onShimmerExit} />}\n          {/* Shared horizontal color gradient - always rendered for stroke */}\n          <HorizontalColorGradientStyle chartConfig={chartConfig} chartId={chartId} />\n          {/* Glow filter for glowing lines */}\n          {glowingLines.length > 0 && (\n            <GlowFilterStyle chartId={chartId} glowingLines={glowingLines as string[]} />\n          )}\n        </defs>\n      </LineChart>\n    </ChartContainer>\n  );\n}\n\n// Buffer line shape - renders the last segment as dashed while the rest stays solid.\n// Renders a single <Curve> and uses a ref callback to measure the actual SVG path\n// length via getTotalLength() + getPointAtLength(), then sets stroke-dasharray\n// imperatively. Works correctly with any curve type (linear, natural, monotone, etc.).\ntype CurvePoint = NonNullable<NonNullable<CurveProps[\"points\"]>[number]>;\ntype DrawableCurvePoint = CurvePoint & { x: number; y: number };\n\nconst isDrawableCurvePoint = (point: CurvePoint): point is DrawableCurvePoint => {\n  return typeof point.x === \"number\" && typeof point.y === \"number\";\n};\n\nconst BUFFER_DASH_SIZE = 4;\nconst BUFFER_GAP_SIZE = 3;\n\n/**\n * Binary-search the path to find the length at which path.x ≈ targetX.\n * Uses the browser's native getPointAtLength for exact curve measurement.\n */\nconst findLengthAtX = (path: SVGPathElement, totalLength: number, targetX: number): number => {\n  let lo = 0;\n  let hi = totalLength;\n  // ~0.5px precision is more than enough for a dasharray split\n  while (hi - lo > 0.5) {\n    const mid = (lo + hi) / 2;\n    const pt = path.getPointAtLength(mid);\n    if (pt.x < targetX) lo = mid;\n    else hi = mid;\n  }\n  return (lo + hi) / 2;\n};\n\nconst bufferLineShape = (props: CurveProps) => {\n  const { points, ...rest } = props;\n\n  if (!points || points.length < 2) {\n    return <Curve {...props} />;\n  }\n\n  const drawablePoints = points.filter(isDrawableCurvePoint);\n\n  if (drawablePoints.length < 2) {\n    return <Curve {...props} />;\n  }\n\n  // x coordinate of the second-to-last point — where solid meets dashed\n  const splitX = drawablePoints[drawablePoints.length - 2].x;\n\n  // Ref callback runs synchronously during React commit (before browser paint),\n  // so there's no visible flash of an un-dashed line.\n  const gRef = (g: SVGGElement | null) => {\n    if (!g) return;\n    const path = g.querySelector(\"path\");\n    if (!path) return;\n\n    const totalLength = path.getTotalLength();\n    const solidLength = findLengthAtX(path, totalLength, splitX);\n    const lastSegmentLength = totalLength - solidLength;\n\n    // Build dasharray: solid run, then repeating dash-gap for the buffer segment\n    const reps = Math.ceil(lastSegmentLength / (BUFFER_DASH_SIZE + BUFFER_GAP_SIZE)) + 1;\n    const dashedPart = Array.from(\n      { length: reps },\n      () => `${BUFFER_DASH_SIZE} ${BUFFER_GAP_SIZE}`,\n    ).join(\" \");\n\n    path.setAttribute(\"stroke-dasharray\", `${solidLength} 0 ${dashedPart}`);\n  };\n\n  return (\n    <g ref={gRef}>\n      <Curve {...rest} points={drawablePoints} />\n    </g>\n  );\n};\n\n// Returns opacity object for stroke and dot\nconst getOpacity = (isClickable: boolean, selectedDataKey: string | null, dataKey: string) => {\n  if (!isClickable || selectedDataKey === null) {\n    return { stroke: 1, dot: 1 };\n  }\n  return selectedDataKey === dataKey ? { stroke: 1, dot: 1 } : { stroke: 0.3, dot: 0.3 };\n};\n\nconst getStrokeDasharray = (enableBufferLine: boolean, strokeVariant: StrokeVariant) => {\n  if (enableBufferLine) {\n    return undefined;\n  }\n\n  if (strokeVariant === \"dashed\" || strokeVariant === \"animated-dashed\") {\n    return \"5 5\";\n  }\n\n  return undefined;\n};\n\n// Animated dashed-stroke style for the line chart\nconst AnimatedDashedStyle = () => {\n  return (\n    <>\n      <animate\n        attributeName=\"stroke-dasharray\"\n        values=\"5 5; 0 5; 5 5\"\n        dur=\"1s\"\n        repeatCount=\"indefinite\"\n        keyTimes=\"0;0.5;1\"\n      />\n      <animate\n        attributeName=\"stroke-dashoffset\"\n        values=\"0; -10\"\n        dur=\"1s\"\n        repeatCount=\"indefinite\"\n        keyTimes=\"0;1\"\n      />\n    </>\n  );\n};\n\n// Shared horizontal color gradient (left to right) - used for stroke\nconst HorizontalColorGradientStyle = ({\n  chartConfig,\n  chartId,\n}: {\n  chartConfig: ChartConfig;\n  chartId: string;\n}) => {\n  return (\n    <>\n      {Object.entries(chartConfig).map(([dataKey, config]) => {\n        const colorsCount = getColorsCount(config);\n\n        return (\n          <linearGradient\n            key={`${chartId}-colors-${dataKey}`}\n            id={`${chartId}-colors-${dataKey}`}\n            x1=\"0\"\n            y1=\"0\"\n            x2=\"1\"\n            y2=\"0\"\n          >\n            {colorsCount === 1 ? (\n              // Single color: same color at start and end\n              <>\n                <stop offset=\"0%\" stopColor={`var(--color-${dataKey}-0)`} />\n                <stop offset=\"100%\" stopColor={`var(--color-${dataKey}-0)`} />\n              </>\n            ) : (\n              // Multiple colors: distribute evenly\n              // Fallback to first color if index doesn't exist in current theme\n              Array.from({ length: colorsCount }, (_, index) => (\n                <stop\n                  key={index}\n                  offset={`${(index / (colorsCount - 1)) * 100}%`}\n                  stopColor={`var(--color-${dataKey}-${index}, var(--color-${dataKey}-0))`}\n                />\n              ))\n            )}\n          </linearGradient>\n        );\n      })}\n    </>\n  );\n};\n\n// Glow filter style for glowing lines - smooth outer glow\nconst GlowFilterStyle = ({\n  chartId,\n  glowingLines,\n}: {\n  chartId: string;\n  glowingLines: string[];\n}) => {\n  return (\n    <>\n      {glowingLines.map((dataKey) => (\n        <filter\n          key={`${chartId}-line-glow-${dataKey}`}\n          id={`${chartId}-line-glow-${dataKey}`}\n          x=\"-50%\"\n          y=\"-50%\"\n          width=\"200%\"\n          height=\"200%\"\n        >\n          {/* Smooth outer glow with increased intensity */}\n          <feGaussianBlur in=\"SourceGraphic\" stdDeviation=\"10\" result=\"blur\" />\n          <feColorMatrix\n            in=\"blur\"\n            type=\"matrix\"\n            values=\"1 0 0 0 0\n                    0 1 0 0 0\n                    0 0 1 0 0\n                    0 0 0 2 0\"\n            result=\"glow\"\n          />\n          {/* Place original line on top of glow */}\n          <feMerge>\n            <feMergeNode in=\"glow\" />\n            <feMergeNode in=\"SourceGraphic\" />\n          </feMerge>\n        </filter>\n      ))}\n    </>\n  );\n};\n\n// Generate gradient stops with smooth easing for loading animation\nconst generateEasedGradientStops = (\n  steps: number = 17,\n  minOpacity: number = 0.05,\n  maxOpacity: number = 0.9,\n) => {\n  return Array.from({ length: steps }, (_, i) => {\n    const t = i / (steps - 1); // 0 to 1\n    // Sine-based bell curve easing: peaks at center (t=0.5), smooth falloff at edges\n    const eased = Math.sin(t * Math.PI) ** 2;\n    const opacity = minOpacity + eased * (maxOpacity - minOpacity);\n    return { offset: `${(t * 100).toFixed(0)}%`, opacity: Number(opacity.toFixed(3)) };\n  });\n};\n\n/**\n * Hook to manage loading data with pixel-perfect shimmer synchronization.\n *\n * Uses motion.dev's onAnimationComplete callback to ensure chart data\n * is only regenerated when the shimmer has completely exited the visible area.\n * This eliminates timing drift issues from setTimeout/setInterval.\n *\n * The shimmer pattern has 200-300% width so that when the visible shimmer\n * exits the chart container (at the 100% point), we can safely swap data\n * while the invisible portion continues animating.\n */\nexport function useLoadingData(isLoading: boolean, loadingPoints: number = 14) {\n  const [loadingDataKey, setLoadingDataKey] = useState(false);\n\n  // Callback fired by motion.dev when shimmer exits visible area\n  const onShimmerExit = useCallback(() => {\n    if (isLoading) {\n      setLoadingDataKey((prev) => !prev);\n    }\n  }, [isLoading]);\n\n  const loadingData = useMemo(\n    () => getLoadingData(loadingPoints),\n    // loadingDataKey toggle triggers re-computation when shimmer exits\n    // eslint-disable-next-line react-hooks/exhaustive-deps\n    [loadingPoints, loadingDataKey],\n  );\n\n  return { loadingData, onShimmerExit };\n}\n\n/**\n * Loading line pattern with animated skeleton effect using motion.dev\n *\n * Key design for pixel-perfect sync:\n * - Visible chart area is normalized to 0-1 in objectBoundingBox units\n * - Shimmer gradient has width=1 (same as visible area)\n * - Pattern width is 3x (300%) to provide buffer on both sides\n * - Animation: x goes from -1 (off-screen left) to 2 (off-screen right)\n * - At x=-1: shimmer is completely outside left edge\n * - At x=0: shimmer starts entering from left\n * - At x=1: shimmer has fully exited right edge\n * - At x=2: shimmer is in the right buffer zone\n * - onShimmerExit fires when x crosses 1 (shimmer fully exited visible area)\n * - Data swaps happen while shimmer is outside visible area (x >= 1)\n * - Loop continues infinitely\n */\nconst LoadingLinePatternStyle = ({\n  chartId,\n  onShimmerExit,\n}: {\n  chartId: string;\n  onShimmerExit: () => void;\n}) => {\n  const gradientStops = generateEasedGradientStops();\n\n  // Pattern width needs to accommodate: 1 (left buffer) + 1 (visible) + 1 (right buffer) = 3\n  const patternWidth = 3;\n\n  // Animation goes from -1 (left of visible) to 2 (right of visible)\n  // Total travel distance = 3, matching pattern width\n  const startX = -1;\n  const endX = 2;\n\n  // Track last x value to detect threshold crossing\n  const lastXRef = useRef(startX);\n\n  return (\n    <>\n      {/* Gradient for smooth fade: edges dim, middle bright for sweep effect */}\n      <linearGradient id={`${chartId}-loading-mask-gradient`} x1=\"0\" y1=\"0\" x2=\"1\" y2=\"0\">\n        {gradientStops.map(({ offset, opacity }) => (\n          <stop key={offset} offset={offset} stopColor=\"white\" stopOpacity={opacity} />\n        ))}\n      </linearGradient>\n      <pattern\n        id={`${chartId}-loading-mask-pattern`}\n        patternUnits=\"objectBoundingBox\"\n        patternContentUnits=\"objectBoundingBox\"\n        patternTransform=\"rotate(25)\"\n        width={patternWidth}\n        height=\"1\"\n        x=\"0\"\n        y=\"0\"\n      >\n        {/* Use motion.rect with keyframe animation for precise timing */}\n        <motion.rect\n          y=\"0\"\n          width=\"1\"\n          height=\"1\"\n          fill={`url(#${chartId}-loading-mask-gradient)`}\n          initial={{ x: startX }}\n          animate={{ x: endX }}\n          transition={{\n            duration: LOADING_ANIMATION_DURATION / 1000,\n            ease: \"linear\",\n            repeat: Infinity,\n            repeatType: \"loop\",\n          }}\n          // Use onUpdate to fire callback at precise exit point\n          onUpdate={(latest) => {\n            const xValue = typeof latest.x === \"number\" ? latest.x : startX;\n            const lastX = lastXRef.current;\n\n            // Fire when crossing the exit threshold (x >= 1 means shimmer fully exited right)\n            if (xValue >= 1 && lastX < 1) {\n              onShimmerExit();\n            }\n\n            // Update tracked value\n            lastXRef.current = xValue;\n          }}\n        />\n      </pattern>\n      {/* Masking */}\n      <mask id={`${chartId}-loading-mask`} maskUnits=\"userSpaceOnUse\">\n        <rect width=\"100%\" height=\"100%\" fill={`url(#${chartId}-loading-mask-pattern)`} />\n      </mask>\n    </>\n  );\n};\n"
  },
  {
    "path": "apps/web/src/components/evilcharts/charts/pie-chart.tsx",
    "content": "\"use client\";\n\nimport { motion } from \"motion/react\";\nimport { useCallback, useId, useState, type ComponentProps } from \"react\";\nimport { LabelList, Pie, PieChart, Sector, type PieSectorShapeProps } from \"recharts\";\n\nimport { ChartBackground, type BackgroundVariant } from \"@/components/evilcharts/ui/background\";\nimport {\n  type ChartConfig,\n  ChartContainer,\n  getColorsCount,\n  LoadingIndicator,\n} from \"@/components/evilcharts/ui/chart\";\nimport {\n  ChartLegend,\n  ChartLegendContent,\n  type ChartLegendVariant,\n} from \"@/components/evilcharts/ui/legend\";\nimport {\n  ChartTooltip,\n  ChartTooltipContent,\n  type TooltipRoundness,\n  type TooltipVariant,\n} from \"@/components/evilcharts/ui/tooltip\";\n\n// Loading animation constants\nconst LOADING_SECTORS = 5;\nconst LOADING_ANIMATION_DURATION = 2000; // Full cycle duration in ms\n\n// Constants\nconst DEFAULT_INNER_RADIUS = 0;\nconst DEFAULT_OUTER_RADIUS = \"80%\";\nconst DEFAULT_CORNER_RADIUS = 0;\nconst DEFAULT_PADDING_ANGLE = 0;\n\ntype ChartProps = ComponentProps<typeof PieChart>;\ntype PieProps = ComponentProps<typeof Pie>;\ntype LabelListProps = ComponentProps<typeof LabelList>;\n\ntype EvilPieChartProps<TData extends Record<string, unknown>> = {\n  // Data\n  data: TData[];\n  dataKey: keyof TData & string;\n  nameKey: keyof TData & string;\n  chartConfig: ChartConfig;\n  className?: string;\n  chartProps?: ChartProps;\n  pieProps?: Omit<PieProps, \"data\" | \"dataKey\" | \"nameKey\">;\n\n  // Pie Shape\n  innerRadius?: number | string;\n  outerRadius?: number | string;\n  cornerRadius?: number;\n  paddingAngle?: number;\n  startAngle?: number;\n  endAngle?: number;\n\n  // Labels\n  showLabels?: boolean;\n  labelKey?: keyof TData & string;\n  labelListProps?: Omit<LabelListProps, \"dataKey\">;\n\n  // Hide Stuffs\n  hideTooltip?: boolean;\n  hideLegend?: boolean;\n  legendVariant?: ChartLegendVariant;\n  // Tooltip\n  tooltipRoundness?: TooltipRoundness;\n  tooltipVariant?: TooltipVariant;\n  tooltipDefaultIndex?: number;\n\n  // Interactive Stuffs\n  isLoading?: boolean;\n\n  // Glow Effects\n  glowingSectors?: string[];\n  // Background\n  backgroundVariant?: BackgroundVariant;\n};\n\ntype EvilPieChartClickable = {\n  isClickable: true;\n  onSelectionChange?: (selection: { dataKey: string; value: number } | null) => void;\n};\n\ntype EvilPieChartNotClickable = {\n  isClickable?: false;\n  onSelectionChange?: never;\n};\n\ntype EvilPieChartPropsWithCallback<TData extends Record<string, unknown>> =\n  EvilPieChartProps<TData> & (EvilPieChartClickable | EvilPieChartNotClickable);\n\nexport function EvilPieChart<TData extends Record<string, unknown>>({\n  data,\n  dataKey,\n  nameKey,\n  chartConfig,\n  className,\n  chartProps,\n  pieProps,\n  innerRadius = DEFAULT_INNER_RADIUS,\n  outerRadius = DEFAULT_OUTER_RADIUS,\n  cornerRadius = DEFAULT_CORNER_RADIUS,\n  paddingAngle = DEFAULT_PADDING_ANGLE,\n  startAngle = 0,\n  endAngle = 360,\n  showLabels = false,\n  labelKey,\n  labelListProps,\n  hideTooltip = false,\n  hideLegend = false,\n  legendVariant,\n  tooltipRoundness,\n  tooltipVariant,\n  tooltipDefaultIndex,\n  isClickable = false,\n  isLoading = false,\n  glowingSectors = [],\n  onSelectionChange,\n  backgroundVariant,\n}: EvilPieChartPropsWithCallback<TData>) {\n  const [selectedSector, setSelectedSector] = useState<string | null>(null);\n  const chartId = useId().replace(/:/g, \"\");\n\n  // Handler to update selection and call callback\n  const handleSelectionChange = useCallback(\n    (sectorName: string | null) => {\n      setSelectedSector(sectorName);\n      if (isClickable && onSelectionChange) {\n        if (sectorName === null) {\n          onSelectionChange(null);\n        } else {\n          // Find the data item and get its value\n          const selectedItem = data.find((item) => (item[nameKey] as string) === sectorName);\n          if (selectedItem) {\n            const value = selectedItem[dataKey] as number;\n            onSelectionChange({ dataKey: sectorName, value });\n          }\n        }\n      }\n    },\n    [isClickable, onSelectionChange, data, nameKey, dataKey],\n  );\n\n  // Prepare data with fill colors referencing gradients\n  const preparedData = data.map((item) => {\n    const sectorName = item[nameKey] as string;\n    return {\n      ...item,\n      fill: `url(#${chartId}-pie-colors-${sectorName})`,\n    };\n  });\n\n  return (\n    <ChartContainer className={className} config={chartConfig}>\n      <LoadingIndicator isLoading={isLoading} />\n      <PieChart id=\"evil-charts-pie-chart\" accessibilityLayer {...chartProps}>\n        {backgroundVariant && <ChartBackground variant={backgroundVariant} />}\n        {!hideLegend && (\n          <ChartLegend\n            verticalAlign=\"bottom\"\n            align=\"center\"\n            content={\n              <ChartLegendContent\n                selected={selectedSector}\n                onSelectChange={handleSelectionChange}\n                isClickable={isClickable}\n                nameKey={nameKey}\n                variant={legendVariant}\n              />\n            }\n          />\n        )}\n        {!hideTooltip && !isLoading && (\n          <ChartTooltip\n            defaultIndex={tooltipDefaultIndex}\n            content={\n              <ChartTooltipContent\n                nameKey={nameKey}\n                hideLabel\n                roundness={tooltipRoundness}\n                variant={tooltipVariant}\n              />\n            }\n          />\n        )}\n        {!isLoading && (\n          <Pie\n            data={preparedData}\n            dataKey={dataKey}\n            nameKey={nameKey}\n            innerRadius={innerRadius}\n            outerRadius={outerRadius}\n            cornerRadius={cornerRadius}\n            paddingAngle={paddingAngle}\n            startAngle={startAngle}\n            endAngle={endAngle}\n            strokeWidth={0}\n            isAnimationActive\n            style={isClickable ? { cursor: \"pointer\" } : undefined}\n            onClick={(_, index) => {\n              if (!isClickable) return;\n              const clickedName = data[index]?.[nameKey] as string;\n              handleSelectionChange(selectedSector === clickedName ? null : clickedName);\n            }}\n            shape={(props: PieSectorShapeProps) => {\n              const index = props.index ?? 0;\n              const sectorName = data[index]?.[nameKey] as string;\n              const isGlowing = glowingSectors.includes(sectorName);\n              const isSelected = selectedSector === null || selectedSector === sectorName;\n\n              const getFilter = () => {\n                if (isGlowing) return `url(#${chartId}-pie-glow-${sectorName})`;\n                return undefined;\n              };\n\n              return (\n                <Sector\n                  {...props}\n                  fill={`url(#${chartId}-pie-colors-${sectorName})`}\n                  filter={getFilter()}\n                  stroke={paddingAngle < 0 ? \"var(--background)\" : \"none\"}\n                  strokeWidth={paddingAngle < 0 ? 5 : 0}\n                  opacity={isClickable && !isSelected ? 0.3 : 1}\n                  className=\"transition-opacity duration-200\"\n                />\n              );\n            }}\n            {...pieProps}\n          >\n            {showLabels && (\n              <LabelList\n                dataKey={labelKey ?? dataKey}\n                stroke=\"none\"\n                fontSize={12}\n                fontWeight={500}\n                fill=\"currentColor\"\n                className=\"fill-background\"\n                {...labelListProps}\n              />\n            )}\n          </Pie>\n        )}\n\n        {/* Animated loading overlay using custom shape */}\n        {isLoading && (\n          <Pie\n            data={LOADING_PIE_DATA}\n            dataKey=\"value\"\n            nameKey=\"name\"\n            innerRadius={innerRadius}\n            outerRadius={outerRadius}\n            cornerRadius={cornerRadius}\n            paddingAngle={paddingAngle}\n            startAngle={startAngle}\n            endAngle={endAngle}\n            strokeWidth={0}\n            isAnimationActive={false}\n            shape={(props) => <AnimatedLoadingSector {...props} />}\n          />\n        )}\n\n        {/* ======== CHART STYLES ======== */}\n        <defs>\n          {/* Radial color gradients for each sector */}\n          <RadialColorGradientStyle chartConfig={chartConfig} chartId={chartId} />\n\n          {/* Glow filters */}\n          {glowingSectors.length > 0 && (\n            <GlowFilterStyle chartId={chartId} glowingSectors={glowingSectors} />\n          )}\n        </defs>\n      </PieChart>\n    </ChartContainer>\n  );\n}\n\n// Generate fixed loading data with equal sectors for circular pulsing animation\nconst LOADING_PIE_DATA = Array.from({ length: LOADING_SECTORS }, (_, i) => ({\n  name: `loading${i}`,\n  value: 100 / LOADING_SECTORS,\n}));\n\n// Animated sector for loading state using motion.dev\nconst AnimatedLoadingSector = (props: ComponentProps<typeof Sector> & { index?: number }) => {\n  const { index = 0, ...sectorProps } = props;\n\n  // Calculate delay for circular wave effect\n  const delay = (index / LOADING_SECTORS) * (LOADING_ANIMATION_DURATION / 1000);\n\n  return (\n    <motion.g\n      initial={{ opacity: 0.15 }}\n      animate={{ opacity: [0.15, 0.5, 0.15] }}\n      transition={{\n        duration: LOADING_ANIMATION_DURATION / 1000,\n        delay,\n        repeat: Infinity,\n        ease: \"easeInOut\",\n      }}\n    >\n      <Sector {...sectorProps} fill=\"currentColor\" />\n    </motion.g>\n  );\n};\n\n// Create radial color gradient for pie sectors\nconst RadialColorGradientStyle = ({\n  chartConfig,\n  chartId,\n}: {\n  chartConfig: ChartConfig;\n  chartId: string;\n}) => {\n  return (\n    <>\n      {Object.entries(chartConfig).map(([dataKey, config]) => {\n        const colorsCount = getColorsCount(config);\n\n        return (\n          <linearGradient\n            key={`${chartId}-pie-colors-${dataKey}`}\n            id={`${chartId}-pie-colors-${dataKey}`}\n            x1=\"0\"\n            y1=\"0\"\n            x2=\"1\"\n            y2=\"1\"\n          >\n            {colorsCount === 1 ? (\n              <>\n                <stop offset=\"0%\" stopColor={`var(--color-${dataKey}-0)`} />\n                <stop offset=\"100%\" stopColor={`var(--color-${dataKey}-0)`} />\n              </>\n            ) : (\n              Array.from({ length: colorsCount }, (_, index) => (\n                <stop\n                  key={index}\n                  offset={`${(index / (colorsCount - 1)) * 100}%`}\n                  stopColor={`var(--color-${dataKey}-${index}, var(--color-${dataKey}-0))`}\n                />\n              ))\n            )}\n          </linearGradient>\n        );\n      })}\n    </>\n  );\n};\n\n// Apply soft glow filter effect to pie sectors using SVG filters\nconst GlowFilterStyle = ({\n  chartId,\n  glowingSectors,\n}: {\n  chartId: string;\n  glowingSectors: string[];\n}) => {\n  return (\n    <>\n      {glowingSectors.map((sectorName) => (\n        <filter\n          key={`${chartId}-pie-glow-${sectorName}`}\n          id={`${chartId}-pie-glow-${sectorName}`}\n          x=\"-100%\"\n          y=\"-100%\"\n          width=\"300%\"\n          height=\"300%\"\n        >\n          <feGaussianBlur in=\"SourceGraphic\" stdDeviation=\"8\" result=\"blur\" />\n          <feColorMatrix\n            in=\"blur\"\n            type=\"matrix\"\n            values=\"1 0 0 0 0  0 1 0 0 0  0 0 1 0 0  0 0 0 0.5 0\"\n            result=\"glow\"\n          />\n          <feMerge>\n            <feMergeNode in=\"glow\" />\n            <feMergeNode in=\"SourceGraphic\" />\n          </feMerge>\n        </filter>\n      ))}\n    </>\n  );\n};\n"
  },
  {
    "path": "apps/web/src/components/evilcharts/charts/radar-chart.tsx",
    "content": "\"use client\";\n\nimport { useCallback, useEffect, useId, useMemo, useState, type ComponentProps } from \"react\";\nimport { PolarAngleAxis, PolarGrid, PolarRadiusAxis, Radar, RadarChart } from \"recharts\";\nimport type { TypedDataKey } from \"recharts/types/util/typedDataKey\";\n\nimport { ChartBackground, type BackgroundVariant } from \"@/components/evilcharts/ui/background\";\nimport {\n  type ChartConfig,\n  ChartContainer,\n  getColorsCount,\n  LoadingIndicator,\n} from \"@/components/evilcharts/ui/chart\";\nimport { ChartDot, DotVariant } from \"@/components/evilcharts/ui/dot\";\nimport {\n  ChartLegend,\n  ChartLegendContent,\n  type ChartLegendVariant,\n} from \"@/components/evilcharts/ui/legend\";\nimport {\n  ChartTooltip,\n  ChartTooltipContent,\n  type TooltipRoundness,\n  type TooltipVariant,\n} from \"@/components/evilcharts/ui/tooltip\";\n\n// Loading animation constants\nconst LOADING_POINTS = 6;\nconst LOADING_ANIMATION_DURATION = 1500;\n\n// Constants\nconst DEFAULT_FILL_OPACITY = 0.3;\n\ntype ChartProps = ComponentProps<typeof RadarChart>;\ntype RadarProps = ComponentProps<typeof Radar>;\ntype PolarGridProps = ComponentProps<typeof PolarGrid>;\n\ntype RadarVariant = \"filled\" | \"lines\";\n\n// Extract only keys from TData where the value is a number\ntype NumericDataKeys<T> = {\n  [K in keyof T]: T[K] extends number ? K : never;\n}[keyof T];\n\ntype EvilRadarChartProps<\n  TData extends Record<string, unknown>,\n  TConfig extends Record<string, ChartConfig[string]>,\n> = {\n  // Data\n  data: TData[];\n  dataKey: keyof TData & string; // The key for the angle axis (e.g., \"month\", \"category\")\n  chartConfig: TConfig;\n  className?: string;\n  chartProps?: ChartProps;\n  radarProps?: Omit<RadarProps, \"dataKey\">;\n  polarGridProps?: PolarGridProps;\n\n  // Variant\n  variant?: RadarVariant;\n  fillOpacity?: number;\n\n  // Axes\n  hideAngleAxis?: boolean;\n  hideRadiusAxis?: boolean;\n  hideGrid?: boolean;\n  gridType?: \"polygon\" | \"circle\";\n\n  // Hide Stuffs\n  hideTooltip?: boolean;\n  hideLegend?: boolean;\n  hideDots?: boolean;\n  legendVariant?: ChartLegendVariant;\n  // Tooltip\n  tooltipRoundness?: TooltipRoundness;\n  tooltipVariant?: TooltipVariant;\n  tooltipDefaultIndex?: number;\n  dotVariant?: DotVariant;\n  activeDotVariant?: DotVariant;\n\n  // Interactive Stuffs\n  isLoading?: boolean;\n\n  // Glow Effects\n  glowingRadars?: NumericDataKeys<TData>[];\n  // Background\n  backgroundVariant?: BackgroundVariant;\n};\n\ntype EvilRadarChartClickable = {\n  isClickable: true;\n  onSelectionChange?: (selectedDataKey: string | null) => void;\n};\n\ntype EvilRadarChartNotClickable = {\n  isClickable?: false;\n  onSelectionChange?: never;\n};\n\ntype EvilRadarChartPropsWithCallback<\n  TData extends Record<string, unknown>,\n  TConfig extends Record<string, ChartConfig[string]>,\n> = EvilRadarChartProps<TData, TConfig> & (EvilRadarChartClickable | EvilRadarChartNotClickable);\n\nexport function EvilRadarChart<\n  TData extends Record<string, unknown>,\n  TConfig extends Record<string, ChartConfig[string]>,\n>({\n  data,\n  dataKey,\n  chartConfig,\n  className,\n  chartProps,\n  radarProps,\n  polarGridProps,\n  variant = \"filled\",\n  fillOpacity = DEFAULT_FILL_OPACITY,\n  hideAngleAxis = false,\n  hideRadiusAxis = true,\n  hideGrid = false,\n  gridType = \"polygon\",\n  hideTooltip = false,\n  hideLegend = false,\n  hideDots = false,\n  legendVariant,\n  tooltipRoundness,\n  tooltipVariant,\n  tooltipDefaultIndex,\n  dotVariant,\n  activeDotVariant,\n  isClickable = false,\n  isLoading = false,\n  glowingRadars = [],\n  onSelectionChange,\n  backgroundVariant,\n}: EvilRadarChartPropsWithCallback<TData, TConfig>) {\n  const [selectedRadar, setSelectedRadar] = useState<string | null>(null);\n  const chartId = useId().replace(/:/g, \"\");\n  const loadingData = useLoadingData(isLoading, dataKey);\n\n  // Wrapper function to update state and call parent callback\n  const handleSelectionChange = useCallback(\n    (newSelectedRadar: string | null) => {\n      setSelectedRadar(newSelectedRadar);\n      if (isClickable && onSelectionChange) {\n        onSelectionChange(newSelectedRadar);\n      }\n    },\n    [onSelectionChange, isClickable],\n  );\n\n  // Get radar data keys from chartConfig\n  const radarDataKeys = Object.keys(chartConfig);\n\n  return (\n    <ChartContainer className={className} config={chartConfig}>\n      <LoadingIndicator isLoading={isLoading} />\n      <RadarChart\n        id=\"evil-charts-radar-chart\"\n        data={isLoading ? loadingData : data}\n        {...chartProps}\n      >\n        {backgroundVariant && <ChartBackground variant={backgroundVariant} />}\n        {!hideGrid && (\n          <PolarGrid\n            gridType={gridType}\n            stroke=\"currentColor\"\n            strokeOpacity={0.2}\n            strokeDasharray=\"3 4\"\n            {...polarGridProps}\n          />\n        )}\n\n        {!hideAngleAxis && !isLoading && (\n          <PolarAngleAxis\n            dataKey={dataKey as TypedDataKey<TData>}\n            tick={{ fill: \"currentColor\", fontSize: 12 }}\n            tickLine={false}\n          />\n        )}\n\n        {!hideRadiusAxis && !isLoading && (\n          <PolarRadiusAxis\n            tick={{ fill: \"currentColor\", fontSize: 10 }}\n            tickLine={false}\n            axisLine={false}\n          />\n        )}\n\n        {!hideLegend && !isLoading && (\n          <ChartLegend\n            verticalAlign=\"bottom\"\n            align=\"center\"\n            content={\n              <ChartLegendContent\n                selected={selectedRadar}\n                onSelectChange={handleSelectionChange}\n                isClickable={isClickable}\n                variant={legendVariant}\n              />\n            }\n          />\n        )}\n\n        {!hideTooltip && !isLoading && (\n          <ChartTooltip\n            defaultIndex={tooltipDefaultIndex}\n            cursor={false}\n            content={\n              <ChartTooltipContent\n                selected={selectedRadar}\n                roundness={tooltipRoundness}\n                variant={tooltipVariant}\n              />\n            }\n          />\n        )}\n\n        {/* Render radars for each data key in chartConfig */}\n        {!isLoading &&\n          radarDataKeys.map((radarKey) => {\n            const isGlowing = glowingRadars.includes(radarKey as NumericDataKeys<TData>);\n            const isSelected = selectedRadar === null || selectedRadar === radarKey;\n            const opacity = isClickable && !isSelected ? 0.2 : 1;\n\n            const getFilter = () => {\n              if (isGlowing) return `url(#${chartId}-radar-glow-${radarKey})`;\n              return undefined;\n            };\n\n            const showDots = !hideDots;\n            const dot = showDots ? (\n              dotVariant ? (\n                <ChartDot\n                  fillOpacity={opacity}\n                  type={dotVariant}\n                  dataKey={radarKey}\n                  chartId={chartId}\n                />\n              ) : (\n                true\n              )\n            ) : (\n              false\n            );\n            const activeDot = showDots ? (\n              activeDotVariant ? (\n                <ChartDot\n                  fillOpacity={opacity}\n                  type={activeDotVariant}\n                  dataKey={radarKey}\n                  chartId={chartId}\n                />\n              ) : undefined\n            ) : (\n              false\n            );\n\n            return (\n              <Radar\n                {...radarProps}\n                key={radarKey}\n                dataKey={radarKey}\n                stroke={`url(#${chartId}-radar-stroke-${radarKey})`}\n                fill={variant === \"filled\" ? `url(#${chartId}-radar-fill-${radarKey})` : \"none\"}\n                fillOpacity={variant === \"filled\" ? fillOpacity * opacity : 0}\n                strokeOpacity={opacity}\n                strokeWidth={1}\n                dot={dot}\n                activeDot={activeDot}\n                filter={getFilter()}\n                style={isClickable ? { cursor: \"pointer\" } : undefined}\n                onClick={() => {\n                  if (!isClickable) return;\n                  handleSelectionChange(selectedRadar === radarKey ? null : radarKey);\n                }}\n                className=\"transition-opacity duration-200\"\n              />\n            );\n          })}\n\n        {/* Loading state radar */}\n        {isLoading && (\n          <Radar\n            dataKey=\"value\"\n            stroke=\"currentColor\"\n            fill=\"currentColor\"\n            fillOpacity={0.1}\n            strokeOpacity={0.3}\n            strokeWidth={2}\n            dot={false}\n            isAnimationActive\n            animationDuration={LOADING_ANIMATION_DURATION}\n            animationEasing=\"ease-in-out\"\n          />\n        )}\n\n        {/* ======== CHART STYLES ======== */}\n        <defs>\n          {/* Shared horizontal color gradient for dots */}\n          <HorizontalColorGradientStyle chartConfig={chartConfig} chartId={chartId} />\n\n          {/* Stroke and fill gradients for each radar */}\n          <RadarGradientStyle chartConfig={chartConfig} chartId={chartId} />\n\n          {/* Glow filters */}\n          {glowingRadars.length > 0 && (\n            <GlowFilterStyle chartId={chartId} glowingRadars={glowingRadars as string[]} />\n          )}\n        </defs>\n      </RadarChart>\n    </ChartContainer>\n  );\n}\n\n// Generate random loading data for radar chart animation\nfunction generateLoadingData(dataKey: string) {\n  const categories = [\"A\", \"B\", \"C\", \"D\", \"E\", \"F\"];\n  return categories.slice(0, LOADING_POINTS).map((cat) => ({\n    [dataKey]: cat,\n    value: 30 + Math.random() * 70,\n  }));\n}\n\nfunction useLoadingData(isLoading: boolean, dataKey: string) {\n  const [refreshKey, setRefreshKey] = useState(0);\n\n  useEffect(() => {\n    if (!isLoading) return;\n\n    const interval = setInterval(() => {\n      setRefreshKey((prev) => prev + 1);\n    }, LOADING_ANIMATION_DURATION);\n\n    return () => clearInterval(interval);\n  }, [isLoading]);\n\n  // eslint-disable-next-line react-hooks/exhaustive-deps\n  const loadingData = useMemo(() => generateLoadingData(dataKey), [dataKey, refreshKey]);\n\n  return loadingData;\n}\n\n// Create stroke and fill gradients for radar chart paths\nconst RadarGradientStyle = ({\n  chartConfig,\n  chartId,\n}: {\n  chartConfig: ChartConfig;\n  chartId: string;\n}) => {\n  return (\n    <>\n      {Object.entries(chartConfig).map(([dataKey, config]) => {\n        const colorsCount = getColorsCount(config);\n\n        return (\n          <g key={dataKey}>\n            {/* Stroke gradient */}\n            <linearGradient id={`${chartId}-radar-stroke-${dataKey}`} x1=\"0\" y1=\"0\" x2=\"1\" y2=\"1\">\n              {colorsCount === 1 ? (\n                <>\n                  <stop offset=\"0%\" stopColor={`var(--color-${dataKey}-0)`} />\n                  <stop offset=\"100%\" stopColor={`var(--color-${dataKey}-0)`} />\n                </>\n              ) : (\n                Array.from({ length: colorsCount }, (_, index) => (\n                  <stop\n                    key={index}\n                    offset={`${(index / (colorsCount - 1)) * 100}%`}\n                    stopColor={`var(--color-${dataKey}-${index}, var(--color-${dataKey}-0))`}\n                  />\n                ))\n              )}\n            </linearGradient>\n\n            {/* Fill gradient (radial for better effect) */}\n            <radialGradient id={`${chartId}-radar-fill-${dataKey}`} cx=\"50%\" cy=\"50%\" r=\"50%\">\n              {colorsCount === 1 ? (\n                <>\n                  <stop offset=\"0%\" stopColor={`var(--color-${dataKey}-0)`} stopOpacity={0.8} />\n                  <stop offset=\"100%\" stopColor={`var(--color-${dataKey}-0)`} stopOpacity={0.3} />\n                </>\n              ) : (\n                Array.from({ length: colorsCount }, (_, index) => (\n                  <stop\n                    key={index}\n                    offset={`${(index / (colorsCount - 1)) * 100}%`}\n                    stopColor={`var(--color-${dataKey}-${index}, var(--color-${dataKey}-0))`}\n                    stopOpacity={index === 0 ? 0.8 : 0.3}\n                  />\n                ))\n              )}\n            </radialGradient>\n          </g>\n        );\n      })}\n    </>\n  );\n};\n\n// Shared horizontal color gradient (left to right) - used by dots\nconst HorizontalColorGradientStyle = ({\n  chartConfig,\n  chartId,\n}: {\n  chartConfig: ChartConfig;\n  chartId: string;\n}) => {\n  return (\n    <>\n      {Object.entries(chartConfig).map(([dataKey, config]) => {\n        const colorsCount = getColorsCount(config);\n\n        return (\n          <linearGradient\n            key={`${chartId}-colors-${dataKey}`}\n            id={`${chartId}-colors-${dataKey}`}\n            x1=\"0\"\n            y1=\"0\"\n            x2=\"1\"\n            y2=\"0\"\n          >\n            {colorsCount === 1 ? (\n              <>\n                <stop offset=\"0%\" stopColor={`var(--color-${dataKey}-0)`} />\n                <stop offset=\"100%\" stopColor={`var(--color-${dataKey}-0)`} />\n              </>\n            ) : (\n              Array.from({ length: colorsCount }, (_, index) => (\n                <stop\n                  key={index}\n                  offset={`${(index / (colorsCount - 1)) * 100}%`}\n                  stopColor={`var(--color-${dataKey}-${index}, var(--color-${dataKey}-0))`}\n                />\n              ))\n            )}\n          </linearGradient>\n        );\n      })}\n    </>\n  );\n};\n\n// Apply soft glow filter effect to radar areas using SVG filters\nconst GlowFilterStyle = ({\n  chartId,\n  glowingRadars,\n}: {\n  chartId: string;\n  glowingRadars: string[];\n}) => {\n  return (\n    <>\n      {glowingRadars.map((radarKey) => (\n        <filter\n          key={`${chartId}-radar-glow-${radarKey}`}\n          id={`${chartId}-radar-glow-${radarKey}`}\n          x=\"-50%\"\n          y=\"-50%\"\n          width=\"200%\"\n          height=\"200%\"\n        >\n          <feGaussianBlur in=\"SourceGraphic\" stdDeviation=\"4\" result=\"blur\" />\n          <feColorMatrix\n            in=\"blur\"\n            type=\"matrix\"\n            values=\"1 0 0 0 0  0 1 0 0 0  0 0 1 0 0  0 0 0 0.6 0\"\n            result=\"glow\"\n          />\n          <feMerge>\n            <feMergeNode in=\"glow\" />\n            <feMergeNode in=\"SourceGraphic\" />\n          </feMerge>\n        </filter>\n      ))}\n    </>\n  );\n};\n"
  },
  {
    "path": "apps/web/src/components/evilcharts/charts/radial-chart.tsx",
    "content": "\"use client\";\n\nimport { useCallback, useEffect, useId, useMemo, useState, type ComponentProps } from \"react\";\nimport { RadialBar, RadialBarChart, Sector, type SectorProps } from \"recharts\";\nimport { TypedDataKey } from \"recharts/types/util/typedDataKey\";\n\nimport { ChartBackground, type BackgroundVariant } from \"@/components/evilcharts/ui/background\";\nimport {\n  type ChartConfig,\n  ChartContainer,\n  getColorsCount,\n  LoadingIndicator,\n} from \"@/components/evilcharts/ui/chart\";\nimport {\n  ChartLegend,\n  ChartLegendContent,\n  type ChartLegendVariant,\n} from \"@/components/evilcharts/ui/legend\";\nimport {\n  ChartTooltip,\n  ChartTooltipContent,\n  type TooltipRoundness,\n  type TooltipVariant,\n} from \"@/components/evilcharts/ui/tooltip\";\n\n// Loading animation constants\nconst LOADING_BARS = 5;\nconst LOADING_ANIMATION_DURATION = 1500; // Duration between data changes in ms\n\n// Constants\nconst DEFAULT_INNER_RADIUS = \"30%\";\nconst DEFAULT_OUTER_RADIUS = \"100%\";\nconst DEFAULT_CORNER_RADIUS = 5;\nconst DEFAULT_BAR_SIZE = 14;\n\ntype ChartProps = ComponentProps<typeof RadialBarChart>;\ntype RadialBarProps = ComponentProps<typeof RadialBar>;\n\ntype RadialVariant = \"full\" | \"semi\";\n\ntype EvilRadialChartProps<TData extends Record<string, unknown>> = {\n  // Data\n  data: TData[];\n  dataKey: keyof TData & string;\n  nameKey: keyof TData & string;\n  chartConfig: ChartConfig;\n  className?: string;\n  chartProps?: ChartProps;\n  radialBarProps?: Omit<RadialBarProps, \"dataKey\">;\n\n  // Variant\n  variant?: RadialVariant;\n\n  // Radial Shape\n  innerRadius?: number | string;\n  outerRadius?: number | string;\n  cornerRadius?: number;\n  barSize?: number;\n\n  // Hide Stuffs\n  hideTooltip?: boolean;\n  hideLegend?: boolean;\n  hideBackground?: boolean;\n  legendVariant?: ChartLegendVariant;\n  // Tooltip\n  tooltipRoundness?: TooltipRoundness;\n  tooltipVariant?: TooltipVariant;\n  tooltipDefaultIndex?: number;\n\n  // Interactive Stuffs\n  isLoading?: boolean;\n\n  // Glow Effects\n  glowingBars?: string[];\n  // Background\n  backgroundVariant?: BackgroundVariant;\n};\n\ntype EvilRadialChartClickable = {\n  isClickable: true;\n  onSelectionChange?: (selection: { dataKey: string; value: number } | null) => void;\n};\n\ntype EvilRadialChartNotClickable = {\n  isClickable?: false;\n  onSelectionChange?: never;\n};\n\ntype EvilRadialChartPropsWithCallback<TData extends Record<string, unknown>> =\n  EvilRadialChartProps<TData> & (EvilRadialChartClickable | EvilRadialChartNotClickable);\n\nexport function EvilRadialChart<TData extends Record<string, unknown>>({\n  data,\n  dataKey,\n  nameKey,\n  chartConfig,\n  className,\n  chartProps,\n  radialBarProps,\n  variant = \"full\",\n  innerRadius = DEFAULT_INNER_RADIUS,\n  outerRadius = DEFAULT_OUTER_RADIUS,\n  cornerRadius = DEFAULT_CORNER_RADIUS,\n  barSize = DEFAULT_BAR_SIZE,\n  hideTooltip = false,\n  hideLegend = false,\n  hideBackground = false,\n  legendVariant,\n  tooltipRoundness,\n  tooltipVariant,\n  tooltipDefaultIndex,\n  isClickable = false,\n  isLoading = false,\n  glowingBars = [],\n  onSelectionChange,\n  backgroundVariant,\n}: EvilRadialChartPropsWithCallback<TData>) {\n  const [selectedBar, setSelectedBar] = useState<string | null>(null);\n  const chartId = useId().replace(/:/g, \"\");\n  const loadingData = useLoadingData(isLoading);\n\n  // Handler to update selection and call callback\n  const handleSelectionChange = useCallback(\n    (barName: string | null) => {\n      setSelectedBar(barName);\n      if (isClickable && onSelectionChange) {\n        if (barName === null) {\n          onSelectionChange(null);\n        } else {\n          // Find the data item and get its value\n          const selectedItem = data.find((item) => (item[nameKey] as string) === barName);\n          if (selectedItem) {\n            const value = selectedItem[dataKey] as number;\n            onSelectionChange({ dataKey: barName, value });\n          }\n        }\n      }\n    },\n    [isClickable, onSelectionChange, data, nameKey, dataKey],\n  );\n\n  // Variant-specific settings\n  const variantConfig = getVariantConfig(variant);\n\n  // Prepare data with fill colors referencing gradients\n  const preparedData = data.map((item) => {\n    const barName = item[nameKey] as string;\n    return {\n      ...item,\n      fill: `url(#${chartId}-radial-colors-${barName})`,\n    };\n  });\n\n  return (\n    <ChartContainer className={className} config={chartConfig}>\n      <LoadingIndicator isLoading={isLoading} />\n      <RadialBarChart\n        id=\"evil-charts-radial-chart\"\n        data={isLoading ? loadingData : preparedData}\n        innerRadius={innerRadius}\n        outerRadius={outerRadius}\n        startAngle={variantConfig.startAngle}\n        endAngle={variantConfig.endAngle}\n        cx={variantConfig.cx}\n        cy={variantConfig.cy}\n        {...chartProps}\n      >\n        {backgroundVariant && <ChartBackground variant={backgroundVariant} />}\n        {!hideLegend && !isLoading && (\n          <ChartLegend\n            verticalAlign={variant === \"semi\" ? \"bottom\" : \"bottom\"}\n            align=\"center\"\n            content={\n              <ChartLegendContent\n                selected={selectedBar}\n                onSelectChange={handleSelectionChange}\n                isClickable={isClickable}\n                nameKey={nameKey}\n                variant={legendVariant}\n              />\n            }\n          />\n        )}\n        {!hideTooltip && !isLoading && (\n          <ChartTooltip\n            defaultIndex={tooltipDefaultIndex}\n            cursor={false}\n            content={\n              <ChartTooltipContent\n                nameKey={nameKey}\n                hideLabel\n                roundness={tooltipRoundness}\n                variant={tooltipVariant}\n              />\n            }\n          />\n        )}\n\n        {/* Main radial bar */}\n        {!isLoading && (\n          <RadialBar\n            dataKey={dataKey as TypedDataKey<TData>}\n            cornerRadius={cornerRadius}\n            barSize={barSize}\n            background={!hideBackground}\n            className=\"drop-shadow-sm\"\n            style={isClickable ? { cursor: \"pointer\" } : undefined}\n            onClick={(_, index) => {\n              if (!isClickable) return;\n              const clickedName = data[index]?.[nameKey] as string;\n              handleSelectionChange(selectedBar === clickedName ? null : clickedName);\n            }}\n            shape={(props: SectorProps) => {\n              // Recharts merges data entry properties into shape props,\n              // so we can read the nameKey value directly from props\n              const barName = (props as unknown as TData)[nameKey] as string;\n              const isGlowing = glowingBars.includes(barName);\n              const isSelected = selectedBar === null || selectedBar === barName;\n\n              const getFilter = () => {\n                if (isGlowing) return `url(#${chartId}-radial-glow-${barName})`;\n                return undefined;\n              };\n\n              return (\n                <Sector\n                  {...props}\n                  filter={getFilter()}\n                  opacity={isClickable && !isSelected ? 0.3 : 1}\n                  className=\"transition-opacity duration-200\"\n                />\n              );\n            }}\n            {...radialBarProps}\n          />\n        )}\n\n        {/* Loading state with animated data */}\n        {isLoading && (\n          <RadialBar\n            dataKey=\"value\"\n            cornerRadius={cornerRadius}\n            barSize={barSize}\n            background\n            isAnimationActive\n            animationDuration={LOADING_ANIMATION_DURATION}\n            animationEasing=\"ease-in-out\"\n            shape={(props: SectorProps) => (\n              <Sector {...props} fill=\"currentColor\" fillOpacity={0.25} />\n            )}\n          />\n        )}\n\n        {/* ======== CHART STYLES ======== */}\n        <defs>\n          {/* Color gradients for each bar */}\n          <ColorGradientStyle chartConfig={chartConfig} chartId={chartId} />\n\n          {/* Glow filters */}\n          {glowingBars.length > 0 && (\n            <GlowFilterStyle chartId={chartId} glowingBars={glowingBars} />\n          )}\n        </defs>\n      </RadialBarChart>\n    </ChartContainer>\n  );\n}\n\n// Get angle and position configuration based on chart variant (full/semi)\nfunction getVariantConfig(variant: RadialVariant) {\n  switch (variant) {\n    case \"semi\":\n      return {\n        startAngle: 180,\n        endAngle: 0,\n        cx: \"50%\",\n        cy: \"70%\",\n      };\n    case \"full\":\n    default:\n      return {\n        startAngle: 90,\n        endAngle: -270,\n        cx: \"50%\",\n        cy: \"50%\",\n      };\n  }\n}\n\n// Generate random loading data with values between 40-100\nfunction generateLoadingData() {\n  return Array.from({ length: LOADING_BARS }, (_, i) => ({\n    name: `loading${i}`,\n    value: 40 + Math.random() * 60, // Random values between 40-100\n  }));\n}\n\n// Hook to animate loading data at intervals\nfunction useLoadingData(isLoading: boolean) {\n  const [dataKey, setDataKey] = useState(0);\n\n  useEffect(() => {\n    if (!isLoading) return;\n\n    const interval = setInterval(() => {\n      setDataKey((prev) => prev + 1);\n    }, LOADING_ANIMATION_DURATION);\n\n    return () => clearInterval(interval);\n  }, [isLoading]);\n\n  // Regenerate data when dataKey changes\n  // eslint-disable-next-line react-hooks/exhaustive-deps\n  const loadingData = useMemo(() => generateLoadingData(), [dataKey]);\n\n  return loadingData;\n}\n\n// Create horizontal color gradient for radial bars following the arc\nconst ColorGradientStyle = ({\n  chartConfig,\n  chartId,\n}: {\n  chartConfig: ChartConfig;\n  chartId: string;\n}) => {\n  return (\n    <>\n      {Object.entries(chartConfig).map(([dataKey, config]) => {\n        const colorsCount = getColorsCount(config);\n\n        return (\n          <linearGradient\n            key={`${chartId}-radial-colors-${dataKey}`}\n            id={`${chartId}-radial-colors-${dataKey}`}\n            x1=\"0\"\n            y1=\"0\"\n            x2=\"1\"\n            y2=\"1\"\n          >\n            {colorsCount === 1 ? (\n              <>\n                <stop offset=\"0%\" stopColor={`var(--color-${dataKey}-0)`} />\n                <stop offset=\"100%\" stopColor={`var(--color-${dataKey}-0)`} />\n              </>\n            ) : (\n              Array.from({ length: colorsCount }, (_, index) => (\n                <stop\n                  key={index}\n                  offset={`${(index / (colorsCount - 1)) * 100}%`}\n                  stopColor={`var(--color-${dataKey}-${index}, var(--color-${dataKey}-0))`}\n                />\n              ))\n            )}\n          </linearGradient>\n        );\n      })}\n    </>\n  );\n};\n\n// Apply soft glow filter effect to radial bars using SVG filters\nconst GlowFilterStyle = ({ chartId, glowingBars }: { chartId: string; glowingBars: string[] }) => {\n  return (\n    <>\n      {glowingBars.map((barName) => (\n        <filter\n          key={`${chartId}-radial-glow-${barName}`}\n          id={`${chartId}-radial-glow-${barName}`}\n          x=\"-100%\"\n          y=\"-100%\"\n          width=\"300%\"\n          height=\"300%\"\n        >\n          <feGaussianBlur in=\"SourceGraphic\" stdDeviation=\"6\" result=\"blur\" />\n          <feColorMatrix\n            in=\"blur\"\n            type=\"matrix\"\n            values=\"1 0 0 0 0  0 1 0 0 0  0 0 1 0 0  0 0 0 0.6 0\"\n            result=\"glow\"\n          />\n          <feMerge>\n            <feMergeNode in=\"glow\" />\n            <feMergeNode in=\"SourceGraphic\" />\n          </feMerge>\n        </filter>\n      ))}\n    </>\n  );\n};\n"
  },
  {
    "path": "apps/web/src/components/evilcharts/ui/background.tsx",
    "content": "\"use client\";\n\nimport { useId } from \"react\";\nimport { ZIndexLayer } from \"recharts\";\n\n// ── Background Variant Types ─────────────────────────────────────────────────\n// To add a new variant:\n// 1. Add its name to the BackgroundVariant union type below\n// 2. Create a pattern component with PatternProps\n// 3. Register it in PATTERN_MAP\n\nexport type BackgroundVariant =\n  | \"dots\"\n  | \"grid\"\n  | \"cross-hatch\"\n  | \"diagonal-lines\"\n  | \"plus\"\n  | \"falling-triangles\"\n  | \"4-pointed-star\"\n  | \"tiny-checkers\"\n  | \"overlapping-circles\"\n  | \"wiggle-lines\"\n  | \"bubbles\";\n\n// ── Pattern Components ───────────────────────────────────────────────────────\n\ntype PatternProps = { id: string };\n\nconst DotsPattern = ({ id }: PatternProps) => (\n  <pattern id={id} x=\"0\" y=\"0\" width=\"20\" height=\"20\" patternUnits=\"userSpaceOnUse\">\n    <circle className=\"text-border dark:text-border\" cx=\"2\" cy=\"2\" r=\"1\" fill=\"currentColor\" />\n  </pattern>\n);\n\nconst GridPattern = ({ id }: PatternProps) => (\n  <pattern id={id} x=\"0\" y=\"0\" width=\"20\" height=\"20\" patternUnits=\"userSpaceOnUse\">\n    <path\n      className=\"text-border dark:text-border\"\n      d=\"M 20 0 L 0 0 0 20\"\n      fill=\"none\"\n      stroke=\"currentColor\"\n      strokeWidth=\"0.5\"\n    />\n  </pattern>\n);\n\nconst CrossHatchPattern = ({ id }: PatternProps) => (\n  <pattern id={id} x=\"0\" y=\"0\" width=\"20\" height=\"20\" patternUnits=\"userSpaceOnUse\">\n    <path\n      className=\"text-border/60 dark:text-border/50\"\n      d=\"M 0 0 L 20 20 M 20 0 L 0 20\"\n      fill=\"none\"\n      stroke=\"currentColor\"\n      strokeWidth=\"0.5\"\n    />\n  </pattern>\n);\n\nconst DiagonalLinesPattern = ({ id }: PatternProps) => (\n  <pattern\n    id={id}\n    x=\"0\"\n    y=\"0\"\n    width=\"6\"\n    height=\"6\"\n    patternUnits=\"userSpaceOnUse\"\n    patternTransform=\"rotate(45)\"\n  >\n    <line\n      className=\"text-border dark:text-border\"\n      x1=\"0\"\n      y1=\"0\"\n      x2=\"0\"\n      y2=\"6\"\n      stroke=\"currentColor\"\n      strokeWidth=\"0.5\"\n    />\n  </pattern>\n);\n\nconst PlusPattern = ({ id }: PatternProps) => (\n  <pattern id={id} x=\"0\" y=\"0\" width=\"16\" height=\"16\" patternUnits=\"userSpaceOnUse\">\n    <path\n      className=\"text-border dark:text-border\"\n      d=\"M 8 4 L 8 12 M 4 8 L 12 8\"\n      fill=\"none\"\n      stroke=\"currentColor\"\n      strokeWidth=\"0.5\"\n      strokeLinecap=\"round\"\n    />\n  </pattern>\n);\n\nconst FallingTrianglesPattern = ({ id }: PatternProps) => (\n  <pattern id={id} x=\"0\" y=\"0\" width=\"18\" height=\"36\" patternUnits=\"userSpaceOnUse\">\n    <path\n      className=\"text-border dark:text-border\"\n      d=\"M2 6h12L8 18 2 6zm18 36h12l-6 12-6-12z\"\n      transform=\"scale(0.5)\"\n      fill=\"currentColor\"\n      fillOpacity=\"0.4\"\n    />\n  </pattern>\n);\n\nconst FourPointedStarPattern = ({ id }: PatternProps) => (\n  <pattern id={id} x=\"0\" y=\"0\" width=\"16\" height=\"16\" patternUnits=\"userSpaceOnUse\">\n    <polygon\n      className=\"text-border dark:text-border\"\n      fillRule=\"evenodd\"\n      points=\"5 3 8 4 5 5 4 8 3 5 0 4 3 3 4 0 5 3\"\n      fill=\"currentColor\"\n      fillOpacity=\"0.4\"\n    />\n  </pattern>\n);\n\nconst TinyCheckersPattern = ({ id }: PatternProps) => (\n  <pattern id={id} x=\"0\" y=\"0\" width=\"8\" height=\"8\" patternUnits=\"userSpaceOnUse\">\n    <path\n      className=\"text-border dark:text-border\"\n      fillRule=\"evenodd\"\n      d=\"M0 0h4v4H0V0zm4 4h4v4H4V4z\"\n      fill=\"currentColor\"\n      fillOpacity=\"0.2\"\n    />\n  </pattern>\n);\n\nconst OverlappingCirclesPattern = ({ id }: PatternProps) => (\n  <pattern id={id} x=\"0\" y=\"0\" width=\"40\" height=\"40\" patternUnits=\"userSpaceOnUse\">\n    <path\n      className=\"text-border dark:text-border\"\n      fillRule=\"evenodd\"\n      d=\"M25 25c0-2.762 2.238-5 5-5s5 2.238 5 5-2.238 5-5 5c0 2.762-2.238 5-5 5s-5-2.238-5-5 2.238-5 5-5zM5 5c0-2.762 2.238-5 5-5s5 2.238 5 5-2.238 5-5 5c0 2.762-2.238 5-5 5S0 12.762 0 10s2.238-5 5-5zm5 4c2.209 0 4-1.791 4-4s-1.791-4-4-4-4 1.791-4 4 1.791 4 4 4zm20 20c2.209 0 4-1.791 4-4s-1.791-4-4-4-4 1.791-4 4 1.791 4 4 4z\"\n      fill=\"currentColor\"\n      fillOpacity=\"0.4\"\n    />\n  </pattern>\n);\n\nconst WiggleLinesPattern = ({ id }: PatternProps) => (\n  <pattern\n    id={id}\n    x=\"0\"\n    y=\"0\"\n    width=\"52\"\n    height=\"26\"\n    patternUnits=\"userSpaceOnUse\"\n    patternTransform=\"scale(0.6)\"\n  >\n    <path\n      className=\"text-border dark:text-border\"\n      d=\"M10 10c0-2.21-1.79-4-4-4-3.314 0-6-2.686-6-6h2c0 2.21 1.79 4 4 4 3.314 0 6 2.686 6 6 0 2.21 1.79 4 4 4 3.314 0 6 2.686 6 6 0 2.21 1.79 4 4 4v2c-3.314 0-6-2.686-6-6 0-2.21-1.79-4-4-4-3.314 0-6-2.686-6-6zm25.464-1.95l8.486 8.486-1.414 1.414-8.486-8.486 1.414-1.414z\"\n      fill=\"currentColor\"\n      fillOpacity=\"0.4\"\n    />\n  </pattern>\n);\n\nconst BubblesPattern = ({ id }: PatternProps) => (\n  <pattern\n    id={id}\n    x=\"0\"\n    y=\"0\"\n    width=\"100\"\n    height=\"100\"\n    patternUnits=\"userSpaceOnUse\"\n    patternTransform=\"scale(0.6667)\"\n  >\n    <path\n      className=\"text-border dark:text-border\"\n      d=\"M11 18c3.866 0 7-3.134 7-7s-3.134-7-7-7-7 3.134-7 7 3.134 7 7 7zm48 25c3.866 0 7-3.134 7-7s-3.134-7-7-7-7 3.134-7 7 3.134 7 7 7zm-43-7c1.657 0 3-1.343 3-3s-1.343-3-3-3-3 1.343-3 3 1.343 3 3 3zm63 31c1.657 0 3-1.343 3-3s-1.343-3-3-3-3 1.343-3 3 1.343 3 3 3zM34 90c1.657 0 3-1.343 3-3s-1.343-3-3-3-3 1.343-3 3 1.343 3 3 3zm56-76c1.657 0 3-1.343 3-3s-1.343-3-3-3-3 1.343-3 3 1.343 3 3 3zM12 86c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm28-65c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm23-11c2.76 0 5-2.24 5-5s-2.24-5-5-5-5 2.24-5 5 2.24 5 5 5zm-6 60c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm29 22c2.76 0 5-2.24 5-5s-2.24-5-5-5-5 2.24-5 5 2.24 5 5 5zM32 63c2.76 0 5-2.24 5-5s-2.24-5-5-5-5 2.24-5 5 2.24 5 5 5zm57-13c2.76 0 5-2.24 5-5s-2.24-5-5-5-5 2.24-5 5 2.24 5 5 5zm-9-21c1.105 0 2-.895 2-2s-.895-2-2-2-2 .895-2 2 .895 2 2 2zM60 91c1.105 0 2-.895 2-2s-.895-2-2-2-2 .895-2 2 .895 2 2 2zM35 41c1.105 0 2-.895 2-2s-.895-2-2-2-2 .895-2 2 .895 2 2 2zM12 60c1.105 0 2-.895 2-2s-.895-2-2-2-2 .895-2 2 .895 2 2 2z\"\n      fill=\"currentColor\"\n      fillOpacity=\"0.4\"\n      fillRule=\"evenodd\"\n    />\n  </pattern>\n);\n\n// ── Pattern Registry ─────────────────────────────────────────────────────────\n// Map variant names to pattern components\n\nconst PATTERN_MAP: Record<BackgroundVariant, React.FC<PatternProps>> = {\n  dots: DotsPattern,\n  grid: GridPattern,\n  plus: PlusPattern,\n  bubbles: BubblesPattern,\n  \"cross-hatch\": CrossHatchPattern,\n  \"diagonal-lines\": DiagonalLinesPattern,\n  \"falling-triangles\": FallingTrianglesPattern,\n  \"4-pointed-star\": FourPointedStarPattern,\n  \"tiny-checkers\": TinyCheckersPattern,\n  \"overlapping-circles\": OverlappingCirclesPattern,\n  \"wiggle-lines\": WiggleLinesPattern,\n};\n\n// ── Main Component ───────────────────────────────────────────────────────────\n// Usage: Place <ChartBackground variant=\"dots\" /> inside any Recharts chart component.\n// ZIndexLayer with zIndex={-1} ensures the background renders behind all chart content.\n\ninterface ChartBackgroundProps {\n  variant: BackgroundVariant;\n}\n\nexport function ChartBackground({ variant }: ChartBackgroundProps) {\n  const baseId = useId().replace(/:/g, \"\");\n  const patternId = `${baseId}-bg-${variant}`;\n  const maskId = `${baseId}-bg-edge-fade`;\n  const filterId = `${baseId}-bg-blur`;\n  const PatternComponent = PATTERN_MAP[variant];\n\n  return (\n    <ZIndexLayer zIndex={-1}>\n      <defs>\n        <PatternComponent id={patternId} />\n        {/* Gaussian blur filter for soft edge fade */}\n        <filter id={filterId}>\n          <feGaussianBlur stdDeviation=\"25\" />\n        </filter>\n        {/* Mask: a slightly inset white rect with blur creates smooth transparent edges */}\n        <mask id={maskId} maskUnits=\"userSpaceOnUse\">\n          <rect x=\"8%\" y=\"20%\" width=\"85%\" height=\"60%\" fill=\"white\" filter={`url(#${filterId})`} />\n        </mask>\n      </defs>\n      <rect width=\"100%\" height=\"100%\" fill={`url(#${patternId})`} mask={`url(#${maskId})`} />\n    </ZIndexLayer>\n  );\n}\n"
  },
  {
    "path": "apps/web/src/components/evilcharts/ui/chart.tsx",
    "content": "\"use client\";\n\nimport * as React from \"react\";\nimport * as RechartsPrimitive from \"recharts\";\n\nimport { cn } from \"@/lib/utils\";\n\n// Format: { THEME_NAME: CSS_SELECTOR }\nconst THEMES = { light: \"\", dark: \".dark\" } as const;\n\ntype ThemeKey = keyof typeof THEMES;\n\n// All Keys are optional at first\ntype ThemeColorsBase = {\n  [K in ThemeKey]?: string[];\n};\n\n// Require at least one theme key\ntype AtLeastOneThemeColor = {\n  [K in ThemeKey]: Required<Pick<ThemeColorsBase, K>> & Partial<Omit<ThemeColorsBase, K>>;\n}[ThemeKey];\n\nconst VALID_THEME_KEYS = Object.keys(THEMES) as ThemeKey[];\n\n// Validation for chart config colors at runtime\nfunction validateChartConfigColors(config: ChartConfig): void {\n  for (const [key, value] of Object.entries(config)) {\n    if (value.colors) {\n      const hasValidThemeKey = VALID_THEME_KEYS.some(\n        (themeKey) => value.colors?.[themeKey] !== undefined,\n      );\n\n      if (!hasValidThemeKey) {\n        throw new Error(\n          `[EvilCharts] Invalid chart config for \"${key}\": colors object must have at least one theme key (${VALID_THEME_KEYS.join(\", \")}). Received empty object or invalid keys.`,\n        );\n      }\n    }\n  }\n}\n\nexport type ChartConfig = Record<\n  string,\n  {\n    label?: React.ReactNode;\n    icon?: React.ComponentType;\n    colors?: AtLeastOneThemeColor;\n  }\n>;\n\ninterface ChartContextProps {\n  config: ChartConfig;\n}\n\nconst ChartContext = React.createContext<ChartContextProps | null>(null);\n\nexport function useChart() {\n  const context = React.useContext(ChartContext);\n\n  if (!context) {\n    throw new Error(\"useChart must be used within a <ChartContainer />\");\n  }\n\n  return context;\n}\n\ninterface ChartContainerProps\n  extends\n    Omit<React.ComponentProps<\"div\">, \"children\">,\n    Pick<\n      React.ComponentProps<typeof RechartsPrimitive.ResponsiveContainer>,\n      | \"initialDimension\"\n      | \"aspect\"\n      | \"debounce\"\n      | \"minHeight\"\n      | \"minWidth\"\n      | \"maxHeight\"\n      | \"height\"\n      | \"width\"\n      | \"onResize\"\n      | \"children\"\n    > {\n  config: ChartConfig;\n  innerResponsiveContainerStyle?: React.ComponentProps<\n    typeof RechartsPrimitive.ResponsiveContainer\n  >[\"style\"];\n  /** Optional content rendered below the chart (e.g. EvilBrush) */\n  footer?: React.ReactNode;\n}\n\nfunction ChartContainer({\n  id,\n  config,\n  initialDimension = { width: 320, height: 200 },\n  className,\n  children,\n  footer,\n  ...props\n}: Readonly<ChartContainerProps>) {\n  const uniqueId = React.useId();\n  const chartId = `chart-${id ?? uniqueId.replace(/:/g, \"\")}`;\n\n  // Validate chart config at runtime\n  validateChartConfigColors(config);\n\n  return (\n    <ChartContext.Provider value={{ config }}>\n      <div\n        data-slot=\"chart\"\n        data-chart={chartId}\n        className={cn(\n          \"min-h-0 min-w-0 w-full flex-1 overflow-hidden\",\n          \"[&_.recharts-cartesian-axis-tick_text]:fill-muted-foreground [&_.recharts-cartesian-grid_line[stroke='#ccc']]:stroke-border/50 [&_.recharts-curve.recharts-tooltip-cursor]:stroke-border [&_.recharts-polar-grid_[stroke='#ccc']]:stroke-border [&_.recharts-radial-bar-background-sector]:fill-muted [&_.recharts-rectangle.recharts-tooltip-cursor]:fill-muted [&_.recharts-reference-line_[stroke='#ccc']]:stroke-border relative flex flex-col justify-center text-xs [&_.recharts-dot[stroke='#fff']]:stroke-transparent [&_.recharts-layer]:outline-hidden [&_.recharts-sector]:outline-hidden [&_.recharts-sector[stroke='#fff']]:stroke-transparent [&_.recharts-surface]:outline-hidden\",\n          !footer && \"aspect-video\",\n          className,\n        )}\n        {...props}\n      >\n        <ChartStyle id={chartId} config={config} />\n        <RechartsPrimitive.ResponsiveContainer\n          className=\"min-h-0 min-w-0 w-full flex-1\"\n          initialDimension={initialDimension}\n          minWidth={0}\n        >\n          {children}\n        </RechartsPrimitive.ResponsiveContainer>\n        {footer}\n      </div>\n    </ChartContext.Provider>\n  );\n}\n\nfunction LoadingIndicator({ isLoading }: { isLoading: boolean }) {\n  if (!isLoading) {\n    return null;\n  }\n\n  return (\n    <div className=\"pointer-events-none absolute inset-0 z-20 flex items-center justify-center\">\n      <div className=\"text-primary bg-background flex items-center justify-center gap-2 rounded-md border px-2 py-0.5 text-sm\">\n        <div className=\"border-border border-t-primary h-3 w-3 animate-spin rounded-full border\" />\n        <span>Loading</span>\n      </div>\n    </div>\n  );\n}\n\n// Distribute colors evenly across slots, extra slots go to last color(s)\n// Example: 2 colors for 4 slots → [red, red, pink, pink]\n// Example: 3 colors for 4 slots → [red, pink, blue, blue]\nfunction distributeColors(colorsArray: string[], maxCount: number): string[] {\n  const availableCount = colorsArray.length;\n  if (availableCount >= maxCount) {\n    return colorsArray.slice(0, maxCount);\n  }\n\n  const result: string[] = [];\n  const baseSlots = Math.floor(maxCount / availableCount);\n  const extraSlots = maxCount % availableCount;\n\n  // First (availableCount - extraSlots) colors get baseSlots each\n  // Last extraSlots colors get (baseSlots + 1) each\n  for (let colorIdx = 0; colorIdx < availableCount; colorIdx++) {\n    const isExtraColor = colorIdx >= availableCount - extraSlots;\n    const slotsForThisColor = baseSlots + (isExtraColor ? 1 : 0);\n    for (let j = 0; j < slotsForThisColor; j++) {\n      result.push(colorsArray[colorIdx]);\n    }\n  }\n\n  return result;\n}\n\nconst ChartStyle = ({ id, config }: { id: string; config: ChartConfig }) => {\n  const colorConfig = Object.entries(config).filter(([, config]) => config.colors);\n\n  if (!colorConfig.length) {\n    return null;\n  }\n\n  const generateCssVars = (theme: keyof typeof THEMES) =>\n    colorConfig\n      .flatMap(([key, itemConfig]) => {\n        const colorsArray = itemConfig.colors?.[theme];\n        if (!colorsArray || !Array.isArray(colorsArray) || colorsArray.length === 0) {\n          return [];\n        }\n\n        // Get max count across all themes for this key\n        const maxCount = getColorsCount(itemConfig);\n\n        // Distribute colors evenly across all required slots\n        const distributedColors = distributeColors(colorsArray, maxCount);\n\n        return distributedColors.map((color, index) => `  --color-${key}-${index}: ${color};`);\n      })\n      .filter(Boolean)\n      .join(\"\\n\");\n\n  const css = Object.entries(THEMES)\n    .map(\n      ([theme, prefix]) =>\n        `${prefix} [data-chart=${id}] {\\n${generateCssVars(theme as keyof typeof THEMES)}\\n}`,\n    )\n    .join(\"\\n\");\n\n  return <style dangerouslySetInnerHTML={{ __html: css }} />;\n};\n\n// Helper to extract item config from a payload.\nexport function getPayloadConfigFromPayload(config: ChartConfig, payload: unknown, key: string) {\n  if (typeof payload !== \"object\" || payload === null) {\n    return undefined;\n  }\n\n  const payloadPayload =\n    \"payload\" in payload && typeof payload.payload === \"object\" && payload.payload !== null\n      ? payload.payload\n      : undefined;\n\n  let configLabelKey: string = key;\n\n  if (key in payload && typeof payload[key as keyof typeof payload] === \"string\") {\n    configLabelKey = payload[key as keyof typeof payload] as string;\n  } else if (\n    payloadPayload &&\n    key in payloadPayload &&\n    typeof payloadPayload[key as keyof typeof payloadPayload] === \"string\"\n  ) {\n    configLabelKey = payloadPayload[key as keyof typeof payloadPayload] as string;\n  }\n\n  return configLabelKey in config ? config[configLabelKey] : config[key];\n}\n\n// Format values to percent for expanded charts\nfunction axisValueToPercentFormatter(value: number) {\n  return `${Math.round(value * 100).toFixed(0)}%`;\n}\n\n// Get max colors count across all themes for a config entry\nfunction getColorsCount(config: ChartConfig[string]): number {\n  if (!config.colors) return 1;\n  const counts = VALID_THEME_KEYS.map((theme) => config.colors?.[theme]?.length ?? 0);\n  return Math.max(...counts, 1);\n}\n\n// Generate random loading data for skeleton/loading state\n// min/max represent percentage of the range (0-100), defaults to 20-80 for realistic look\nexport const getLoadingData = (points: number = 10, min: number = 0, max: number = 70) => {\n  const range = max - min;\n  return Array.from({ length: points }, () => ({\n    loading: Math.floor(Math.random() * range) + min,\n  }));\n};\n\nexport {\n  ChartContainer,\n  ChartStyle,\n  axisValueToPercentFormatter,\n  LoadingIndicator,\n  getColorsCount,\n};\n"
  },
  {
    "path": "apps/web/src/components/evilcharts/ui/dot.tsx",
    "content": "import * as React from \"react\";\n\nimport { cn } from \"@/lib/utils\";\n\nexport type DotVariant = \"default\" | \"border\" | \"colored-border\";\n\ntype ChartDotProps = {\n  cx?: number;\n  cy?: number;\n  dataKey: string;\n  chartId: string;\n  className?: string;\n  fillOpacity?: number;\n  type?: DotVariant;\n};\n\nconst ChartDot = React.memo(function ChartDot({\n  cx,\n  cy,\n  dataKey,\n  chartId: _chartId,\n  className,\n  fillOpacity = 1,\n  type = \"default\",\n}: ChartDotProps) {\n  const dotId = React.useId().replace(/:/g, \"\");\n  const fillColor = `var(--color-${String(dataKey)}-0)`;\n\n  if (cx === undefined || cy === undefined) return null;\n\n  switch (type) {\n    case \"border\":\n      return (\n        <PrimaryBorderDot\n          cx={cx}\n          cy={cy}\n          dotId={dotId}\n          fillOpacity={fillOpacity}\n          fillColor={fillColor}\n          className={className}\n        />\n      );\n    case \"colored-border\":\n      return (\n        <ColoredBorderDot\n          cx={cx}\n          cy={cy}\n          dotId={dotId}\n          fillOpacity={fillOpacity}\n          fillColor={fillColor}\n          className={className}\n        />\n      );\n    default:\n      return (\n        <DefaultDot\n          cx={cx}\n          cy={cy}\n          dotId={dotId}\n          fillOpacity={fillOpacity}\n          fillColor={fillColor}\n          className={className}\n        />\n      );\n  }\n});\n\ntype DotVariantProps = {\n  cx: number;\n  cy: number;\n  dotId: string;\n  fillOpacity: number;\n  fillColor: string;\n  className?: string;\n};\n\nconst DefaultDot = React.memo(\n  ({ cx, cy, dotId, fillOpacity, fillColor, className }: DotVariantProps) => {\n    const r = 3;\n    return (\n      <g className={className}>\n        <defs>\n          <clipPath id={`dot-clip-${dotId}`}>\n            <circle cx={cx} cy={cy} r={r} />\n          </clipPath>\n        </defs>\n        <rect\n          x=\"0\"\n          y={cy - r}\n          width=\"100%\"\n          height={r * 2}\n          fill={fillColor}\n          fillOpacity={fillOpacity}\n          clipPath={`url(#dot-clip-${dotId})`}\n        />\n      </g>\n    );\n  },\n);\n\nDefaultDot.displayName = \"DefaultDot\";\n\nconst PrimaryBorderDot = React.memo(\n  ({ cx, cy, dotId, fillOpacity, fillColor, className }: DotVariantProps) => {\n    const r = 6;\n    const strokeWidth = 5;\n    return (\n      <g className={cn(className, \"text-background\")}>\n        <defs>\n          <clipPath id={`dot-clip-${dotId}`}>\n            <circle cx={cx} cy={cy} r={r} />\n          </clipPath>\n        </defs>\n        {/* Background stroke (border) */}\n        <circle cx={cx} cy={cy} r={r} fill=\"currentColor\" />\n        <rect\n          x=\"0\"\n          y={cy - (r - strokeWidth / 2)}\n          width=\"100%\"\n          height={(r - strokeWidth / 2) * 2}\n          fill={fillColor}\n          fillOpacity={fillOpacity}\n          clipPath={`url(#dot-clip-inner-${dotId})`}\n        />\n        <defs>\n          <clipPath id={`dot-clip-inner-${dotId}`}>\n            <circle cx={cx} cy={cy} r={r - strokeWidth / 2} />\n          </clipPath>\n        </defs>\n      </g>\n    );\n  },\n);\n\nPrimaryBorderDot.displayName = \"PrimaryBorderDot\";\n\nconst ColoredBorderDot = React.memo(\n  ({ cx, cy, dotId, fillOpacity, fillColor, className }: DotVariantProps) => {\n    const r = 3;\n    const strokeWidth = 1;\n    return (\n      <g className={cn(className, \"text-background\")}>\n        <defs>\n          <clipPath id={`dot-clip-${dotId}`}>\n            <circle cx={cx} cy={cy} r={r + strokeWidth / 2} />\n          </clipPath>\n        </defs>\n        {/* Gradient stroke (border) via clipped rect */}\n        <rect\n          x=\"0\"\n          y={cy - r - strokeWidth / 2}\n          width=\"100%\"\n          height={(r + strokeWidth / 2) * 2}\n          fill={fillColor}\n          fillOpacity={fillOpacity}\n          clipPath={`url(#dot-clip-${dotId})`}\n        />\n        {/* Inner solid fill */}\n        <circle cx={cx} cy={cy} r={r - strokeWidth / 2} fill=\"currentColor\" />\n      </g>\n    );\n  },\n);\n\nColoredBorderDot.displayName = \"ColoredBorderDot\";\n\nexport { ChartDot };\n"
  },
  {
    "path": "apps/web/src/components/evilcharts/ui/evil-brush.tsx",
    "content": "\"use client\";\n\nimport { motion, useMotionValue, useMotionValueEvent, useSpring, useTransform } from \"motion/react\";\nimport type { MotionValue } from \"motion/react\";\nimport { useCallback, useEffect, type ComponentProps } from \"react\";\nimport * as React from \"react\";\nimport { ResponsiveContainer, AreaChart, Area, LineChart, Line, BarChart, Bar } from \"recharts\";\n\nimport { ChartStyle, getColorsCount, type ChartConfig } from \"@/components/evilcharts/ui/chart\";\nimport { cn } from \"@/lib/utils\";\n\n// ─── Types ──────────────────────────────────────────────────────────────────\n\ntype EvilBrushVariant = \"line\" | \"area\" | \"bar\";\ntype CurveType = ComponentProps<typeof Area>[\"type\"];\n\ninterface EvilBrushRange {\n  startIndex: number;\n  endIndex: number;\n}\n\ninterface EvilBrushProps {\n  /** Full dataset – always rendered in the miniature chart */\n  data: Record<string, unknown>[];\n  /** Chart config with colour definitions */\n  chartConfig: ChartConfig;\n  /** Data keys to plot (default: all keys from chartConfig) */\n  dataKeys?: string[];\n  /** X-axis data key – used for handle labels */\n  xDataKey?: string;\n  /** Visual variant of the mini chart */\n  variant?: EvilBrushVariant;\n  /** Pixel height of the brush */\n  height?: number;\n  /** Extra className */\n  className?: string;\n  /** Whether areas/bars should be stacked in the mini chart */\n  stacked?: boolean;\n  /** Stroke variant for line / area strokes in the mini chart */\n  strokeVariant?: \"solid\" | \"dashed\" | \"animated-dashed\";\n  /** Whether to connect null data points in line / area variants */\n  connectNulls?: boolean;\n  /** Radius for bar corners in the bar variant */\n  barRadius?: number;\n\n  // ── Controlled mode ──────────────────────────────────────────────────\n  /** Controlled start index */\n  startIndex?: number;\n  /** Controlled end index */\n  endIndex?: number;\n\n  // ── Uncontrolled mode ────────────────────────────────────────────────\n  /** Initial start index (uncontrolled) */\n  defaultStartIndex?: number;\n  /** Initial end index (uncontrolled) */\n  defaultEndIndex?: number;\n\n  /** Fired whenever the visible range changes */\n  onChange?: (range: EvilBrushRange) => void;\n  /** Format the handle label from the xDataKey value */\n  formatLabel?: (value: unknown, index: number) => string;\n  /** Curve type for line / area variants */\n  curveType?: CurveType;\n  /** Minimum number of data points that must remain selected */\n  minSpan?: number;\n  /** Whether to render labels on the handles */\n  showLabels?: boolean;\n  /** Skip rendering own ChartStyle (when inside a ChartContainer that already provides CSS vars) */\n  skipStyle?: boolean;\n}\n\n// ─── Spring config ──────────────────────────────────────────────────────────\n\nconst SPRING_CONFIG = { stiffness: 300, damping: 35, mass: 0.8 };\n\n// ─── Pointer-capture drag hook ──────────────────────────────────────────────\n// Replaces raw addEventListener with the modern Pointer Events API.\n// setPointerCapture routes all pointer events to the originating element,\n// so we get mouse + touch + pen support with zero global listeners.\n\ntype DragType = \"left\" | \"right\" | \"middle\";\n\ninterface DragState {\n  type: DragType;\n  originX: number;\n  originRange: EvilBrushRange;\n}\n\nfunction useBrushDrag({\n  range,\n  totalPoints,\n  containerRef,\n  commit,\n}: {\n  range: EvilBrushRange;\n  totalPoints: number;\n  containerRef: React.RefObject<HTMLDivElement | null>;\n  commit: (next: EvilBrushRange, mode?: DragType) => void;\n}) {\n  const dragRef = React.useRef<DragState | null>(null);\n  const [isDragging, setIsDragging] = React.useState(false);\n\n  const toIndexDelta = useCallback(\n    (px: number) => {\n      if (!containerRef.current || totalPoints <= 1) return 0;\n      return Math.round(\n        (px / containerRef.current.getBoundingClientRect().width) * (totalPoints - 1),\n      );\n    },\n    [totalPoints, containerRef],\n  );\n\n  const onPointerDown = useCallback(\n    (e: React.PointerEvent, type: DragType) => {\n      e.preventDefault();\n      (e.target as HTMLElement).setPointerCapture(e.pointerId);\n      dragRef.current = { type, originX: e.clientX, originRange: { ...range } };\n      setIsDragging(true);\n    },\n    [range],\n  );\n\n  const onPointerMove = useCallback(\n    (e: React.PointerEvent) => {\n      const d = dragRef.current;\n      if (!d) return;\n\n      const delta = toIndexDelta(e.clientX - d.originX);\n      const { type, originRange: o } = d;\n\n      if (type === \"left\") {\n        commit({ startIndex: o.startIndex + delta, endIndex: o.endIndex }, \"left\");\n      } else if (type === \"right\") {\n        commit({ startIndex: o.startIndex, endIndex: o.endIndex + delta }, \"right\");\n      } else {\n        const span = o.endIndex - o.startIndex;\n        let s = o.startIndex + delta;\n        let e2 = s + span;\n        if (s < 0) {\n          s = 0;\n          e2 = span;\n        }\n        if (e2 > totalPoints - 1) {\n          e2 = totalPoints - 1;\n          s = Math.max(0, e2 - span);\n        }\n        commit({ startIndex: s, endIndex: e2 }, \"middle\");\n      }\n    },\n    [toIndexDelta, totalPoints, commit],\n  );\n\n  const onPointerUp = useCallback((e: React.PointerEvent) => {\n    (e.target as HTMLElement).releasePointerCapture(e.pointerId);\n    dragRef.current = null;\n    setIsDragging(false);\n  }, []);\n\n  // Helper to bind all three pointer handlers for a given drag type\n  const bind = useCallback(\n    (type: DragType) => ({\n      onPointerDown: (e: React.PointerEvent) => onPointerDown(e, type),\n      onPointerMove,\n      onPointerUp,\n    }),\n    [onPointerDown, onPointerMove, onPointerUp],\n  );\n\n  return { isDragging, bind };\n}\n\n// ─── EvilBrush ────────────────────────────────────────────────────────────\n\nfunction EvilBrush({\n  data,\n  chartConfig,\n  dataKeys,\n  xDataKey,\n  variant = \"area\",\n  height = 56,\n  className,\n  stacked = false,\n  strokeVariant = \"solid\",\n  connectNulls = false,\n  barRadius,\n  startIndex: controlledStart,\n  endIndex: controlledEnd,\n  defaultStartIndex = 0,\n  defaultEndIndex,\n  onChange,\n  formatLabel,\n  curveType = \"monotone\",\n  minSpan = 2,\n  showLabels = true,\n  skipStyle = false,\n}: EvilBrushProps) {\n  const containerRef = React.useRef<HTMLDivElement>(null);\n  const keys = React.useMemo(() => dataKeys ?? Object.keys(chartConfig), [dataKeys, chartConfig]);\n  const totalPoints = data.length;\n  const chartId = React.useId().replace(/:/g, \"\");\n\n  // ── Controlled vs uncontrolled ──────────────────────────────────────────\n\n  const isControlled = controlledStart !== undefined && controlledEnd !== undefined;\n\n  const [internalRange, setInternalRange] = React.useState<EvilBrushRange>(() => ({\n    startIndex: Math.max(0, Math.min(defaultStartIndex, totalPoints - 1)),\n    endIndex: Math.max(0, Math.min(defaultEndIndex ?? totalPoints - 1, totalPoints - 1)),\n  }));\n\n  // Track the last committed range to avoid duplicate updates when small\n  // mouse movements don't produce index changes (e.g., at boundaries)\n  const lastCommittedRef = React.useRef<EvilBrushRange>(internalRange);\n\n  useEffect(() => {\n    if (!isControlled) {\n      setInternalRange((prev) => {\n        const adjusted = {\n          startIndex: Math.min(prev.startIndex, Math.max(0, totalPoints - 1)),\n          endIndex: Math.min(prev.endIndex, Math.max(0, totalPoints - 1)),\n        };\n        lastCommittedRef.current = adjusted;\n        return adjusted;\n      });\n    }\n  }, [totalPoints, isControlled]);\n\n  // ── Clamping & committing ───────────────────────────────────────────────\n\n  const clampRange = useCallback(\n    (range: EvilBrushRange, mode?: DragType): EvilBrushRange => {\n      let { startIndex, endIndex } = range;\n      const maxIndex = Math.max(0, totalPoints - 1);\n\n      startIndex = Math.max(0, Math.min(startIndex, maxIndex));\n      endIndex = Math.max(0, Math.min(endIndex, maxIndex));\n\n      if (mode === \"left\") {\n        const maxStart = Math.max(0, endIndex - minSpan);\n        startIndex = Math.min(startIndex, maxStart);\n        return { startIndex, endIndex };\n      }\n\n      if (mode === \"right\") {\n        const minEnd = Math.min(maxIndex, startIndex + minSpan);\n        endIndex = Math.max(endIndex, minEnd);\n        return { startIndex, endIndex };\n      }\n\n      if (endIndex - startIndex < minSpan) {\n        endIndex = Math.min(startIndex + minSpan, maxIndex);\n        if (endIndex - startIndex < minSpan) {\n          startIndex = Math.max(0, endIndex - minSpan);\n        }\n      }\n      return { startIndex, endIndex };\n    },\n    [totalPoints, minSpan],\n  );\n\n  const commit = useCallback(\n    (next: EvilBrushRange, mode?: DragType) => {\n      const clamped = clampRange(next, mode);\n      const last = lastCommittedRef.current;\n\n      // Only update if the range has actually changed — avoids unnecessary\n      // re-renders when the brush is at a boundary and small mouse movements\n      // don't produce index changes\n      if (last.startIndex === clamped.startIndex && last.endIndex === clamped.endIndex) {\n        return;\n      }\n\n      lastCommittedRef.current = clamped;\n      setInternalRange(clamped);\n      // Defer the parent callback — chart re-render happens at lower priority,\n      // React can skip intermediate frames during fast drags\n      React.startTransition(() => {\n        onChange?.(clamped);\n      });\n    },\n    [clampRange, onChange],\n  );\n\n  // ── Drag ────────────────────────────────────────────────────────────────\n\n  const { isDragging, bind } = useBrushDrag({\n    range: internalRange,\n    totalPoints,\n    containerRef,\n    commit,\n  });\n\n  // Position always driven by internalRange (never lags behind controlled props)\n  const range = internalRange;\n\n  // Sync internalRange with controlled props when not dragging\n  useEffect(() => {\n    if (isControlled && !isDragging) {\n      const syncedRange = { startIndex: controlledStart, endIndex: controlledEnd };\n      // eslint-disable-next-line react-hooks/set-state-in-effect\n      setInternalRange(syncedRange);\n      lastCommittedRef.current = syncedRange;\n    }\n  }, [isControlled, controlledStart, controlledEnd, isDragging]);\n\n  // ── Computed positions (%) ──────────────────────────────────────────────\n\n  const leftPct = totalPoints > 1 ? (range.startIndex / (totalPoints - 1)) * 100 : 0;\n  const rightPct = totalPoints > 1 ? (range.endIndex / (totalPoints - 1)) * 100 : 100;\n\n  // Drive all moving brush UI from the same springed edge values.\n  const leftTarget = useMotionValue(leftPct);\n  const rightTarget = useMotionValue(rightPct);\n  if (leftTarget.get() !== leftPct) leftTarget.set(leftPct);\n  if (rightTarget.get() !== rightPct) rightTarget.set(rightPct);\n\n  const leftSpring = useSpring(leftTarget, SPRING_CONFIG);\n  const rightSpring = useSpring(rightTarget, SPRING_CONFIG);\n  const leftPosition = useTransform(leftSpring, (v) => `${v}%`);\n  const rightPosition = useTransform(rightSpring, (v) => `${v}%`);\n  const leftOverlayWidth = useTransform(leftSpring, (v) => `${v}%`);\n  const rightOverlayWidth = useTransform(rightSpring, (v) => `${Math.max(0, 100 - v)}%`);\n  const selectedWidth = useMotionValue(`${Math.max(0, rightPct - leftPct)}%`);\n\n  const updateSelectedWidth = useCallback(() => {\n    selectedWidth.set(`${Math.max(0, rightSpring.get() - leftSpring.get())}%`);\n  }, [leftSpring, rightSpring, selectedWidth]);\n\n  useMotionValueEvent(leftSpring, \"change\", updateSelectedWidth);\n  useMotionValueEvent(rightSpring, \"change\", updateSelectedWidth);\n\n  const getLabel = useCallback(\n    (idx: number) => {\n      if (!xDataKey) return String(idx);\n      const v = data[idx]?.[xDataKey];\n      return formatLabel ? formatLabel(v, idx) : String(v ?? idx);\n    },\n    [data, xDataKey, formatLabel],\n  );\n\n  // ── Render ──────────────────────────────────────────────────────────────\n\n  if (totalPoints === 0) return null;\n\n  return (\n    <div\n      ref={containerRef}\n      data-chart={skipStyle ? undefined : chartId}\n      className={cn(\"group relative select-none\", className)}\n      style={{ height }}\n    >\n      {!skipStyle && <ChartStyle id={chartId} config={chartConfig} />}\n\n      {/* Mini chart – always shows all data */}\n      <div className=\"absolute inset-0 overflow-hidden rounded-md\">\n        <MiniChart\n          data={data}\n          keys={keys}\n          chartConfig={chartConfig}\n          variant={variant}\n          curveType={curveType}\n          chartId={chartId}\n          stacked={stacked}\n          strokeVariant={strokeVariant === \"animated-dashed\" ? \"dashed\" : strokeVariant}\n          connectNulls={connectNulls}\n          barRadius={barRadius}\n        />\n      </div>\n\n      {/* Dim overlay – left */}\n      <motion.div\n        className=\"bg-background/70 pointer-events-none absolute inset-y-0 left-0 rounded-l-md\"\n        style={{ width: leftOverlayWidth }}\n      />\n      {/* Dim overlay – right */}\n      <motion.div\n        className=\"bg-background/70 pointer-events-none absolute inset-y-0 right-0 rounded-r-md\"\n        style={{ width: rightOverlayWidth }}\n      />\n\n      {/* Selected region – draggable to pan */}\n      <motion.div\n        className=\"absolute inset-y-0 cursor-grab touch-none rounded-sm border active:cursor-grabbing\"\n        style={{ left: leftPosition, width: selectedWidth }}\n        {...bind(\"middle\")}\n      />\n\n      {/* Left handle */}\n      <BrushHandle\n        side=\"left\"\n        position={leftPosition}\n        label={showLabels ? getLabel(range.startIndex) : undefined}\n        bind={bind(\"left\")}\n      />\n\n      {/* Right handle */}\n      <BrushHandle\n        side=\"right\"\n        position={rightPosition}\n        label={showLabels ? getLabel(range.endIndex) : undefined}\n        bind={bind(\"right\")}\n      />\n    </div>\n  );\n}\n\n// ─── Brush Handle ───────────────────────────────────────────────────────────\n\nfunction BrushHandle({\n  side,\n  position,\n  label,\n  bind,\n}: {\n  side: \"left\" | \"right\";\n  position: MotionValue<string>;\n  label?: string;\n  bind: {\n    onPointerDown: (e: React.PointerEvent) => void;\n    onPointerMove: (e: React.PointerEvent) => void;\n    onPointerUp: (e: React.PointerEvent) => void;\n  };\n}) {\n  const isLeft = side === \"left\";\n\n  return (\n    <motion.div className=\"absolute inset-y-0 z-10\" style={{ left: position }}>\n      <div\n        className={cn(\n          \"group absolute inset-y-0 flex w-3 cursor-ew-resize touch-none items-center justify-center after:absolute after:inset-y-0 after:-left-4 after:w-11 after:content-['']\",\n          isLeft ? \"\" : \"-translate-x-full\",\n        )}\n        {...bind}\n      >\n        <div\n          className={cn(\n            \"bg-muted-foreground group-hover:bg-foreground relative flex h-4 w-1.5 items-center justify-center rounded-md transition-colors\",\n            isLeft ? \"-left-[5.5px]\" : \"-right-[5.5px]\",\n          )}\n        >\n          <div className=\"flex flex-col gap-[2px]\">\n            <div className=\"bg-background/70 h-[2px] w-[2px] rounded-full\" />\n            <div className=\"bg-background/70 h-[2px] w-[2px] rounded-full\" />\n            <div className=\"bg-background/70 h-[2px] w-[2px] rounded-full\" />\n          </div>\n        </div>\n      </div>\n\n      {label && (\n        <div\n          className={cn(\n            \"bg-foreground text-background pointer-events-none absolute -bottom-3 -translate-y-1/2 rounded-[3px] px-1 py-px text-[8px] leading-tight font-medium whitespace-nowrap opacity-0 group-hover:opacity-100\",\n            isLeft ? \"left-1.5\" : \"right-1.5\",\n          )}\n        >\n          {label}\n        </div>\n      )}\n    </motion.div>\n  );\n}\n\n// ─── Mini Chart ─────────────────────────────────────────────────────────────\n\nfunction MiniChart({\n  data,\n  keys,\n  chartConfig,\n  variant,\n  curveType,\n  chartId,\n  stacked,\n  strokeVariant = \"solid\",\n  connectNulls = false,\n  barRadius,\n}: {\n  data: Record<string, unknown>[];\n  keys: string[];\n  chartConfig: ChartConfig;\n  variant: EvilBrushVariant;\n  curveType: CurveType;\n  chartId: string;\n  stacked: boolean;\n  strokeVariant?: \"solid\" | \"dashed\" | \"animated-dashed\";\n  connectNulls?: boolean;\n  barRadius?: number;\n}) {\n  const gradients = React.useMemo(\n    () =>\n      Object.entries(chartConfig)\n        .filter(([key]) => keys.includes(key))\n        .map(([dataKey, config]) => ({\n          dataKey,\n          colorsCount: getColorsCount(config),\n        })),\n    [chartConfig, keys],\n  );\n\n  const dashArray =\n    strokeVariant === \"dashed\" || strokeVariant === \"animated-dashed\" ? \"4 4\" : undefined;\n\n  const defsContent = (\n    <>\n      {/* Vertical fade gradient for area fill mask */}\n      {variant === \"area\" && (\n        <linearGradient id={`${chartId}-zm-vertical-fade`} x1=\"0\" y1=\"0\" x2=\"0\" y2=\"1\">\n          <stop offset=\"0%\" stopColor=\"white\" stopOpacity={0.15} />\n          <stop offset=\"100%\" stopColor=\"white\" stopOpacity={0} />\n        </linearGradient>\n      )}\n      {gradients.map(({ dataKey, colorsCount }) => {\n        const colorStops =\n          colorsCount === 1 ? (\n            <>\n              <stop offset=\"0%\" stopColor={`var(--color-${dataKey}-0)`} />\n              <stop offset=\"100%\" stopColor={`var(--color-${dataKey}-0)`} />\n            </>\n          ) : (\n            Array.from({ length: colorsCount }, (_, i) => (\n              <stop\n                key={i}\n                offset={`${(i / (colorsCount - 1)) * 100}%`}\n                stopColor={`var(--color-${dataKey}-${i}, var(--color-${dataKey}-0))`}\n              />\n            ))\n          );\n\n        return (\n          <React.Fragment key={dataKey}>\n            {/* Vertical color gradient (stroke + bar fill) */}\n            <linearGradient id={`${chartId}-zm-${dataKey}`} x1=\"0\" y1=\"0\" x2=\"0\" y2=\"1\">\n              {colorStops}\n            </linearGradient>\n\n            {/* Area fill: color gradient masked with vertical fade */}\n            {variant === \"area\" && (\n              <>\n                <mask id={`${chartId}-zm-fill-mask-${dataKey}`}>\n                  <rect width=\"100%\" height=\"100%\" fill={`url(#${chartId}-zm-vertical-fade)`} />\n                </mask>\n                <pattern\n                  id={`${chartId}-zm-fill-${dataKey}`}\n                  patternUnits=\"userSpaceOnUse\"\n                  width=\"100%\"\n                  height=\"100%\"\n                >\n                  <rect\n                    width=\"100%\"\n                    height=\"100%\"\n                    fill={`url(#${chartId}-zm-${dataKey})`}\n                    mask={`url(#${chartId}-zm-fill-mask-${dataKey})`}\n                  />\n                </pattern>\n              </>\n            )}\n          </React.Fragment>\n        );\n      })}\n    </>\n  );\n\n  if (variant === \"line\") {\n    return (\n      <ResponsiveContainer width=\"100%\" height=\"100%\">\n        <LineChart data={data} margin={{ top: 4, right: 0, bottom: 0, left: 0 }}>\n          <defs>{defsContent}</defs>\n          {keys.map((dk) => (\n            <Line\n              key={dk}\n              type={curveType}\n              dataKey={dk}\n              stroke={`url(#${chartId}-zm-${dk})`}\n              strokeWidth={1}\n              strokeOpacity={0.5}\n              strokeDasharray={dashArray}\n              connectNulls={connectNulls}\n              dot={false}\n              activeDot={false}\n              isAnimationActive={false}\n            />\n          ))}\n        </LineChart>\n      </ResponsiveContainer>\n    );\n  }\n\n  if (variant === \"bar\") {\n    const r = barRadius ?? 3;\n    return (\n      <ResponsiveContainer width=\"100%\" height=\"100%\">\n        <BarChart\n          data={data}\n          margin={{ top: 2, right: 0, bottom: 0, left: 0 }}\n          barGap={2}\n          barSize={14}\n        >\n          <defs>{defsContent}</defs>\n          {keys.map((dk) => (\n            <Bar\n              key={dk}\n              dataKey={dk}\n              fill={`url(#${chartId}-zm-${dk})`}\n              fillOpacity={0.35}\n              stackId={stacked ? \"zm-stack\" : undefined}\n              isAnimationActive={false}\n              radius={[r, r, r, r]}\n            />\n          ))}\n        </BarChart>\n      </ResponsiveContainer>\n    );\n  }\n\n  // Default: area\n  return (\n    <ResponsiveContainer width=\"100%\" height=\"100%\">\n      <AreaChart data={data} margin={{ top: 4, right: 0, bottom: 0, left: 0 }}>\n        <defs>{defsContent}</defs>\n        {keys.map((dk) => (\n          <Area\n            key={dk}\n            type={curveType}\n            dataKey={dk}\n            stroke={`url(#${chartId}-zm-${dk})`}\n            fill={`url(#${chartId}-zm-fill-${dk})`}\n            strokeWidth={1}\n            strokeOpacity={0.5}\n            strokeDasharray={dashArray}\n            connectNulls={connectNulls}\n            fillOpacity={1}\n            stackId={stacked ? \"zm-stack\" : undefined}\n            dot={false}\n            activeDot={false}\n            isAnimationActive={false}\n          />\n        ))}\n      </AreaChart>\n    </ResponsiveContainer>\n  );\n}\n\n// ─── useEvilBrush Hook ──────────────────────────────────────────────────────\n\nfunction useEvilBrush<TData extends Record<string, unknown>>({\n  data,\n  defaultStartIndex = 0,\n  defaultEndIndex,\n}: {\n  data: TData[];\n  defaultStartIndex?: number;\n  defaultEndIndex?: number;\n}) {\n  const [range, setRange] = React.useState<EvilBrushRange>({\n    startIndex: defaultStartIndex,\n    endIndex: defaultEndIndex ?? Math.max(0, data.length - 1),\n  });\n\n  // Defer the range used for data slicing — the brush handles move at the\n\n  // immediate `range` cadence while the expensive chart re-render uses the\n  // deferred value.  React can skip intermediate slices during fast drags.\n  const deferredRange = React.useDeferredValue(range);\n\n  useEffect(() => {\n    // eslint-disable-next-line react-hooks/set-state-in-effect\n    setRange({\n      startIndex: 0,\n      endIndex: Math.max(0, data.length - 1),\n    });\n  }, [data.length]);\n\n  const visibleData = React.useMemo(\n    () => data.slice(deferredRange.startIndex, deferredRange.endIndex + 1),\n    [data, deferredRange.startIndex, deferredRange.endIndex],\n  );\n\n  return {\n    range,\n    visibleData,\n    brushProps: {\n      startIndex: range.startIndex,\n      endIndex: range.endIndex,\n      onChange: setRange,\n    } satisfies Pick<EvilBrushProps, \"startIndex\" | \"endIndex\" | \"onChange\">,\n  };\n}\n\nexport { EvilBrush, useEvilBrush, type EvilBrushProps, type EvilBrushRange, type EvilBrushVariant };\n"
  },
  {
    "path": "apps/web/src/components/evilcharts/ui/legend.tsx",
    "content": "import * as React from \"react\";\nimport * as RechartsPrimitive from \"recharts\";\n\nimport {\n  getPayloadConfigFromPayload,\n  getColorsCount,\n  useChart,\n} from \"@/components/evilcharts/ui/chart\";\nimport { cn } from \"@/lib/utils\";\n\ntype ChartLegendVariant =\n  | \"square\"\n  | \"circle\"\n  | \"circle-outline\"\n  | \"rounded-square\"\n  | \"rounded-square-outline\"\n  | \"vertical-bar\"\n  | \"horizontal-bar\";\n\nfunction ChartLegendContent({\n  className,\n  hideIcon = false,\n  nameKey,\n  payload,\n  verticalAlign,\n  align = \"right\",\n  selected,\n  onSelectChange,\n  isClickable,\n  variant = \"rounded-square\",\n}: React.ComponentProps<\"div\"> & {\n  hideIcon?: boolean;\n  nameKey?: string;\n  selected?: string | null;\n  isClickable?: boolean;\n  onSelectChange?: (selected: string | null) => void;\n  variant?: ChartLegendVariant;\n} & RechartsPrimitive.DefaultLegendContentProps) {\n  const { config } = useChart();\n\n  if (!payload?.length) {\n    return null;\n  }\n\n  return (\n    <div\n      className={cn(\n        \"flex items-center gap-4 select-none\",\n        align === \"left\" && \"justify-start\",\n        align === \"center\" && \"justify-center\",\n        align === \"right\" && \"justify-end\",\n        verticalAlign === \"top\" ? \"pb-4\" : \"pt-4\",\n        className,\n      )}\n    >\n      {payload\n        .filter((item) => item.type !== \"none\")\n        .map((item) => {\n          // For pie charts, item.value contains the sector name (e.g., \"chrome\")\n          // For radial charts, the name is in item.payload[nameKey]\n          // For other charts, item.dataKey contains the series name (e.g., \"desktop\")\n          const payloadName =\n            nameKey && item.payload\n              ? (item.payload as Record<string, unknown>)[nameKey]\n              : undefined;\n          const key = `${payloadName ?? item.value ?? item.dataKey ?? \"value\"}`;\n          const itemConfig = getPayloadConfigFromPayload(config, item, key);\n          const isSelected = selected === null || selected === key;\n\n          // Get colors count for this item to determine gradient vs solid\n          const colorsCount = itemConfig ? getColorsCount(itemConfig) : 1;\n\n          return (\n            <div\n              key={key}\n              className={cn(\n                \"[&>svg]:text-muted-foreground flex items-center gap-1.5 transition-opacity [&>svg]:h-3 [&>svg]:w-3\",\n                !isSelected && \"opacity-30\",\n                isClickable && \"cursor-pointer\",\n              )}\n              onClick={() => {\n                if (!isClickable) return;\n\n                onSelectChange?.(selected === key ? null : key);\n              }}\n            >\n              {itemConfig?.icon && !hideIcon ? (\n                <itemConfig.icon />\n              ) : (\n                <LegendIndicator variant={variant} dataKey={key} colorsCount={colorsCount} />\n              )}\n              {itemConfig?.label}\n            </div>\n          );\n        })}\n    </div>\n  );\n}\n\n// ---------------------------------------------------------------------------\n// Legend indicator — each variant gets its own branch so future variants\n// can diverge freely in markup & style.\n// ---------------------------------------------------------------------------\n\nfunction LegendIndicator({\n  variant,\n  dataKey,\n  colorsCount,\n}: {\n  variant: ChartLegendVariant;\n  dataKey: string;\n  colorsCount: number;\n}) {\n  const fillStyle = getLegendFillStyle(dataKey, colorsCount);\n  const outlineStyle = getLegendOutlineStyle(dataKey, colorsCount);\n\n  switch (variant) {\n    case \"square\":\n      return <div className=\"h-2 w-2 shrink-0\" style={fillStyle} />;\n\n    case \"circle\":\n      return <div className=\"h-2 w-2 shrink-0 rounded-full\" style={fillStyle} />;\n\n    case \"circle-outline\":\n      return <div className=\"h-2.5 w-2.5 shrink-0 rounded-full p-[1.5px]\" style={outlineStyle} />;\n\n    case \"vertical-bar\":\n      return <div className=\"h-3 w-1 shrink-0 rounded-[2px]\" style={fillStyle} />;\n\n    case \"horizontal-bar\":\n      return <div className=\"h-1 w-3 shrink-0 rounded-[2px]\" style={fillStyle} />;\n\n    case \"rounded-square-outline\":\n      return <div className=\"h-2.5 w-2.5 shrink-0 rounded-[3px] p-[1.5px]\" style={outlineStyle} />;\n\n    case \"rounded-square\":\n    default:\n      return <div className=\"h-2 w-2 shrink-0 rounded-[2px]\" style={fillStyle} />;\n  }\n}\n\n// ---------------------------------------------------------------------------\n// Style helpers\n// ---------------------------------------------------------------------------\n\n/** Solid fill / gradient background for filled variants. */\nfunction getLegendFillStyle(dataKey: string, colorsCount: number): React.CSSProperties {\n  if (colorsCount <= 1) {\n    return { backgroundColor: `var(--color-${dataKey}-0)` };\n  }\n\n  const stops = Array.from({ length: colorsCount }, (_, i) => {\n    const offset = (i / (colorsCount - 1)) * 100;\n    return `var(--color-${dataKey}-${i}) ${offset}%`;\n  }).join(\", \");\n\n  return { background: `linear-gradient(to right, ${stops})` };\n}\n\n/**\n * Outline style for stroke variants.\n * Uses background + mask-composite to punch out the center, leaving only the\n * \"border\" visible. Works with both solid colors and gradients, and respects\n * border-radius — unlike plain `border-color`.\n */\nfunction getLegendOutlineStyle(dataKey: string, colorsCount: number): React.CSSProperties {\n  const maskStyle: React.CSSProperties = {\n    WebkitMask: \"linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0)\",\n    WebkitMaskComposite: \"xor\",\n    mask: \"linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0)\",\n    maskComposite: \"exclude\",\n  };\n\n  if (colorsCount <= 1) {\n    return {\n      backgroundColor: `var(--color-${dataKey}-0)`,\n      ...maskStyle,\n    };\n  }\n\n  const stops = Array.from({ length: colorsCount }, (_, i) => {\n    const offset = (i / (colorsCount - 1)) * 100;\n    return `var(--color-${dataKey}-${i}) ${offset}%`;\n  }).join(\", \");\n\n  return {\n    background: `linear-gradient(to right, ${stops})`,\n    ...maskStyle,\n  };\n}\n\nconst ChartLegend = RechartsPrimitive.Legend;\n\nexport { ChartLegend, ChartLegendContent, type ChartLegendVariant };\n"
  },
  {
    "path": "apps/web/src/components/evilcharts/ui/tooltip.tsx",
    "content": "import * as React from \"react\";\nimport * as RechartsPrimitive from \"recharts\";\nimport type { NameType, ValueType } from \"recharts/types/component/DefaultTooltipContent\";\n\nimport {\n  getPayloadConfigFromPayload,\n  getColorsCount,\n  useChart,\n} from \"@/components/evilcharts/ui/chart\";\nimport { cn } from \"@/lib/utils\";\n\ntype TooltipRoundness = \"sm\" | \"md\" | \"lg\" | \"xl\";\ntype TooltipVariant = \"default\" | \"frosted-glass\";\n\nconst roundnessMap: Record<TooltipRoundness, string> = {\n  sm: \"rounded-sm\",\n  md: \"rounded-md\",\n  lg: \"rounded-lg\",\n  xl: \"rounded-xl\",\n};\n\nconst variantMap: Record<TooltipVariant, string> = {\n  default: \"bg-background\",\n  \"frosted-glass\": \"bg-background/70 backdrop-blur-sm\",\n};\n\nfunction ChartTooltipContent({\n  active,\n  payload,\n  className,\n  indicator = \"dot\",\n  hideLabel = false,\n  hideIndicator = false,\n  label,\n  labelFormatter,\n  labelClassName,\n  formatter,\n  nameKey,\n  labelKey,\n  selected,\n  roundness = \"lg\",\n  variant = \"default\",\n}: React.ComponentProps<typeof RechartsPrimitive.Tooltip> &\n  React.ComponentProps<\"div\"> & {\n    hideLabel?: boolean;\n    hideIndicator?: boolean;\n    indicator?: \"line\" | \"dot\" | \"dashed\";\n    nameKey?: string;\n    labelKey?: string;\n    selected?: string | null;\n    roundness?: TooltipRoundness;\n    variant?: TooltipVariant;\n  } & Omit<\n    RechartsPrimitive.DefaultTooltipContentProps<ValueType, NameType>,\n    \"accessibilityLayer\"\n  >) {\n  const { config } = useChart();\n\n  const tooltipLabel = React.useMemo(() => {\n    if (hideLabel || !payload?.length) {\n      return null;\n    }\n\n    const [item] = payload;\n    const key = `${labelKey ?? item?.dataKey ?? item?.name ?? \"value\"}`;\n    const itemConfig = getPayloadConfigFromPayload(config, item, key);\n    const value =\n      !labelKey && typeof label === \"string\" ? (config[label]?.label ?? label) : itemConfig?.label;\n\n    if (labelFormatter) {\n      return (\n        <div className={cn(\"font-medium\", labelClassName)}>{labelFormatter(value, payload)}</div>\n      );\n    }\n\n    if (!value) {\n      return null;\n    }\n\n    return <div className={cn(\"font-medium\", labelClassName)}>{value}</div>;\n  }, [label, labelFormatter, payload, hideLabel, labelClassName, config, labelKey]);\n\n  if (!active || !payload?.length) {\n    // Empty tooltip - to prevent position getting 0.0 so it doesnt animate tooltip every time from 0.0 origin\n    return <span className=\"p-4\" />;\n  }\n\n  const nestLabel = payload.length === 1 && indicator !== \"dot\";\n\n  return (\n    <div\n      className={cn(\n        \"border-border/50 grid min-w-32 items-start gap-1.5 border px-2.5 py-1.5 text-xs shadow-xl\",\n        roundnessMap[roundness],\n        variantMap[variant],\n        className,\n      )}\n    >\n      {!nestLabel ? tooltipLabel : null}\n      <div className=\"grid gap-1.5\">\n        {payload\n          .filter((item) => item.type !== \"none\")\n          .map((item, index) => {\n            // For pie charts, item.name contains the sector name (e.g., \"chrome\")\n            // For radial charts, the name is in item.payload[nameKey]\n            // For other charts, item.name or item.dataKey contains the series name\n            const payloadName =\n              nameKey && item.payload\n                ? (item.payload as Record<string, unknown>)[nameKey]\n                : undefined;\n            const key = `${payloadName ?? item.name ?? item.dataKey ?? \"value\"}`;\n            const itemConfig = getPayloadConfigFromPayload(config, item, key);\n\n            // Get colors count for this item to determine gradient vs solid\n            const colorsCount = itemConfig ? getColorsCount(itemConfig) : 1;\n\n            return (\n              <div\n                key={index}\n                className={cn(\n                  \"[&>svg]:text-muted-foreground flex w-full flex-wrap items-stretch gap-2 [&>svg]:h-2.5 [&>svg]:w-2.5\",\n                  indicator === \"dot\" && \"items-center\",\n                  selected != null && selected !== item.dataKey && \"opacity-30\",\n                )}\n              >\n                {formatter && item?.value !== undefined && item.name ? (\n                  formatter(item.value, item.name, item, index, item.payload)\n                ) : (\n                  <>\n                    {itemConfig?.icon ? (\n                      <itemConfig.icon />\n                    ) : (\n                      !hideIndicator && (\n                        <div\n                          className={cn(\"shrink-0 rounded-[2px]\", {\n                            \"h-2.5 w-2.5\": indicator === \"dot\",\n                            \"w-1\": indicator === \"line\",\n                            \"w-0 border-[1.5px] border-dashed bg-transparent!\":\n                              indicator === \"dashed\",\n                            \"my-0.5\": nestLabel && indicator === \"dashed\",\n                          })}\n                          style={getIndicatorColorStyle(key, colorsCount)}\n                        />\n                      )\n                    )}\n                    <div\n                      className={cn(\n                        \"flex flex-1 justify-between gap-4 leading-none\",\n                        nestLabel ? \"items-end\" : \"items-center\",\n                      )}\n                    >\n                      <div className=\"grid gap-1.5\">\n                        {nestLabel ? tooltipLabel : null}\n                        <span className=\"text-muted-foreground\">\n                          {itemConfig?.label ?? item.name}\n                        </span>\n                      </div>\n                      {item.value != null && (\n                        <span className=\"text-foreground font-mono font-medium tabular-nums\">\n                          {typeof item.value === \"number\"\n                            ? item.value.toLocaleString()\n                            : String(item.value)}\n                        </span>\n                      )}\n                    </div>\n                  </>\n                )}\n              </div>\n            );\n          })}\n      </div>\n    </div>\n  );\n}\n\nfunction getIndicatorColorStyle(dataKey: string, colorsCount: number): React.CSSProperties {\n  if (colorsCount <= 1) {\n    return { background: `var(--color-${dataKey}-0)` };\n  }\n\n  // Multiple colors: create linear gradient with evenly distributed stops\n  const stops = Array.from({ length: colorsCount }, (_, index) => {\n    const offset = (index / (colorsCount - 1)) * 100;\n    return `var(--color-${dataKey}-${index}) ${offset}%`;\n  }).join(\", \");\n\n  return { background: `linear-gradient(to right, ${stops})` };\n}\n\nconst ChartTooltip = ({\n  animationDuration = 200,\n  ...props\n}: React.ComponentProps<typeof RechartsPrimitive.Tooltip>) => (\n  <RechartsPrimitive.Tooltip animationDuration={animationDuration} {...props} />\n);\n\nexport { ChartTooltip, ChartTooltipContent };\nexport type { TooltipRoundness, TooltipVariant };\n"
  },
  {
    "path": "apps/web/src/components/providers.tsx",
    "content": "\"use client\";\n\nimport { ConvexProvider, ConvexReactClient } from \"convex/react\";\nimport { NuqsAdapter } from \"nuqs/adapters/next/app\";\n\nimport { Toaster } from \"@/components/ui/sonner\";\n\nconst convex = new ConvexReactClient(process.env.NEXT_PUBLIC_CONVEX_URL || \"\");\n\nexport default function Providers({ children }: { children: React.ReactNode }) {\n  return (\n    <>\n      <ConvexProvider client={convex}>\n        <NuqsAdapter>{children}</NuqsAdapter>\n      </ConvexProvider>\n      <Toaster />\n    </>\n  );\n}\n"
  },
  {
    "path": "apps/web/src/components/special-sponsor-banner.tsx",
    "content": "import { Globe, Star } from \"lucide-react\";\nimport Image from \"next/image\";\nimport { FaGithub } from \"react-icons/fa6\";\n\nimport { HoverCard, HoverCardContent, HoverCardTrigger } from \"@/components/ui/hover-card\";\nimport { formatSponsorUrl, getSponsorUrl, shouldShowLifetimeTotal } from \"@/lib/sponsor-utils\";\nimport { fetchSponsors } from \"@/lib/sponsors\";\n\nexport async function SpecialSponsorBanner() {\n  const data = await fetchSponsors();\n  const specialSponsors = data.specialSponsors;\n\n  if (!specialSponsors.length) {\n    return null;\n  }\n\n  return (\n    <div>\n      <div className=\"no-scrollbar grid grid-cols-4 items-center gap-2 overflow-x-auto whitespace-nowrap py-1\">\n        {specialSponsors.map((entry) => {\n          const sponsorUrl = getSponsorUrl(entry);\n\n          return (\n            <HoverCard key={entry.githubId}>\n              <HoverCardTrigger\n                render={\n                  <a\n                    href={sponsorUrl}\n                    target=\"_blank\"\n                    rel=\"noopener noreferrer\"\n                    aria-label={entry.name}\n                    className=\"inline-flex\"\n                  />\n                }\n              >\n                <Image\n                  src={entry.avatarUrl}\n                  alt={entry.name}\n                  width={66}\n                  height={66}\n                  className=\"size-12 rounded border border-border\"\n                  unoptimized\n                />\n              </HoverCardTrigger>\n              <HoverCardContent align=\"start\" sideOffset={8} className=\"bg-fd-background\">\n                <div className=\"space-y-3\">\n                  <div className=\"flex items-center gap-2\">\n                    <Star className=\"h-4 w-4 text-yellow-500/90\" />\n                    <div className=\"ml-auto text-muted-foreground text-xs\">\n                      <span>SPECIAL</span>\n                      <span className=\"px-1\">•</span>\n                      <span>{entry.sinceWhen.toUpperCase()}</span>\n                    </div>\n                  </div>\n                  <div className=\"flex gap-3\">\n                    <Image\n                      src={entry.avatarUrl}\n                      alt={entry.name}\n                      width={80}\n                      height={80}\n                      className=\"rounded border border-border\"\n                      unoptimized\n                    />\n                    <div className=\"grid grid-cols-1 grid-rows-[1fr_auto]\">\n                      <div>\n                        <h3 className=\"truncate font-semibold text-sm\">{entry.name}</h3>\n                        {shouldShowLifetimeTotal(entry) ? (\n                          <>\n                            {entry.tierName && (\n                              <p className=\"text-primary text-xs\">{entry.tierName}</p>\n                            )}\n                            <p className=\"text-muted-foreground text-xs\">\n                              Total: {entry.formattedAmount}\n                            </p>\n                          </>\n                        ) : (\n                          <p className=\"text-primary text-xs\">{entry.tierName}</p>\n                        )}\n                      </div>\n                      <div className=\"flex flex-col gap-1\">\n                        <a\n                          href={entry.githubUrl}\n                          target=\"_blank\"\n                          rel=\"noopener noreferrer\"\n                          className=\"group flex items-center gap-2 text-muted-foreground text-xs transition-colors hover:text-primary\"\n                        >\n                          <FaGithub className=\"h-4 w-4\" />\n                          <span className=\"truncate\">{entry.githubId}</span>\n                        </a>\n                        {entry.websiteUrl ? (\n                          <a\n                            href={sponsorUrl}\n                            target=\"_blank\"\n                            rel=\"noopener noreferrer\"\n                            className=\"group flex items-center gap-2 text-muted-foreground text-xs transition-colors hover:text-primary\"\n                          >\n                            <Globe className=\"h-4 w-4\" />\n                            <span className=\"truncate\">{formatSponsorUrl(sponsorUrl)}</span>\n                          </a>\n                        ) : null}\n                      </div>\n                    </div>\n                  </div>\n                </div>\n              </HoverCardContent>\n            </HoverCard>\n          );\n        })}\n      </div>\n    </div>\n  );\n}\n"
  },
  {
    "path": "apps/web/src/components/theme-toggle.tsx",
    "content": "\"use client\";\n\nimport { Switch } from \"@base-ui/react/switch\";\nimport { Moon, Sun } from \"lucide-react\";\nimport { useTheme } from \"next-themes\";\nimport * as React from \"react\";\n\nimport { cn } from \"@/lib/utils\";\n\nexport function ThemeToggle({ className }: { className?: string }) {\n  const { setTheme, resolvedTheme } = useTheme();\n  const [mounted, setMounted] = React.useState(false);\n\n  React.useEffect(() => {\n    setMounted(true);\n  }, []);\n\n  const isChecked = mounted ? resolvedTheme === \"dark\" : false;\n\n  const handleCheckedChange = (checked: boolean) => {\n    setTheme(checked ? \"dark\" : \"light\");\n  };\n\n  if (!mounted) {\n    return (\n      <button\n        type=\"button\"\n        className={cn(\n          \"inline-flex h-4 w-9 shrink-0 cursor-not-allowed items-center rounded-full border-2 border-transparent bg-input opacity-50\",\n          className,\n        )}\n        disabled\n        aria-label=\"Toggle theme (loading)\"\n      >\n        <span className=\"block h-3 w-3 rounded-full shadow-lg ring-0\" />\n      </button>\n    );\n  }\n\n  return (\n    <Switch.Root\n      checked={isChecked}\n      onCheckedChange={handleCheckedChange}\n      className={cn(\n        \"peer inline-flex h-4 w-9 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50 data-[checked]:bg-primary data-[unchecked]:bg-input\",\n        className,\n      )}\n      aria-label=\"Toggle theme between light and dark\"\n    >\n      <Switch.Thumb\n        className={cn(\n          \"pointer-events-none flex h-3 w-3 items-center justify-center rounded-full shadow-lg ring-0 transition-transform data-[checked]:translate-x-5 data-[unchecked]:translate-x-0\",\n        )}\n      >\n        {isChecked ? (\n          <Moon className=\"size-2 text-foreground\" />\n        ) : (\n          <Sun className=\"size-2 text-foreground\" />\n        )}\n      </Switch.Thumb>\n    </Switch.Root>\n  );\n}\n"
  },
  {
    "path": "apps/web/src/components/ui/accordion.tsx",
    "content": "\"use client\";\n\nimport { Accordion as AccordionPrimitive } from \"@base-ui/react/accordion\";\nimport { ChevronDownIcon, ChevronUpIcon } from \"lucide-react\";\n\nimport { cn } from \"@/lib/utils\";\n\nfunction Accordion({ className, ...props }: AccordionPrimitive.Root.Props) {\n  return (\n    <AccordionPrimitive.Root\n      data-slot=\"accordion\"\n      className={cn(\"flex w-full flex-col\", className)}\n      {...props}\n    />\n  );\n}\n\nfunction AccordionItem({ className, ...props }: AccordionPrimitive.Item.Props) {\n  return (\n    <AccordionPrimitive.Item\n      data-slot=\"accordion-item\"\n      className={cn(\"not-last:border-b\", className)}\n      {...props}\n    />\n  );\n}\n\nfunction AccordionTrigger({ className, children, ...props }: AccordionPrimitive.Trigger.Props) {\n  return (\n    <AccordionPrimitive.Header className=\"flex\">\n      <AccordionPrimitive.Trigger\n        data-slot=\"accordion-trigger\"\n        className={cn(\n          \"focus-visible:ring-ring/50 focus-visible:border-ring focus-visible:after:border-ring **:data-[slot=accordion-trigger-icon]:text-muted-foreground rounded-none py-2.5 text-left text-xs font-medium hover:underline focus-visible:ring-1 **:data-[slot=accordion-trigger-icon]:ml-auto **:data-[slot=accordion-trigger-icon]:size-4 group/accordion-trigger relative flex flex-1 items-start justify-between border border-transparent transition-all outline-none disabled:pointer-events-none disabled:opacity-50\",\n          className,\n        )}\n        {...props}\n      >\n        {children}\n        <ChevronDownIcon\n          data-slot=\"accordion-trigger-icon\"\n          className=\"pointer-events-none shrink-0 group-aria-expanded/accordion-trigger:hidden\"\n        />\n        <ChevronUpIcon\n          data-slot=\"accordion-trigger-icon\"\n          className=\"pointer-events-none hidden shrink-0 group-aria-expanded/accordion-trigger:inline\"\n        />\n      </AccordionPrimitive.Trigger>\n    </AccordionPrimitive.Header>\n  );\n}\n\nfunction AccordionContent({ className, children, ...props }: AccordionPrimitive.Panel.Props) {\n  return (\n    <AccordionPrimitive.Panel\n      data-slot=\"accordion-content\"\n      className=\"data-open:animate-accordion-down data-closed:animate-accordion-up text-xs overflow-hidden\"\n      {...props}\n    >\n      <div\n        className={cn(\n          \"pt-0 pb-2.5 [&_a]:hover:text-foreground h-(--accordion-panel-height) data-ending-style:h-0 data-starting-style:h-0 [&_a]:underline [&_a]:underline-offset-3 [&_p:not(:last-child)]:mb-4\",\n          className,\n        )}\n      >\n        {children}\n      </div>\n    </AccordionPrimitive.Panel>\n  );\n}\n\nexport { Accordion, AccordionItem, AccordionTrigger, AccordionContent };\n"
  },
  {
    "path": "apps/web/src/components/ui/button.tsx",
    "content": "import { Button as ButtonPrimitive } from \"@base-ui/react/button\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\n\nimport { cn } from \"@/lib/utils\";\n\nconst buttonVariants = cva(\n  \"focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 rounded-none border border-transparent bg-clip-padding text-xs font-medium focus-visible:ring-1 aria-invalid:ring-1 [&_svg:not([class*='size-'])]:size-4 inline-flex items-center justify-center whitespace-nowrap transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none shrink-0 [&_svg]:shrink-0 outline-none group/button select-none\",\n  {\n    variants: {\n      variant: {\n        default: \"bg-primary text-primary-foreground [a]:hover:bg-primary/80\",\n        outline:\n          \"border-border bg-background hover:bg-muted hover:text-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50 aria-expanded:bg-muted aria-expanded:text-foreground\",\n        secondary:\n          \"bg-secondary text-secondary-foreground hover:bg-secondary/80 aria-expanded:bg-secondary aria-expanded:text-secondary-foreground\",\n        ghost:\n          \"hover:bg-muted hover:text-foreground dark:hover:bg-muted/50 aria-expanded:bg-muted aria-expanded:text-foreground\",\n        destructive:\n          \"bg-destructive/10 hover:bg-destructive/20 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/20 text-destructive focus-visible:border-destructive/40 dark:hover:bg-destructive/30\",\n        link: \"text-primary underline-offset-4 hover:underline\",\n      },\n      size: {\n        default:\n          \"h-8 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2\",\n        xs: \"h-6 gap-1 rounded-none px-2 text-xs has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3\",\n        sm: \"h-7 gap-1 rounded-none px-2.5 has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3.5\",\n        lg: \"h-9 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-3 has-data-[icon=inline-start]:pl-3\",\n        icon: \"size-8\",\n        \"icon-xs\": \"size-6 rounded-none [&_svg:not([class*='size-'])]:size-3\",\n        \"icon-sm\": \"size-7 rounded-none\",\n        \"icon-lg\": \"size-9\",\n      },\n    },\n    defaultVariants: {\n      variant: \"default\",\n      size: \"default\",\n    },\n  },\n);\n\nfunction Button({\n  className,\n  variant = \"default\",\n  size = \"default\",\n  ...props\n}: ButtonPrimitive.Props & VariantProps<typeof buttonVariants>) {\n  return (\n    <ButtonPrimitive\n      data-slot=\"button\"\n      className={cn(buttonVariants({ variant, size, className }))}\n      {...props}\n    />\n  );\n}\n\nexport { Button, buttonVariants };\n"
  },
  {
    "path": "apps/web/src/components/ui/card.tsx",
    "content": "import * as React from \"react\";\n\nimport { cn } from \"@/lib/utils\";\n\nfunction Card({\n  className,\n  size = \"default\",\n  ...props\n}: React.ComponentProps<\"div\"> & { size?: \"default\" | \"sm\" }) {\n  return (\n    <div\n      data-slot=\"card\"\n      data-size={size}\n      className={cn(\n        \"ring-foreground/10 bg-card text-card-foreground gap-4 overflow-hidden rounded-none py-4 text-xs/relaxed ring-1 has-data-[slot=card-footer]:pb-0 has-[>img:first-child]:pt-0 data-[size=sm]:gap-2 data-[size=sm]:py-3 data-[size=sm]:has-data-[slot=card-footer]:pb-0 *:[img:first-child]:rounded-none *:[img:last-child]:rounded-none group/card flex flex-col\",\n        className,\n      )}\n      {...props}\n    />\n  );\n}\n\nfunction CardHeader({ className, ...props }: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"card-header\"\n      className={cn(\n        \"gap-1 rounded-none px-4 group-data-[size=sm]/card:px-3 [.border-b]:pb-4 group-data-[size=sm]/card:[.border-b]:pb-3 group/card-header @container/card-header grid auto-rows-min items-start has-data-[slot=card-action]:grid-cols-[1fr_auto] has-data-[slot=card-description]:grid-rows-[auto_auto]\",\n        className,\n      )}\n      {...props}\n    />\n  );\n}\n\nfunction CardTitle({ className, ...props }: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"card-title\"\n      className={cn(\"text-sm font-medium group-data-[size=sm]/card:text-sm\", className)}\n      {...props}\n    />\n  );\n}\n\nfunction CardDescription({ className, ...props }: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"card-description\"\n      className={cn(\"text-muted-foreground text-xs/relaxed\", className)}\n      {...props}\n    />\n  );\n}\n\nfunction CardAction({ className, ...props }: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"card-action\"\n      className={cn(\"col-start-2 row-span-2 row-start-1 self-start justify-self-end\", className)}\n      {...props}\n    />\n  );\n}\n\nfunction CardContent({ className, ...props }: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"card-content\"\n      className={cn(\"px-4 group-data-[size=sm]/card:px-3\", className)}\n      {...props}\n    />\n  );\n}\n\nfunction CardFooter({ className, ...props }: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"card-footer\"\n      className={cn(\n        \"rounded-none border-t p-4 group-data-[size=sm]/card:p-3 flex items-center\",\n        className,\n      )}\n      {...props}\n    />\n  );\n}\n\nexport { Card, CardHeader, CardFooter, CardTitle, CardAction, CardDescription, CardContent };\n"
  },
  {
    "path": "apps/web/src/components/ui/dialog.tsx",
    "content": "\"use client\";\n\nimport { Dialog as DialogPrimitive } from \"@base-ui/react/dialog\";\nimport { XIcon } from \"lucide-react\";\nimport * as React from \"react\";\n\nimport { Button } from \"@/components/ui/button\";\nimport { cn } from \"@/lib/utils\";\n\nfunction Dialog({ ...props }: DialogPrimitive.Root.Props) {\n  return <DialogPrimitive.Root data-slot=\"dialog\" {...props} />;\n}\n\nfunction DialogTrigger({ ...props }: DialogPrimitive.Trigger.Props) {\n  return <DialogPrimitive.Trigger data-slot=\"dialog-trigger\" {...props} />;\n}\n\nfunction DialogPortal({ ...props }: DialogPrimitive.Portal.Props) {\n  return <DialogPrimitive.Portal data-slot=\"dialog-portal\" {...props} />;\n}\n\nfunction DialogClose({ ...props }: DialogPrimitive.Close.Props) {\n  return <DialogPrimitive.Close data-slot=\"dialog-close\" {...props} />;\n}\n\nfunction DialogOverlay({ className, ...props }: DialogPrimitive.Backdrop.Props) {\n  return (\n    <DialogPrimitive.Backdrop\n      data-slot=\"dialog-overlay\"\n      className={cn(\n        \"data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 bg-black/10 duration-100 supports-backdrop-filter:backdrop-blur-xs fixed inset-0 isolate z-50\",\n        className,\n      )}\n      {...props}\n    />\n  );\n}\n\nfunction DialogContent({\n  className,\n  children,\n  showCloseButton = true,\n  ...props\n}: DialogPrimitive.Popup.Props & {\n  showCloseButton?: boolean;\n}) {\n  return (\n    <DialogPortal>\n      <DialogOverlay />\n      <DialogPrimitive.Popup\n        data-slot=\"dialog-content\"\n        className={cn(\n          \"bg-background data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 ring-foreground/10 grid max-w-[calc(100%-2rem)] gap-4 rounded-none p-4 text-xs/relaxed ring-1 duration-100 sm:max-w-sm fixed top-1/2 left-1/2 z-50 w-full -translate-x-1/2 -translate-y-1/2 outline-none\",\n          className,\n        )}\n        {...props}\n      >\n        {children}\n        {showCloseButton && (\n          <DialogPrimitive.Close\n            data-slot=\"dialog-close\"\n            render={<Button variant=\"ghost\" className=\"absolute top-2 right-2\" size=\"icon-sm\" />}\n          >\n            <XIcon />\n            <span className=\"sr-only\">Close</span>\n          </DialogPrimitive.Close>\n        )}\n      </DialogPrimitive.Popup>\n    </DialogPortal>\n  );\n}\n\nfunction DialogHeader({ className, ...props }: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"dialog-header\"\n      className={cn(\"gap-1 text-left flex flex-col\", className)}\n      {...props}\n    />\n  );\n}\n\nfunction DialogFooter({\n  className,\n  showCloseButton = false,\n  children,\n  ...props\n}: React.ComponentProps<\"div\"> & {\n  showCloseButton?: boolean;\n}) {\n  return (\n    <div\n      data-slot=\"dialog-footer\"\n      className={cn(\"flex flex-col-reverse gap-2 sm:flex-row sm:justify-end\", className)}\n      {...props}\n    >\n      {children}\n      {showCloseButton && (\n        <DialogPrimitive.Close render={<Button variant=\"outline\" />}>Close</DialogPrimitive.Close>\n      )}\n    </div>\n  );\n}\n\nfunction DialogTitle({ className, ...props }: DialogPrimitive.Title.Props) {\n  return (\n    <DialogPrimitive.Title\n      data-slot=\"dialog-title\"\n      className={cn(\"text-sm font-medium\", className)}\n      {...props}\n    />\n  );\n}\n\nfunction DialogDescription({ className, ...props }: DialogPrimitive.Description.Props) {\n  return (\n    <DialogPrimitive.Description\n      data-slot=\"dialog-description\"\n      className={cn(\n        \"text-muted-foreground *:[a]:hover:text-foreground text-xs/relaxed *:[a]:underline *:[a]:underline-offset-3\",\n        className,\n      )}\n      {...props}\n    />\n  );\n}\n\nexport {\n  Dialog,\n  DialogClose,\n  DialogContent,\n  DialogDescription,\n  DialogFooter,\n  DialogHeader,\n  DialogOverlay,\n  DialogPortal,\n  DialogTitle,\n  DialogTrigger,\n};\n"
  },
  {
    "path": "apps/web/src/components/ui/dropdown-menu.tsx",
    "content": "\"use client\";\n\nimport { Menu as MenuPrimitive } from \"@base-ui/react/menu\";\nimport { ChevronRightIcon, CheckIcon } from \"lucide-react\";\nimport * as React from \"react\";\n\nimport { cn } from \"@/lib/utils\";\n\nfunction DropdownMenu({ ...props }: MenuPrimitive.Root.Props) {\n  return <MenuPrimitive.Root data-slot=\"dropdown-menu\" {...props} />;\n}\n\nfunction DropdownMenuPortal({ ...props }: MenuPrimitive.Portal.Props) {\n  return <MenuPrimitive.Portal data-slot=\"dropdown-menu-portal\" {...props} />;\n}\n\nfunction DropdownMenuTrigger({ ...props }: MenuPrimitive.Trigger.Props) {\n  return <MenuPrimitive.Trigger data-slot=\"dropdown-menu-trigger\" {...props} />;\n}\n\nfunction DropdownMenuContent({\n  align = \"start\",\n  alignOffset = 0,\n  side = \"bottom\",\n  sideOffset = 4,\n  className,\n  ...props\n}: MenuPrimitive.Popup.Props &\n  Pick<MenuPrimitive.Positioner.Props, \"align\" | \"alignOffset\" | \"side\" | \"sideOffset\">) {\n  return (\n    <MenuPrimitive.Portal>\n      <MenuPrimitive.Positioner\n        className=\"isolate z-50 outline-none\"\n        align={align}\n        alignOffset={alignOffset}\n        side={side}\n        sideOffset={sideOffset}\n      >\n        <MenuPrimitive.Popup\n          data-slot=\"dropdown-menu-content\"\n          className={cn(\n            \"data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 ring-foreground/10 bg-popover text-popover-foreground min-w-32 rounded-none shadow-md ring-1 duration-100 z-50 max-h-(--available-height) w-(--anchor-width) origin-(--transform-origin) overflow-x-hidden overflow-y-auto outline-none data-closed:overflow-hidden\",\n            className,\n          )}\n          {...props}\n        />\n      </MenuPrimitive.Positioner>\n    </MenuPrimitive.Portal>\n  );\n}\n\nfunction DropdownMenuGroup({ ...props }: MenuPrimitive.Group.Props) {\n  return <MenuPrimitive.Group data-slot=\"dropdown-menu-group\" {...props} />;\n}\n\nfunction DropdownMenuLabel({\n  className,\n  inset,\n  ...props\n}: MenuPrimitive.GroupLabel.Props & {\n  inset?: boolean;\n}) {\n  return (\n    <MenuPrimitive.GroupLabel\n      data-slot=\"dropdown-menu-label\"\n      data-inset={inset}\n      className={cn(\"text-muted-foreground px-2 py-2 text-xs data-[inset]:pl-8\", className)}\n      {...props}\n    />\n  );\n}\n\nfunction DropdownMenuItem({\n  className,\n  inset,\n  variant = \"default\",\n  ...props\n}: MenuPrimitive.Item.Props & {\n  inset?: boolean;\n  variant?: \"default\" | \"destructive\";\n}) {\n  return (\n    <MenuPrimitive.Item\n      data-slot=\"dropdown-menu-item\"\n      data-inset={inset}\n      data-variant={variant}\n      className={cn(\n        \"focus:bg-accent focus:text-accent-foreground data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 dark:data-[variant=destructive]:focus:bg-destructive/20 data-[variant=destructive]:focus:text-destructive data-[variant=destructive]:*:[svg]:text-destructive not-data-[variant=destructive]:focus:**:text-accent-foreground gap-2 rounded-none px-2 py-2 text-xs [&_svg:not([class*='size-'])]:size-4 group/dropdown-menu-item relative flex cursor-default items-center outline-hidden select-none data-disabled:pointer-events-none data-disabled:opacity-50 data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0\",\n        className,\n      )}\n      {...props}\n    />\n  );\n}\n\nfunction DropdownMenuSub({ ...props }: MenuPrimitive.SubmenuRoot.Props) {\n  return <MenuPrimitive.SubmenuRoot data-slot=\"dropdown-menu-sub\" {...props} />;\n}\n\nfunction DropdownMenuSubTrigger({\n  className,\n  inset,\n  children,\n  ...props\n}: MenuPrimitive.SubmenuTrigger.Props & {\n  inset?: boolean;\n}) {\n  return (\n    <MenuPrimitive.SubmenuTrigger\n      data-slot=\"dropdown-menu-sub-trigger\"\n      data-inset={inset}\n      className={cn(\n        \"focus:bg-accent focus:text-accent-foreground data-open:bg-accent data-open:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground gap-2 rounded-none px-2 py-2 text-xs [&_svg:not([class*='size-'])]:size-4 flex cursor-default items-center outline-hidden select-none data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0\",\n        className,\n      )}\n      {...props}\n    >\n      {children}\n      <ChevronRightIcon className=\"ml-auto\" />\n    </MenuPrimitive.SubmenuTrigger>\n  );\n}\n\nfunction DropdownMenuSubContent({\n  align = \"start\",\n  alignOffset = -3,\n  side = \"right\",\n  sideOffset = 0,\n  className,\n  ...props\n}: React.ComponentProps<typeof DropdownMenuContent>) {\n  return (\n    <DropdownMenuContent\n      data-slot=\"dropdown-menu-sub-content\"\n      className={cn(\n        \"data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 ring-foreground/10 bg-popover text-popover-foreground min-w-[96px] rounded-none shadow-lg ring-1 duration-100 w-auto\",\n        className,\n      )}\n      align={align}\n      alignOffset={alignOffset}\n      side={side}\n      sideOffset={sideOffset}\n      {...props}\n    />\n  );\n}\n\nfunction DropdownMenuCheckboxItem({\n  className,\n  children,\n  checked,\n  ...props\n}: MenuPrimitive.CheckboxItem.Props) {\n  return (\n    <MenuPrimitive.CheckboxItem\n      data-slot=\"dropdown-menu-checkbox-item\"\n      className={cn(\n        \"focus:bg-accent focus:text-accent-foreground focus:**:text-accent-foreground gap-2 rounded-none py-2 pr-8 pl-2 text-xs [&_svg:not([class*='size-'])]:size-4 relative flex cursor-default items-center outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0\",\n        className,\n      )}\n      checked={checked}\n      {...props}\n    >\n      <span\n        className=\"pointer-events-none absolute right-2 flex items-center justify-center pointer-events-none\"\n        data-slot=\"dropdown-menu-checkbox-item-indicator\"\n      >\n        <MenuPrimitive.CheckboxItemIndicator>\n          <CheckIcon />\n        </MenuPrimitive.CheckboxItemIndicator>\n      </span>\n      {children}\n    </MenuPrimitive.CheckboxItem>\n  );\n}\n\nfunction DropdownMenuRadioGroup({ ...props }: MenuPrimitive.RadioGroup.Props) {\n  return <MenuPrimitive.RadioGroup data-slot=\"dropdown-menu-radio-group\" {...props} />;\n}\n\nfunction DropdownMenuRadioItem({ className, children, ...props }: MenuPrimitive.RadioItem.Props) {\n  return (\n    <MenuPrimitive.RadioItem\n      data-slot=\"dropdown-menu-radio-item\"\n      className={cn(\n        \"focus:bg-accent focus:text-accent-foreground focus:**:text-accent-foreground gap-2 rounded-none py-2 pr-8 pl-2 text-xs [&_svg:not([class*='size-'])]:size-4 relative flex cursor-default items-center outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0\",\n        className,\n      )}\n      {...props}\n    >\n      <span\n        className=\"pointer-events-none absolute right-2 flex items-center justify-center pointer-events-none\"\n        data-slot=\"dropdown-menu-radio-item-indicator\"\n      >\n        <MenuPrimitive.RadioItemIndicator>\n          <CheckIcon />\n        </MenuPrimitive.RadioItemIndicator>\n      </span>\n      {children}\n    </MenuPrimitive.RadioItem>\n  );\n}\n\nfunction DropdownMenuSeparator({ className, ...props }: MenuPrimitive.Separator.Props) {\n  return (\n    <MenuPrimitive.Separator\n      data-slot=\"dropdown-menu-separator\"\n      className={cn(\"bg-border -mx-1 h-px\", className)}\n      {...props}\n    />\n  );\n}\n\nfunction DropdownMenuShortcut({ className, ...props }: React.ComponentProps<\"span\">) {\n  return (\n    <span\n      data-slot=\"dropdown-menu-shortcut\"\n      className={cn(\n        \"text-muted-foreground group-focus/dropdown-menu-item:text-accent-foreground ml-auto text-xs tracking-widest\",\n        className,\n      )}\n      {...props}\n    />\n  );\n}\n\nexport {\n  DropdownMenu,\n  DropdownMenuPortal,\n  DropdownMenuTrigger,\n  DropdownMenuContent,\n  DropdownMenuGroup,\n  DropdownMenuLabel,\n  DropdownMenuItem,\n  DropdownMenuCheckboxItem,\n  DropdownMenuRadioGroup,\n  DropdownMenuRadioItem,\n  DropdownMenuSeparator,\n  DropdownMenuShortcut,\n  DropdownMenuSub,\n  DropdownMenuSubTrigger,\n  DropdownMenuSubContent,\n};\n"
  },
  {
    "path": "apps/web/src/components/ui/file-tree.tsx",
    "content": "\"use client\";\n\nimport { Accordion as AccordionPrimitive } from \"@base-ui/react/accordion\";\nimport { FileIcon, FolderIcon, FolderOpenIcon } from \"lucide-react\";\nimport React, { createContext, useCallback, useContext, useEffect, useState } from \"react\";\n\nimport { Button } from \"@/components/ui/button\";\nimport { ScrollArea } from \"@/components/ui/scroll-area\";\nimport { cn } from \"@/lib/utils\";\n\ntype TreeViewElement = {\n  id: string;\n  name: string;\n  isSelectable?: boolean;\n  children?: TreeViewElement[];\n};\n\ntype TreeContextProps = {\n  selectedId: string | undefined;\n  expandedItems: string[] | undefined;\n  indicator: boolean;\n  handleExpand: (id: string) => void;\n  selectItem: (id: string) => void;\n  setExpandedItems?: React.Dispatch<React.SetStateAction<string[] | undefined>>;\n  openIcon?: React.ReactNode;\n  closeIcon?: React.ReactNode;\n  direction: \"rtl\" | \"ltr\";\n};\n\nconst TreeContext = createContext<TreeContextProps | null>(null);\n\nconst useTree = () => {\n  const context = useContext(TreeContext);\n  if (!context) {\n    throw new Error(\"useTree must be used within a TreeProvider\");\n  }\n  return context;\n};\n\ntype Direction = \"rtl\" | \"ltr\" | undefined;\n\ntype TreeViewProps = {\n  initialSelectedId?: string;\n  indicator?: boolean;\n  elements?: TreeViewElement[];\n  initialExpandedItems?: string[];\n  openIcon?: React.ReactNode;\n  closeIcon?: React.ReactNode;\n  dir?: Direction;\n} & React.HTMLAttributes<HTMLDivElement>;\n\nfunction Tree({\n  className,\n  elements,\n  initialSelectedId,\n  initialExpandedItems,\n  children,\n  indicator = true,\n  openIcon,\n  closeIcon,\n  dir,\n  ...props\n}: TreeViewProps) {\n  const [selectedId, setSelectedId] = useState<string | undefined>(initialSelectedId);\n  const [expandedItems, setExpandedItems] = useState<string[] | undefined>(initialExpandedItems);\n\n  const selectItem = useCallback((id: string) => {\n    setSelectedId(id);\n  }, []);\n\n  const handleExpand = useCallback((id: string) => {\n    setExpandedItems((prev) => {\n      if (prev?.includes(id)) {\n        return prev.filter((item) => item !== id);\n      }\n      return [...(prev ?? []), id];\n    });\n  }, []);\n\n  const expandSpecificTargetedElements = useCallback(\n    (elements?: TreeViewElement[], selectId?: string) => {\n      if (!elements || !selectId) return;\n      const findParent = (currentElement: TreeViewElement, currentPath: string[] = []) => {\n        const isSelectable = currentElement.isSelectable ?? true;\n        const newPath = [...currentPath, currentElement.id];\n        if (currentElement.id === selectId) {\n          if (isSelectable) {\n            setExpandedItems((prev) => [...(prev ?? []), ...newPath]);\n          } else {\n            if (newPath.includes(currentElement.id)) {\n              newPath.pop();\n              setExpandedItems((prev) => [...(prev ?? []), ...newPath]);\n            }\n          }\n          return;\n        }\n        if (isSelectable && currentElement.children && currentElement.children.length > 0) {\n          currentElement.children.forEach((child) => {\n            findParent(child, newPath);\n          });\n        }\n      };\n      elements.forEach((element) => {\n        findParent(element);\n      });\n    },\n    [],\n  );\n\n  useEffect(() => {\n    if (initialSelectedId) {\n      expandSpecificTargetedElements(elements, initialSelectedId);\n    }\n  }, [initialSelectedId, elements, expandSpecificTargetedElements]);\n\n  const direction = dir === \"rtl\" ? \"rtl\" : \"ltr\";\n\n  return (\n    <TreeContext.Provider\n      value={{\n        selectedId,\n        expandedItems,\n        handleExpand,\n        selectItem,\n        setExpandedItems,\n        indicator,\n        openIcon,\n        closeIcon,\n        direction,\n      }}\n    >\n      <div className={cn(\"size-full\", className)} {...props}>\n        <ScrollArea className=\"relative h-full px-2\">\n          <div className=\"flex flex-col gap-1\">{children}</div>\n        </ScrollArea>\n      </div>\n    </TreeContext.Provider>\n  );\n}\n\nTree.displayName = \"Tree\";\n\nfunction TreeIndicator({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {\n  const { direction } = useTree();\n\n  return (\n    <div\n      dir={direction}\n      className={cn(\n        \"bg-muted absolute left-1.5 h-full w-px rounded-md py-3 duration-300 ease-in-out hover:bg-slate-300 rtl:right-1.5\",\n        className,\n      )}\n      {...props}\n    />\n  );\n}\n\nTreeIndicator.displayName = \"TreeIndicator\";\n\ntype FolderProps = {\n  element: string;\n  value: string;\n  isSelectable?: boolean;\n  isSelect?: boolean;\n  children?: React.ReactNode;\n} & React.HTMLAttributes<HTMLDivElement>;\n\nfunction Folder({\n  className,\n  element,\n  value,\n  isSelectable = true,\n  isSelect,\n  children,\n  ...props\n}: FolderProps) {\n  const { handleExpand, expandedItems, indicator, openIcon, closeIcon } = useTree();\n\n  const isExpanded = expandedItems?.includes(value);\n\n  return (\n    <AccordionPrimitive.Root\n      className=\"relative h-full overflow-hidden\"\n      value={isExpanded ? [value] : []}\n      onValueChange={() => handleExpand(value)}\n    >\n      <AccordionPrimitive.Item value={value} {...props}>\n        <AccordionPrimitive.Header>\n          <AccordionPrimitive.Trigger\n            className={cn(`flex items-center gap-1 rounded-md text-sm`, className, {\n              \"bg-muted rounded-md\": isSelect && isSelectable,\n              \"cursor-pointer\": isSelectable,\n              \"cursor-not-allowed opacity-50\": !isSelectable,\n            })}\n            disabled={!isSelectable}\n          >\n            {isExpanded\n              ? (openIcon ?? <FolderOpenIcon className=\"size-4\" />)\n              : (closeIcon ?? <FolderIcon className=\"size-4\" />)}\n            <span>{element}</span>\n          </AccordionPrimitive.Trigger>\n        </AccordionPrimitive.Header>\n        <AccordionPrimitive.Panel\n          className={cn(\n            \"relative h-full overflow-hidden text-sm\",\n            \"data-[open]:animate-accordion-down data-[closed]:animate-accordion-up\",\n          )}\n        >\n          {element && indicator && <TreeIndicator aria-hidden=\"true\" />}\n          <div className=\"ml-5 flex flex-col gap-1 py-1 rtl:mr-5\">{children}</div>\n        </AccordionPrimitive.Panel>\n      </AccordionPrimitive.Item>\n    </AccordionPrimitive.Root>\n  );\n}\n\nFolder.displayName = \"Folder\";\n\ntype FileProps = {\n  value: string;\n  handleSelect?: (id: string) => void;\n  isSelectable?: boolean;\n  isSelect?: boolean;\n  fileIcon?: React.ReactNode;\n  children?: React.ReactNode;\n} & React.ButtonHTMLAttributes<HTMLButtonElement>;\n\nfunction File({\n  value,\n  className,\n  handleSelect: _handleSelect,\n  isSelectable = true,\n  isSelect,\n  fileIcon,\n  children,\n  ...props\n}: FileProps) {\n  const { direction, selectedId, selectItem } = useTree();\n  const isSelected = isSelect ?? selectedId === value;\n  return (\n    <button\n      type=\"button\"\n      disabled={!isSelectable}\n      className={cn(\n        \"flex w-fit items-center gap-1 rounded-md pr-1 text-sm duration-200 ease-in-out rtl:pr-0 rtl:pl-1\",\n        {\n          \"bg-muted\": isSelected && isSelectable,\n        },\n        isSelectable ? \"cursor-pointer\" : \"cursor-not-allowed opacity-50\",\n        direction === \"rtl\" ? \"rtl\" : \"ltr\",\n        className,\n      )}\n      onClick={() => selectItem(value)}\n      {...props}\n    >\n      {fileIcon ?? <FileIcon className=\"size-4\" />}\n      {children}\n    </button>\n  );\n}\n\nFile.displayName = \"File\";\n\ntype CollapseButtonProps = {\n  elements: TreeViewElement[];\n  expandAll?: boolean;\n  children?: React.ReactNode;\n} & React.ButtonHTMLAttributes<HTMLButtonElement>;\n\nfunction CollapseButton({ elements, expandAll = false, children, ...props }: CollapseButtonProps) {\n  const { expandedItems, setExpandedItems } = useTree();\n\n  const expendAllTree = useCallback(\n    (elements: TreeViewElement[]) => {\n      const expandTree = (element: TreeViewElement) => {\n        const isSelectable = element.isSelectable ?? true;\n        if (isSelectable && element.children && element.children.length > 0) {\n          setExpandedItems?.((prev) => [...(prev ?? []), element.id]);\n          element.children.forEach(expandTree);\n        }\n      };\n\n      elements.forEach(expandTree);\n    },\n    [setExpandedItems],\n  );\n\n  const closeAll = useCallback(() => {\n    setExpandedItems?.([]);\n  }, [setExpandedItems]);\n\n  useEffect(() => {\n    if (expandAll) {\n      expendAllTree(elements);\n    }\n  }, [expandAll, elements, expendAllTree]);\n\n  return (\n    <Button\n      variant={\"ghost\"}\n      className=\"absolute right-2 bottom-1 h-8 w-fit p-1\"\n      onClick={expandedItems && expandedItems.length > 0 ? closeAll : () => expendAllTree(elements)}\n      {...props}\n    >\n      {children}\n      <span className=\"sr-only\">Toggle</span>\n    </Button>\n  );\n}\n\nCollapseButton.displayName = \"CollapseButton\";\n\nexport { CollapseButton, File, Folder, Tree, type TreeViewElement };\n"
  },
  {
    "path": "apps/web/src/components/ui/hover-card.tsx",
    "content": "\"use client\";\n\nimport { PreviewCard as PreviewCardPrimitive } from \"@base-ui/react/preview-card\";\n\nimport { cn } from \"@/lib/utils\";\n\nfunction HoverCard({ ...props }: PreviewCardPrimitive.Root.Props) {\n  return <PreviewCardPrimitive.Root data-slot=\"hover-card\" {...props} />;\n}\n\nfunction HoverCardTrigger({ ...props }: PreviewCardPrimitive.Trigger.Props) {\n  return <PreviewCardPrimitive.Trigger data-slot=\"hover-card-trigger\" {...props} />;\n}\n\nfunction HoverCardContent({\n  className,\n  side = \"bottom\",\n  sideOffset = 4,\n  align = \"center\",\n  alignOffset = 4,\n  ...props\n}: PreviewCardPrimitive.Popup.Props &\n  Pick<PreviewCardPrimitive.Positioner.Props, \"align\" | \"alignOffset\" | \"side\" | \"sideOffset\">) {\n  return (\n    <PreviewCardPrimitive.Portal data-slot=\"hover-card-portal\">\n      <PreviewCardPrimitive.Positioner\n        align={align}\n        alignOffset={alignOffset}\n        side={side}\n        sideOffset={sideOffset}\n        className=\"isolate z-50\"\n      >\n        <PreviewCardPrimitive.Popup\n          data-slot=\"hover-card-content\"\n          className={cn(\n            \"data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 ring-foreground/10 bg-popover text-popover-foreground w-64 rounded-none p-2.5 text-xs/relaxed shadow-md ring-1 duration-100 z-50 origin-(--transform-origin) outline-hidden\",\n            className,\n          )}\n          {...props}\n        />\n      </PreviewCardPrimitive.Positioner>\n    </PreviewCardPrimitive.Portal>\n  );\n}\n\nexport { HoverCard, HoverCardTrigger, HoverCardContent };\n"
  },
  {
    "path": "apps/web/src/components/ui/input.tsx",
    "content": "import { Input as InputPrimitive } from \"@base-ui/react/input\";\nimport * as React from \"react\";\n\nimport { cn } from \"@/lib/utils\";\n\nfunction Input({ className, type, ...props }: React.ComponentProps<\"input\">) {\n  return (\n    <InputPrimitive\n      type={type}\n      data-slot=\"input\"\n      className={cn(\n        \"dark:bg-input/30 border-input focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 disabled:bg-input/50 dark:disabled:bg-input/80 h-8 rounded-none border bg-transparent px-2.5 py-1 text-xs transition-colors file:h-6 file:text-xs file:font-medium focus-visible:ring-1 aria-invalid:ring-1 md:text-xs file:text-foreground placeholder:text-muted-foreground w-full min-w-0 outline-none file:inline-flex file:border-0 file:bg-transparent disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50\",\n        className,\n      )}\n      {...props}\n    />\n  );\n}\n\nexport { Input };\n"
  },
  {
    "path": "apps/web/src/components/ui/kibo-ui/code-block/index.tsx",
    "content": "\"use client\";\n\nimport { useControlled } from \"@base-ui/utils/useControlled\";\nimport {\n  transformerNotationDiff,\n  transformerNotationErrorLevel,\n  transformerNotationFocus,\n  transformerNotationHighlight,\n  transformerNotationWordHighlight,\n} from \"@shikijs/transformers\";\nimport { CheckIcon, CopyIcon } from \"lucide-react\";\nimport type { ComponentProps, HTMLAttributes, ReactNode } from \"react\";\nimport { createContext, useContext, useEffect, useState } from \"react\";\nimport type { IconType } from \"react-icons\";\nimport {\n  SiAstro,\n  SiBiome,\n  SiBower,\n  SiBun,\n  SiC,\n  SiCircleci,\n  SiCoffeescript,\n  SiCplusplus,\n  SiCss,\n  SiCssmodules,\n  SiDart,\n  SiDocker,\n  SiDocusaurus,\n  SiDotenv,\n  SiEditorconfig,\n  SiEslint,\n  SiGatsby,\n  SiGitignoredotio,\n  SiGnubash,\n  SiGo,\n  SiGraphql,\n  SiGrunt,\n  SiGulp,\n  SiHandlebarsdotjs,\n  SiHtml5,\n  SiJavascript,\n  SiJest,\n  SiJson,\n  SiLess,\n  SiMarkdown,\n  SiMdx,\n  SiMintlify,\n  SiMocha,\n  SiMysql,\n  SiNextdotjs,\n  SiPerl,\n  SiPhp,\n  SiPostcss,\n  SiPrettier,\n  SiPrisma,\n  SiPug,\n  SiPython,\n  SiR,\n  SiReact,\n  SiReadme,\n  SiRedis,\n  SiRemix,\n  SiRive,\n  SiRollupdotjs,\n  SiRuby,\n  SiSanity,\n  SiSass,\n  SiScala,\n  SiSentry,\n  SiShadcnui,\n  SiStorybook,\n  SiStylelint,\n  SiSublimetext,\n  SiSvelte,\n  SiSvg,\n  SiSwift,\n  SiTailwindcss,\n  SiToml,\n  SiTypescript,\n  SiVercel,\n  SiVite,\n  SiVuedotjs,\n  SiWebassembly,\n} from \"react-icons/si\";\nimport { type BundledLanguage, type CodeOptionsMultipleThemes, codeToHtml } from \"shiki\";\n\nimport { Button } from \"@/components/ui/button\";\nimport {\n  Select,\n  SelectContent,\n  SelectItem,\n  SelectTrigger,\n  SelectValue,\n} from \"@/components/ui/select\";\nimport { cn } from \"@/lib/utils\";\n\nexport type { BundledLanguage } from \"shiki\";\n\nconst filenameIconMap = {\n  \".env\": SiDotenv,\n  \"*.astro\": SiAstro,\n  \"biome.json\": SiBiome,\n  \".bowerrc\": SiBower,\n  \"bun.lockb\": SiBun,\n  \"*.c\": SiC,\n  \"*.cpp\": SiCplusplus,\n  \".circleci/config.yml\": SiCircleci,\n  \"*.coffee\": SiCoffeescript,\n  \"*.module.css\": SiCssmodules,\n  \"*.css\": SiCss,\n  \"*.dart\": SiDart,\n  Dockerfile: SiDocker,\n  \"docusaurus.config.js\": SiDocusaurus,\n  \".editorconfig\": SiEditorconfig,\n  \".eslintrc\": SiEslint,\n  \"eslint.config.*\": SiEslint,\n  \"gatsby-config.*\": SiGatsby,\n  \".gitignore\": SiGitignoredotio,\n  \"*.go\": SiGo,\n  \"*.graphql\": SiGraphql,\n  \"*.sh\": SiGnubash,\n  \"Gruntfile.*\": SiGrunt,\n  \"gulpfile.*\": SiGulp,\n  \"*.hbs\": SiHandlebarsdotjs,\n  \"*.html\": SiHtml5,\n  \"*.js\": SiJavascript,\n  \"*.json\": SiJson,\n  \"*.test.js\": SiJest,\n  \"*.less\": SiLess,\n  \"*.md\": SiMarkdown,\n  \"*.mdx\": SiMdx,\n  \"mintlify.json\": SiMintlify,\n  \"mocha.opts\": SiMocha,\n  \"*.mustache\": SiHandlebarsdotjs,\n  \"*.sql\": SiMysql,\n  \"next.config.*\": SiNextdotjs,\n  \"*.pl\": SiPerl,\n  \"*.php\": SiPhp,\n  \"postcss.config.*\": SiPostcss,\n  \"prettier.config.*\": SiPrettier,\n  \"*.prisma\": SiPrisma,\n  \"*.pug\": SiPug,\n  \"*.py\": SiPython,\n  \"*.r\": SiR,\n  \"*.rb\": SiRuby,\n  \"*.jsx\": SiReact,\n  \"*.tsx\": SiReact,\n  \"readme.md\": SiReadme,\n  \"*.rdb\": SiRedis,\n  \"remix.config.*\": SiRemix,\n  \"*.riv\": SiRive,\n  \"rollup.config.*\": SiRollupdotjs,\n  \"sanity.config.*\": SiSanity,\n  \"*.sass\": SiSass,\n  \"*.scss\": SiSass,\n  \"*.sc\": SiScala,\n  \"*.scala\": SiScala,\n  \"sentry.client.config.*\": SiSentry,\n  \"components.json\": SiShadcnui,\n  \"storybook.config.*\": SiStorybook,\n  \"stylelint.config.*\": SiStylelint,\n  \".sublime-settings\": SiSublimetext,\n  \"*.svelte\": SiSvelte,\n  \"*.svg\": SiSvg,\n  \"*.swift\": SiSwift,\n  \"tailwind.config.*\": SiTailwindcss,\n  \"*.toml\": SiToml,\n  \"*.ts\": SiTypescript,\n  \"vercel.json\": SiVercel,\n  \"vite.config.*\": SiVite,\n  \"*.vue\": SiVuedotjs,\n  \"*.wasm\": SiWebassembly,\n};\n\nconst lineNumberClassNames = cn(\n  \"[&_code]:[counter-reset:line]\",\n  \"[&_code]:[counter-increment:line_0]\",\n  \"[&_.line]:before:content-[counter(line)]\",\n  \"[&_.line]:before:inline-block\",\n  \"[&_.line]:before:[counter-increment:line]\",\n  \"[&_.line]:before:w-4\",\n  \"[&_.line]:before:mr-4\",\n  \"[&_.line]:before:text-[13px]\",\n  \"[&_.line]:before:text-right\",\n  \"[&_.line]:before:text-muted-foreground/50\",\n  \"[&_.line]:before:font-mono\",\n  \"[&_.line]:before:select-none\",\n);\n\nconst darkModeClassNames = cn(\n  \"dark:[&_.shiki]:!text-[var(--shiki-dark)]\",\n  // \"dark:[&_.shiki]:!bg-[var(--shiki-dark-bg)]\",\n  \"dark:[&_.shiki]:![font-style:var(--shiki-dark-font-style)]\",\n  \"dark:[&_.shiki]:![font-weight:var(--shiki-dark-font-weight)]\",\n  \"dark:[&_.shiki]:![text-decoration:var(--shiki-dark-text-decoration)]\",\n  \"dark:[&_.shiki_span]:!text-[var(--shiki-dark)]\",\n  \"dark:[&_.shiki_span]:![font-style:var(--shiki-dark-font-style)]\",\n  \"dark:[&_.shiki_span]:![font-weight:var(--shiki-dark-font-weight)]\",\n  \"dark:[&_.shiki_span]:![text-decoration:var(--shiki-dark-text-decoration)]\",\n);\n\nconst lineHighlightClassNames = cn(\n  \"[&_.line.highlighted]:bg-blue-50\",\n  \"[&_.line.highlighted]:after:bg-blue-500\",\n  \"[&_.line.highlighted]:after:absolute\",\n  \"[&_.line.highlighted]:after:left-0\",\n  \"[&_.line.highlighted]:after:top-0\",\n  \"[&_.line.highlighted]:after:bottom-0\",\n  \"[&_.line.highlighted]:after:w-0.5\",\n  \"dark:[&_.line.highlighted]:!bg-blue-500/10\",\n);\n\nconst lineDiffClassNames = cn(\n  \"[&_.line.diff]:after:absolute\",\n  \"[&_.line.diff]:after:left-0\",\n  \"[&_.line.diff]:after:top-0\",\n  \"[&_.line.diff]:after:bottom-0\",\n  \"[&_.line.diff]:after:w-0.5\",\n  \"[&_.line.diff.add]:bg-emerald-50\",\n  \"[&_.line.diff.add]:after:bg-emerald-500\",\n  \"[&_.line.diff.remove]:bg-rose-50\",\n  \"[&_.line.diff.remove]:after:bg-rose-500\",\n  \"dark:[&_.line.diff.add]:!bg-emerald-500/10\",\n  \"dark:[&_.line.diff.remove]:!bg-rose-500/10\",\n);\n\nconst lineFocusedClassNames = cn(\n  \"[&_code:has(.focused)_.line]:blur-[2px]\",\n  \"[&_code:has(.focused)_.line.focused]:blur-none\",\n);\n\nconst wordHighlightClassNames = cn(\n  \"[&_.highlighted-word]:bg-blue-50\",\n  \"dark:[&_.highlighted-word]:!bg-blue-500/10\",\n);\n\nconst codeBlockClassName = cn(\n  \"mt-0 bg-background text-sm\",\n  \"[&_pre]:py-4\",\n  // \"[&_.shiki]:!bg-[var(--shiki-bg)]\",\n  \"[&_.shiki]:!bg-transparent\",\n  \"[&_code]:w-full\",\n  \"[&_code]:grid\",\n  \"[&_code]:overflow-x-auto\",\n  \"[&_code]:bg-transparent\",\n  \"[&_.line]:px-4\",\n  \"[&_.line]:w-full\",\n  \"[&_.line]:relative\",\n);\n\nconst highlight = (\n  html: string,\n  language?: BundledLanguage,\n  themes?: CodeOptionsMultipleThemes[\"themes\"],\n) =>\n  codeToHtml(html, {\n    lang: language ?? \"typescript\",\n    themes: themes ?? {\n      light: \"github-light\",\n      dark: \"github-dark-default\",\n    },\n    transformers: [\n      transformerNotationDiff({\n        matchAlgorithm: \"v3\",\n      }),\n      transformerNotationHighlight({\n        matchAlgorithm: \"v3\",\n      }),\n      transformerNotationWordHighlight({\n        matchAlgorithm: \"v3\",\n      }),\n      transformerNotationFocus({\n        matchAlgorithm: \"v3\",\n      }),\n      transformerNotationErrorLevel({\n        matchAlgorithm: \"v3\",\n      }),\n    ],\n  });\n\ntype CodeBlockData = {\n  language: string;\n  filename: string;\n  code: string;\n};\n\ntype CodeBlockContextType = {\n  value: string | undefined;\n  onValueChange: ((value: string) => void) | undefined;\n  data: CodeBlockData[];\n};\n\nconst CodeBlockContext = createContext<CodeBlockContextType>({\n  value: undefined,\n  onValueChange: undefined,\n  data: [],\n});\n\nexport type CodeBlockProps = HTMLAttributes<HTMLDivElement> & {\n  defaultValue?: string;\n  value?: string;\n  onValueChange?: (value: string) => void;\n  data: CodeBlockData[];\n};\n\nexport const CodeBlock = ({\n  value: controlledValue,\n  onValueChange: controlledOnValueChange,\n  defaultValue,\n  className,\n  data,\n  ...props\n}: CodeBlockProps) => {\n  const [value, setValue] = useControlled({\n    controlled: controlledValue,\n    default: defaultValue ?? \"\",\n    name: \"CodeBlock\",\n    state: \"value\",\n  });\n\n  const onValueChange = (newValue: string) => {\n    setValue(newValue);\n    controlledOnValueChange?.(newValue);\n  };\n\n  return (\n    <CodeBlockContext.Provider value={{ value, onValueChange, data }}>\n      <div className={cn(\"size-full overflow-hidden rounded-md\", className)} {...props} />\n    </CodeBlockContext.Provider>\n  );\n};\n\nexport type CodeBlockHeaderProps = HTMLAttributes<HTMLDivElement>;\n\nexport const CodeBlockHeader = ({ className, ...props }: CodeBlockHeaderProps) => (\n  <div className={cn(\"flex flex-row items-center border-b p-1\", className)} {...props} />\n);\n\nexport type CodeBlockFilesProps = Omit<HTMLAttributes<HTMLDivElement>, \"children\"> & {\n  children: (item: CodeBlockData) => ReactNode;\n};\n\nexport const CodeBlockFiles = ({ className, children, ...props }: CodeBlockFilesProps) => {\n  const { data } = useContext(CodeBlockContext);\n\n  return (\n    <div className={cn(\"flex grow flex-row items-center gap-2\", className)} {...props}>\n      {data.map(children)}\n    </div>\n  );\n};\n\nexport type CodeBlockFilenameProps = HTMLAttributes<HTMLDivElement> & {\n  icon?: IconType;\n  value?: string;\n};\n\nexport const CodeBlockFilename = ({\n  className: _className,\n  icon,\n  value,\n  children,\n  ...props\n}: CodeBlockFilenameProps) => {\n  const { value: activeValue } = useContext(CodeBlockContext);\n  const defaultIcon = Object.entries(filenameIconMap).find(([pattern]) => {\n    const regex = new RegExp(\n      `^${pattern.replace(/\\\\/g, \"\\\\\\\\\").replace(/\\./g, \"\\\\.\").replace(/\\*/g, \".*\")}$`,\n    );\n    return regex.test(children as string);\n  })?.[1];\n  const Icon = icon ?? defaultIcon;\n\n  if (value !== activeValue) {\n    return null;\n  }\n\n  return (\n    <div className=\"flex items-center gap-2 px-4 py-1.5 text-muted-foreground text-xs\" {...props}>\n      {Icon && <Icon className=\"h-4 w-4 shrink-0\" />}\n      <span className=\"flex-1 truncate\">{children}</span>\n    </div>\n  );\n};\n\nexport type CodeBlockSelectProps = ComponentProps<typeof Select>;\n\nexport const CodeBlockSelect = (props: CodeBlockSelectProps) => {\n  const { value, onValueChange } = useContext(CodeBlockContext);\n\n  return (\n    <Select\n      onValueChange={\n        onValueChange\n          ? (newValue: unknown) => {\n              onValueChange(newValue as string);\n            }\n          : undefined\n      }\n      value={value}\n      {...props}\n    />\n  );\n};\n\nexport type CodeBlockSelectTriggerProps = ComponentProps<typeof SelectTrigger>;\n\nexport const CodeBlockSelectTrigger = ({ className, ...props }: CodeBlockSelectTriggerProps) => (\n  <SelectTrigger\n    className={cn(\"w-fit border-none text-muted-foreground text-xs shadow-none\", className)}\n    {...props}\n  />\n);\n\nexport type CodeBlockSelectValueProps = ComponentProps<typeof SelectValue>;\n\nexport const CodeBlockSelectValue = (props: CodeBlockSelectValueProps) => (\n  <SelectValue {...props} />\n);\n\nexport type CodeBlockSelectContentProps = Omit<ComponentProps<typeof SelectContent>, \"children\"> & {\n  children: (item: CodeBlockData) => ReactNode;\n};\n\nexport const CodeBlockSelectContent = ({ children, ...props }: CodeBlockSelectContentProps) => {\n  const { data } = useContext(CodeBlockContext);\n\n  return <SelectContent {...props}>{data.map(children)}</SelectContent>;\n};\n\nexport type CodeBlockSelectItemProps = ComponentProps<typeof SelectItem>;\n\nexport const CodeBlockSelectItem = ({ className, ...props }: CodeBlockSelectItemProps) => (\n  <SelectItem className={cn(\"text-sm\", className)} {...props} />\n);\n\nexport type CodeBlockCopyButtonProps = Omit<ComponentProps<typeof Button>, \"onClick\"> & {\n  onCopy?: () => void;\n  onError?: (error: Error) => void;\n  timeout?: number;\n};\n\nexport const CodeBlockCopyButton = ({\n  onCopy,\n  onError,\n  timeout = 2000,\n  children,\n  className,\n  ...props\n}: CodeBlockCopyButtonProps) => {\n  const [isCopied, setIsCopied] = useState(false);\n  const { data, value } = useContext(CodeBlockContext);\n  const code = data.find((item) => item.language === value)?.code;\n\n  const copyToClipboard = () => {\n    if (typeof window === \"undefined\" || !navigator.clipboard.writeText || !code) {\n      return;\n    }\n\n    navigator.clipboard.writeText(code).then(() => {\n      setIsCopied(true);\n      onCopy?.();\n\n      setTimeout(() => setIsCopied(false), timeout);\n    }, onError);\n  };\n\n  const Icon = isCopied ? CheckIcon : CopyIcon;\n\n  return (\n    <Button\n      className={cn(\"shrink-0\", className)}\n      onClick={copyToClipboard}\n      size=\"icon\"\n      variant=\"ghost\"\n      {...props}\n    >\n      {children ?? <Icon className=\"text-muted-foreground\" size={14} />}\n    </Button>\n  );\n};\n\ntype CodeBlockFallbackProps = HTMLAttributes<HTMLDivElement> & {\n  className?: string;\n};\n\nconst CodeBlockFallback = ({ children, className, ...props }: CodeBlockFallbackProps) => (\n  <div className={cn(\"bg-fd-background\", className)} {...props}>\n    <pre className=\"w-full bg-fd-background\">\n      <code>\n        {children\n          ?.toString()\n          .split(\"\\n\")\n          .map((line, i) => (\n            <span className=\"line\" key={i}>\n              {line}\n            </span>\n          ))}\n      </code>\n    </pre>\n  </div>\n);\n\nexport type CodeBlockBodyProps = Omit<HTMLAttributes<HTMLDivElement>, \"children\"> & {\n  children: (item: CodeBlockData) => ReactNode;\n};\n\nexport const CodeBlockBody = ({ children, ...props }: CodeBlockBodyProps) => {\n  const { data } = useContext(CodeBlockContext);\n\n  return <div {...props}>{data.map(children)}</div>;\n};\n\nexport type CodeBlockItemProps = HTMLAttributes<HTMLDivElement> & {\n  value: string;\n  lineNumbers?: boolean;\n};\n\nexport const CodeBlockItem = ({\n  children,\n  lineNumbers = true,\n  className,\n  value,\n  ...props\n}: CodeBlockItemProps) => {\n  const { value: activeValue } = useContext(CodeBlockContext);\n\n  if (value !== activeValue) {\n    return null;\n  }\n\n  return (\n    <div\n      className={cn(\n        codeBlockClassName,\n        lineHighlightClassNames,\n        lineDiffClassNames,\n        lineFocusedClassNames,\n        wordHighlightClassNames,\n        darkModeClassNames,\n        lineNumbers && lineNumberClassNames,\n        className,\n      )}\n      {...props}\n    >\n      {children}\n    </div>\n  );\n};\n\nexport type CodeBlockContentProps = HTMLAttributes<HTMLDivElement> & {\n  themes?: CodeOptionsMultipleThemes[\"themes\"];\n  language?: BundledLanguage;\n  syntaxHighlighting?: boolean;\n  children: string;\n};\n\nexport const CodeBlockContent = ({\n  children,\n  themes,\n  language,\n  syntaxHighlighting = true,\n  className,\n  ...props\n}: CodeBlockContentProps) => {\n  const [html, setHtml] = useState<string | null>(null);\n\n  useEffect(() => {\n    if (!syntaxHighlighting) {\n      return;\n    }\n\n    highlight(children as string, language, themes)\n      .then(setHtml)\n      // biome-ignore lint/suspicious/noConsole: \"it's fine\"\n      .catch(console.error);\n  }, [children, themes, syntaxHighlighting, language]);\n\n  if (!(syntaxHighlighting && html)) {\n    return <CodeBlockFallback className={className}>{children}</CodeBlockFallback>;\n  }\n\n  return (\n    <div\n      className={className}\n      // biome-ignore lint/security/noDangerouslySetInnerHtml: \"Kinda how Shiki works\"\n      dangerouslySetInnerHTML={{ __html: html }}\n      {...props}\n    />\n  );\n};\n"
  },
  {
    "path": "apps/web/src/components/ui/kibo-ui/code-block/server.tsx",
    "content": "import {\n  transformerNotationDiff,\n  transformerNotationErrorLevel,\n  transformerNotationFocus,\n  transformerNotationHighlight,\n  transformerNotationWordHighlight,\n} from \"@shikijs/transformers\";\nimport type { HTMLAttributes } from \"react\";\nimport { type BundledLanguage, type CodeOptionsMultipleThemes, codeToHtml } from \"shiki\";\n\nexport type CodeBlockContentProps = HTMLAttributes<HTMLDivElement> & {\n  themes?: CodeOptionsMultipleThemes[\"themes\"];\n  language?: BundledLanguage;\n  children: string;\n  syntaxHighlighting?: boolean;\n};\n\nexport const CodeBlockContent = async ({\n  children,\n  themes,\n  language,\n  syntaxHighlighting = true,\n  ...props\n}: CodeBlockContentProps) => {\n  const html = syntaxHighlighting\n    ? await codeToHtml(children as string, {\n        lang: language ?? \"typescript\",\n        themes: themes ?? {\n          light: \"vitesse-light\",\n          dark: \"vitesse-dark\",\n        },\n        transformers: [\n          transformerNotationDiff({\n            matchAlgorithm: \"v3\",\n          }),\n          transformerNotationHighlight({\n            matchAlgorithm: \"v3\",\n          }),\n          transformerNotationWordHighlight({\n            matchAlgorithm: \"v3\",\n          }),\n          transformerNotationFocus({\n            matchAlgorithm: \"v3\",\n          }),\n          transformerNotationErrorLevel({\n            matchAlgorithm: \"v3\",\n          }),\n        ],\n      })\n    : children;\n\n  return (\n    <div\n      // biome-ignore lint/security/noDangerouslySetInnerHtml: \"Kinda how Shiki works\"\n      dangerouslySetInnerHTML={{ __html: html }}\n      {...props}\n    />\n  );\n};\n"
  },
  {
    "path": "apps/web/src/components/ui/kibo-ui/qr-code/index.tsx",
    "content": "\"use client\";\n\nimport { formatHex, oklch } from \"culori\";\nimport QR from \"qrcode\";\nimport { type HTMLAttributes, useEffect, useState } from \"react\";\n\nimport { cn } from \"@/lib/utils\";\n\nexport type QRCodeProps = HTMLAttributes<HTMLDivElement> & {\n  data: string;\n  foreground?: string;\n  background?: string;\n  robustness?: \"L\" | \"M\" | \"Q\" | \"H\";\n};\n\nconst oklchRegex = /oklch\\(([0-9.]+)\\s+([0-9.]+)\\s+([0-9.]+)\\)/;\n\nconst getOklch = (color: string, fallback: [number, number, number]) => {\n  const oklchMatch = color.match(oklchRegex);\n\n  if (!oklchMatch) {\n    return { l: fallback[0], c: fallback[1], h: fallback[2] };\n  }\n\n  return {\n    l: Number.parseFloat(oklchMatch[1]),\n    c: Number.parseFloat(oklchMatch[2]),\n    h: Number.parseFloat(oklchMatch[3]),\n  };\n};\n\nexport const QRCode = ({\n  data,\n  foreground,\n  background,\n  robustness = \"M\",\n  className,\n  ...props\n}: QRCodeProps) => {\n  const [svg, setSVG] = useState<string | null>(null);\n\n  useEffect(() => {\n    const generateQR = async () => {\n      try {\n        const styles = getComputedStyle(document.documentElement);\n        const foregroundColor = foreground ?? styles.getPropertyValue(\"--foreground\");\n        const backgroundColor = background ?? styles.getPropertyValue(\"--background\");\n\n        const foregroundOklch = getOklch(foregroundColor, [0.21, 0.006, 285.885]);\n        const backgroundOklch = getOklch(backgroundColor, [0.985, 0, 0]);\n\n        const newSvg = await QR.toString(data, {\n          type: \"svg\",\n          color: {\n            dark: formatHex(oklch({ mode: \"oklch\", ...foregroundOklch })),\n            light: formatHex(oklch({ mode: \"oklch\", ...backgroundOklch })),\n          },\n          width: 200,\n          errorCorrectionLevel: robustness,\n          margin: 0,\n        });\n\n        setSVG(newSvg);\n      } catch (err) {\n        console.error(err);\n      }\n    };\n\n    generateQR();\n  }, [data, foreground, background, robustness]);\n\n  if (!svg) {\n    return null;\n  }\n\n  return (\n    <div\n      className={cn(\"size-full\", \"[&_svg]:size-full\", className)}\n      // biome-ignore lint/security/noDangerouslySetInnerHtml: \"Required for SVG\"\n      dangerouslySetInnerHTML={{ __html: svg }}\n      {...props}\n    />\n  );\n};\n"
  },
  {
    "path": "apps/web/src/components/ui/kibo-ui/qr-code/server.tsx",
    "content": "import QR from \"qrcode\";\nimport type { HTMLAttributes } from \"react\";\n\nimport { cn } from \"@/lib/utils\";\n\nexport type QRCodeProps = HTMLAttributes<HTMLDivElement> & {\n  data: string;\n  foreground: string;\n  background: string;\n  robustness?: \"L\" | \"M\" | \"Q\" | \"H\";\n};\n\nexport const QRCode = async ({\n  data,\n  foreground,\n  background,\n  robustness = \"M\",\n  className,\n  ...props\n}: QRCodeProps) => {\n  const svg = await QR.toString(data, {\n    type: \"svg\",\n    color: {\n      dark: foreground,\n      light: background,\n    },\n    width: 200,\n    errorCorrectionLevel: robustness,\n  });\n\n  if (!svg) {\n    throw new Error(\"Failed to generate QR code\");\n  }\n\n  return (\n    <div\n      className={cn(\"size-full\", \"[&_svg]:size-full\", className)}\n      // biome-ignore lint/security/noDangerouslySetInnerHtml: \"Required for SVG\"\n      dangerouslySetInnerHTML={{ __html: svg }}\n      {...props}\n    />\n  );\n};\n"
  },
  {
    "path": "apps/web/src/components/ui/scroll-area.tsx",
    "content": "\"use client\";\n\nimport { ScrollArea as ScrollAreaPrimitive } from \"@base-ui/react/scroll-area\";\nimport * as React from \"react\";\n\nimport { cn } from \"@/lib/utils\";\n\nfunction ScrollArea({ className, children, ...props }: ScrollAreaPrimitive.Root.Props) {\n  return (\n    <ScrollAreaPrimitive.Root\n      data-slot=\"scroll-area\"\n      className={cn(\"relative\", className)}\n      {...props}\n    >\n      <ScrollAreaPrimitive.Viewport\n        data-slot=\"scroll-area-viewport\"\n        className=\"focus-visible:ring-ring/50 size-full rounded-[inherit] transition-[color,box-shadow] outline-none focus-visible:ring-[3px] focus-visible:outline-1\"\n      >\n        {children}\n      </ScrollAreaPrimitive.Viewport>\n      <ScrollBar />\n      <ScrollBar orientation=\"horizontal\" />\n      <ScrollAreaPrimitive.Corner />\n    </ScrollAreaPrimitive.Root>\n  );\n}\n\nfunction ScrollBar({\n  className,\n  orientation = \"vertical\",\n  ...props\n}: ScrollAreaPrimitive.Scrollbar.Props) {\n  return (\n    <ScrollAreaPrimitive.Scrollbar\n      data-slot=\"scroll-area-scrollbar\"\n      data-orientation={orientation}\n      orientation={orientation}\n      className={cn(\n        \"data-horizontal:h-2.5 data-horizontal:flex-col data-horizontal:border-t data-horizontal:border-t-transparent data-vertical:h-full data-vertical:w-2.5 data-vertical:border-l data-vertical:border-l-transparent flex touch-none p-px transition-colors select-none\",\n        className,\n      )}\n      {...props}\n    >\n      <ScrollAreaPrimitive.Thumb\n        data-slot=\"scroll-area-thumb\"\n        className=\"rounded-none bg-border relative flex-1\"\n      />\n    </ScrollAreaPrimitive.Scrollbar>\n  );\n}\n\nexport { ScrollArea, ScrollBar };\n"
  },
  {
    "path": "apps/web/src/components/ui/select.tsx",
    "content": "\"use client\";\n\nimport { Select as SelectPrimitive } from \"@base-ui/react/select\";\nimport { ChevronDownIcon, CheckIcon, ChevronUpIcon } from \"lucide-react\";\nimport * as React from \"react\";\n\nimport { cn } from \"@/lib/utils\";\n\nconst Select = SelectPrimitive.Root;\n\nfunction SelectGroup({ className, ...props }: SelectPrimitive.Group.Props) {\n  return (\n    <SelectPrimitive.Group\n      data-slot=\"select-group\"\n      className={cn(\"scroll-my-1\", className)}\n      {...props}\n    />\n  );\n}\n\nfunction SelectValue({ className, ...props }: SelectPrimitive.Value.Props) {\n  return (\n    <SelectPrimitive.Value\n      data-slot=\"select-value\"\n      className={cn(\"flex flex-1 text-left\", className)}\n      {...props}\n    />\n  );\n}\n\nfunction SelectTrigger({\n  className,\n  size = \"default\",\n  children,\n  ...props\n}: SelectPrimitive.Trigger.Props & {\n  size?: \"sm\" | \"default\";\n}) {\n  return (\n    <SelectPrimitive.Trigger\n      data-slot=\"select-trigger\"\n      data-size={size}\n      className={cn(\n        \"border-input data-[placeholder]:text-muted-foreground dark:bg-input/30 dark:hover:bg-input/50 focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 gap-1.5 rounded-none border bg-transparent py-2 pr-2 pl-2.5 text-xs transition-colors select-none focus-visible:ring-1 aria-invalid:ring-1 data-[size=default]:h-8 data-[size=sm]:h-7 data-[size=sm]:rounded-none *:data-[slot=select-value]:flex *:data-[slot=select-value]:gap-1.5 [&_svg:not([class*='size-'])]:size-4 flex w-fit items-center justify-between whitespace-nowrap outline-none disabled:cursor-not-allowed disabled:opacity-50 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center [&_svg]:pointer-events-none [&_svg]:shrink-0\",\n        className,\n      )}\n      {...props}\n    >\n      {children}\n      <SelectPrimitive.Icon\n        render={<ChevronDownIcon className=\"text-muted-foreground size-4 pointer-events-none\" />}\n      />\n    </SelectPrimitive.Trigger>\n  );\n}\n\nfunction SelectContent({\n  className,\n  children,\n  side = \"bottom\",\n  sideOffset = 4,\n  align = \"center\",\n  alignOffset = 0,\n  alignItemWithTrigger = true,\n  ...props\n}: SelectPrimitive.Popup.Props &\n  Pick<\n    SelectPrimitive.Positioner.Props,\n    \"align\" | \"alignOffset\" | \"side\" | \"sideOffset\" | \"alignItemWithTrigger\"\n  >) {\n  return (\n    <SelectPrimitive.Portal>\n      <SelectPrimitive.Positioner\n        side={side}\n        sideOffset={sideOffset}\n        align={align}\n        alignOffset={alignOffset}\n        alignItemWithTrigger={alignItemWithTrigger}\n        className=\"isolate z-50\"\n      >\n        <SelectPrimitive.Popup\n          data-slot=\"select-content\"\n          className={cn(\n            \"bg-popover text-popover-foreground data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 ring-foreground/10 min-w-36 rounded-none shadow-md ring-1 duration-100 relative isolate z-50 max-h-(--available-height) w-(--anchor-width) origin-(--transform-origin) overflow-x-hidden overflow-y-auto\",\n            className,\n          )}\n          {...props}\n        >\n          <SelectScrollUpButton />\n          <SelectPrimitive.List>{children}</SelectPrimitive.List>\n          <SelectScrollDownButton />\n        </SelectPrimitive.Popup>\n      </SelectPrimitive.Positioner>\n    </SelectPrimitive.Portal>\n  );\n}\n\nfunction SelectLabel({ className, ...props }: SelectPrimitive.GroupLabel.Props) {\n  return (\n    <SelectPrimitive.GroupLabel\n      data-slot=\"select-label\"\n      className={cn(\"text-muted-foreground px-2 py-2 text-xs\", className)}\n      {...props}\n    />\n  );\n}\n\nfunction SelectItem({ className, children, ...props }: SelectPrimitive.Item.Props) {\n  return (\n    <SelectPrimitive.Item\n      data-slot=\"select-item\"\n      className={cn(\n        \"focus:bg-accent focus:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground gap-2 rounded-none py-2 pr-8 pl-2 text-xs [&_svg:not([class*='size-'])]:size-4 *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2 relative flex w-full cursor-default items-center outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0\",\n        className,\n      )}\n      {...props}\n    >\n      <SelectPrimitive.ItemText className=\"flex flex-1 gap-2 shrink-0 whitespace-nowrap\">\n        {children}\n      </SelectPrimitive.ItemText>\n      <SelectPrimitive.ItemIndicator\n        render={\n          <span className=\"pointer-events-none absolute right-2 flex size-4 items-center justify-center\" />\n        }\n      >\n        <CheckIcon className=\"pointer-events-none\" />\n      </SelectPrimitive.ItemIndicator>\n    </SelectPrimitive.Item>\n  );\n}\n\nfunction SelectSeparator({ className, ...props }: SelectPrimitive.Separator.Props) {\n  return (\n    <SelectPrimitive.Separator\n      data-slot=\"select-separator\"\n      className={cn(\"bg-border -mx-1 h-px pointer-events-none\", className)}\n      {...props}\n    />\n  );\n}\n\nfunction SelectScrollUpButton({\n  className,\n  ...props\n}: React.ComponentProps<typeof SelectPrimitive.ScrollUpArrow>) {\n  return (\n    <SelectPrimitive.ScrollUpArrow\n      data-slot=\"select-scroll-up-button\"\n      className={cn(\n        \"bg-popover z-10 flex cursor-default items-center justify-center py-1 [&_svg:not([class*='size-'])]:size-4 top-0 w-full\",\n        className,\n      )}\n      {...props}\n    >\n      <ChevronUpIcon />\n    </SelectPrimitive.ScrollUpArrow>\n  );\n}\n\nfunction SelectScrollDownButton({\n  className,\n  ...props\n}: React.ComponentProps<typeof SelectPrimitive.ScrollDownArrow>) {\n  return (\n    <SelectPrimitive.ScrollDownArrow\n      data-slot=\"select-scroll-down-button\"\n      className={cn(\n        \"bg-popover z-10 flex cursor-default items-center justify-center py-1 [&_svg:not([class*='size-'])]:size-4 bottom-0 w-full\",\n        className,\n      )}\n      {...props}\n    >\n      <ChevronDownIcon />\n    </SelectPrimitive.ScrollDownArrow>\n  );\n}\n\nexport {\n  Select,\n  SelectContent,\n  SelectGroup,\n  SelectItem,\n  SelectLabel,\n  SelectScrollDownButton,\n  SelectScrollUpButton,\n  SelectSeparator,\n  SelectTrigger,\n  SelectValue,\n};\n"
  },
  {
    "path": "apps/web/src/components/ui/share-dialog.tsx",
    "content": "\"use client\";\n\nimport { Check, Copy, Terminal } from \"lucide-react\";\nimport { useTheme } from \"next-themes\";\nimport Image from \"next/image\";\nimport QRCode from \"qrcode\";\nimport React, { useEffect, useState } from \"react\";\nimport { FaXTwitter } from \"react-icons/fa6\";\nimport { toast } from \"sonner\";\n\nimport {\n  Dialog,\n  DialogContent,\n  DialogDescription,\n  DialogHeader,\n  DialogTitle,\n  DialogTrigger,\n} from \"@/components/ui/dialog\";\nimport { TechBadge } from \"@/components/ui/tech-badge\";\nimport type { StackState } from \"@/lib/constant\";\nimport { TECH_OPTIONS } from \"@/lib/constant\";\nimport { CATEGORY_ORDER } from \"@/lib/stack-utils\";\nimport { cn } from \"@/lib/utils\";\n\ninterface ShareDialogProps {\n  children: React.ReactNode;\n  stackUrl: string;\n  stackState: StackState;\n}\n\nexport function ShareDialog({ children, stackUrl, stackState }: ShareDialogProps) {\n  const [copied, setCopied] = useState(false);\n  const [qrCodeDataUrl, setQrCodeDataUrl] = useState<string>(\"\");\n  const { resolvedTheme } = useTheme();\n\n  const techBadges = (() => {\n    const badges: React.ReactNode[] = [];\n    for (const category of CATEGORY_ORDER) {\n      const categoryKey = category as keyof StackState;\n      const options = TECH_OPTIONS[category as keyof typeof TECH_OPTIONS];\n      const selectedValue = stackState[categoryKey];\n\n      if (!options) continue;\n\n      if (Array.isArray(selectedValue)) {\n        if (\n          selectedValue.length === 0 ||\n          (selectedValue.length === 1 && selectedValue[0] === \"none\")\n        ) {\n          continue;\n        }\n\n        for (const id of selectedValue) {\n          if (id === \"none\") continue;\n          const tech = options.find((opt) => opt.id === id);\n          if (tech) {\n            badges.push(\n              <TechBadge\n                key={`${category}-${tech.id}`}\n                icon={tech.icon}\n                name={tech.name}\n                category={category}\n              />,\n            );\n          }\n        }\n      } else {\n        const tech = options.find((opt) => opt.id === selectedValue);\n        if (\n          !tech ||\n          tech.id === \"none\" ||\n          tech.id === \"false\" ||\n          ((category === \"git\" || category === \"install\" || category === \"auth\") &&\n            tech.id === \"true\")\n        ) {\n          continue;\n        }\n        badges.push(\n          <TechBadge\n            key={`${category}-${tech.id}`}\n            icon={tech.icon}\n            name={tech.name}\n            category={category}\n          />,\n        );\n      }\n    }\n    return badges;\n  })();\n\n  const copyToClipboard = async () => {\n    try {\n      await navigator.clipboard.writeText(stackUrl);\n      setCopied(true);\n      toast.success(\"Link copied to clipboard!\");\n      setTimeout(() => setCopied(false), 2000);\n    } catch {\n      toast.error(\"Failed to copy link\");\n    }\n  };\n\n  const shareToTwitter = () => {\n    const text = encodeURIComponent(\n      `Check out this cool tech stack I configured with Create Better T Stack!\\n\\n🚀 ${techBadges.length} technologies selected\\n\\n`,\n    );\n    const url = encodeURIComponent(stackUrl);\n    window.open(`https://twitter.com/intent/tweet?text=${text}&url=${url}`, \"_blank\");\n  };\n\n  useEffect(() => {\n    const generateQRCode = async () => {\n      try {\n        const isDark = resolvedTheme === \"dark\";\n        const dataUrl = await QRCode.toDataURL(stackUrl, {\n          width: 128,\n          margin: 2,\n          color: {\n            dark: isDark ? \"#cdd6f4\" : \"#11111b\",\n            light: isDark ? \"#11111b\" : \"#ffffff\",\n          },\n        });\n        setQrCodeDataUrl(dataUrl);\n      } catch (error) {\n        console.error(\"Failed to generate QR code:\", error);\n        setQrCodeDataUrl(\"\");\n      }\n    };\n\n    if (stackUrl) {\n      generateQRCode();\n    }\n  }, [stackUrl, resolvedTheme]);\n\n  return (\n    <Dialog>\n      <DialogTrigger\n        render={\n          React.isValidElement(children) ? children : <button type=\"button\">{children}</button>\n        }\n      />\n      <DialogContent className=\"grid grid-cols-1 bg-fd-background sm:max-w-md\">\n        <DialogHeader className=\"border-border border-b pb-4\">\n          <div className=\"flex items-center gap-2\">\n            <Terminal className=\"h-4 w-4 text-primary\" />\n            <DialogTitle className=\"font-mono font-semibold text-foreground text-sm\">\n              SHARE_STACK.SH\n            </DialogTitle>\n          </div>\n          <DialogDescription className=\"font-mono text-muted-foreground text-xs\">\n            $ ./share_configuration --export\n          </DialogDescription>\n        </DialogHeader>\n\n        <div className=\"space-y-4\">\n          <div className=\"rounded border border-border\">\n            <div className=\"border-border border-b px-3 py-2\">\n              <div className=\"flex items-center gap-2\">\n                <span className=\"text-primary text-xs\">▶</span>\n                <span className=\"font-mono font-semibold text-foreground text-xs\">\n                  DEPENDENCIES.LIST\n                </span>\n                <div className=\"ml-auto flex items-center gap-2 text-muted-foreground text-xs\">\n                  <span>•</span>\n                  <span>{techBadges.length} PACKAGES</span>\n                </div>\n              </div>\n            </div>\n            <div className=\"p-3\">\n              <div className=\"flex flex-wrap gap-1.5\">\n                {techBadges.length > 0 ? (\n                  techBadges\n                ) : (\n                  <div className=\"flex items-center gap-2 text-muted-foreground text-sm\">\n                    <span className=\"text-primary\">$</span>\n                    <span>No technologies selected</span>\n                  </div>\n                )}\n              </div>\n            </div>\n          </div>\n\n          <div className=\"rounded border border-border\">\n            <div className=\"border-border border-b px-3 py-2\">\n              <div className=\"flex items-center gap-2\">\n                <span className=\"text-primary text-xs\">▶</span>\n                <span className=\"font-mono font-semibold text-foreground text-xs\">QR_CODE.PNG</span>\n              </div>\n            </div>\n            <div className=\"p-4\">\n              <div className=\"flex items-center justify-center rounded border border-border bg-muted/20 p-4\">\n                <div className=\"flex h-32 w-32 items-center justify-center\">\n                  {qrCodeDataUrl ? (\n                    <Image\n                      src={qrCodeDataUrl}\n                      width={128}\n                      height={128}\n                      alt=\"QR Code for stack configuration\"\n                      className=\"h-full w-full object-contain\"\n                    />\n                  ) : (\n                    <div className=\"flex flex-col items-center gap-2 text-muted-foreground text-xs\">\n                      <span className=\"text-primary\">$</span>\n                      <span>Generating QR code...</span>\n                    </div>\n                  )}\n                </div>\n              </div>\n              <div className=\"mt-2 flex items-center justify-center gap-2 text-muted-foreground text-xs\">\n                <span className=\"text-primary\">$</span>\n                <span>scan --url stack_config</span>\n              </div>\n            </div>\n          </div>\n\n          <div className=\"rounded border border-border\">\n            <div className=\"border-border border-b px-3 py-2\">\n              <div className=\"flex items-center gap-2\">\n                <span className=\"text-primary text-xs\">▶</span>\n                <span className=\"font-mono font-semibold text-foreground text-xs\">\n                  EXPORT_ACTIONS.SH\n                </span>\n              </div>\n            </div>\n            <div className=\"p-3\">\n              <div className=\"grid gap-2\">\n                <button\n                  type=\"button\"\n                  onClick={shareToTwitter}\n                  className=\"flex items-center gap-2 rounded border border-border bg-fd-background px-3 py-2 font-mono text-muted-foreground text-xs transition-all hover:border-muted-foreground/30 hover:bg-muted hover:text-foreground\"\n                >\n                  <FaXTwitter className=\"h-3 w-3\" />\n                  <span className=\"text-primary\">$</span>\n                  <span>./share --platform twitter</span>\n                </button>\n\n                <button\n                  type=\"button\"\n                  onClick={copyToClipboard}\n                  className={cn(\n                    \"flex items-center gap-2 rounded border px-3 py-2 font-mono text-xs transition-all\",\n                    copied\n                      ? \"border-green-500/20 bg-green-500/10 text-green-600 dark:text-green-400\"\n                      : \"border-border bg-fd-background text-muted-foreground hover:border-muted-foreground/30 hover:bg-muted hover:text-foreground\",\n                  )}\n                >\n                  {copied ? <Check className=\"h-3 w-3\" /> : <Copy className=\"h-3 w-3\" />}\n                  <span className=\"text-primary\">$</span>\n                  <span>{copied ? \"./copy --status success\" : \"./copy --url clipboard\"}</span>\n                </button>\n              </div>\n            </div>\n          </div>\n        </div>\n      </DialogContent>\n    </Dialog>\n  );\n}\n"
  },
  {
    "path": "apps/web/src/components/ui/skeleton.tsx",
    "content": "import { cn } from \"@/lib/utils\";\n\nfunction Skeleton({ className, ...props }: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"skeleton\"\n      className={cn(\"bg-muted rounded-none animate-pulse\", className)}\n      {...props}\n    />\n  );\n}\n\nexport { Skeleton };\n"
  },
  {
    "path": "apps/web/src/components/ui/sonner.tsx",
    "content": "\"use client\";\n\nimport {\n  CircleCheckIcon,\n  InfoIcon,\n  TriangleAlertIcon,\n  OctagonXIcon,\n  Loader2Icon,\n} from \"lucide-react\";\nimport { useTheme } from \"next-themes\";\nimport { Toaster as Sonner, type ToasterProps } from \"sonner\";\n\nconst Toaster = ({ ...props }: ToasterProps) => {\n  const { theme = \"system\" } = useTheme();\n\n  return (\n    <Sonner\n      theme={theme as ToasterProps[\"theme\"]}\n      className=\"toaster group\"\n      icons={{\n        success: <CircleCheckIcon className=\"size-4\" />,\n        info: <InfoIcon className=\"size-4\" />,\n        warning: <TriangleAlertIcon className=\"size-4\" />,\n        error: <OctagonXIcon className=\"size-4\" />,\n        loading: <Loader2Icon className=\"size-4 animate-spin\" />,\n      }}\n      style={\n        {\n          \"--normal-bg\": \"var(--popover)\",\n          \"--normal-text\": \"var(--popover-foreground)\",\n          \"--normal-border\": \"var(--border)\",\n          \"--border-radius\": \"var(--radius)\",\n        } as React.CSSProperties\n      }\n      toastOptions={{\n        classNames: {\n          toast: \"cn-toast\",\n        },\n      }}\n      {...props}\n    />\n  );\n};\n\nexport { Toaster };\n"
  },
  {
    "path": "apps/web/src/components/ui/spinner.tsx",
    "content": "import { Loader2Icon } from \"lucide-react\";\n\nimport { cn } from \"@/lib/utils\";\n\nfunction Spinner({ className, ...props }: React.ComponentProps<\"svg\">) {\n  return (\n    <Loader2Icon\n      role=\"status\"\n      aria-label=\"Loading\"\n      className={cn(\"size-4 animate-spin\", className)}\n      {...props}\n    />\n  );\n}\n\nexport { Spinner };\n"
  },
  {
    "path": "apps/web/src/components/ui/switch.tsx",
    "content": "\"use client\";\n\nimport { Switch as SwitchPrimitive } from \"@base-ui/react/switch\";\n\nimport { cn } from \"@/lib/utils\";\n\nfunction Switch({\n  className,\n  size = \"default\",\n  ...props\n}: SwitchPrimitive.Root.Props & {\n  size?: \"sm\" | \"default\";\n}) {\n  return (\n    <SwitchPrimitive.Root\n      data-slot=\"switch\"\n      data-size={size}\n      className={cn(\n        \"data-checked:bg-primary data-unchecked:bg-input focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 dark:data-unchecked:bg-input/80 shrink-0 rounded-full border border-transparent focus-visible:ring-1 aria-invalid:ring-1 data-[size=default]:h-[18.4px] data-[size=default]:w-[32px] data-[size=sm]:h-[14px] data-[size=sm]:w-[24px] peer group/switch relative inline-flex items-center transition-all outline-none after:absolute after:-inset-x-3 after:-inset-y-2 data-disabled:cursor-not-allowed data-disabled:opacity-50\",\n        className,\n      )}\n      {...props}\n    >\n      <SwitchPrimitive.Thumb\n        data-slot=\"switch-thumb\"\n        className=\"bg-background dark:data-unchecked:bg-foreground dark:data-checked:bg-primary-foreground rounded-full group-data-[size=default]/switch:size-4 group-data-[size=sm]/switch:size-3 group-data-[size=default]/switch:data-checked:translate-x-[calc(100%-2px)] group-data-[size=sm]/switch:data-checked:translate-x-[calc(100%-2px)] group-data-[size=default]/switch:data-unchecked:translate-x-0 group-data-[size=sm]/switch:data-unchecked:translate-x-0 pointer-events-none block ring-0 transition-transform\"\n      />\n    </SwitchPrimitive.Root>\n  );\n}\n\nexport { Switch };\n"
  },
  {
    "path": "apps/web/src/components/ui/tabs.tsx",
    "content": "\"use client\";\n\nimport { Tabs as TabsPrimitive } from \"@base-ui/react/tabs\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\n\nimport { cn } from \"@/lib/utils\";\n\nfunction Tabs({ className, orientation = \"horizontal\", ...props }: TabsPrimitive.Root.Props) {\n  return (\n    <TabsPrimitive.Root\n      data-slot=\"tabs\"\n      data-orientation={orientation}\n      className={cn(\"gap-2 group/tabs flex data-[orientation=horizontal]:flex-col\", className)}\n      {...props}\n    />\n  );\n}\n\nconst tabsListVariants = cva(\n  \"rounded-none p-[3px] group-data-horizontal/tabs:h-8 data-[variant=line]:rounded-none group/tabs-list text-muted-foreground inline-flex w-fit items-center justify-center group-data-[orientation=vertical]/tabs:h-fit group-data-[orientation=vertical]/tabs:flex-col\",\n  {\n    variants: {\n      variant: {\n        default: \"bg-muted\",\n        line: \"gap-1 bg-transparent\",\n      },\n    },\n    defaultVariants: {\n      variant: \"default\",\n    },\n  },\n);\n\nfunction TabsList({\n  className,\n  variant = \"default\",\n  ...props\n}: TabsPrimitive.List.Props & VariantProps<typeof tabsListVariants>) {\n  return (\n    <TabsPrimitive.List\n      data-slot=\"tabs-list\"\n      data-variant={variant}\n      className={cn(tabsListVariants({ variant }), className)}\n      {...props}\n    />\n  );\n}\n\nfunction TabsTrigger({ className, ...props }: TabsPrimitive.Tab.Props) {\n  return (\n    <TabsPrimitive.Tab\n      data-slot=\"tabs-trigger\"\n      className={cn(\n        \"gap-1.5 rounded-none border border-transparent px-1.5 py-0.5 text-xs font-medium group-data-vertical/tabs:py-[calc(--spacing(1.25))] [&_svg:not([class*='size-'])]:size-4 focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:outline-ring text-foreground/60 hover:text-foreground dark:text-muted-foreground dark:hover:text-foreground relative inline-flex h-[calc(100%-1px)] flex-1 items-center justify-center whitespace-nowrap transition-all group-data-[orientation=vertical]/tabs:w-full group-data-[orientation=vertical]/tabs:justify-start focus-visible:ring-[3px] focus-visible:outline-1 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0\",\n        \"group-data-[variant=line]/tabs-list:bg-transparent group-data-[variant=line]/tabs-list:data-active:bg-transparent dark:group-data-[variant=line]/tabs-list:data-active:border-transparent dark:group-data-[variant=line]/tabs-list:data-active:bg-transparent\",\n        \"data-active:bg-background dark:data-active:text-foreground dark:data-active:border-input dark:data-active:bg-input/30 data-active:text-foreground\",\n        \"after:bg-foreground after:absolute after:opacity-0 after:transition-opacity group-data-[orientation=horizontal]/tabs:after:inset-x-0 group-data-[orientation=horizontal]/tabs:after:bottom-[-5px] group-data-[orientation=horizontal]/tabs:after:h-0.5 group-data-[orientation=vertical]/tabs:after:inset-y-0 group-data-[orientation=vertical]/tabs:after:-right-1 group-data-[orientation=vertical]/tabs:after:w-0.5 group-data-[variant=line]/tabs-list:data-active:after:opacity-100\",\n        className,\n      )}\n      {...props}\n    />\n  );\n}\n\nfunction TabsContent({ className, ...props }: TabsPrimitive.Panel.Props) {\n  return (\n    <TabsPrimitive.Panel\n      data-slot=\"tabs-content\"\n      className={cn(\"text-xs/relaxed flex-1 outline-none\", className)}\n      {...props}\n    />\n  );\n}\n\nexport { Tabs, TabsList, TabsTrigger, TabsContent, tabsListVariants };\n"
  },
  {
    "path": "apps/web/src/components/ui/tech-badge.tsx",
    "content": "\"use client\";\n\nimport { useTheme } from \"next-themes\";\nimport Image from \"next/image\";\n\nimport { cn } from \"@/lib/utils\";\n\ninterface TechBadgeProps {\n  icon: string;\n  name: string;\n  category: string;\n  className?: string;\n}\n\nconst getBadgeColors = (category: string): string => {\n  switch (category) {\n    case \"webFrontend\":\n    case \"nativeFrontend\":\n      return \"border-blue-300 bg-blue-100 text-blue-800 dark:border-blue-700/30 dark:bg-blue-900/30 dark:text-blue-300\";\n    case \"runtime\":\n      return \"border-amber-300 bg-amber-100 text-amber-800 dark:border-amber-700/30 dark:bg-amber-900/30 dark:text-amber-300\";\n    case \"backend\":\n      return \"border-sky-300 bg-sky-100 text-sky-800 dark:border-sky-700/30 dark:bg-sky-900/30 dark:text-sky-300\";\n    case \"api\":\n      return \"border-indigo-300 bg-indigo-100 text-indigo-800 dark:border-indigo-700/30 dark:bg-indigo-900/30 dark:text-indigo-300\";\n    case \"database\":\n      return \"border-emerald-300 bg-emerald-100 text-emerald-800 dark:border-emerald-700/30 dark:bg-emerald-900/30 dark:text-emerald-300\";\n    case \"orm\":\n      return \"border-cyan-300 bg-cyan-100 text-cyan-800 dark:border-cyan-700/30 dark:bg-cyan-900/30 dark:text-cyan-300\";\n    case \"auth\":\n      return \"border-green-300 bg-green-100 text-green-800 dark:border-green-700/30 dark:bg-green-900/30 dark:text-green-300\";\n    case \"dbSetup\":\n      return \"border-pink-300 bg-pink-100 text-pink-800 dark:border-pink-700/30 dark:bg-pink-900/30 dark:text-pink-300\";\n    case \"addons\":\n      return \"border-violet-300 bg-violet-100 text-violet-800 dark:border-violet-700/30 dark:bg-violet-900/30 dark:text-violet-300\";\n    case \"examples\":\n      return \"border-teal-300 bg-teal-100 text-teal-800 dark:border-teal-700/30 dark:bg-teal-900/30 dark:text-teal-300\";\n    case \"packageManager\":\n      return \"border-orange-300 bg-orange-100 text-orange-800 dark:border-orange-700/30 dark:bg-orange-900/30 dark:text-orange-300\";\n    case \"git\":\n    case \"webDeploy\":\n    case \"serverDeploy\":\n    case \"install\":\n      return \"border-gray-300 bg-gray-100 text-gray-700 dark:border-gray-600 dark:bg-gray-800 dark:text-gray-400\";\n    default:\n      return \"border-gray-300 bg-gray-100 text-gray-800 dark:border-gray-700/30 dark:bg-gray-900/30 dark:text-gray-300\";\n  }\n};\n\nfunction TechIcon({ icon, name, className }: { icon: string; name: string; className?: string }) {\n  const { theme } = useTheme();\n\n  if (!icon) return null;\n\n  if (!icon.startsWith(\"https://\")) {\n    return <span className={cn(\"inline-flex items-center text-lg\", className)}>{icon}</span>;\n  }\n\n  let iconSrc = icon;\n  if (\n    theme === \"light\" &&\n    (icon.includes(\"drizzle\") ||\n      icon.includes(\"prisma\") ||\n      icon.includes(\"express\") ||\n      icon.includes(\"clerk\") ||\n      icon.includes(\"planetscale\") ||\n      icon.includes(\"nx\") ||\n      icon.includes(\"polar\") ||\n      icon.includes(\"astro\"))\n  ) {\n    iconSrc = icon.replace(\".svg\", \"-light.svg\");\n  }\n\n  return (\n    <Image\n      suppressHydrationWarning\n      src={iconSrc}\n      alt={`${name} icon`}\n      width={20}\n      height={20}\n      className={cn(\"inline-block\", className)}\n      unoptimized\n    />\n  );\n}\n\nexport function TechBadge({ icon, name, category, className }: TechBadgeProps) {\n  return (\n    <span\n      className={cn(\n        \"inline-flex items-center gap-1.5 rounded-full border px-2 py-0.5 text-xs\",\n        getBadgeColors(category),\n        className,\n      )}\n    >\n      {icon !== \"\" && <TechIcon icon={icon} name={name} className={cn(\"h-3 w-3\")} />}\n      {name}\n    </span>\n  );\n}\n"
  },
  {
    "path": "apps/web/src/components/ui/toggle.tsx",
    "content": "\"use client\";\n\nimport { Toggle as TogglePrimitive } from \"@base-ui/react/toggle\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\n\nimport { cn } from \"@/lib/utils\";\n\nconst toggleVariants = cva(\n  \"hover:text-foreground aria-pressed:bg-muted focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive data-[state=on]:bg-muted gap-1 rounded-none text-xs font-medium transition-all [&_svg:not([class*='size-'])]:size-4 group/toggle hover:bg-muted inline-flex items-center justify-center whitespace-nowrap outline-none focus-visible:ring-[3px] disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0\",\n  {\n    variants: {\n      variant: {\n        default: \"bg-transparent\",\n        outline: \"border-input hover:bg-muted border bg-transparent\",\n      },\n      size: {\n        default: \"h-8 min-w-8 px-2\",\n        sm: \"h-7 min-w-7 rounded-none px-1.5\",\n        lg: \"h-9 min-w-9 px-2.5\",\n      },\n    },\n    defaultVariants: {\n      variant: \"default\",\n      size: \"default\",\n    },\n  },\n);\n\nfunction Toggle({\n  className,\n  variant = \"default\",\n  size = \"default\",\n  ...props\n}: TogglePrimitive.Props & VariantProps<typeof toggleVariants>) {\n  return (\n    <TogglePrimitive\n      data-slot=\"toggle\"\n      className={cn(toggleVariants({ variant, size, className }))}\n      {...props}\n    />\n  );\n}\n\nexport { Toggle, toggleVariants };\n"
  },
  {
    "path": "apps/web/src/components/ui/tooltip.tsx",
    "content": "\"use client\";\n\nimport { Tooltip as TooltipPrimitive } from \"@base-ui/react/tooltip\";\n\nimport { cn } from \"@/lib/utils\";\n\nfunction TooltipProvider({ delay = 0, ...props }: TooltipPrimitive.Provider.Props) {\n  return <TooltipPrimitive.Provider data-slot=\"tooltip-provider\" delay={delay} {...props} />;\n}\n\nfunction Tooltip({ delay, ...props }: TooltipPrimitive.Root.Props & { delay?: number }) {\n  return (\n    <TooltipProvider delay={delay}>\n      <TooltipPrimitive.Root data-slot=\"tooltip\" {...props} />\n    </TooltipProvider>\n  );\n}\n\nfunction TooltipTrigger({ ...props }: TooltipPrimitive.Trigger.Props) {\n  return <TooltipPrimitive.Trigger data-slot=\"tooltip-trigger\" {...props} />;\n}\n\nfunction TooltipContent({\n  className,\n  side = \"top\",\n  sideOffset = 4,\n  align = \"center\",\n  alignOffset = 0,\n  children,\n  ...props\n}: TooltipPrimitive.Popup.Props &\n  Pick<TooltipPrimitive.Positioner.Props, \"align\" | \"alignOffset\" | \"side\" | \"sideOffset\">) {\n  return (\n    <TooltipPrimitive.Portal>\n      <TooltipPrimitive.Positioner\n        align={align}\n        alignOffset={alignOffset}\n        side={side}\n        sideOffset={sideOffset}\n        className=\"isolate z-50\"\n      >\n        <TooltipPrimitive.Popup\n          data-slot=\"tooltip-content\"\n          className={cn(\n            \"data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-[state=delayed-open]:animate-in data-[state=delayed-open]:fade-in-0 data-[state=delayed-open]:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 rounded-none px-3 py-1.5 text-xs bg-foreground text-background z-50 w-fit max-w-xs origin-(--transform-origin)\",\n            className,\n          )}\n          {...props}\n        >\n          {children}\n          <TooltipPrimitive.Arrow className=\"size-2.5 translate-y-[calc(-50%_-_2px)] rotate-45 rounded-none bg-foreground fill-foreground z-50 data-[side=bottom]:top-1 data-[side=left]:top-1/2! data-[side=left]:-right-1 data-[side=left]:-translate-y-1/2 data-[side=right]:top-1/2! data-[side=right]:-left-1 data-[side=right]:-translate-y-1/2 data-[side=top]:-bottom-2.5\" />\n        </TooltipPrimitive.Popup>\n      </TooltipPrimitive.Positioner>\n    </TooltipPrimitive.Portal>\n  );\n}\n\nexport { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider };\n"
  },
  {
    "path": "apps/web/src/lib/constant.ts",
    "content": "import type { TechCategory } from \"./types\";\n\nexport const ICON_BASE_URL = \"https://r2.better-t-stack.dev/icons\";\n\nexport const TECH_OPTIONS: Record<\n  TechCategory,\n  {\n    id: string;\n    name: string;\n    description: string;\n    icon: string;\n    color: string;\n    default?: boolean;\n    className?: string;\n  }[]\n> = {\n  api: [\n    {\n      id: \"trpc\",\n      name: \"tRPC\",\n      description: \"End-to-end typesafe APIs\",\n      icon: `${ICON_BASE_URL}/trpc.svg`,\n      color: \"from-blue-500 to-blue-700\",\n      default: true,\n    },\n    {\n      id: \"orpc\",\n      name: \"oRPC\",\n      description: \"Typesafe APIs Made Simple\",\n      icon: `${ICON_BASE_URL}/orpc.svg`,\n      color: \"from-indigo-400 to-indigo-600\",\n    },\n    {\n      id: \"none\",\n      name: \"No API\",\n      description: \"No API layer (API routes disabled)\",\n      icon: \"\",\n      color: \"from-gray-400 to-gray-600\",\n    },\n  ],\n  webFrontend: [\n    {\n      id: \"tanstack-router\",\n      name: \"TanStack Router\",\n      description: \"Modern type-safe router for React\",\n      icon: `${ICON_BASE_URL}/tanstack.svg`,\n      color: \"from-blue-400 to-blue-600\",\n      default: true,\n    },\n    {\n      id: \"react-router\",\n      name: \"React Router\",\n      description: \"Declarative routing for React\",\n      icon: `${ICON_BASE_URL}/react-router.svg`,\n      color: \"from-cyan-400 to-cyan-600\",\n      default: false,\n    },\n    {\n      id: \"tanstack-start\",\n      name: \"TanStack Start\",\n      description: \"Full-stack React and Solid framework powered by TanStack Router\",\n      icon: `${ICON_BASE_URL}/tanstack.svg`,\n      color: \"from-purple-400 to-purple-600\",\n      default: false,\n    },\n    {\n      id: \"next\",\n      name: \"Next.js\",\n      description: \"React framework with hybrid rendering\",\n      icon: `${ICON_BASE_URL}/nextjs.svg`,\n      color: \"from-gray-700 to-black\",\n      default: false,\n    },\n    {\n      id: \"nuxt\",\n      name: \"Nuxt\",\n      description: \"Vue full-stack framework (SSR, SSG, hybrid)\",\n      icon: `${ICON_BASE_URL}/nuxt.svg`,\n      color: \"from-green-400 to-green-700\",\n      default: false,\n    },\n    {\n      id: \"svelte\",\n      name: \"Svelte\",\n      description: \"Cybernetically enhanced web apps\",\n      icon: `${ICON_BASE_URL}/svelte.svg`,\n      color: \"from-orange-500 to-orange-700\",\n      default: false,\n    },\n    {\n      id: \"solid\",\n      name: \"Solid\",\n      description: \"Simple and performant reactivity for building UIs\",\n      icon: `${ICON_BASE_URL}/solid.svg`,\n      color: \"from-blue-600 to-blue-800\",\n      default: false,\n    },\n    {\n      id: \"astro\",\n      name: \"Astro\",\n      description: \"The web framework for content-driven websites\",\n      icon: `${ICON_BASE_URL}/astro.svg`,\n      color: \"from-purple-500 to-orange-500\",\n      default: false,\n    },\n    {\n      id: \"none\",\n      name: \"No Web Frontend\",\n      description: \"No web-based frontend\",\n      icon: \"\",\n      color: \"from-gray-400 to-gray-600\",\n      default: false,\n    },\n  ],\n  nativeFrontend: [\n    {\n      id: \"native-bare\",\n      name: \"Expo + Bare\",\n      description: \"Expo with StyleSheet (no styling library)\",\n      icon: `${ICON_BASE_URL}/expo.svg`,\n      color: \"from-blue-400 to-blue-600\",\n      className: \"invert-0 dark:invert\",\n      default: true,\n    },\n    {\n      id: \"native-uniwind\",\n      name: \"Expo + Uniwind\",\n      description: \"Fastest Tailwind bindings for React Native with HeroUI Native\",\n      icon: `${ICON_BASE_URL}/expo.svg`,\n      color: \"from-purple-400 to-purple-600\",\n      className: \"invert-0 dark:invert\",\n      default: false,\n    },\n    {\n      id: \"native-unistyles\",\n      name: \"Expo + Unistyles\",\n      description: \"Expo with Unistyles (type-safe styling)\",\n      icon: `${ICON_BASE_URL}/expo.svg`,\n      color: \"from-pink-400 to-pink-600\",\n      className: \"invert-0 dark:invert\",\n      default: false,\n    },\n    {\n      id: \"none\",\n      name: \"No Native Frontend\",\n      description: \"No native mobile frontend\",\n      icon: \"\",\n      color: \"from-gray-400 to-gray-600\",\n      default: false,\n    },\n  ],\n  runtime: [\n    {\n      id: \"bun\",\n      name: \"Bun\",\n      description: \"Fast JavaScript runtime & toolkit\",\n      icon: `${ICON_BASE_URL}/bun.svg`,\n      color: \"from-amber-400 to-amber-600\",\n      default: true,\n    },\n    {\n      id: \"node\",\n      name: \"Node.js\",\n      description: \"JavaScript runtime environment\",\n      icon: `${ICON_BASE_URL}/node.svg`,\n      color: \"from-green-400 to-green-600\",\n    },\n    {\n      id: \"workers\",\n      name: \"Cloudflare Workers\",\n      description: \"Serverless runtime for the edge\",\n      icon: `${ICON_BASE_URL}/workers.svg`,\n      color: \"from-orange-400 to-orange-600\",\n    },\n    {\n      id: \"none\",\n      name: \"No Runtime\",\n      description: \"No specific runtime\",\n      icon: \"\",\n      color: \"from-gray-400 to-gray-600\",\n    },\n  ],\n  backend: [\n    {\n      id: \"hono\",\n      name: \"Hono\",\n      description: \"Ultrafast web framework\",\n      icon: `${ICON_BASE_URL}/hono.svg`,\n      color: \"from-blue-500 to-blue-700\",\n      default: true,\n    },\n    {\n      id: \"elysia\",\n      name: \"Elysia\",\n      description: \"TypeScript web framework\",\n      icon: `${ICON_BASE_URL}/elysia.svg`,\n      color: \"from-purple-500 to-purple-700\",\n    },\n    {\n      id: \"express\",\n      name: \"Express\",\n      description: \"Popular Node.js framework\",\n      icon: `${ICON_BASE_URL}/express.svg`,\n      color: \"from-gray-500 to-gray-700\",\n    },\n    {\n      id: \"fastify\",\n      name: \"Fastify\",\n      description: \"Fast, low-overhead web framework for Node.js\",\n      icon: `${ICON_BASE_URL}/fastify.svg`,\n      color: \"from-gray-500 to-gray-700\",\n    },\n    {\n      id: \"convex\",\n      name: \"Convex\",\n      description: \"Reactive backend-as-a-service\",\n      icon: `${ICON_BASE_URL}/convex.svg`,\n      color: \"from-pink-500 to-pink-700\",\n    },\n    {\n      id: \"self-next\",\n      name: \"Fullstack Next.js\",\n      description: \"Use Next.js built-in API routes\",\n      icon: `${ICON_BASE_URL}/nextjs.svg`,\n      color: \"from-gray-700 to-black\",\n    },\n    {\n      id: \"self-tanstack-start\",\n      name: \"Fullstack TanStack Start\",\n      description: \"Use TanStack Start's built-in API routes\",\n      icon: `${ICON_BASE_URL}/tanstack.svg`,\n      color: \"from-purple-400 to-purple-600\",\n    },\n    {\n      id: \"self-nuxt\",\n      name: \"Fullstack Nuxt\",\n      description: \"Use Nuxt's built-in server routes\",\n      icon: `${ICON_BASE_URL}/nuxt.svg`,\n      color: \"from-green-400 to-green-700\",\n    },\n    {\n      id: \"self-svelte\",\n      name: \"Fullstack SvelteKit\",\n      description: \"Use SvelteKit's built-in server routes\",\n      icon: `${ICON_BASE_URL}/svelte.svg`,\n      color: \"from-orange-500 to-orange-700\",\n    },\n    {\n      id: \"self-astro\",\n      name: \"Fullstack Astro\",\n      description: \"Use Astro's built-in API routes\",\n      icon: `${ICON_BASE_URL}/astro.svg`,\n      color: \"from-purple-500 to-orange-500\",\n    },\n    {\n      id: \"none\",\n      name: \"No Backend\",\n      description: \"Skip backend integration (frontend only)\",\n      icon: \"\",\n      color: \"from-gray-400 to-gray-600\",\n    },\n  ],\n  database: [\n    {\n      id: \"sqlite\",\n      name: \"SQLite\",\n      description: \"File-based SQL database\",\n      icon: `${ICON_BASE_URL}/sqlite.svg`,\n      color: \"from-blue-400 to-cyan-500\",\n      default: true,\n    },\n    {\n      id: \"postgres\",\n      name: \"PostgreSQL\",\n      description: \"Advanced SQL database\",\n      icon: `${ICON_BASE_URL}/postgres.svg`,\n      color: \"from-indigo-400 to-indigo-600\",\n    },\n    {\n      id: \"mysql\",\n      name: \"MySQL\",\n      description: \"Popular relational database\",\n      icon: `${ICON_BASE_URL}/mysql.svg`,\n      color: \"from-blue-500 to-blue-700\",\n    },\n    {\n      id: \"mongodb\",\n      name: \"MongoDB\",\n      description: \"NoSQL document database\",\n      icon: `${ICON_BASE_URL}/mongodb.svg`,\n      color: \"from-green-400 to-green-600\",\n    },\n    {\n      id: \"none\",\n      name: \"No Database\",\n      description: \"Skip database integration\",\n      icon: \"\",\n      color: \"from-gray-400 to-gray-600\",\n    },\n  ],\n  orm: [\n    {\n      id: \"drizzle\",\n      name: \"Drizzle\",\n      description: \"TypeScript ORM\",\n      icon: `${ICON_BASE_URL}/drizzle.svg`,\n      color: \"from-cyan-400 to-cyan-600\",\n      default: true,\n    },\n    {\n      id: \"prisma\",\n      name: \"Prisma\",\n      description: \"Next-gen ORM\",\n      icon: `${ICON_BASE_URL}/prisma.svg`,\n      color: \"from-purple-400 to-purple-600\",\n    },\n    {\n      id: \"mongoose\",\n      name: \"Mongoose\",\n      description: \"Elegant object modeling tool\",\n      icon: `${ICON_BASE_URL}/mongoose.svg`,\n      color: \"from-blue-400 to-blue-600\",\n    },\n    {\n      id: \"none\",\n      name: \"No ORM\",\n      description: \"Skip ORM integration\",\n      icon: \"\",\n      color: \"from-gray-400 to-gray-600\",\n    },\n  ],\n  dbSetup: [\n    {\n      id: \"turso\",\n      name: \"Turso\",\n      description: \"Distributed SQLite with edge replicas (libSQL)\",\n      icon: `${ICON_BASE_URL}/turso.svg`,\n      color: \"from-pink-400 to-pink-600\",\n    },\n    {\n      id: \"d1\",\n      name: \"Cloudflare D1\",\n      description: \"Serverless SQLite-compatible database for Cloudflare Workers\",\n      icon: `${ICON_BASE_URL}/workers.svg`,\n      color: \"from-orange-400 to-orange-600\",\n    },\n    {\n      id: \"neon\",\n      name: \"Neon Postgres\",\n      description: \"Serverless Postgres with autoscaling and branching\",\n      icon: `${ICON_BASE_URL}/neon.svg`,\n      color: \"from-blue-400 to-blue-600\",\n    },\n    {\n      id: \"prisma-postgres\",\n      name: \"Prisma PostgreSQL\",\n      description: \"Managed Postgres via Prisma Data Platform\",\n      icon: `${ICON_BASE_URL}/prisma.svg`,\n      color: \"from-indigo-400 to-indigo-600\",\n    },\n    {\n      id: \"mongodb-atlas\",\n      name: \"MongoDB Atlas\",\n      description: \"Managed MongoDB clusters in the cloud\",\n      icon: `${ICON_BASE_URL}/mongodb.svg`,\n      color: \"from-green-400 to-green-600\",\n    },\n    {\n      id: \"supabase\",\n      name: \"Supabase\",\n      description: \"Local Postgres stack via Supabase (Docker required)\",\n      icon: `${ICON_BASE_URL}/supabase.svg`,\n      color: \"from-emerald-400 to-emerald-600\",\n    },\n    {\n      id: \"planetscale\",\n      name: \"PlanetScale\",\n      description: \"Postgres & Vitess (MySQL) on NVMe\",\n      icon: `${ICON_BASE_URL}/planetscale.svg`,\n      color: \"from-orange-400 to-orange-600\",\n    },\n    {\n      id: \"docker\",\n      name: \"Docker\",\n      description: \"Run Postgres/MySQL/MongoDB locally via Docker Compose\",\n      icon: `${ICON_BASE_URL}/docker.svg`,\n      color: \"from-blue-500 to-blue-700\",\n    },\n    {\n      id: \"none\",\n      name: \"Basic Setup\",\n      description: \"No cloud DB integration\",\n      icon: \"\",\n      color: \"from-gray-400 to-gray-600\",\n      default: true,\n    },\n  ],\n  webDeploy: [\n    {\n      id: \"cloudflare\",\n      name: \"Cloudflare\",\n      description: \"Deploy to Cloudflare Workers using Alchemy\",\n      icon: `${ICON_BASE_URL}/workers.svg`,\n      color: \"from-orange-400 to-orange-600\",\n    },\n    {\n      id: \"none\",\n      name: \"None\",\n      description: \"Skip deployment setup\",\n      icon: \"\",\n      color: \"from-gray-400 to-gray-600\",\n      default: true,\n    },\n  ],\n  serverDeploy: [\n    {\n      id: \"cloudflare\",\n      name: \"Cloudflare\",\n      description: \"Deploy to Cloudflare Workers using Alchemy\",\n      icon: `${ICON_BASE_URL}/workers.svg`,\n      color: \"from-orange-400 to-orange-600\",\n    },\n    {\n      id: \"none\",\n      name: \"None\",\n      description: \"Skip deployment setup\",\n      icon: \"\",\n      color: \"from-gray-400 to-gray-600\",\n      default: true,\n    },\n  ],\n  auth: [\n    {\n      id: \"better-auth\",\n      name: \"Better-Auth\",\n      description: \"The most comprehensive authentication framework for TypeScript\",\n      icon: `${ICON_BASE_URL}/better-auth.svg`,\n      color: \"from-green-400 to-green-600\",\n      default: true,\n    },\n    {\n      id: \"clerk\",\n      name: \"Clerk\",\n      description: \"More than authentication, Complete User Management\",\n      icon: `${ICON_BASE_URL}/clerk.svg`,\n      color: \"from-blue-400 to-blue-600\",\n    },\n    {\n      id: \"none\",\n      name: \"No Auth\",\n      description: \"Skip authentication\",\n      icon: \"\",\n      color: \"from-red-400 to-red-600\",\n    },\n  ],\n  payments: [\n    {\n      id: \"polar\",\n      name: \"Polar\",\n      description: \"Turn your software into a business. 6 lines of code.\",\n      icon: `${ICON_BASE_URL}/polar.svg`,\n      color: \"from-purple-400 to-purple-600\",\n      default: false,\n    },\n    {\n      id: \"none\",\n      name: \"No Payments\",\n      description: \"Skip payments integration\",\n      icon: \"\",\n      color: \"from-gray-400 to-gray-600\",\n      default: true,\n    },\n  ],\n  packageManager: [\n    {\n      id: \"npm\",\n      name: \"npm\",\n      description: \"Default package manager\",\n      icon: `${ICON_BASE_URL}/npm.svg`,\n      color: \"from-red-500 to-red-700\",\n      className: \"invert-0 dark:invert\",\n    },\n    {\n      id: \"pnpm\",\n      name: \"pnpm\",\n      description: \"Fast, disk space efficient\",\n      icon: `${ICON_BASE_URL}/pnpm.svg`,\n      color: \"from-orange-500 to-orange-700\",\n    },\n    {\n      id: \"bun\",\n      name: \"bun\",\n      description: \"All-in-one toolkit\",\n      icon: `${ICON_BASE_URL}/bun.svg`,\n      color: \"from-amber-500 to-amber-700\",\n      default: true,\n    },\n  ],\n  addons: [\n    {\n      id: \"pwa\",\n      name: \"PWA (Progressive Web App)\",\n      description: \"Make your app installable and work offline\",\n      icon: \"\",\n      color: \"from-blue-500 to-blue-700\",\n      default: false,\n    },\n    {\n      id: \"tauri\",\n      name: \"Tauri\",\n      description: \"Build native desktop apps\",\n      icon: `${ICON_BASE_URL}/tauri.svg`,\n      color: \"from-amber-500 to-amber-700\",\n      default: false,\n    },\n    {\n      id: \"electrobun\",\n      name: \"Electrobun\",\n      description: \"Wrap web frontends in a lightweight desktop shell\",\n      icon: \"\",\n      color: \"from-orange-500 to-orange-700\",\n      default: false,\n    },\n    {\n      id: \"starlight\",\n      name: \"Starlight\",\n      description: \"Build stellar docs with astro\",\n      icon: `${ICON_BASE_URL}/starlight.svg`,\n      color: \"from-teal-500 to-teal-700\",\n      default: false,\n    },\n    {\n      id: \"fumadocs\",\n      name: \"Fumadocs\",\n      description: \"Build excellent documentation site\",\n      icon: `${ICON_BASE_URL}/fumadocs.svg`,\n      color: \"from-indigo-500 to-indigo-700\",\n      default: false,\n    },\n    {\n      id: \"lefthook\",\n      name: \"Lefthook\",\n      description: \"Fast and powerful Git hooks manager\",\n      icon: \"\",\n      color: \"from-red-500 to-red-700\",\n      default: false,\n    },\n    {\n      id: \"husky\",\n      name: \"Husky\",\n      description: \"Modern native Git hooks made easy\",\n      icon: \"\",\n      color: \"from-purple-500 to-purple-700\",\n      default: false,\n    },\n    {\n      id: \"biome\",\n      name: \"Biome\",\n      description: \"Format, lint, and more\",\n      icon: `${ICON_BASE_URL}/biome.svg`,\n      color: \"from-green-500 to-green-700\",\n      default: false,\n    },\n    {\n      id: \"oxlint\",\n      name: \"Oxlint\",\n      description: \"Oxlint + Oxfmt (linting & formatting)\",\n      icon: `${ICON_BASE_URL}/oxc.svg`,\n      color: \"from-orange-500 to-orange-700\",\n      default: false,\n    },\n    {\n      id: \"turborepo\",\n      name: \"Turborepo\",\n      description: \"High-performance build system\",\n      icon: `${ICON_BASE_URL}/turborepo.svg`,\n      color: \"from-gray-400 to-gray-700\",\n      default: true,\n    },\n    {\n      id: \"nx\",\n      name: \"Nx\",\n      description: \"Smart monorepo build system and task runner\",\n      icon: `${ICON_BASE_URL}/nx.svg`,\n      color: \"from-cyan-500 to-cyan-700\",\n      default: false,\n    },\n    {\n      id: \"ultracite\",\n      name: \"Ultracite\",\n      description: \"Biome preset with AI integration\",\n      icon: `${ICON_BASE_URL}/ultracite.svg`,\n      color: \"from-blue-500 to-blue-700\",\n      className: \"invert-0 dark:invert\",\n      default: false,\n    },\n    {\n      id: \"opentui\",\n      name: \"OpenTUI\",\n      description: \"Build terminal user interfaces\",\n      icon: \"\",\n      color: \"from-cyan-500 to-cyan-700\",\n      default: false,\n    },\n    {\n      id: \"wxt\",\n      name: \"WXT\",\n      description: \"Build browser extensions\",\n      icon: \"\",\n      color: \"from-emerald-500 to-emerald-700\",\n      default: false,\n    },\n    {\n      id: \"skills\",\n      name: \"Skills\",\n      description: \"Install AI agent skills for coding assistants\",\n      icon: \"\",\n      color: \"from-pink-500 to-pink-700\",\n      default: false,\n    },\n    {\n      id: \"mcp\",\n      name: \"MCP\",\n      description: \"Install MCP servers for your agents/editors\",\n      icon: \"\",\n      color: \"from-emerald-500 to-emerald-700\",\n      default: false,\n    },\n    {\n      id: \"evlog\",\n      name: \"evlog\",\n      description: \"Request logging with Better Auth context and AI SDK telemetry\",\n      icon: \"\",\n      color: \"from-sky-500 to-slate-700\",\n      default: false,\n    },\n  ],\n  examples: [\n    {\n      id: \"todo\",\n      name: \"Todo Example\",\n      description: \"Simple todo application\",\n      icon: \"\",\n      color: \"from-indigo-500 to-indigo-700\",\n      default: false,\n    },\n    {\n      id: \"ai\",\n      name: \"AI Example\",\n      description: \"AI integration example using AI SDK\",\n      icon: \"\",\n      color: \"from-purple-500 to-purple-700\",\n      default: false,\n    },\n  ],\n  git: [\n    {\n      id: \"true\",\n      name: \"Git\",\n      description: \"Initialize Git repository\",\n      icon: `${ICON_BASE_URL}/git.svg`,\n      color: \"from-gray-500 to-gray-700\",\n      default: true,\n    },\n    {\n      id: \"false\",\n      name: \"No Git\",\n      description: \"Skip Git initialization\",\n      icon: \"\",\n      color: \"from-red-400 to-red-600\",\n    },\n  ],\n  install: [\n    {\n      id: \"true\",\n      name: \"Install Dependencies\",\n      description: \"Install packages automatically\",\n      icon: \"\",\n      color: \"from-green-400 to-green-600\",\n      default: true,\n    },\n    {\n      id: \"false\",\n      name: \"Skip Install\",\n      description: \"Skip dependency installation\",\n      icon: \"\",\n      color: \"from-yellow-400 to-yellow-600\",\n    },\n  ],\n};\n\nexport const PRESET_TEMPLATES = [\n  {\n    id: \"mern\",\n    name: \"MERN Stack\",\n    description: \"MongoDB + Express + React + Node.js - Classic MERN stack\",\n    stack: {\n      projectName: \"my-better-t-app\",\n      webFrontend: [\"react-router\"],\n      nativeFrontend: [\"none\"],\n      runtime: \"node\",\n      backend: \"express\",\n      database: \"mongodb\",\n      orm: \"mongoose\",\n      dbSetup: \"mongodb-atlas\",\n      auth: \"better-auth\",\n      payments: \"none\",\n      packageManager: \"bun\",\n      addons: [\"turborepo\"],\n      examples: [\"todo\"],\n      git: \"true\",\n      install: \"true\",\n      api: \"orpc\",\n      webDeploy: \"none\",\n      serverDeploy: \"none\",\n      yolo: \"false\",\n    },\n  },\n  {\n    id: \"pern\",\n    name: \"PERN Stack\",\n    description: \"PostgreSQL + Express + React + Node.js - Popular PERN stack\",\n    stack: {\n      projectName: \"my-better-t-app\",\n      webFrontend: [\"tanstack-router\"],\n      nativeFrontend: [\"none\"],\n      runtime: \"node\",\n      backend: \"express\",\n      database: \"postgres\",\n      orm: \"drizzle\",\n      dbSetup: \"none\",\n      auth: \"better-auth\",\n      payments: \"none\",\n      packageManager: \"bun\",\n      addons: [\"turborepo\"],\n      examples: [\"todo\"],\n      git: \"true\",\n      install: \"true\",\n      api: \"trpc\",\n      webDeploy: \"none\",\n      serverDeploy: \"none\",\n      yolo: \"false\",\n    },\n  },\n  {\n    id: \"t3\",\n    name: \"T3 Stack\",\n    description: \"Next.js + tRPC + Prisma + PostgreSQL + Better Auth\",\n    stack: {\n      projectName: \"my-better-t-app\",\n      webFrontend: [\"next\"],\n      nativeFrontend: [\"none\"],\n      runtime: \"none\",\n      backend: \"self-next\",\n      database: \"postgres\",\n      orm: \"prisma\",\n      dbSetup: \"none\",\n      auth: \"better-auth\",\n      payments: \"none\",\n      packageManager: \"bun\",\n      addons: [\"biome\", \"turborepo\"],\n      examples: [\"none\"],\n      git: \"true\",\n      install: \"true\",\n      api: \"trpc\",\n      webDeploy: \"none\",\n      serverDeploy: \"none\",\n      yolo: \"false\",\n    },\n  },\n  {\n    id: \"uniwind\",\n    name: \"Uniwind Native\",\n    description: \"Expo + Uniwind native app with no backend services\",\n    stack: {\n      projectName: \"my-better-t-app\",\n      webFrontend: [\"none\"],\n      nativeFrontend: [\"native-uniwind\"],\n      runtime: \"none\",\n      backend: \"none\",\n      database: \"none\",\n      orm: \"none\",\n      dbSetup: \"none\",\n      auth: \"none\",\n      payments: \"none\",\n      packageManager: \"bun\",\n      addons: [\"none\"],\n      examples: [\"none\"],\n      git: \"true\",\n      install: \"true\",\n      api: \"none\",\n      webDeploy: \"none\",\n      serverDeploy: \"none\",\n      yolo: \"false\",\n    },\n  },\n];\n\nexport type StackState = {\n  projectName: string | null;\n  webFrontend: string[];\n  nativeFrontend: string[];\n  runtime: string;\n  backend: string;\n  database: string;\n  orm: string;\n  dbSetup: string;\n  auth: string;\n  payments: string;\n  packageManager: string;\n  addons: string[];\n  examples: string[];\n  git: string;\n  install: string;\n  api: string;\n  webDeploy: string;\n  serverDeploy: string;\n  yolo: string;\n};\n\nexport const DEFAULT_STACK: StackState = {\n  projectName: \"my-better-t-app\",\n  webFrontend: [\"tanstack-router\"],\n  nativeFrontend: [\"none\"],\n  runtime: \"bun\",\n  backend: \"hono\",\n  database: \"sqlite\",\n  orm: \"drizzle\",\n  dbSetup: \"none\",\n  auth: \"better-auth\",\n  payments: \"none\",\n  packageManager: \"bun\",\n  addons: [\"turborepo\"],\n  examples: [\"none\"],\n  git: \"true\",\n  install: \"true\",\n  api: \"trpc\",\n  webDeploy: \"none\",\n  serverDeploy: \"none\",\n  yolo: \"false\",\n};\n\nexport const isStackDefault = <K extends keyof StackState>(\n  stack: StackState,\n  key: K,\n  value: StackState[K],\n): boolean => {\n  const defaultValue = DEFAULT_STACK[key];\n\n  if (stack.backend === \"convex\") {\n    if (key === \"runtime\" && value === \"none\") return true;\n    if (key === \"database\" && value === \"none\") return true;\n    if (key === \"orm\" && value === \"none\") return true;\n    if (key === \"api\" && value === \"none\") return true;\n    if (key === \"auth\" && value === \"none\") return true;\n    if (key === \"dbSetup\" && value === \"none\") return true;\n  }\n\n  if (key === \"webFrontend\" || key === \"nativeFrontend\" || key === \"addons\" || key === \"examples\") {\n    if (Array.isArray(defaultValue) && Array.isArray(value)) {\n      const sortedDefault = [...defaultValue].sort();\n      const sortedValue = [...value].sort();\n      return (\n        sortedDefault.length === sortedValue.length &&\n        sortedDefault.every((item, index) => item === sortedValue[index])\n      );\n    }\n  }\n\n  if (Array.isArray(defaultValue) && Array.isArray(value)) {\n    const sortedDefault = [...defaultValue].sort();\n    const sortedValue = [...value].sort();\n    return (\n      sortedDefault.length === sortedValue.length &&\n      sortedDefault.every((item, index) => item === sortedValue[index])\n    );\n  }\n\n  return defaultValue === value;\n};\n"
  },
  {
    "path": "apps/web/src/lib/get-llm-text.ts",
    "content": "import type { InferPageType } from \"fumadocs-core/source\";\n\nimport type { source } from \"@/lib/source\";\n\nexport async function getLLMText(page: InferPageType<typeof source>) {\n  const processed = await page.data.getText(\"processed\");\n\n  return `# ${page.data.title} (${page.url})\n\n${processed}`;\n}\n"
  },
  {
    "path": "apps/web/src/lib/sanitize-stack-addons.ts",
    "content": "import { DEFAULT_STACK, type StackState, TECH_OPTIONS } from \"./constant\";\n\nconst validWebFrontendIds = new Set(TECH_OPTIONS.webFrontend.map((option) => option.id));\nconst validNativeFrontendIds = new Set(TECH_OPTIONS.nativeFrontend.map((option) => option.id));\nconst validAddonIds = new Set([\"none\", ...TECH_OPTIONS.addons.map((option) => option.id)]);\nconst validExampleIds = new Set([\"none\", ...TECH_OPTIONS.examples.map((option) => option.id)]);\n\nfunction sanitizeSingleSelection(\n  values: readonly string[] | null | undefined,\n  validIds: ReadonlySet<string>,\n  defaultValue: readonly string[],\n): string[] {\n  if (values == null) {\n    return [...defaultValue];\n  }\n\n  const selectedValue = values.filter((value) => validIds.has(value) && value !== \"none\").at(-1);\n  return selectedValue ? [selectedValue] : [\"none\"];\n}\n\nfunction sanitizeMultiSelection(\n  values: readonly string[] | null | undefined,\n  validIds: ReadonlySet<string>,\n  defaultValue: readonly string[],\n): string[] {\n  if (values == null) {\n    return [...defaultValue];\n  }\n\n  const sanitized = values.filter((value) => validIds.has(value));\n  const normalized =\n    sanitized.length > 1 ? sanitized.filter((value) => value !== \"none\") : sanitized;\n  const unique = [...new Set(normalized)];\n\n  return unique.length > 0 ? unique : [\"none\"];\n}\n\nfunction resolveMonorepoAddonConflicts(addons: readonly string[]): string[] {\n  const resolved: string[] = [];\n\n  for (const addon of addons) {\n    if (addon === \"nx\" || addon === \"turborepo\") {\n      const existingMonorepoIndex = resolved.findIndex(\n        (value) => value === \"nx\" || value === \"turborepo\",\n      );\n\n      if (existingMonorepoIndex !== -1) {\n        resolved.splice(existingMonorepoIndex, 1);\n      }\n    }\n\n    if (!resolved.includes(addon)) {\n      resolved.push(addon);\n    }\n  }\n\n  return resolved;\n}\n\nexport function sanitizeAddons(addons: readonly string[] | null | undefined): string[] {\n  const sanitized = sanitizeMultiSelection(addons, validAddonIds, DEFAULT_STACK.addons);\n  return resolveMonorepoAddonConflicts(sanitized);\n}\n\nexport function sanitizeExamples(examples: readonly string[] | null | undefined): string[] {\n  return sanitizeMultiSelection(examples, validExampleIds, DEFAULT_STACK.examples);\n}\n\nexport function sanitizeWebFrontends(webFrontend: readonly string[] | null | undefined): string[] {\n  return sanitizeSingleSelection(webFrontend, validWebFrontendIds, DEFAULT_STACK.webFrontend);\n}\n\nexport function sanitizeNativeFrontends(\n  nativeFrontend: readonly string[] | null | undefined,\n): string[] {\n  return sanitizeSingleSelection(\n    nativeFrontend,\n    validNativeFrontendIds,\n    DEFAULT_STACK.nativeFrontend,\n  );\n}\n\nexport function sanitizeStackState(stack: StackState): StackState {\n  return {\n    ...stack,\n    webFrontend: sanitizeWebFrontends(stack.webFrontend),\n    nativeFrontend: sanitizeNativeFrontends(stack.nativeFrontend),\n    addons: sanitizeAddons(stack.addons),\n    examples: sanitizeExamples(stack.examples),\n  };\n}\n\nexport function sanitizeStackAddons(stack: StackState): StackState {\n  return sanitizeStackState(stack);\n}\n"
  },
  {
    "path": "apps/web/src/lib/search-config.ts",
    "content": "export interface CustomSearchItem {\n  title: string;\n  url: string;\n  content: string;\n  tags: string[];\n}\n\nexport const customSearchItems: CustomSearchItem[] = [\n  {\n    title: \"Analytics\",\n    url: \"/analytics\",\n    content: \"Analytics\",\n    tags: [\"analytics\", \"insights\", \"statistics\", \"data\", \"metrics\"],\n  },\n  {\n    title: \"Showcase\",\n    url: \"/showcase\",\n    content: \"Showcase\",\n    tags: [\"showcase\", \"projects\", \"examples\", \"demos\", \"portfolio\"],\n  },\n  {\n    title: \"Builder\",\n    url: \"/new\",\n    content: \"Builder\",\n    tags: [\"builder\", \"create\", \"new\", \"project\", \"setup\"],\n  },\n  {\n    title: \"GitHub Repository\",\n    url: \"https://github.com/AmanVarshney01/create-better-t-stack\",\n    content: \"GitHub\",\n    tags: [\"github\", \"source\", \"code\", \"repository\", \"contribute\", \"star\"],\n  },\n  {\n    title: \"NPM Package\",\n    url: \"https://www.npmjs.com/package/create-better-t-stack\",\n    content: \"NPM\",\n    tags: [\"npm\", \"package\", \"install\", \"cli\", \"tool\"],\n  },\n  {\n    title: \"X (Twitter)\",\n    url: \"https://x.com/amanvarshney01\",\n    content: \"X\",\n    tags: [\"twitter\", \"x\", \"social\", \"updates\", \"announcements\", \"follow\"],\n  },\n  {\n    title: \"Discord Community\",\n    url: \"https://discord.gg/ZYsbjpDaM5\",\n    content: \"Discord\",\n    tags: [\"discord\", \"community\", \"chat\", \"help\", \"support\", \"discussions\"],\n  },\n];\n\nexport function filterCustomItems(\n  items: CustomSearchItem[],\n  searchQuery: string,\n): CustomSearchItem[] {\n  if (!searchQuery) return items;\n\n  const searchLower = searchQuery.toLowerCase();\n  return items.filter(\n    (item) =>\n      item.title.toLowerCase().includes(searchLower) ||\n      item.content.toLowerCase().includes(searchLower) ||\n      item.tags.some((tag) => tag.toLowerCase().includes(searchLower)),\n  );\n}\n"
  },
  {
    "path": "apps/web/src/lib/source.ts",
    "content": "import { loader, type InferPageType } from \"fumadocs-core/source\";\nimport { docs } from \"fumadocs-mdx:collections/server\";\n\nexport const source = loader({\n  baseUrl: \"/docs\",\n  source: docs.toFumadocsSource(),\n});\n\nexport function getPageImage(page: InferPageType<typeof source>) {\n  const segments = [...page.slugs, \"image.png\"];\n\n  return {\n    segments,\n    url: `/og/docs/${segments.join(\"/\")}`,\n  };\n}\n"
  },
  {
    "path": "apps/web/src/lib/sponsor-utils.ts",
    "content": "import type { Sponsor } from \"@/lib/types\";\n\nexport const SPECIAL_SPONSOR_THRESHOLD = 100;\n\nexport const calculateLifetimeContribution = (sponsor: Sponsor): number => {\n  // totalProcessedAmount is always provided by the API\n  return sponsor.totalProcessedAmount || 0;\n};\n\nexport const shouldShowLifetimeTotal = (sponsor: Sponsor): boolean => {\n  // Only show lifetime total if totalProcessedAmount exists and tierName is present\n  return sponsor.totalProcessedAmount !== undefined && !!sponsor.tierName;\n};\n\nexport const isLifetimeSpecialSponsor = (sponsor: Sponsor): boolean => {\n  const lifetimeAmount = calculateLifetimeContribution(sponsor);\n  return lifetimeAmount >= SPECIAL_SPONSOR_THRESHOLD;\n};\n\nexport const getSponsorUrl = (sponsor: Sponsor): string => {\n  const url = sponsor.websiteUrl || sponsor.githubUrl;\n\n  // Ensure URL has a protocol\n  if (url && !url.startsWith(\"http://\") && !url.startsWith(\"https://\")) {\n    return `https://${url}`;\n  }\n\n  return url;\n};\n\nexport const formatSponsorUrl = (url: string): string => {\n  return url?.replace(/^https?:\\/\\//, \"\")?.replace(/\\/$/, \"\");\n};\n"
  },
  {
    "path": "apps/web/src/lib/sponsors.ts",
    "content": "import type { SponsorsData } from \"./types\";\n\nconst SPONSORS_URL = \"https://sponsors.better-t-stack.dev/sponsors.json\";\n\nexport async function fetchSponsors() {\n  try {\n    const response = await fetch(SPONSORS_URL, {\n      next: { revalidate: 3600 },\n    });\n\n    if (!response.ok) {\n      throw new Error(`Failed to fetch sponsors: ${response.status}`);\n    }\n\n    const data = await response.json();\n    return data as SponsorsData;\n  } catch (error) {\n    console.error(\"Error fetching sponsors:\", error);\n    return {\n      generated_at: new Date().toISOString(),\n      summary: {\n        total_sponsors: 0,\n        total_lifetime_amount: 0,\n        total_current_monthly: 0,\n        special_sponsors: 0,\n        current_sponsors: 0,\n        past_sponsors: 0,\n        backers: 0,\n        top_sponsor: { name: \"\", amount: 0 },\n      },\n      specialSponsors: [],\n      sponsors: [],\n      pastSponsors: [],\n      backers: [],\n    };\n  }\n}\n"
  },
  {
    "path": "apps/web/src/lib/stack-url-keys.ts",
    "content": "import type { UrlKeys } from \"nuqs\";\n\nimport type { StackState } from \"@/lib/constant\";\n\nexport const stackUrlKeys: UrlKeys<\n  Record<keyof StackState, unknown> & { viewMode: unknown; selectedFile: unknown }\n> = {\n  projectName: \"name\",\n  webFrontend: \"fe-w\",\n  nativeFrontend: \"fe-n\",\n  runtime: \"rt\",\n  backend: \"be\",\n  api: \"api\",\n  database: \"db\",\n  orm: \"orm\",\n  dbSetup: \"dbs\",\n  auth: \"au\",\n  payments: \"pay\",\n  packageManager: \"pm\",\n  addons: \"add\",\n  examples: \"ex\",\n  git: \"git\",\n  install: \"i\",\n  webDeploy: \"wd\",\n  serverDeploy: \"sd\",\n  yolo: \"yolo\",\n  viewMode: \"view\",\n  selectedFile: \"file\",\n};\n"
  },
  {
    "path": "apps/web/src/lib/stack-url-state.client.ts",
    "content": "\"use client\";\nimport { parseAsArrayOf, parseAsString, parseAsStringEnum, useQueryStates } from \"nuqs\";\n\nimport { DEFAULT_STACK, type StackState, TECH_OPTIONS } from \"@/lib/constant\";\n\nimport { sanitizeStackState } from \"./sanitize-stack-addons\";\nimport { stackUrlKeys } from \"./stack-url-keys\";\n\nconst getValidIds = (category: keyof typeof TECH_OPTIONS): string[] => {\n  return TECH_OPTIONS[category]?.map((opt) => opt.id) ?? [];\n};\n\nexport const stackParsers = {\n  projectName: parseAsString.withDefault(DEFAULT_STACK.projectName ?? \"my-better-t-app\"),\n  webFrontend: parseAsArrayOf(parseAsString).withDefault(DEFAULT_STACK.webFrontend),\n  nativeFrontend: parseAsArrayOf(parseAsString).withDefault(DEFAULT_STACK.nativeFrontend),\n  runtime: parseAsStringEnum<StackState[\"runtime\"]>(getValidIds(\"runtime\")).withDefault(\n    DEFAULT_STACK.runtime,\n  ),\n  backend: parseAsStringEnum<StackState[\"backend\"]>(getValidIds(\"backend\")).withDefault(\n    DEFAULT_STACK.backend,\n  ),\n  api: parseAsStringEnum<StackState[\"api\"]>(getValidIds(\"api\")).withDefault(DEFAULT_STACK.api),\n  database: parseAsStringEnum<StackState[\"database\"]>(getValidIds(\"database\")).withDefault(\n    DEFAULT_STACK.database,\n  ),\n  orm: parseAsStringEnum<StackState[\"orm\"]>(getValidIds(\"orm\")).withDefault(DEFAULT_STACK.orm),\n  dbSetup: parseAsStringEnum<StackState[\"dbSetup\"]>(getValidIds(\"dbSetup\")).withDefault(\n    DEFAULT_STACK.dbSetup,\n  ),\n  auth: parseAsStringEnum<StackState[\"auth\"]>(getValidIds(\"auth\")).withDefault(DEFAULT_STACK.auth),\n  payments: parseAsStringEnum<StackState[\"payments\"]>(getValidIds(\"payments\")).withDefault(\n    DEFAULT_STACK.payments,\n  ),\n  packageManager: parseAsStringEnum<StackState[\"packageManager\"]>(\n    getValidIds(\"packageManager\"),\n  ).withDefault(DEFAULT_STACK.packageManager),\n  addons: parseAsArrayOf(parseAsString).withDefault(DEFAULT_STACK.addons),\n  examples: parseAsArrayOf(parseAsString).withDefault(DEFAULT_STACK.examples),\n  git: parseAsStringEnum<StackState[\"git\"]>([\"true\", \"false\"]).withDefault(DEFAULT_STACK.git),\n  install: parseAsStringEnum<StackState[\"install\"]>([\"true\", \"false\"]).withDefault(\n    DEFAULT_STACK.install,\n  ),\n  webDeploy: parseAsStringEnum<StackState[\"webDeploy\"]>(getValidIds(\"webDeploy\")).withDefault(\n    DEFAULT_STACK.webDeploy,\n  ),\n  serverDeploy: parseAsStringEnum<StackState[\"serverDeploy\"]>(\n    getValidIds(\"serverDeploy\"),\n  ).withDefault(DEFAULT_STACK.serverDeploy),\n  yolo: parseAsStringEnum<StackState[\"yolo\"]>([\"true\", \"false\"]).withDefault(DEFAULT_STACK.yolo),\n  viewMode: parseAsStringEnum<\"command\" | \"preview\">([\"command\", \"preview\"]).withDefault(\"command\"),\n  selectedFile: parseAsString.withDefault(\"\"),\n};\n\nexport const stackQueryStatesOptions = {\n  history: \"replace\" as const,\n  // The stack builder state is fully client-driven on /new, so URL updates\n  // should stay shallow instead of forcing a server navigation.\n  shallow: true,\n  urlKeys: stackUrlKeys,\n  clearOnDefault: true,\n};\n\nexport function useStackState() {\n  const [queryState, setQueryState] = useQueryStates(stackParsers, stackQueryStatesOptions);\n\n  const stack = sanitizeStackState({\n    projectName: queryState.projectName,\n    webFrontend: queryState.webFrontend,\n    nativeFrontend: queryState.nativeFrontend,\n    runtime: queryState.runtime,\n    backend: queryState.backend,\n    api: queryState.api,\n    database: queryState.database,\n    orm: queryState.orm,\n    dbSetup: queryState.dbSetup,\n    auth: queryState.auth,\n    payments: queryState.payments,\n    packageManager: queryState.packageManager,\n    addons: queryState.addons,\n    examples: queryState.examples,\n    git: queryState.git,\n    install: queryState.install,\n    webDeploy: queryState.webDeploy,\n    serverDeploy: queryState.serverDeploy,\n    yolo: queryState.yolo,\n  });\n\n  const viewMode = queryState.viewMode;\n  const selectedFile = queryState.selectedFile;\n\n  const updateStack = async (\n    updates: Partial<StackState> | ((prev: StackState) => Partial<StackState>),\n  ) => {\n    const newStack = typeof updates === \"function\" ? updates(stack) : updates;\n    const finalStack = sanitizeStackState({ ...stack, ...newStack });\n    await setQueryState({ ...finalStack, viewMode, selectedFile });\n  };\n\n  const setViewMode = async (mode: \"command\" | \"preview\") => {\n    await setQueryState({ viewMode: mode, selectedFile });\n  };\n\n  const setSelectedFile = async (filePath: string | null) => {\n    await setQueryState({ selectedFile: filePath || \"\" });\n  };\n\n  return [stack, updateStack, viewMode, setViewMode, selectedFile, setSelectedFile] as const;\n}\n"
  },
  {
    "path": "apps/web/src/lib/stack-url-state.ts",
    "content": "import {\n  createLoader,\n  createSerializer,\n  parseAsArrayOf as parseAsArrayOfServer,\n  parseAsStringEnum as parseAsStringEnumServer,\n  parseAsString as parseAsStringServer,\n  type UrlKeys,\n} from \"nuqs/server\";\n\nimport { DEFAULT_STACK, type StackState, TECH_OPTIONS } from \"@/lib/constant\";\nimport { sanitizeStackState } from \"@/lib/sanitize-stack-addons\";\nimport { stackUrlKeys } from \"@/lib/stack-url-keys\";\n\nconst getValidIds = (category: keyof typeof TECH_OPTIONS): string[] => {\n  return TECH_OPTIONS[category]?.map((opt) => opt.id) ?? [];\n};\n\nconst serverStackParsers = {\n  projectName: parseAsStringServer.withDefault(DEFAULT_STACK.projectName || \"my-better-t-app\"),\n  webFrontend: parseAsArrayOfServer(parseAsStringServer).withDefault(DEFAULT_STACK.webFrontend),\n  nativeFrontend: parseAsArrayOfServer(parseAsStringServer).withDefault(\n    DEFAULT_STACK.nativeFrontend,\n  ),\n  runtime: parseAsStringEnumServer<StackState[\"runtime\"]>(getValidIds(\"runtime\")).withDefault(\n    DEFAULT_STACK.runtime,\n  ),\n  backend: parseAsStringEnumServer<StackState[\"backend\"]>(getValidIds(\"backend\")).withDefault(\n    DEFAULT_STACK.backend,\n  ),\n  api: parseAsStringEnumServer<StackState[\"api\"]>(getValidIds(\"api\")).withDefault(\n    DEFAULT_STACK.api,\n  ),\n  database: parseAsStringEnumServer<StackState[\"database\"]>(getValidIds(\"database\")).withDefault(\n    DEFAULT_STACK.database,\n  ),\n  orm: parseAsStringEnumServer<StackState[\"orm\"]>(getValidIds(\"orm\")).withDefault(\n    DEFAULT_STACK.orm,\n  ),\n  dbSetup: parseAsStringEnumServer<StackState[\"dbSetup\"]>(getValidIds(\"dbSetup\")).withDefault(\n    DEFAULT_STACK.dbSetup,\n  ),\n  auth: parseAsStringEnumServer<StackState[\"auth\"]>(getValidIds(\"auth\")).withDefault(\n    DEFAULT_STACK.auth,\n  ),\n  payments: parseAsStringEnumServer<StackState[\"payments\"]>(getValidIds(\"payments\")).withDefault(\n    DEFAULT_STACK.payments,\n  ),\n  packageManager: parseAsStringEnumServer<StackState[\"packageManager\"]>(\n    getValidIds(\"packageManager\"),\n  ).withDefault(DEFAULT_STACK.packageManager),\n  addons: parseAsArrayOfServer(parseAsStringServer).withDefault(DEFAULT_STACK.addons),\n  examples: parseAsArrayOfServer(parseAsStringServer).withDefault(DEFAULT_STACK.examples),\n  git: parseAsStringEnumServer<StackState[\"git\"]>([\"true\", \"false\"]).withDefault(DEFAULT_STACK.git),\n  install: parseAsStringEnumServer<StackState[\"install\"]>([\"true\", \"false\"]).withDefault(\n    DEFAULT_STACK.install,\n  ),\n  webDeploy: parseAsStringEnumServer<StackState[\"webDeploy\"]>(getValidIds(\"webDeploy\")).withDefault(\n    DEFAULT_STACK.webDeploy,\n  ),\n  serverDeploy: parseAsStringEnumServer<StackState[\"serverDeploy\"]>(\n    getValidIds(\"serverDeploy\"),\n  ).withDefault(DEFAULT_STACK.serverDeploy),\n  yolo: parseAsStringEnumServer<StackState[\"yolo\"]>([\"true\", \"false\"]).withDefault(\n    DEFAULT_STACK.yolo,\n  ),\n};\n\nconst rawLoadStackParams = createLoader(serverStackParsers, {\n  urlKeys: stackUrlKeys as UrlKeys<typeof serverStackParsers>,\n});\n\nexport const serializeStackParams = createSerializer(serverStackParsers, {\n  urlKeys: stackUrlKeys as UrlKeys<typeof serverStackParsers>,\n});\n\nexport async function loadStackParams(\n  searchParams: Parameters<typeof rawLoadStackParams>[0],\n): Promise<StackState> {\n  const stackState = await rawLoadStackParams(searchParams);\n  return sanitizeStackState(stackState);\n}\n\nexport type LoadedStackState = Awaited<ReturnType<typeof loadStackParams>>;\n"
  },
  {
    "path": "apps/web/src/lib/stack-utils.ts",
    "content": "import { DEFAULT_STACK, isStackDefault, type StackState, TECH_OPTIONS } from \"@/lib/constant\";\nimport { stackUrlKeys } from \"@/lib/stack-url-keys\";\n\nconst CATEGORY_ORDER: Array<keyof typeof TECH_OPTIONS> = [\n  \"webFrontend\",\n  \"nativeFrontend\",\n  \"backend\",\n  \"runtime\",\n  \"api\",\n  \"database\",\n  \"orm\",\n  \"dbSetup\",\n  \"webDeploy\",\n  \"serverDeploy\",\n  \"auth\",\n  \"payments\",\n  \"packageManager\",\n  \"addons\",\n  \"examples\",\n  \"git\",\n  \"install\",\n];\n\nconst desktopAddonNames = {\n  tauri: \"Tauri\",\n  electrobun: \"Electrobun\",\n} as const;\n\nconst staticDesktopFrontendNames = {\n  \"tanstack-start\": \"TanStack Start\",\n  next: \"Next.js\",\n  nuxt: \"Nuxt\",\n  svelte: \"SvelteKit\",\n  astro: \"Astro\",\n} as const;\n\nexport function generateStackSummary(stack: StackState) {\n  const selectedTechs = CATEGORY_ORDER.flatMap((category) => {\n    const options = TECH_OPTIONS[category];\n    const selectedValue = stack[category as keyof StackState];\n\n    if (!options) return [];\n\n    const getTechNames = (value: string | string[]) => {\n      const values = Array.isArray(value) ? value : [value];\n      return values\n        .filter(\n          (id) =>\n            id !== \"none\" &&\n            id !== \"false\" &&\n            !([\"git\", \"install\", \"auth\"].includes(category) && id === \"true\"),\n        )\n        .map((id) => options.find((opt) => opt.id === id)?.name)\n        .filter(Boolean) as string[];\n    };\n\n    return selectedValue ? getTechNames(selectedValue) : [];\n  });\n\n  return selectedTechs.length > 0 ? selectedTechs.join(\" • \") : \"Custom stack\";\n}\n\nexport function getDesktopBuildNote(stack: Pick<StackState, \"addons\" | \"webFrontend\">) {\n  const selectedDesktopAddons = stack.addons.filter(\n    (addon): addon is keyof typeof desktopAddonNames => addon in desktopAddonNames,\n  );\n\n  if (selectedDesktopAddons.length === 0) {\n    return null;\n  }\n\n  const staticFrontend = stack.webFrontend.find(\n    (frontend): frontend is keyof typeof staticDesktopFrontendNames =>\n      frontend in staticDesktopFrontendNames,\n  );\n\n  if (!staticFrontend) {\n    return null;\n  }\n\n  const addonLabel =\n    selectedDesktopAddons.length === 2\n      ? \"Tauri and Electrobun desktop builds\"\n      : `${desktopAddonNames[selectedDesktopAddons[0]]} desktop builds`;\n\n  return `${addonLabel} package static web assets. ${staticDesktopFrontendNames[staticFrontend]} needs a static/export build configuration before desktop packaging will work.`;\n}\n\nexport function generateStackCommand(stack: StackState) {\n  const packageManagerCommands = {\n    npm: \"npx create-better-t-stack@latest\",\n    pnpm: \"pnpm create better-t-stack@latest\",\n    default: \"bun create better-t-stack@latest\",\n  };\n\n  const base =\n    packageManagerCommands[stack.packageManager as keyof typeof packageManagerCommands] ||\n    packageManagerCommands.default;\n  const projectName = stack.projectName || \"my-better-t-app\";\n\n  const isStackDefaultExceptProjectName = Object.entries(DEFAULT_STACK).every(\n    ([key]) =>\n      key === \"projectName\" ||\n      isStackDefault(stack, key as keyof StackState, stack[key as keyof StackState]),\n  );\n\n  if (isStackDefaultExceptProjectName) {\n    return `${base} ${projectName} --yes`;\n  }\n\n  // Map web interface backend IDs to CLI backend flags\n  const mapBackendToCli = (backend: string) => {\n    if (\n      backend === \"self-next\" ||\n      backend === \"self-tanstack-start\" ||\n      backend === \"self-nuxt\" ||\n      backend === \"self-svelte\" ||\n      backend === \"self-astro\"\n    ) {\n      return \"self\";\n    }\n    return backend;\n  };\n\n  const flags = [\n    `--frontend ${\n      [...stack.webFrontend, ...stack.nativeFrontend]\n        .filter((v, _, arr) => v !== \"none\" || arr.length === 1)\n        .join(\" \") || \"none\"\n    }`,\n    `--backend ${mapBackendToCli(stack.backend)}`,\n    `--runtime ${stack.runtime}`,\n    `--api ${stack.api}`,\n    `--auth ${stack.auth}`,\n    `--payments ${stack.payments}`,\n    `--database ${stack.database}`,\n    `--orm ${stack.orm}`,\n    `--db-setup ${stack.dbSetup}`,\n    `--package-manager ${stack.packageManager}`,\n    stack.git === \"false\" ? \"--no-git\" : \"--git\",\n    `--web-deploy ${stack.webDeploy}`,\n    `--server-deploy ${stack.serverDeploy}`,\n    stack.install === \"false\" ? \"--no-install\" : \"--install\",\n    `--addons ${\n      stack.addons.length > 0\n        ? stack.addons\n            .filter((addon) =>\n              [\n                \"pwa\",\n                \"tauri\",\n                \"electrobun\",\n                \"starlight\",\n                \"biome\",\n                \"lefthook\",\n                \"husky\",\n                \"turborepo\",\n                \"nx\",\n                \"ultracite\",\n                \"fumadocs\",\n                \"oxlint\",\n                \"opentui\",\n                \"wxt\",\n                \"skills\",\n                \"mcp\",\n                \"evlog\",\n              ].includes(addon),\n            )\n            .join(\" \") || \"none\"\n        : \"none\"\n    }`,\n    `--examples ${stack.examples.join(\" \") || \"none\"}`,\n  ];\n\n  if (stack.yolo === \"true\") {\n    flags.push(\"--yolo\");\n  }\n\n  return `${base} ${projectName} ${flags.join(\" \")}`;\n}\n\nexport function generateStackUrlFromState(stack: StackState, baseUrl?: string) {\n  const origin = baseUrl || \"https://better-t-stack.dev\";\n\n  const stackParams = new URLSearchParams();\n  Object.entries(stackUrlKeys).forEach(([stackKey, urlKey]) => {\n    const value = stack[stackKey as keyof StackState];\n    if (value !== undefined) {\n      stackParams.set(urlKey as string, Array.isArray(value) ? value.join(\",\") : String(value));\n    }\n  });\n\n  const searchString = stackParams.toString();\n  return `${origin}/new${searchString ? `?${searchString}` : \"\"}`;\n}\n\nexport function generateStackSharingUrl(stack: StackState, baseUrl?: string) {\n  const origin = baseUrl || \"https://better-t-stack.dev\";\n\n  const stackParams = new URLSearchParams();\n  Object.entries(stackUrlKeys).forEach(([stackKey, urlKey]) => {\n    const value = stack[stackKey as keyof StackState];\n    if (value !== undefined) {\n      stackParams.set(urlKey as string, Array.isArray(value) ? value.join(\",\") : String(value));\n    }\n  });\n\n  const searchString = stackParams.toString();\n  return `${origin}/stack${searchString ? `?${searchString}` : \"\"}`;\n}\n\nexport { CATEGORY_ORDER };\n"
  },
  {
    "path": "apps/web/src/lib/types.ts",
    "content": "// TechCategory for the stack builder UI\nexport type TechCategory =\n  | \"api\"\n  | \"webFrontend\"\n  | \"nativeFrontend\"\n  | \"runtime\"\n  | \"backend\"\n  | \"database\"\n  | \"orm\"\n  | \"dbSetup\"\n  | \"webDeploy\"\n  | \"serverDeploy\"\n  | \"auth\"\n  | \"payments\"\n  | \"packageManager\"\n  | \"addons\"\n  | \"examples\"\n  | \"git\"\n  | \"install\";\n\nexport type TechEdge = {\n  id: string;\n  source: string;\n  target: string;\n  type?: string;\n  animated?: boolean;\n};\n\nexport type Sponsor = {\n  name: string;\n  githubId: string;\n  avatarUrl: string;\n  websiteUrl?: string;\n  githubUrl: string;\n  tierName: string;\n  totalProcessedAmount?: number;\n  sinceWhen: string;\n  transactionCount: number;\n  formattedAmount?: string;\n};\n\nexport type SponsorsData = {\n  generated_at: string;\n  summary: {\n    total_sponsors: number;\n    total_lifetime_amount: number;\n    total_current_monthly: number;\n    special_sponsors: number;\n    current_sponsors: number;\n    past_sponsors: number;\n    backers: number;\n    top_sponsor: {\n      name: string;\n      amount: number;\n    };\n  };\n  specialSponsors: Sponsor[];\n  sponsors: Sponsor[];\n  pastSponsors: Sponsor[];\n  backers: Sponsor[];\n};\n"
  },
  {
    "path": "apps/web/src/lib/utils.ts",
    "content": "import { type ClassValue, clsx } from \"clsx\";\nimport { twMerge } from \"tailwind-merge\";\n\nexport function cn(...inputs: ClassValue[]) {\n  return twMerge(clsx(inputs));\n}\n"
  },
  {
    "path": "apps/web/test/stack-builder-compatibility.test.ts",
    "content": "import { describe, expect, test } from \"bun:test\";\n\nimport {\n  getCompatibilityAdjustmentKey,\n  getCompatibilityAdjustmentState,\n} from \"../src/app/(home)/new/_components/stack-builder/use-stack-builder\";\nimport {\n  analyzeStackCompatibility,\n  getDisabledReason,\n} from \"../src/app/(home)/new/_components/utils\";\nimport { DEFAULT_STACK, type StackState } from \"../src/lib/constant\";\n\nfunction createStack(overrides: Partial<StackState> = {}): StackState {\n  return {\n    ...DEFAULT_STACK,\n    ...overrides,\n    webFrontend: [...(overrides.webFrontend ?? DEFAULT_STACK.webFrontend)],\n    nativeFrontend: [...(overrides.nativeFrontend ?? DEFAULT_STACK.nativeFrontend)],\n    addons: [...(overrides.addons ?? DEFAULT_STACK.addons)],\n    examples: [...(overrides.examples ?? DEFAULT_STACK.examples)],\n  };\n}\n\ndescribe(\"stack builder D1 compatibility\", () => {\n  test(\"keeps self fullstack backends on the D1 + Cloudflare path\", () => {\n    const stack = createStack({\n      backend: \"self-next\",\n      webFrontend: [\"next\"],\n      runtime: \"none\",\n      database: \"sqlite\",\n      orm: \"drizzle\",\n      dbSetup: \"d1\",\n      webDeploy: \"none\",\n      serverDeploy: \"none\",\n    });\n\n    const result = analyzeStackCompatibility(stack);\n\n    expect(result.adjustedStack).toMatchObject({\n      backend: \"self-next\",\n      runtime: \"none\",\n      database: \"sqlite\",\n      dbSetup: \"d1\",\n      webDeploy: \"cloudflare\",\n      serverDeploy: \"none\",\n    });\n  });\n\n  test(\"still routes non-self D1 stacks through workers + cloudflare\", () => {\n    const stack = createStack({\n      backend: \"hono\",\n      runtime: \"bun\",\n      database: \"sqlite\",\n      orm: \"drizzle\",\n      dbSetup: \"d1\",\n      serverDeploy: \"none\",\n    });\n\n    const result = analyzeStackCompatibility(stack);\n\n    expect(result.adjustedStack).toMatchObject({\n      backend: \"hono\",\n      runtime: \"workers\",\n      database: \"sqlite\",\n      dbSetup: \"d1\",\n      serverDeploy: \"cloudflare\",\n    });\n  });\n\n  test(\"allows selecting D1 for self fullstack backends\", () => {\n    const stack = createStack({\n      backend: \"self-next\",\n      webFrontend: [\"next\"],\n      runtime: \"none\",\n      database: \"sqlite\",\n    });\n\n    expect(getDisabledReason(stack, \"dbSetup\", \"d1\")).toBeNull();\n  });\n\n  test(\"blocks non-cloudflare web deployment for self fullstack D1 stacks\", () => {\n    const stack = createStack({\n      backend: \"self-next\",\n      webFrontend: [\"next\"],\n      runtime: \"none\",\n      database: \"sqlite\",\n      dbSetup: \"d1\",\n      webDeploy: \"cloudflare\",\n    });\n\n    expect(getDisabledReason(stack, \"webDeploy\", \"none\")).toBe(\n      \"D1 with a self fullstack backend requires Cloudflare web deployment\",\n    );\n  });\n\n  test(\"reapplies the same D1 adjustment after leaving and returning to it\", () => {\n    const adjustedD1Stack = createStack({\n      backend: \"self-next\",\n      webFrontend: [\"next\"],\n      runtime: \"none\",\n      database: \"sqlite\",\n      dbSetup: \"d1\",\n      webDeploy: \"cloudflare\",\n      serverDeploy: \"none\",\n    });\n    const initialRawD1Stack = createStack({\n      ...adjustedD1Stack,\n      webDeploy: \"none\",\n    });\n    const tursoStack = createStack({\n      backend: \"self-next\",\n      webFrontend: [\"next\"],\n      runtime: \"none\",\n      database: \"sqlite\",\n      dbSetup: \"turso\",\n      webDeploy: \"none\",\n      serverDeploy: \"none\",\n    });\n\n    const firstAdjustment = getCompatibilityAdjustmentState(\"\", initialRawD1Stack, adjustedD1Stack);\n    const settledState = getCompatibilityAdjustmentState(\n      firstAdjustment.adjustmentKey,\n      tursoStack,\n      null,\n    );\n    const secondAdjustment = getCompatibilityAdjustmentState(\n      settledState.adjustmentKey,\n      initialRawD1Stack,\n      adjustedD1Stack,\n    );\n\n    expect(firstAdjustment.adjustmentKey).toBe(\n      getCompatibilityAdjustmentKey(initialRawD1Stack, adjustedD1Stack),\n    );\n    expect(firstAdjustment.shouldApply).toBe(true);\n    expect(settledState.adjustmentKey).toBe(\"\");\n    expect(settledState.shouldApply).toBe(false);\n    expect(secondAdjustment.adjustmentKey).toBe(\n      getCompatibilityAdjustmentKey(initialRawD1Stack, adjustedD1Stack),\n    );\n    expect(secondAdjustment.shouldApply).toBe(true);\n  });\n\n  test(\"allows Polar when there is no frontend at all\", () => {\n    const stack = createStack({\n      webFrontend: [\"none\"],\n      nativeFrontend: [\"none\"],\n      backend: \"hono\",\n      auth: \"better-auth\",\n    });\n\n    expect(getDisabledReason(stack, \"payments\", \"polar\")).toBeNull();\n  });\n\n  test(\"blocks Polar for native-only stacks\", () => {\n    const stack = createStack({\n      webFrontend: [\"none\"],\n      nativeFrontend: [\"native-bare\"],\n      backend: \"hono\",\n      auth: \"better-auth\",\n    });\n\n    expect(getDisabledReason(stack, \"payments\", \"polar\")).toBe(\n      \"Polar requires a web frontend or no frontend\",\n    );\n  });\n\n  test(\"blocks the AI example for Astro frontends\", () => {\n    const stack = createStack({\n      webFrontend: [\"astro\"],\n      backend: \"self-astro\",\n      api: \"orpc\",\n    });\n\n    expect(getDisabledReason(stack, \"examples\", \"ai\")).toBe(\n      \"AI example not compatible with Solid or Astro frontend\",\n    );\n\n    const result = analyzeStackCompatibility({\n      ...stack,\n      examples: [\"ai\"],\n    });\n\n    expect(result.adjustedStack?.examples).toEqual([\"none\"]);\n  });\n\n  test(\"blocks Evlog for Convex stacks\", () => {\n    const stack = createStack({\n      webFrontend: [\"tanstack-start\"],\n      nativeFrontend: [\"native-uniwind\"],\n      backend: \"convex\",\n      runtime: \"none\",\n      addons: [\"turborepo\"],\n    });\n\n    expect(getDisabledReason(stack, \"addons\", \"evlog\")).toBe(\n      \"evlog requires Hono, Express, Fastify, Elysia, or a fullstack backend\",\n    );\n  });\n\n  test(\"removes Evlog when a selected stack switches to Convex\", () => {\n    const stack = createStack({\n      webFrontend: [\"tanstack-start\"],\n      nativeFrontend: [\"native-uniwind\"],\n      backend: \"convex\",\n      runtime: \"none\",\n      addons: [\"turborepo\", \"evlog\"],\n    });\n\n    const result = analyzeStackCompatibility(stack);\n\n    expect(result.adjustedStack?.addons).toEqual([\"turborepo\"]);\n    expect(result.changes).toContainEqual({\n      category: \"addons\",\n      message: \"evlog removed (requires a server or fullstack backend)\",\n    });\n  });\n\n  test(\"allows Evlog for server and fullstack stacks\", () => {\n    const serverStack = createStack({\n      backend: \"hono\",\n      runtime: \"bun\",\n    });\n    const fullstackStack = createStack({\n      webFrontend: [\"tanstack-start\"],\n      backend: \"self-tanstack-start\",\n      runtime: \"none\",\n    });\n\n    expect(getDisabledReason(serverStack, \"addons\", \"evlog\")).toBeNull();\n    expect(getDisabledReason(fullstackStack, \"addons\", \"evlog\")).toBeNull();\n  });\n});\n"
  },
  {
    "path": "apps/web/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ESNext\",\n    \"lib\": [\"dom\", \"dom.iterable\", \"esnext\"],\n    \"allowJs\": true,\n    \"skipLibCheck\": true,\n    \"strict\": true,\n    \"forceConsistentCasingInFileNames\": true,\n    \"noEmit\": true,\n    \"esModuleInterop\": true,\n    \"module\": \"esnext\",\n    \"moduleResolution\": \"bundler\",\n    \"resolveJsonModule\": true,\n    \"isolatedModules\": true,\n    \"jsx\": \"react-jsx\",\n    \"incremental\": true,\n    \"paths\": {\n      \"fumadocs-mdx:collections/*\": [\"./.source/*\"],\n      \"@/*\": [\"./src/*\"],\n      \"@/public/*\": [\"./public/*\"]\n    },\n    \"plugins\": [\n      {\n        \"name\": \"next\"\n      }\n    ]\n  },\n  \"include\": [\n    \"next-env.d.ts\",\n    \"**/*.ts\",\n    \"**/*.tsx\",\n    \".next/types/**/*.ts\",\n    \".next/dev/types/**/*.ts\"\n  ],\n  \"exclude\": [\"node_modules\"]\n}\n"
  },
  {
    "path": "bunfig.toml",
    "content": "[install]\nlinker = \"isolated\"\n\n[test]\ncoverage = true\ncoverageReporter = [\"text\", \"lcov\"]\ncoverageSkipTestFiles = true # default false\n"
  },
  {
    "path": "changelogithub.config.ts",
    "content": "export default {\n  repo: \"AmanVarshney01/create-better-t-stack\",\n  emoji: true,\n  contributors: true,\n};\n"
  },
  {
    "path": "lefthook.yml",
    "content": "pre-commit:\n  commands:\n    lint-staged:\n      run: bunx lint-staged\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"better-t-stack\",\n  \"version\": \"0.0.0\",\n  \"private\": true,\n  \"workspaces\": {\n    \"packages\": [\n      \"apps/*\",\n      \"packages/*\"\n    ],\n    \"catalog\": {\n      \"@erquhart/convex-oss-stats\": \"^0.8.2\",\n      \"@types/fs-extra\": \"^11.0.4\",\n      \"@types/node\": \"^25.6.0\",\n      \"better-result\": \"^2.8.2\",\n      \"convex\": \"^1.35.1\",\n      \"convex-helpers\": \"^0.1.115\",\n      \"handlebars\": \"^4.7.9\",\n      \"oxfmt\": \"^0.46.0\",\n      \"ts-morph\": \"^28.0.0\",\n      \"tsdown\": \"^0.21.9\",\n      \"typescript\": \"^6.0.3\",\n      \"yaml\": \"^2.8.3\",\n      \"zod\": \"^4.3.6\"\n    }\n  },\n  \"scripts\": {\n    \"build\": \"turbo run build\",\n    \"dev\": \"turbo run dev\",\n    \"dev:cli\": \"turbo run dev --filter=create-better-t-stack\",\n    \"cli\": \"cd apps/cli && node dist/cli.js\",\n    \"smoke:publish\": \"bun run scripts/publish-smoke.ts\",\n    \"dev:web\": \"turbo run dev --filter=web\",\n    \"build:web\": \"turbo run build --filter=web\",\n    \"build:cli\": \"turbo run build --filter=create-better-t-stack\",\n    \"lint\": \"turbo run lint\",\n    \"check\": \"oxfmt . && oxlint .\",\n    \"prepare\": \"lefthook install\",\n    \"release\": \"bun run scripts/release.ts\",\n    \"bump\": \"bun run scripts/bump-version.ts\",\n    \"canary\": \"bun run scripts/canary-release.ts\",\n    \"cleanup-previews\": \"bun run scripts/cleanup-previews.ts\",\n    \"deploy:convex\": \"turbo run --filter=@better-t-stack/backend deploy\",\n    \"deploy:web\": \"turbo run deploy --filter=web\",\n    \"generate\": \"turbo run generate-schema --filter=web\",\n    \"deploy\": \"turbo run deploy --filter=web\"\n  },\n  \"dependencies\": {\n    \"@clack/prompts\": \"^1.2.0\"\n  },\n  \"devDependencies\": {\n    \"@types/bun\": \"^1.3.12\",\n    \"changelogithub\": \"^14.0.0\",\n    \"lefthook\": \"^2.1.6\",\n    \"lint-staged\": \"^16.4.0\",\n    \"oxfmt\": \"catalog:\",\n    \"oxlint\": \"^1.61.0\",\n    \"turbo\": \"^2.9.6\",\n    \"typescript\": \"catalog:\"\n  },\n  \"lint-staged\": {\n    \"*\": [\n      \"bun check\"\n    ]\n  },\n  \"engines\": {\n    \"node\": \"24.x\"\n  },\n  \"packageManager\": \"bun@1.3.13\"\n}\n"
  },
  {
    "path": "packages/backend/.gitignore",
    "content": "\n.env.local\n"
  },
  {
    "path": "packages/backend/convex/README.md",
    "content": "# Welcome to your Convex functions directory!\n\nWrite your Convex functions here.\nSee https://docs.convex.dev/functions for more.\n\nA query function that takes two arguments looks like:\n\n```ts\n// convex/myFunctions.ts\nimport { query } from \"./_generated/server\";\nimport { v } from \"convex/values\";\n\nexport const myQueryFunction = query({\n  // Validators for arguments.\n  args: {\n    first: v.number(),\n    second: v.string(),\n  },\n\n  // Function implementation.\n  handler: async (ctx, args) => {\n    // Read the database as many times as you need here.\n    // See https://docs.convex.dev/database/reading-data.\n    const documents = await ctx.db.query(\"tablename\").collect();\n\n    // Arguments passed from the client are properties of the args object.\n    console.log(args.first, args.second);\n\n    // Write arbitrary JavaScript here: filter, aggregate, build derived data,\n    // remove non-public properties, or create new objects.\n    return documents;\n  },\n});\n```\n\nUsing this query function in a React component looks like:\n\n```ts\nconst data = useQuery(api.myFunctions.myQueryFunction, {\n  first: 10,\n  second: \"hello\",\n});\n```\n\nA mutation function looks like:\n\n```ts\n// convex/myFunctions.ts\nimport { mutation } from \"./_generated/server\";\nimport { v } from \"convex/values\";\n\nexport const myMutationFunction = mutation({\n  // Validators for arguments.\n  args: {\n    first: v.string(),\n    second: v.string(),\n  },\n\n  // Function implementation.\n  handler: async (ctx, args) => {\n    // Insert or modify documents in the database here.\n    // Mutations can also read from the database like queries.\n    // See https://docs.convex.dev/database/writing-data.\n    const message = { body: args.first, author: args.second };\n    const id = await ctx.db.insert(\"messages\", message);\n\n    // Optionally, return a value from your mutation.\n    return await ctx.db.get(\"messages\", id);\n  },\n});\n```\n\nUsing this mutation function in a React component looks like:\n\n```ts\nconst mutation = useMutation(api.myFunctions.myMutationFunction);\nfunction handleButtonPress() {\n  // fire and forget, the most common way to use mutations\n  mutation({ first: \"Hello!\", second: \"me\" });\n  // OR\n  // use the result once the mutation has completed\n  mutation({ first: \"Hello!\", second: \"me\" }).then((result) => console.log(result));\n}\n```\n\nUse the Convex CLI to push your functions to a deployment. See everything\nthe Convex CLI can do by running `npx convex -h` in your project root\ndirectory. To learn more, launch the docs with `npx convex docs`.\n"
  },
  {
    "path": "packages/backend/convex/_generated/api.d.ts",
    "content": "/* eslint-disable */\n/**\n * Generated `api` utility.\n *\n * THIS CODE IS AUTOMATICALLY GENERATED.\n *\n * To regenerate, run `npx convex dev`.\n * @module\n */\n\nimport type * as analytics from \"../analytics.js\";\nimport type * as analytics_date_utils from \"../analytics_date_utils.js\";\nimport type * as healthCheck from \"../healthCheck.js\";\nimport type * as hooks from \"../hooks.js\";\nimport type * as http from \"../http.js\";\nimport type * as showcase from \"../showcase.js\";\nimport type * as stats from \"../stats.js\";\nimport type * as testimonials from \"../testimonials.js\";\n\nimport type {\n  ApiFromModules,\n  FilterApi,\n  FunctionReference,\n} from \"convex/server\";\n\ndeclare const fullApi: ApiFromModules<{\n  analytics: typeof analytics;\n  analytics_date_utils: typeof analytics_date_utils;\n  healthCheck: typeof healthCheck;\n  hooks: typeof hooks;\n  http: typeof http;\n  showcase: typeof showcase;\n  stats: typeof stats;\n  testimonials: typeof testimonials;\n}>;\n\n/**\n * A utility for referencing Convex functions in your app's public API.\n *\n * Usage:\n * ```js\n * const myFunctionReference = api.myModule.myFunction;\n * ```\n */\nexport declare const api: FilterApi<\n  typeof fullApi,\n  FunctionReference<any, \"public\">\n>;\n\n/**\n * A utility for referencing Convex functions in your app's internal API.\n *\n * Usage:\n * ```js\n * const myFunctionReference = internal.myModule.myFunction;\n * ```\n */\nexport declare const internal: FilterApi<\n  typeof fullApi,\n  FunctionReference<any, \"internal\">\n>;\n\nexport declare const components: {\n  ossStats: import(\"@erquhart/convex-oss-stats/_generated/component.js\").ComponentApi<\"ossStats\">;\n};\n"
  },
  {
    "path": "packages/backend/convex/_generated/api.js",
    "content": "/* eslint-disable */\n/**\n * Generated `api` utility.\n *\n * THIS CODE IS AUTOMATICALLY GENERATED.\n *\n * To regenerate, run `npx convex dev`.\n * @module\n */\n\nimport { anyApi, componentsGeneric } from \"convex/server\";\n\n/**\n * A utility for referencing Convex functions in your app's API.\n *\n * Usage:\n * ```js\n * const myFunctionReference = api.myModule.myFunction;\n * ```\n */\nexport const api = anyApi;\nexport const internal = anyApi;\nexport const components = componentsGeneric();\n"
  },
  {
    "path": "packages/backend/convex/_generated/dataModel.d.ts",
    "content": "/* eslint-disable */\n/**\n * Generated data model types.\n *\n * THIS CODE IS AUTOMATICALLY GENERATED.\n *\n * To regenerate, run `npx convex dev`.\n * @module\n */\n\nimport type {\n  DataModelFromSchemaDefinition,\n  DocumentByName,\n  TableNamesInDataModel,\n  SystemTableNames,\n} from \"convex/server\";\nimport type { GenericId } from \"convex/values\";\nimport schema from \"../schema.js\";\n\n/**\n * The names of all of your Convex tables.\n */\nexport type TableNames = TableNamesInDataModel<DataModel>;\n\n/**\n * The type of a document stored in Convex.\n *\n * @typeParam TableName - A string literal type of the table name (like \"users\").\n */\nexport type Doc<TableName extends TableNames> = DocumentByName<\n  DataModel,\n  TableName\n>;\n\n/**\n * An identifier for a document in Convex.\n *\n * Convex documents are uniquely identified by their `Id`, which is accessible\n * on the `_id` field. To learn more, see [Document IDs](https://docs.convex.dev/using/document-ids).\n *\n * Documents can be loaded using `db.get(tableName, id)` in query and mutation functions.\n *\n * IDs are just strings at runtime, but this type can be used to distinguish them from other\n * strings when type checking.\n *\n * @typeParam TableName - A string literal type of the table name (like \"users\").\n */\nexport type Id<TableName extends TableNames | SystemTableNames> =\n  GenericId<TableName>;\n\n/**\n * A type describing your Convex data model.\n *\n * This type includes information about what tables you have, the type of\n * documents stored in those tables, and the indexes defined on them.\n *\n * This type is used to parameterize methods like `queryGeneric` and\n * `mutationGeneric` to make them type-safe.\n */\nexport type DataModel = DataModelFromSchemaDefinition<typeof schema>;\n"
  },
  {
    "path": "packages/backend/convex/_generated/server.d.ts",
    "content": "/* eslint-disable */\n/**\n * Generated utilities for implementing server-side Convex query and mutation functions.\n *\n * THIS CODE IS AUTOMATICALLY GENERATED.\n *\n * To regenerate, run `npx convex dev`.\n * @module\n */\n\nimport {\n  ActionBuilder,\n  HttpActionBuilder,\n  MutationBuilder,\n  QueryBuilder,\n  GenericActionCtx,\n  GenericMutationCtx,\n  GenericQueryCtx,\n  GenericDatabaseReader,\n  GenericDatabaseWriter,\n} from \"convex/server\";\nimport type { DataModel } from \"./dataModel.js\";\n\n/**\n * Define a query in this Convex app's public API.\n *\n * This function will be allowed to read your Convex database and will be accessible from the client.\n *\n * @param func - The query function. It receives a {@link QueryCtx} as its first argument.\n * @returns The wrapped query. Include this as an `export` to name it and make it accessible.\n */\nexport declare const query: QueryBuilder<DataModel, \"public\">;\n\n/**\n * Define a query that is only accessible from other Convex functions (but not from the client).\n *\n * This function will be allowed to read from your Convex database. It will not be accessible from the client.\n *\n * @param func - The query function. It receives a {@link QueryCtx} as its first argument.\n * @returns The wrapped query. Include this as an `export` to name it and make it accessible.\n */\nexport declare const internalQuery: QueryBuilder<DataModel, \"internal\">;\n\n/**\n * Define a mutation in this Convex app's public API.\n *\n * This function will be allowed to modify your Convex database and will be accessible from the client.\n *\n * @param func - The mutation function. It receives a {@link MutationCtx} as its first argument.\n * @returns The wrapped mutation. Include this as an `export` to name it and make it accessible.\n */\nexport declare const mutation: MutationBuilder<DataModel, \"public\">;\n\n/**\n * Define a mutation that is only accessible from other Convex functions (but not from the client).\n *\n * This function will be allowed to modify your Convex database. It will not be accessible from the client.\n *\n * @param func - The mutation function. It receives a {@link MutationCtx} as its first argument.\n * @returns The wrapped mutation. Include this as an `export` to name it and make it accessible.\n */\nexport declare const internalMutation: MutationBuilder<DataModel, \"internal\">;\n\n/**\n * Define an action in this Convex app's public API.\n *\n * An action is a function which can execute any JavaScript code, including non-deterministic\n * code and code with side-effects, like calling third-party services.\n * They can be run in Convex's JavaScript environment or in Node.js using the \"use node\" directive.\n * They can interact with the database indirectly by calling queries and mutations using the {@link ActionCtx}.\n *\n * @param func - The action. It receives an {@link ActionCtx} as its first argument.\n * @returns The wrapped action. Include this as an `export` to name it and make it accessible.\n */\nexport declare const action: ActionBuilder<DataModel, \"public\">;\n\n/**\n * Define an action that is only accessible from other Convex functions (but not from the client).\n *\n * @param func - The function. It receives an {@link ActionCtx} as its first argument.\n * @returns The wrapped function. Include this as an `export` to name it and make it accessible.\n */\nexport declare const internalAction: ActionBuilder<DataModel, \"internal\">;\n\n/**\n * Define an HTTP action.\n *\n * The wrapped function will be used to respond to HTTP requests received\n * by a Convex deployment if the requests matches the path and method where\n * this action is routed. Be sure to route your httpAction in `convex/http.js`.\n *\n * @param func - The function. It receives an {@link ActionCtx} as its first argument\n * and a Fetch API `Request` object as its second.\n * @returns The wrapped function. Import this function from `convex/http.js` and route it to hook it up.\n */\nexport declare const httpAction: HttpActionBuilder;\n\n/**\n * A set of services for use within Convex query functions.\n *\n * The query context is passed as the first argument to any Convex query\n * function run on the server.\n *\n * This differs from the {@link MutationCtx} because all of the services are\n * read-only.\n */\nexport type QueryCtx = GenericQueryCtx<DataModel>;\n\n/**\n * A set of services for use within Convex mutation functions.\n *\n * The mutation context is passed as the first argument to any Convex mutation\n * function run on the server.\n */\nexport type MutationCtx = GenericMutationCtx<DataModel>;\n\n/**\n * A set of services for use within Convex action functions.\n *\n * The action context is passed as the first argument to any Convex action\n * function run on the server.\n */\nexport type ActionCtx = GenericActionCtx<DataModel>;\n\n/**\n * An interface to read from the database within Convex query functions.\n *\n * The two entry points are {@link DatabaseReader.get}, which fetches a single\n * document by its {@link Id}, or {@link DatabaseReader.query}, which starts\n * building a query.\n */\nexport type DatabaseReader = GenericDatabaseReader<DataModel>;\n\n/**\n * An interface to read from and write to the database within Convex mutation\n * functions.\n *\n * Convex guarantees that all writes within a single mutation are\n * executed atomically, so you never have to worry about partial writes leaving\n * your data in an inconsistent state. See [the Convex Guide](https://docs.convex.dev/understanding/convex-fundamentals/functions#atomicity-and-optimistic-concurrency-control)\n * for the guarantees Convex provides your functions.\n */\nexport type DatabaseWriter = GenericDatabaseWriter<DataModel>;\n"
  },
  {
    "path": "packages/backend/convex/_generated/server.js",
    "content": "/* eslint-disable */\n/**\n * Generated utilities for implementing server-side Convex query and mutation functions.\n *\n * THIS CODE IS AUTOMATICALLY GENERATED.\n *\n * To regenerate, run `npx convex dev`.\n * @module\n */\n\nimport {\n  actionGeneric,\n  httpActionGeneric,\n  queryGeneric,\n  mutationGeneric,\n  internalActionGeneric,\n  internalMutationGeneric,\n  internalQueryGeneric,\n} from \"convex/server\";\n\n/**\n * Define a query in this Convex app's public API.\n *\n * This function will be allowed to read your Convex database and will be accessible from the client.\n *\n * @param func - The query function. It receives a {@link QueryCtx} as its first argument.\n * @returns The wrapped query. Include this as an `export` to name it and make it accessible.\n */\nexport const query = queryGeneric;\n\n/**\n * Define a query that is only accessible from other Convex functions (but not from the client).\n *\n * This function will be allowed to read from your Convex database. It will not be accessible from the client.\n *\n * @param func - The query function. It receives a {@link QueryCtx} as its first argument.\n * @returns The wrapped query. Include this as an `export` to name it and make it accessible.\n */\nexport const internalQuery = internalQueryGeneric;\n\n/**\n * Define a mutation in this Convex app's public API.\n *\n * This function will be allowed to modify your Convex database and will be accessible from the client.\n *\n * @param func - The mutation function. It receives a {@link MutationCtx} as its first argument.\n * @returns The wrapped mutation. Include this as an `export` to name it and make it accessible.\n */\nexport const mutation = mutationGeneric;\n\n/**\n * Define a mutation that is only accessible from other Convex functions (but not from the client).\n *\n * This function will be allowed to modify your Convex database. It will not be accessible from the client.\n *\n * @param func - The mutation function. It receives a {@link MutationCtx} as its first argument.\n * @returns The wrapped mutation. Include this as an `export` to name it and make it accessible.\n */\nexport const internalMutation = internalMutationGeneric;\n\n/**\n * Define an action in this Convex app's public API.\n *\n * An action is a function which can execute any JavaScript code, including non-deterministic\n * code and code with side-effects, like calling third-party services.\n * They can be run in Convex's JavaScript environment or in Node.js using the \"use node\" directive.\n * They can interact with the database indirectly by calling queries and mutations using the {@link ActionCtx}.\n *\n * @param func - The action. It receives an {@link ActionCtx} as its first argument.\n * @returns The wrapped action. Include this as an `export` to name it and make it accessible.\n */\nexport const action = actionGeneric;\n\n/**\n * Define an action that is only accessible from other Convex functions (but not from the client).\n *\n * @param func - The function. It receives an {@link ActionCtx} as its first argument.\n * @returns The wrapped function. Include this as an `export` to name it and make it accessible.\n */\nexport const internalAction = internalActionGeneric;\n\n/**\n * Define an HTTP action.\n *\n * The wrapped function will be used to respond to HTTP requests received\n * by a Convex deployment if the requests matches the path and method where\n * this action is routed. Be sure to route your httpAction in `convex/http.js`.\n *\n * @param func - The function. It receives an {@link ActionCtx} as its first argument\n * and a Fetch API `Request` object as its second.\n * @returns The wrapped function. Import this function from `convex/http.js` and route it to hook it up.\n */\nexport const httpAction = httpActionGeneric;\n"
  },
  {
    "path": "packages/backend/convex/analytics.ts",
    "content": "import { v } from \"convex/values\";\n\nimport { internalMutation, mutation, query } from \"./_generated/server\";\nimport { buildDailyWindow } from \"./analytics_date_utils\";\n\nconst MAX_DAILY_STATS_WINDOW = 366;\n\nfunction incrementKey(\n  dist: Record<string, number>,\n  key: string | undefined,\n): Record<string, number> {\n  if (!key) return dist;\n  return { ...dist, [key]: (dist[key] || 0) + 1 };\n}\n\nfunction incrementKeys(\n  dist: Record<string, number>,\n  keys: string[] | undefined,\n): Record<string, number> {\n  if (!keys) return dist;\n  const result = { ...dist };\n  for (const key of keys) {\n    result[key] = (result[key] || 0) + 1;\n  }\n  return result;\n}\n\nfunction incrementBool(\n  dist: Record<string, number>,\n  val: boolean | undefined,\n): Record<string, number> {\n  if (val === undefined) return dist;\n  const key = val ? \"Yes\" : \"No\";\n  return { ...dist, [key]: (dist[key] || 0) + 1 };\n}\n\nfunction getMajorVersion(version: string | undefined): string | undefined {\n  if (!version) return undefined;\n  const clean = version.startsWith(\"v\") ? version.slice(1) : version;\n  return `v${clean.split(\".\")[0]}`;\n}\n\nexport const ingestEvent = internalMutation({\n  args: {\n    database: v.optional(v.string()),\n    orm: v.optional(v.string()),\n    backend: v.optional(v.string()),\n    runtime: v.optional(v.string()),\n    frontend: v.optional(v.array(v.string())),\n    addons: v.optional(v.array(v.string())),\n    examples: v.optional(v.array(v.string())),\n    auth: v.optional(v.string()),\n    payments: v.optional(v.string()),\n    git: v.optional(v.boolean()),\n    packageManager: v.optional(v.string()),\n    install: v.optional(v.boolean()),\n    dbSetup: v.optional(v.string()),\n    api: v.optional(v.string()),\n    webDeploy: v.optional(v.string()),\n    serverDeploy: v.optional(v.string()),\n    cli_version: v.optional(v.string()),\n    node_version: v.optional(v.string()),\n    platform: v.optional(v.string()),\n  },\n  returns: v.null(),\n  handler: async (ctx, args) => {\n    const id = await ctx.db.insert(\"analyticsEvents\", args);\n    const event = await ctx.db.get(id);\n    const now = event!._creationTime;\n\n    const hourKey = String(new Date(now).getUTCHours()).padStart(2, \"0\");\n    const fe = args.frontend?.[0] || \"none\";\n    const be = args.backend || \"none\";\n    const stackKey = `${be} + ${fe}`;\n    const db = args.database || \"none\";\n    const o = args.orm || \"none\";\n    const dbOrmKey = `${db} + ${o}`;\n\n    const existingStats = await ctx.db.query(\"analyticsStats\").first();\n\n    if (existingStats) {\n      await ctx.db.patch(\"analyticsStats\", existingStats._id, {\n        totalProjects: existingStats.totalProjects + 1,\n        lastEventTime: now,\n        backend: incrementKey(existingStats.backend, args.backend),\n        frontend: incrementKeys(existingStats.frontend, args.frontend),\n        database: incrementKey(existingStats.database, args.database),\n        orm: incrementKey(existingStats.orm, args.orm),\n        api: incrementKey(existingStats.api, args.api),\n        auth: incrementKey(existingStats.auth, args.auth),\n        runtime: incrementKey(existingStats.runtime, args.runtime),\n        packageManager: incrementKey(existingStats.packageManager, args.packageManager),\n        platform: incrementKey(existingStats.platform, args.platform),\n        addons: incrementKeys(existingStats.addons, args.addons),\n        examples: incrementKeys(existingStats.examples, args.examples),\n        dbSetup: incrementKey(existingStats.dbSetup, args.dbSetup),\n        webDeploy: incrementKey(existingStats.webDeploy, args.webDeploy),\n        serverDeploy: incrementKey(existingStats.serverDeploy, args.serverDeploy),\n        payments: incrementKey(existingStats.payments, args.payments),\n        git: incrementBool(existingStats.git, args.git),\n        install: incrementBool(existingStats.install, args.install),\n        nodeVersion: incrementKey(existingStats.nodeVersion, getMajorVersion(args.node_version)),\n        cliVersion: incrementKey(existingStats.cliVersion, args.cli_version),\n        hourlyDistribution: incrementKey(existingStats.hourlyDistribution || {}, hourKey),\n        stackCombinations: incrementKey(existingStats.stackCombinations || {}, stackKey),\n        dbOrmCombinations: incrementKey(existingStats.dbOrmCombinations || {}, dbOrmKey),\n      });\n    } else {\n      const emptyDist: Record<string, number> = {};\n      await ctx.db.insert(\"analyticsStats\", {\n        totalProjects: 1,\n        lastEventTime: now,\n        backend: incrementKey(emptyDist, args.backend),\n        frontend: incrementKeys(emptyDist, args.frontend),\n        database: incrementKey(emptyDist, args.database),\n        orm: incrementKey(emptyDist, args.orm),\n        api: incrementKey(emptyDist, args.api),\n        auth: incrementKey(emptyDist, args.auth),\n        runtime: incrementKey(emptyDist, args.runtime),\n        packageManager: incrementKey(emptyDist, args.packageManager),\n        platform: incrementKey(emptyDist, args.platform),\n        addons: incrementKeys(emptyDist, args.addons),\n        examples: incrementKeys(emptyDist, args.examples),\n        dbSetup: incrementKey(emptyDist, args.dbSetup),\n        webDeploy: incrementKey(emptyDist, args.webDeploy),\n        serverDeploy: incrementKey(emptyDist, args.serverDeploy),\n        payments: incrementKey(emptyDist, args.payments),\n        git: incrementBool(emptyDist, args.git),\n        install: incrementBool(emptyDist, args.install),\n        nodeVersion: incrementKey(emptyDist, getMajorVersion(args.node_version)),\n        cliVersion: incrementKey(emptyDist, args.cli_version),\n        hourlyDistribution: incrementKey(emptyDist, hourKey),\n        stackCombinations: incrementKey(emptyDist, stackKey),\n        dbOrmCombinations: incrementKey(emptyDist, dbOrmKey),\n      });\n    }\n\n    const today = new Date(now).toISOString().slice(0, 10);\n    const dailyStats = await ctx.db\n      .query(\"analyticsDailyStats\")\n      .withIndex(\"by_date\", (q) => q.eq(\"date\", today))\n      .first();\n\n    if (dailyStats) {\n      await ctx.db.patch(\"analyticsDailyStats\", dailyStats._id, { count: dailyStats.count + 1 });\n    } else {\n      await ctx.db.insert(\"analyticsDailyStats\", { date: today, count: 1 });\n    }\n\n    return null;\n  },\n});\n\nconst distributionValidator = v.record(v.string(), v.number());\n\nexport const getStats = query({\n  args: {},\n  returns: v.union(\n    v.object({\n      totalProjects: v.number(),\n      lastEventTime: v.number(),\n      backend: distributionValidator,\n      frontend: distributionValidator,\n      database: distributionValidator,\n      orm: distributionValidator,\n      api: distributionValidator,\n      auth: distributionValidator,\n      runtime: distributionValidator,\n      packageManager: distributionValidator,\n      platform: distributionValidator,\n      addons: distributionValidator,\n      examples: distributionValidator,\n      dbSetup: distributionValidator,\n      webDeploy: distributionValidator,\n      serverDeploy: distributionValidator,\n      payments: distributionValidator,\n      git: distributionValidator,\n      install: distributionValidator,\n      nodeVersion: distributionValidator,\n      cliVersion: distributionValidator,\n      hourlyDistribution: distributionValidator,\n      stackCombinations: distributionValidator,\n      dbOrmCombinations: distributionValidator,\n    }),\n    v.null(),\n  ),\n  handler: async (ctx) => {\n    const stats = await ctx.db.query(\"analyticsStats\").first();\n    if (!stats) return null;\n    return {\n      totalProjects: stats.totalProjects,\n      lastEventTime: stats.lastEventTime,\n      backend: stats.backend,\n      frontend: stats.frontend,\n      database: stats.database,\n      orm: stats.orm,\n      api: stats.api,\n      auth: stats.auth,\n      runtime: stats.runtime,\n      packageManager: stats.packageManager,\n      platform: stats.platform,\n      addons: stats.addons,\n      examples: stats.examples,\n      dbSetup: stats.dbSetup,\n      webDeploy: stats.webDeploy,\n      serverDeploy: stats.serverDeploy,\n      payments: stats.payments,\n      git: stats.git,\n      install: stats.install,\n      nodeVersion: stats.nodeVersion,\n      cliVersion: stats.cliVersion,\n      hourlyDistribution: stats.hourlyDistribution || {},\n      stackCombinations: stats.stackCombinations || {},\n      dbOrmCombinations: stats.dbOrmCombinations || {},\n    };\n  },\n});\n\nexport const getDailyStats = query({\n  args: {\n    days: v.optional(v.number()),\n  },\n  returns: v.array(\n    v.object({\n      date: v.string(),\n      count: v.number(),\n    }),\n  ),\n  handler: async (ctx, args) => {\n    const now = Date.now();\n    const today = new Date(now).toISOString().slice(0, 10);\n\n    const allDaily = await ctx.db\n      .query(\"analyticsDailyStats\")\n      .withIndex(\"by_date\")\n      .order(\"asc\")\n      .collect();\n\n    const requestedDays = args.days;\n    const sanitizedDays =\n      typeof requestedDays === \"number\" && Number.isFinite(requestedDays) && requestedDays > 0\n        ? Math.min(Math.floor(requestedDays), MAX_DAILY_STATS_WINDOW)\n        : 30;\n    const cutoffDate = new Date(now - (sanitizedDays - 1) * 24 * 60 * 60 * 1000)\n      .toISOString()\n      .slice(0, 10);\n\n    return buildDailyWindow(\n      allDaily\n        .filter((d) => d.date >= cutoffDate && d.date <= today)\n        .map((d) => ({ date: d.date, count: d.count })),\n      cutoffDate,\n      today,\n    );\n  },\n});\n\nexport const getMonthlyStats = query({\n  args: {},\n  returns: v.object({\n    monthly: v.array(\n      v.object({\n        month: v.string(),\n        totalProjects: v.number(),\n      }),\n    ),\n    firstDate: v.union(v.string(), v.null()),\n    lastDate: v.union(v.string(), v.null()),\n  }),\n  handler: async (ctx) => {\n    const today = new Date().toISOString().slice(0, 10);\n    const allDaily = await ctx.db\n      .query(\"analyticsDailyStats\")\n      .withIndex(\"by_date\")\n      .order(\"asc\")\n      .collect();\n\n    const usableDaily = allDaily.filter((d) => d.date <= today);\n    if (usableDaily.length === 0) {\n      return { monthly: [], firstDate: null, lastDate: null };\n    }\n\n    const byMonth = new Map<string, number>();\n    for (const d of usableDaily) {\n      const month = d.date.slice(0, 7);\n      byMonth.set(month, (byMonth.get(month) || 0) + d.count);\n    }\n\n    const monthly = Array.from(byMonth.entries())\n      .map(([month, totalProjects]) => ({ month, totalProjects }))\n      .sort((a, b) => a.month.localeCompare(b.month));\n\n    return {\n      monthly,\n      firstDate: usableDaily[0]?.date ?? null,\n      lastDate: usableDaily[usableDaily.length - 1]?.date ?? null,\n    };\n  },\n});\n\nexport const getRecentEvents = query({\n  args: {\n    limit: v.optional(v.number()),\n  },\n  returns: v.array(\n    v.object({\n      _id: v.id(\"analyticsEvents\"),\n      _creationTime: v.number(),\n      database: v.optional(v.string()),\n      orm: v.optional(v.string()),\n      backend: v.optional(v.string()),\n      runtime: v.optional(v.string()),\n      frontend: v.optional(v.array(v.string())),\n      addons: v.optional(v.array(v.string())),\n      examples: v.optional(v.array(v.string())),\n      auth: v.optional(v.string()),\n      payments: v.optional(v.string()),\n      git: v.optional(v.boolean()),\n      packageManager: v.optional(v.string()),\n      install: v.optional(v.boolean()),\n      dbSetup: v.optional(v.string()),\n      api: v.optional(v.string()),\n      webDeploy: v.optional(v.string()),\n      serverDeploy: v.optional(v.string()),\n      cli_version: v.optional(v.string()),\n      node_version: v.optional(v.string()),\n      platform: v.optional(v.string()),\n    }),\n  ),\n  handler: async (ctx, args) => {\n    const limit =\n      typeof args.limit === \"number\" && Number.isFinite(args.limit) && args.limit > 0\n        ? Math.min(Math.floor(args.limit), 50)\n        : 20;\n\n    return await ctx.db.query(\"analyticsEvents\").order(\"desc\").take(limit);\n  },\n});\n\nexport const backfillStats = mutation({\n  args: {},\n  returns: v.object({\n    totalProcessed: v.number(),\n    dailyDates: v.number(),\n  }),\n  handler: async (ctx) => {\n    const existing = await ctx.db.query(\"analyticsStats\").first();\n    if (existing) {\n      await ctx.db.delete(\"analyticsStats\", existing._id);\n    }\n\n    const existingDaily = await ctx.db.query(\"analyticsDailyStats\").collect();\n    for (const d of existingDaily) {\n      await ctx.db.delete(\"analyticsDailyStats\", d._id);\n    }\n\n    const events = await ctx.db.query(\"analyticsEvents\").collect();\n\n    const emptyDist: Record<string, number> = {};\n    const stats = {\n      totalProjects: 0,\n      lastEventTime: 0,\n      backend: { ...emptyDist },\n      frontend: { ...emptyDist },\n      database: { ...emptyDist },\n      orm: { ...emptyDist },\n      api: { ...emptyDist },\n      auth: { ...emptyDist },\n      runtime: { ...emptyDist },\n      packageManager: { ...emptyDist },\n      platform: { ...emptyDist },\n      addons: { ...emptyDist },\n      examples: { ...emptyDist },\n      dbSetup: { ...emptyDist },\n      webDeploy: { ...emptyDist },\n      serverDeploy: { ...emptyDist },\n      payments: { ...emptyDist },\n      git: { ...emptyDist },\n      install: { ...emptyDist },\n      nodeVersion: { ...emptyDist },\n      cliVersion: { ...emptyDist },\n      hourlyDistribution: { ...emptyDist },\n      stackCombinations: { ...emptyDist },\n      dbOrmCombinations: { ...emptyDist },\n    };\n\n    const dailyCounts = new Map<string, number>();\n\n    for (const ev of events) {\n      stats.totalProjects++;\n      if (ev._creationTime > stats.lastEventTime) {\n        stats.lastEventTime = ev._creationTime;\n      }\n\n      const hourKey = String(new Date(ev._creationTime).getUTCHours()).padStart(2, \"0\");\n      const fe = ev.frontend?.[0] || \"none\";\n      const be = ev.backend || \"none\";\n      const stackKey = `${be} + ${fe}`;\n      const db = ev.database || \"none\";\n      const o = ev.orm || \"none\";\n      const dbOrmKey = `${db} + ${o}`;\n\n      stats.backend = incrementKey(stats.backend, ev.backend);\n      stats.frontend = incrementKeys(stats.frontend, ev.frontend);\n      stats.database = incrementKey(stats.database, ev.database);\n      stats.orm = incrementKey(stats.orm, ev.orm);\n      stats.api = incrementKey(stats.api, ev.api);\n      stats.auth = incrementKey(stats.auth, ev.auth);\n      stats.runtime = incrementKey(stats.runtime, ev.runtime);\n      stats.packageManager = incrementKey(stats.packageManager, ev.packageManager);\n      stats.platform = incrementKey(stats.platform, ev.platform);\n      stats.addons = incrementKeys(stats.addons, ev.addons);\n      stats.examples = incrementKeys(stats.examples, ev.examples);\n      stats.dbSetup = incrementKey(stats.dbSetup, ev.dbSetup);\n      stats.webDeploy = incrementKey(stats.webDeploy, ev.webDeploy);\n      stats.serverDeploy = incrementKey(stats.serverDeploy, ev.serverDeploy);\n      stats.payments = incrementKey(stats.payments, ev.payments);\n      stats.git = incrementBool(stats.git, ev.git);\n      stats.install = incrementBool(stats.install, ev.install);\n      stats.nodeVersion = incrementKey(stats.nodeVersion, getMajorVersion(ev.node_version));\n      stats.cliVersion = incrementKey(stats.cliVersion, ev.cli_version);\n      stats.hourlyDistribution = incrementKey(stats.hourlyDistribution, hourKey);\n      stats.stackCombinations = incrementKey(stats.stackCombinations, stackKey);\n      stats.dbOrmCombinations = incrementKey(stats.dbOrmCombinations, dbOrmKey);\n\n      const date = new Date(ev._creationTime).toISOString().slice(0, 10);\n      dailyCounts.set(date, (dailyCounts.get(date) || 0) + 1);\n    }\n\n    if (stats.totalProjects > 0) {\n      await ctx.db.insert(\"analyticsStats\", stats);\n    }\n\n    for (const [date, count] of dailyCounts) {\n      await ctx.db.insert(\"analyticsDailyStats\", { date, count });\n    }\n\n    return {\n      totalProcessed: stats.totalProjects,\n      dailyDates: dailyCounts.size,\n    };\n  },\n});\n"
  },
  {
    "path": "packages/backend/convex/analytics_date_utils.ts",
    "content": "export type DailyCount = {\n  date: string;\n  count: number;\n};\n\nconst MILLISECONDS_PER_DAY = 24 * 60 * 60 * 1000;\n\nfunction parseUtcDate(date: string): number | null {\n  const timestamp = Date.parse(`${date}T00:00:00Z`);\n  return Number.isNaN(timestamp) ? null : timestamp;\n}\n\nfunction formatUtcDate(timestamp: number): string {\n  return new Date(timestamp).toISOString().slice(0, 10);\n}\n\nexport function buildDailyWindow(\n  items: DailyCount[],\n  startDate: string,\n  endDate: string,\n): DailyCount[] {\n  const start = parseUtcDate(startDate);\n  const end = parseUtcDate(endDate);\n\n  if (start === null || end === null || end < start) {\n    return [...items].sort((a, b) => a.date.localeCompare(b.date));\n  }\n\n  const countsByDate = new Map<string, number>();\n  for (const item of items) {\n    countsByDate.set(item.date, (countsByDate.get(item.date) || 0) + item.count);\n  }\n\n  const result: DailyCount[] = [];\n  for (let timestamp = start; timestamp <= end; timestamp += MILLISECONDS_PER_DAY) {\n    const date = formatUtcDate(timestamp);\n    result.push({\n      date,\n      count: countsByDate.get(date) || 0,\n    });\n  }\n\n  return result;\n}\n"
  },
  {
    "path": "packages/backend/convex/convex.config.ts",
    "content": "import ossStats from \"@erquhart/convex-oss-stats/convex.config\";\nimport { defineApp } from \"convex/server\";\n\nconst app = defineApp();\napp.use(ossStats);\n\nexport default app;\n"
  },
  {
    "path": "packages/backend/convex/healthCheck.ts",
    "content": "import { query } from \"./_generated/server\";\n\nexport const get = query({\n  handler: async () => {\n    return \"OK\";\n  },\n});\n"
  },
  {
    "path": "packages/backend/convex/hooks.ts",
    "content": "import { makeUseQueryWithStatus } from \"convex-helpers/react\";\nimport { useQueries } from \"convex/react\";\n\nexport const useQueryWithStatus = makeUseQueryWithStatus(useQueries);\n"
  },
  {
    "path": "packages/backend/convex/http.ts",
    "content": "import { httpRouter } from \"convex/server\";\n\nimport { internal } from \"./_generated/api\";\nimport { httpAction } from \"./_generated/server\";\nimport { ossStats } from \"./stats\";\n\nconst http = httpRouter();\n\nhttp.route({\n  path: \"/api/analytics/ingest\",\n  method: \"POST\",\n  handler: httpAction(async (ctx, req) => {\n    const body = await req.json();\n    if (!body) {\n      return new Response(\"Bad Request\", { status: 400 });\n    }\n\n    const ingest = internal.analytics?.ingestEvent;\n    if (ingest) {\n      try {\n        await ctx.runMutation(ingest, {\n          database: body.database,\n          orm: body.orm,\n          backend: body.backend,\n          runtime: body.runtime,\n          frontend: body.frontend,\n          addons: body.addons,\n          examples: body.examples,\n          auth: body.auth,\n          payments: body.payments,\n          git: body.git,\n          packageManager: body.packageManager,\n          install: body.install,\n          dbSetup: body.dbSetup,\n          api: body.api,\n          webDeploy: body.webDeploy,\n          serverDeploy: body.serverDeploy,\n          cli_version: body.cli_version,\n          node_version: body.node_version,\n          platform: body.platform,\n        });\n      } catch (error) {\n        console.error(\"Failed to ingest analytics:\", error);\n        return new Response(\"Internal Server Error\", { status: 500 });\n      }\n    }\n    return new Response(\"ok\");\n  }),\n});\n\nossStats.registerRoutes(http);\nexport default http;\n"
  },
  {
    "path": "packages/backend/convex/schema.ts",
    "content": "import { defineSchema, defineTable } from \"convex/server\";\nimport { v } from \"convex/values\";\n\nconst distributionValidator = v.record(v.string(), v.number());\n\nexport default defineSchema({\n  videos: defineTable({\n    embedId: v.string(),\n    title: v.string(),\n  }),\n\n  tweets: defineTable({\n    tweetId: v.string(),\n    order: v.optional(v.number()),\n  }),\n\n  showcase: defineTable({\n    title: v.string(),\n    description: v.string(),\n    imageUrl: v.string(),\n    liveUrl: v.string(),\n    tags: v.array(v.string()),\n  }),\n\n  analyticsEvents: defineTable({\n    database: v.optional(v.string()),\n    orm: v.optional(v.string()),\n    backend: v.optional(v.string()),\n    runtime: v.optional(v.string()),\n    frontend: v.optional(v.array(v.string())),\n    addons: v.optional(v.array(v.string())),\n    examples: v.optional(v.array(v.string())),\n    auth: v.optional(v.string()),\n    payments: v.optional(v.string()),\n    git: v.optional(v.boolean()),\n    packageManager: v.optional(v.string()),\n    install: v.optional(v.boolean()),\n    dbSetup: v.optional(v.string()),\n    api: v.optional(v.string()),\n    webDeploy: v.optional(v.string()),\n    serverDeploy: v.optional(v.string()),\n    cli_version: v.optional(v.string()),\n    node_version: v.optional(v.string()),\n    platform: v.optional(v.string()),\n  }),\n\n  analyticsStats: defineTable({\n    totalProjects: v.number(),\n    lastEventTime: v.number(),\n    backend: distributionValidator,\n    frontend: distributionValidator,\n    database: distributionValidator,\n    orm: distributionValidator,\n    api: distributionValidator,\n    auth: distributionValidator,\n    runtime: distributionValidator,\n    packageManager: distributionValidator,\n    platform: distributionValidator,\n    addons: distributionValidator,\n    examples: distributionValidator,\n    dbSetup: distributionValidator,\n    webDeploy: distributionValidator,\n    serverDeploy: distributionValidator,\n    payments: distributionValidator,\n    git: distributionValidator,\n    install: distributionValidator,\n    nodeVersion: distributionValidator,\n    cliVersion: distributionValidator,\n    hourlyDistribution: v.optional(distributionValidator),\n    stackCombinations: v.optional(distributionValidator),\n    dbOrmCombinations: v.optional(distributionValidator),\n  }),\n\n  analyticsDailyStats: defineTable({\n    date: v.string(),\n    count: v.number(),\n  }).index(\"by_date\", [\"date\"]),\n});\n"
  },
  {
    "path": "packages/backend/convex/showcase.ts",
    "content": "import { v } from \"convex/values\";\n\nimport { query } from \"./_generated/server\";\n\nexport const getShowcaseProjects = query({\n  args: {},\n  returns: v.array(\n    v.object({\n      _id: v.id(\"showcase\"),\n      _creationTime: v.number(),\n      title: v.string(),\n      description: v.string(),\n      imageUrl: v.string(),\n      liveUrl: v.string(),\n      tags: v.array(v.string()),\n    }),\n  ),\n  handler: async (ctx) => {\n    return await ctx.db.query(\"showcase\").collect();\n  },\n});\n"
  },
  {
    "path": "packages/backend/convex/stats.ts",
    "content": "import { OssStats } from \"@erquhart/convex-oss-stats\";\n\nimport { components } from \"./_generated/api\";\n\nexport const ossStats = new OssStats(components.ossStats, {\n  githubOwners: [\"AmanVarshney01\"],\n  githubRepos: [\"AmanVarshney01/create-better-t-stack\"],\n  npmPackages: [\"create-better-t-stack\"],\n});\n\nexport const {\n  sync,\n  clearAndSync,\n  getGithubOwner,\n  getNpmOrg,\n  getGithubRepo,\n  getGithubRepos,\n  getNpmPackage,\n  getNpmPackages,\n} = ossStats.api();\n"
  },
  {
    "path": "packages/backend/convex/testimonials.ts",
    "content": "import { v } from \"convex/values\";\n\nimport { query } from \"./_generated/server\";\n\nexport const getVideos = query({\n  args: {},\n  returns: v.array(\n    v.object({\n      _id: v.id(\"videos\"),\n      _creationTime: v.number(),\n      embedId: v.string(),\n      title: v.string(),\n    }),\n  ),\n  handler: async (ctx) => {\n    return await ctx.db.query(\"videos\").collect();\n  },\n});\n\nexport const getTweets = query({\n  args: {},\n  returns: v.array(\n    v.object({\n      _id: v.id(\"tweets\"),\n      _creationTime: v.number(),\n      tweetId: v.string(),\n      order: v.optional(v.number()),\n    }),\n  ),\n  handler: async (ctx) => {\n    const rows = await ctx.db.query(\"tweets\").collect();\n    return rows.sort((a, b) => {\n      const aHas = typeof a.order === \"number\";\n      const bHas = typeof b.order === \"number\";\n      if (aHas && bHas) return (a.order as number) - (b.order as number);\n      if (aHas && !bHas) return -1;\n      if (!aHas && bHas) return 1;\n      return b._creationTime - a._creationTime;\n    });\n  },\n});\n"
  },
  {
    "path": "packages/backend/convex/tsconfig.json",
    "content": "{\n  /* This TypeScript project config describes the environment that\n   * Convex functions run in and is used to typecheck them.\n   * You can modify it, but some settings are required to use Convex.\n   */\n  \"compilerOptions\": {\n    /* These settings are not required by Convex and can be modified. */\n    \"allowJs\": true,\n    \"strict\": true,\n    \"moduleResolution\": \"Bundler\",\n    \"jsx\": \"react-jsx\",\n    \"skipLibCheck\": true,\n    \"allowSyntheticDefaultImports\": true,\n\n    /* These compiler options are required by Convex */\n    \"target\": \"ESNext\",\n    \"lib\": [\"ES2021\", \"dom\"],\n    \"forceConsistentCasingInFileNames\": true,\n    \"module\": \"ESNext\",\n    \"isolatedModules\": true,\n    \"noEmit\": true\n  },\n  \"include\": [\"./**/*\"],\n  \"exclude\": [\"./_generated\"]\n}\n"
  },
  {
    "path": "packages/backend/package.json",
    "content": "{\n  \"name\": \"@better-t-stack/backend\",\n  \"version\": \"1.0.0\",\n  \"private\": true,\n  \"description\": \"\",\n  \"license\": \"ISC\",\n  \"author\": \"\",\n  \"scripts\": {\n    \"dev\": \"convex dev\",\n    \"dev:setup\": \"convex dev --configure --until-success\",\n    \"deploy\": \"convex deploy\"\n  },\n  \"dependencies\": {\n    \"@convex-dev/crons\": \"^0.2.0\",\n    \"@erquhart/convex-oss-stats\": \"catalog:\",\n    \"convex\": \"catalog:\",\n    \"convex-helpers\": \"catalog:\"\n  },\n  \"devDependencies\": {\n    \"typescript\": \"catalog:\"\n  }\n}\n"
  },
  {
    "path": "packages/create-bts/.gitignore",
    "content": "node_modules\n\n"
  },
  {
    "path": "packages/create-bts/README.md",
    "content": "# create-bts\n\nThis is an alias package for [`create-better-t-stack`](https://www.npmjs.com/package/create-better-t-stack).\n\n## Usage\n\n```bash\nnpx create-bts@latest\n```\n\nor\n\n```bash\nbun create bts\n```\n\nFor full documentation, please visit [better-t-stack.dev](https://better-t-stack.dev/).\n\n## About\n\n`create-bts` is a shorter alias for the full `create-better-t-stack` command. Both packages provide the same functionality - a modern CLI tool for scaffolding end-to-end type-safe TypeScript projects.\n\nAll functionality is provided by the main [`create-better-t-stack`](https://www.npmjs.com/package/create-better-t-stack) package.\n"
  },
  {
    "path": "packages/create-bts/cli.js",
    "content": "#!/usr/bin/env node\nimport(\"create-better-t-stack/cli\");\n"
  },
  {
    "path": "packages/create-bts/index.d.ts",
    "content": "export * from \"create-better-t-stack\";\n"
  },
  {
    "path": "packages/create-bts/index.js",
    "content": "export * from \"create-better-t-stack\";\n"
  },
  {
    "path": "packages/create-bts/package.json",
    "content": "{\n  \"name\": \"create-bts\",\n  \"version\": \"3.28.0\",\n  \"description\": \"A modern CLI tool for scaffolding end-to-end type-safe TypeScript projects with best practices and customizable configurations (alias for create-better-t-stack)\",\n  \"keywords\": [\n    \"better-auth\",\n    \"better-t-stack\",\n    \"biome\",\n    \"boilerplate\",\n    \"bts\",\n    \"cli\",\n    \"drizzle\",\n    \"elysia\",\n    \"expo\",\n    \"fullstack\",\n    \"hono\",\n    \"monorepo\",\n    \"prisma\",\n    \"pwa\",\n    \"react\",\n    \"react-native\",\n    \"shadcn\",\n    \"starter\",\n    \"tailwind\",\n    \"tanstack\",\n    \"tauri\",\n    \"trpc\",\n    \"turborepo\",\n    \"type-safety\",\n    \"typescript\"\n  ],\n  \"homepage\": \"https://better-t-stack.dev/\",\n  \"license\": \"MIT\",\n  \"author\": \"Aman Varshney\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/AmanVarshney01/create-better-t-stack.git\",\n    \"directory\": \"packages/create-bts\"\n  },\n  \"bin\": {\n    \"create-bts\": \"cli.js\"\n  },\n  \"files\": [\n    \"cli.js\",\n    \"index.d.ts\",\n    \"index.js\"\n  ],\n  \"type\": \"module\",\n  \"main\": \"./index.js\",\n  \"module\": \"./index.js\",\n  \"types\": \"./index.d.ts\",\n  \"exports\": {\n    \".\": {\n      \"types\": \"./index.d.ts\",\n      \"import\": \"./index.js\"\n    },\n    \"./cli\": {\n      \"import\": \"./cli.js\"\n    }\n  },\n  \"publishConfig\": {\n    \"access\": \"public\"\n  },\n  \"dependencies\": {\n    \"create-better-t-stack\": \"^3.28.0\"\n  }\n}\n"
  },
  {
    "path": "packages/template-generator/package.json",
    "content": "{\n  \"name\": \"@better-t-stack/template-generator\",\n  \"version\": \"3.28.0\",\n  \"description\": \"Virtual file system generator for Better-T-Stack templates - works in browsers and Node.js\",\n  \"keywords\": [\n    \"better-t-stack\",\n    \"cli\",\n    \"template-generator\",\n    \"typescript\",\n    \"virtual-fs\"\n  ],\n  \"homepage\": \"https://better-t-stack.dev/\",\n  \"license\": \"MIT\",\n  \"author\": \"Aman Varshney\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/AmanVarshney01/create-better-t-stack.git\",\n    \"directory\": \"packages/template-generator\"\n  },\n  \"files\": [\n    \"dist\",\n    \"templates-binary\"\n  ],\n  \"type\": \"module\",\n  \"exports\": {\n    \".\": {\n      \"types\": \"./dist/index.d.mts\",\n      \"default\": \"./dist/index.mjs\"\n    },\n    \"./fs-writer\": {\n      \"types\": \"./dist/fs-writer.d.mts\",\n      \"default\": \"./dist/fs-writer.mjs\"\n    },\n    \"./template-reader\": {\n      \"types\": \"./dist/template-reader.d.mts\",\n      \"default\": \"./dist/template-reader.mjs\"\n    }\n  },\n  \"publishConfig\": {\n    \"access\": \"public\"\n  },\n  \"scripts\": {\n    \"generate-templates\": \"bun scripts/generate-templates.ts\",\n    \"prebuild\": \"bun run generate-templates\",\n    \"build\": \"tsdown\",\n    \"prepublishOnly\": \"bun run build\",\n    \"dev\": \"tsdown --watch\",\n    \"typecheck\": \"tsc --noEmit\"\n  },\n  \"dependencies\": {\n    \"@better-t-stack/types\": \"^3.28.0\",\n    \"better-result\": \"^2.8.2\",\n    \"handlebars\": \"^4.7.9\",\n    \"memfs\": \"^4.57.2\",\n    \"pathe\": \"^2.0.3\",\n    \"ts-morph\": \"^28.0.0\",\n    \"yaml\": \"^2.8.3\"\n  },\n  \"devDependencies\": {\n    \"@types/fs-extra\": \"^11.0.4\",\n    \"@types/node\": \"^25.6.0\",\n    \"fs-extra\": \"^11.3.4\",\n    \"is-binary-path\": \"^3.0.0\",\n    \"tinyglobby\": \"^0.2.15\",\n    \"tsdown\": \"^0.21.9\",\n    \"typescript\": \"^6.0.3\"\n  }\n}\n"
  },
  {
    "path": "packages/template-generator/scripts/generate-templates.ts",
    "content": "import fs from \"node:fs\";\nimport path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\n\nimport isBinaryPath from \"is-binary-path\";\nimport { glob } from \"tinyglobby\";\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = path.dirname(__filename);\n\nconst TEMPLATES_DIR = path.join(__dirname, \"../templates\");\nconst OUTPUT_FILE = path.join(__dirname, \"../src/templates.generated.ts\");\nconst BINARY_OUTPUT_DIR = path.join(__dirname, \"../templates-binary\");\n\nasync function generateTemplates() {\n  console.log(\"📦 Generating embedded templates...\");\n\n  const files = await glob(\"**/*\", { cwd: TEMPLATES_DIR, dot: true, onlyFiles: true });\n\n  // Sort files alphabetically for deterministic output (minimizes git diffs)\n  files.sort((a, b) => a.localeCompare(b));\n\n  console.log(`📂 Found ${files.length} template files`);\n\n  const entries: string[] = [];\n  const binaryFiles: string[] = [];\n\n  for (const file of files) {\n    const fullPath = path.join(TEMPLATES_DIR, file);\n    const normalizedPath = file.replace(/\\\\/g, \"/\");\n\n    if (isBinaryPath(file)) {\n      binaryFiles.push(normalizedPath);\n      entries.push(`  [\"${normalizedPath}\", \\`[Binary file]\\`]`);\n    } else {\n      const content = fs.readFileSync(fullPath, \"utf-8\");\n      const escapedContent = content\n        .replace(/\\\\/g, \"\\\\\\\\\")\n        .replace(/`/g, \"\\\\`\")\n        .replace(/\\$\\{/g, \"\\\\${\");\n      entries.push(`  [\"${normalizedPath}\", \\`${escapedContent}\\`]`);\n    }\n  }\n\n  const output = `// Auto-generated - DO NOT EDIT\n// Run 'bun run generate-templates' to regenerate\n\nexport const EMBEDDED_TEMPLATES: Map<string, string> = new Map([\n${entries.join(\",\\n\")}\n]);\n\nexport const TEMPLATE_COUNT = ${files.length};\n`;\n\n  fs.writeFileSync(OUTPUT_FILE, output);\n\n  const stats = fs.statSync(OUTPUT_FILE);\n  const sizeMB = (stats.size / (1024 * 1024)).toFixed(2);\n\n  console.log(`✅ Generated ${OUTPUT_FILE}`);\n  console.log(`📊 File size: ${sizeMB} MB (${files.length} templates)`);\n\n  await copyBinaryFiles(binaryFiles);\n}\n\nasync function copyBinaryFiles(binaryFiles: string[]) {\n  // Sort for deterministic output\n  binaryFiles.sort((a, b) => a.localeCompare(b));\n\n  console.log(`\\n📁 Copying ${binaryFiles.length} binary files to templates-binary/...`);\n\n  if (fs.existsSync(BINARY_OUTPUT_DIR)) {\n    fs.rmSync(BINARY_OUTPUT_DIR, { recursive: true });\n  }\n\n  let totalSize = 0;\n\n  for (const file of binaryFiles) {\n    const srcPath = path.join(TEMPLATES_DIR, file);\n    const destPath = path.join(BINARY_OUTPUT_DIR, file);\n\n    fs.mkdirSync(path.dirname(destPath), { recursive: true });\n\n    fs.copyFileSync(srcPath, destPath);\n\n    totalSize += fs.statSync(destPath).size;\n  }\n\n  const sizeKB = (totalSize / 1024).toFixed(2);\n  console.log(`✅ Copied ${binaryFiles.length} binary files (${sizeKB} KB)`);\n}\n\ngenerateTemplates().catch((err) => {\n  console.error(\"❌ Failed to generate templates:\", err);\n  process.exit(1);\n});\n"
  },
  {
    "path": "packages/template-generator/src/bts-config.ts",
    "content": "import type { BetterTStackConfig, ProjectConfig } from \"@better-t-stack/types\";\n\nimport type { VirtualFileSystem } from \"./core/virtual-fs\";\n\nconst BTS_CONFIG_FILE = \"bts.jsonc\";\n\n/**\n * Writes the BTS configuration file to the VFS (for new project creation).\n * This is browser-safe as it only writes to VFS, not the real filesystem.\n */\nexport function writeBtsConfigToVfs(\n  vfs: VirtualFileSystem,\n  projectConfig: ProjectConfig,\n  version: string,\n  reproducibleCommand?: string,\n): void {\n  const btsConfig: BetterTStackConfig = {\n    version,\n    createdAt: new Date().toISOString(),\n    reproducibleCommand,\n    addonOptions: projectConfig.addonOptions,\n    dbSetupOptions: projectConfig.dbSetupOptions,\n    database: projectConfig.database,\n    orm: projectConfig.orm,\n    backend: projectConfig.backend,\n    runtime: projectConfig.runtime,\n    frontend: projectConfig.frontend,\n    addons: projectConfig.addons,\n    examples: projectConfig.examples,\n    auth: projectConfig.auth,\n    payments: projectConfig.payments,\n    packageManager: projectConfig.packageManager,\n    dbSetup: projectConfig.dbSetup,\n    api: projectConfig.api,\n    webDeploy: projectConfig.webDeploy,\n    serverDeploy: projectConfig.serverDeploy,\n  };\n\n  const baseContent = {\n    $schema: \"https://r2.better-t-stack.dev/schema.json\",\n    ...btsConfig,\n  };\n\n  const jsonContent = JSON.stringify(baseContent, null, 2);\n\n  const addCommand =\n    projectConfig.packageManager === \"npm\"\n      ? \"npx create-better-t-stack add\"\n      : projectConfig.packageManager === \"pnpm\"\n        ? \"pnpm dlx create-better-t-stack add\"\n        : \"bun create better-t-stack add\";\n\n  const finalContent = `// Better-T-Stack\n//\n// Website: https://www.better-t-stack.dev/\n// Stack Builder: https://www.better-t-stack.dev/new\n// Analytics: https://www.better-t-stack.dev/analytics\n// Showcase: https://www.better-t-stack.dev/showcase\n// Sponsor: https://github.com/sponsors/AmanVarshney01\n//\n// Add new addons with: ${addCommand}\n// This file is safe to delete\n\n${jsonContent}`;\n\n  vfs.writeFile(BTS_CONFIG_FILE, finalContent);\n}\n"
  },
  {
    "path": "packages/template-generator/src/core/template-processor.ts",
    "content": "import type { ProjectConfig } from \"@better-t-stack/types\";\nimport Handlebars from \"handlebars\";\nimport isBinaryPath from \"is-binary-path\";\n\nHandlebars.registerHelper(\"eq\", (a, b) => a === b);\nHandlebars.registerHelper(\"ne\", (a, b) => a !== b);\nHandlebars.registerHelper(\"and\", (...args) => args.slice(0, -1).every(Boolean));\nHandlebars.registerHelper(\"or\", (...args) => args.slice(0, -1).some(Boolean));\nHandlebars.registerHelper(\"includes\", (arr, val) => Array.isArray(arr) && arr.includes(val));\n\nexport function processTemplateString(content: string, context: ProjectConfig): string {\n  return Handlebars.compile(content)(context);\n}\n\nexport function isBinaryFile(filePath: string): boolean {\n  return isBinaryPath(filePath);\n}\n\nexport function transformFilename(filename: string): string {\n  let result = filename.endsWith(\".hbs\") ? filename.slice(0, -4) : filename;\n\n  const basename = result.split(\"/\").pop() || result;\n  if (basename === \"_gitignore\") result = result.replace(/_gitignore$/, \".gitignore\");\n  else if (basename === \"_npmrc\") result = result.replace(/_npmrc$/, \".npmrc\");\n\n  return result;\n}\n\nexport function processFileContent(\n  filePath: string,\n  content: string,\n  context: ProjectConfig,\n): string {\n  if (isBinaryFile(filePath)) return \"[Binary file]\";\n\n  const originalPath = filePath.endsWith(\".hbs\") ? filePath : filePath + \".hbs\";\n  if (filePath !== originalPath || filePath.includes(\".hbs\")) {\n    try {\n      return processTemplateString(content, context);\n    } catch (error) {\n      console.warn(`Template processing failed for ${filePath}:`, error);\n      return content;\n    }\n  }\n\n  return content;\n}\n\nexport { Handlebars };\n"
  },
  {
    "path": "packages/template-generator/src/core/template-reader.ts",
    "content": "import fs from \"node:fs\";\nimport { fileURLToPath } from \"node:url\";\n\nimport isBinaryPath from \"is-binary-path\";\nimport { dirname, join } from \"pathe\";\nimport { glob } from \"tinyglobby\";\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = dirname(__filename);\n\nexport function getTemplatesRoot(): string {\n  const possiblePaths = [\n    join(__dirname, \"../templates\"),\n    join(__dirname, \"../../templates\"),\n    join(__dirname, \"../../../templates\"),\n  ];\n\n  for (const p of possiblePaths) {\n    if (fs.existsSync(p)) return p;\n  }\n\n  throw new Error(\"Templates directory not found. Checked: \" + possiblePaths.join(\", \"));\n}\n\nexport function getBinaryTemplatesRoot(): string {\n  const possiblePaths = [\n    join(__dirname, \"../templates-binary\"),\n    join(__dirname, \"../../templates-binary\"),\n    join(__dirname, \"../../../templates-binary\"),\n  ];\n\n  for (const p of possiblePaths) {\n    if (fs.existsSync(p)) return p;\n  }\n\n  throw new Error(\"Binary templates directory not found. Checked: \" + possiblePaths.join(\", \"));\n}\n\nexport async function loadTemplates(prefix?: string): Promise<Map<string, string>> {\n  const templatesRoot = getTemplatesRoot();\n  const searchDir = prefix ? join(templatesRoot, prefix) : templatesRoot;\n\n  if (!fs.existsSync(searchDir)) return new Map();\n\n  const files = await glob(\"**/*\", { cwd: searchDir, dot: true, onlyFiles: true });\n  const templates = new Map<string, string>();\n\n  for (const file of files) {\n    const fullPath = join(searchDir, file);\n    const relativePath = prefix ? `${prefix}/${file}` : file;\n\n    try {\n      if (isBinaryPath(file)) {\n        templates.set(relativePath, \"[Binary file]\");\n      } else {\n        templates.set(relativePath, fs.readFileSync(fullPath, \"utf-8\"));\n      }\n    } catch (error) {\n      console.warn(`Failed to read template: ${relativePath}`, error);\n    }\n  }\n\n  return templates;\n}\n\nexport function loadTemplate(relativePath: string): string | undefined {\n  const fullPath = join(getTemplatesRoot(), relativePath);\n  if (!fs.existsSync(fullPath)) return undefined;\n  if (isBinaryPath(relativePath)) return \"[Binary file]\";\n  return fs.readFileSync(fullPath, \"utf-8\");\n}\n\nexport async function listTemplates(prefix?: string): Promise<string[]> {\n  const templatesRoot = getTemplatesRoot();\n  const searchDir = prefix ? join(templatesRoot, prefix) : templatesRoot;\n\n  if (!fs.existsSync(searchDir)) return [];\n\n  const files = await glob(\"**/*\", { cwd: searchDir, dot: true, onlyFiles: true });\n  return prefix ? files.map((f: string) => `${prefix}/${f}`) : files;\n}\n\nexport { isBinaryPath, getTemplatesRoot as TEMPLATES_ROOT };\n"
  },
  {
    "path": "packages/template-generator/src/core/virtual-fs.ts",
    "content": "import type { Dirent } from \"node:fs\";\n\nimport { memfs } from \"memfs\";\nimport { dirname, extname, normalize, join } from \"pathe\";\n\nimport type { VirtualDirectory, VirtualFile } from \"../types\";\n\nexport class VirtualFileSystem {\n  private _fs: ReturnType<typeof memfs>[\"fs\"];\n  private _vol: ReturnType<typeof memfs>[\"vol\"];\n  private _sourcePathMap: Map<string, string> = new Map();\n\n  constructor() {\n    const { fs, vol } = memfs();\n    this._fs = fs;\n    this._vol = vol;\n  }\n\n  writeFile(filePath: string, content: string, sourcePath?: string): void {\n    const path = this.normalizePath(filePath);\n    const dir = dirname(path);\n    if (dir && dir !== \"/\" && dir !== \".\") {\n      this._fs.mkdirSync(dir, { recursive: true });\n    }\n    this._fs.writeFileSync(path, content, { encoding: \"utf-8\" });\n    if (sourcePath) {\n      this._sourcePathMap.set(path, sourcePath);\n    }\n  }\n\n  readFile(filePath: string): string | undefined {\n    try {\n      return this._fs.readFileSync(this.normalizePath(filePath), \"utf-8\") as string;\n    } catch {\n      return undefined;\n    }\n  }\n\n  exists(path: string): boolean {\n    try {\n      this._fs.statSync(this.normalizePath(path));\n      return true;\n    } catch {\n      return false;\n    }\n  }\n\n  fileExists(filePath: string): boolean {\n    try {\n      return this._fs.statSync(this.normalizePath(filePath)).isFile();\n    } catch {\n      return false;\n    }\n  }\n\n  directoryExists(dirPath: string): boolean {\n    try {\n      return this._fs.statSync(this.normalizePath(dirPath)).isDirectory();\n    } catch {\n      return false;\n    }\n  }\n\n  mkdir(dirPath: string): void {\n    this._fs.mkdirSync(this.normalizePath(dirPath), { recursive: true });\n  }\n\n  deleteFile(filePath: string): boolean {\n    try {\n      this._fs.unlinkSync(this.normalizePath(filePath));\n      return true;\n    } catch {\n      return false;\n    }\n  }\n\n  listDir(dirPath: string): string[] {\n    try {\n      return (this._fs.readdirSync(this.normalizePath(dirPath) || \"/\") as string[]).sort();\n    } catch {\n      return [];\n    }\n  }\n\n  readJson<T = unknown>(filePath: string): T | undefined {\n    const content = this.readFile(filePath);\n    if (!content) return undefined;\n    try {\n      return JSON.parse(content) as T;\n    } catch {\n      return undefined;\n    }\n  }\n\n  writeJson(filePath: string, data: unknown, spaces = 2): void {\n    this.writeFile(filePath, JSON.stringify(data, null, spaces));\n  }\n\n  getAllFiles(): string[] {\n    const files: string[] = [];\n    this.walkDir(\"/\", files, true);\n    return files.sort();\n  }\n\n  getAllDirectories(): string[] {\n    const dirs: string[] = [];\n    this.walkDir(\"/\", dirs, false);\n    return dirs.filter((d) => d !== \"/\").sort();\n  }\n\n  getFileCount(): number {\n    return this.getAllFiles().length;\n  }\n\n  getDirectoryCount(): number {\n    return this.getAllDirectories().length;\n  }\n\n  toTree(rootName = \"project\"): VirtualDirectory {\n    const root: VirtualDirectory = { type: \"directory\", path: \"\", name: rootName, children: [] };\n    this.buildTree(\"/\", root);\n    this.sortChildren(root);\n    return root;\n  }\n\n  clear(): void {\n    const { fs, vol } = memfs();\n    this._fs = fs;\n    this._vol = vol;\n    this._sourcePathMap.clear();\n  }\n\n  getVolume() {\n    return this._vol;\n  }\n  getFs() {\n    return this._fs;\n  }\n\n  private walkDir(dir: string, results: string[], filesOnly: boolean): void {\n    try {\n      for (const entry of this._fs.readdirSync(dir, { withFileTypes: true }) as Dirent[]) {\n        const fullPath = join(dir, entry.name);\n        const relativePath = fullPath.replace(/^\\//, \"\");\n        if (entry.isDirectory()) {\n          if (!filesOnly) results.push(relativePath);\n          this.walkDir(fullPath, results, filesOnly);\n        } else if (entry.isFile() && filesOnly) {\n          results.push(relativePath);\n        }\n      }\n    } catch {}\n  }\n\n  private buildTree(dir: string, parent: VirtualDirectory): void {\n    try {\n      for (const entry of this._fs.readdirSync(dir, { withFileTypes: true }) as Dirent[]) {\n        const fullPath = join(dir, entry.name);\n        const relativePath = fullPath.replace(/^\\//, \"\");\n        if (entry.isDirectory()) {\n          const dirNode: VirtualDirectory = {\n            type: \"directory\",\n            path: relativePath,\n            name: entry.name,\n            children: [],\n          };\n          parent.children.push(dirNode);\n          this.buildTree(fullPath, dirNode);\n        } else if (entry.isFile()) {\n          const content = this._fs.readFileSync(fullPath, \"utf-8\") as string;\n          const sourcePath = this._sourcePathMap.get(fullPath);\n          const fileNode: VirtualFile = {\n            type: \"file\",\n            path: relativePath,\n            name: entry.name,\n            content,\n            extension: extname(entry.name).slice(1),\n          };\n          if (sourcePath) {\n            fileNode.sourcePath = sourcePath;\n          }\n          parent.children.push(fileNode);\n        }\n      }\n    } catch {}\n  }\n\n  private sortChildren(node: VirtualDirectory): void {\n    node.children.sort((a, b) => {\n      if (a.type === \"directory\" && b.type === \"file\") return -1;\n      if (a.type === \"file\" && b.type === \"directory\") return 1;\n      return a.name.localeCompare(b.name);\n    });\n    for (const child of node.children) {\n      if (child.type === \"directory\") this.sortChildren(child);\n    }\n  }\n\n  private normalizePath(p: string): string {\n    return \"/\" + normalize(p).replace(/^\\/+/, \"\");\n  }\n}\n"
  },
  {
    "path": "packages/template-generator/src/fs-writer.ts",
    "content": "import * as fs from \"node:fs/promises\";\n\nimport { Result, TaggedError } from \"better-result\";\nimport { join, dirname } from \"pathe\";\n\nimport { getBinaryTemplatesRoot } from \"./core/template-reader\";\nimport type { VirtualFileTree, VirtualNode, VirtualFile, VirtualDirectory } from \"./types\";\n\nconst BINARY_FILE_MARKER = \"[Binary file]\";\n\n/**\n * Error class for filesystem write failures\n */\nexport class FileWriteError extends TaggedError(\"FileWriteError\")<{\n  message: string;\n  path?: string;\n  cause?: unknown;\n}>() {}\n\n/**\n * Writes a virtual file tree to the filesystem.\n * Returns a Result type for type-safe error handling.\n */\nexport async function writeTree(\n  tree: VirtualFileTree,\n  destDir: string,\n): Promise<Result<void, FileWriteError>> {\n  return Result.tryPromise({\n    try: async () => {\n      for (const child of tree.root.children) {\n        await writeNodeInternal(child, destDir, \"\");\n      }\n    },\n    catch: (e) => {\n      if (FileWriteError.is(e)) return e;\n      return new FileWriteError({\n        message: e instanceof Error ? e.message : String(e),\n        cause: e,\n      });\n    },\n  });\n}\n\nasync function writeNodeInternal(\n  node: VirtualNode,\n  baseDir: string,\n  relativePath: string,\n): Promise<void> {\n  const fullPath = join(baseDir, relativePath, node.name);\n  const nodePath = relativePath ? join(relativePath, node.name) : node.name;\n\n  if (node.type === \"file\") {\n    const fileNode = node as VirtualFile;\n    await fs.mkdir(dirname(fullPath), { recursive: true });\n\n    if (fileNode.content === BINARY_FILE_MARKER && fileNode.sourcePath) {\n      await copyBinaryFile(fileNode.sourcePath, fullPath);\n    } else if (fileNode.content !== BINARY_FILE_MARKER) {\n      await fs.writeFile(fullPath, fileNode.content, \"utf-8\");\n    }\n  } else {\n    await fs.mkdir(fullPath, { recursive: true });\n    for (const child of (node as VirtualDirectory).children) {\n      await writeNodeInternal(child, baseDir, nodePath);\n    }\n  }\n}\n\n/**\n * Writes selected files from a virtual file tree to the filesystem.\n * Returns a Result with the list of written file paths.\n */\nexport async function writeSelected(\n  tree: VirtualFileTree,\n  destDir: string,\n  filter: (filePath: string) => boolean,\n): Promise<Result<string[], FileWriteError>> {\n  return Result.tryPromise({\n    try: async () => {\n      const writtenFiles: string[] = [];\n      await writeSelectedNodeInternal(tree.root, destDir, \"\", filter, writtenFiles);\n      return writtenFiles;\n    },\n    catch: (e) => {\n      if (FileWriteError.is(e)) return e;\n      return new FileWriteError({\n        message: e instanceof Error ? e.message : String(e),\n        cause: e,\n      });\n    },\n  });\n}\n\nasync function writeSelectedNodeInternal(\n  node: VirtualNode,\n  baseDir: string,\n  relativePath: string,\n  filter: (filePath: string) => boolean,\n  writtenFiles: string[],\n): Promise<void> {\n  const nodePath = relativePath ? `${relativePath}/${node.name}` : node.name;\n\n  if (node.type === \"file\") {\n    if (filter(nodePath)) {\n      const fileNode = node as VirtualFile;\n      await fs.mkdir(dirname(join(baseDir, nodePath)), { recursive: true });\n\n      if (fileNode.content === BINARY_FILE_MARKER && fileNode.sourcePath) {\n        await copyBinaryFile(fileNode.sourcePath, join(baseDir, nodePath));\n      } else if (fileNode.content !== BINARY_FILE_MARKER) {\n        await fs.writeFile(join(baseDir, nodePath), fileNode.content, \"utf-8\");\n      }\n      writtenFiles.push(nodePath);\n    }\n  } else {\n    for (const child of (node as VirtualDirectory).children) {\n      await writeSelectedNodeInternal(child, baseDir, nodePath, filter, writtenFiles);\n    }\n  }\n}\n\nasync function copyBinaryFile(templatePath: string, destPath: string): Promise<void> {\n  const templatesRoot = getBinaryTemplatesRoot();\n  const sourcePath = join(templatesRoot, templatePath);\n  // Let errors propagate - they'll be caught by the Result wrapper\n  await fs.copyFile(sourcePath, destPath);\n}\n"
  },
  {
    "path": "packages/template-generator/src/generator.ts",
    "content": "import { Result } from \"better-result\";\n\nimport { writeBtsConfigToVfs } from \"./bts-config\";\nimport { VirtualFileSystem } from \"./core/virtual-fs\";\nimport { processCatalogs, processPackageConfigs } from \"./post-process\";\nimport {\n  processDependencies,\n  processReadme,\n  processAuthPlugins,\n  processAlchemyPlugins,\n  processPwaPlugins,\n  processEnvVariables,\n} from \"./processors\";\nimport {\n  type TemplateData,\n  processBaseTemplate,\n  processFrontendTemplates,\n  processBackendTemplates,\n  processDbTemplates,\n  processApiTemplates,\n  processConfigPackage,\n  processEnvPackage,\n  processUiPackage,\n  processAuthTemplates,\n  processPaymentsTemplates,\n  processAddonTemplates,\n  processExampleTemplates,\n  processExtrasTemplates,\n  processDeployTemplates,\n} from \"./template-handlers\";\nimport type { GeneratorOptions, VirtualFileTree } from \"./types\";\nimport { GeneratorError } from \"./types\";\nimport { generateReproducibleCommand } from \"./utils/reproducible-command\";\n\nexport type { TemplateData };\n\n/**\n * Generates a virtual project file tree from templates and configuration.\n * Returns a Result type for type-safe error handling.\n *\n * @example\n * ```typescript\n * const result = await generate(options);\n * result.match({\n *   ok: (tree) => console.log(`Generated ${tree.fileCount} files`),\n *   err: (error) => console.error(`Failed: ${error.message}`),\n * });\n * ```\n */\nexport async function generate(\n  options: GeneratorOptions,\n): Promise<Result<VirtualFileTree, GeneratorError>> {\n  return Result.tryPromise({\n    try: async () => {\n      const { config, templates } = options;\n\n      if (!templates || templates.size === 0) {\n        throw new GeneratorError({\n          message: \"No templates provided. Templates must be passed via the templates option.\",\n          phase: \"initialization\",\n        });\n      }\n\n      const vfs = new VirtualFileSystem();\n\n      await processBaseTemplate(vfs, templates, config);\n      await processFrontendTemplates(vfs, templates, config);\n      await processBackendTemplates(vfs, templates, config);\n      await processDbTemplates(vfs, templates, config);\n      await processApiTemplates(vfs, templates, config);\n      await processConfigPackage(vfs, templates, config);\n      await processEnvPackage(vfs, templates, config);\n      await processUiPackage(vfs, templates, config);\n      await processAuthTemplates(vfs, templates, config);\n      await processPaymentsTemplates(vfs, templates, config);\n      await processAddonTemplates(vfs, templates, config);\n      await processExampleTemplates(vfs, templates, config);\n      await processExtrasTemplates(vfs, templates, config);\n      await processDeployTemplates(vfs, templates, config);\n\n      processPackageConfigs(vfs, config);\n      processDependencies(vfs, config);\n      processEnvVariables(vfs, config);\n      processAuthPlugins(vfs, config);\n      processAlchemyPlugins(vfs, config);\n      processPwaPlugins(vfs, config);\n      processCatalogs(vfs, config);\n      processReadme(vfs, config);\n\n      // Write bts.jsonc config file\n      if (options.version) {\n        const reproducibleCommand = generateReproducibleCommand(config);\n        writeBtsConfigToVfs(vfs, config, options.version, reproducibleCommand);\n      }\n\n      const tree: VirtualFileTree = {\n        root: vfs.toTree(config.projectName),\n        fileCount: vfs.getFileCount(),\n        directoryCount: vfs.getDirectoryCount(),\n        config,\n      };\n\n      return tree;\n    },\n    catch: (e) => {\n      if (GeneratorError.is(e)) {\n        return e;\n      }\n      return new GeneratorError({\n        message: e instanceof Error ? e.message : String(e),\n        phase: \"unknown\",\n        cause: e,\n      });\n    },\n  });\n}\n"
  },
  {
    "path": "packages/template-generator/src/index.ts",
    "content": "export * from \"./types\";\nexport * from \"./core/virtual-fs\";\nexport * from \"./core/template-processor\";\nexport * from \"./generator\";\nexport { processAddonTemplates } from \"./template-handlers/addons\";\nexport { processAddonsDeps } from \"./processors/addons-deps\";\nexport { writeBtsConfigToVfs } from \"./bts-config\";\n\nexport { EMBEDDED_TEMPLATES, TEMPLATE_COUNT } from \"./templates.generated\";\nexport { dependencyVersionMap, type AvailableDependencies } from \"./utils/add-deps\";\nexport { generateReproducibleCommand } from \"./utils/reproducible-command\";\n"
  },
  {
    "path": "packages/template-generator/src/post-process/catalogs.ts",
    "content": "/**\n * Catalogs post-processor\n * Deduplicates dependencies across packages using pnpm/bun catalogs\n */\n\nimport type { ProjectConfig } from \"@better-t-stack/types\";\nimport yaml from \"yaml\";\n\nimport type { VirtualFileSystem } from \"../core/virtual-fs\";\n\ntype PackageJson = {\n  name?: string;\n  scripts?: Record<string, string>;\n  dependencies?: Record<string, string>;\n  devDependencies?: Record<string, string>;\n  workspaces?: string[] | { packages?: string[]; catalog?: Record<string, string> };\n  packageManager?: string;\n  [key: string]: unknown;\n};\n\ntype CatalogEntry = {\n  versions: Set<string>;\n  packages: string[];\n};\n\ntype PackageInfo = {\n  path: string;\n  dependencies: Record<string, string>;\n  devDependencies: Record<string, string>;\n};\n\nconst PACKAGE_PATHS = [\n  \".\",\n  \"apps/server\",\n  \"apps/web\",\n  \"apps/native\",\n  \"apps/desktop\",\n  \"apps/fumadocs\",\n  \"apps/docs\",\n  \"packages/api\",\n  \"packages/db\",\n  \"packages/auth\",\n  \"packages/backend\",\n  \"packages/config\",\n  \"packages/env\",\n  \"packages/infra\",\n  \"packages/ui\",\n];\n\n/**\n * Process dependency catalogs for pnpm/bun\n */\nexport function processCatalogs(vfs: VirtualFileSystem, config: ProjectConfig): void {\n  if (config.packageManager === \"npm\") return;\n\n  const packagesInfo: PackageInfo[] = [];\n\n  for (const pkgPath of PACKAGE_PATHS) {\n    const jsonPath = pkgPath === \".\" ? \"package.json\" : `${pkgPath}/package.json`;\n    const pkgJson = vfs.readJson<PackageJson>(jsonPath);\n\n    if (pkgJson) {\n      packagesInfo.push({\n        path: pkgPath,\n        dependencies: (pkgJson.dependencies || {}) as Record<string, string>,\n        devDependencies: (pkgJson.devDependencies || {}) as Record<string, string>,\n      });\n    }\n  }\n\n  const catalog = findDuplicateDependencies(packagesInfo, config.projectName);\n\n  if (Object.keys(catalog).length === 0) return;\n\n  if (config.packageManager === \"bun\") {\n    setupBunCatalogs(vfs, catalog);\n  } else if (config.packageManager === \"pnpm\") {\n    setupPnpmCatalogs(vfs, catalog);\n  }\n\n  updatePackageJsonsWithCatalogs(vfs, packagesInfo, catalog);\n}\n\nfunction findDuplicateDependencies(\n  packagesInfo: PackageInfo[],\n  projectName: string,\n): Record<string, string> {\n  const depCount = new Map<string, CatalogEntry>();\n  const projectScope = `@${projectName}/`;\n\n  for (const pkg of packagesInfo) {\n    const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };\n\n    for (const [depName, version] of Object.entries(allDeps)) {\n      if (depName.startsWith(projectScope)) continue;\n      if (version.startsWith(\"workspace:\")) continue;\n\n      const existing = depCount.get(depName);\n      if (existing) {\n        existing.versions.add(version);\n        existing.packages.push(pkg.path);\n      } else {\n        depCount.set(depName, {\n          versions: new Set([version]),\n          packages: [pkg.path],\n        });\n      }\n    }\n  }\n\n  const catalog: Record<string, string> = {};\n  for (const [depName, info] of depCount.entries()) {\n    if (info.packages.length > 1 && info.versions.size === 1) {\n      const version = Array.from(info.versions)[0];\n      if (version) {\n        catalog[depName] = version;\n      }\n    }\n  }\n\n  return catalog;\n}\n\nfunction setupBunCatalogs(vfs: VirtualFileSystem, catalog: Record<string, string>): void {\n  const pkgJson = vfs.readJson<PackageJson>(\"package.json\");\n  if (!pkgJson) return;\n\n  if (!pkgJson.workspaces) {\n    pkgJson.workspaces = {};\n  }\n\n  if (Array.isArray(pkgJson.workspaces)) {\n    pkgJson.workspaces = {\n      packages: pkgJson.workspaces,\n      catalog,\n    };\n  } else if (typeof pkgJson.workspaces === \"object\") {\n    if (!pkgJson.workspaces.catalog) {\n      pkgJson.workspaces.catalog = {};\n    }\n    pkgJson.workspaces.catalog = {\n      ...pkgJson.workspaces.catalog,\n      ...catalog,\n    };\n  }\n\n  vfs.writeJson(\"package.json\", pkgJson);\n}\n\nfunction setupPnpmCatalogs(vfs: VirtualFileSystem, catalog: Record<string, string>): void {\n  let content = vfs.readFile(\"pnpm-workspace.yaml\");\n\n  // Create pnpm-workspace.yaml if it doesn't exist\n  if (!content) {\n    content = `packages:\n  - \"apps/*\"\n  - \"packages/*\"\n`;\n    vfs.writeFile(\"pnpm-workspace.yaml\", content);\n  }\n\n  const workspaceYaml = yaml.parse(content);\n\n  if (!workspaceYaml.catalog) {\n    workspaceYaml.catalog = {};\n  }\n\n  workspaceYaml.catalog = {\n    ...workspaceYaml.catalog,\n    ...catalog,\n  };\n\n  vfs.writeFile(\"pnpm-workspace.yaml\", yaml.stringify(workspaceYaml));\n}\n\nfunction updatePackageJsonsWithCatalogs(\n  vfs: VirtualFileSystem,\n  packagesInfo: PackageInfo[],\n  catalog: Record<string, string>,\n): void {\n  for (const pkg of packagesInfo) {\n    const jsonPath = pkg.path === \".\" ? \"package.json\" : `${pkg.path}/package.json`;\n    const pkgJson = vfs.readJson<PackageJson>(jsonPath);\n    if (!pkgJson) continue;\n\n    let updated = false;\n\n    if (pkgJson.dependencies) {\n      for (const depName of Object.keys(pkgJson.dependencies)) {\n        if (catalog[depName]) {\n          (pkgJson.dependencies as Record<string, string>)[depName] = \"catalog:\";\n          updated = true;\n        }\n      }\n    }\n\n    if (pkgJson.devDependencies) {\n      for (const depName of Object.keys(pkgJson.devDependencies)) {\n        if (catalog[depName]) {\n          (pkgJson.devDependencies as Record<string, string>)[depName] = \"catalog:\";\n          updated = true;\n        }\n      }\n    }\n\n    if (updated) {\n      vfs.writeJson(jsonPath, pkgJson);\n    }\n  }\n}\n"
  },
  {
    "path": "packages/template-generator/src/post-process/index.ts",
    "content": "/**\n * Post-processor - Orchestrates post-generation processing\n * Modifies virtual files after template generation\n */\n\nimport type { ProjectConfig } from \"@better-t-stack/types\";\n\nimport type { VirtualFileSystem } from \"../core/virtual-fs\";\nimport { processCatalogs } from \"./catalogs\";\nimport { processPackageConfigs } from \"./package-configs\";\n\n/**\n * Run all post-processing steps on the virtual filesystem\n */\nexport function processPostGeneration(vfs: VirtualFileSystem, config: ProjectConfig) {\n  processPackageConfigs(vfs, config);\n  processCatalogs(vfs, config);\n}\n\nexport { processCatalogs, processPackageConfigs };\n"
  },
  {
    "path": "packages/template-generator/src/post-process/package-configs.ts",
    "content": "/**\n * Package.json configuration post-processor\n * Updates package names, scripts, and workspaces after template generation\n */\n\nimport { desktopWebFrontends, type ProjectConfig } from \"@better-t-stack/types\";\n\nimport type { VirtualFileSystem } from \"../core/virtual-fs\";\nimport { getDbScriptSupport } from \"../utils/db-scripts\";\n\ntype PackageJson = {\n  name?: string;\n  scripts?: Record<string, string>;\n  dependencies?: Record<string, string>;\n  devDependencies?: Record<string, string>;\n  overrides?: Record<string, string>;\n  workspaces?: string[] | { packages?: string[]; catalog?: Record<string, string> };\n  packageManager?: string;\n  [key: string]: unknown;\n};\n\ntype PackageManagerConfig = {\n  dev: string;\n  build: string;\n  checkTypes: string;\n  filter: (workspace: string, script: string) => string;\n};\n\ntype DesktopWebScript = \"build\" | \"dev\" | \"generate\";\n\n/**\n * Update all package.json files with proper names, scripts, and workspaces\n */\nexport function processPackageConfigs(vfs: VirtualFileSystem, config: ProjectConfig): void {\n  updateRootPackageJson(vfs, config);\n  updateConfigPackageJson(vfs, config);\n  updateEnvPackageJson(vfs, config);\n  updateUiPackageJson(vfs, config);\n  updateInfraPackageJson(vfs, config);\n  updateDesktopPackageJson(vfs, config);\n  renameDevScriptsForAlchemy(vfs, config);\n\n  if (config.backend === \"convex\") {\n    updateConvexPackageJson(vfs, config);\n  } else if (config.backend !== \"none\") {\n    updateDbPackageJson(vfs, config);\n    updateAuthPackageJson(vfs, config);\n    updateApiPackageJson(vfs, config);\n  }\n}\n\nfunction updateRootPackageJson(vfs: VirtualFileSystem, config: ProjectConfig): void {\n  const pkgJson = vfs.readJson<PackageJson>(\"package.json\");\n  if (!pkgJson) return;\n\n  pkgJson.name = config.projectName;\n  pkgJson.scripts = pkgJson.scripts || {};\n\n  // Ensure workspaces is an array\n  let workspaces: string[] = [];\n  if (Array.isArray(pkgJson.workspaces)) {\n    workspaces = pkgJson.workspaces;\n  } else if (\n    pkgJson.workspaces &&\n    typeof pkgJson.workspaces === \"object\" &&\n    pkgJson.workspaces.packages\n  ) {\n    workspaces = pkgJson.workspaces.packages;\n  }\n  pkgJson.workspaces = workspaces;\n\n  const scripts = pkgJson.scripts;\n  const { projectName, packageManager, backend, database, orm, dbSetup, addons, frontend } = config;\n  const hasWebApp = frontend.some((item) =>\n    (desktopWebFrontends as readonly string[]).includes(item),\n  );\n  const hasNativeApp = frontend.some((item) =>\n    [\"native-bare\", \"native-uniwind\", \"native-unistyles\"].includes(item),\n  );\n\n  const backendPackageName = backend === \"convex\" ? `@${projectName}/backend` : \"server\";\n  const dbPackageName = `@${projectName}/db`;\n  const hasTurborepo = addons.includes(\"turborepo\");\n  const hasNx = addons.includes(\"nx\");\n\n  const dbSupport = getDbScriptSupport(config);\n  const needsDbScripts = dbSupport.hasDbScripts;\n  const isD1Alchemy = dbSupport.isD1Alchemy;\n\n  const pmConfig = getPackageManagerConfig(packageManager, { hasTurborepo, hasNx });\n\n  scripts.dev = pmConfig.dev;\n  scripts.build = pmConfig.build;\n  scripts[\"check-types\"] = pmConfig.checkTypes;\n\n  if (hasNativeApp) {\n    scripts[\"dev:native\"] = pmConfig.filter(\"native\", \"dev\");\n  }\n\n  if (hasWebApp) {\n    scripts[\"dev:web\"] = pmConfig.filter(\"web\", \"dev\");\n  }\n\n  if (addons.includes(\"electrobun\")) {\n    scripts[\"dev:desktop\"] = pmConfig.filter(\"desktop\", \"dev:hmr\");\n    scripts[\"build:desktop\"] = pmConfig.filter(\"desktop\", \"build:stable\");\n    scripts[\"build:desktop:canary\"] = pmConfig.filter(\"desktop\", \"build:canary\");\n  }\n\n  if (addons.includes(\"opentui\")) {\n    scripts[\"dev:tui\"] = pmConfig.filter(\"tui\", \"dev\");\n  }\n\n  if (backend !== \"self\" && backend !== \"none\") {\n    scripts[\"dev:server\"] = pmConfig.filter(backendPackageName, \"dev\");\n  }\n\n  if (backend === \"convex\") {\n    scripts[\"dev:setup\"] = pmConfig.filter(backendPackageName, \"dev:setup\");\n  }\n\n  if (needsDbScripts) {\n    scripts[\"db:push\"] = pmConfig.filter(dbPackageName, \"db:push\");\n\n    if (!isD1Alchemy) {\n      scripts[\"db:studio\"] = pmConfig.filter(dbPackageName, \"db:studio\");\n    }\n\n    if (orm === \"prisma\") {\n      scripts[\"db:generate\"] = pmConfig.filter(dbPackageName, \"db:generate\");\n      scripts[\"db:migrate\"] = pmConfig.filter(dbPackageName, \"db:migrate\");\n    } else if (orm === \"drizzle\") {\n      scripts[\"db:generate\"] = pmConfig.filter(dbPackageName, \"db:generate\");\n      if (!isD1Alchemy) {\n        scripts[\"db:migrate\"] = pmConfig.filter(dbPackageName, \"db:migrate\");\n      }\n    }\n  }\n\n  if (database === \"sqlite\" && dbSetup !== \"d1\") {\n    scripts[\"db:local\"] = pmConfig.filter(dbPackageName, \"db:local\");\n  }\n\n  if (dbSetup === \"docker\") {\n    scripts[\"db:start\"] = pmConfig.filter(dbPackageName, \"db:start\");\n    scripts[\"db:watch\"] = pmConfig.filter(dbPackageName, \"db:watch\");\n    scripts[\"db:stop\"] = pmConfig.filter(dbPackageName, \"db:stop\");\n    scripts[\"db:down\"] = pmConfig.filter(dbPackageName, \"db:down\");\n  }\n\n  // Add deploy/destroy scripts when using alchemy (cloudflare deployment)\n  const infraPackageName = `@${projectName}/infra`;\n  if (config.webDeploy === \"cloudflare\" || config.serverDeploy === \"cloudflare\") {\n    scripts.deploy = pmConfig.filter(infraPackageName, \"deploy\");\n    scripts.destroy = pmConfig.filter(infraPackageName, \"destroy\");\n  }\n\n  // Note: packageManager version is set by CLI at runtime since it requires running the actual CLI\n  // For preview purposes, we just show the configured package manager\n  pkgJson.packageManager = `${packageManager}@latest`;\n\n  if (config.api === \"orpc\" && config.frontend.includes(\"nuxt\")) {\n    pkgJson.overrides = {\n      ...pkgJson.overrides,\n      \"@vue/devtools-api\": \"^8.0.7\",\n    };\n  }\n\n  if (backend === \"convex\") {\n    if (!workspaces.includes(\"packages/*\")) {\n      workspaces.push(\"packages/*\");\n    }\n    const needsAppsDir = config.frontend.length > 0 || addons.includes(\"starlight\");\n    if (needsAppsDir && !workspaces.includes(\"apps/*\")) {\n      workspaces.push(\"apps/*\");\n    }\n  } else {\n    if (!workspaces.includes(\"apps/*\")) {\n      workspaces.push(\"apps/*\");\n    }\n    if (!workspaces.includes(\"packages/*\")) {\n      workspaces.push(\"packages/*\");\n    }\n  }\n\n  vfs.writeJson(\"package.json\", pkgJson);\n}\n\nfunction getPackageManagerConfig(\n  packageManager: ProjectConfig[\"packageManager\"],\n  options: { hasTurborepo: boolean; hasNx: boolean },\n): PackageManagerConfig {\n  if (options.hasTurborepo) {\n    return {\n      dev: \"turbo dev\",\n      build: \"turbo build\",\n      checkTypes: \"turbo check-types\",\n      filter: (workspace, script) => `turbo -F ${workspace} ${script}`,\n    };\n  }\n\n  if (options.hasNx) {\n    return {\n      dev: \"nx run-many -t dev\",\n      build: \"nx run-many -t build\",\n      checkTypes: \"nx run-many -t check-types\",\n      filter: (workspace, script) => `nx run-many -t ${script} --projects=${workspace}`,\n    };\n  }\n\n  switch (packageManager) {\n    case \"pnpm\":\n      return {\n        dev: \"pnpm -r dev\",\n        build: \"pnpm -r build\",\n        checkTypes: \"pnpm -r check-types\",\n        filter: (workspace, script) => `pnpm --filter ${workspace} ${script}`,\n      };\n    case \"npm\":\n      return {\n        dev: \"npm run dev --workspaces\",\n        build: \"npm run build --workspaces\",\n        checkTypes: \"npm run check-types --workspaces\",\n        filter: (workspace, script) => `npm run ${script} --workspace ${workspace}`,\n      };\n    case \"bun\":\n    default:\n      return {\n        dev: \"bun run --filter '*' dev\",\n        build: \"bun run --filter '*' build\",\n        checkTypes: \"bun run --filter '*' check-types\",\n        filter: (workspace, script) => `bun run --filter ${workspace} ${script}`,\n      };\n  }\n}\n\nfunction updateDesktopPackageJson(vfs: VirtualFileSystem, config: ProjectConfig): void {\n  const pkgJson = vfs.readJson<PackageJson>(\"apps/desktop/package.json\");\n  if (!pkgJson) return;\n\n  const { packageManager, addons, frontend } = config;\n  const hasTurborepo = addons.includes(\"turborepo\");\n  const hasNx = addons.includes(\"nx\");\n  const desktopBuildScript: DesktopWebScript = frontend.includes(\"nuxt\") ? \"generate\" : \"build\";\n  const webBuildCommand = getDesktopWebCommand(\n    packageManager,\n    { hasTurborepo, hasNx },\n    desktopBuildScript,\n  );\n  const webDevCommand = getDesktopWebCommand(packageManager, { hasTurborepo, hasNx }, \"dev\");\n  const localRunCommand = getLocalRunCommand(packageManager);\n\n  pkgJson.scripts = {\n    ...pkgJson.scripts,\n    start: `${webBuildCommand} && electrobun dev`,\n    dev: \"electrobun dev --watch\",\n    \"dev:hmr\": `concurrently \"${localRunCommand} hmr\" \"${localRunCommand} start\"`,\n    hmr: webDevCommand,\n    build: `${webBuildCommand} && electrobun build`,\n    \"build:stable\": `${webBuildCommand} && electrobun build --env=stable`,\n    \"build:canary\": `${webBuildCommand} && electrobun build --env=canary`,\n    \"check-types\": \"tsc --noEmit\",\n  };\n\n  vfs.writeJson(\"apps/desktop/package.json\", pkgJson);\n}\n\nfunction getDesktopWebCommand(\n  packageManager: ProjectConfig[\"packageManager\"],\n  options: { hasTurborepo: boolean; hasNx: boolean },\n  script: DesktopWebScript,\n): string {\n  if (options.hasTurborepo) {\n    return `turbo -F web ${script}`;\n  }\n\n  if (options.hasNx) {\n    return `nx run-many -t ${script} --projects=web`;\n  }\n\n  switch (packageManager) {\n    case \"npm\":\n      return `npm run ${script} --workspace web`;\n    case \"pnpm\":\n      return `pnpm -w --filter web ${script}`;\n    case \"bun\":\n    default:\n      return `bun run --filter web ${script}`;\n  }\n}\n\nfunction getLocalRunCommand(packageManager: ProjectConfig[\"packageManager\"]): string {\n  switch (packageManager) {\n    case \"npm\":\n      return \"npm run\";\n    case \"pnpm\":\n      return \"pnpm run\";\n    case \"bun\":\n    default:\n      return \"bun run\";\n  }\n}\n\nfunction updateDbPackageJson(vfs: VirtualFileSystem, config: ProjectConfig): void {\n  const pkgJson = vfs.readJson<PackageJson>(\"packages/db/package.json\");\n  if (!pkgJson) return;\n\n  pkgJson.name = `@${config.projectName}/db`;\n  pkgJson.scripts = pkgJson.scripts || {};\n\n  const scripts = pkgJson.scripts;\n  const { database, orm, dbSetup } = config;\n  const { isD1Alchemy } = getDbScriptSupport(config);\n\n  if (database !== \"none\") {\n    if (database === \"sqlite\" && dbSetup !== \"d1\") {\n      scripts[\"db:local\"] = \"turso dev --db-file local.db\";\n    }\n\n    if (orm === \"prisma\") {\n      scripts[\"db:push\"] = \"prisma db push\";\n      scripts[\"db:generate\"] = \"prisma generate\";\n      scripts[\"db:migrate\"] = \"prisma migrate dev\";\n      scripts.postinstall ??= \"prisma generate\";\n      if (!isD1Alchemy) {\n        scripts[\"db:studio\"] = \"prisma studio\";\n      }\n    } else if (orm === \"drizzle\") {\n      scripts[\"db:push\"] = \"drizzle-kit push\";\n      scripts[\"db:generate\"] = \"drizzle-kit generate\";\n      if (!isD1Alchemy) {\n        scripts[\"db:studio\"] = \"drizzle-kit studio\";\n        scripts[\"db:migrate\"] = \"drizzle-kit migrate\";\n      }\n    }\n  }\n\n  if (dbSetup === \"docker\") {\n    scripts[\"db:start\"] = \"docker compose up -d\";\n    scripts[\"db:watch\"] = \"docker compose up\";\n    scripts[\"db:stop\"] = \"docker compose stop\";\n    scripts[\"db:down\"] = \"docker compose down\";\n  }\n\n  vfs.writeJson(\"packages/db/package.json\", pkgJson);\n}\n\nfunction updateAuthPackageJson(vfs: VirtualFileSystem, config: ProjectConfig): void {\n  const pkgJson = vfs.readJson<PackageJson>(\"packages/auth/package.json\");\n  if (!pkgJson) return;\n\n  pkgJson.name = `@${config.projectName}/auth`;\n  vfs.writeJson(\"packages/auth/package.json\", pkgJson);\n}\n\nfunction updateApiPackageJson(vfs: VirtualFileSystem, config: ProjectConfig): void {\n  const pkgJson = vfs.readJson<PackageJson>(\"packages/api/package.json\");\n  if (!pkgJson) return;\n\n  pkgJson.name = `@${config.projectName}/api`;\n  vfs.writeJson(\"packages/api/package.json\", pkgJson);\n}\n\nfunction updateConfigPackageJson(vfs: VirtualFileSystem, config: ProjectConfig): void {\n  const pkgJson = vfs.readJson<PackageJson>(\"packages/config/package.json\");\n  if (!pkgJson) return;\n\n  pkgJson.name = `@${config.projectName}/config`;\n  vfs.writeJson(\"packages/config/package.json\", pkgJson);\n}\n\nfunction updateEnvPackageJson(vfs: VirtualFileSystem, config: ProjectConfig): void {\n  const pkgJson = vfs.readJson<PackageJson>(\"packages/env/package.json\");\n  if (!pkgJson) return;\n\n  pkgJson.name = `@${config.projectName}/env`;\n\n  // Set exports based on which env files exist\n  const hasWebFrontend = config.frontend.some((f: string) =>\n    (desktopWebFrontends as readonly string[]).includes(f),\n  );\n  const hasNative = config.frontend.some((f: string) =>\n    [\"native-bare\", \"native-uniwind\", \"native-unistyles\"].includes(f),\n  );\n  const needsServerEnv = config.backend !== \"none\" && config.backend !== \"convex\";\n\n  const exports: Record<string, string> = {};\n\n  if (needsServerEnv) {\n    exports[\"./server\"] = \"./src/server.ts\";\n  }\n  if (hasWebFrontend) {\n    exports[\"./web\"] = \"./src/web.ts\";\n  }\n  if (hasNative) {\n    exports[\"./native\"] = \"./src/native.ts\";\n  }\n\n  pkgJson.exports = exports;\n\n  vfs.writeJson(\"packages/env/package.json\", pkgJson);\n}\n\nfunction updateUiPackageJson(vfs: VirtualFileSystem, config: ProjectConfig): void {\n  const pkgJson = vfs.readJson<PackageJson>(\"packages/ui/package.json\");\n  if (!pkgJson) return;\n\n  pkgJson.name = `@${config.projectName}/ui`;\n  vfs.writeJson(\"packages/ui/package.json\", pkgJson);\n}\n\nfunction updateInfraPackageJson(vfs: VirtualFileSystem, config: ProjectConfig): void {\n  const pkgJson = vfs.readJson<PackageJson>(\"packages/infra/package.json\");\n  if (!pkgJson) return;\n\n  pkgJson.name = `@${config.projectName}/infra`;\n  vfs.writeJson(\"packages/infra/package.json\", pkgJson);\n}\n\nfunction updateConvexPackageJson(vfs: VirtualFileSystem, config: ProjectConfig): void {\n  const pkgJson = vfs.readJson<PackageJson>(\"packages/backend/package.json\");\n  if (!pkgJson) return;\n\n  pkgJson.name = `@${config.projectName}/backend`;\n  pkgJson.scripts = pkgJson.scripts || {};\n  vfs.writeJson(\"packages/backend/package.json\", pkgJson);\n}\n\nfunction renameDevScriptsForAlchemy(vfs: VirtualFileSystem, config: ProjectConfig): void {\n  const { serverDeploy, webDeploy, backend } = config;\n\n  // Rename server dev script to dev:bare when serverDeploy is cloudflare\n  if (serverDeploy === \"cloudflare\" && backend !== \"self\") {\n    const serverPkgPath = \"apps/server/package.json\";\n    const serverPkg = vfs.readJson<PackageJson>(serverPkgPath);\n    if (serverPkg?.scripts?.dev) {\n      serverPkg.scripts[\"dev:bare\"] = serverPkg.scripts.dev;\n      delete serverPkg.scripts.dev;\n      vfs.writeJson(serverPkgPath, serverPkg);\n    }\n  }\n\n  // Rename web dev script to dev:bare when webDeploy is cloudflare\n  if (webDeploy === \"cloudflare\") {\n    const webPkgPath = \"apps/web/package.json\";\n    const webPkg = vfs.readJson<PackageJson>(webPkgPath);\n    if (webPkg?.scripts?.dev) {\n      webPkg.scripts[\"dev:bare\"] = webPkg.scripts.dev;\n      delete webPkg.scripts.dev;\n      vfs.writeJson(webPkgPath, webPkg);\n    }\n  }\n}\n"
  },
  {
    "path": "packages/template-generator/src/processors/addons-deps.ts",
    "content": "import type { ProjectConfig } from \"@better-t-stack/types\";\n\nimport type { VirtualFileSystem } from \"../core/virtual-fs\";\nimport { addPackageDependency } from \"../utils/add-deps\";\n\ntype PackageJson = {\n  name?: string;\n  scripts?: Record<string, string>;\n  dependencies?: Record<string, string>;\n  devDependencies?: Record<string, string>;\n  \"lint-staged\"?: Record<string, string | string[]>;\n  [key: string]: unknown;\n};\n\nexport function processAddonsDeps(vfs: VirtualFileSystem, config: ProjectConfig): void {\n  if (!config.addons || config.addons.length === 0) return;\n\n  const hasViteReactFrontend =\n    config.frontend.includes(\"react-router\") || config.frontend.includes(\"tanstack-router\");\n  const hasSolidFrontend = config.frontend.includes(\"solid\");\n  const hasPwaCompatibleFrontend = hasViteReactFrontend || hasSolidFrontend;\n  const hasEvlogWebServer = config.frontend.some((frontend) =>\n    [\"next\", \"nuxt\", \"svelte\", \"tanstack-start\", \"astro\"].includes(frontend),\n  );\n\n  if (config.addons.includes(\"turborepo\")) {\n    addPackageDependency({ vfs, packagePath: \"package.json\", devDependencies: [\"turbo\"] });\n  }\n\n  if (config.addons.includes(\"nx\")) {\n    addPackageDependency({ vfs, packagePath: \"package.json\", devDependencies: [\"nx\"] });\n  }\n\n  if (config.addons.includes(\"evlog\")) {\n    const serverPkgPath = \"apps/server/package.json\";\n    if (vfs.exists(serverPkgPath) && config.backend !== \"self\" && config.backend !== \"none\") {\n      addPackageDependency({ vfs, packagePath: serverPkgPath, dependencies: [\"evlog\"] });\n    }\n\n    const webPkgPath = \"apps/web/package.json\";\n    if (vfs.exists(webPkgPath) && hasEvlogWebServer) {\n      addPackageDependency({\n        vfs,\n        packagePath: webPkgPath,\n        dependencies: config.frontend.includes(\"tanstack-start\") ? [\"evlog\", \"nitro\"] : [\"evlog\"],\n      });\n    }\n  }\n\n  if (config.addons.includes(\"pwa\") && hasPwaCompatibleFrontend) {\n    const webPkgPath = \"apps/web/package.json\";\n    if (vfs.exists(webPkgPath)) {\n      addPackageDependency({\n        vfs,\n        packagePath: webPkgPath,\n        dependencies: [\"vite-plugin-pwa\"],\n        devDependencies: [\"@vite-pwa/assets-generator\"],\n      });\n      const webPkg = vfs.readJson<PackageJson>(webPkgPath);\n      if (webPkg) {\n        webPkg.scripts = { ...webPkg.scripts, \"generate-pwa-assets\": \"pwa-assets-generator\" };\n        vfs.writeJson(webPkgPath, webPkg);\n      }\n    }\n  }\n\n  if (config.addons.includes(\"tauri\")) {\n    const webPkgPath = \"apps/web/package.json\";\n    if (vfs.exists(webPkgPath)) {\n      addPackageDependency({ vfs, packagePath: webPkgPath, devDependencies: [\"@tauri-apps/cli\"] });\n      const webPkg = vfs.readJson<PackageJson>(webPkgPath);\n      if (webPkg) {\n        webPkg.scripts = {\n          ...webPkg.scripts,\n          tauri: \"tauri\",\n          \"desktop:dev\": \"tauri dev\",\n          \"desktop:build\": \"tauri build\",\n        };\n        vfs.writeJson(webPkgPath, webPkg);\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "packages/template-generator/src/processors/alchemy-plugins.ts",
    "content": "import type { ProjectConfig } from \"@better-t-stack/types\";\nimport { IndentationText, Node, Project, QuoteKind } from \"ts-morph\";\n\nimport type { VirtualFileSystem } from \"../core/virtual-fs\";\n\nexport function processAlchemyPlugins(vfs: VirtualFileSystem, config: ProjectConfig): void {\n  const { webDeploy, frontend } = config;\n\n  if (webDeploy !== \"cloudflare\") return;\n\n  const isNext = frontend.includes(\"next\");\n  const isNuxt = frontend.includes(\"nuxt\");\n  const isSvelte = frontend.includes(\"svelte\");\n  const isTanstackStart = frontend.includes(\"tanstack-start\");\n  const isAstro = frontend.includes(\"astro\");\n\n  if (isNext) {\n    processNextAlchemy(vfs);\n  } else if (isNuxt) {\n    processNuxtAlchemy(vfs);\n  } else if (isSvelte) {\n    processSvelteAlchemy(vfs);\n  } else if (isTanstackStart) {\n    processTanStackStartAlchemy(vfs);\n  } else if (isAstro) {\n    processAstroAlchemy(vfs);\n  }\n}\n\nfunction processNextAlchemy(vfs: VirtualFileSystem) {\n  const webAppDir = \"apps/web\";\n  const openNextConfigPath = `${webAppDir}/open-next.config.ts`;\n\n  if (!vfs.exists(openNextConfigPath)) {\n    const openNextConfigContent = `import { defineCloudflareConfig } from \"@opennextjs/cloudflare\";\n\nexport default defineCloudflareConfig({});\n`;\n    vfs.writeFile(openNextConfigPath, openNextConfigContent);\n  }\n\n  const gitignorePath = `${webAppDir}/.gitignore`;\n  if (vfs.exists(gitignorePath)) {\n    let gitignoreContent = vfs.readFile(gitignorePath);\n    if (gitignoreContent && !gitignoreContent.includes(\"wrangler.jsonc\")) {\n      gitignoreContent += \"\\nwrangler.jsonc\\n\";\n      vfs.writeFile(gitignorePath, gitignoreContent);\n    }\n  } else {\n    vfs.writeFile(gitignorePath, \"wrangler.jsonc\\n\");\n  }\n}\n\nfunction processNuxtAlchemy(vfs: VirtualFileSystem) {\n  const nuxtConfigPath = \"apps/web/nuxt.config.ts\";\n  if (!vfs.exists(nuxtConfigPath)) return;\n\n  const content = vfs.readFile(nuxtConfigPath);\n  const project = new Project({\n    useInMemoryFileSystem: true,\n    manipulationSettings: {\n      indentationText: IndentationText.TwoSpaces,\n      quoteKind: QuoteKind.Double,\n    },\n  });\n\n  const sourceFile = project.createSourceFile(\"nuxt.config.ts\", content);\n\n  const hasAlchemyImport = sourceFile\n    .getImportDeclarations()\n    .some((decl) => decl.getModuleSpecifierValue() === \"alchemy/cloudflare/nuxt\");\n  if (!hasAlchemyImport) {\n    sourceFile.addImportDeclaration({\n      moduleSpecifier: \"alchemy/cloudflare/nuxt\",\n      defaultImport: \"alchemy\",\n    });\n  }\n\n  const hasFsImport = sourceFile\n    .getImportDeclarations()\n    .some((decl) => decl.getModuleSpecifierValue() === \"node:fs\");\n  if (!hasFsImport) {\n    sourceFile.addImportDeclaration({\n      moduleSpecifier: \"node:fs\",\n      namedImports: [\"existsSync\"],\n    });\n  }\n\n  const hasUrlImport = sourceFile\n    .getImportDeclarations()\n    .some((decl) => decl.getModuleSpecifierValue() === \"node:url\");\n  if (!hasUrlImport) {\n    sourceFile.addImportDeclaration({\n      moduleSpecifier: \"node:url\",\n      namedImports: [\"fileURLToPath\"],\n    });\n  }\n\n  if (!sourceFile.getVariableDeclaration(\"alchemyConfigPath\")) {\n    const firstExport = sourceFile\n      .getStatements()\n      .findIndex((statement) => Node.isExportAssignment(statement));\n    sourceFile.insertStatements(\n      firstExport === -1 ? sourceFile.getStatements().length : firstExport,\n      `const alchemyConfigPath = fileURLToPath(new URL(\"./.alchemy/local/wrangler.jsonc\", import.meta.url));\nconst hasAlchemyConfig = existsSync(alchemyConfigPath);`,\n    );\n  }\n\n  if (!sourceFile.getVariableDeclaration(\"cloudflareWorkersShimPath\")) {\n    const firstExport = sourceFile\n      .getStatements()\n      .findIndex((statement) => Node.isExportAssignment(statement));\n    sourceFile.insertStatements(\n      firstExport === -1 ? sourceFile.getStatements().length : firstExport,\n      `const cloudflareWorkersShimPath = fileURLToPath(new URL(\"../../packages/env/src/cloudflare-local.ts\", import.meta.url));`,\n    );\n  }\n\n  if (!sourceFile.getVariableDeclaration(\"isNuxtPrepare\")) {\n    const firstExport = sourceFile\n      .getStatements()\n      .findIndex((statement) => Node.isExportAssignment(statement));\n    sourceFile.insertStatements(\n      firstExport === -1 ? sourceFile.getStatements().length : firstExport,\n      `const isNuxtPrepare =\n  process.env.npm_lifecycle_event === \"postinstall\" ||\n  process.env.npm_lifecycle_script === \"nuxt prepare\" ||\n  process.argv.includes(\"prepare\");`,\n    );\n  }\n\n  if (!sourceFile.getVariableDeclaration(\"shouldUseAlchemy\")) {\n    const firstExport = sourceFile\n      .getStatements()\n      .findIndex((statement) => Node.isExportAssignment(statement));\n    sourceFile.insertStatements(\n      firstExport === -1 ? sourceFile.getStatements().length : firstExport,\n      `const shouldUseAlchemy = !isNuxtPrepare && hasAlchemyConfig;`,\n    );\n  }\n\n  if (!sourceFile.getVariableDeclaration(\"cloudflareWorkersAlias\")) {\n    const firstExport = sourceFile\n      .getStatements()\n      .findIndex((statement) => Node.isExportAssignment(statement));\n    sourceFile.insertStatements(\n      firstExport === -1 ? sourceFile.getStatements().length : firstExport,\n      `const cloudflareWorkersAlias = shouldUseAlchemy\n  ? {}\n  : {\n      \"cloudflare:workers\": cloudflareWorkersShimPath,\n    };`,\n    );\n  }\n\n  if (!sourceFile.getVariableDeclaration(\"isNuxtDev\")) {\n    const firstExport = sourceFile\n      .getStatements()\n      .findIndex((statement) => Node.isExportAssignment(statement));\n    sourceFile.insertStatements(\n      firstExport === -1 ? sourceFile.getStatements().length : firstExport,\n      `const isNuxtDev =\n  !isNuxtPrepare &&\n  process.env.NODE_ENV !== \"production\" &&\n  !process.argv.some((arg) => arg === \"build\" || arg === \"generate\");`,\n    );\n  }\n\n  const exportAssignment = sourceFile.getExportAssignment((d) => !d.isExportEquals());\n\n  if (!exportAssignment) return;\n\n  const defineConfigCall = exportAssignment.getExpression();\n  if (\n    !Node.isCallExpression(defineConfigCall) ||\n    defineConfigCall.getExpression().getText() !== \"defineNuxtConfig\"\n  ) {\n    return;\n  }\n\n  let configObject = defineConfigCall.getArguments()[0];\n  if (!configObject) {\n    configObject = defineConfigCall.addArgument(\"{}\");\n  }\n\n  if (Node.isObjectLiteralExpression(configObject)) {\n    if (!configObject.getProperty(\"nitro\")) {\n      configObject.addPropertyAssignment({\n        name: \"nitro\",\n        initializer: `{\n    preset: \"cloudflare-module\",\n    ...(shouldUseAlchemy ? { cloudflare: alchemy({ dev: { configPath: alchemyConfigPath } }) } : {}),\n    alias: cloudflareWorkersAlias,\n    prerender: {\n      routes: [\"/\"],\n      autoSubfolderIndex: false,\n    },\n  }`,\n      });\n    }\n\n    if (!configObject.getProperty(\"vite\")) {\n      configObject.addPropertyAssignment({\n        name: \"vite\",\n        initializer: `{\n    resolve: {\n      alias: cloudflareWorkersAlias,\n    },\n  }`,\n      });\n    }\n\n    const modulesProperty = configObject.getProperty(\"modules\");\n    if (modulesProperty && Node.isPropertyAssignment(modulesProperty)) {\n      const initializer = modulesProperty.getInitializer();\n      if (Node.isArrayLiteralExpression(initializer)) {\n        const hasModule = initializer\n          .getElements()\n          .some(\n            (el) =>\n              el.getText() === '\"nitro-cloudflare-dev\"' ||\n              el.getText() === \"'nitro-cloudflare-dev'\",\n          );\n        if (!hasModule) {\n          initializer.addElement('\"nitro-cloudflare-dev\"');\n        }\n      }\n    } else if (!modulesProperty) {\n      configObject.addPropertyAssignment({\n        name: \"modules\",\n        initializer: '[\"nitro-cloudflare-dev\"]',\n      });\n    }\n  }\n\n  vfs.writeFile(nuxtConfigPath, sourceFile.getFullText());\n}\n\nfunction processSvelteAlchemy(vfs: VirtualFileSystem) {\n  const svelteConfigPath = \"apps/web/svelte.config.js\";\n  if (!vfs.exists(svelteConfigPath)) return;\n\n  const content = vfs.readFile(svelteConfigPath);\n  const project = new Project({\n    useInMemoryFileSystem: true,\n    manipulationSettings: {\n      indentationText: IndentationText.TwoSpaces,\n      quoteKind: QuoteKind.Single,\n    },\n  });\n\n  const sourceFile = project.createSourceFile(\"svelte.config.js\", content);\n\n  if (\n    !sourceFile\n      .getImportDeclarations()\n      .some((imp) => imp.getModuleSpecifierValue() === \"alchemy/cloudflare/sveltekit\")\n  ) {\n    sourceFile.insertImportDeclaration(0, {\n      moduleSpecifier: \"alchemy/cloudflare/sveltekit\",\n      defaultImport: \"alchemy\",\n    });\n  }\n\n  if (\n    !sourceFile\n      .getImportDeclarations()\n      .some((imp) => imp.getModuleSpecifierValue() === \"@sveltejs/adapter-auto\")\n  ) {\n    sourceFile.insertImportDeclaration(0, {\n      moduleSpecifier: \"@sveltejs/adapter-auto\",\n      defaultImport: \"adapter\",\n    });\n  }\n\n  if (\n    !sourceFile.getImportDeclarations().some((decl) => decl.getModuleSpecifierValue() === \"node:fs\")\n  ) {\n    sourceFile.insertImportDeclaration(0, {\n      moduleSpecifier: \"node:fs\",\n      namedImports: [\"existsSync\"],\n    });\n  }\n\n  if (\n    !sourceFile\n      .getImportDeclarations()\n      .some((decl) => decl.getModuleSpecifierValue() === \"node:url\")\n  ) {\n    sourceFile.insertImportDeclaration(0, {\n      moduleSpecifier: \"node:url\",\n      namedImports: [\"fileURLToPath\"],\n    });\n  }\n\n  if (!sourceFile.getVariableDeclaration(\"alchemyConfigPath\")) {\n    const configVariableIndex = sourceFile.getStatements().findIndex(\n      (statement) =>\n        Node.isVariableStatement(statement) &&\n        statement\n          .getDeclarationList()\n          .getDeclarations()\n          .some((decl) => decl.getName() === \"config\"),\n    );\n    sourceFile.insertStatements(\n      configVariableIndex === -1 ? sourceFile.getStatements().length : configVariableIndex,\n      `const alchemyConfigPath = fileURLToPath(new URL(\"./.alchemy/local/wrangler.jsonc\", import.meta.url));\nconst shouldUseAlchemy = existsSync(alchemyConfigPath);`,\n    );\n  }\n\n  const configVariable = sourceFile.getVariableDeclaration(\"config\");\n  if (configVariable) {\n    const initializer = configVariable.getInitializer();\n    if (Node.isObjectLiteralExpression(initializer)) {\n      const kitProperty = initializer.getProperty(\"kit\");\n      if (kitProperty && Node.isPropertyAssignment(kitProperty)) {\n        const kitInitializer = kitProperty.getInitializer();\n        if (Node.isObjectLiteralExpression(kitInitializer)) {\n          const adapterProperty = kitInitializer.getProperty(\"adapter\");\n          if (adapterProperty && Node.isPropertyAssignment(adapterProperty)) {\n            adapterProperty.setInitializer(\n              \"shouldUseAlchemy ? alchemy({ platformProxy: { configPath: alchemyConfigPath } }) : adapter()\",\n            );\n          }\n        }\n      }\n    }\n  }\n\n  vfs.writeFile(svelteConfigPath, sourceFile.getFullText());\n}\n\nfunction processTanStackStartAlchemy(vfs: VirtualFileSystem) {\n  const viteConfigPath = \"apps/web/vite.config.ts\";\n  if (!vfs.exists(viteConfigPath)) return;\n\n  const content = vfs.readFile(viteConfigPath);\n  const project = new Project({\n    useInMemoryFileSystem: true,\n    manipulationSettings: {\n      indentationText: IndentationText.TwoSpaces,\n      quoteKind: QuoteKind.Double,\n    },\n  });\n\n  const sourceFile = project.createSourceFile(\"vite.config.ts\", content);\n\n  const alchemyImport = sourceFile.getImportDeclaration(\n    (decl) => decl.getModuleSpecifierValue() === \"alchemy/cloudflare/tanstack-start\",\n  );\n\n  if (!alchemyImport) {\n    sourceFile.addImportDeclaration({\n      moduleSpecifier: \"alchemy/cloudflare/tanstack-start\",\n      defaultImport: \"alchemy\",\n    });\n  }\n\n  const hasFsImport = sourceFile\n    .getImportDeclarations()\n    .some((decl) => decl.getModuleSpecifierValue() === \"node:fs\");\n  if (!hasFsImport) {\n    sourceFile.addImportDeclaration({\n      moduleSpecifier: \"node:fs\",\n      namedImports: [\"existsSync\"],\n    });\n  }\n\n  const hasUrlImport = sourceFile\n    .getImportDeclarations()\n    .some((decl) => decl.getModuleSpecifierValue() === \"node:url\");\n  if (!hasUrlImport) {\n    sourceFile.addImportDeclaration({\n      moduleSpecifier: \"node:url\",\n      namedImports: [\"fileURLToPath\"],\n    });\n  }\n\n  if (!sourceFile.getVariableDeclaration(\"alchemyConfigPath\")) {\n    const firstExport = sourceFile\n      .getStatements()\n      .findIndex((statement) => Node.isExportAssignment(statement));\n    sourceFile.insertStatements(\n      firstExport === -1 ? sourceFile.getStatements().length : firstExport,\n      `const alchemyConfigPath = fileURLToPath(new URL(\"./.alchemy/local/wrangler.jsonc\", import.meta.url));\nconst shouldUseAlchemy = existsSync(alchemyConfigPath);`,\n    );\n  }\n\n  if (!sourceFile.getVariableDeclaration(\"cloudflareWorkersShimPath\")) {\n    const firstExport = sourceFile\n      .getStatements()\n      .findIndex((statement) => Node.isExportAssignment(statement));\n    sourceFile.insertStatements(\n      firstExport === -1 ? sourceFile.getStatements().length : firstExport,\n      `const cloudflareWorkersShimPath = fileURLToPath(new URL(\"../../packages/env/src/cloudflare-local.ts\", import.meta.url));`,\n    );\n  }\n\n  if (!sourceFile.getVariableDeclaration(\"cloudflareWorkersAlias\")) {\n    const firstExport = sourceFile\n      .getStatements()\n      .findIndex((statement) => Node.isExportAssignment(statement));\n    sourceFile.insertStatements(\n      firstExport === -1 ? sourceFile.getStatements().length : firstExport,\n      `const cloudflareWorkersAlias = shouldUseAlchemy\n  ? {}\n  : {\n      \"cloudflare:workers\": cloudflareWorkersShimPath,\n    };`,\n    );\n  }\n\n  const exportAssignment = sourceFile.getExportAssignment((d) => !d.isExportEquals());\n  if (!exportAssignment) return;\n\n  const defineConfigCall = exportAssignment.getExpression();\n  if (\n    !Node.isCallExpression(defineConfigCall) ||\n    defineConfigCall.getExpression().getText() !== \"defineConfig\"\n  ) {\n    return;\n  }\n\n  let configObject = defineConfigCall.getArguments()[0];\n  if (!configObject) {\n    configObject = defineConfigCall.addArgument(\"{}\");\n  }\n\n  if (Node.isObjectLiteralExpression(configObject)) {\n    const pluginsProperty = configObject.getProperty(\"plugins\");\n    if (pluginsProperty && Node.isPropertyAssignment(pluginsProperty)) {\n      const initializer = pluginsProperty.getInitializer();\n      if (Node.isArrayLiteralExpression(initializer)) {\n        const hasAlchemy = initializer\n          .getElements()\n          .some((el) => el.getText().includes(\"alchemy(\"));\n        if (!hasAlchemy) {\n          initializer.addElement(\n            \"...(shouldUseAlchemy ? [alchemy({ configPath: alchemyConfigPath })] : [])\",\n          );\n        }\n      }\n    } else {\n      configObject.addPropertyAssignment({\n        name: \"plugins\",\n        initializer: \"[...(shouldUseAlchemy ? [alchemy({ configPath: alchemyConfigPath })] : [])]\",\n      });\n    }\n\n    const resolveProperty = configObject.getProperty(\"resolve\");\n    if (resolveProperty && Node.isPropertyAssignment(resolveProperty)) {\n      const initializer = resolveProperty.getInitializer();\n      if (Node.isObjectLiteralExpression(initializer) && !initializer.getProperty(\"alias\")) {\n        initializer.addPropertyAssignment({\n          name: \"alias\",\n          initializer: \"cloudflareWorkersAlias\",\n        });\n      }\n    } else if (!resolveProperty) {\n      configObject.addPropertyAssignment({\n        name: \"resolve\",\n        initializer: \"{ alias: cloudflareWorkersAlias }\",\n      });\n    }\n  }\n\n  vfs.writeFile(viteConfigPath, sourceFile.getFullText());\n}\n\nfunction processAstroAlchemy(vfs: VirtualFileSystem) {\n  const webAppDir = \"apps/web\";\n  const astroConfigPath = `${webAppDir}/astro.config.mjs`;\n\n  if (!vfs.exists(astroConfigPath)) return;\n\n  const content = vfs.readFile(astroConfigPath);\n  const project = new Project({\n    useInMemoryFileSystem: true,\n    manipulationSettings: {\n      indentationText: IndentationText.TwoSpaces,\n      quoteKind: QuoteKind.Double,\n    },\n  });\n\n  const sourceFile = project.createSourceFile(\"astro.config.mjs\", content);\n\n  if (\n    !sourceFile\n      .getImportDeclarations()\n      .some((imp) => imp.getModuleSpecifierValue() === \"alchemy/cloudflare/astro\")\n  ) {\n    sourceFile.insertImportDeclaration(0, {\n      moduleSpecifier: \"alchemy/cloudflare/astro\",\n      defaultImport: \"alchemy\",\n    });\n  }\n\n  if (\n    !sourceFile\n      .getImportDeclarations()\n      .some((imp) => imp.getModuleSpecifierValue() === \"@astrojs/node\")\n  ) {\n    sourceFile.insertImportDeclaration(0, {\n      moduleSpecifier: \"@astrojs/node\",\n      defaultImport: \"node\",\n    });\n  }\n\n  if (\n    !sourceFile.getImportDeclarations().some((decl) => decl.getModuleSpecifierValue() === \"node:fs\")\n  ) {\n    sourceFile.insertImportDeclaration(0, {\n      moduleSpecifier: \"node:fs\",\n      namedImports: [\"existsSync\"],\n    });\n  }\n\n  if (\n    !sourceFile\n      .getImportDeclarations()\n      .some((decl) => decl.getModuleSpecifierValue() === \"node:url\")\n  ) {\n    sourceFile.insertImportDeclaration(0, {\n      moduleSpecifier: \"node:url\",\n      namedImports: [\"fileURLToPath\"],\n    });\n  }\n\n  if (!sourceFile.getVariableDeclaration(\"alchemyConfigPath\")) {\n    const firstExport = sourceFile\n      .getStatements()\n      .findIndex((statement) => Node.isExportAssignment(statement));\n    sourceFile.insertStatements(\n      firstExport === -1 ? sourceFile.getStatements().length : firstExport,\n      `const alchemyConfigPath = fileURLToPath(new URL(\"./.alchemy/local/wrangler.jsonc\", import.meta.url));\nconst shouldUseAlchemy = existsSync(alchemyConfigPath);`,\n    );\n  }\n\n  if (!sourceFile.getVariableDeclaration(\"cloudflareWorkersShimPath\")) {\n    const firstExport = sourceFile\n      .getStatements()\n      .findIndex((statement) => Node.isExportAssignment(statement));\n    sourceFile.insertStatements(\n      firstExport === -1 ? sourceFile.getStatements().length : firstExport,\n      `const cloudflareWorkersShimPath = fileURLToPath(new URL(\"../../packages/env/src/cloudflare-local.ts\", import.meta.url));`,\n    );\n  }\n\n  if (!sourceFile.getVariableDeclaration(\"cloudflareWorkersAlias\")) {\n    const firstExport = sourceFile\n      .getStatements()\n      .findIndex((statement) => Node.isExportAssignment(statement));\n    sourceFile.insertStatements(\n      firstExport === -1 ? sourceFile.getStatements().length : firstExport,\n      `const cloudflareWorkersAlias = shouldUseAlchemy\n  ? {}\n  : {\n      \"cloudflare:workers\": cloudflareWorkersShimPath,\n    };`,\n    );\n  }\n\n  const exportAssignment = sourceFile.getExportAssignment((d) => !d.isExportEquals());\n  if (exportAssignment) {\n    const defineConfigCall = exportAssignment.getExpression();\n    if (\n      Node.isCallExpression(defineConfigCall) &&\n      defineConfigCall.getExpression().getText() === \"defineConfig\"\n    ) {\n      const configObject = defineConfigCall.getArguments()[0];\n      if (Node.isObjectLiteralExpression(configObject)) {\n        const adapterProperty = configObject.getProperty(\"adapter\");\n        if (adapterProperty && Node.isPropertyAssignment(adapterProperty)) {\n          adapterProperty.setInitializer(\n            'shouldUseAlchemy ? alchemy({ platformProxy: { configPath: alchemyConfigPath } }) : node({ mode: \"standalone\" })',\n          );\n        }\n\n        const viteProperty = configObject.getProperty(\"vite\");\n        if (viteProperty && Node.isPropertyAssignment(viteProperty)) {\n          const viteInitializer = viteProperty.getInitializer();\n          if (Node.isObjectLiteralExpression(viteInitializer)) {\n            const resolveProperty = viteInitializer.getProperty(\"resolve\");\n            if (resolveProperty && Node.isPropertyAssignment(resolveProperty)) {\n              const resolveInitializer = resolveProperty.getInitializer();\n              if (\n                Node.isObjectLiteralExpression(resolveInitializer) &&\n                !resolveInitializer.getProperty(\"alias\")\n              ) {\n                resolveInitializer.addPropertyAssignment({\n                  name: \"alias\",\n                  initializer: \"cloudflareWorkersAlias\",\n                });\n              }\n            } else if (!resolveProperty) {\n              viteInitializer.addPropertyAssignment({\n                name: \"resolve\",\n                initializer: \"{ alias: cloudflareWorkersAlias }\",\n              });\n            }\n          }\n        } else if (!viteProperty) {\n          configObject.addPropertyAssignment({\n            name: \"vite\",\n            initializer: \"{ resolve: { alias: cloudflareWorkersAlias } }\",\n          });\n        }\n      }\n    }\n  }\n\n  vfs.writeFile(astroConfigPath, sourceFile.getFullText());\n}\n"
  },
  {
    "path": "packages/template-generator/src/processors/api-deps.ts",
    "content": "import type { ProjectConfig, Frontend, API, Backend } from \"@better-t-stack/types\";\n\nimport type { VirtualFileSystem } from \"../core/virtual-fs\";\nimport { addPackageDependency, type AvailableDependencies } from \"../utils/add-deps\";\n\ntype FrontendType = {\n  hasReactWeb: boolean;\n  hasNuxtWeb: boolean;\n  hasSvelteWeb: boolean;\n  hasSolidWeb: boolean;\n  hasAstroWeb: boolean;\n  hasNative: boolean;\n};\n\nfunction getFrontendType(frontend: Frontend[]): FrontendType {\n  return {\n    hasReactWeb: frontend.some((f) =>\n      [\"tanstack-router\", \"react-router\", \"tanstack-start\", \"next\"].includes(f),\n    ),\n    hasNuxtWeb: frontend.includes(\"nuxt\"),\n    hasSvelteWeb: frontend.includes(\"svelte\"),\n    hasSolidWeb: frontend.includes(\"solid\"),\n    hasAstroWeb: frontend.includes(\"astro\"),\n    hasNative: frontend.some((f) =>\n      [\"native-bare\", \"native-uniwind\", \"native-unistyles\"].includes(f),\n    ),\n  };\n}\n\nexport function processApiDeps(vfs: VirtualFileSystem, config: ProjectConfig): void {\n  const { api, backend, frontend, auth } = config;\n  const frontendType = getFrontendType(frontend);\n\n  if (backend === \"convex\") {\n    addConvexDeps(vfs, frontend, frontendType);\n    return;\n  }\n\n  if (api === \"none\") return;\n\n  addApiPackageDeps(vfs, api, backend, frontend, auth);\n  addServerDeps(vfs, api, backend);\n  addSelfBackendWebDeps(vfs, api, backend, frontendType);\n  addWebClientDeps(vfs, api, backend, frontend, frontendType);\n  if (frontendType.hasNative) addNativeDeps(vfs, api, backend);\n  addQueryDeps(vfs, frontend, backend);\n}\n\nfunction addApiPackageDeps(\n  vfs: VirtualFileSystem,\n  api: API,\n  backend: Backend,\n  frontend: Frontend[],\n  auth: ProjectConfig[\"auth\"],\n): void {\n  const pkgPath = \"packages/api/package.json\";\n  if (!vfs.exists(pkgPath)) return;\n\n  if (api === \"trpc\") {\n    addPackageDependency({\n      vfs,\n      packagePath: pkgPath,\n      dependencies: [\"@trpc/server\", \"@trpc/client\", \"zod\"],\n    });\n  } else if (api === \"orpc\") {\n    addPackageDependency({\n      vfs,\n      packagePath: pkgPath,\n      dependencies: [\"@orpc/server\", \"@orpc/client\", \"@orpc/openapi\", \"@orpc/zod\", \"zod\"],\n    });\n  }\n\n  // Add next dep for api package when backend is self and frontend includes next\n  if (backend === \"self\" && frontend.includes(\"next\")) {\n    addPackageDependency({ vfs, packagePath: pkgPath, dependencies: [\"next\"] });\n  }\n\n  // Add better-auth for express/fastify backends\n  if (auth === \"better-auth\" && (backend === \"express\" || backend === \"fastify\")) {\n    addPackageDependency({ vfs, packagePath: pkgPath, dependencies: [\"better-auth\"] });\n  }\n\n  // Add @types/express for express backend\n  if (backend === \"express\") {\n    addPackageDependency({ vfs, packagePath: pkgPath, devDependencies: [\"@types/express\"] });\n  }\n\n  // Add hono types for hono backend\n  if (backend === \"hono\") {\n    addPackageDependency({ vfs, packagePath: pkgPath, devDependencies: [\"hono\"] });\n  }\n\n  // Add elysia types for elysia backend\n  if (backend === \"elysia\") {\n    addPackageDependency({ vfs, packagePath: pkgPath, devDependencies: [\"elysia\"] });\n  }\n}\n\nfunction addServerDeps(vfs: VirtualFileSystem, api: API, backend: Backend): void {\n  const serverPath = \"apps/server/package.json\";\n  if (!vfs.exists(serverPath)) return;\n\n  if (backend === \"convex\") return;\n\n  if (api === \"trpc\") {\n    addPackageDependency({\n      vfs,\n      packagePath: serverPath,\n      dependencies: [\"@trpc/server\", \"@hono/trpc-server\"],\n    });\n  } else if (api === \"orpc\") {\n    addPackageDependency({\n      vfs,\n      packagePath: serverPath,\n      dependencies: [\"@orpc/server\", \"@orpc/openapi\"],\n    });\n  }\n}\n\nfunction addSelfBackendWebDeps(\n  vfs: VirtualFileSystem,\n  api: API,\n  backend: Backend,\n  _frontendType: FrontendType,\n): void {\n  if (backend !== \"self\") return;\n\n  const webPath = \"apps/web/package.json\";\n  if (!vfs.exists(webPath)) return;\n\n  // When backend is \"self\", add server deps to web too\n  if (api === \"trpc\") {\n    addPackageDependency({\n      vfs,\n      packagePath: webPath,\n      dependencies: [\"@trpc/server\", \"@trpc/client\"],\n    });\n  } else if (api === \"orpc\") {\n    addPackageDependency({\n      vfs,\n      packagePath: webPath,\n      dependencies: [\"@orpc/server\", \"@orpc/client\", \"@orpc/openapi\", \"@orpc/zod\"],\n    });\n  }\n}\n\nfunction addWebClientDeps(\n  vfs: VirtualFileSystem,\n  api: API,\n  backend: Backend,\n  frontend: Frontend[],\n  frontendType: FrontendType,\n): void {\n  const webPath = \"apps/web/package.json\";\n  if (!vfs.exists(webPath) || backend === \"convex\") return;\n\n  if (api === \"trpc\" && frontendType.hasReactWeb) {\n    const deps: AvailableDependencies[] = [\n      \"@trpc/tanstack-react-query\",\n      \"@trpc/client\",\n      \"@trpc/server\",\n    ];\n    if (frontend.includes(\"tanstack-start\")) {\n      deps.push(\"@tanstack/react-router-ssr-query\");\n    }\n    addPackageDependency({\n      vfs,\n      packagePath: webPath,\n      dependencies: deps,\n    });\n  } else if (api === \"orpc\" && frontendType.hasReactWeb) {\n    const deps: AvailableDependencies[] = [\"@orpc/tanstack-query\", \"@orpc/client\", \"@orpc/server\"];\n    if (frontend.includes(\"tanstack-start\")) {\n      deps.push(\"@tanstack/react-router-ssr-query\");\n    }\n    addPackageDependency({\n      vfs,\n      packagePath: webPath,\n      dependencies: deps,\n    });\n  } else if (api === \"orpc\" && frontendType.hasNuxtWeb) {\n    addPackageDependency({\n      vfs,\n      packagePath: webPath,\n      dependencies: [\"@tanstack/vue-query\", \"@orpc/tanstack-query\", \"@orpc/client\", \"@orpc/server\"],\n      devDependencies: [\"@tanstack/vue-query-devtools\"],\n    });\n  } else if (api === \"orpc\" && frontendType.hasSvelteWeb) {\n    addPackageDependency({\n      vfs,\n      packagePath: webPath,\n      dependencies: [\n        \"@orpc/tanstack-query\",\n        \"@orpc/client\",\n        \"@orpc/server\",\n        \"@tanstack/svelte-query\",\n      ],\n      devDependencies: [\"@tanstack/svelte-query-devtools\"],\n    });\n  } else if (api === \"orpc\" && frontendType.hasSolidWeb) {\n    addPackageDependency({\n      vfs,\n      packagePath: webPath,\n      dependencies: [\n        \"@orpc/tanstack-query\",\n        \"@orpc/client\",\n        \"@orpc/server\",\n        \"@tanstack/solid-query\",\n      ],\n      devDependencies: [\"@tanstack/solid-query-devtools\", \"@tanstack/solid-router-devtools\"],\n    });\n  } else if (api === \"orpc\" && frontendType.hasAstroWeb) {\n    // Astro uses vanilla oRPC client without TanStack Query\n    addPackageDependency({\n      vfs,\n      packagePath: webPath,\n      dependencies: [\"@orpc/client\"],\n    });\n  }\n}\n\nfunction addNativeDeps(vfs: VirtualFileSystem, api: API, backend: Backend): void {\n  const nativePath = \"apps/native/package.json\";\n  if (!vfs.exists(nativePath)) return;\n\n  if (backend === \"convex\") return;\n\n  if (api === \"trpc\") {\n    addPackageDependency({\n      vfs,\n      packagePath: nativePath,\n      dependencies: [\"@trpc/tanstack-react-query\", \"@trpc/client\", \"@trpc/server\"],\n    });\n  } else if (api === \"orpc\") {\n    addPackageDependency({\n      vfs,\n      packagePath: nativePath,\n      dependencies: [\"@orpc/tanstack-query\", \"@orpc/client\"],\n    });\n  }\n}\n\nfunction addQueryDeps(vfs: VirtualFileSystem, frontend: Frontend[], backend: Backend): void {\n  const webPath = \"apps/web/package.json\";\n  const nativePath = \"apps/native/package.json\";\n  const frontendType = getFrontendType(frontend);\n\n  if (frontendType.hasReactWeb && vfs.exists(webPath) && backend !== \"convex\") {\n    addPackageDependency({\n      vfs,\n      packagePath: webPath,\n      dependencies: [\"@tanstack/react-query\"],\n      devDependencies: [\"@tanstack/react-query-devtools\"],\n    });\n  }\n\n  if (frontendType.hasSolidWeb && vfs.exists(webPath) && backend !== \"convex\") {\n    addPackageDependency({\n      vfs,\n      packagePath: webPath,\n      dependencies: [\"@tanstack/solid-query\"],\n      devDependencies: [\"@tanstack/solid-query-devtools\", \"@tanstack/solid-router-devtools\"],\n    });\n  }\n\n  if (frontendType.hasNative && vfs.exists(nativePath) && backend !== \"convex\") {\n    addPackageDependency({\n      vfs,\n      packagePath: nativePath,\n      dependencies: [\"@tanstack/react-query\"],\n    });\n  }\n}\n\nfunction addConvexDeps(\n  vfs: VirtualFileSystem,\n  frontend: Frontend[],\n  frontendType: FrontendType,\n): void {\n  const webPath = \"apps/web/package.json\";\n  const nativePath = \"apps/native/package.json\";\n  const webExists = vfs.exists(webPath);\n  const nativeExists = vfs.exists(nativePath);\n\n  if (webExists) {\n    const deps: AvailableDependencies[] = [\"convex\"];\n    if (frontend.includes(\"tanstack-start\")) {\n      deps.push(\"@convex-dev/react-query\", \"@tanstack/react-router-ssr-query\");\n    }\n    if (frontend.includes(\"svelte\")) {\n      deps.push(\"convex-svelte\");\n    }\n    if (frontend.includes(\"nuxt\")) {\n      deps.push(\"convex-nuxt\", \"convex-vue\");\n    }\n    addPackageDependency({ vfs, packagePath: webPath, dependencies: deps });\n  }\n\n  if (nativeExists && frontendType.hasNative) {\n    addPackageDependency({ vfs, packagePath: nativePath, dependencies: [\"convex\"] });\n  }\n}\n"
  },
  {
    "path": "packages/template-generator/src/processors/auth-deps.ts",
    "content": "import type { ProjectConfig } from \"@better-t-stack/types\";\n\nimport type { VirtualFileSystem } from \"../core/virtual-fs\";\nimport { addPackageDependency } from \"../utils/add-deps\";\n\nconst CONVEX_BETTER_AUTH_VERSION = \"1.6.9\";\n\nexport function processAuthDeps(vfs: VirtualFileSystem, config: ProjectConfig): void {\n  const { auth, backend } = config;\n  if (!auth || auth === \"none\") return;\n\n  if (backend === \"convex\") {\n    processConvexAuthDeps(vfs, config);\n  } else {\n    processStandardAuthDeps(vfs, config);\n  }\n}\n\nfunction processConvexAuthDeps(vfs: VirtualFileSystem, config: ProjectConfig): void {\n  const { auth, frontend } = config;\n  const webPath = \"apps/web/package.json\";\n  const nativePath = \"apps/native/package.json\";\n  const backendPath = \"packages/backend/package.json\";\n\n  const webExists = vfs.exists(webPath);\n  const nativeExists = vfs.exists(nativePath);\n  const backendExists = vfs.exists(backendPath);\n\n  const hasNative = frontend.some((f) =>\n    [\"native-bare\", \"native-uniwind\", \"native-unistyles\"].includes(f),\n  );\n  const hasNextJs = frontend.includes(\"next\");\n  const hasReactRouter = frontend.includes(\"react-router\");\n  const hasTanStackRouter = frontend.includes(\"tanstack-router\");\n  const hasTanStackStart = frontend.includes(\"tanstack-start\");\n  const hasViteReact = hasReactRouter || hasTanStackRouter;\n  const hasSolid = frontend.includes(\"solid\");\n  const hasSvelte = frontend.includes(\"svelte\");\n  const hasReactWebAuthForms = hasNextJs || hasTanStackStart || hasViteReact;\n\n  if (auth === \"clerk\") {\n    if (webExists) {\n      if (hasNextJs) {\n        addPackageDependency({ vfs, packagePath: webPath, dependencies: [\"@clerk/nextjs\"] });\n      } else if (hasReactRouter) {\n        addPackageDependency({ vfs, packagePath: webPath, dependencies: [\"@clerk/react-router\"] });\n      } else if (hasTanStackRouter) {\n        addPackageDependency({ vfs, packagePath: webPath, dependencies: [\"@clerk/react\"] });\n      } else if (hasTanStackStart) {\n        addPackageDependency({\n          vfs,\n          packagePath: webPath,\n          dependencies: [\"@clerk/tanstack-react-start\"],\n        });\n      }\n    }\n    if (nativeExists && hasNative) {\n      addPackageDependency({ vfs, packagePath: nativePath, dependencies: [\"@clerk/expo\"] });\n    }\n  } else if (auth === \"better-auth\") {\n    if (backendExists) {\n      addPackageDependency({\n        vfs,\n        packagePath: backendPath,\n        dependencies: [\"better-auth\", \"@convex-dev/better-auth\"],\n        customDependencies: {\n          \"better-auth\": CONVEX_BETTER_AUTH_VERSION,\n        },\n      });\n      if (hasNative) {\n        addPackageDependency({\n          vfs,\n          packagePath: backendPath,\n          dependencies: [\"@better-auth/expo\"],\n          customDependencies: {\n            \"@better-auth/expo\": CONVEX_BETTER_AUTH_VERSION,\n          },\n        });\n      }\n    }\n\n    if (webExists) {\n      addPackageDependency({\n        vfs,\n        packagePath: webPath,\n        dependencies: [\"better-auth\", \"@convex-dev/better-auth\"],\n        customDependencies: {\n          \"better-auth\": CONVEX_BETTER_AUTH_VERSION,\n        },\n      });\n\n      if (hasReactWebAuthForms) {\n        addPackageDependency({ vfs, packagePath: webPath, dependencies: [\"@tanstack/react-form\"] });\n      }\n      if (hasSolid) {\n        addPackageDependency({ vfs, packagePath: webPath, dependencies: [\"@tanstack/solid-form\"] });\n      }\n      if (hasSvelte) {\n        addPackageDependency({\n          vfs,\n          packagePath: webPath,\n          dependencies: [\"@tanstack/svelte-form\"],\n        });\n      }\n    }\n\n    if (nativeExists && hasNative) {\n      addPackageDependency({\n        vfs,\n        packagePath: nativePath,\n        dependencies: [\n          \"better-auth\",\n          \"@better-auth/expo\",\n          \"@convex-dev/better-auth\",\n          \"@tanstack/react-form\",\n        ],\n        customDependencies: {\n          \"better-auth\": CONVEX_BETTER_AUTH_VERSION,\n          \"@better-auth/expo\": CONVEX_BETTER_AUTH_VERSION,\n        },\n      });\n    }\n  }\n}\n\nfunction processStandardAuthDeps(vfs: VirtualFileSystem, config: ProjectConfig): void {\n  const { auth, backend, frontend } = config;\n  const authPath = \"packages/auth/package.json\";\n  const apiPath = \"packages/api/package.json\";\n  const webPath = \"apps/web/package.json\";\n  const nativePath = \"apps/native/package.json\";\n  const serverPath = \"apps/server/package.json\";\n\n  const authExists = vfs.exists(authPath);\n  const apiExists = vfs.exists(apiPath);\n  const webExists = vfs.exists(webPath);\n  const nativeExists = vfs.exists(nativePath);\n  const serverExists = vfs.exists(serverPath);\n\n  const hasNative = frontend.some((f) =>\n    [\"native-bare\", \"native-uniwind\", \"native-unistyles\"].includes(f),\n  );\n  const hasWebFrontend = frontend.some((f) =>\n    [\n      \"react-router\",\n      \"tanstack-router\",\n      \"tanstack-start\",\n      \"next\",\n      \"nuxt\",\n      \"svelte\",\n      \"solid\",\n      \"astro\",\n    ].includes(f),\n  );\n  const hasReactWebAuthForms = frontend.some((f) =>\n    [\"react-router\", \"tanstack-router\", \"tanstack-start\", \"next\"].includes(f),\n  );\n  const hasSolid = frontend.includes(\"solid\");\n  const hasSvelte = frontend.includes(\"svelte\");\n  const hasNextJs = frontend.includes(\"next\");\n  const hasReactRouter = frontend.includes(\"react-router\");\n  const hasTanStackRouter = frontend.includes(\"tanstack-router\");\n  const hasTanStackStart = frontend.includes(\"tanstack-start\");\n\n  if (auth === \"clerk\") {\n    if (webExists) {\n      if (hasNextJs) {\n        addPackageDependency({ vfs, packagePath: webPath, dependencies: [\"@clerk/nextjs\"] });\n      } else if (hasReactRouter) {\n        addPackageDependency({ vfs, packagePath: webPath, dependencies: [\"@clerk/react-router\"] });\n      } else if (hasTanStackRouter) {\n        addPackageDependency({ vfs, packagePath: webPath, dependencies: [\"@clerk/react\"] });\n      } else if (hasTanStackStart) {\n        addPackageDependency({\n          vfs,\n          packagePath: webPath,\n          dependencies: [\"@clerk/tanstack-react-start\"],\n        });\n      }\n    }\n\n    if (hasNative && nativeExists) {\n      addPackageDependency({ vfs, packagePath: nativePath, dependencies: [\"@clerk/expo\"] });\n    }\n\n    if (apiExists) {\n      if (backend === \"self\" || backend === \"hono\" || backend === \"elysia\") {\n        addPackageDependency({ vfs, packagePath: apiPath, dependencies: [\"@clerk/backend\"] });\n      } else if (backend === \"express\") {\n        addPackageDependency({ vfs, packagePath: apiPath, dependencies: [\"@clerk/express\"] });\n      } else if (backend === \"fastify\") {\n        addPackageDependency({ vfs, packagePath: apiPath, dependencies: [\"@clerk/fastify\"] });\n      }\n    }\n\n    if (serverExists) {\n      if (backend === \"express\") {\n        addPackageDependency({ vfs, packagePath: serverPath, dependencies: [\"@clerk/express\"] });\n      } else if (backend === \"fastify\") {\n        addPackageDependency({ vfs, packagePath: serverPath, dependencies: [\"@clerk/fastify\"] });\n      }\n    }\n  } else if (auth === \"better-auth\") {\n    if (authExists) {\n      addPackageDependency({ vfs, packagePath: authPath, dependencies: [\"better-auth\"] });\n      if (hasNative) {\n        addPackageDependency({ vfs, packagePath: authPath, dependencies: [\"@better-auth/expo\"] });\n      }\n    }\n\n    if (hasWebFrontend && webExists) {\n      addPackageDependency({ vfs, packagePath: webPath, dependencies: [\"better-auth\"] });\n\n      if (hasReactWebAuthForms) {\n        addPackageDependency({ vfs, packagePath: webPath, dependencies: [\"@tanstack/react-form\"] });\n      }\n      if (hasSolid) {\n        addPackageDependency({ vfs, packagePath: webPath, dependencies: [\"@tanstack/solid-form\"] });\n      }\n      if (hasSvelte) {\n        addPackageDependency({\n          vfs,\n          packagePath: webPath,\n          dependencies: [\"@tanstack/svelte-form\"],\n        });\n      }\n    }\n\n    if (hasNative && nativeExists) {\n      addPackageDependency({\n        vfs,\n        packagePath: nativePath,\n        dependencies: [\"better-auth\", \"@better-auth/expo\", \"@tanstack/react-form\"],\n      });\n    }\n  }\n}\n"
  },
  {
    "path": "packages/template-generator/src/processors/auth-plugins.ts",
    "content": "import type { ProjectConfig } from \"@better-t-stack/types\";\nimport { Node, Project, SyntaxKind } from \"ts-morph\";\n\nimport type { VirtualFileSystem } from \"../core/virtual-fs\";\n\nexport function processAuthPlugins(vfs: VirtualFileSystem, config: ProjectConfig): void {\n  const authIndexPath = \"packages/auth/src/index.ts\";\n  if (!vfs.exists(authIndexPath)) return;\n\n  const content = vfs.readFile(authIndexPath);\n  const project = new Project({\n    useInMemoryFileSystem: true,\n  });\n\n  const sourceFile = project.createSourceFile(\"index.ts\", content);\n\n  const pluginsToAdd: string[] = [];\n  const importsToAdd: { named: string; module: string }[] = [];\n\n  // TanStack Start Cookies\n  if (config.backend === \"self\" && config.frontend.includes(\"tanstack-start\")) {\n    pluginsToAdd.push(\"tanstackStartCookies()\");\n    importsToAdd.push({\n      named: \"tanstackStartCookies\",\n      module: \"better-auth/tanstack-start\",\n    });\n  }\n\n  // Next.js Cookies\n  if (config.backend === \"self\" && config.frontend.includes(\"next\")) {\n    pluginsToAdd.push(\"nextCookies()\");\n    importsToAdd.push({\n      named: \"nextCookies\",\n      module: \"better-auth/next-js\",\n    });\n  }\n\n  // Expo Plugin\n  const hasNative = config.frontend.some((f) =>\n    [\"native-bare\", \"native-uniwind\", \"native-unistyles\"].includes(f),\n  );\n  if (hasNative) {\n    pluginsToAdd.push(\"expo()\");\n    importsToAdd.push({\n      named: \"expo\",\n      module: \"@better-auth/expo\",\n    });\n  }\n\n  if (pluginsToAdd.length === 0) return;\n\n  // Add imports\n  importsToAdd.forEach(({ named, module }) => {\n    const existingImport = sourceFile.getImportDeclaration((decl) =>\n      decl.getModuleSpecifierValue().includes(module),\n    );\n\n    if (existingImport) {\n      const namedImports = existingImport.getNamedImports();\n      if (!namedImports.some((ni) => ni.getName() === named)) {\n        existingImport.addNamedImport(named);\n      }\n    } else {\n      sourceFile.addImportDeclaration({\n        moduleSpecifier: module,\n        namedImports: [named],\n      });\n    }\n  });\n\n  // Add plugins to the generated betterAuth config, regardless of whether the\n  // file exports a singleton `auth` or wraps the config in `createAuth()`.\n  const betterAuthCall = sourceFile\n    .getDescendantsOfKind(SyntaxKind.CallExpression)\n    .find((callExpression) => {\n      const expression = callExpression.getExpression();\n      return Node.isIdentifier(expression) && expression.getText() === \"betterAuth\";\n    });\n\n  if (betterAuthCall) {\n    const configObject = betterAuthCall\n      .getArguments()[0]\n      ?.asKind(SyntaxKind.ObjectLiteralExpression);\n\n    if (configObject) {\n      const pluginsProp = configObject.getProperty(\"plugins\");\n\n      if (pluginsProp?.isKind(SyntaxKind.PropertyAssignment)) {\n        const arrayLiteral = pluginsProp.getInitializerIfKind(SyntaxKind.ArrayLiteralExpression);\n        if (arrayLiteral) {\n          pluginsToAdd.forEach((plugin) => {\n            const normalizedPlugin = plugin.replace(/\\s/g, \"\");\n            const alreadyExists = arrayLiteral\n              .getElements()\n              .some((element) => element.getText().replace(/\\s/g, \"\") === normalizedPlugin);\n\n            if (!alreadyExists) {\n              arrayLiteral.addElement(plugin);\n            }\n          });\n        }\n      } else {\n        // Create plugins array if it doesn't exist\n        configObject.addPropertyAssignment({\n          name: \"plugins\",\n          initializer: `[${pluginsToAdd.join(\", \")}]`,\n        });\n      }\n    }\n  }\n\n  vfs.writeFile(authIndexPath, sourceFile.getFullText());\n}\n"
  },
  {
    "path": "packages/template-generator/src/processors/backend-deps.ts",
    "content": "import type { ProjectConfig } from \"@better-t-stack/types\";\n\nimport type { VirtualFileSystem } from \"../core/virtual-fs\";\nimport { addPackageDependency, type AvailableDependencies } from \"../utils/add-deps\";\n\nexport function processBackendDeps(vfs: VirtualFileSystem, config: ProjectConfig): void {\n  const { backend, runtime, api, auth } = config;\n\n  if (backend === \"convex\") {\n    const convexPath = \"packages/backend/package.json\";\n    if (vfs.exists(convexPath)) {\n      addPackageDependency({ vfs, packagePath: convexPath, dependencies: [\"convex\"] });\n    }\n    return;\n  }\n\n  const serverPath = \"apps/server/package.json\";\n  if (!vfs.exists(serverPath) || backend === \"self\" || backend === \"none\") return;\n\n  const deps: AvailableDependencies[] = [];\n  const devDeps: AvailableDependencies[] = [];\n\n  if (backend === \"hono\") {\n    deps.push(\"hono\");\n    if (runtime === \"node\") deps.push(\"@hono/node-server\");\n  } else if (backend === \"elysia\") {\n    deps.push(\"elysia\", \"@elysiajs/cors\", \"@sinclair/typebox\");\n    if (runtime === \"node\") deps.push(\"@elysiajs/node\");\n  } else if (backend === \"express\") {\n    deps.push(\"express\", \"cors\");\n    devDeps.push(\"@types/express\", \"@types/cors\");\n  } else if (backend === \"fastify\") {\n    deps.push(\"fastify\", \"@fastify/cors\");\n  }\n\n  if (api === \"trpc\") {\n    deps.push(\"@trpc/server\");\n    if (backend === \"hono\") deps.push(\"@hono/trpc-server\");\n    else if (backend === \"elysia\") deps.push(\"@elysiajs/trpc\");\n  } else if (api === \"orpc\") {\n    deps.push(\"@orpc/server\", \"@orpc/openapi\", \"@orpc/zod\");\n  }\n\n  if (auth === \"better-auth\") deps.push(\"better-auth\");\n\n  if (runtime === \"node\") devDeps.push(\"tsx\", \"@types/node\");\n  else if (runtime === \"bun\") devDeps.push(\"@types/bun\");\n\n  if (deps.length > 0 || devDeps.length > 0) {\n    addPackageDependency({\n      vfs,\n      packagePath: serverPath,\n      dependencies: deps,\n      devDependencies: devDeps,\n    });\n  }\n}\n"
  },
  {
    "path": "packages/template-generator/src/processors/db-deps.ts",
    "content": "import type { ProjectConfig } from \"@better-t-stack/types\";\n\nimport type { VirtualFileSystem } from \"../core/virtual-fs\";\nimport { addPackageDependency, type AvailableDependencies } from \"../utils/add-deps\";\n\nexport function processDatabaseDeps(vfs: VirtualFileSystem, config: ProjectConfig): void {\n  const { database, orm, backend } = config;\n\n  if (backend === \"convex\" || database === \"none\") return;\n\n  const dbPkgPath = \"packages/db/package.json\";\n  const webPkgPath = \"apps/web/package.json\";\n\n  if (!vfs.exists(dbPkgPath)) return;\n  const webNeedsDbRuntime = backend === \"self\" && vfs.exists(webPkgPath);\n\n  if (orm === \"prisma\") {\n    processPrismaDeps(vfs, config, dbPkgPath, webPkgPath, webNeedsDbRuntime);\n  } else if (orm === \"drizzle\") {\n    processDrizzleDeps(vfs, config, dbPkgPath, webPkgPath, webNeedsDbRuntime);\n  } else if (orm === \"mongoose\") {\n    addPackageDependency({ vfs, packagePath: dbPkgPath, dependencies: [\"mongoose\"] });\n  }\n}\n\nfunction processPrismaDeps(\n  vfs: VirtualFileSystem,\n  config: ProjectConfig,\n  dbPkgPath: string,\n  webPkgPath: string,\n  webExists: boolean,\n): void {\n  const { database, dbSetup } = config;\n\n  if (database === \"mongodb\") {\n    addPackageDependency({\n      vfs,\n      packagePath: dbPkgPath,\n      customDependencies: { \"@prisma/client\": \"6.19.0\" },\n      customDevDependencies: { prisma: \"6.19.0\" },\n    });\n    if (webExists) {\n      addPackageDependency({\n        vfs,\n        packagePath: webPkgPath,\n        customDependencies: { \"@prisma/client\": \"6.19.0\" },\n      });\n    }\n    return;\n  }\n\n  const deps: AvailableDependencies[] = [\"@prisma/client\"];\n  const devDeps: AvailableDependencies[] = [\"prisma\"];\n\n  if (database === \"mysql\" && dbSetup === \"planetscale\") {\n    deps.push(\"@prisma/adapter-planetscale\", \"@planetscale/database\");\n  } else if (database === \"mysql\") {\n    deps.push(\"@prisma/adapter-mariadb\");\n  } else if (database === \"sqlite\") {\n    deps.push(dbSetup === \"d1\" ? \"@prisma/adapter-d1\" : \"@prisma/adapter-libsql\");\n  } else if (database === \"postgres\") {\n    if (dbSetup === \"neon\") {\n      deps.push(\"@prisma/adapter-neon\", \"@neondatabase/serverless\");\n    } else if (dbSetup === \"prisma-postgres\") {\n      deps.push(\"@prisma/adapter-pg\", \"pg\");\n      devDeps.push(\"@types/pg\");\n    } else {\n      deps.push(\"@prisma/adapter-pg\", \"pg\");\n      devDeps.push(\"@types/pg\");\n    }\n  }\n\n  addPackageDependency({\n    vfs,\n    packagePath: dbPkgPath,\n    dependencies: deps,\n    devDependencies: devDeps,\n  });\n\n  if (webExists) {\n    addPackageDependency({ vfs, packagePath: webPkgPath, dependencies: [\"@prisma/client\"] });\n  }\n}\n\nfunction processDrizzleDeps(\n  vfs: VirtualFileSystem,\n  config: ProjectConfig,\n  dbPkgPath: string,\n  webPkgPath: string,\n  webExists: boolean,\n): void {\n  const { database, dbSetup } = config;\n\n  if (database === \"sqlite\") {\n    addPackageDependency({\n      vfs,\n      packagePath: dbPkgPath,\n      dependencies: [\"drizzle-orm\", \"@libsql/client\", \"libsql\"],\n      devDependencies: [\"drizzle-kit\"],\n    });\n    if (webExists) {\n      addPackageDependency({\n        vfs,\n        packagePath: webPkgPath,\n        dependencies: [\"@libsql/client\", \"libsql\"],\n      });\n    }\n  } else if (database === \"postgres\") {\n    const deps: AvailableDependencies[] = [\"drizzle-orm\"];\n    const devDeps: AvailableDependencies[] = [\"drizzle-kit\"];\n\n    if (dbSetup === \"neon\") {\n      deps.push(\"@neondatabase/serverless\");\n    } else {\n      deps.push(\"pg\");\n      devDeps.push(\"@types/pg\");\n    }\n\n    addPackageDependency({\n      vfs,\n      packagePath: dbPkgPath,\n      dependencies: deps,\n      devDependencies: devDeps,\n    });\n  } else if (database === \"mysql\") {\n    addPackageDependency({\n      vfs,\n      packagePath: dbPkgPath,\n      dependencies:\n        dbSetup === \"planetscale\"\n          ? [\"drizzle-orm\", \"@planetscale/database\"]\n          : [\"drizzle-orm\", \"mysql2\"],\n      devDependencies: [\"drizzle-kit\"],\n    });\n  }\n}\n"
  },
  {
    "path": "packages/template-generator/src/processors/deploy-deps.ts",
    "content": "import type { ProjectConfig } from \"@better-t-stack/types\";\n\nimport type { VirtualFileSystem } from \"../core/virtual-fs\";\nimport { addPackageDependency } from \"../utils/add-deps\";\n\nexport function processDeployDeps(vfs: VirtualFileSystem, config: ProjectConfig): void {\n  const { webDeploy, serverDeploy, frontend, backend } = config;\n\n  const isCloudflareWeb = webDeploy === \"cloudflare\";\n  const isCloudflareServer = serverDeploy === \"cloudflare\";\n  const isBackendSelf = backend === \"self\";\n\n  if (!isCloudflareWeb && !isCloudflareServer) return;\n\n  if (isCloudflareWeb || isCloudflareServer) {\n    addPackageDependency({\n      vfs,\n      packagePath: \"package.json\",\n      devDependencies: [\"@cloudflare/workers-types\"],\n    });\n  }\n\n  if (isCloudflareServer && !isBackendSelf) {\n    const serverPkgPath = \"apps/server/package.json\";\n    if (vfs.exists(serverPkgPath)) {\n      addPackageDependency({\n        vfs,\n        packagePath: serverPkgPath,\n        devDependencies: [\"alchemy\", \"wrangler\", \"@types/node\", \"@cloudflare/workers-types\"],\n      });\n    }\n  }\n\n  if (isCloudflareWeb) {\n    const webPkgPath = \"apps/web/package.json\";\n    if (!vfs.exists(webPkgPath)) return;\n\n    if (frontend.includes(\"next\")) {\n      addPackageDependency({\n        vfs,\n        packagePath: webPkgPath,\n        dependencies: [\"@opennextjs/cloudflare\"],\n        devDependencies: [\"alchemy\", \"wrangler\", \"@cloudflare/workers-types\"],\n      });\n    } else if (frontend.includes(\"nuxt\")) {\n      addPackageDependency({\n        vfs,\n        packagePath: webPkgPath,\n        devDependencies: [\"alchemy\", \"nitro-cloudflare-dev\", \"wrangler\"],\n      });\n    } else if (frontend.includes(\"svelte\")) {\n      addPackageDependency({\n        vfs,\n        packagePath: webPkgPath,\n        devDependencies: [\"alchemy\", \"@sveltejs/adapter-cloudflare\"],\n      });\n    } else if (frontend.includes(\"tanstack-start\")) {\n      addPackageDependency({\n        vfs,\n        packagePath: webPkgPath,\n        devDependencies: [\"alchemy\", \"@cloudflare/vite-plugin\", \"wrangler\"],\n      });\n    } else if (frontend.includes(\"astro\")) {\n      addPackageDependency({\n        vfs,\n        packagePath: webPkgPath,\n        dependencies: [\"@astrojs/node\"],\n        devDependencies: [\"alchemy\", \"@astrojs/cloudflare\", \"@cloudflare/workers-types\"],\n      });\n    } else if (\n      frontend.includes(\"tanstack-router\") ||\n      frontend.includes(\"react-router\") ||\n      frontend.includes(\"solid\")\n    ) {\n      addPackageDependency({ vfs, packagePath: webPkgPath, devDependencies: [\"alchemy\"] });\n    }\n  }\n}\n"
  },
  {
    "path": "packages/template-generator/src/processors/env-deps.ts",
    "content": "import type { ProjectConfig } from \"@better-t-stack/types\";\n\nimport type { VirtualFileSystem } from \"../core/virtual-fs\";\nimport { addPackageDependency, type AvailableDependencies } from \"../utils/add-deps\";\n\nexport function processEnvDeps(vfs: VirtualFileSystem, config: ProjectConfig): void {\n  const envPath = \"packages/env/package.json\";\n  if (!vfs.exists(envPath)) return;\n\n  const { frontend, backend, runtime, webDeploy } = config;\n  const deps: AvailableDependencies[] = [\"zod\"];\n  const hasNative = frontend.some((value) =>\n    [\"native-bare\", \"native-uniwind\", \"native-unistyles\"].includes(value),\n  );\n  const hasNextJs = frontend.includes(\"next\");\n  const hasNuxt = frontend.includes(\"nuxt\");\n\n  if (hasNextJs) {\n    deps.push(\"@t3-oss/env-nextjs\");\n  } else if (hasNuxt) {\n    deps.push(\"@t3-oss/env-nuxt\");\n  }\n\n  const needsCoreEnv = hasNative || (!hasNextJs && !hasNuxt);\n  if (needsCoreEnv) {\n    deps.push(\"@t3-oss/env-core\");\n  }\n\n  const needsServerEnv = backend !== \"convex\" && backend !== \"none\" && runtime !== \"workers\";\n  if (needsServerEnv && !deps.includes(\"@t3-oss/env-core\")) {\n    deps.push(\"@t3-oss/env-core\");\n  }\n\n  if (backend === \"self\" && webDeploy === \"cloudflare\" && hasNextJs) {\n    deps.push(\"@opennextjs/cloudflare\");\n  }\n\n  addPackageDependency({ vfs, packagePath: envPath, dependencies: deps });\n}\n"
  },
  {
    "path": "packages/template-generator/src/processors/env-vars.ts",
    "content": "import type { ProjectConfig } from \"@better-t-stack/types\";\n\nimport type { VirtualFileSystem } from \"../core/virtual-fs\";\n\nexport interface EnvVariable {\n  key: string;\n  value: string | null | undefined;\n  condition: boolean;\n  comment?: string;\n}\n\ntype AddEnvVariablesOptions = {\n  commentOutEmptyValues?: boolean;\n};\n\nfunction generateRandomString(length: number, charset: string) {\n  let result = \"\";\n  if (\n    typeof globalThis.crypto !== \"undefined\" &&\n    typeof globalThis.crypto.getRandomValues === \"function\"\n  ) {\n    const values = new Uint8Array(length);\n    globalThis.crypto.getRandomValues(values);\n    for (let i = 0; i < length; i++) {\n      const value = values[i];\n      if (value !== undefined) {\n        result += charset[value % charset.length];\n      }\n    }\n    return result;\n  } else {\n    // Fallback for environments without crypto\n    for (let i = 0; i < length; i++) {\n      result += charset[Math.floor(Math.random() * charset.length)];\n    }\n    return result;\n  }\n}\n\nfunction generateAuthSecret() {\n  return generateRandomString(32, \"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789\");\n}\n\nfunction getClientServerVar(frontend: string[], backend: ProjectConfig[\"backend\"]) {\n  const hasNextJs = frontend.includes(\"next\");\n  const hasNuxt = frontend.includes(\"nuxt\");\n  const hasSvelte = frontend.includes(\"svelte\");\n  const hasAstro = frontend.includes(\"astro\");\n  const hasTanstackStart = frontend.includes(\"tanstack-start\");\n\n  // For fullstack self, no base URL is needed (same-origin)\n  if (backend === \"self\") {\n    return { key: \"\", value: \"\", write: false } as const;\n  }\n\n  let key = \"VITE_SERVER_URL\";\n  if (hasNextJs) key = \"NEXT_PUBLIC_SERVER_URL\";\n  else if (hasNuxt) key = \"NUXT_PUBLIC_SERVER_URL\";\n  else if (hasSvelte || hasAstro) key = \"PUBLIC_SERVER_URL\";\n  else if (hasTanstackStart) key = \"VITE_SERVER_URL\";\n\n  return { key, value: \"http://localhost:3000\", write: true } as const;\n}\n\nfunction getConvexVar(frontend: string[]) {\n  const hasNextJs = frontend.includes(\"next\");\n  const hasNuxt = frontend.includes(\"nuxt\");\n  const hasSvelte = frontend.includes(\"svelte\");\n  const hasTanstackStart = frontend.includes(\"tanstack-start\");\n  if (hasNextJs) return \"NEXT_PUBLIC_CONVEX_URL\";\n  if (hasNuxt) return \"NUXT_PUBLIC_CONVEX_URL\";\n  if (hasSvelte) return \"PUBLIC_CONVEX_URL\";\n  if (hasTanstackStart) return \"VITE_CONVEX_URL\";\n  return \"VITE_CONVEX_URL\";\n}\n\nfunction escapeRegExp(value: string): string {\n  return value.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n}\n\nfunction addEnvVariablesToContent(\n  currentContent: string,\n  variables: EnvVariable[],\n  options: AddEnvVariablesOptions = {},\n): string {\n  let envContent = currentContent || \"\";\n  let contentToAdd = \"\";\n\n  for (const { key, value, condition, comment } of variables) {\n    if (condition) {\n      const valueToWrite = value ?? \"\";\n      const shouldComment = options.commentOutEmptyValues === true && valueToWrite.trim() === \"\";\n      const lineToWrite = shouldComment ? `# ${key}=${valueToWrite}` : `${key}=${valueToWrite}`;\n      const lineRegex = new RegExp(`^\\\\s*#?\\\\s*${escapeRegExp(key)}=.*$`, \"m\");\n\n      if (lineRegex.test(envContent)) {\n        const existingMatch = envContent.match(lineRegex);\n        if (existingMatch && existingMatch[0] !== lineToWrite) {\n          envContent = envContent.replace(lineRegex, lineToWrite);\n        }\n      } else {\n        if (comment) {\n          contentToAdd += `# ${comment}\\n`;\n        }\n        contentToAdd += `${lineToWrite}\\n`;\n      }\n    }\n  }\n\n  if (contentToAdd) {\n    if (envContent.length > 0 && !envContent.endsWith(\"\\n\")) {\n      envContent += \"\\n\";\n    }\n    envContent += contentToAdd;\n  }\n\n  return `${envContent.trimEnd()}\\n`;\n}\n\nfunction writeEnvFile(\n  vfs: VirtualFileSystem,\n  envPath: string,\n  variables: EnvVariable[],\n  options: AddEnvVariablesOptions = {},\n): void {\n  let currentContent = \"\";\n  if (vfs.exists(envPath)) {\n    currentContent = vfs.readFile(envPath) || \"\";\n  }\n  const newContent = addEnvVariablesToContent(currentContent, variables, options);\n  vfs.writeFile(envPath, newContent);\n}\n\nfunction buildClientVars(\n  frontend: string[],\n  backend: ProjectConfig[\"backend\"],\n  auth: ProjectConfig[\"auth\"],\n): EnvVariable[] {\n  const hasNextJs = frontend.includes(\"next\");\n  const hasReactRouter = frontend.includes(\"react-router\");\n  const hasTanStackRouter = frontend.includes(\"tanstack-router\");\n  const hasTanStackStart = frontend.includes(\"tanstack-start\");\n\n  const baseVar = getClientServerVar(frontend, backend);\n  const envVarName = backend === \"convex\" ? getConvexVar(frontend) : baseVar.key;\n  const serverUrl = backend === \"convex\" ? \"https://<YOUR_CONVEX_URL>\" : baseVar.value;\n\n  const vars: EnvVariable[] = [\n    {\n      key: envVarName,\n      value: serverUrl,\n      condition: backend === \"convex\" ? true : baseVar.write,\n    },\n  ];\n\n  if (auth === \"clerk\") {\n    if (hasNextJs) {\n      vars.push(\n        {\n          key: \"NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY\",\n          value: \"\",\n          condition: true,\n        },\n        {\n          key: \"CLERK_SECRET_KEY\",\n          value: \"\",\n          condition: true,\n        },\n      );\n    } else if (hasReactRouter || hasTanStackRouter || hasTanStackStart) {\n      vars.push({\n        key: \"VITE_CLERK_PUBLISHABLE_KEY\",\n        value: \"\",\n        condition: true,\n      });\n      if (hasReactRouter || hasTanStackStart) {\n        vars.push({\n          key: \"CLERK_SECRET_KEY\",\n          value: \"\",\n          condition: true,\n        });\n      }\n    }\n  }\n\n  if (backend === \"convex\" && auth === \"better-auth\") {\n    if (hasNextJs) {\n      vars.push({\n        key: \"NEXT_PUBLIC_CONVEX_SITE_URL\",\n        value: \"https://<YOUR_CONVEX_URL>\",\n        condition: true,\n      });\n    } else if (hasReactRouter || hasTanStackRouter || hasTanStackStart) {\n      vars.push({\n        key: \"VITE_CONVEX_SITE_URL\",\n        value: \"https://<YOUR_CONVEX_URL>\",\n        condition: true,\n      });\n    }\n  }\n\n  return vars;\n}\n\nfunction buildNativeVars(\n  frontend: string[],\n  backend: ProjectConfig[\"backend\"],\n  auth: ProjectConfig[\"auth\"],\n): EnvVariable[] {\n  const hasAstro = frontend.includes(\"astro\");\n  const hasSvelte = frontend.includes(\"svelte\");\n\n  let envVarName = \"EXPO_PUBLIC_SERVER_URL\";\n  let serverUrl = \"http://localhost:3000\";\n\n  if (backend === \"self\") {\n    // SvelteKit uses Vite's default port, Astro uses 4321, others use 3001.\n    serverUrl = hasSvelte\n      ? \"http://localhost:5173\"\n      : hasAstro\n        ? \"http://localhost:4321\"\n        : \"http://localhost:3001\";\n  }\n\n  if (backend === \"convex\") {\n    envVarName = \"EXPO_PUBLIC_CONVEX_URL\";\n    serverUrl = \"https://<YOUR_CONVEX_URL>\";\n  }\n\n  const vars: EnvVariable[] = [\n    {\n      key: envVarName,\n      value: serverUrl,\n      condition: true,\n    },\n  ];\n\n  if (auth === \"clerk\") {\n    vars.push({\n      key: \"EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY\",\n      value: \"\",\n      condition: true,\n    });\n  }\n\n  if (backend === \"convex\" && auth === \"better-auth\") {\n    vars.push({\n      key: \"EXPO_PUBLIC_CONVEX_SITE_URL\",\n      value: \"https://<YOUR_CONVEX_URL>\",\n      condition: true,\n    });\n  }\n\n  return vars;\n}\n\nfunction buildConvexBackendVars(\n  frontend: string[],\n  auth: ProjectConfig[\"auth\"],\n  examples: ProjectConfig[\"examples\"],\n): EnvVariable[] {\n  const hasNextJs = frontend.includes(\"next\");\n  const hasNative =\n    frontend.includes(\"native-bare\") ||\n    frontend.includes(\"native-uniwind\") ||\n    frontend.includes(\"native-unistyles\");\n  const hasWeb =\n    frontend.includes(\"react-router\") ||\n    frontend.includes(\"tanstack-router\") ||\n    frontend.includes(\"tanstack-start\") ||\n    hasNextJs ||\n    frontend.includes(\"nuxt\") ||\n    frontend.includes(\"solid\") ||\n    frontend.includes(\"svelte\") ||\n    frontend.includes(\"astro\");\n  const defaultSiteUrl =\n    hasNative && !hasWeb\n      ? \"http://localhost:8081\"\n      : frontend.includes(\"react-router\") || frontend.includes(\"svelte\")\n        ? \"http://localhost:5173\"\n        : frontend.includes(\"astro\")\n          ? \"http://localhost:4321\"\n          : \"http://localhost:3001\";\n\n  const vars: EnvVariable[] = [];\n\n  if (examples?.includes(\"ai\")) {\n    vars.push({\n      key: \"GOOGLE_GENERATIVE_AI_API_KEY\",\n      value: \"\",\n      condition: true,\n      comment: \"Google AI API key for AI agent\",\n    });\n  }\n\n  if (auth === \"better-auth\") {\n    if (hasNative) {\n      vars.push({\n        key: \"EXPO_PUBLIC_CONVEX_SITE_URL\",\n        value: \"\",\n        condition: true,\n        comment: \"Same as CONVEX_URL but ends in .site\",\n      });\n    }\n\n    if (hasWeb) {\n      vars.push(\n        {\n          key: hasNextJs ? \"NEXT_PUBLIC_CONVEX_SITE_URL\" : \"VITE_CONVEX_SITE_URL\",\n          value: \"\",\n          condition: true,\n          comment: \"Same as CONVEX_URL but ends in .site\",\n        },\n        {\n          key: \"SITE_URL\",\n          value: defaultSiteUrl,\n          condition: true,\n          comment: \"Web app URL for authentication\",\n        },\n      );\n    } else if (hasNative) {\n      vars.push({\n        key: \"SITE_URL\",\n        value: defaultSiteUrl,\n        condition: true,\n        comment: \"Web app URL for authentication (for Expo web support)\",\n      });\n    }\n  }\n\n  return vars;\n}\n\nfunction buildConvexCommentBlocks(\n  frontend: string[],\n  auth: ProjectConfig[\"auth\"],\n  examples: ProjectConfig[\"examples\"],\n): string {\n  const hasNative =\n    frontend.includes(\"native-bare\") ||\n    frontend.includes(\"native-uniwind\") ||\n    frontend.includes(\"native-unistyles\");\n  const hasWeb =\n    frontend.includes(\"react-router\") ||\n    frontend.includes(\"tanstack-router\") ||\n    frontend.includes(\"tanstack-start\") ||\n    frontend.includes(\"next\") ||\n    frontend.includes(\"nuxt\") ||\n    frontend.includes(\"solid\") ||\n    frontend.includes(\"svelte\") ||\n    frontend.includes(\"astro\");\n  const defaultSiteUrl =\n    hasNative && !hasWeb\n      ? \"http://localhost:8081\"\n      : frontend.includes(\"react-router\") || frontend.includes(\"svelte\")\n        ? \"http://localhost:5173\"\n        : frontend.includes(\"astro\")\n          ? \"http://localhost:4321\"\n          : \"http://localhost:3001\";\n\n  let commentBlocks = \"\";\n\n  if (examples?.includes(\"ai\")) {\n    commentBlocks += `# Set Google AI API key for AI agent\n# npx convex env set GOOGLE_GENERATIVE_AI_API_KEY=your_google_api_key\n\n`;\n  }\n\n  if (auth === \"better-auth\") {\n    commentBlocks += `# Set Convex environment variables\n# npx convex env set BETTER_AUTH_SECRET=$(openssl rand -base64 32)\n${hasWeb || hasNative ? `# npx convex env set SITE_URL ${defaultSiteUrl}\\n` : \"\"}`;\n  }\n\n  return commentBlocks;\n}\n\nfunction buildServerVars(\n  backend: ProjectConfig[\"backend\"],\n  frontend: string[],\n  auth: ProjectConfig[\"auth\"],\n  api: ProjectConfig[\"api\"],\n  database: ProjectConfig[\"database\"],\n  dbSetup: ProjectConfig[\"dbSetup\"],\n  runtime: ProjectConfig[\"runtime\"],\n  webDeploy: ProjectConfig[\"webDeploy\"],\n  serverDeploy: ProjectConfig[\"serverDeploy\"],\n  payments: ProjectConfig[\"payments\"],\n  examples: ProjectConfig[\"examples\"],\n): EnvVariable[] {\n  const hasReactRouter = frontend.includes(\"react-router\");\n  const hasSvelte = frontend.includes(\"svelte\");\n  const hasAstro = frontend.includes(\"astro\");\n\n  let corsOrigin = \"http://localhost:3001\";\n  if (hasAstro) {\n    corsOrigin = \"http://localhost:4321\";\n  } else if (hasReactRouter || hasSvelte) {\n    corsOrigin = \"http://localhost:5173\";\n  }\n\n  let databaseUrl: string | null = null;\n  if (database !== \"none\" && dbSetup === \"none\") {\n    switch (database) {\n      case \"postgres\":\n        databaseUrl = \"postgresql://postgres:password@localhost:5432/postgres\";\n        break;\n      case \"mysql\":\n        databaseUrl = \"mysql://root:password@localhost:3306/mydb\";\n        break;\n      case \"mongodb\":\n        databaseUrl = \"mongodb://localhost:27017/mydatabase\";\n        break;\n      case \"sqlite\":\n        if (runtime === \"workers\" || webDeploy === \"cloudflare\" || serverDeploy === \"cloudflare\") {\n          databaseUrl = \"http://127.0.0.1:8080\";\n        } else {\n          databaseUrl = \"file:../../local.db\";\n        }\n        break;\n    }\n  }\n\n  const hasBetterAuth = auth === \"better-auth\";\n  const hasClerk = auth === \"clerk\";\n  const needsClerkPublishableKey =\n    hasClerk &&\n    ([\"express\", \"fastify\"].includes(backend) ||\n      (api !== \"none\" && [\"self\", \"hono\", \"elysia\"].includes(backend)));\n\n  return [\n    {\n      key: \"BETTER_AUTH_SECRET\",\n      value: generateAuthSecret(),\n      condition: hasBetterAuth,\n    },\n    {\n      key: \"BETTER_AUTH_URL\",\n      value:\n        backend === \"self\"\n          ? hasSvelte\n            ? \"http://localhost:5173\"\n            : hasAstro\n              ? \"http://localhost:4321\"\n              : \"http://localhost:3001\"\n          : \"http://localhost:3000\",\n      condition: hasBetterAuth,\n    },\n    {\n      key: \"CLERK_SECRET_KEY\",\n      value: \"\",\n      condition: hasClerk,\n    },\n    {\n      key: \"CLERK_PUBLISHABLE_KEY\",\n      value: \"\",\n      condition: needsClerkPublishableKey,\n    },\n    {\n      key: \"POLAR_ACCESS_TOKEN\",\n      value: \"\",\n      condition: payments === \"polar\",\n    },\n    {\n      key: \"POLAR_SUCCESS_URL\",\n      value: `${corsOrigin}/success?checkout_id={CHECKOUT_ID}`,\n      condition: payments === \"polar\",\n    },\n    {\n      key: \"CORS_ORIGIN\",\n      value: corsOrigin,\n      condition: true,\n    },\n    {\n      key: \"GOOGLE_GENERATIVE_AI_API_KEY\",\n      value: \"\",\n      condition: examples?.includes(\"ai\") || false,\n    },\n    {\n      key: \"DATABASE_URL\",\n      value: databaseUrl,\n      condition: database !== \"none\" && dbSetup === \"none\",\n    },\n  ];\n}\n\nexport function processEnvVariables(vfs: VirtualFileSystem, config: ProjectConfig): void {\n  const {\n    backend,\n    frontend,\n    database,\n    auth,\n    api,\n    examples,\n    dbSetup,\n    webDeploy,\n    serverDeploy,\n    runtime,\n    payments,\n  } = config;\n\n  const hasReactRouter = frontend.includes(\"react-router\");\n  const hasTanStackRouter = frontend.includes(\"tanstack-router\");\n  const hasTanStackStart = frontend.includes(\"tanstack-start\");\n  const hasNextJs = frontend.includes(\"next\");\n  const hasNuxt = frontend.includes(\"nuxt\");\n  const hasSvelte = frontend.includes(\"svelte\");\n  const hasSolid = frontend.includes(\"solid\");\n  const hasAstro = frontend.includes(\"astro\");\n  const hasWebFrontend =\n    hasReactRouter ||\n    hasTanStackRouter ||\n    hasTanStackStart ||\n    hasNextJs ||\n    hasNuxt ||\n    hasSolid ||\n    hasSvelte ||\n    hasAstro;\n\n  // --- Client App .env ---\n  if (hasWebFrontend) {\n    const clientDir = \"apps/web\";\n    if (vfs.directoryExists(clientDir)) {\n      const envPath = `${clientDir}/.env`;\n      const clientVars = buildClientVars(frontend, backend, auth);\n      writeEnvFile(vfs, envPath, clientVars);\n    }\n  }\n\n  // --- Native App .env ---\n  if (\n    frontend.includes(\"native-bare\") ||\n    frontend.includes(\"native-uniwind\") ||\n    frontend.includes(\"native-unistyles\")\n  ) {\n    const nativeDir = \"apps/native\";\n    if (vfs.directoryExists(nativeDir)) {\n      const envPath = `${nativeDir}/.env`;\n      const nativeVars = buildNativeVars(frontend, backend, auth);\n      writeEnvFile(vfs, envPath, nativeVars);\n    }\n  }\n\n  // --- Convex Backend .env.local ---\n  if (backend === \"convex\") {\n    const convexBackendDir = \"packages/backend\";\n    if (vfs.directoryExists(convexBackendDir)) {\n      const envLocalPath = `${convexBackendDir}/.env.local`;\n\n      // Write comment blocks first\n      const commentBlocks = buildConvexCommentBlocks(frontend, auth, examples);\n      if (commentBlocks) {\n        let currentContent = \"\";\n        if (vfs.exists(envLocalPath)) {\n          currentContent = vfs.readFile(envLocalPath) || \"\";\n        }\n        vfs.writeFile(envLocalPath, commentBlocks + currentContent);\n      }\n\n      // Then add variables\n      const convexBackendVars = buildConvexBackendVars(frontend, auth, examples);\n      if (convexBackendVars.length > 0) {\n        let existingContent = \"\";\n        if (vfs.exists(envLocalPath)) {\n          existingContent = vfs.readFile(envLocalPath) || \"\";\n        }\n        const contentWithVars = addEnvVariablesToContent(existingContent, convexBackendVars, {\n          commentOutEmptyValues: true,\n        });\n        vfs.writeFile(envLocalPath, contentWithVars);\n      }\n    }\n    return;\n  }\n\n  // --- Server App .env ---\n  const serverVars = buildServerVars(\n    backend,\n    frontend,\n    auth,\n    api,\n    database,\n    dbSetup,\n    runtime,\n    webDeploy,\n    serverDeploy,\n    payments,\n    examples,\n  );\n\n  if (backend === \"self\") {\n    const webDir = \"apps/web\";\n    if (vfs.directoryExists(webDir)) {\n      const envPath = `${webDir}/.env`;\n      writeEnvFile(vfs, envPath, serverVars);\n    }\n  } else if (vfs.directoryExists(\"apps/server\")) {\n    const envPath = \"apps/server/.env\";\n    writeEnvFile(vfs, envPath, serverVars);\n  }\n\n  // --- Alchemy Infra .env ---\n  const isUnifiedAlchemy = webDeploy === \"cloudflare\" && serverDeploy === \"cloudflare\";\n  const isIndividualAlchemy = webDeploy === \"cloudflare\" || serverDeploy === \"cloudflare\";\n\n  if (isUnifiedAlchemy || isIndividualAlchemy) {\n    const infraDir = \"packages/infra\";\n    if (vfs.directoryExists(infraDir)) {\n      const envPath = `${infraDir}/.env`;\n      const infraAlchemyVars: EnvVariable[] = [\n        {\n          key: \"ALCHEMY_PASSWORD\",\n          value: \"please-change-this\",\n          condition: true,\n        },\n      ];\n      writeEnvFile(vfs, envPath, infraAlchemyVars);\n    }\n  }\n}\n"
  },
  {
    "path": "packages/template-generator/src/processors/examples-deps.ts",
    "content": "import type { ProjectConfig } from \"@better-t-stack/types\";\n\nimport type { VirtualFileSystem } from \"../core/virtual-fs\";\nimport { addPackageDependency, type AvailableDependencies } from \"../utils/add-deps\";\n\nexport function processExamplesDeps(vfs: VirtualFileSystem, config: ProjectConfig): void {\n  if (!config.examples || config.examples.length === 0 || config.examples[0] === \"none\") return;\n\n  if (\n    config.examples.includes(\"todo\") &&\n    config.backend !== \"convex\" &&\n    config.backend !== \"none\"\n  ) {\n    setupTodoDependencies(vfs, config);\n  }\n\n  if (config.examples.includes(\"ai\")) {\n    setupAIDependencies(vfs, config);\n  }\n}\n\nfunction setupTodoDependencies(vfs: VirtualFileSystem, config: ProjectConfig): void {\n  const { orm, database, backend } = config;\n  const apiPkgPath = \"packages/api/package.json\";\n  if (!vfs.exists(apiPkgPath) || backend === \"none\") return;\n\n  if (orm === \"drizzle\") {\n    const deps: AvailableDependencies[] = [\"drizzle-orm\"];\n    if (database === \"postgres\") deps.push(\"@types/pg\");\n    addPackageDependency({ vfs, packagePath: apiPkgPath, dependencies: deps });\n  } else if (orm === \"prisma\") {\n    addPackageDependency({ vfs, packagePath: apiPkgPath, dependencies: [\"@prisma/client\"] });\n  } else if (orm === \"mongoose\") {\n    addPackageDependency({ vfs, packagePath: apiPkgPath, dependencies: [\"mongoose\"] });\n  }\n}\n\nfunction setupAIDependencies(vfs: VirtualFileSystem, config: ProjectConfig): void {\n  const { frontend, backend } = config;\n\n  const webPkgPath = \"apps/web/package.json\";\n  const nativePkgPath = \"apps/native/package.json\";\n  const serverPkgPath = \"apps/server/package.json\";\n  const convexBackendPkgPath = \"packages/backend/package.json\";\n\n  const webExists = vfs.exists(webPkgPath);\n  const nativeExists = vfs.exists(nativePkgPath);\n  const serverExists = vfs.exists(serverPkgPath);\n  const convexBackendExists = vfs.exists(convexBackendPkgPath);\n\n  const hasReactWeb = frontend.some((f) =>\n    [\"react-router\", \"tanstack-router\", \"next\", \"tanstack-start\"].includes(f),\n  );\n  const hasNuxt = frontend.includes(\"nuxt\");\n  const hasSvelte = frontend.includes(\"svelte\");\n  const hasReactNative = frontend.some((f) =>\n    [\"native-bare\", \"native-uniwind\", \"native-unistyles\"].includes(f),\n  );\n\n  if (backend === \"convex\" && convexBackendExists) {\n    addPackageDependency({\n      vfs,\n      packagePath: convexBackendPkgPath,\n      dependencies: [\"@convex-dev/agent\"],\n      customDependencies: { ai: \"^5.0.117\", \"@ai-sdk/google\": \"^2.0.52\" },\n    });\n  } else if (backend === \"self\" && webExists) {\n    addPackageDependency({\n      vfs,\n      packagePath: webPkgPath,\n      dependencies: [\"ai\", \"@ai-sdk/google\", \"@ai-sdk/devtools\"],\n    });\n  } else if (serverExists && backend !== \"none\") {\n    addPackageDependency({\n      vfs,\n      packagePath: serverPkgPath,\n      dependencies: [\"ai\", \"@ai-sdk/google\", \"@ai-sdk/devtools\"],\n    });\n  }\n\n  if (webExists) {\n    const deps: AvailableDependencies[] = [];\n    if (backend === \"convex\") {\n      if (hasReactWeb) deps.push(\"@convex-dev/agent\", \"streamdown\");\n    } else {\n      deps.push(\"ai\");\n      if (hasNuxt) deps.push(\"@ai-sdk/vue\");\n      else if (hasSvelte) deps.push(\"@ai-sdk/svelte\");\n      else if (hasReactWeb) deps.push(\"@ai-sdk/react\", \"streamdown\");\n    }\n    if (deps.length > 0) {\n      addPackageDependency({ vfs, packagePath: webPkgPath, dependencies: deps });\n    }\n  }\n\n  if (nativeExists && hasReactNative) {\n    if (backend === \"convex\") {\n      addPackageDependency({\n        vfs,\n        packagePath: nativePkgPath,\n        dependencies: [\"@convex-dev/agent\"],\n      });\n    } else {\n      addPackageDependency({\n        vfs,\n        packagePath: nativePkgPath,\n        dependencies: [\"ai\", \"@ai-sdk/react\"],\n      });\n    }\n  }\n}\n"
  },
  {
    "path": "packages/template-generator/src/processors/frontend-deps.ts",
    "content": "import type { ProjectConfig } from \"@better-t-stack/types\";\n\nimport type { VirtualFileSystem } from \"../core/virtual-fs\";\nimport { addPackageDependency } from \"../utils/add-deps\";\n\nexport function processFrontendDeps(vfs: VirtualFileSystem, config: ProjectConfig): void {\n  const { frontend, webDeploy } = config;\n\n  if (!frontend.includes(\"astro\") || webDeploy === \"cloudflare\") return;\n\n  const webPackagePath = \"apps/web/package.json\";\n  if (!vfs.exists(webPackagePath)) return;\n\n  addPackageDependency({\n    vfs,\n    packagePath: webPackagePath,\n    dependencies: [\"@astrojs/node\"],\n  });\n}\n"
  },
  {
    "path": "packages/template-generator/src/processors/index.ts",
    "content": "import type { ProjectConfig } from \"@better-t-stack/types\";\n\nimport type { VirtualFileSystem } from \"../core/virtual-fs\";\nimport { processAddonsDeps } from \"./addons-deps\";\nimport { processAlchemyPlugins } from \"./alchemy-plugins\";\nimport { processApiDeps } from \"./api-deps\";\nimport { processAuthDeps } from \"./auth-deps\";\nimport { processAuthPlugins } from \"./auth-plugins\";\nimport { processBackendDeps } from \"./backend-deps\";\nimport { processDatabaseDeps } from \"./db-deps\";\nimport { processDeployDeps } from \"./deploy-deps\";\nimport { processEnvDeps } from \"./env-deps\";\nimport { processEnvVariables } from \"./env-vars\";\nimport { processExamplesDeps } from \"./examples-deps\";\nimport { processFrontendDeps } from \"./frontend-deps\";\nimport { processInfraDeps } from \"./infra-deps\";\nimport { processNxConfig } from \"./nx-generator\";\nimport { processPaymentsDeps } from \"./payments-deps\";\nimport { processPwaPlugins } from \"./pwa-plugins\";\nimport { processReadme } from \"./readme-generator\";\nimport { processRuntimeDeps } from \"./runtime-deps\";\nimport { processTurboConfig } from \"./turbo-generator\";\nimport { processWorkspaceDeps } from \"./workspace-deps\";\n\nexport function processDependencies(vfs: VirtualFileSystem, config: ProjectConfig): void {\n  processWorkspaceDeps(vfs, config);\n  processFrontendDeps(vfs, config);\n  processEnvDeps(vfs, config);\n  processInfraDeps(vfs, config);\n  processDatabaseDeps(vfs, config);\n  processBackendDeps(vfs, config);\n  processRuntimeDeps(vfs, config);\n  processApiDeps(vfs, config);\n  processAuthDeps(vfs, config);\n  processPaymentsDeps(vfs, config);\n  processDeployDeps(vfs, config);\n  processAddonsDeps(vfs, config);\n  processExamplesDeps(vfs, config);\n  processTurboConfig(vfs, config);\n  processNxConfig(vfs, config);\n}\n\nexport {\n  processAddonsDeps,\n  processApiDeps,\n  processAuthDeps,\n  processBackendDeps,\n  processDatabaseDeps,\n  processDeployDeps,\n  processEnvDeps,\n  processExamplesDeps,\n  processFrontendDeps,\n  processInfraDeps,\n  processPaymentsDeps,\n  processNxConfig,\n  processReadme,\n  processRuntimeDeps,\n  processTurboConfig,\n  processWorkspaceDeps,\n  processAuthPlugins,\n  processAlchemyPlugins,\n  processPwaPlugins,\n  processEnvVariables,\n};\n"
  },
  {
    "path": "packages/template-generator/src/processors/infra-deps.ts",
    "content": "import type { ProjectConfig } from \"@better-t-stack/types\";\n\nimport type { VirtualFileSystem } from \"../core/virtual-fs\";\nimport { addPackageDependency } from \"../utils/add-deps\";\n\nexport function processInfraDeps(vfs: VirtualFileSystem, config: ProjectConfig): void {\n  const infraPath = \"packages/infra/package.json\";\n  if (!vfs.exists(infraPath)) return;\n\n  const { serverDeploy, webDeploy } = config;\n  if (serverDeploy === \"cloudflare\" || webDeploy === \"cloudflare\") {\n    addPackageDependency({ vfs, packagePath: infraPath, devDependencies: [\"alchemy\"] });\n  }\n}\n"
  },
  {
    "path": "packages/template-generator/src/processors/nx-generator.ts",
    "content": "/**\n * Nx config generator\n * Generates a minimal nx.json for workspace orchestration when the Nx addon is selected.\n */\n\nimport type { ProjectConfig } from \"@better-t-stack/types\";\n\nimport type { VirtualFileSystem } from \"../core/virtual-fs\";\nimport { getDbScriptSupport, type DbScriptSupport } from \"../utils/db-scripts\";\n\ntype NxTargetDefaults = {\n  dependsOn?: string[];\n  inputs?: string[];\n  cache?: boolean;\n};\n\ntype NxConfig = {\n  $schema: string;\n  namedInputs: Record<string, string[]>;\n  targetDefaults: Record<string, NxTargetDefaults>;\n};\n\nexport function processNxConfig(vfs: VirtualFileSystem, config: ProjectConfig): void {\n  if (!config.addons.includes(\"nx\")) return;\n\n  const nxConfig = generateNxConfig(config);\n  vfs.writeFile(\"nx.json\", JSON.stringify(nxConfig, null, \"\\t\"));\n}\n\nfunction generateNxConfig(config: ProjectConfig): NxConfig {\n  const { backend, database, dbSetup, webDeploy, serverDeploy } = config;\n  const isConvex = backend === \"convex\";\n  const dbSupport = getDbScriptSupport(config);\n  const hasDatabase = dbSupport.hasDbScripts;\n  const isDocker = dbSetup === \"docker\";\n  const isSqliteLocal = database === \"sqlite\" && dbSetup !== \"d1\" && hasDatabase;\n  const hasCloudflare = webDeploy === \"cloudflare\" || serverDeploy === \"cloudflare\";\n\n  const targetDefaults: Record<string, NxTargetDefaults> = {\n    build: {\n      dependsOn: [\"^build\"],\n      inputs: [\"production\", \"^production\"],\n    },\n    \"check-types\": {\n      dependsOn: [\"^check-types\"],\n      inputs: [\"default\", \"^default\"],\n    },\n    dev: {\n      cache: false,\n    },\n    ...(isConvex ? getConvexTargets() : {}),\n    ...(!isConvex && hasDatabase ? getDatabaseTargets(dbSupport) : {}),\n    ...(isDocker ? getDockerTargets() : {}),\n    ...(isSqliteLocal ? getSqliteLocalTarget() : {}),\n    ...(hasCloudflare ? getDeployTargets() : {}),\n  };\n\n  return {\n    $schema: \"./node_modules/nx/schemas/nx-schema.json\",\n    namedInputs: {\n      default: [\"{projectRoot}/**/*\", \"sharedGlobals\"],\n      production: [\n        \"default\",\n        \"!{projectRoot}/**/?(*.)+(spec|test).[jt]s?(x)?(.snap)\",\n        \"!{projectRoot}/tsconfig.spec.json\",\n      ],\n      sharedGlobals: [],\n    },\n    targetDefaults,\n  };\n}\n\nfunction getConvexTargets(): Record<string, NxTargetDefaults> {\n  return {\n    \"dev:setup\": {\n      cache: false,\n    },\n  };\n}\n\nfunction getDatabaseTargets(dbSupport: DbScriptSupport): Record<string, NxTargetDefaults> {\n  const targets: Record<string, NxTargetDefaults> = {};\n\n  if (dbSupport.hasDbPush) {\n    targets[\"db:push\"] = { cache: false };\n  }\n\n  if (dbSupport.hasDbGenerate) {\n    targets[\"db:generate\"] = { cache: false };\n  }\n\n  if (dbSupport.hasDbMigrate) {\n    targets[\"db:migrate\"] = { cache: false };\n  }\n\n  if (dbSupport.hasDbStudio) {\n    targets[\"db:studio\"] = { cache: false };\n  }\n\n  return targets;\n}\n\nfunction getDockerTargets(): Record<string, NxTargetDefaults> {\n  return {\n    \"db:start\": { cache: false },\n    \"db:stop\": { cache: false },\n    \"db:watch\": { cache: false },\n    \"db:down\": { cache: false },\n  };\n}\n\nfunction getSqliteLocalTarget(): Record<string, NxTargetDefaults> {\n  return {\n    \"db:local\": { cache: false },\n  };\n}\n\nfunction getDeployTargets(): Record<string, NxTargetDefaults> {\n  return {\n    deploy: { cache: false },\n    destroy: { cache: false },\n  };\n}\n"
  },
  {
    "path": "packages/template-generator/src/processors/payments-deps.ts",
    "content": "import type { ProjectConfig } from \"@better-t-stack/types\";\n\nimport type { VirtualFileSystem } from \"../core/virtual-fs\";\nimport { addPackageDependency } from \"../utils/add-deps\";\n\nexport function processPaymentsDeps(vfs: VirtualFileSystem, config: ProjectConfig): void {\n  const { payments, frontend } = config;\n  if (!payments || payments === \"none\") return;\n\n  const authPath = \"packages/auth/package.json\";\n  const webPath = \"apps/web/package.json\";\n\n  if (payments === \"polar\") {\n    if (vfs.exists(authPath)) {\n      addPackageDependency({\n        vfs,\n        packagePath: authPath,\n        dependencies: [\"@polar-sh/better-auth\", \"@polar-sh/sdk\"],\n      });\n    }\n\n    if (vfs.exists(webPath)) {\n      const hasWebFrontend = frontend.some((f) =>\n        [\n          \"react-router\",\n          \"tanstack-router\",\n          \"tanstack-start\",\n          \"next\",\n          \"nuxt\",\n          \"svelte\",\n          \"solid\",\n          \"astro\",\n        ].includes(f),\n      );\n      if (hasWebFrontend) {\n        addPackageDependency({\n          vfs,\n          packagePath: webPath,\n          dependencies: [\"@polar-sh/better-auth\"],\n        });\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "packages/template-generator/src/processors/pwa-plugins.ts",
    "content": "import type { ProjectConfig } from \"@better-t-stack/types\";\nimport { IndentationText, Node, Project, QuoteKind } from \"ts-morph\";\n\nimport type { VirtualFileSystem } from \"../core/virtual-fs\";\n\nexport function processPwaPlugins(vfs: VirtualFileSystem, config: ProjectConfig): void {\n  const { addons, projectName } = config;\n\n  if (!addons.includes(\"pwa\")) return;\n\n  const viteConfigPath = \"apps/web/vite.config.ts\";\n  if (!vfs.exists(viteConfigPath)) return;\n\n  const content = vfs.readFile(viteConfigPath);\n  const project = new Project({\n    useInMemoryFileSystem: true,\n    manipulationSettings: {\n      indentationText: IndentationText.TwoSpaces,\n      quoteKind: QuoteKind.Double,\n    },\n  });\n\n  const sourceFile = project.createSourceFile(\"vite.config.ts\", content);\n\n  const hasImport = sourceFile\n    .getImportDeclarations()\n    .some((imp) => imp.getModuleSpecifierValue() === \"vite-plugin-pwa\");\n\n  if (!hasImport) {\n    sourceFile.addImportDeclaration({\n      namedImports: [\"VitePWA\"],\n      moduleSpecifier: \"vite-plugin-pwa\",\n    });\n  }\n\n  const exportAssignment = sourceFile.getExportAssignment((d) => !d.isExportEquals());\n  if (!exportAssignment) return;\n\n  const defineConfigCall = exportAssignment.getExpression();\n  if (\n    !Node.isCallExpression(defineConfigCall) ||\n    defineConfigCall.getExpression().getText() !== \"defineConfig\"\n  ) {\n    return;\n  }\n\n  let configObject = defineConfigCall.getArguments()[0];\n  if (!configObject) {\n    configObject = defineConfigCall.addArgument(\"{}\");\n  }\n\n  if (Node.isObjectLiteralExpression(configObject)) {\n    const pluginsProperty = configObject.getProperty(\"plugins\");\n\n    const pwaConfig = `VitePWA({\n  registerType: \"autoUpdate\",\n  manifest: {\n    name: \"${projectName}\",\n    short_name: \"${projectName}\",\n    description: \"${projectName} - PWA Application\",\n    theme_color: \"#0c0c0c\",\n  },\n  pwaAssets: { disabled: false, config: true },\n  devOptions: { enabled: true },\n})`;\n\n    if (pluginsProperty && Node.isPropertyAssignment(pluginsProperty)) {\n      const initializer = pluginsProperty.getInitializer();\n      if (Node.isArrayLiteralExpression(initializer)) {\n        const hasPwa = initializer.getElements().some((el) => el.getText().startsWith(\"VitePWA(\"));\n        if (!hasPwa) {\n          initializer.addElement(pwaConfig);\n        }\n      }\n    } else {\n      configObject.addPropertyAssignment({\n        name: \"plugins\",\n        initializer: `[${pwaConfig}]`,\n      });\n    }\n  }\n\n  vfs.writeFile(viteConfigPath, sourceFile.getFullText());\n}\n"
  },
  {
    "path": "packages/template-generator/src/processors/readme-generator.ts",
    "content": "import type { ProjectConfig } from \"@better-t-stack/types\";\n\nimport type { VirtualFileSystem } from \"../core/virtual-fs\";\nimport { getDbScriptSupport } from \"../utils/db-scripts\";\n\nfunction getDesktopStaticBuildNote(frontend: ProjectConfig[\"frontend\"]): string {\n  const staticBuildFrontends = new Map([\n    [\"tanstack-start\", \"TanStack Start\"],\n    [\"next\", \"Next.js\"],\n    [\"nuxt\", \"Nuxt\"],\n    [\"svelte\", \"SvelteKit\"],\n    [\"astro\", \"Astro\"],\n  ]);\n\n  const staticBuildFrontend = frontend.find((value) => staticBuildFrontends.has(value));\n  if (!staticBuildFrontend) {\n    return \"\";\n  }\n\n  return `Desktop builds package static web assets. ${staticBuildFrontends.get(\n    staticBuildFrontend,\n  )} needs a static/export build configuration before desktop packaging will work.`;\n}\n\nfunction getClerkQuickstartUrl(frontend: ProjectConfig[\"frontend\"]): string {\n  if (frontend.includes(\"next\")) return \"https://clerk.com/docs/nextjs/getting-started/quickstart\";\n  if (frontend.includes(\"react-router\")) {\n    return \"https://clerk.com/docs/react-router/getting-started/quickstart\";\n  }\n  if (frontend.includes(\"tanstack-start\")) {\n    return \"https://clerk.com/docs/tanstack-react-start/getting-started/quickstart\";\n  }\n  if (frontend.includes(\"tanstack-router\")) {\n    return \"https://clerk.com/docs/react/getting-started/quickstart\";\n  }\n  if (\n    frontend.includes(\"native-bare\") ||\n    frontend.includes(\"native-uniwind\") ||\n    frontend.includes(\"native-unistyles\")\n  ) {\n    return \"https://clerk.com/docs/expo/getting-started/quickstart\";\n  }\n\n  return \"https://clerk.com/docs\";\n}\n\nfunction getClerkFrontendEnvLines(frontend: ProjectConfig[\"frontend\"]): string[] {\n  const lines: string[] = [];\n\n  if (frontend.includes(\"next\")) {\n    lines.push(\"- Set `NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY` in `apps/web/.env`\");\n  }\n\n  if (\n    frontend.some((value) => [\"react-router\", \"tanstack-router\", \"tanstack-start\"].includes(value))\n  ) {\n    lines.push(\"- Set `VITE_CLERK_PUBLISHABLE_KEY` in `apps/web/.env`\");\n  }\n\n  if (\n    frontend.some((value) => [\"native-bare\", \"native-uniwind\", \"native-unistyles\"].includes(value))\n  ) {\n    lines.push(\"- Set `EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY` in `apps/native/.env`\");\n  }\n\n  return lines;\n}\n\nfunction getClerkSetupLines(\n  frontend: ProjectConfig[\"frontend\"],\n  backend: ProjectConfig[\"backend\"],\n  api: ProjectConfig[\"api\"],\n  isConvex: boolean,\n): string[] {\n  const lines = getClerkFrontendEnvLines(frontend);\n  const hasClerkServerFrontend = frontend.some((value) =>\n    [\"next\", \"react-router\", \"tanstack-start\"].includes(value),\n  );\n\n  if (isConvex) {\n    return [\n      \"- Set `CLERK_JWT_ISSUER_DOMAIN` in Convex Dashboard\",\n      ...lines,\n      ...(hasClerkServerFrontend\n        ? [\"- Set `CLERK_SECRET_KEY` in `apps/web/.env` for Clerk server middleware\"]\n        : []),\n    ];\n  }\n\n  const serverEnvPath = backend === \"self\" ? \"apps/web/.env\" : \"apps/server/.env\";\n  const needsServerSideClerkAuth = backend !== \"none\";\n  const needsClerkBackendPublishableKey = [\"express\", \"fastify\"].includes(backend);\n  const needsClerkRequestVerification =\n    api !== \"none\" && [\"self\", \"hono\", \"elysia\"].includes(backend);\n\n  if (hasClerkServerFrontend && backend === \"self\") {\n    lines.push(\n      \"- Set `CLERK_SECRET_KEY` in `apps/web/.env` for Clerk server middleware and server-side Clerk auth\",\n    );\n  } else {\n    if (hasClerkServerFrontend) {\n      lines.push(\"- Set `CLERK_SECRET_KEY` in `apps/web/.env` for Clerk server middleware\");\n    }\n\n    if (needsServerSideClerkAuth) {\n      lines.push(`- Set \\`CLERK_SECRET_KEY\\` in \\`${serverEnvPath}\\` for server-side Clerk auth`);\n    }\n  }\n\n  if (needsClerkRequestVerification) {\n    lines.push(\n      `- Set \\`CLERK_PUBLISHABLE_KEY\\` in \\`${serverEnvPath}\\` for server-side Clerk request verification`,\n    );\n  }\n\n  if (needsClerkBackendPublishableKey) {\n    lines.push(\n      `- Set \\`CLERK_PUBLISHABLE_KEY\\` in \\`${serverEnvPath}\\` for Clerk backend middleware`,\n    );\n  }\n\n  return lines;\n}\n\nfunction hasNativeFrontend(frontend: ProjectConfig[\"frontend\"]): boolean {\n  return frontend.some((value) =>\n    [\"native-bare\", \"native-uniwind\", \"native-unistyles\"].includes(value),\n  );\n}\n\nfunction hasWebFrontend(frontend: ProjectConfig[\"frontend\"]): boolean {\n  return frontend.some((value) =>\n    [\n      \"tanstack-router\",\n      \"react-router\",\n      \"tanstack-start\",\n      \"next\",\n      \"svelte\",\n      \"nuxt\",\n      \"solid\",\n      \"astro\",\n    ].includes(value),\n  );\n}\n\nexport function processReadme(vfs: VirtualFileSystem, config: ProjectConfig): void {\n  const content = generateReadmeContent(config);\n  vfs.writeFile(\"README.md\", content);\n}\n\nfunction generateReadmeContent(options: ProjectConfig): string {\n  const {\n    projectName,\n    packageManager,\n    database,\n    auth,\n    addons = [],\n    orm = \"drizzle\",\n    runtime = \"bun\",\n    frontend = [\"tanstack-router\"],\n    backend = \"hono\",\n    api = \"trpc\",\n    webDeploy,\n    serverDeploy,\n  } = options;\n\n  const isConvex = backend === \"convex\";\n  const hasReactRouter = frontend.includes(\"react-router\");\n  const hasTanStackRouter = frontend.includes(\"tanstack-router\");\n  const hasNative = hasNativeFrontend(frontend);\n  const hasReactWeb = frontend.some((f) =>\n    [\"tanstack-router\", \"react-router\", \"tanstack-start\", \"next\"].includes(f),\n  );\n  const hasSvelte = frontend.includes(\"svelte\");\n  const hasAstro = frontend.includes(\"astro\");\n  const packageManagerRunCmd = `${packageManager} run`;\n  const webPort =\n    hasReactRouter || hasTanStackRouter || hasSvelte ? \"5173\" : hasAstro ? \"4321\" : \"3001\";\n\n  const stackDescription = generateStackDescription(frontend, backend, api, isConvex);\n\n  return `# ${projectName}\n\nThis project was created with [Better-T-Stack](https://github.com/AmanVarshney01/create-better-t-stack), a modern TypeScript stack${\n    stackDescription ? ` that combines ${stackDescription}` : \"\"\n  }.\n\n## Features\n\n${generateFeaturesList(database, auth, addons, orm, runtime, frontend, backend, api)}\n\n## Getting Started\n\nFirst, install the dependencies:\n\n\\`\\`\\`bash\n${packageManager} install\n\\`\\`\\`\n${\n  isConvex\n    ? `\n## Convex Setup\n\nThis project uses Convex as a backend. You'll need to set up Convex before running the app:\n\n\\`\\`\\`bash\n${packageManagerRunCmd} dev:setup\n\\`\\`\\`\n\nFollow the prompts to create a new Convex project and connect it to your application.\n\nCopy environment variables from \\`packages/backend/.env.local\\` to \\`apps/*/.env\\`.\n${\n  auth === \"clerk\"\n    ? `\n### Clerk Authentication Setup\n\n- Follow the guide: [Convex + Clerk](https://docs.convex.dev/auth/clerk)\n${getClerkSetupLines(frontend, backend, api, true).join(\"\\n\")}`\n    : \"\"\n}`\n    : generateDatabaseSetup(options, packageManagerRunCmd)\n}\n${\n  !isConvex && auth === \"clerk\"\n    ? `\n## Clerk Authentication Setup\n\n- Follow the guide: [Clerk Quickstart](${getClerkQuickstartUrl(frontend)})\n${getClerkSetupLines(frontend, backend, api, false).join(\"\\n\")}`\n    : \"\"\n}\n\nThen, run the development server:\n\n\\`\\`\\`bash\n${packageManagerRunCmd} dev\n\\`\\`\\`\n\n${generateRunningInstructions(frontend, backend, webPort, hasNative, isConvex)}\n${generateReactUiSection(hasReactWeb, projectName)}\n${\n  addons.includes(\"pwa\") && hasReactRouter\n    ? \"\\n## PWA Support with React Router v7\\n\\nThere is a known compatibility issue between VitePWA and React Router v7.\\nSee: https://github.com/vite-pwa/vite-plugin-pwa/issues/809\\n\"\n    : \"\"\n}\n${generateDeploymentCommands(packageManagerRunCmd, webDeploy, serverDeploy, backend)}\n${generateGitHooksSection(packageManagerRunCmd, addons)}\n\n## Project Structure\n\n\\`\\`\\`\n${generateProjectStructure(options)}\n\\`\\`\\`\n\n## Available Scripts\n\n${generateScriptsList(packageManagerRunCmd, options, hasNative)}\n`;\n}\n\nfunction generateStackDescription(\n  frontend: ProjectConfig[\"frontend\"],\n  backend: ProjectConfig[\"backend\"],\n  api: ProjectConfig[\"api\"],\n  isConvex: boolean,\n): string {\n  const parts: string[] = [];\n\n  const frontendMap: Record<string, string> = {\n    \"tanstack-router\": \"React, TanStack Router\",\n    \"react-router\": \"React, React Router\",\n    next: \"Next.js\",\n    \"tanstack-start\": \"React, TanStack Start\",\n    svelte: \"SvelteKit\",\n    nuxt: \"Nuxt\",\n    solid: \"SolidJS\",\n    astro: \"Astro\",\n    \"native-bare\": \"React Native, Expo\",\n    \"native-uniwind\": \"React Native, Expo\",\n    \"native-unistyles\": \"React Native, Expo\",\n  };\n\n  for (const fe of frontend) {\n    if (frontendMap[fe]) {\n      parts.push(frontendMap[fe]);\n      break;\n    }\n  }\n\n  if (backend !== \"none\") {\n    parts.push((backend[0]?.toUpperCase() ?? \"\") + backend.slice(1));\n  }\n\n  if (!isConvex && api !== \"none\") {\n    parts.push(api.toUpperCase());\n  }\n\n  return parts.length > 0 ? `${parts.join(\", \")}, and more` : \"\";\n}\n\nfunction generateRunningInstructions(\n  frontend: ProjectConfig[\"frontend\"],\n  backend: ProjectConfig[\"backend\"],\n  webPort: string,\n  hasNative: boolean,\n  isConvex: boolean,\n): string {\n  const instructions: string[] = [];\n  const hasAppWebFrontend = hasWebFrontend(frontend);\n  const isBackendSelf = backend === \"self\";\n\n  if (hasAppWebFrontend) {\n    const desc = isBackendSelf ? \"fullstack application\" : \"web application\";\n    instructions.push(\n      `Open [http://localhost:${webPort}](http://localhost:${webPort}) in your browser to see the ${desc}.`,\n    );\n  }\n\n  if (hasNative) {\n    instructions.push(\"Use the Expo Go app to run the mobile application.\");\n  }\n\n  if (isConvex) {\n    instructions.push(\"Your app will connect to the Convex cloud backend automatically.\");\n  } else if (backend !== \"none\" && !isBackendSelf) {\n    instructions.push(\"The API is running at [http://localhost:3000](http://localhost:3000).\");\n  }\n\n  return instructions.join(\"\\n\");\n}\n\nfunction generateReactUiSection(hasReactWeb: boolean, projectName: string): string {\n  if (!hasReactWeb) return \"\";\n\n  return `\n## UI Customization\n\nReact web apps in this stack share shadcn/ui primitives through \\`packages/ui\\`.\n\n- Change design tokens and global styles in \\`packages/ui/src/styles/globals.css\\`\n- Update shared primitives in \\`packages/ui/src/components/*\\`\n- Adjust shadcn aliases or style config in \\`packages/ui/components.json\\` and \\`apps/web/components.json\\`\n\n### Add more shared components\n\nRun this from the project root to add more primitives to the shared UI package:\n\n\\`\\`\\`bash\nnpx shadcn@latest add accordion dialog popover sheet table -c packages/ui\n\\`\\`\\`\n\nImport shared components like this:\n\n\\`\\`\\`tsx\nimport { Button } from \"@${projectName}/ui/components/button\"\n\\`\\`\\`\n\n### Add app-specific blocks\n\nIf you want to add app-specific blocks instead of shared primitives, run the shadcn CLI from \\`apps/web\\`.\n`;\n}\n\nfunction generateProjectStructure(config: ProjectConfig): string {\n  const { projectName, frontend, backend, addons, api, auth, database, orm } = config;\n  const isConvex = backend === \"convex\";\n  const structure: string[] = [`${projectName}/`, \"├── apps/\"];\n  const hasAppWebFrontend = hasWebFrontend(frontend);\n  const isBackendSelf = backend === \"self\";\n  const hasReactWeb = frontend.some((f) =>\n    [\"tanstack-router\", \"react-router\", \"tanstack-start\", \"next\"].includes(f),\n  );\n  const hasNative = hasNativeFrontend(frontend);\n  const hasDbPackage = !isConvex && database !== \"none\" && orm !== \"none\";\n\n  if (hasAppWebFrontend) {\n    const frontendTypes: Record<string, string> = {\n      \"tanstack-router\": \"React + TanStack Router\",\n      \"react-router\": \"React + React Router\",\n      next: \"Next.js\",\n      \"tanstack-start\": \"React + TanStack Start\",\n      svelte: \"SvelteKit\",\n      nuxt: \"Nuxt\",\n      solid: \"SolidJS\",\n      astro: \"Astro\",\n    };\n    const frontendType = frontend.find((f) => frontendTypes[f])\n      ? frontendTypes[frontend.find((f) => frontendTypes[f]) || \"\"]\n      : \"\";\n\n    const prefix = isBackendSelf ? \"└──\" : \"├──\";\n    const desc = isBackendSelf ? \"Fullstack application\" : \"Frontend application\";\n    structure.push(`│   ${prefix} web/         # ${desc} (${frontendType})`);\n  }\n\n  if (hasNative) {\n    structure.push(\"│   ├── native/      # Mobile application (React Native, Expo)\");\n  }\n\n  if (addons.includes(\"starlight\")) {\n    structure.push(\"│   ├── docs/        # Documentation site (Astro Starlight)\");\n  }\n\n  if (!isBackendSelf && backend !== \"none\" && !isConvex) {\n    const backendName = (backend[0]?.toUpperCase() ?? \"\") + backend.slice(1);\n    const apiName = api !== \"none\" ? api.toUpperCase() : \"\";\n    const desc = apiName ? `${backendName}, ${apiName}` : backendName;\n    structure.push(`│   └── server/      # Backend API (${desc})`);\n  }\n\n  if (isConvex || backend !== \"none\" || hasReactWeb) {\n    structure.push(\"├── packages/\");\n\n    if (hasReactWeb) {\n      structure.push(\"│   ├── ui/          # Shared shadcn/ui components and styles\");\n    }\n\n    if (isConvex) {\n      structure.push(\"│   ├── backend/     # Convex backend functions and schema\");\n      if (auth === \"clerk\") {\n        structure.push(\n          \"│   │   ├── convex/    # Convex functions and schema\",\n          \"│   │   └── .env.local # Convex environment variables\",\n        );\n      }\n    }\n\n    if (!isConvex) {\n      if (api !== \"none\") {\n        structure.push(\"│   ├── api/         # API layer / business logic\");\n      }\n      if (auth === \"better-auth\") {\n        structure.push(\"│   ├── auth/        # Authentication configuration & logic\");\n      }\n      if (hasDbPackage) {\n        structure.push(\"│   └── db/          # Database schema & queries\");\n      }\n    }\n  }\n\n  return structure.join(\"\\n\");\n}\n\nfunction generateFeaturesList(\n  database: ProjectConfig[\"database\"],\n  auth: ProjectConfig[\"auth\"],\n  addons: ProjectConfig[\"addons\"],\n  orm: ProjectConfig[\"orm\"],\n  runtime: ProjectConfig[\"runtime\"],\n  frontend: ProjectConfig[\"frontend\"],\n  backend: ProjectConfig[\"backend\"],\n  api: ProjectConfig[\"api\"],\n): string {\n  const isConvex = backend === \"convex\";\n  const hasNative = hasNativeFrontend(frontend);\n  const hasAppWebFrontend = hasWebFrontend(frontend);\n  const hasReactWeb = frontend.some((f) =>\n    [\"tanstack-router\", \"react-router\", \"tanstack-start\", \"next\"].includes(f),\n  );\n  const usesTailwind = hasAppWebFrontend || frontend.includes(\"native-uniwind\");\n\n  const features = [\"- **TypeScript** - For type safety and improved developer experience\"];\n\n  const frontendFeatures: Record<string, string> = {\n    \"tanstack-router\": \"- **TanStack Router** - File-based routing with full type safety\",\n    \"react-router\": \"- **React Router** - Declarative routing for React\",\n    next: \"- **Next.js** - Full-stack React framework\",\n    \"tanstack-start\": \"- **TanStack Start** - SSR framework with TanStack Router\",\n    svelte: \"- **SvelteKit** - Web framework for building Svelte apps\",\n    nuxt: \"- **Nuxt** - The Intuitive Vue Framework\",\n    solid: \"- **SolidJS** - Simple and performant reactivity\",\n    astro: \"- **Astro** - The web framework for content-driven websites\",\n  };\n\n  for (const fe of frontend) {\n    if (frontendFeatures[fe]) {\n      features.push(frontendFeatures[fe]);\n      break;\n    }\n  }\n\n  if (hasNative) {\n    features.push(\n      \"- **React Native** - Build mobile apps using React\",\n      \"- **Expo** - Tools for React Native development\",\n    );\n  }\n\n  if (usesTailwind) {\n    features.push(\"- **TailwindCSS** - Utility-first CSS for rapid UI development\");\n  }\n\n  if (hasReactWeb) {\n    features.push(\"- **Shared UI package** - shadcn/ui primitives live in `packages/ui`\");\n  }\n\n  const backendFeatures: Record<string, string> = {\n    convex: \"- **Convex** - Reactive backend-as-a-service platform\",\n    hono: \"- **Hono** - Lightweight, performant server framework\",\n    express: \"- **Express** - Fast, unopinionated web framework\",\n    fastify: \"- **Fastify** - Fast, low-overhead web framework\",\n    elysia: \"- **Elysia** - Type-safe, high-performance framework\",\n  };\n\n  if (backendFeatures[backend]) {\n    features.push(backendFeatures[backend]);\n  }\n\n  if (!isConvex && api === \"trpc\") {\n    features.push(\"- **tRPC** - End-to-end type-safe APIs\");\n  } else if (!isConvex && api === \"orpc\") {\n    features.push(\"- **oRPC** - End-to-end type-safe APIs with OpenAPI integration\");\n  }\n\n  if (!isConvex && backend !== \"none\" && runtime !== \"none\") {\n    const runtimeName = runtime === \"bun\" ? \"Bun\" : runtime === \"node\" ? \"Node.js\" : runtime;\n    features.push(`- **${runtimeName}** - Runtime environment`);\n  }\n\n  if (database !== \"none\" && !isConvex) {\n    const ormNames: Record<string, string> = {\n      drizzle: \"Drizzle\",\n      prisma: \"Prisma\",\n      mongoose: \"Mongoose\",\n    };\n    const dbNames: Record<string, string> = {\n      sqlite: \"SQLite/Turso\",\n      postgres: \"PostgreSQL\",\n      mysql: \"MySQL\",\n      mongodb: \"MongoDB\",\n    };\n    features.push(\n      `- **${ormNames[orm] || \"ORM\"}** - TypeScript-first ORM`,\n      `- **${dbNames[database] || \"Database\"}** - Database engine`,\n    );\n  }\n\n  if (auth !== \"none\") {\n    const authLabel = auth === \"clerk\" ? \"Clerk\" : \"Better-Auth\";\n    features.push(`- **Authentication** - ${authLabel}`);\n  }\n\n  const addonFeatures: Record<string, string> = {\n    pwa: \"- **PWA** - Progressive Web App support\",\n    tauri: \"- **Tauri** - Build native desktop applications\",\n    electrobun: \"- **Electrobun** - Lightweight desktop shell for web frontends\",\n    biome: \"- **Biome** - Linting and formatting\",\n    oxlint: \"- **Oxlint** - Oxlint + Oxfmt (linting & formatting)\",\n    husky: \"- **Husky** - Git hooks for code quality\",\n    starlight: \"- **Starlight** - Documentation site with Astro\",\n    turborepo: \"- **Turborepo** - Optimized monorepo build system\",\n    nx: \"- **Nx** - Smart monorepo task orchestration and caching\",\n  };\n\n  for (const addon of addons) {\n    if (addonFeatures[addon]) {\n      features.push(addonFeatures[addon]);\n    }\n  }\n\n  return features.join(\"\\n\");\n}\n\nfunction generateDatabaseSetup(config: ProjectConfig, packageManagerRunCmd: string): string {\n  const { database, orm, dbSetup, backend } = config;\n  if (database === \"none\") return \"\";\n\n  const isBackendSelf = backend === \"self\";\n  const envPath = isBackendSelf ? \"apps/web/.env\" : \"apps/server/.env\";\n  const ormLabels: Record<ProjectConfig[\"orm\"], string> = {\n    drizzle: \"Drizzle ORM\",\n    prisma: \"Prisma\",\n    mongoose: \"Mongoose\",\n    none: \"ORM\",\n  };\n  const ormDesc = orm === \"none\" ? \"\" : ` with ${ormLabels[orm] || orm}`;\n  const dbSupport = getDbScriptSupport(config);\n\n  let setup = \"## Database Setup\\n\\n\";\n\n  const dbDescriptions: Record<string, string> = {\n    sqlite: `This project uses SQLite${ormDesc}.\n\n1. Start the local SQLite database (optional):\n${\n  dbSetup === \"d1\"\n    ? \"D1 local development and migrations are handled automatically by Alchemy during dev and deploy.\"\n    : `\\`\\`\\`bash\n${packageManagerRunCmd} db:local\n\\`\\`\\``\n}\n\n2. Update your \\`.env\\` file in the \\`${isBackendSelf ? \"apps/web\" : \"apps/server\"}\\` directory with the appropriate connection details if needed.`,\n\n    postgres: `This project uses PostgreSQL${ormDesc}.\n\n1. Make sure you have a PostgreSQL database set up.\n2. Update your \\`${envPath}\\` file with your PostgreSQL connection details.`,\n\n    mysql: `This project uses MySQL${ormDesc}.\n\n1. Make sure you have a MySQL database set up.\n2. Update your \\`${envPath}\\` file with your MySQL connection details.`,\n\n    mongodb: `This project uses MongoDB ${ormDesc.replace(\" with \", \"with \")}.\n\n1. Make sure you have MongoDB set up.\n2. Update your \\`${envPath}\\` file with your MongoDB connection URI.`,\n  };\n\n  setup += dbDescriptions[database] || \"\";\n\n  if (dbSupport.hasDbPush) {\n    setup += `\n\n3. Apply the schema to your database:\n\\`\\`\\`bash\n${packageManagerRunCmd} db:push\n\\`\\`\\`\n`;\n  }\n\n  return setup;\n}\n\nfunction generateScriptsList(\n  packageManagerRunCmd: string,\n  config: ProjectConfig,\n  hasNative: boolean,\n): string {\n  const { database, addons, backend, dbSetup, frontend } = config;\n  const isConvex = backend === \"convex\";\n  const isBackendSelf = backend === \"self\";\n  const hasWeb = frontend.some((f) =>\n    [\n      \"tanstack-router\",\n      \"react-router\",\n      \"tanstack-start\",\n      \"next\",\n      \"nuxt\",\n      \"svelte\",\n      \"solid\",\n      \"astro\",\n    ].includes(f),\n  );\n  const dbSupport = getDbScriptSupport(config);\n\n  let scripts = `- \\`${packageManagerRunCmd} dev\\`: Start all applications in development mode\n- \\`${packageManagerRunCmd} build\\`: Build all applications`;\n\n  if (hasWeb) {\n    scripts += `\\n- \\`${packageManagerRunCmd} dev:web\\`: Start only the web application`;\n  }\n\n  if (isConvex) {\n    scripts += `\\n- \\`${packageManagerRunCmd} dev:setup\\`: Setup and configure your Convex project`;\n  } else if (backend !== \"none\" && !isBackendSelf) {\n    scripts += `\\n- \\`${packageManagerRunCmd} dev:server\\`: Start only the server`;\n  }\n\n  scripts += `\\n- \\`${packageManagerRunCmd} check-types\\`: Check TypeScript types across all apps`;\n\n  if (hasNative) {\n    scripts += `\\n- \\`${packageManagerRunCmd} dev:native\\`: Start the React Native/Expo development server`;\n  }\n\n  if (dbSupport.hasDbScripts) {\n    scripts += `\\n- \\`${packageManagerRunCmd} db:push\\`: Push schema changes to database`;\n    if (dbSupport.hasDbGenerate) {\n      scripts += `\\n- \\`${packageManagerRunCmd} db:generate\\`: Generate database client/types`;\n    }\n    if (dbSupport.hasDbMigrate) {\n      scripts += `\\n- \\`${packageManagerRunCmd} db:migrate\\`: Run database migrations`;\n    }\n    if (dbSupport.hasDbStudio) {\n      scripts += `\\n- \\`${packageManagerRunCmd} db:studio\\`: Open database studio UI`;\n    }\n  }\n\n  if (database === \"sqlite\" && dbSetup !== \"d1\" && dbSupport.hasDbScripts) {\n    scripts += `\\n- \\`${packageManagerRunCmd} db:local\\`: Start the local SQLite database`;\n  }\n\n  if (addons.includes(\"biome\")) {\n    scripts += `\\n- \\`${packageManagerRunCmd} check\\`: Run Biome formatting and linting`;\n  }\n\n  if (addons.includes(\"oxlint\")) {\n    scripts += `\\n- \\`${packageManagerRunCmd} check\\`: Run Oxlint and Oxfmt`;\n  }\n\n  if (addons.includes(\"pwa\")) {\n    scripts += `\\n- \\`cd apps/web && ${packageManagerRunCmd} generate-pwa-assets\\`: Generate PWA assets`;\n  }\n\n  if (addons.includes(\"tauri\")) {\n    scripts += `\\n- \\`cd apps/web && ${packageManagerRunCmd} desktop:dev\\`: Start Tauri desktop app in development\n- \\`cd apps/web && ${packageManagerRunCmd} desktop:build\\`: Build Tauri desktop app`;\n    const staticBuildNote = getDesktopStaticBuildNote(frontend);\n    if (staticBuildNote) {\n      scripts += `\\n- Note: ${staticBuildNote}`;\n    }\n  }\n\n  if (addons.includes(\"electrobun\")) {\n    scripts += `\\n- \\`${packageManagerRunCmd} dev:desktop\\`: Start the Electrobun desktop app with HMR\n- \\`${packageManagerRunCmd} build:desktop\\`: Build the stable Electrobun desktop app\n- \\`${packageManagerRunCmd} build:desktop:canary\\`: Build the canary Electrobun desktop app`;\n    const staticBuildNote = getDesktopStaticBuildNote(frontend);\n    if (staticBuildNote) {\n      scripts += `\\n- Note: ${staticBuildNote}`;\n    }\n  }\n\n  if (addons.includes(\"starlight\")) {\n    scripts += `\\n- \\`cd apps/docs && ${packageManagerRunCmd} dev\\`: Start documentation site\n- \\`cd apps/docs && ${packageManagerRunCmd} build\\`: Build documentation site`;\n  }\n\n  return scripts;\n}\n\nfunction generateDeploymentCommands(\n  packageManagerRunCmd: string,\n  webDeploy: ProjectConfig[\"webDeploy\"],\n  serverDeploy: ProjectConfig[\"serverDeploy\"],\n  backend: ProjectConfig[\"backend\"],\n): string {\n  if (webDeploy !== \"cloudflare\" && serverDeploy !== \"cloudflare\") {\n    return \"\";\n  }\n\n  const lines: string[] = [\"## Deployment (Cloudflare via Alchemy)\"];\n  const targetLabel =\n    webDeploy === \"cloudflare\" && (serverDeploy === \"cloudflare\" || backend === \"self\")\n      ? \"web + server\"\n      : webDeploy === \"cloudflare\"\n        ? \"web\"\n        : \"server\";\n\n  lines.push(\n    `- Target: ${targetLabel}`,\n    `- Dev: ${packageManagerRunCmd} dev`,\n    `- Deploy: ${packageManagerRunCmd} deploy`,\n    `- Destroy: ${packageManagerRunCmd} destroy`,\n  );\n\n  lines.push(\n    \"\",\n    \"For more details, see the guide on [Deploying to Cloudflare with Alchemy](https://www.better-t-stack.dev/docs/guides/cloudflare-alchemy).\",\n  );\n\n  return `${lines.join(\"\\n\")}\\n`;\n}\n\nfunction generateGitHooksSection(\n  packageManagerRunCmd: string,\n  addons: ProjectConfig[\"addons\"],\n): string {\n  const hasHusky = addons.includes(\"husky\");\n  const hasLinting = addons.includes(\"biome\") || addons.includes(\"oxlint\");\n\n  if (!hasHusky && !hasLinting) {\n    return \"\";\n  }\n\n  const lines: string[] = [\"## Git Hooks and Formatting\", \"\"];\n\n  if (hasHusky) {\n    lines.push(`- Initialize hooks: \\`${packageManagerRunCmd} prepare\\``);\n  }\n\n  if (hasLinting) {\n    lines.push(`- Format and lint fix: \\`${packageManagerRunCmd} check\\``);\n  }\n\n  return `${lines.join(\"\\n\")}\\n\\n`;\n}\n"
  },
  {
    "path": "packages/template-generator/src/processors/runtime-deps.ts",
    "content": "import type { ProjectConfig } from \"@better-t-stack/types\";\n\nimport type { VirtualFileSystem } from \"../core/virtual-fs\";\nimport { addPackageDependency } from \"../utils/add-deps\";\n\ntype PackageJson = {\n  scripts?: Record<string, string>;\n  [key: string]: unknown;\n};\n\nexport function processRuntimeDeps(vfs: VirtualFileSystem, config: ProjectConfig): void {\n  const { runtime, backend } = config;\n\n  if (backend === \"convex\" || backend === \"self\" || runtime === \"none\") return;\n\n  const serverPath = \"apps/server/package.json\";\n  if (!vfs.exists(serverPath)) return;\n\n  const pkgJson = vfs.readJson<PackageJson>(serverPath);\n  if (!pkgJson) return;\n\n  pkgJson.scripts = pkgJson.scripts || {};\n\n  if (runtime === \"bun\") {\n    pkgJson.scripts.dev = \"bun run --hot src/index.ts\";\n    pkgJson.scripts.start = \"bun run dist/index.mjs\";\n\n    addPackageDependency({\n      vfs,\n      packagePath: serverPath,\n      devDependencies: [\"@types/bun\"],\n    });\n  } else if (runtime === \"node\") {\n    pkgJson.scripts.dev = \"tsx watch src/index.ts\";\n    pkgJson.scripts.start = \"node dist/index.mjs\";\n\n    addPackageDependency({\n      vfs,\n      packagePath: serverPath,\n      devDependencies: [\"tsx\", \"@types/node\"],\n    });\n\n    if (backend === \"hono\") {\n      addPackageDependency({\n        vfs,\n        packagePath: serverPath,\n        dependencies: [\"@hono/node-server\"],\n      });\n    } else if (backend === \"elysia\") {\n      addPackageDependency({\n        vfs,\n        packagePath: serverPath,\n        dependencies: [\"@elysiajs/node\"],\n      });\n    }\n  }\n\n  vfs.writeJson(serverPath, pkgJson);\n}\n"
  },
  {
    "path": "packages/template-generator/src/processors/turbo-generator.ts",
    "content": "/**\n * Turbo.json Generator - Programmatically generates turbo.json configuration\n * Replaces the previous Handlebars template with type-safe TypeScript generation\n */\n\nimport type { ProjectConfig } from \"@better-t-stack/types\";\n\nimport type { VirtualFileSystem } from \"../core/virtual-fs\";\nimport { getDbScriptSupport, type DbScriptSupport } from \"../utils/db-scripts\";\n\ninterface TurboTask {\n  dependsOn?: string[];\n  inputs?: string[];\n  outputs?: string[];\n  cache?: boolean;\n  persistent?: boolean;\n}\n\ninterface TurboConfig {\n  $schema: string;\n  ui: string;\n  tasks: Record<string, TurboTask>;\n}\n\nexport function processTurboConfig(vfs: VirtualFileSystem, config: ProjectConfig): void {\n  if (!config.addons.includes(\"turborepo\")) return;\n\n  const turboConfig = generateTurboConfig(config);\n  vfs.writeFile(\"turbo.json\", JSON.stringify(turboConfig, null, \"\\t\"));\n}\n\nfunction generateTurboConfig(config: ProjectConfig): TurboConfig {\n  const { backend, database, dbSetup, webDeploy, serverDeploy, frontend } = config;\n\n  const isConvex = backend === \"convex\";\n  const dbSupport = getDbScriptSupport(config);\n  const hasDatabase = dbSupport.hasDbScripts;\n  const isDocker = dbSetup === \"docker\";\n  const isSqliteLocal = database === \"sqlite\" && dbSetup !== \"d1\" && hasDatabase;\n  const hasCloudflare = webDeploy === \"cloudflare\" || serverDeploy === \"cloudflare\";\n\n  const tasks: Record<string, TurboTask> = {\n    ...getBaseTasks(frontend),\n    ...(isConvex ? getConvexTasks() : {}),\n    ...(!isConvex && hasDatabase ? getDatabaseTasks(dbSupport) : {}),\n    ...(isDocker ? getDockerTasks() : {}),\n    ...(isSqliteLocal ? getSqliteLocalTask() : {}),\n    ...(hasCloudflare ? getDeployTasks() : {}),\n  };\n\n  return {\n    $schema: \"https://turbo.build/schema.json\",\n    ui: \"tui\",\n    tasks,\n  };\n}\n\nfunction getBaseTasks(frontend: string[]): Record<string, TurboTask> {\n  // Build outputs per framework:\n  // - Vite-based (tanstack-router, react-router, tanstack-start, solid, svelte): dist/**\n  // - Next.js: .next/**\n  // - Nuxt: .nuxt/**, .output/**\n  const buildOutputs = [\"dist/**\"];\n\n  if (frontend.includes(\"next\")) {\n    buildOutputs.push(\".next/**\");\n  }\n\n  if (frontend.includes(\"nuxt\")) {\n    buildOutputs.push(\".nuxt/**\", \".output/**\");\n  }\n\n  // SvelteKit outputs to .svelte-kit/** in addition to build/\n  if (frontend.includes(\"svelte\")) {\n    buildOutputs.push(\".svelte-kit/**\", \"build/**\");\n  }\n\n  // Astro outputs to dist/**\n  if (frontend.includes(\"astro\")) {\n    buildOutputs.push(\".astro/**\");\n  }\n\n  return {\n    build: {\n      dependsOn: [\"^build\"],\n      inputs: [\"$TURBO_DEFAULT$\", \".env*\"],\n      outputs: buildOutputs,\n    },\n    lint: {\n      dependsOn: [\"^lint\"],\n    },\n    \"check-types\": {\n      dependsOn: [\"^check-types\"],\n    },\n    dev: {\n      cache: false,\n      persistent: true,\n    },\n  };\n}\n\nfunction getConvexTasks(): Record<string, TurboTask> {\n  return {\n    \"dev:setup\": {\n      cache: false,\n      persistent: true,\n    },\n  };\n}\n\nfunction getDatabaseTasks(dbSupport: DbScriptSupport): Record<string, TurboTask> {\n  const tasks: Record<string, TurboTask> = {};\n\n  if (dbSupport.hasDbPush) {\n    tasks[\"db:push\"] = { cache: false };\n  }\n\n  if (dbSupport.hasDbGenerate) {\n    tasks[\"db:generate\"] = { cache: false };\n  }\n\n  if (dbSupport.hasDbMigrate) {\n    tasks[\"db:migrate\"] = { cache: false, persistent: true };\n  }\n\n  if (dbSupport.hasDbStudio) {\n    tasks[\"db:studio\"] = { cache: false, persistent: true };\n  }\n\n  return tasks;\n}\n\nfunction getDockerTasks(): Record<string, TurboTask> {\n  return {\n    \"db:start\": {\n      cache: false,\n      persistent: true,\n    },\n    \"db:stop\": {\n      cache: false,\n    },\n    \"db:watch\": {\n      cache: false,\n      persistent: true,\n    },\n    \"db:down\": {\n      cache: false,\n    },\n  };\n}\n\nfunction getSqliteLocalTask(): Record<string, TurboTask> {\n  return {\n    \"db:local\": {\n      cache: false,\n    },\n  };\n}\n\nfunction getDeployTasks(): Record<string, TurboTask> {\n  return {\n    deploy: {\n      cache: false,\n    },\n    destroy: {\n      cache: false,\n    },\n  };\n}\n"
  },
  {
    "path": "packages/template-generator/src/processors/workspace-deps.ts",
    "content": "import type { ProjectConfig } from \"@better-t-stack/types\";\n\nimport type { VirtualFileSystem } from \"../core/virtual-fs\";\nimport { addPackageDependency, type AvailableDependencies } from \"../utils/add-deps\";\n\n// Expo SDK 55 requires TypeScript ~5.9.x — override the generic `^6` pin.\nconst NATIVE_TYPESCRIPT_VERSION = \"~5.9.2\";\n\nexport function processWorkspaceDeps(vfs: VirtualFileSystem, config: ProjectConfig): void {\n  const {\n    projectName,\n    packageManager,\n    runtime,\n    backend,\n    database,\n    auth,\n    api,\n    serverDeploy,\n    webDeploy,\n  } = config;\n\n  const workspaceVersion = packageManager === \"npm\" ? \"*\" : \"workspace:*\";\n  const packages = {\n    config: vfs.exists(\"packages/config/package.json\"),\n    env: vfs.exists(\"packages/env/package.json\"),\n    infra: vfs.exists(\"packages/infra/package.json\"),\n    db: vfs.exists(\"packages/db/package.json\"),\n    auth: vfs.exists(\"packages/auth/package.json\"),\n    api: vfs.exists(\"packages/api/package.json\"),\n    ui: vfs.exists(\"packages/ui/package.json\"),\n    backend: vfs.exists(\"packages/backend/package.json\"),\n    server: vfs.exists(\"apps/server/package.json\"),\n    web: vfs.exists(\"apps/web/package.json\"),\n    native: vfs.exists(\"apps/native/package.json\"),\n  };\n\n  const configDep = packages.config ? { [`@${projectName}/config`]: workspaceVersion } : {};\n  const envDep = packages.env ? { [`@${projectName}/env`]: workspaceVersion } : {};\n  const uiDep = packages.ui ? { [`@${projectName}/ui`]: workspaceVersion } : {};\n  const isCloudflare = serverDeploy === \"cloudflare\" || webDeploy === \"cloudflare\";\n  const runtimeDevDeps = getRuntimeDevDeps(runtime, backend);\n  const commonDeps: AvailableDependencies[] = [\"dotenv\", \"zod\"];\n  const commonDevDeps: AvailableDependencies[] = [\"typescript\", ...runtimeDevDeps];\n\n  addPackageDependency({\n    vfs,\n    packagePath: \"package.json\",\n    dependencies: commonDeps,\n    devDependencies: commonDevDeps,\n    customDependencies: envDep,\n    customDevDependencies: configDep,\n  });\n\n  if (packages.env) {\n    const envDevDeps: Record<string, string> = { ...configDep };\n    if (isCloudflare && packages.infra) {\n      envDevDeps[`@${projectName}/infra`] = workspaceVersion;\n    }\n    addPackageDependency({\n      vfs,\n      packagePath: \"packages/env/package.json\",\n      dependencies: commonDeps,\n      devDependencies: commonDevDeps,\n      customDevDependencies: envDevDeps,\n    });\n  }\n\n  if (packages.infra) {\n    addPackageDependency({\n      vfs,\n      packagePath: \"packages/infra/package.json\",\n      dependencies: commonDeps,\n      devDependencies: [\"typescript\"],\n      customDevDependencies: configDep,\n    });\n  }\n\n  if (packages.db) {\n    addPackageDependency({\n      vfs,\n      packagePath: \"packages/db/package.json\",\n      dependencies: commonDeps,\n      devDependencies: [\"typescript\"],\n      customDependencies: envDep,\n      customDevDependencies: configDep,\n    });\n  }\n\n  if (packages.auth) {\n    const authDeps: Record<string, string> = { ...envDep };\n    if (database !== \"none\" && packages.db) {\n      authDeps[`@${projectName}/db`] = workspaceVersion;\n    }\n    addPackageDependency({\n      vfs,\n      packagePath: \"packages/auth/package.json\",\n      dependencies: commonDeps,\n      devDependencies: [\"typescript\"],\n      customDependencies: authDeps,\n      customDevDependencies: configDep,\n    });\n  }\n\n  if (packages.api) {\n    const apiPackageDeps: Record<string, string> = { ...envDep };\n    if (auth !== \"none\" && packages.auth) {\n      apiPackageDeps[`@${projectName}/auth`] = workspaceVersion;\n    }\n    if (database !== \"none\" && packages.db) {\n      apiPackageDeps[`@${projectName}/db`] = workspaceVersion;\n    }\n    addPackageDependency({\n      vfs,\n      packagePath: \"packages/api/package.json\",\n      dependencies: commonDeps,\n      devDependencies: [\"typescript\"],\n      customDependencies: apiPackageDeps,\n      customDevDependencies: configDep,\n    });\n  }\n\n  if (packages.backend) {\n    addPackageDependency({\n      vfs,\n      packagePath: \"packages/backend/package.json\",\n      dependencies: commonDeps,\n      devDependencies: [\"typescript\"],\n      customDevDependencies: configDep,\n    });\n  }\n\n  if (packages.server) {\n    const serverDeps: Record<string, string> = { ...envDep };\n    if (api !== \"none\" && packages.api) serverDeps[`@${projectName}/api`] = workspaceVersion;\n    if (auth !== \"none\" && packages.auth) serverDeps[`@${projectName}/auth`] = workspaceVersion;\n    if (database !== \"none\" && packages.db) serverDeps[`@${projectName}/db`] = workspaceVersion;\n    addPackageDependency({\n      vfs,\n      packagePath: \"apps/server/package.json\",\n      dependencies: commonDeps,\n      devDependencies: [\"typescript\", \"tsdown\"],\n      customDependencies: serverDeps,\n      customDevDependencies: configDep,\n    });\n  }\n\n  if (packages.web) {\n    const webPackageDeps: Record<string, string> = { ...envDep, ...uiDep };\n\n    if (api !== \"none\" && packages.api) webPackageDeps[`@${projectName}/api`] = workspaceVersion;\n    if (backend === \"self\" && auth !== \"none\" && packages.auth) {\n      webPackageDeps[`@${projectName}/auth`] = workspaceVersion;\n    }\n    if (backend === \"convex\" && packages.backend)\n      webPackageDeps[`@${projectName}/backend`] = workspaceVersion;\n\n    addPackageDependency({\n      vfs,\n      packagePath: \"apps/web/package.json\",\n      dependencies: commonDeps,\n      devDependencies: [\"typescript\"],\n      customDependencies: webPackageDeps,\n      customDevDependencies: configDep,\n    });\n  }\n\n  if (packages.ui) {\n    addPackageDependency({\n      vfs,\n      packagePath: \"packages/ui/package.json\",\n      devDependencies: [\"typescript\"],\n      customDevDependencies: configDep,\n    });\n  }\n\n  if (packages.native) {\n    const nativeDeps: Record<string, string> = { ...envDep };\n    if (api !== \"none\" && packages.api) nativeDeps[`@${projectName}/api`] = workspaceVersion;\n    if (backend === \"convex\" && packages.backend)\n      nativeDeps[`@${projectName}/backend`] = workspaceVersion;\n    addPackageDependency({\n      vfs,\n      packagePath: \"apps/native/package.json\",\n      dependencies: commonDeps,\n      customDependencies: nativeDeps,\n      customDevDependencies: { ...configDep, typescript: NATIVE_TYPESCRIPT_VERSION },\n    });\n  }\n}\n\nfunction getRuntimeDevDeps(\n  runtime: ProjectConfig[\"runtime\"],\n  backend: ProjectConfig[\"backend\"],\n): AvailableDependencies[] {\n  if (runtime === \"none\" && backend === \"self\") return [\"@types/node\"];\n  if (runtime === \"node\" || runtime === \"workers\") return [\"@types/node\"];\n  if (runtime === \"bun\") return [\"@types/bun\"];\n  return [];\n}\n"
  },
  {
    "path": "packages/template-generator/src/template-handlers/addons.ts",
    "content": "import type { ProjectConfig } from \"@better-t-stack/types\";\n\nimport type { VirtualFileSystem } from \"../core/virtual-fs\";\nimport { type TemplateData, processTemplatesFromPrefix } from \"./utils\";\n\nexport async function processAddonTemplates(\n  vfs: VirtualFileSystem,\n  templates: TemplateData,\n  config: ProjectConfig,\n): Promise<void> {\n  if (!config.addons || config.addons.length === 0) return;\n\n  for (const addon of config.addons) {\n    if (addon === \"none\") continue;\n\n    // monorepo tools are handled programmatically by generators\n    if (addon === \"turborepo\" || addon === \"nx\") continue;\n\n    if (addon === \"pwa\") {\n      if (config.frontend.includes(\"next\")) {\n        processTemplatesFromPrefix(vfs, templates, \"addons/pwa/apps/web/next\", \"apps/web\", config);\n      } else if (\n        config.frontend.some((f) => [\"tanstack-router\", \"react-router\", \"solid\"].includes(f))\n      ) {\n        processTemplatesFromPrefix(vfs, templates, \"addons/pwa/apps/web/vite\", \"apps/web\", config);\n      }\n      continue;\n    }\n\n    processTemplatesFromPrefix(vfs, templates, `addons/${addon}`, \"\", config);\n  }\n}\n"
  },
  {
    "path": "packages/template-generator/src/template-handlers/api.ts",
    "content": "import type { ProjectConfig } from \"@better-t-stack/types\";\n\nimport type { VirtualFileSystem } from \"../core/virtual-fs\";\nimport { type TemplateData, processTemplatesFromPrefix, processSingleTemplate } from \"./utils\";\n\nexport async function processApiTemplates(\n  vfs: VirtualFileSystem,\n  templates: TemplateData,\n  config: ProjectConfig,\n): Promise<void> {\n  if (config.api === \"none\") return;\n  if (config.backend === \"convex\") return;\n\n  processTemplatesFromPrefix(vfs, templates, `api/${config.api}/server`, \"packages/api\", config);\n\n  const hasReactWeb = config.frontend.some((f) =>\n    [\"tanstack-router\", \"react-router\", \"tanstack-start\", \"next\"].includes(f),\n  );\n  const hasNuxtWeb = config.frontend.includes(\"nuxt\");\n  const hasSvelteWeb = config.frontend.includes(\"svelte\");\n  const hasSolidWeb = config.frontend.includes(\"solid\");\n  const hasAstroWeb = config.frontend.includes(\"astro\");\n\n  if (hasReactWeb) {\n    processTemplatesFromPrefix(\n      vfs,\n      templates,\n      `api/${config.api}/web/react/base`,\n      \"apps/web\",\n      config,\n    );\n\n    const reactFramework = config.frontend.find((f) =>\n      [\"tanstack-router\", \"react-router\", \"tanstack-start\", \"next\"].includes(f),\n    );\n    if (\n      config.backend === \"self\" &&\n      (reactFramework === \"next\" || reactFramework === \"tanstack-start\")\n    ) {\n      processTemplatesFromPrefix(\n        vfs,\n        templates,\n        `api/${config.api}/fullstack/${reactFramework}`,\n        \"apps/web\",\n        config,\n      );\n    }\n  } else if (hasNuxtWeb && config.api === \"orpc\") {\n    if (config.backend === \"self\") {\n      processTemplatesFromPrefix(\n        vfs,\n        templates,\n        `api/${config.api}/fullstack/nuxt`,\n        \"apps/web\",\n        config,\n      );\n      // Only include vue-query from web templates, skip generic orpc.ts\n      processSingleTemplate(\n        vfs,\n        templates,\n        `api/${config.api}/web/nuxt/app/plugins/vue-query.ts`,\n        \"apps/web/app/plugins/vue-query.ts\",\n        config,\n      );\n    } else {\n      processTemplatesFromPrefix(vfs, templates, `api/${config.api}/web/nuxt`, \"apps/web\", config);\n    }\n  } else if (hasSvelteWeb && config.api === \"orpc\") {\n    processTemplatesFromPrefix(vfs, templates, `api/${config.api}/web/svelte`, \"apps/web\", config);\n    if (config.backend === \"self\") {\n      processTemplatesFromPrefix(\n        vfs,\n        templates,\n        `api/${config.api}/fullstack/svelte`,\n        \"apps/web\",\n        config,\n      );\n      if (config.auth !== \"better-auth\") {\n        vfs.writeFile(\"apps/web/src/hooks.server.ts\", 'import \"./lib/orpc.server\";\\n');\n      }\n    }\n  } else if (hasSolidWeb && config.api === \"orpc\") {\n    processTemplatesFromPrefix(vfs, templates, `api/${config.api}/web/solid`, \"apps/web\", config);\n  } else if (hasAstroWeb && config.api === \"orpc\") {\n    // Always include the orpc client (handles both self and external backend)\n    processTemplatesFromPrefix(vfs, templates, `api/${config.api}/web/astro`, \"apps/web\", config);\n    // Add fullstack API routes when backend=self\n    if (config.backend === \"self\") {\n      processTemplatesFromPrefix(\n        vfs,\n        templates,\n        `api/${config.api}/fullstack/astro`,\n        \"apps/web\",\n        config,\n      );\n    }\n  }\n}\n"
  },
  {
    "path": "packages/template-generator/src/template-handlers/auth.ts",
    "content": "import type { ProjectConfig } from \"@better-t-stack/types\";\n\nimport type { VirtualFileSystem } from \"../core/virtual-fs\";\nimport { type TemplateData, processTemplatesFromPrefix } from \"./utils\";\n\nexport async function processAuthTemplates(\n  vfs: VirtualFileSystem,\n  templates: TemplateData,\n  config: ProjectConfig,\n): Promise<void> {\n  if (!config.auth || config.auth === \"none\") return;\n\n  const hasReactWeb = config.frontend.some((f) =>\n    [\"tanstack-router\", \"react-router\", \"tanstack-start\", \"next\"].includes(f),\n  );\n  const hasNuxtWeb = config.frontend.includes(\"nuxt\");\n  const hasSvelteWeb = config.frontend.includes(\"svelte\");\n  const hasSolidWeb = config.frontend.includes(\"solid\");\n  const hasAstroWeb = config.frontend.includes(\"astro\");\n  const hasNativeBare = config.frontend.includes(\"native-bare\");\n  const hasUniwind = config.frontend.includes(\"native-uniwind\");\n  const hasUnistyles = config.frontend.includes(\"native-unistyles\");\n  const hasNative = hasNativeBare || hasUniwind || hasUnistyles;\n\n  const authProvider = config.auth;\n\n  if (config.backend === \"convex\" && authProvider === \"clerk\") {\n    processTemplatesFromPrefix(\n      vfs,\n      templates,\n      \"auth/clerk/convex/backend\",\n      \"packages/backend\",\n      config,\n    );\n\n    if (hasReactWeb) {\n      const reactFramework = config.frontend.find((f) =>\n        [\"tanstack-router\", \"react-router\", \"tanstack-start\", \"next\"].includes(f),\n      );\n      if (reactFramework) {\n        processTemplatesFromPrefix(\n          vfs,\n          templates,\n          `auth/clerk/convex/web/react/${reactFramework}`,\n          \"apps/web\",\n          config,\n        );\n      }\n    }\n\n    if (hasNative) {\n      processTemplatesFromPrefix(\n        vfs,\n        templates,\n        \"auth/clerk/convex/native/base\",\n        \"apps/native\",\n        config,\n      );\n\n      let nativeFramework = \"\";\n      if (hasNativeBare) nativeFramework = \"bare\";\n      else if (hasUniwind) nativeFramework = \"uniwind\";\n      else if (hasUnistyles) nativeFramework = \"unistyles\";\n\n      if (nativeFramework) {\n        processTemplatesFromPrefix(\n          vfs,\n          templates,\n          `auth/clerk/convex/native/${nativeFramework}`,\n          \"apps/native\",\n          config,\n        );\n      }\n    }\n    return;\n  }\n\n  if (config.backend === \"convex\" && authProvider === \"better-auth\") {\n    processTemplatesFromPrefix(\n      vfs,\n      templates,\n      \"auth/better-auth/convex/backend\",\n      \"packages/backend\",\n      config,\n    );\n\n    if (hasReactWeb) {\n      processTemplatesFromPrefix(\n        vfs,\n        templates,\n        \"auth/better-auth/convex/web/react/base\",\n        \"apps/web\",\n        config,\n      );\n\n      const reactFramework = config.frontend.find((f) =>\n        [\"tanstack-router\", \"react-router\", \"tanstack-start\", \"next\"].includes(f),\n      );\n      if (reactFramework) {\n        processTemplatesFromPrefix(\n          vfs,\n          templates,\n          `auth/better-auth/convex/web/react/${reactFramework}`,\n          \"apps/web\",\n          config,\n        );\n      }\n    }\n\n    if (hasNative) {\n      processTemplatesFromPrefix(\n        vfs,\n        templates,\n        \"auth/better-auth/convex/native/base\",\n        \"apps/native\",\n        config,\n      );\n\n      let nativeFramework = \"\";\n      if (hasNativeBare) nativeFramework = \"bare\";\n      else if (hasUniwind) nativeFramework = \"uniwind\";\n      else if (hasUnistyles) nativeFramework = \"unistyles\";\n\n      if (nativeFramework) {\n        processTemplatesFromPrefix(\n          vfs,\n          templates,\n          `auth/better-auth/convex/native/${nativeFramework}`,\n          \"apps/native\",\n          config,\n        );\n      }\n    }\n    return;\n  }\n\n  if (config.backend !== \"convex\" && config.backend !== \"none\") {\n    processTemplatesFromPrefix(\n      vfs,\n      templates,\n      `auth/${authProvider}/server/base`,\n      \"packages/auth\",\n      config,\n    );\n\n    if (config.orm !== \"none\" && config.database !== \"none\") {\n      processTemplatesFromPrefix(\n        vfs,\n        templates,\n        `auth/${authProvider}/server/db/${config.orm}/${config.database}`,\n        \"packages/db\",\n        config,\n      );\n    }\n  }\n\n  if (hasReactWeb) {\n    processTemplatesFromPrefix(\n      vfs,\n      templates,\n      `auth/${authProvider}/web/react/base`,\n      \"apps/web\",\n      config,\n    );\n\n    const reactFramework = config.frontend.find((f) =>\n      [\"tanstack-router\", \"react-router\", \"tanstack-start\", \"next\"].includes(f),\n    );\n    if (reactFramework) {\n      processTemplatesFromPrefix(\n        vfs,\n        templates,\n        `auth/${authProvider}/web/react/${reactFramework}`,\n        \"apps/web\",\n        config,\n      );\n\n      if (\n        config.backend === \"self\" &&\n        (reactFramework === \"next\" || reactFramework === \"tanstack-start\")\n      ) {\n        processTemplatesFromPrefix(\n          vfs,\n          templates,\n          `auth/${authProvider}/fullstack/${reactFramework}`,\n          \"apps/web\",\n          config,\n        );\n      }\n    }\n  } else if (hasNuxtWeb) {\n    if (config.backend === \"self\") {\n      processTemplatesFromPrefix(\n        vfs,\n        templates,\n        `auth/${authProvider}/fullstack/nuxt`,\n        \"apps/web\",\n        config,\n      );\n    }\n    processTemplatesFromPrefix(vfs, templates, `auth/${authProvider}/web/nuxt`, \"apps/web\", config);\n  } else if (hasSvelteWeb) {\n    if (config.backend === \"self\" && authProvider === \"better-auth\") {\n      processTemplatesFromPrefix(\n        vfs,\n        templates,\n        `auth/${authProvider}/fullstack/svelte`,\n        \"apps/web\",\n        config,\n      );\n    }\n    processTemplatesFromPrefix(\n      vfs,\n      templates,\n      `auth/${authProvider}/web/svelte`,\n      \"apps/web\",\n      config,\n    );\n  } else if (hasSolidWeb) {\n    processTemplatesFromPrefix(\n      vfs,\n      templates,\n      `auth/${authProvider}/web/solid`,\n      \"apps/web\",\n      config,\n    );\n  } else if (hasAstroWeb) {\n    if (config.backend === \"self\" && authProvider === \"better-auth\") {\n      processTemplatesFromPrefix(\n        vfs,\n        templates,\n        `auth/${authProvider}/fullstack/astro`,\n        \"apps/web\",\n        config,\n      );\n    }\n    processTemplatesFromPrefix(\n      vfs,\n      templates,\n      `auth/${authProvider}/web/astro`,\n      \"apps/web\",\n      config,\n    );\n  }\n\n  if (hasNative) {\n    processTemplatesFromPrefix(\n      vfs,\n      templates,\n      `auth/${authProvider}/native/base`,\n      \"apps/native\",\n      config,\n    );\n\n    let nativeFramework = \"\";\n    if (hasNativeBare) nativeFramework = \"bare\";\n    else if (hasUniwind) nativeFramework = \"uniwind\";\n    else if (hasUnistyles) nativeFramework = \"unistyles\";\n\n    if (nativeFramework) {\n      processTemplatesFromPrefix(\n        vfs,\n        templates,\n        `auth/${authProvider}/native/${nativeFramework}`,\n        \"apps/native\",\n        config,\n      );\n    }\n  }\n}\n"
  },
  {
    "path": "packages/template-generator/src/template-handlers/backend.ts",
    "content": "import type { ProjectConfig } from \"@better-t-stack/types\";\n\nimport type { VirtualFileSystem } from \"../core/virtual-fs\";\nimport { type TemplateData, processTemplatesFromPrefix } from \"./utils\";\n\nexport async function processBackendTemplates(\n  vfs: VirtualFileSystem,\n  templates: TemplateData,\n  config: ProjectConfig,\n): Promise<void> {\n  if (config.backend === \"none\") return;\n\n  if (config.backend === \"convex\") {\n    processTemplatesFromPrefix(\n      vfs,\n      templates,\n      \"backend/convex/packages/backend\",\n      \"packages/backend\",\n      config,\n    );\n    return;\n  }\n\n  if (config.backend === \"self\") return;\n\n  processTemplatesFromPrefix(vfs, templates, \"backend/server/base\", \"apps/server\", config);\n  processTemplatesFromPrefix(\n    vfs,\n    templates,\n    `backend/server/${config.backend}`,\n    \"apps/server\",\n    config,\n  );\n}\n"
  },
  {
    "path": "packages/template-generator/src/template-handlers/base.ts",
    "content": "import type { ProjectConfig } from \"@better-t-stack/types\";\n\nimport type { VirtualFileSystem } from \"../core/virtual-fs\";\nimport { type TemplateData, processTemplatesFromPrefix } from \"./utils\";\n\nexport async function processBaseTemplate(\n  vfs: VirtualFileSystem,\n  templates: TemplateData,\n  config: ProjectConfig,\n): Promise<void> {\n  processTemplatesFromPrefix(vfs, templates, \"base\", \"\", config);\n}\n"
  },
  {
    "path": "packages/template-generator/src/template-handlers/database.ts",
    "content": "import type { ProjectConfig } from \"@better-t-stack/types\";\n\nimport type { VirtualFileSystem } from \"../core/virtual-fs\";\nimport { type TemplateData, processTemplatesFromPrefix } from \"./utils\";\n\nexport async function processDbTemplates(\n  vfs: VirtualFileSystem,\n  templates: TemplateData,\n  config: ProjectConfig,\n): Promise<void> {\n  if (config.database === \"none\" || config.orm === \"none\") return;\n  if (config.backend === \"convex\") return;\n\n  processTemplatesFromPrefix(vfs, templates, \"db/base\", \"packages/db\", config);\n  processTemplatesFromPrefix(vfs, templates, `db/${config.orm}/base`, \"packages/db\", config);\n  processTemplatesFromPrefix(\n    vfs,\n    templates,\n    `db/${config.orm}/${config.database}`,\n    \"packages/db\",\n    config,\n  );\n\n  if (config.dbSetup === \"docker\") {\n    processTemplatesFromPrefix(\n      vfs,\n      templates,\n      `db-setup/docker-compose/${config.database}`,\n      \"packages/db\",\n      config,\n    );\n  }\n}\n"
  },
  {
    "path": "packages/template-generator/src/template-handlers/deploy.ts",
    "content": "import type { ProjectConfig } from \"@better-t-stack/types\";\n\nimport type { VirtualFileSystem } from \"../core/virtual-fs\";\nimport { type TemplateData, processTemplatesFromPrefix } from \"./utils\";\n\nexport async function processDeployTemplates(\n  vfs: VirtualFileSystem,\n  templates: TemplateData,\n  config: ProjectConfig,\n): Promise<void> {\n  const isBackendSelf = config.backend === \"self\";\n\n  if (config.webDeploy === \"cloudflare\" || config.serverDeploy === \"cloudflare\") {\n    processTemplatesFromPrefix(vfs, templates, \"packages/infra\", \"packages/infra\", config);\n  }\n\n  if (config.webDeploy !== \"none\" && config.webDeploy !== \"cloudflare\") {\n    const templateMap: Record<string, string> = {\n      \"tanstack-router\": \"react/tanstack-router\",\n      \"tanstack-start\": \"react/tanstack-start\",\n      \"react-router\": \"react/react-router\",\n      solid: \"solid\",\n      next: \"react/next\",\n      nuxt: \"nuxt\",\n      svelte: \"svelte\",\n    };\n\n    for (const f of config.frontend) {\n      if (templateMap[f]) {\n        processTemplatesFromPrefix(\n          vfs,\n          templates,\n          `deploy/${config.webDeploy}/web/${templateMap[f]}`,\n          \"apps/web\",\n          config,\n        );\n      }\n    }\n  }\n\n  if (config.serverDeploy !== \"none\" && config.serverDeploy !== \"cloudflare\" && !isBackendSelf) {\n    processTemplatesFromPrefix(\n      vfs,\n      templates,\n      `deploy/${config.serverDeploy}/server`,\n      \"apps/server\",\n      config,\n    );\n  }\n}\n"
  },
  {
    "path": "packages/template-generator/src/template-handlers/examples.ts",
    "content": "import type { ProjectConfig } from \"@better-t-stack/types\";\n\nimport type { VirtualFileSystem } from \"../core/virtual-fs\";\nimport { type TemplateData, processTemplatesFromPrefix } from \"./utils\";\n\nexport async function processExampleTemplates(\n  vfs: VirtualFileSystem,\n  templates: TemplateData,\n  config: ProjectConfig,\n): Promise<void> {\n  if (!config.examples || config.examples.length === 0 || config.examples[0] === \"none\") return;\n\n  const hasReactWeb = config.frontend.some((f) =>\n    [\"tanstack-router\", \"react-router\", \"tanstack-start\", \"next\"].includes(f),\n  );\n  const hasNuxtWeb = config.frontend.includes(\"nuxt\");\n  const hasSvelteWeb = config.frontend.includes(\"svelte\");\n  const hasSolidWeb = config.frontend.includes(\"solid\");\n  const hasAstroWeb = config.frontend.includes(\"astro\");\n  const hasNativeBare = config.frontend.includes(\"native-bare\");\n  const hasUniwind = config.frontend.includes(\"native-uniwind\");\n  const hasUnistyles = config.frontend.includes(\"native-unistyles\");\n  const hasNative = hasNativeBare || hasUniwind || hasUnistyles;\n\n  for (const example of config.examples) {\n    if (example === \"none\") continue;\n\n    if (config.backend === \"convex\") {\n      processTemplatesFromPrefix(\n        vfs,\n        templates,\n        `examples/${example}/convex/packages/backend`,\n        \"packages/backend\",\n        config,\n      );\n    } else if (config.backend !== \"none\" && config.api !== \"none\") {\n      processTemplatesFromPrefix(\n        vfs,\n        templates,\n        `examples/${example}/server/${config.orm}/base`,\n        \"packages/api\",\n        config,\n      );\n\n      if (config.orm !== \"none\" && config.database !== \"none\") {\n        processTemplatesFromPrefix(\n          vfs,\n          templates,\n          `examples/${example}/server/${config.orm}/${config.database}`,\n          \"packages/db\",\n          config,\n        );\n      }\n    }\n\n    if (hasReactWeb) {\n      const reactFramework = config.frontend.find((f) =>\n        [\"next\", \"react-router\", \"tanstack-router\", \"tanstack-start\"].includes(f),\n      );\n      if (reactFramework) {\n        processTemplatesFromPrefix(\n          vfs,\n          templates,\n          `examples/${example}/web/react/${reactFramework}`,\n          \"apps/web\",\n          config,\n        );\n\n        if (\n          config.backend === \"self\" &&\n          (reactFramework === \"next\" || reactFramework === \"tanstack-start\")\n        ) {\n          processTemplatesFromPrefix(\n            vfs,\n            templates,\n            `examples/${example}/fullstack/${reactFramework}`,\n            \"apps/web\",\n            config,\n          );\n        }\n      }\n    } else if (hasNuxtWeb) {\n      if (config.backend === \"self\") {\n        processTemplatesFromPrefix(\n          vfs,\n          templates,\n          `examples/${example}/fullstack/nuxt`,\n          \"apps/web\",\n          config,\n        );\n      }\n      processTemplatesFromPrefix(\n        vfs,\n        templates,\n        `examples/${example}/web/nuxt`,\n        \"apps/web\",\n        config,\n      );\n    } else if (hasSvelteWeb) {\n      if (config.backend === \"self\") {\n        processTemplatesFromPrefix(\n          vfs,\n          templates,\n          `examples/${example}/fullstack/svelte`,\n          \"apps/web\",\n          config,\n        );\n      }\n      processTemplatesFromPrefix(\n        vfs,\n        templates,\n        `examples/${example}/web/svelte`,\n        \"apps/web\",\n        config,\n      );\n    } else if (hasSolidWeb) {\n      processTemplatesFromPrefix(\n        vfs,\n        templates,\n        `examples/${example}/web/solid`,\n        \"apps/web\",\n        config,\n      );\n    } else if (hasAstroWeb) {\n      processTemplatesFromPrefix(\n        vfs,\n        templates,\n        `examples/${example}/web/astro`,\n        \"apps/web\",\n        config,\n      );\n    }\n\n    if (hasNative) {\n      let nativeFramework = \"\";\n      if (hasNativeBare) nativeFramework = \"bare\";\n      else if (hasUniwind) nativeFramework = \"uniwind\";\n      else if (hasUnistyles) nativeFramework = \"unistyles\";\n\n      if (nativeFramework) {\n        processTemplatesFromPrefix(\n          vfs,\n          templates,\n          `examples/${example}/native/${nativeFramework}`,\n          \"apps/native\",\n          config,\n        );\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "packages/template-generator/src/template-handlers/extras.ts",
    "content": "import type { ProjectConfig } from \"@better-t-stack/types\";\n\nimport type { VirtualFileSystem } from \"../core/virtual-fs\";\nimport {\n  type TemplateData,\n  hasTemplatesWithPrefix,\n  processTemplatesFromPrefix,\n  processSingleTemplate,\n} from \"./utils\";\n\nexport async function processExtrasTemplates(\n  vfs: VirtualFileSystem,\n  templates: TemplateData,\n  config: ProjectConfig,\n): Promise<void> {\n  const hasNative = config.frontend.some((f) =>\n    [\"native-bare\", \"native-uniwind\", \"native-unistyles\"].includes(f),\n  );\n  const hasNuxt = config.frontend.includes(\"nuxt\");\n\n  if (config.packageManager === \"pnpm\") {\n    if (hasTemplatesWithPrefix(templates, \"extras\")) {\n      processTemplatesFromPrefix(vfs, templates, \"extras/pnpm-workspace.yaml\", \"\", config);\n    }\n  }\n\n  if (config.packageManager === \"bun\") {\n    processTemplatesFromPrefix(vfs, templates, \"extras/bunfig.toml\", \"\", config);\n  }\n\n  if (config.packageManager === \"pnpm\" && (hasNative || hasNuxt)) {\n    processTemplatesFromPrefix(vfs, templates, \"extras/_npmrc\", \"\", config);\n  }\n\n  if (\n    config.serverDeploy === \"cloudflare\" ||\n    (config.backend === \"self\" && config.webDeploy === \"cloudflare\")\n  ) {\n    processSingleTemplate(vfs, templates, \"extras/env.d.ts\", \"packages/env/env.d.ts\", config);\n  }\n}\n"
  },
  {
    "path": "packages/template-generator/src/template-handlers/frontend.ts",
    "content": "import type { ProjectConfig } from \"@better-t-stack/types\";\n\nimport type { VirtualFileSystem } from \"../core/virtual-fs\";\nimport { type TemplateData, processTemplatesFromPrefix } from \"./utils\";\n\nexport async function processFrontendTemplates(\n  vfs: VirtualFileSystem,\n  templates: TemplateData,\n  config: ProjectConfig,\n): Promise<void> {\n  const hasReactWeb = config.frontend.some((f) =>\n    [\"tanstack-router\", \"react-router\", \"tanstack-start\", \"next\"].includes(f),\n  );\n  const hasNuxtWeb = config.frontend.includes(\"nuxt\");\n  const hasSvelteWeb = config.frontend.includes(\"svelte\");\n  const hasSolidWeb = config.frontend.includes(\"solid\");\n  const hasAstroWeb = config.frontend.includes(\"astro\");\n  const hasNativeBare = config.frontend.includes(\"native-bare\");\n  const hasNativeUniwind = config.frontend.includes(\"native-uniwind\");\n  const hasUnistyles = config.frontend.includes(\"native-unistyles\");\n  const isConvex = config.backend === \"convex\";\n\n  if (hasReactWeb || hasNuxtWeb || hasSvelteWeb || hasSolidWeb || hasAstroWeb) {\n    if (hasReactWeb) {\n      processTemplatesFromPrefix(vfs, templates, \"frontend/react/web-base\", \"apps/web\", config);\n\n      const reactFramework = config.frontend.find((f) =>\n        [\"tanstack-router\", \"react-router\", \"tanstack-start\", \"next\"].includes(f),\n      );\n      if (reactFramework) {\n        processTemplatesFromPrefix(\n          vfs,\n          templates,\n          `frontend/react/${reactFramework}`,\n          \"apps/web\",\n          config,\n        );\n      }\n    } else if (hasNuxtWeb) {\n      processTemplatesFromPrefix(vfs, templates, \"frontend/nuxt\", \"apps/web\", config);\n    } else if (hasSvelteWeb) {\n      processTemplatesFromPrefix(vfs, templates, \"frontend/svelte\", \"apps/web\", config);\n    } else if (hasSolidWeb) {\n      processTemplatesFromPrefix(vfs, templates, \"frontend/solid\", \"apps/web\", config);\n    } else if (hasAstroWeb) {\n      processTemplatesFromPrefix(vfs, templates, \"frontend/astro\", \"apps/web\", config);\n    }\n  }\n\n  if (hasNativeBare || hasNativeUniwind || hasUnistyles) {\n    processTemplatesFromPrefix(vfs, templates, \"frontend/native/base\", \"apps/native\", config);\n\n    if (hasNativeBare) {\n      processTemplatesFromPrefix(vfs, templates, \"frontend/native/bare\", \"apps/native\", config);\n    } else if (hasNativeUniwind) {\n      processTemplatesFromPrefix(vfs, templates, \"frontend/native/uniwind\", \"apps/native\", config);\n    } else if (hasUnistyles) {\n      processTemplatesFromPrefix(\n        vfs,\n        templates,\n        \"frontend/native/unistyles\",\n        \"apps/native\",\n        config,\n      );\n    }\n\n    if (!isConvex && (config.api === \"trpc\" || config.api === \"orpc\")) {\n      processTemplatesFromPrefix(vfs, templates, `api/${config.api}/native`, \"apps/native\", config);\n    }\n  }\n}\n"
  },
  {
    "path": "packages/template-generator/src/template-handlers/index.ts",
    "content": "export { type TemplateData, processTemplatesFromPrefix, hasTemplatesWithPrefix } from \"./utils\";\nexport { processBaseTemplate } from \"./base\";\nexport { processFrontendTemplates } from \"./frontend\";\nexport { processBackendTemplates } from \"./backend\";\nexport { processDbTemplates } from \"./database\";\nexport { processApiTemplates } from \"./api\";\nexport { processConfigPackage, processEnvPackage, processUiPackage } from \"./packages\";\nexport { processAuthTemplates } from \"./auth\";\nexport { processPaymentsTemplates } from \"./payments\";\nexport { processAddonTemplates } from \"./addons\";\nexport { processExampleTemplates } from \"./examples\";\nexport { processExtrasTemplates } from \"./extras\";\nexport { processDeployTemplates } from \"./deploy\";\n"
  },
  {
    "path": "packages/template-generator/src/template-handlers/packages.ts",
    "content": "import type { ProjectConfig } from \"@better-t-stack/types\";\n\nimport type { VirtualFileSystem } from \"../core/virtual-fs\";\nimport { type TemplateData, processTemplatesFromPrefix, processSingleTemplate } from \"./utils\";\n\nexport async function processConfigPackage(\n  vfs: VirtualFileSystem,\n  templates: TemplateData,\n  config: ProjectConfig,\n): Promise<void> {\n  processTemplatesFromPrefix(vfs, templates, \"packages/config\", \"packages/config\", config);\n}\n\nexport async function processEnvPackage(\n  vfs: VirtualFileSystem,\n  templates: TemplateData,\n  config: ProjectConfig,\n): Promise<void> {\n  const hasWebFrontend = config.frontend.some((f) =>\n    [\n      \"tanstack-router\",\n      \"react-router\",\n      \"tanstack-start\",\n      \"next\",\n      \"nuxt\",\n      \"svelte\",\n      \"solid\",\n      \"astro\",\n    ].includes(f),\n  );\n  const hasNative = config.frontend.some((f) =>\n    [\"native-bare\", \"native-uniwind\", \"native-unistyles\"].includes(f),\n  );\n\n  if (!hasWebFrontend && !hasNative && config.backend === \"none\") return;\n\n  // Process base env package files (package.json, tsconfig.json)\n  processSingleTemplate(\n    vfs,\n    templates,\n    \"packages/env/package.json\",\n    \"packages/env/package.json\",\n    config,\n  );\n  processSingleTemplate(\n    vfs,\n    templates,\n    \"packages/env/tsconfig.json\",\n    \"packages/env/tsconfig.json\",\n    config,\n  );\n\n  // Conditionally include web.ts\n  if (hasWebFrontend) {\n    processSingleTemplate(\n      vfs,\n      templates,\n      \"packages/env/src/web.ts\",\n      \"packages/env/src/web.ts\",\n      config,\n    );\n  }\n\n  // Conditionally include native.ts only when native frontend is selected\n  if (hasNative) {\n    processSingleTemplate(\n      vfs,\n      templates,\n      \"packages/env/src/native.ts\",\n      \"packages/env/src/native.ts\",\n      config,\n    );\n  }\n\n  // Conditionally include server.ts when backend is NOT none and NOT convex\n  if (config.backend !== \"none\" && config.backend !== \"convex\") {\n    processSingleTemplate(\n      vfs,\n      templates,\n      \"packages/env/src/server.ts\",\n      \"packages/env/src/server.ts\",\n      config,\n    );\n\n    if (\n      config.serverDeploy === \"cloudflare\" ||\n      (config.backend === \"self\" && config.webDeploy === \"cloudflare\")\n    ) {\n      processSingleTemplate(\n        vfs,\n        templates,\n        \"packages/env/src/cloudflare-local.ts\",\n        \"packages/env/src/cloudflare-local.ts\",\n        config,\n      );\n    }\n  }\n}\n\nexport async function processUiPackage(\n  vfs: VirtualFileSystem,\n  templates: TemplateData,\n  config: ProjectConfig,\n): Promise<void> {\n  const hasReactWeb = config.frontend.some((f) =>\n    [\"tanstack-router\", \"react-router\", \"tanstack-start\", \"next\"].includes(f),\n  );\n\n  if (!hasReactWeb) return;\n\n  processTemplatesFromPrefix(vfs, templates, \"packages/ui\", \"packages/ui\", config);\n}\n"
  },
  {
    "path": "packages/template-generator/src/template-handlers/payments.ts",
    "content": "import type { ProjectConfig } from \"@better-t-stack/types\";\n\nimport type { VirtualFileSystem } from \"../core/virtual-fs\";\nimport { type TemplateData, processTemplatesFromPrefix } from \"./utils\";\n\nexport async function processPaymentsTemplates(\n  vfs: VirtualFileSystem,\n  templates: TemplateData,\n  config: ProjectConfig,\n): Promise<void> {\n  if (!config.payments || config.payments === \"none\") return;\n  if (config.backend === \"convex\") return;\n\n  const hasReactWeb = config.frontend.some((f) =>\n    [\"tanstack-router\", \"react-router\", \"tanstack-start\", \"next\"].includes(f),\n  );\n  const hasNuxtWeb = config.frontend.includes(\"nuxt\");\n  const hasSvelteWeb = config.frontend.includes(\"svelte\");\n  const hasSolidWeb = config.frontend.includes(\"solid\");\n\n  if (config.backend !== \"none\") {\n    processTemplatesFromPrefix(\n      vfs,\n      templates,\n      `payments/${config.payments}/server/base`,\n      \"packages/auth\",\n      config,\n    );\n  }\n\n  if (hasReactWeb) {\n    const reactFramework = config.frontend.find((f) =>\n      [\"tanstack-router\", \"react-router\", \"tanstack-start\", \"next\"].includes(f),\n    );\n    if (reactFramework) {\n      processTemplatesFromPrefix(\n        vfs,\n        templates,\n        `payments/${config.payments}/web/react/${reactFramework}`,\n        \"apps/web\",\n        config,\n      );\n    }\n  } else if (hasNuxtWeb) {\n    processTemplatesFromPrefix(\n      vfs,\n      templates,\n      `payments/${config.payments}/web/nuxt`,\n      \"apps/web\",\n      config,\n    );\n  } else if (hasSvelteWeb) {\n    processTemplatesFromPrefix(\n      vfs,\n      templates,\n      `payments/${config.payments}/web/svelte`,\n      \"apps/web\",\n      config,\n    );\n  } else if (hasSolidWeb) {\n    processTemplatesFromPrefix(\n      vfs,\n      templates,\n      `payments/${config.payments}/web/solid`,\n      \"apps/web\",\n      config,\n    );\n  }\n}\n"
  },
  {
    "path": "packages/template-generator/src/template-handlers/utils.ts",
    "content": "import type { ProjectConfig } from \"@better-t-stack/types\";\n\nimport { processTemplateString, transformFilename, isBinaryFile } from \"../core/template-processor\";\nimport type { VirtualFileSystem } from \"../core/virtual-fs\";\n\nexport type TemplateData = Map<string, string>;\n\nexport function hasTemplatesWithPrefix(templates: TemplateData, prefix: string): boolean {\n  const normalizedPrefix = prefix.endsWith(\"/\") ? prefix : `${prefix}/`;\n  for (const path of templates.keys()) {\n    if (path.startsWith(normalizedPrefix)) return true;\n  }\n  return false;\n}\n\nexport function processSingleTemplate(\n  vfs: VirtualFileSystem,\n  templates: TemplateData,\n  templatePath: string,\n  destPath: string,\n  config: ProjectConfig,\n): void {\n  const templateKey = templatePath.endsWith(\".hbs\") ? templatePath : `${templatePath}.hbs`;\n  const content = templates.get(templateKey);\n\n  if (!content) return;\n\n  let processedContent: string;\n  if (isBinaryFile(templateKey)) {\n    processedContent = \"[Binary file]\";\n  } else if (templateKey.endsWith(\".hbs\")) {\n    processedContent = processTemplateString(content, config);\n  } else {\n    processedContent = content;\n  }\n\n  // Pass original template path for binary files\n  const sourcePath = isBinaryFile(templateKey) ? templateKey : undefined;\n  vfs.writeFile(destPath, processedContent, sourcePath);\n}\n\nexport function processTemplatesFromPrefix(\n  vfs: VirtualFileSystem,\n  templates: TemplateData,\n  prefix: string,\n  destPrefix: string,\n  config: ProjectConfig,\n): void {\n  const normalizedPrefix = prefix.endsWith(\"/\") ? prefix : `${prefix}/`;\n\n  for (const [templatePath, content] of templates) {\n    if (!templatePath.startsWith(normalizedPrefix)) continue;\n\n    const relativePath = templatePath.slice(normalizedPrefix.length);\n    const outputPath = transformFilename(relativePath);\n    const destPath = destPrefix ? `${destPrefix}/${outputPath}` : outputPath;\n\n    let processedContent: string;\n    if (isBinaryFile(templatePath)) {\n      processedContent = \"[Binary file]\";\n    } else if (templatePath.endsWith(\".hbs\")) {\n      processedContent = processTemplateString(content, config);\n    } else {\n      processedContent = content;\n    }\n\n    // Pass original template path for binary files\n    const sourcePath = isBinaryFile(templatePath) ? templatePath : undefined;\n    vfs.writeFile(destPath, processedContent, sourcePath);\n  }\n}\n"
  },
  {
    "path": "packages/template-generator/src/templates.generated.ts",
    "content": "// Auto-generated - DO NOT EDIT\n// Run 'bun run generate-templates' to regenerate\n\nexport const EMBEDDED_TEMPLATES: Map<string, string> = new Map([\n  [\"addons/biome/biome.json.hbs\", `{\n    \"$schema\": \"./node_modules/@biomejs/biome/configuration_schema.json\",\n\t\"vcs\": {\n\t\t\"enabled\": false,\n\t\t\"clientKind\": \"git\",\n\t\t\"useIgnoreFile\": false\n\t},\n\t\"files\": {\n\t\t\"ignoreUnknown\": false,\n\t\t\"includes\": [\n\t\t\t\"**\",\n\t\t\t\"!**/.next\",\n\t\t\t\"!**/dist\",\n\t\t\t\"!**/.turbo\",\n\t\t\t\"!**/.nx\",\n\t\t\t\"!**/dev-dist\",\n\t\t\t\"!**/.zed\",\n\t\t\t\"!**/.vscode\",\n\t\t\t\"!**/routeTree.gen.ts\",\n\t\t\t\"!**/src-tauri\",\n\t\t\t\"!**/.nuxt\",\n\t\t\t\"!bts.jsonc\",\n\t\t\t\"!**/.expo\",\n\t\t\t\"!**/.wrangler\",\n\t\t\t\"!**/.alchemy\",\n\t\t\t\"!**/.svelte-kit\",\n\t\t\t\"!**/wrangler.jsonc\",\n\t\t\t\"!**/.source\",\n\t\t\t\"!**/convex/_generated\"\n\t\t]\n\t},\n\t\"formatter\": {\n\t\t\"enabled\": true,\n\t\t\"indentStyle\": \"tab\"\n\t},\n\t\"assist\": { \"actions\": { \"source\": { \"organizeImports\": \"on\" } } },\n\t\"linter\": {\n\t\t\"enabled\": true,\n\t\t\"rules\": {\n\t\t\t\"recommended\": true,\n\t\t\t\"correctness\": {\n\t\t\t\t\"useExhaustiveDependencies\": \"info\"\n\t\t\t},\n\t\t\t\"nursery\": {\n\t\t\t\t\"useSortedClasses\": {\n\t\t\t\t\t\"level\": \"warn\",\n\t\t\t\t\t\"fix\": \"safe\",\n\t\t\t\t\t\"options\": {\n\t\t\t\t\t\t\"functions\": [\"clsx\", \"cva\", \"cn\"]\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t},\n\t\t\t\"style\": {\n\t\t\t\t\"noParameterAssign\": \"error\",\n\t\t\t\t\"useAsConstAssertion\": \"error\",\n\t\t\t\t\"useDefaultParameterLast\": \"error\",\n\t\t\t\t\"useEnumInitializers\": \"error\",\n\t\t\t\t\"useSelfClosingElements\": \"error\",\n\t\t\t\t\"useSingleVarDeclarator\": \"error\",\n\t\t\t\t\"noUnusedTemplateLiteral\": \"error\",\n\t\t\t\t\"useNumberNamespace\": \"error\",\n\t\t\t\t\"noInferrableTypes\": \"error\",\n\t\t\t\t\"noUselessElse\": \"error\"\n\t\t\t}\n\t\t}\n\t},\n\t\"javascript\": {\n\t\t\"formatter\": {\n\t\t\t\"quoteStyle\": \"double\"\n\t\t}\n\t},\n\t\"css\": {\n\t\t\"parser\": {\n\t\t\t\"tailwindDirectives\": true\n\t\t}\n\t}\n\t{{#if (or (includes frontend \"svelte\") (includes frontend \"nuxt\"))}}\n\t,\n\t\"overrides\": [\n\t\t{\n\t\t\t\"includes\": [\"**/*.svelte\", \"**/*.vue\"],\n\t\t\t\"linter\": {\n\t\t\t\t\"rules\": {\n\t\t\t\t\t\"style\": {\n\t\t\t\t\t\t\"useConst\": \"off\",\n\t\t\t\t\t\t\"useImportType\": \"off\"\n\t\t\t\t\t},\n\t\t\t\t\t\"correctness\": {\n\t\t\t\t\t\t\"noUnusedVariables\": \"off\",\n\t\t\t\t\t\t\"noUnusedImports\": \"off\"\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t]\n\t{{/if}}\n}\n`],\n  [\"addons/electrobun/apps/desktop/.gitignore\", `artifacts\n`],\n  [\"addons/electrobun/apps/desktop/electrobun.config.ts.hbs\", `import type { ElectrobunConfig } from \"electrobun\";\n\nconst webBuildDir =\n  \"{{#if (includes frontend \"react-router\")}}../web/build/client{{else if (includes frontend \"tanstack-start\")}}../web/dist/client{{else if (includes frontend \"next\")}}../web/out{{else if (includes frontend \"nuxt\")}}../web/.output/public{{else if (includes frontend \"svelte\")}}../web/build{{else}}../web/dist{{/if}}\";\n\nexport default {\n  app: {\n    name: \"{{projectName}}\",\n    identifier: \"dev.bettertstack.{{projectName}}.desktop\",\n    version: \"0.0.1\",\n  },\n  runtime: {\n    exitOnLastWindowClosed: true,\n  },\n  build: {\n    bun: {\n      entrypoint: \"src/bun/index.ts\",\n    },\n    copy: {\n      [webBuildDir]: \"views/mainview\",\n    },\n    watchIgnore: [\\`\\${webBuildDir}/**\\`],\n    mac: {\n      bundleCEF: true,\n      defaultRenderer: \"cef\",\n    },\n    linux: {\n      bundleCEF: true,\n      defaultRenderer: \"cef\",\n    },\n    win: {\n      bundleCEF: true,\n      defaultRenderer: \"cef\",\n    },\n  },\n} satisfies ElectrobunConfig;\n`],\n  [\"addons/electrobun/apps/desktop/package.json.hbs\", `{\n  \"name\": \"desktop\",\n  \"private\": true,\n  \"type\": \"module\",\n  \"scripts\": {},\n  \"dependencies\": {\n    \"electrobun\": \"^1.15.1\"\n  },\n  \"devDependencies\": {\n    \"@types/bun\": \"^1.3.4\",\n    \"concurrently\": \"^9.1.0\",\n    \"typescript\": \"^6\"\n  }\n}\n`],\n  [\"addons/electrobun/apps/desktop/src/bun/index.ts.hbs\", `import { BrowserWindow, Updater } from \"electrobun/bun\";\n\nconst DEV_SERVER_PORT = {{#if (or (includes frontend \"react-router\") (includes frontend \"svelte\"))}}5173{{else if (includes frontend \"astro\")}}4321{{else}}3001{{/if}};\nconst DEV_SERVER_URL = \\`http://localhost:\\${DEV_SERVER_PORT}\\`;\n\n// Check if the web dev server is running for HMR\nasync function getMainViewUrl(): Promise<string> {\n  const channel = await Updater.localInfo.channel();\n  if (channel === \"dev\") {\n    try {\n      await fetch(DEV_SERVER_URL, { method: \"HEAD\" });\n      console.log(\\`HMR enabled: Using web dev server at \\${DEV_SERVER_URL}\\`);\n      return DEV_SERVER_URL;\n    } catch {\n      console.log(\n        'Web dev server not running. Run \"{{packageManager}} run dev:hmr\" for HMR support.',\n      );\n    }\n  }\n\n  return \"views://mainview/index.html\";\n}\n\nconst url = await getMainViewUrl();\n\nnew BrowserWindow({\n  title: \"{{projectName}}\",\n  url,\n  frame: {\n    width: 1280,\n    height: 820,\n    x: 120,\n    y: 120,\n  },\n});\n\nconsole.log(\"Electrobun desktop shell started.\");\n`],\n  [\"addons/electrobun/apps/desktop/tsconfig.json.hbs\", `{\n  \"extends\": \"../../packages/config/tsconfig.base.json\",\n  \"compilerOptions\": {\n    \"lib\": [\"ESNext\", \"DOM\"],\n    \"target\": \"ESNext\",\n    \"module\": \"ESNext\",\n    \"moduleResolution\": \"bundler\",\n    \"noEmit\": true,\n    \"paths\": {\n      \"@/*\": [\"./src/*\"]\n    }\n  },\n  \"include\": [\"src/**/*.ts\", \"electrobun.config.ts\"]\n}\n`],\n  [\"addons/husky/.husky/pre-commit\", `lint-staged\n`],\n  [\"addons/lefthook/lefthook.yml.hbs\", `# Lefthook configuration\n# https://github.com/evilmartians/lefthook\n\npre-commit:\n  parallel: true\n  jobs:\n{{#if (includes addons \"biome\")}}\n    - name: biome\n      glob: \"*.{js,ts,cjs,mjs,d.cts,d.mts,jsx,tsx,json,jsonc}\"\n      run: {{packageManager}} biome check --write --no-errors-on-unmatched --files-ignore-unknown=true {staged_files}\n      stage_fixed: true\n{{else if (includes addons \"oxlint\")}}\n    - name: oxlint\n      run: {{packageManager}} oxlint --fix {staged_files}\n      stage_fixed: true\n    - name: oxfmt\n      run: {{packageManager}} oxfmt --write {staged_files}\n      stage_fixed: true\n{{else}}\n    # Add your pre-commit commands here\n    # Example:\n    # - name: lint\n    #   run: {{packageManagerRunCmd}} lint\n{{/if}}\n`],\n  [\"addons/pwa/apps/web/next/public/favicon/apple-touch-icon.png\", `[Binary file]`],\n  [\"addons/pwa/apps/web/next/public/favicon/favicon-96x96.png\", `[Binary file]`],\n  [\"addons/pwa/apps/web/next/public/favicon/favicon.svg\", `<svg xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" width=\"92\" height=\"92\"><svg width=\"92\" height=\"92\" viewBox=\"0 0 92 92\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n  <rect x=\"8\" y=\"8\" width=\"76\" height=\"76\" rx=\"12\" fill=\"#F5EEFF\" stroke=\"#B79AFF\" stroke-width=\"3\"></rect>\n  <text x=\"46\" y=\"56\" text-anchor=\"middle\" font-family=\"monospace\" font-size=\"40\" fill=\"#8F5BFF\">$<tspan dx=\"0\" dy=\"0\">_</tspan></text>\n</svg><style>@media (prefers-color-scheme: light) { :root { filter: none; } }\n@media (prefers-color-scheme: dark) { :root { filter: none; } }\n</style></svg>`],\n  [\"addons/pwa/apps/web/next/public/favicon/site.webmanifest.hbs\", `{\n\t\"name\": \"{{projectName}}\",\n\t\"short_name\": \"{{projectName}}\",\n\t\"icons\": [\n\t\t{\n\t\t\t\"src\": \"/web-app-manifest-192x192.png\",\n\t\t\t\"sizes\": \"192x192\",\n\t\t\t\"type\": \"image/png\",\n\t\t\t\"purpose\": \"maskable\"\n\t\t},\n\t\t{\n\t\t\t\"src\": \"/web-app-manifest-512x512.png\",\n\t\t\t\"sizes\": \"512x512\",\n\t\t\t\"type\": \"image/png\",\n\t\t\t\"purpose\": \"maskable\"\n\t\t}\n\t],\n\t\"theme_color\": \"#ffffff\",\n\t\"background_color\": \"#ffffff\",\n\t\"display\": \"standalone\"\n}\n`],\n  [\"addons/pwa/apps/web/next/public/favicon/web-app-manifest-192x192.png\", `[Binary file]`],\n  [\"addons/pwa/apps/web/next/public/favicon/web-app-manifest-512x512.png\", `[Binary file]`],\n  [\"addons/pwa/apps/web/next/src/app/manifest.ts.hbs\", `import type { MetadataRoute } from \"next\";\n\nexport default function manifest(): MetadataRoute.Manifest {\n\treturn {\n\t\tname: \"{{projectName}}\",\n\t\tshort_name: \"{{projectName}}\",\n\t\tdescription:\n\t\t\t\"my pwa app\",\n\t\tstart_url: \"/new\",\n\t\tdisplay: \"standalone\",\n\t\tbackground_color: \"#ffffff\",\n\t\ttheme_color: \"#000000\",\n\t\ticons: [\n\t\t\t{\n\t\t\t\tsrc: \"/favicon/web-app-manifest-192x192.png\",\n\t\t\t\tsizes: \"192x192\",\n\t\t\t\ttype: \"image/png\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tsrc: \"/favicon/web-app-manifest-512x512.png\",\n\t\t\t\tsizes: \"512x512\",\n\t\t\t\ttype: \"image/png\",\n\t\t\t},\n\t\t],\n\t};\n}\n`],\n  [\"addons/pwa/apps/web/vite/public/logo.png\", `[Binary file]`],\n  [\"addons/pwa/apps/web/vite/pwa-assets.config.ts.hbs\", `import {\n  defineConfig,\n  minimal2023Preset as preset,\n} from \"@vite-pwa/assets-generator/config\";\n\nexport default defineConfig({\n  headLinkOptions: {\n    preset: \"2023\",\n  },\n  preset,\n  images: [\"public/logo.png\"],\n});\n`],\n  [\"api/orpc/fullstack/astro/src/pages/rpc/[...rest].ts.hbs\", `import type { APIRoute } from \"astro\";\nimport { OpenAPIHandler } from \"@orpc/openapi/fetch\";\nimport { OpenAPIReferencePlugin } from \"@orpc/openapi/plugins\";\nimport { ZodToJsonSchemaConverter } from \"@orpc/zod/zod4\";\nimport { RPCHandler } from \"@orpc/server/fetch\";\nimport { onError } from \"@orpc/server\";\nimport { appRouter } from \"@{{projectName}}/api/routers/index\";\nimport { createContext } from \"@{{projectName}}/api/context\";\n\nconst handler = new RPCHandler(appRouter, {\n  interceptors: [\n    onError((error) => {\n      console.error(error);\n    }),\n  ],\n});\n\nconst apiHandler = new OpenAPIHandler(appRouter, {\n  plugins: [\n    new OpenAPIReferencePlugin({\n      schemaConverters: [new ZodToJsonSchemaConverter()],\n    }),\n  ],\n  interceptors: [\n    onError((error) => {\n      console.error(error);\n    }),\n  ],\n});\n\nexport const prerender = false;\n\nexport const ALL: APIRoute = async ({ request }) => {\n  const context = await createContext({ headers: request.headers });\n\n  const rpcResult = await handler.handle(request, {\n    prefix: \"/rpc\",\n    context,\n  });\n  if (rpcResult.response) return rpcResult.response;\n\n  const apiResult = await apiHandler.handle(request, {\n    prefix: \"/rpc/api-reference\",\n    context,\n  });\n  if (apiResult.response) return apiResult.response;\n\n  return new Response(\"Not found\", { status: 404 });\n};\n`],\n  [\"api/orpc/fullstack/next/src/app/api/rpc/[[...rest]]/route.ts.hbs\", `import { createContext } from \"@{{projectName}}/api/context\";\nimport { appRouter } from \"@{{projectName}}/api/routers/index\";\nimport { OpenAPIHandler } from \"@orpc/openapi/fetch\";\nimport { OpenAPIReferencePlugin } from \"@orpc/openapi/plugins\";\nimport { ZodToJsonSchemaConverter } from \"@orpc/zod/zod4\";\nimport { RPCHandler } from \"@orpc/server/fetch\";\nimport { onError } from \"@orpc/server\";\nimport { NextRequest } from \"next/server\";\n\nconst rpcHandler = new RPCHandler(appRouter, {\n\tinterceptors: [\n\t\tonError((error) => {\n\t\t\tconsole.error(error);\n\t\t}),\n\t],\n});\nconst apiHandler = new OpenAPIHandler(appRouter, {\n\tplugins: [\n\t\tnew OpenAPIReferencePlugin({\n\t\t\tschemaConverters: [new ZodToJsonSchemaConverter()],\n\t\t}),\n\t],\n\tinterceptors: [\n\t\tonError((error) => {\n\t\t\tconsole.error(error);\n\t\t}),\n\t],\n});\n\nasync function handleRequest(req: NextRequest) {\n\tconst rpcResult = await rpcHandler.handle(req, {\n\t\tprefix: \"/api/rpc\",\n\t\tcontext: await createContext(req),\n\t});\n\tif (rpcResult.response) return rpcResult.response;\n\n\tconst apiResult = await apiHandler.handle(req, {\n\t\tprefix: \"/api/rpc/api-reference\",\n\t\tcontext: await createContext(req),\n\t});\n\tif (apiResult.response) return apiResult.response;\n\n\treturn new Response(\"Not found\", { status: 404 });\n}\n\nexport const GET = handleRequest;\nexport const POST = handleRequest;\nexport const PUT = handleRequest;\nexport const PATCH = handleRequest;\nexport const DELETE = handleRequest;`],\n  [\"api/orpc/fullstack/nuxt/app/plugins/orpc.client.ts.hbs\", `import type { AppRouterClient } from \"@{{projectName}}/api/routers/index\";\nimport { createORPCClient } from \"@orpc/client\";\nimport { RPCLink } from \"@orpc/client/fetch\";\nimport { createTanstackQueryUtils } from \"@orpc/tanstack-query\";\n\nexport default defineNuxtPlugin(() => {\n  const rpcLink = new RPCLink({\n    url: \\`\\${window.location.origin}/rpc\\`,\n    {{#if (eq auth \"better-auth\")}}\n    fetch(url, options) {\n        return fetch(url, {\n        ...options,\n        credentials: \"include\",\n        });\n    },\n    {{/if}}\n  });\n\n  const client: AppRouterClient = createORPCClient(rpcLink);\n  const orpcUtils = createTanstackQueryUtils(client);\n\n  return {\n    provide: {\n      orpc: orpcUtils,\n    },\n  };\n});\n`],\n  [\"api/orpc/fullstack/nuxt/app/plugins/orpc.server.ts.hbs\", `import { createRouterClient } from \"@orpc/server\";\nimport { createTanstackQueryUtils } from \"@orpc/tanstack-query\";\nimport { appRouter } from \"@{{projectName}}/api/routers/index\";\nimport { createContext } from \"@{{projectName}}/api/context\";\n\nexport default defineNuxtPlugin(async () => {\n  const event = useRequestEvent();\n\n  const context = await createContext({\n    headers: event?.headers ?? new Headers(),\n  });\n\n  const client = createRouterClient(appRouter, {\n    context,\n  });\n\n  const orpc = createTanstackQueryUtils(client);\n\n  return {\n    provide: {\n      orpc,\n    },\n  };\n});\n`],\n  [\"api/orpc/fullstack/nuxt/server/routes/rpc/[...].ts.hbs\", `import { RPCHandler } from \"@orpc/server/fetch\";\nimport { OpenAPIHandler } from \"@orpc/openapi/fetch\";\nimport { OpenAPIReferencePlugin } from \"@orpc/openapi/plugins\";\nimport { onError } from \"@orpc/server\";\nimport { BatchHandlerPlugin } from \"@orpc/server/plugins\";\nimport { ZodToJsonSchemaConverter } from \"@orpc/zod/zod4\";\nimport { appRouter } from \"@{{projectName}}/api/routers/index\";\nimport { createContext } from \"@{{projectName}}/api/context\";\n\nconst rpcHandler = new RPCHandler(appRouter, {\n  interceptors: [\n    onError((error) => {\n      console.error(error);\n    }),\n  ],\n  plugins: [new BatchHandlerPlugin()],\n});\n\nconst apiHandler = new OpenAPIHandler(appRouter, {\n  plugins: [\n    new OpenAPIReferencePlugin({\n      schemaConverters: [new ZodToJsonSchemaConverter()],\n    }),\n  ],\n  interceptors: [\n    onError((error) => {\n      console.error(error);\n    }),\n  ],\n});\n\nexport default defineEventHandler(async (event) => {\n  const request = toWebRequest(event);\n  const context = await createContext({ headers: request.headers });\n\n  const rpcResult = await rpcHandler.handle(request, {\n    prefix: \"/rpc\",\n    context,\n  });\n  if (rpcResult.response) return rpcResult.response;\n\n  const apiResult = await apiHandler.handle(request, {\n    prefix: \"/rpc/api-reference\",\n    context,\n  });\n  if (apiResult.response) return apiResult.response;\n\n  setResponseStatus(event, 404, \"Not Found\");\n  return \"Not found\";\n});\n`],\n  [\"api/orpc/fullstack/nuxt/server/routes/rpc/index.ts.hbs\", `export { default } from \"./[...]\";\n`],\n  [\"api/orpc/fullstack/svelte/src/lib/orpc.server.ts.hbs\", `import { getRequestEvent } from \"$app/server\";\nimport { createContext } from \"@{{projectName}}/api/context\";\nimport { appRouter, type AppRouterClient } from \"@{{projectName}}/api/routers/index\";\n{{#if (eq webDeploy \"cloudflare\")}}\nimport { env as localEnv } from \"@{{projectName}}/env/server\";\n{{/if}}\nimport { createRouterClient } from \"@orpc/server\";\n\nif (typeof window !== \"undefined\") {\n\tthrow new Error(\"This file should only be imported on the server.\");\n}\n\nconst serverClient: AppRouterClient = createRouterClient(appRouter, {\n\tcontext: async () => {\n\t\tconst event = getRequestEvent();\n{{#if (eq webDeploy \"cloudflare\")}}\n\t\tconst env = event.platform?.env ?? localEnv;\n\n{{/if}}\n\t\treturn createContext({\n\t\t\theaders: event.request.headers,\n{{#if (eq webDeploy \"cloudflare\")}}\n\t\t\tenv,\n{{/if}}\n\t\t});\n\t},\n});\n\n// oRPC's SvelteKit SSR setup loads this from hooks.server.ts so $lib/orpc can\n// reuse the in-process server client during SSR and fall back to HTTP in the browser.\nglobalThis.$client = serverClient;\n`],\n  [\"api/orpc/fullstack/svelte/src/routes/rpc/[...rest]/+server.ts.hbs\", `import { createContext } from \"@{{projectName}}/api/context\";\nimport { appRouter } from \"@{{projectName}}/api/routers/index\";\n{{#if (eq webDeploy \"cloudflare\")}}\nimport { env as localEnv } from \"@{{projectName}}/env/server\";\n{{/if}}\nimport { OpenAPIHandler } from \"@orpc/openapi/fetch\";\nimport { OpenAPIReferencePlugin } from \"@orpc/openapi/plugins\";\nimport { ZodToJsonSchemaConverter } from \"@orpc/zod/zod4\";\nimport { onError } from \"@orpc/server\";\nimport { RPCHandler } from \"@orpc/server/fetch\";\nimport type { RequestHandler } from \"@sveltejs/kit\";\n\nconst rpcHandler = new RPCHandler(appRouter, {\n\tinterceptors: [\n\t\tonError((error) => {\n\t\t\tconsole.error(error);\n\t\t}),\n\t],\n});\n\nconst apiHandler = new OpenAPIHandler(appRouter, {\n\tplugins: [\n\t\tnew OpenAPIReferencePlugin({\n\t\t\tschemaConverters: [new ZodToJsonSchemaConverter()],\n\t\t}),\n\t],\n\tinterceptors: [\n\t\tonError((error) => {\n\t\t\tconsole.error(error);\n\t\t}),\n\t],\n});\n\nconst handle: RequestHandler = async ({ request{{#if (eq webDeploy \"cloudflare\")}}, platform{{/if}} }) => {\n{{#if (eq webDeploy \"cloudflare\")}}\n\tconst env = platform?.env ?? localEnv;\n\n{{/if}}\n\tconst context = await createContext({\n\t\theaders: request.headers,\n{{#if (eq webDeploy \"cloudflare\")}}\n\t\tenv,\n{{/if}}\n\t});\n\n\tconst rpcResult = await rpcHandler.handle(request, {\n\t\tprefix: \"/rpc\",\n\t\tcontext,\n\t});\n\tif (rpcResult.response) return rpcResult.response;\n\n\tconst apiResult = await apiHandler.handle(request, {\n\t\tprefix: \"/rpc/api-reference\",\n\t\tcontext,\n\t});\n\tif (apiResult.response) return apiResult.response;\n\n\treturn new Response(\"Not found\", { status: 404 });\n};\n\nexport const HEAD = handle;\nexport const GET = handle;\nexport const POST = handle;\nexport const PUT = handle;\nexport const PATCH = handle;\nexport const DELETE = handle;\n`],\n  [\"api/orpc/fullstack/tanstack-start/src/routes/api/rpc/$.ts.hbs\", `import { createContext } from \"@{{projectName}}/api/context\";\nimport { appRouter } from \"@{{projectName}}/api/routers/index\";\nimport { OpenAPIHandler } from \"@orpc/openapi/fetch\";\nimport { OpenAPIReferencePlugin } from \"@orpc/openapi/plugins\";\nimport { ZodToJsonSchemaConverter } from \"@orpc/zod/zod4\";\nimport { RPCHandler } from \"@orpc/server/fetch\";\nimport { onError } from \"@orpc/server\";\nimport { createFileRoute } from \"@tanstack/react-router\";\n\nconst rpcHandler = new RPCHandler(appRouter, {\n\tinterceptors: [\n\t\tonError((error) => {\n\t\t\tconsole.error(error);\n\t\t}),\n\t],\n});\n\nconst apiHandler = new OpenAPIHandler(appRouter, {\n\tplugins: [\n\t\tnew OpenAPIReferencePlugin({\n\t\t\tschemaConverters: [new ZodToJsonSchemaConverter()],\n\t\t}),\n\t],\n\tinterceptors: [\n\t\tonError((error) => {\n\t\t\tconsole.error(error);\n\t\t}),\n\t],\n});\n\nasync function handle({ request }: { request: Request }) {\n\tconst rpcResult = await rpcHandler.handle(request, {\n\t\tprefix: \"/api/rpc\",\n\t\tcontext: await createContext({ req: request }),\n\t});\n\tif (rpcResult.response) return rpcResult.response;\n\n\tconst apiResult = await apiHandler.handle(request, {\n\t\tprefix: \"/api/rpc/api-reference\",\n\t\tcontext: await createContext({ req: request }),\n\t});\n\tif (apiResult.response) return apiResult.response;\n\n\treturn new Response(\"Not found\", { status: 404 });\n}\n\nexport const Route = createFileRoute('/api/rpc/$')({\n  server: {\n    handlers: {\n      HEAD: handle,\n      GET: handle,\n      POST: handle,\n      PUT: handle,\n      PATCH: handle,\n      DELETE: handle,\n    },\n  },\n})`],\n  [\"api/orpc/native/utils/orpc.ts.hbs\", `import { createORPCClient } from \"@orpc/client\";\nimport { RPCLink } from \"@orpc/client/fetch\";\nimport { createTanstackQueryUtils } from \"@orpc/tanstack-query\";\nimport { QueryCache, QueryClient } from \"@tanstack/react-query\";\nimport type { AppRouterClient } from \"@{{projectName}}/api/routers/index\";\nimport { env } from \"@{{projectName}}/env/native\";\n{{#if (eq auth \"better-auth\")}}\nimport { authClient } from \"@/lib/auth-client\";\nimport { Platform } from \"react-native\";\n{{else if (eq auth \"clerk\")}}\nimport { getClerkAuthToken } from \"@/utils/clerk-auth\";\n{{/if}}\n\nexport const queryClient = new QueryClient({\n\tqueryCache: new QueryCache({\n\t\tonError: (error) => {\n\t\t\tconsole.log(error)\n\t\t},\n\t}),\n});\n\nexport const link = new RPCLink({\n{{#if (eq backend \"self\")}}\n{{#if (or (includes frontend \"next\") (includes frontend \"tanstack-start\"))}}\n\turl: \\`\\${env.EXPO_PUBLIC_SERVER_URL}/api/rpc\\`,\n{{else}}\n\turl: \\`\\${env.EXPO_PUBLIC_SERVER_URL}/rpc\\`,\n{{/if}}\n{{else}}\n\turl: \\`\\${env.EXPO_PUBLIC_SERVER_URL}/rpc\\`,\n{{/if}}\n{{#if (eq auth \"better-auth\")}}\n\tfetch:\n\t\tfunction (url, options) {\n\t\t\treturn fetch(url, {\n\t\t\t\t...options,\n\t\t\t\t// Better Auth Expo forwards the session cookie manually on native.\n\t\t\t\tcredentials: Platform.OS === \"web\" ? \"include\" : \"omit\",\n\t\t\t});\n\t\t},\n\theaders() {\n\t\tif (Platform.OS === \"web\") {\n\t\t\treturn {};\n\t\t}\n\t\tconst headers = new Map<string, string>();\n\t\tconst cookies = authClient.getCookie();\n\t\tif (cookies) {\n\t\t\theaders.set(\"Cookie\", cookies);\n\t\t}\n\t\treturn Object.fromEntries(headers);\n\t},\n{{else if (eq auth \"clerk\")}}\n\theaders: async () => {\n\t\tconst token = await getClerkAuthToken();\n\t\treturn token ? { Authorization: \\`Bearer \\${token}\\` } : {};\n\t},\n{{/if}}\n});\n\nexport const client: AppRouterClient = createORPCClient(link);\n\nexport const orpc = createTanstackQueryUtils(client);\n`],\n  [\"api/orpc/server/_gitignore\", `# dependencies (bun install)\nnode_modules\n\n# output\nout\ndist\n*.tgz\n\n# code coverage\ncoverage\n*.lcov\n\n# logs\nlogs\n_.log\nreport.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json\n\n# dotenv environment variable files\n.env\n.env.development.local\n.env.test.local\n.env.production.local\n.env.local\n\n# caches\n.eslintcache\n.cache\n*.tsbuildinfo\n\n# IntelliJ based IDEs\n.idea\n\n# Finder (MacOS) folder config\n.DS_Store\n`],\n  [\"api/orpc/server/package.json.hbs\", `{\n  \"name\": \"@{{projectName}}/api\",\n  \"exports\": {\n    \".\": {\n      \"default\": \"./src/index.ts\"\n    },\n    \"./*\": {\n      \"default\": \"./src/*.ts\"\n    }\n  },\n  \"type\": \"module\",\n  \"scripts\": {},\n  \"devDependencies\": {},\n  \"dependencies\": {}\n}`],\n  [\"api/orpc/server/src/context.ts.hbs\", `{{#if (eq auth \"clerk\")}}\ntype ClerkContextAuth = {\n\tuserId: string | null;\n};\n\ntype ClerkRequestContext = {\n\tauth: ClerkContextAuth | null;\n\tsession: null;\n};\n\nfunction toClerkContextAuth(auth: { userId: string | null } | null): ClerkContextAuth | null {\n\treturn auth ? { userId: auth.userId } : null;\n}\n{{/if}}\n\n{{#if (and (eq auth \"clerk\") (or (eq backend 'self') (eq backend 'hono') (eq backend 'elysia')))}}\n{{#if (and (eq backend \"self\") (eq webDeploy \"cloudflare\") (includes frontend \"svelte\"))}}\n{{else}}\nimport { createClerkClient } from \"@clerk/backend\";\nimport { env } from \"@{{projectName}}/env/server\";\n\nconst clerkClient = createClerkClient({\n\tsecretKey: env.CLERK_SECRET_KEY,\n\tpublishableKey: env.CLERK_PUBLISHABLE_KEY,\n});\n\nasync function authenticateClerkRequest(request: Request): Promise<ClerkContextAuth | null> {\n\tconst requestState = await clerkClient.authenticateRequest(request, {\n\t\tauthorizedParties: [env.CORS_ORIGIN],\n\t});\n\treturn toClerkContextAuth(requestState.toAuth());\n}\n{{/if}}\n{{/if}}\n\n{{#if (and (eq backend 'self') (includes frontend \"next\"))}}\nimport type { NextRequest } from \"next/server\";\n{{#if (eq auth \"better-auth\")}}\n{{#if (or (eq runtime \"workers\") (eq serverDeploy \"cloudflare\") (and (eq backend \"self\") (eq webDeploy \"cloudflare\")))}}\nimport { createAuth } from \"@{{projectName}}/auth\";\n{{else}}\nimport { auth } from \"@{{projectName}}/auth\";\n{{/if}}\n{{/if}}\n\nexport async function createContext(req: NextRequest){{#if (eq auth \"clerk\")}}: Promise<ClerkRequestContext>{{/if}} {\n{{#if (eq auth \"better-auth\")}}\n\tconst session = await {{#if (or (eq runtime \"workers\") (eq serverDeploy \"cloudflare\") (and (eq backend \"self\") (eq webDeploy \"cloudflare\")))}}createAuth(){{else}}auth{{/if}}.api.getSession({\n\t\theaders: req.headers,\n\t});\n\treturn {\n\t\tauth: null,\n\t\tsession,\n\t};\n{{else if (eq auth \"clerk\")}}\n\tconst clerkAuth = await authenticateClerkRequest(req);\n\treturn {\n\t\tauth: clerkAuth,\n\t\tsession: null,\n\t};\n{{else}}\n\treturn {\n\t\tauth: null,\n\t\tsession: null,\n\t};\n{{/if}}\n}\n\n{{else if (and (eq backend 'self') (includes frontend \"tanstack-start\"))}}\n{{#if (eq auth \"better-auth\")}}\n{{#if (or (eq runtime \"workers\") (eq serverDeploy \"cloudflare\") (and (eq backend \"self\") (eq webDeploy \"cloudflare\")))}}\nimport { createAuth } from \"@{{projectName}}/auth\";\n{{else}}\nimport { auth } from \"@{{projectName}}/auth\";\n{{/if}}\n{{/if}}\n\nexport async function createContext({ req }: { req: Request }){{#if (eq auth \"clerk\")}}: Promise<ClerkRequestContext>{{/if}} {\n{{#if (eq auth \"better-auth\")}}\n\tconst session = await {{#if (or (eq runtime \"workers\") (eq serverDeploy \"cloudflare\") (and (eq backend \"self\") (eq webDeploy \"cloudflare\")))}}createAuth(){{else}}auth{{/if}}.api.getSession({\n\t\theaders: req.headers,\n\t});\n\treturn {\n\t\tauth: null,\n\t\tsession,\n\t};\n{{else if (eq auth \"clerk\")}}\n\tconst clerkAuth = await authenticateClerkRequest(req);\n\treturn {\n\t\tauth: clerkAuth,\n\t\tsession: null,\n\t};\n{{else}}\n\treturn {\n\t\tauth: null,\n\t\tsession: null,\n\t};\n{{/if}}\n}\n\n{{else if (and (eq backend 'self') (includes frontend \"nuxt\"))}}\n{{#if (eq auth \"better-auth\")}}\n{{#if (or (eq runtime \"workers\") (eq serverDeploy \"cloudflare\") (and (eq backend \"self\") (eq webDeploy \"cloudflare\")))}}\nimport { createAuth } from \"@{{projectName}}/auth\";\n{{else}}\nimport { auth } from \"@{{projectName}}/auth\";\n{{/if}}\n{{/if}}\n\nexport type CreateContextOptions = {\n\theaders: Headers;\n};\n\nexport async function createContext({ headers }: CreateContextOptions) {\n{{#if (eq auth \"better-auth\")}}\n\tconst session = await {{#if (or (eq runtime \"workers\") (eq serverDeploy \"cloudflare\") (and (eq backend \"self\") (eq webDeploy \"cloudflare\")))}}createAuth(){{else}}auth{{/if}}.api.getSession({ headers });\n\treturn {\n\t\tauth: null,\n\t\tsession,\n\t};\n{{else}}\n\treturn {\n\t\tauth: null,\n\t\tsession: null,\n\t};\n{{/if}}\n}\n\n{{else if (and (eq backend 'self') (includes frontend \"svelte\"))}}\n{{#if (eq auth \"better-auth\")}}\n{{#if (or (eq runtime \"workers\") (eq serverDeploy \"cloudflare\") (and (eq backend \"self\") (eq webDeploy \"cloudflare\")))}}\nimport { createAuth } from \"@{{projectName}}/auth\";\n{{else}}\nimport { auth } from \"@{{projectName}}/auth\";\n{{/if}}\n{{/if}}\n{{#if (eq webDeploy \"cloudflare\")}}\nimport type {} from \"@{{projectName}}/env/server\";\n{{/if}}\n\nexport type CreateContextOptions = {\n\theaders: Headers;\n{{#if (eq webDeploy \"cloudflare\")}}\n\tenv: Env;\n{{/if}}\n};\n\nexport async function createContext({ headers{{#if (eq webDeploy \"cloudflare\")}}, env{{/if}} }: CreateContextOptions) {\n{{#if (eq auth \"better-auth\")}}\n\tconst session = await {{#if (or (eq runtime \"workers\") (eq serverDeploy \"cloudflare\") (and (eq backend \"self\") (eq webDeploy \"cloudflare\")))}}createAuth({{#if (eq webDeploy \"cloudflare\")}}env{{/if}}){{else}}auth{{/if}}.api.getSession({ headers });\n\treturn {\n\t\tauth: null,\n\t\tsession,\n{{#if (eq webDeploy \"cloudflare\")}}\n\t\tenv,\n{{/if}}\n\t};\n{{else}}\n\treturn {\n\t\tauth: null,\n\t\tsession: null,\n{{#if (eq webDeploy \"cloudflare\")}}\n\t\tenv,\n{{/if}}\n\t};\n{{/if}}\n}\n\n{{else if (and (eq backend 'self') (includes frontend \"astro\"))}}\n{{#if (eq auth \"better-auth\")}}\n{{#if (or (eq runtime \"workers\") (eq serverDeploy \"cloudflare\") (and (eq backend \"self\") (eq webDeploy \"cloudflare\")))}}\nimport { createAuth } from \"@{{projectName}}/auth\";\n{{else}}\nimport { auth } from \"@{{projectName}}/auth\";\n{{/if}}\n{{/if}}\n\nexport type CreateContextOptions = {\n\theaders: Headers;\n};\n\nexport async function createContext({ headers }: CreateContextOptions) {\n{{#if (eq auth \"better-auth\")}}\n\tconst session = await {{#if (or (eq runtime \"workers\") (eq serverDeploy \"cloudflare\") (and (eq backend \"self\") (eq webDeploy \"cloudflare\")))}}createAuth(){{else}}auth{{/if}}.api.getSession({ headers });\n\treturn {\n\t\tauth: null,\n\t\tsession,\n\t};\n{{else}}\n\treturn {\n\t\tauth: null,\n\t\tsession: null,\n\t};\n{{/if}}\n}\n\n{{else if (eq backend 'hono')}}\nimport type { Context as HonoContext } from \"hono\";\n{{#if (eq auth \"better-auth\")}}\n{{#if (or (eq runtime \"workers\") (eq serverDeploy \"cloudflare\") (and (eq backend \"self\") (eq webDeploy \"cloudflare\")))}}\nimport { createAuth } from \"@{{projectName}}/auth\";\n{{else}}\nimport { auth } from \"@{{projectName}}/auth\";\n{{/if}}\n{{/if}}\n\nexport type CreateContextOptions = {\n\tcontext: HonoContext;\n};\n\nexport async function createContext({ context }: CreateContextOptions){{#if (eq auth \"clerk\")}}: Promise<ClerkRequestContext>{{/if}} {\n{{#if (eq auth \"better-auth\")}}\n\tconst session = await {{#if (or (eq runtime \"workers\") (eq serverDeploy \"cloudflare\") (and (eq backend \"self\") (eq webDeploy \"cloudflare\")))}}createAuth(){{else}}auth{{/if}}.api.getSession({\n\t\theaders: context.req.raw.headers,\n\t});\n\treturn {\n\t\tauth: null,\n\t\tsession,\n\t};\n{{else if (eq auth \"clerk\")}}\n\tconst clerkAuth = await authenticateClerkRequest(context.req.raw);\n\treturn {\n\t\tauth: clerkAuth,\n\t\tsession: null,\n\t};\n{{else}}\n\treturn {\n\t\tauth: null,\n\t\tsession: null,\n\t};\n{{/if}}\n}\n\n{{else if (eq backend 'elysia')}}\nimport type { Context as ElysiaContext } from \"elysia\";\n{{#if (eq auth \"better-auth\")}}\nimport { auth } from \"@{{projectName}}/auth\";\n{{/if}}\n\nexport type CreateContextOptions = {\n\tcontext: ElysiaContext;\n};\n\nexport async function createContext({ context }: CreateContextOptions){{#if (eq auth \"clerk\")}}: Promise<ClerkRequestContext>{{/if}} {\n{{#if (eq auth \"better-auth\")}}\n\tconst session = await auth.api.getSession({\n\t\theaders: context.request.headers,\n\t});\n\treturn {\n\t\tauth: null,\n\t\tsession,\n\t};\n{{else if (eq auth \"clerk\")}}\n\tconst clerkAuth = await authenticateClerkRequest(context.request);\n\treturn {\n\t\tauth: clerkAuth,\n\t\tsession: null,\n\t};\n{{else}}\n\treturn {\n\t\tauth: null,\n\t\tsession: null,\n\t};\n{{/if}}\n}\n\n{{else if (eq backend 'express')}}\nimport type { Request } from \"express\";\n{{#if (eq auth \"better-auth\")}}\nimport { fromNodeHeaders } from \"better-auth/node\";\nimport { auth } from \"@{{projectName}}/auth\";\n{{else if (eq auth \"clerk\")}}\nimport { getAuth } from \"@clerk/express\";\n{{/if}}\n\ninterface CreateContextOptions {\n\treq: Request;\n}\n\nexport async function createContext(opts: CreateContextOptions){{#if (eq auth \"clerk\")}}: Promise<ClerkRequestContext>{{/if}} {\n{{#if (eq auth \"better-auth\")}}\n\tconst session = await auth.api.getSession({\n\t\theaders: fromNodeHeaders(opts.req.headers),\n\t});\n\treturn {\n\t\tauth: null,\n\t\tsession,\n\t};\n{{else if (eq auth \"clerk\")}}\n\tconst clerkAuth = toClerkContextAuth(getAuth(opts.req));\n\treturn {\n\t\tauth: clerkAuth,\n\t\tsession: null,\n\t};\n{{else}}\n\treturn {\n\t\tauth: null,\n\t\tsession: null,\n\t};\n{{/if}}\n}\n\n{{else if (eq backend 'fastify')}}\n{{#if (eq auth \"better-auth\")}}\nimport type { IncomingHttpHeaders } from \"node:http\";\nimport { fromNodeHeaders } from \"better-auth/node\";\nimport { auth } from \"@{{projectName}}/auth\";\n{{else if (eq auth \"clerk\")}}\nimport { getAuth } from \"@clerk/fastify\";\n{{else}}\nimport type { IncomingHttpHeaders } from \"node:http\";\n{{/if}}\n\nexport async function createContext(req: {{#if (eq auth \"clerk\")}}Parameters<typeof getAuth>[0]{{else}}IncomingHttpHeaders{{/if}}){{#if (eq auth \"clerk\")}}: Promise<ClerkRequestContext>{{/if}} {\n{{#if (eq auth \"better-auth\")}}\n\tconst session = await auth.api.getSession({\n\t\theaders: fromNodeHeaders(req),\n\t});\n\treturn {\n\t\tauth: null,\n\t\tsession,\n\t};\n{{else if (eq auth \"clerk\")}}\n\tconst clerkAuth = toClerkContextAuth(getAuth(req));\n\treturn {\n\t\tauth: clerkAuth,\n\t\tsession: null,\n\t};\n{{else}}\n\treturn {\n\t\tauth: null,\n\t\tsession: null,\n\t};\n{{/if}}\n}\n\n{{else}}\nexport async function createContext() {\n\treturn {\n\t\tauth: null,\n\t\tsession: null,\n\t};\n}\n{{/if}}\n\nexport type Context = Awaited<ReturnType<typeof createContext>>;\n`],\n  [\"api/orpc/server/src/index.ts.hbs\", `import { ORPCError, os } from \"@orpc/server\";\nimport type { Context } from \"./context\";\n\nexport const o = os.$context<Context>();\n\nexport const publicProcedure = o;\n\n{{#if (or (eq auth \"better-auth\") (eq auth \"clerk\"))}}\nconst requireAuth = o.middleware(async ({ context, next }) => {\n  {{#if (eq auth \"better-auth\")}}\n  if (!context.session?.user) {\n    throw new ORPCError(\"UNAUTHORIZED\");\n  }\n  return next({\n    context: {\n      session: context.session,\n    },\n  });\n  {{else}}\n  if (!context.auth?.userId) {\n    throw new ORPCError(\"UNAUTHORIZED\");\n  }\n  return next({\n    context: {\n      auth: context.auth,\n    },\n  });\n  {{/if}}\n});\n\nexport const protectedProcedure = publicProcedure.use(requireAuth);\n{{/if}}\n`],\n  [\"api/orpc/server/src/routers/index.ts.hbs\", `{{#if (eq api \"orpc\")}}\nimport { {{#if (or (eq auth \"better-auth\") (eq auth \"clerk\"))}}protectedProcedure, {{/if}}publicProcedure } from \"../index\";\nimport type { RouterClient } from \"@orpc/server\";\n{{#if (includes examples \"todo\")}}\nimport { todoRouter } from \"./todo\";\n{{/if}}\n\nexport const appRouter = {\n  healthCheck: publicProcedure.handler(() => {\n    return \"OK\";\n  }),\n  {{#if (or (eq auth \"better-auth\") (eq auth \"clerk\"))}}\n  privateData: protectedProcedure.handler(({ context }) => {\n    return {\n      message: \"This is private\",\n      {{#if (eq auth \"better-auth\")}}\n      user: context.session?.user,\n      {{else}}\n      userId: context.auth?.userId,\n      {{/if}}\n    };\n  }),\n  {{/if}}\n  {{#if (includes examples \"todo\")}}\n  todo: todoRouter,\n  {{/if}}\n};\nexport type AppRouter = typeof appRouter;\nexport type AppRouterClient = RouterClient<typeof appRouter>;\n{{else if (eq api \"trpc\")}}\nimport {\n  {{#if (eq auth \"better-auth\")}}protectedProcedure, {{/if}}publicProcedure,\n  router,\n} from \"../index\";\n{{#if (includes examples \"todo\")}}\nimport { todoRouter } from \"./todo\";\n{{/if}}\n\nexport const appRouter = router({\n  healthCheck: publicProcedure.query(() => {\n    return \"OK\";\n  }),\n  {{#if (eq auth \"better-auth\")}}\n  privateData: protectedProcedure.query(({ ctx }) => {\n    return {\n      message: \"This is private\",\n      user: ctx.session.user,\n    };\n  }),\n  {{/if}}\n  {{#if (includes examples \"todo\")}}\n  todo: todoRouter,\n  {{/if}}\n});\nexport type AppRouter = typeof appRouter;\n{{else}}\nexport const appRouter = {};\nexport type AppRouter = typeof appRouter;\n{{/if}}\n`],\n  [\"api/orpc/server/tsconfig.json.hbs\", `{\n  \"extends\": \"@{{projectName}}/config/tsconfig.base.json\",\n  \"compilerOptions\": {\n    \"declaration\": true,\n    \"declarationMap\": true,\n    \"sourceMap\": true,\n    \"outDir\": \"dist\",\n    \"composite\": true\n  }\n}`],\n  [\"api/orpc/web/astro/src/lib/orpc.ts.hbs\", `import type { AppRouterClient } from \"@{{projectName}}/api/routers/index\";\n\nimport { createORPCClient } from \"@orpc/client\";\nimport { RPCLink } from \"@orpc/client/fetch\";\n\n{{#if (eq backend \"self\")}}\nexport const link = new RPCLink({\n  url: \\`\\${window.location.origin}/rpc\\`,\n});\n{{else}}\nimport { PUBLIC_SERVER_URL } from \"astro:env/client\";\n\nexport const link = new RPCLink({\n  url: \\`\\${PUBLIC_SERVER_URL}/rpc\\`,\n{{#if (eq auth \"better-auth\")}}\n  fetch(url, options) {\n    return fetch(url, {\n      ...options,\n      credentials: \"include\",\n    });\n  },\n{{/if}}\n});\n{{/if}}\n\nexport const orpc: AppRouterClient = createORPCClient(link);\n`],\n  [\"api/orpc/web/nuxt/app/plugins/orpc.ts.hbs\", `import { defineNuxtPlugin } from '#app'\nimport type { AppRouterClient } from \"@{{projectName}}/api/routers/index\";\nimport { createORPCClient } from '@orpc/client'\nimport { RPCLink } from '@orpc/client/fetch'\nimport { createTanstackQueryUtils } from \"@orpc/tanstack-query\";\n\nexport default defineNuxtPlugin(() => {\n  const config = useRuntimeConfig();\n  const rpcUrl = \\`\\${config.public.serverUrl}/rpc\\`;\n\n  const rpcLink = new RPCLink({\n    url: rpcUrl,\n    {{#if (eq auth \"better-auth\")}}\n    fetch(url, options) {\n        return fetch(url, {\n        ...options,\n        credentials: \"include\",\n        });\n    },\n    {{/if}}\n  })\n\n\n  const client: AppRouterClient = createORPCClient(rpcLink)\n  const orpcUtils = createTanstackQueryUtils(client)\n\n  return {\n    provide: {\n      orpc: orpcUtils\n    }\n  }\n})\n`],\n  [\"api/orpc/web/nuxt/app/plugins/vue-query.ts.hbs\", `import type {\n  DehydratedState,\n  VueQueryPluginOptions,\n} from '@tanstack/vue-query'\nimport {\n  dehydrate,\n  hydrate,\n  QueryCache,\n  QueryClient,\n  VueQueryPlugin,\n} from '@tanstack/vue-query'\n\nexport default defineNuxtPlugin((nuxt) => {\n  const vueQueryState = useState<DehydratedState | null>('vue-query')\n\n  const toast = useToast()\n\n  const queryClient = new QueryClient({\n    defaultOptions: {\n      queries: {\n        staleTime: 5_000,\n      },\n    },\n    queryCache: new QueryCache({\n      onError: (error) => {\n        console.log(error)\n        toast.add({\n          title: 'Error',\n          description: error?.message || 'An unexpected error occurred.',\n        })\n      },\n    }),\n  })\n  const options: VueQueryPluginOptions = { queryClient }\n\n  nuxt.vueApp.use(VueQueryPlugin, options)\n\n  if (import.meta.server) {\n    nuxt.hooks.hook('app:rendered', () => {\n      vueQueryState.value = dehydrate(queryClient)\n    })\n  }\n\n  if (import.meta.client) {\n    nuxt.hooks.hook('app:created', () => {\n      hydrate(queryClient, vueQueryState.value)\n    })\n  }\n})\n`],\n  [\"api/orpc/web/react/base/src/utils/orpc.ts.hbs\", `import { createORPCClient } from \"@orpc/client\";\nimport { RPCLink } from \"@orpc/client/fetch\";\nimport { createTanstackQueryUtils } from \"@orpc/tanstack-query\";\nimport { QueryCache, QueryClient } from \"@tanstack/react-query\";\nimport { toast } from \"sonner\";\n{{#if (and (includes frontend \"tanstack-start\") (eq backend \"self\"))}}\nimport { createRouterClient } from \"@orpc/server\";\nimport type { RouterClient } from \"@orpc/server\";\nimport { createIsomorphicFn } from \"@tanstack/react-start\";\nimport { getRequest } from \"@tanstack/react-start/server\";\nimport { appRouter } from \"@{{projectName}}/api/routers/index\";\nimport { createContext } from \"@{{projectName}}/api/context\";\n{{else if (includes frontend \"tanstack-start\")}}\nimport type { RouterClient } from \"@orpc/server\";\nimport type { AppRouter } from \"@{{projectName}}/api/routers/index\";\nimport { env } from \"@{{projectName}}/env/web\";\n{{#if (eq auth \"clerk\")}}\nimport { getClerkAuthToken } from \"@/utils/clerk-auth\";\n{{/if}}\n{{else}}\nimport type { AppRouterClient } from \"@{{projectName}}/api/routers/index\";\n{{#unless (eq backend \"self\")}}\nimport { env } from \"@{{projectName}}/env/web\";\n{{/unless}}\n{{#if (eq auth \"clerk\")}}\nimport { getClerkAuthToken } from \"@/utils/clerk-auth\";\n{{/if}}\n{{/if}}\n\nexport const queryClient = new QueryClient({\n\tqueryCache: new QueryCache({\n\t\tonError: (error, query) => {\n\t\t\ttoast.error(\\`Error: \\${error.message}\\`, {\n\t\t\t\taction: {\n\t\t\t\t\tlabel: \"retry\",\n\t\t\t\t\tonClick: query.invalidate,\n\t\t\t\t},\n\t\t\t});\n\t\t},\n\t}),\n});\n\n{{#if (and (includes frontend \"tanstack-start\") (eq backend \"self\"))}}\nconst getORPCClient = createIsomorphicFn()\n\t.server(() =>\n\t\tcreateRouterClient(appRouter, {\n\t\t\tcontext: async () => {\n\t\t\t\treturn createContext({ req: getRequest() });\n\t\t\t},\n\t\t}),\n\t)\n\t.client((): RouterClient<typeof appRouter> => {\n\t\t\tconst link = new RPCLink({\n\t\t\turl: \\`\\${window.location.origin}/api/rpc\\`,\n{{#if (eq auth \"better-auth\")}}\n\t\t\tfetch(url, options) {\n\t\t\t\treturn fetch(url, {\n\t\t\t\t\t...options,\n\t\t\t\t\tcredentials: \"include\",\n\t\t\t\t});\n\t\t\t},\n{{/if}}\n\t\t});\n\n\t\treturn createORPCClient(link);\n\t});\n\nexport const client: RouterClient<typeof appRouter> = getORPCClient();\n{{else if (includes frontend \"tanstack-start\")}}\nconst link = new RPCLink({\n\turl: \\`\\${env.VITE_SERVER_URL}/rpc\\`,\n{{#if (eq auth \"clerk\")}}\n\theaders: async () => {\n\t\tconst token = await getClerkAuthToken();\n\t\treturn token ? { Authorization: \\`Bearer \\${token}\\` } : {};\n\t},\n{{/if}}\n{{#if (eq auth \"better-auth\")}}\n\tfetch(url, options) {\n\t\treturn fetch(url, {\n\t\t\t...options,\n\t\t\tcredentials: \"include\",\n\t\t});\n\t},\n{{/if}}\n});\n\nconst getORPCClient = () => {\n\treturn createORPCClient(link) as RouterClient<AppRouter>;\n};\n\nexport const client: RouterClient<AppRouter> = getORPCClient();\n{{else}}\nexport const link = new RPCLink({\n{{#if (and (eq backend \"self\") (includes frontend \"next\"))}}\n\turl: \\`\\${typeof window !== \"undefined\" ? window.location.origin : \"http://localhost:3001\"}/api/rpc\\`,\n{{else if (includes frontend \"next\")}}\n\turl: \\`\\${env.NEXT_PUBLIC_SERVER_URL}/rpc\\`,\n{{else}}\n\turl: \\`\\${env.VITE_SERVER_URL}/rpc\\`,\n{{/if}}\n{{#if (eq auth \"clerk\")}}\n\theaders: async () => {\n\t\t{{#if (includes frontend \"next\")}}\n\t\tif (typeof window !== \"undefined\") {\n\t\t\tconst token = await getClerkAuthToken();\n\t\t\treturn token ? { Authorization: \\`Bearer \\${token}\\` } : {};\n\t\t}\n\n\t\tconst { auth } = await import(\"@clerk/nextjs/server\");\n\t\tconst clerkAuth = await auth();\n\t\tconst token = await clerkAuth.getToken();\n\n\t\treturn token ? { Authorization: \\`Bearer \\${token}\\` } : {};\n\t\t{{else}}\n\t\tconst token = await getClerkAuthToken();\n\t\treturn token ? { Authorization: \\`Bearer \\${token}\\` } : {};\n\t\t{{/if}}\n\t},\n{{/if}}\n{{#if (eq auth \"better-auth\")}}\n\tfetch(url, options) {\n\t\treturn fetch(url, {\n\t\t\t...options,\n\t\t\tcredentials: \"include\",\n\t\t});\n\t},\n{{#if (includes frontend \"next\")}}\n\theaders: async () => {\n\t\tif (typeof window !== \"undefined\") {\n\t\t\treturn {}\n\t\t}\n\n\t\tconst { headers } = await import(\"next/headers\")\n\t\treturn Object.fromEntries(await headers())\n\t},\n{{/if}}\n{{/if}}\n});\n\nexport const client: AppRouterClient = createORPCClient(link)\n{{/if}}\n\nexport const orpc = createTanstackQueryUtils(client)\n`],\n  [\"api/orpc/web/solid/src/utils/orpc.ts.hbs\", `import { createORPCClient } from \"@orpc/client\";\nimport { RPCLink } from \"@orpc/client/fetch\";\nimport { createTanstackQueryUtils } from \"@orpc/tanstack-query\";\nimport { QueryCache, QueryClient } from \"@tanstack/solid-query\";\nimport type { AppRouterClient } from \"@{{projectName}}/api/routers/index\";\nimport { env } from \"@{{projectName}}/env/web\";\n\nexport const queryClient = new QueryClient({\n\tqueryCache: new QueryCache({\n\t\tonError: (error) => {\n\t\t\tconsole.error(\\`Error: \\${error.message}\\`);\n\t\t},\n\t}),\n});\n\nexport const link = new RPCLink({\n\turl: \\`\\${env.VITE_SERVER_URL}/rpc\\`,\n{{#if (eq auth \"better-auth\")}}\n\tfetch(url, options) {\n\t\treturn fetch(url, {\n\t\t\t...options,\n\t\t\tcredentials: \"include\",\n\t\t});\n\t},\n{{/if}}\n});\n\nexport const client: AppRouterClient = createORPCClient(link);\n\nexport const orpc = createTanstackQueryUtils(client);\n`],\n  [\"api/orpc/web/svelte/src/lib/orpc.ts.hbs\", `{{#unless (eq backend \"self\")}}\nimport { PUBLIC_SERVER_URL } from \"$env/static/public\";\n{{/unless}}\nimport { createORPCClient } from \"@orpc/client\";\nimport { RPCLink } from \"@orpc/client/fetch\";\nimport { createTanstackQueryUtils } from \"@orpc/tanstack-query\";\nimport { QueryCache, QueryClient } from \"@tanstack/svelte-query\";\nimport type { AppRouterClient } from \"@{{projectName}}/api/routers/index\";\n\nexport const queryClient = new QueryClient({\n\tqueryCache: new QueryCache({\n\t\tonError: (error) => {\n\t\t\tconsole.error(\\`Error: \\${error.message}\\`);\n\t\t},\n\t}),\n});\n\nexport const link = new RPCLink({\n\t{{#if (eq backend \"self\")}}\n\turl: () => {\n\t\tif (typeof window === \"undefined\") {\n\t\t\tthrow new Error(\"This link is not allowed on the server side.\");\n\t\t}\n\n\t\treturn \\`\\${window.location.origin}/rpc\\`;\n\t},\n\t{{else}}\n\turl: \\`\\${PUBLIC_SERVER_URL}/rpc\\`,\n\t{{/if}}\n\t{{#if (eq auth \"better-auth\")}}\n\tfetch(url, options) {\n\t\treturn fetch(url, {\n\t\t\t...options,\n\t\t\tcredentials: \"include\",\n\t\t});\n\t},\n\t{{/if}}\n});\n\n{{#if (eq backend \"self\")}}\nexport const client: AppRouterClient = globalThis.$client ?? createORPCClient(link);\n{{else}}\nexport const client: AppRouterClient = createORPCClient(link);\n{{/if}}\n\nexport const orpc = createTanstackQueryUtils(client);\n`],\n  [\"api/trpc/fullstack/next/src/app/api/trpc/[trpc]/route.ts.hbs\", `import { fetchRequestHandler } from \"@trpc/server/adapters/fetch\";\nimport { appRouter } from \"@{{projectName}}/api/routers/index\";\nimport { createContext } from \"@{{projectName}}/api/context\";\nimport { NextRequest } from \"next/server\";\n\nfunction handler(req: NextRequest) {\n\treturn fetchRequestHandler({\n\t\tendpoint: \"/api/trpc\",\n\t\treq,\n\t\trouter: appRouter,\n\t\tcreateContext: () => createContext(req),\n\t});\n}\nexport { handler as GET, handler as POST };\n`],\n  [\"api/trpc/fullstack/tanstack-start/src/routes/api/trpc/$.ts.hbs\", `import { fetchRequestHandler } from '@trpc/server/adapters/fetch'\nimport { appRouter } from '@{{projectName}}/api/routers/index'\nimport { createContext } from '@{{projectName}}/api/context'\nimport { createFileRoute } from '@tanstack/react-router'\n\nfunction handler({ request }: { request: Request }) {\n  return fetchRequestHandler({\n    req: request,\n    router: appRouter,\n    createContext,\n    endpoint: '/api/trpc',\n  })\n}\n\nexport const Route = createFileRoute('/api/trpc/$')({\n  server: {\n    handlers: {\n      GET: handler,\n      POST: handler,\n    },\n  },\n})\n`],\n  [\"api/trpc/native/utils/trpc.ts.hbs\", `{{#if (eq auth \"better-auth\")}}\nimport { authClient } from \"@/lib/auth-client\";\nimport { Platform } from \"react-native\";\n{{else if (eq auth \"clerk\")}}\nimport { getClerkAuthToken } from \"@/utils/clerk-auth\";\n{{/if}}\nimport { QueryClient } from \"@tanstack/react-query\";\nimport { createTRPCClient, httpBatchLink } from \"@trpc/client\";\nimport { createTRPCOptionsProxy } from \"@trpc/tanstack-react-query\";\nimport type { AppRouter } from \"@{{projectName}}/api/routers/index\";\nimport { env } from \"@{{projectName}}/env/native\";\n\nexport const queryClient = new QueryClient();\n\nconst trpcClient = createTRPCClient<AppRouter>({\n\tlinks: [\n\t\thttpBatchLink({\n{{#if (eq backend \"self\")}}\n\t\t\turl: \\`\\${env.EXPO_PUBLIC_SERVER_URL}/api/trpc\\`,\n{{else}}\n\t\t\turl: \\`\\${env.EXPO_PUBLIC_SERVER_URL}/trpc\\`,\n{{/if}}\n{{#if (eq auth \"better-auth\")}}\n\t\t\tfetch:\n\t\t\t\tfunction (url, options) {\n\t\t\t\t\treturn fetch(url, {\n\t\t\t\t\t\t...options,\n\t\t\t\t\t\t// Better Auth Expo forwards the session cookie manually on native.\n\t\t\t\t\t\tcredentials: Platform.OS === \"web\" ? \"include\" : \"omit\",\n\t\t\t\t\t});\n\t\t\t\t},\n\t\t\theaders() {\n\t\t\t\tif (Platform.OS === \"web\") {\n\t\t\t\t\treturn {};\n\t\t\t\t}\n\t\t\t\tconst headers = new Map<string, string>();\n\t\t\t\tconst cookies = authClient.getCookie();\n\t\t\t\tif (cookies) {\n\t\t\t\t\theaders.set(\"Cookie\", cookies);\n\t\t\t\t}\n\t\t\t\treturn Object.fromEntries(headers);\n\t\t\t},\n{{else if (eq auth \"clerk\")}}\n\t\t\theaders: async function () {\n\t\t\t\tconst token = await getClerkAuthToken();\n\t\t\t\treturn token ? { Authorization: \\`Bearer \\${token}\\` } : {};\n\t\t\t},\n{{/if}}\n\t\t}),\n\t],\n});\n\nexport const trpc = createTRPCOptionsProxy<AppRouter>({\n\tclient: trpcClient,\n\tqueryClient,\n});\n`],\n  [\"api/trpc/server/_gitignore\", `# dependencies (bun install)\nnode_modules\n\n# output\nout\ndist\n*.tgz\n\n# code coverage\ncoverage\n*.lcov\n\n# logs\nlogs\n_.log\nreport.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json\n\n# dotenv environment variable files\n.env\n.env.development.local\n.env.test.local\n.env.production.local\n.env.local\n\n# caches\n.eslintcache\n.cache\n*.tsbuildinfo\n\n# IntelliJ based IDEs\n.idea\n\n# Finder (MacOS) folder config\n.DS_Store\n`],\n  [\"api/trpc/server/package.json.hbs\", `{\n  \"name\": \"@{{projectName}}/api\",\n  \"exports\": {\n    \".\": {\n      \"default\": \"./src/index.ts\"\n    },\n    \"./*\": {\n      \"default\": \"./src/*.ts\"\n    }\n  },\n  \"type\": \"module\",\n  \"scripts\": {},\n  \"devDependencies\": {}\n}`],\n  [\"api/trpc/server/src/context.ts.hbs\", `{{#if (eq auth \"clerk\")}}\ntype ClerkContextAuth = {\n\tuserId: string | null;\n};\n\ntype ClerkRequestContext = {\n\tauth: ClerkContextAuth | null;\n\tsession: null;\n};\n\nfunction toClerkContextAuth(auth: { userId: string | null } | null): ClerkContextAuth | null {\n\treturn auth ? { userId: auth.userId } : null;\n}\n{{/if}}\n\n{{#if (and (eq auth \"clerk\") (or (eq backend 'self') (eq backend 'hono') (eq backend 'elysia')))}}\nimport { createClerkClient } from \"@clerk/backend\";\nimport { env } from \"@{{projectName}}/env/server\";\n\nconst clerkClient = createClerkClient({\n\tsecretKey: env.CLERK_SECRET_KEY,\n\tpublishableKey: env.CLERK_PUBLISHABLE_KEY,\n});\n\nasync function authenticateClerkRequest(request: Request): Promise<ClerkContextAuth | null> {\n\tconst requestState = await clerkClient.authenticateRequest(request, {\n\t\tauthorizedParties: [env.CORS_ORIGIN],\n\t});\n\treturn toClerkContextAuth(requestState.toAuth());\n}\n{{/if}}\n\n{{#if (and (eq backend 'self') (includes frontend \"next\"))}}\nimport type { NextRequest } from \"next/server\";\n{{#if (eq auth \"better-auth\")}}\n{{#if (or (eq runtime \"workers\") (eq serverDeploy \"cloudflare\") (and (eq backend \"self\") (eq webDeploy \"cloudflare\")))}}\nimport { createAuth } from \"@{{projectName}}/auth\";\n{{else}}\nimport { auth } from \"@{{projectName}}/auth\";\n{{/if}}\n{{/if}}\n\nexport async function createContext(req: NextRequest){{#if (eq auth \"clerk\")}}: Promise<ClerkRequestContext>{{/if}} {\n{{#if (eq auth \"better-auth\")}}\n\tconst session = await {{#if (or (eq runtime \"workers\") (eq serverDeploy \"cloudflare\") (and (eq backend \"self\") (eq webDeploy \"cloudflare\")))}}createAuth(){{else}}auth{{/if}}.api.getSession({\n\t\theaders: req.headers,\n\t});\n\treturn {\n\t\tauth: null,\n\t\tsession,\n\t};\n{{else if (eq auth \"clerk\")}}\n\tconst clerkAuth = await authenticateClerkRequest(req);\n\treturn {\n\t\tauth: clerkAuth,\n\t\tsession: null,\n\t};\n{{else}}\n\treturn {\n\t\tauth: null,\n\t\tsession: null,\n\t};\n{{/if}}\n}\n\n{{else if (and (eq backend 'self') (includes frontend \"tanstack-start\"))}}\n{{#if (eq auth \"better-auth\")}}\n{{#if (or (eq runtime \"workers\") (eq serverDeploy \"cloudflare\") (and (eq backend \"self\") (eq webDeploy \"cloudflare\")))}}\nimport { createAuth } from \"@{{projectName}}/auth\";\n{{else}}\nimport { auth } from \"@{{projectName}}/auth\";\n{{/if}}\n{{/if}}\n\nexport async function createContext({ req }: { req: Request }){{#if (eq auth \"clerk\")}}: Promise<ClerkRequestContext>{{/if}} {\n{{#if (eq auth \"better-auth\")}}\n\tconst session = await {{#if (or (eq runtime \"workers\") (eq serverDeploy \"cloudflare\") (and (eq backend \"self\") (eq webDeploy \"cloudflare\")))}}createAuth(){{else}}auth{{/if}}.api.getSession({\n\t\theaders: req.headers,\n\t});\n\treturn {\n\t\tauth: null,\n\t\tsession,\n\t};\n{{else if (eq auth \"clerk\")}}\n\tconst clerkAuth = await authenticateClerkRequest(req);\n\treturn {\n\t\tauth: clerkAuth,\n\t\tsession: null,\n\t};\n{{else}}\n\treturn {\n\t\tauth: null,\n\t\tsession: null,\n\t};\n{{/if}}\n}\n\n{{else if (eq backend 'hono')}}\nimport type { Context as HonoContext } from \"hono\";\n{{#if (eq auth \"better-auth\")}}\n{{#if (or (eq runtime \"workers\") (eq serverDeploy \"cloudflare\") (and (eq backend \"self\") (eq webDeploy \"cloudflare\")))}}\nimport { createAuth } from \"@{{projectName}}/auth\";\n{{else}}\nimport { auth } from \"@{{projectName}}/auth\";\n{{/if}}\n{{/if}}\n\nexport type CreateContextOptions = {\n\tcontext: HonoContext;\n};\n\nexport async function createContext({ context }: CreateContextOptions){{#if (eq auth \"clerk\")}}: Promise<ClerkRequestContext>{{/if}} {\n{{#if (eq auth \"better-auth\")}}\n\tconst session = await {{#if (or (eq runtime \"workers\") (eq serverDeploy \"cloudflare\") (and (eq backend \"self\") (eq webDeploy \"cloudflare\")))}}createAuth(){{else}}auth{{/if}}.api.getSession({\n\t\theaders: context.req.raw.headers,\n\t});\n\treturn {\n\t\tauth: null,\n\t\tsession,\n\t};\n{{else if (eq auth \"clerk\")}}\n\tconst clerkAuth = await authenticateClerkRequest(context.req.raw);\n\treturn {\n\t\tauth: clerkAuth,\n\t\tsession: null,\n\t};\n{{else}}\n\treturn {\n\t\tauth: null,\n\t\tsession: null,\n\t};\n{{/if}}\n}\n\n{{else if (eq backend 'elysia')}}\nimport type { Context as ElysiaContext } from \"elysia\";\n{{#if (eq auth \"better-auth\")}}\nimport { auth } from \"@{{projectName}}/auth\";\n{{/if}}\n\nexport type CreateContextOptions = {\n\tcontext: ElysiaContext;\n};\n\nexport async function createContext({ context }: CreateContextOptions){{#if (eq auth \"clerk\")}}: Promise<ClerkRequestContext>{{/if}} {\n{{#if (eq auth \"better-auth\")}}\n\tconst session = await auth.api.getSession({\n\t\theaders: context.request.headers,\n\t});\n\treturn {\n\t\tauth: null,\n\t\tsession,\n\t};\n{{else if (eq auth \"clerk\")}}\n\tconst clerkAuth = await authenticateClerkRequest(context.request);\n\treturn {\n\t\tauth: clerkAuth,\n\t\tsession: null,\n\t};\n{{else}}\n\treturn {\n\t\tauth: null,\n\t\tsession: null,\n\t};\n{{/if}}\n}\n\n{{else if (eq backend 'express')}}\nimport type { CreateExpressContextOptions } from \"@trpc/server/adapters/express\";\n{{#if (eq auth \"better-auth\")}}\nimport { fromNodeHeaders } from \"better-auth/node\";\nimport { auth } from \"@{{projectName}}/auth\";\n{{else if (eq auth \"clerk\")}}\nimport { getAuth } from \"@clerk/express\";\n{{/if}}\n\nexport async function createContext(opts: CreateExpressContextOptions){{#if (eq auth \"clerk\")}}: Promise<ClerkRequestContext>{{/if}} {\n{{#if (eq auth \"better-auth\")}}\n\tconst session = await auth.api.getSession({\n\t\theaders: fromNodeHeaders(opts.req.headers),\n\t});\n\treturn {\n\t\tauth: null,\n\t\tsession,\n\t};\n{{else if (eq auth \"clerk\")}}\n\tconst clerkAuth = toClerkContextAuth(getAuth(opts.req));\n\treturn {\n\t\tauth: clerkAuth,\n\t\tsession: null,\n\t};\n{{else}}\n\treturn {\n\t\tauth: null,\n\t\tsession: null,\n\t};\n{{/if}}\n}\n\n{{else if (eq backend 'fastify')}}\nimport type { CreateFastifyContextOptions } from \"@trpc/server/adapters/fastify\";\n{{#if (eq auth \"better-auth\")}}\nimport { fromNodeHeaders } from \"better-auth/node\";\nimport { auth } from \"@{{projectName}}/auth\";\n{{else if (eq auth \"clerk\")}}\nimport { getAuth } from \"@clerk/fastify\";\n{{/if}}\n\nexport async function createContext({ req }: CreateFastifyContextOptions){{#if (eq auth \"clerk\")}}: Promise<ClerkRequestContext>{{/if}} {\n{{#if (eq auth \"better-auth\")}}\n\tconst session = await auth.api.getSession({\n\t\theaders: fromNodeHeaders(req.headers),\n\t});\n\treturn {\n\t\tauth: null,\n\t\tsession,\n\t};\n{{else if (eq auth \"clerk\")}}\n\tconst clerkAuth = toClerkContextAuth(getAuth(req));\n\treturn {\n\t\tauth: clerkAuth,\n\t\tsession: null,\n\t};\n{{else}}\n\treturn {\n\t\tauth: null,\n\t\tsession: null,\n\t};\n{{/if}}\n}\n\n{{else}}\nexport async function createContext() {\n\treturn {\n\t\tauth: null,\n\t\tsession: null,\n\t};\n}\n{{/if}}\n\nexport type Context = Awaited<ReturnType<typeof createContext>>;\n`],\n  [\"api/trpc/server/src/index.ts.hbs\", `import { initTRPC, TRPCError } from \"@trpc/server\";\nimport type { Context } from \"./context\";\n\nexport const t = initTRPC.context<Context>().create();\n\nexport const router = t.router;\n\nexport const publicProcedure = t.procedure;\n\n{{#if (or (eq auth \"better-auth\") (eq auth \"clerk\"))}}\nexport const protectedProcedure = t.procedure.use(({ ctx, next }) => {\n  {{#if (eq auth \"better-auth\")}}\n  if (!ctx.session) {\n    throw new TRPCError({\n      code: \"UNAUTHORIZED\",\n      message: \"Authentication required\",\n      cause: \"No session\",\n    });\n  }\n  {{else}}\n  if (!ctx.auth?.userId) {\n    throw new TRPCError({\n      code: \"UNAUTHORIZED\",\n      message: \"Authentication required\",\n      cause: \"No Clerk userId\",\n    });\n  }\n  {{/if}}\n  return next({\n    ctx: {\n      ...ctx,\n      {{#if (eq auth \"better-auth\")}}\n      session: ctx.session,\n      {{else}}\n      auth: ctx.auth,\n      {{/if}}\n    },\n  });\n});\n{{/if}}\n`],\n  [\"api/trpc/server/src/routers/index.ts.hbs\", `{{#if (eq api \"orpc\")}}\nimport { {{#if (eq auth \"better-auth\")}}protectedProcedure, {{/if}}publicProcedure } from \"../index\";\nimport type { RouterClient } from \"@orpc/server\";\n{{#if (includes examples \"todo\")}}\nimport { todoRouter } from \"./todo\";\n{{/if}}\n\nexport const appRouter = {\n  healthCheck: publicProcedure.handler(() => {\n    return \"OK\";\n  }),\n  {{#if (eq auth \"better-auth\")}}\n  privateData: protectedProcedure.handler(({ context }) => {\n    return {\n      message: \"This is private\",\n      user: context.session?.user,\n    };\n  }),\n  {{/if}}\n  {{#if (includes examples \"todo\")}}\n  todo: todoRouter,\n  {{/if}}\n};\nexport type AppRouter = typeof appRouter;\nexport type AppRouterClient = RouterClient<typeof appRouter>;\n{{else if (eq api \"trpc\")}}\nimport {\n  {{#if (or (eq auth \"better-auth\") (eq auth \"clerk\"))}}protectedProcedure, {{/if}}publicProcedure,\n  router,\n} from \"../index\";\n{{#if (includes examples \"todo\")}}\nimport { todoRouter } from \"./todo\";\n{{/if}}\n\nexport const appRouter = router({\n  healthCheck: publicProcedure.query(() => {\n    return \"OK\";\n  }),\n  {{#if (or (eq auth \"better-auth\") (eq auth \"clerk\"))}}\n  privateData: protectedProcedure.query(({ ctx }) => {\n    return {\n      message: \"This is private\",\n      {{#if (eq auth \"better-auth\")}}\n      user: ctx.session.user,\n      {{else}}\n      userId: ctx.auth.userId,\n      {{/if}}\n    };\n  }),\n  {{/if}}\n  {{#if (includes examples \"todo\")}}\n  todo: todoRouter,\n  {{/if}}\n});\nexport type AppRouter = typeof appRouter;\n{{else}}\nexport const appRouter = {};\nexport type AppRouter = typeof appRouter;\n{{/if}}\n`],\n  [\"api/trpc/server/tsconfig.json.hbs\", `{\n  \"extends\": \"@{{projectName}}/config/tsconfig.base.json\",\n  \"compilerOptions\": {\n    \"declaration\": true,\n    \"declarationMap\": true,\n    \"sourceMap\": true,\n    \"outDir\": \"dist\",\n    \"composite\": true\n  }\n}`],\n  [\"api/trpc/web/react/base/src/utils/trpc.ts.hbs\", `{{#if (includes frontend 'next')}}\nimport { QueryCache, QueryClient } from '@tanstack/react-query';\nimport { createTRPCClient, httpBatchLink } from '@trpc/client';\nimport { createTRPCOptionsProxy } from '@trpc/tanstack-react-query';\nimport type { AppRouter } from \"@{{projectName}}/api/routers/index\";\nimport { toast } from 'sonner';\n{{#unless (eq backend \"self\")}}\nimport { env } from \"@{{projectName}}/env/web\";\n{{/unless}}\n{{#if (eq auth \"clerk\")}}\nimport { getClerkAuthToken } from \"@/utils/clerk-auth\";\n{{/if}}\n\nexport const queryClient = new QueryClient({\n\tqueryCache: new QueryCache({\n\t\tonError: (error, query) => {\n\t\t\ttoast.error(error.message, {\n\t\t\t\taction: {\n\t\t\t\t\tlabel: \"retry\",\n\t\t\t\t\tonClick: query.invalidate,\n\t\t\t\t},\n\t\t\t});\n\t\t},\n\t}),\n});\n\nconst trpcClient = createTRPCClient<AppRouter>({\n\tlinks: [\n\t\thttpBatchLink({\n{{#if (eq backend \"self\")}}\n\t\t\turl: \"/api/trpc\",\n{{else}}\n\t\t\turl: \\`\\${env.NEXT_PUBLIC_SERVER_URL}/trpc\\`,\n{{/if}}\n{{#if (eq auth \"clerk\")}}\n\t\t\theaders: async () => {\n\t\t\t\tif (typeof window !== \"undefined\") {\n\t\t\t\t\tconst token = await getClerkAuthToken();\n\t\t\t\t\treturn token ? { Authorization: \\`Bearer \\${token}\\` } : {};\n\t\t\t\t}\n\n\t\t\t\tconst { auth } = await import(\"@clerk/nextjs/server\");\n\t\t\t\tconst clerkAuth = await auth();\n\t\t\t\tconst token = await clerkAuth.getToken();\n\n\t\t\t\treturn token ? { Authorization: \\`Bearer \\${token}\\` } : {};\n\t\t\t},\n{{/if}}\n{{#if (eq auth \"better-auth\")}}\n\t\t\tfetch(url, options) {\n\t\t\t\treturn fetch(url, {\n\t\t\t\t\t...options,\n\t\t\t\t\tcredentials: \"include\",\n\t\t\t\t});\n\t\t\t},\n{{/if}}\n\t\t}),\n\t],\n})\n\nexport const trpc = createTRPCOptionsProxy<AppRouter>({\n\tclient: trpcClient,\n\tqueryClient,\n});\n\n{{else if (includes frontend 'tanstack-start')}}\nimport { createTRPCContext } from \"@trpc/tanstack-react-query\";\nimport type { AppRouter } from \"@{{projectName}}/api/routers/index\";\n\nexport const { TRPCProvider, useTRPC, useTRPCClient } =\n\tcreateTRPCContext<AppRouter>();\n\n{{else}}\nimport type { AppRouter } from \"@{{projectName}}/api/routers/index\";\nimport { QueryCache, QueryClient } from \"@tanstack/react-query\";\nimport { createTRPCClient, httpBatchLink } from \"@trpc/client\";\nimport { createTRPCOptionsProxy } from \"@trpc/tanstack-react-query\";\nimport { toast } from \"sonner\";\nimport { env } from \"@{{projectName}}/env/web\";\n{{#if (eq auth \"clerk\")}}\nimport { getClerkAuthToken } from \"@/utils/clerk-auth\";\n{{/if}}\n\nexport const queryClient = new QueryClient({\n\tqueryCache: new QueryCache({\n\t\tonError: (error, query) => {\n\t\t\ttoast.error(error.message, {\n\t\t\t\taction: {\n\t\t\t\t\tlabel: \"retry\",\n\t\t\t\t\tonClick: query.invalidate,\n\t\t\t\t},\n\t\t\t});\n\t\t},\n\t}),\n});\n\nexport const trpcClient = createTRPCClient<AppRouter>({\n\tlinks: [\n\t\thttpBatchLink({\n\t\t\turl: \\`\\${env.VITE_SERVER_URL}/trpc\\`,\n{{#if (eq auth \"clerk\")}}\n\t\t\theaders: async () => {\n\t\t\t\tconst token = await getClerkAuthToken();\n\t\t\t\treturn token ? { Authorization: \\`Bearer \\${token}\\` } : {};\n\t\t\t},\n{{/if}}\n{{#if (eq auth \"better-auth\")}}\n\t\t\tfetch(url, options) {\n\t\t\t\treturn fetch(url, {\n\t\t\t\t\t...options,\n\t\t\t\t\tcredentials: \"include\",\n\t\t\t\t});\n\t\t\t},\n{{/if}}\n\t\t}),\n\t],\n});\n\nexport const trpc = createTRPCOptionsProxy<AppRouter>({\n\tclient: trpcClient,\n\tqueryClient,\n});\n{{/if}}\n`],\n  [\"auth/better-auth/convex/backend/convex/auth.config.ts.hbs\", `import { getAuthConfigProvider } from \"@convex-dev/better-auth/auth-config\";\nimport type { AuthConfig } from \"convex/server\";\n\nexport default {\n  providers: [getAuthConfigProvider()],\n} satisfies AuthConfig;\n`],\n  [\"auth/better-auth/convex/backend/convex/auth.ts.hbs\", `import { createClient, type GenericCtx } from \"@convex-dev/better-auth\";\n{{#if (or (includes frontend \"native-bare\") (includes frontend \"native-uniwind\") (includes frontend \"native-unistyles\") (includes frontend \"tanstack-router\") (includes frontend \"react-router\") (includes frontend \"nuxt\") (includes frontend \"svelte\") (includes frontend \"solid\"))}}\nimport { convex, crossDomain } from \"@convex-dev/better-auth/plugins\";\n{{else}}\nimport { convex } from \"@convex-dev/better-auth/plugins\";\n{{/if}}\n{{#if (or (includes frontend \"native-bare\") (includes frontend \"native-uniwind\") (includes frontend \"native-unistyles\"))}}\nimport { expo } from \"@better-auth/expo\";\n{{/if}}\nimport { components } from \"./_generated/api\";\nimport type { DataModel } from \"./_generated/dataModel\";\nimport { query } from \"./_generated/server\";\nimport { betterAuth } from \"better-auth/minimal\";\nimport authConfig from \"./auth.config\";\n\n{{#if (or (includes frontend \"tanstack-start\") (includes frontend \"next\") (includes frontend \"tanstack-router\") (includes frontend \"react-router\") (includes frontend \"nuxt\") (includes frontend \"svelte\") (includes frontend \"solid\") (includes frontend \"native-bare\") (includes frontend \"native-uniwind\") (includes frontend \"native-unistyles\"))}}\nconst siteUrl = process.env.SITE_URL{{#if (or (includes frontend \"tanstack-start\") (includes frontend \"next\") (includes frontend \"tanstack-router\") (includes frontend \"react-router\") (includes frontend \"nuxt\") (includes frontend \"svelte\") (includes frontend \"solid\"))}}!{{else}} || \"http://localhost:8081\"{{/if}};\n{{/if}}\n{{#if (or (includes frontend \"native-bare\") (includes frontend \"native-uniwind\") (includes frontend \"native-unistyles\"))}}\nconst nativeAppUrl = process.env.NATIVE_APP_URL || \"{{projectName}}://\";\n{{/if}}\n\nexport const authComponent = createClient<DataModel>(components.betterAuth);\n\nfunction createAuth(ctx: GenericCtx<DataModel>) {\n  return betterAuth({\n    {{#if (or (includes frontend \"tanstack-start\") (includes frontend \"next\"))}}\n    baseURL: siteUrl,\n    {{/if}}\n    {{#if (or (includes frontend \"native-bare\") (includes frontend \"native-uniwind\") (includes frontend \"native-unistyles\"))}}\n    trustedOrigins: [siteUrl, nativeAppUrl, ...(process.env.NODE_ENV === \"development\" ? [\"exp://\", \"exp://**\", \"exp://192.168.*.*:*/**\"] : [])],\n    {{else if (or (includes frontend \"tanstack-router\") (includes frontend \"react-router\") (includes frontend \"nuxt\") (includes frontend \"svelte\") (includes frontend \"solid\"))}}\n    trustedOrigins: [siteUrl],\n    {{else if (or (includes frontend \"tanstack-start\") (includes frontend \"next\"))}}\n    trustedOrigins: [siteUrl],\n    {{/if}}\n    database: authComponent.adapter(ctx),\n    emailAndPassword: {\n      enabled: true,\n      requireEmailVerification: false,\n    },\n    plugins: [\n      {{#if (or (includes frontend \"native-bare\") (includes frontend \"native-uniwind\") (includes frontend \"native-unistyles\"))}}\n      expo(),\n      {{/if}}\n      {{#if (or (includes frontend \"native-bare\") (includes frontend \"native-uniwind\") (includes frontend \"native-unistyles\") (includes frontend \"tanstack-router\") (includes frontend \"react-router\") (includes frontend \"nuxt\") (includes frontend \"svelte\") (includes frontend \"solid\"))}}\n      crossDomain({ siteUrl }),\n      {{/if}}\n      convex({\n        authConfig,\n        jwksRotateOnTokenGenerationError: true,\n      }),\n    ],\n  });\n}\n\nexport { createAuth };\n\nexport const getCurrentUser = query({\n  args: {},\n  handler: async (ctx) => {\n    return await authComponent.safeGetAuthUser(ctx);\n  },\n});\n`],\n  [\"auth/better-auth/convex/backend/convex/http.ts.hbs\", `import { httpRouter } from \"convex/server\";\nimport { authComponent, createAuth } from \"./auth\";\n\nconst http = httpRouter();\n\n{{#if (or (includes frontend \"native-bare\") (includes frontend \"native-uniwind\") (includes frontend \"native-unistyles\") (includes frontend \"tanstack-router\") (includes frontend \"react-router\") (includes frontend \"nuxt\") (includes frontend \"svelte\") (includes frontend \"solid\"))}}\n{{#if (or (includes frontend \"tanstack-router\") (includes frontend \"react-router\") (includes frontend \"nuxt\") (includes frontend \"svelte\") (includes frontend \"solid\"))}}\nauthComponent.registerRoutesLazy(http, createAuth, {\n  cors: true,\n  trustedOrigins: [process.env.SITE_URL!],\n});\n{{else}}\nauthComponent.registerRoutes(http, createAuth, { cors: true });\n{{/if}}\n{{else}}\nauthComponent.registerRoutes(http, createAuth);\n{{/if}}\n\nexport default http;\n`],\n  [\"auth/better-auth/convex/backend/convex/privateData.ts.hbs\", `import { query } from \"./_generated/server\";\nimport { authComponent } from \"./auth\";\n\nexport const get = query({\n  args: {},\n  handler: async (ctx) => {\n    const authUser = await authComponent.safeGetAuthUser(ctx);\n    if (!authUser) {\n      return {\n        message: \"Not authenticated\",\n      };\n    }\n    return {\n      message: \"This is private\",\n    };\n  },\n});\n`],\n  [\"auth/better-auth/convex/native/bare/components/sign-in.tsx.hbs\", `import { authClient } from \"@/lib/auth-client\";\nimport { useForm } from \"@tanstack/react-form\";\nimport { useState } from \"react\";\nimport {\n  ActivityIndicator,\n  StyleSheet,\n  Text,\n  TextInput,\n  TouchableOpacity,\n  View,\n} from \"react-native\";\nimport { NAV_THEME } from \"@/lib/constants\";\nimport { useColorScheme } from \"@/lib/use-color-scheme\";\nimport z from \"zod\";\n\nconst signInSchema = z.object({\n  email: z\n    .string()\n    .trim()\n    .min(1, \"Email is required\")\n    .email(\"Enter a valid email address\"),\n  password: z\n    .string()\n    .min(1, \"Password is required\")\n    .min(8, \"Use at least 8 characters\"),\n});\n\nfunction getErrorMessage(error: unknown): string | null {\n  if (!error) return null;\n\n  if (typeof error === \"string\") {\n    return error;\n  }\n\n  if (Array.isArray(error)) {\n    for (const issue of error) {\n      const message = getErrorMessage(issue);\n      if (message) {\n        return message;\n      }\n    }\n    return null;\n  }\n\n  if (typeof error === \"object\" && error !== null) {\n    const maybeError = error as { message?: unknown };\n    if (typeof maybeError.message === \"string\") {\n      return maybeError.message;\n    }\n  }\n\n  return null;\n}\n\nfunction SignIn() {\n  const { colorScheme } = useColorScheme();\n  const theme = colorScheme === \"dark\" ? NAV_THEME.dark : NAV_THEME.light;\n  const [error, setError] = useState<string | null>(null);\n\n  const form = useForm({\n    defaultValues: {\n      email: \"\",\n      password: \"\",\n    },\n    validators: {\n      onSubmit: signInSchema,\n    },\n    onSubmit: async ({ value, formApi }) => {\n      await authClient.signIn.email(\n        {\n          email: value.email.trim(),\n          password: value.password,\n        },\n        {\n          onError(error) {\n            setError(error.error?.message || \"Failed to sign in\");\n          },\n          onSuccess() {\n            setError(null);\n            formApi.reset();\n          },\n        },\n      );\n    },\n  });\n\n  return (\n    <View style={[styles.card, { backgroundColor: theme.card, borderColor: theme.border }]}>\n      <Text style={[styles.title, { color: theme.text }]}>Sign In</Text>\n\n      <form.Subscribe\n        selector={(state) => ({\n          isSubmitting: state.isSubmitting,\n          validationError: getErrorMessage(state.errorMap.onSubmit),\n        })}\n      >\n        {({ isSubmitting, validationError }) => {\n          const formError = error ?? validationError;\n\n          return (\n            <>\n              {formError ? (\n                <View style={[styles.errorContainer, { backgroundColor: theme.notification + \"20\" }]}>\n                  <Text style={[styles.errorText, { color: theme.notification }]}>{formError}</Text>\n                </View>\n              ) : null}\n\n              <form.Field name=\"email\">\n                {(field) => (\n                  <TextInput\n                    style={[\n                      styles.input,\n                      {\n                        color: theme.text,\n                        borderColor: theme.border,\n                        backgroundColor: theme.background,\n                      },\n                    ]}\n                    placeholder=\"Email\"\n                    placeholderTextColor={theme.text}\n                    value={field.state.value}\n                    onBlur={field.handleBlur}\n                    onChangeText={(value) => {\n                      field.handleChange(value);\n                      if (error) {\n                        setError(null);\n                      }\n                    }}\n                    keyboardType=\"email-address\"\n                    autoCapitalize=\"none\"\n                  />\n                )}\n              </form.Field>\n\n              <form.Field name=\"password\">\n                {(field) => (\n                  <TextInput\n                    style={[\n                      styles.input,\n                      {\n                        color: theme.text,\n                        borderColor: theme.border,\n                        backgroundColor: theme.background,\n                      },\n                    ]}\n                    placeholder=\"Password\"\n                    placeholderTextColor={theme.text}\n                    value={field.state.value}\n                    onBlur={field.handleBlur}\n                    onChangeText={(value) => {\n                      field.handleChange(value);\n                      if (error) {\n                        setError(null);\n                      }\n                    }}\n                    secureTextEntry\n                    onSubmitEditing={form.handleSubmit}\n                  />\n                )}\n              </form.Field>\n\n              <TouchableOpacity\n                onPress={form.handleSubmit}\n                disabled={isSubmitting}\n                style={[\n                  styles.button,\n                  {\n                    backgroundColor: theme.primary,\n                    opacity: isSubmitting ? 0.5 : 1,\n                  },\n                ]}\n              >\n                {isSubmitting ? (\n                  <ActivityIndicator size=\"small\" color=\"#ffffff\" />\n                ) : (\n                  <Text style={styles.buttonText}>Sign In</Text>\n                )}\n              </TouchableOpacity>\n            </>\n          );\n        }}\n      </form.Subscribe>\n    </View>\n  );\n}\n\nconst styles = StyleSheet.create({\n  card: {\n    marginTop: 16,\n    padding: 16,\n    borderWidth: 1,\n  },\n  title: {\n    fontSize: 18,\n    fontWeight: \"bold\",\n    marginBottom: 12,\n  },\n  errorContainer: {\n    marginBottom: 12,\n    padding: 8,\n  },\n  errorText: {\n    fontSize: 14,\n  },\n  input: {\n    borderWidth: 1,\n    padding: 12,\n    fontSize: 16,\n    marginBottom: 12,\n  },\n  button: {\n    padding: 12,\n    alignItems: \"center\",\n    justifyContent: \"center\",\n  },\n  buttonText: {\n    color: \"#ffffff\",\n    fontSize: 16,\n  },\n});\n\nexport { SignIn };\n`],\n  [\"auth/better-auth/convex/native/bare/components/sign-up.tsx.hbs\", `import { authClient } from \"@/lib/auth-client\";\nimport { useForm } from \"@tanstack/react-form\";\nimport { useState } from \"react\";\nimport {\n  ActivityIndicator,\n  StyleSheet,\n  Text,\n  TextInput,\n  TouchableOpacity,\n  View,\n} from \"react-native\";\nimport { NAV_THEME } from \"@/lib/constants\";\nimport { useColorScheme } from \"@/lib/use-color-scheme\";\nimport z from \"zod\";\n\nconst signUpSchema = z.object({\n  name: z\n    .string()\n    .trim()\n    .min(1, \"Name is required\")\n    .min(2, \"Name must be at least 2 characters\"),\n  email: z\n    .string()\n    .trim()\n    .min(1, \"Email is required\")\n    .email(\"Enter a valid email address\"),\n  password: z\n    .string()\n    .min(1, \"Password is required\")\n    .min(8, \"Use at least 8 characters\"),\n});\n\nfunction getErrorMessage(error: unknown): string | null {\n  if (!error) return null;\n\n  if (typeof error === \"string\") {\n    return error;\n  }\n\n  if (Array.isArray(error)) {\n    for (const issue of error) {\n      const message = getErrorMessage(issue);\n      if (message) {\n        return message;\n      }\n    }\n    return null;\n  }\n\n  if (typeof error === \"object\" && error !== null) {\n    const maybeError = error as { message?: unknown };\n    if (typeof maybeError.message === \"string\") {\n      return maybeError.message;\n    }\n  }\n\n  return null;\n}\n\nfunction SignUp() {\n  const { colorScheme } = useColorScheme();\n  const theme = colorScheme === \"dark\" ? NAV_THEME.dark : NAV_THEME.light;\n  const [error, setError] = useState<string | null>(null);\n\n  const form = useForm({\n    defaultValues: {\n      name: \"\",\n      email: \"\",\n      password: \"\",\n    },\n    validators: {\n      onSubmit: signUpSchema,\n    },\n    onSubmit: async ({ value, formApi }) => {\n      await authClient.signUp.email(\n        {\n          name: value.name.trim(),\n          email: value.email.trim(),\n          password: value.password,\n        },\n        {\n          onError(error) {\n            setError(error.error?.message || \"Failed to sign up\");\n          },\n          onSuccess() {\n            setError(null);\n            formApi.reset();\n          },\n        },\n      );\n    },\n  });\n\n  return (\n    <View style={[styles.card, { backgroundColor: theme.card, borderColor: theme.border }]}>\n      <Text style={[styles.title, { color: theme.text }]}>Create Account</Text>\n\n      <form.Subscribe\n        selector={(state) => ({\n          isSubmitting: state.isSubmitting,\n          validationError: getErrorMessage(state.errorMap.onSubmit),\n        })}\n      >\n        {({ isSubmitting, validationError }) => {\n          const formError = error ?? validationError;\n\n          return (\n            <>\n              {formError ? (\n                <View style={[styles.errorContainer, { backgroundColor: theme.notification + \"20\" }]}>\n                  <Text style={[styles.errorText, { color: theme.notification }]}>{formError}</Text>\n                </View>\n              ) : null}\n\n              <form.Field name=\"name\">\n                {(field) => (\n                  <TextInput\n                    style={[\n                      styles.input,\n                      {\n                        color: theme.text,\n                        borderColor: theme.border,\n                        backgroundColor: theme.background,\n                      },\n                    ]}\n                    placeholder=\"Name\"\n                    placeholderTextColor={theme.text}\n                    value={field.state.value}\n                    onBlur={field.handleBlur}\n                    onChangeText={(value) => {\n                      field.handleChange(value);\n                      if (error) {\n                        setError(null);\n                      }\n                    }}\n                  />\n                )}\n              </form.Field>\n\n              <form.Field name=\"email\">\n                {(field) => (\n                  <TextInput\n                    style={[\n                      styles.input,\n                      {\n                        color: theme.text,\n                        borderColor: theme.border,\n                        backgroundColor: theme.background,\n                      },\n                    ]}\n                    placeholder=\"Email\"\n                    placeholderTextColor={theme.text}\n                    value={field.state.value}\n                    onBlur={field.handleBlur}\n                    onChangeText={(value) => {\n                      field.handleChange(value);\n                      if (error) {\n                        setError(null);\n                      }\n                    }}\n                    keyboardType=\"email-address\"\n                    autoCapitalize=\"none\"\n                  />\n                )}\n              </form.Field>\n\n              <form.Field name=\"password\">\n                {(field) => (\n                  <TextInput\n                    style={[\n                      styles.input,\n                      {\n                        color: theme.text,\n                        borderColor: theme.border,\n                        backgroundColor: theme.background,\n                      },\n                    ]}\n                    placeholder=\"Password\"\n                    placeholderTextColor={theme.text}\n                    value={field.state.value}\n                    onBlur={field.handleBlur}\n                    onChangeText={(value) => {\n                      field.handleChange(value);\n                      if (error) {\n                        setError(null);\n                      }\n                    }}\n                    secureTextEntry\n                    onSubmitEditing={form.handleSubmit}\n                  />\n                )}\n              </form.Field>\n\n              <TouchableOpacity\n                onPress={form.handleSubmit}\n                disabled={isSubmitting}\n                style={[\n                  styles.button,\n                  {\n                    backgroundColor: theme.primary,\n                    opacity: isSubmitting ? 0.5 : 1,\n                  },\n                ]}\n              >\n                {isSubmitting ? (\n                  <ActivityIndicator size=\"small\" color=\"#ffffff\" />\n                ) : (\n                  <Text style={styles.buttonText}>Sign Up</Text>\n                )}\n              </TouchableOpacity>\n            </>\n          );\n        }}\n      </form.Subscribe>\n    </View>\n  );\n}\n\nconst styles = StyleSheet.create({\n  card: {\n    marginTop: 16,\n    padding: 16,\n    borderWidth: 1,\n  },\n  title: {\n    fontSize: 18,\n    fontWeight: \"bold\",\n    marginBottom: 12,\n  },\n  errorContainer: {\n    marginBottom: 12,\n    padding: 8,\n  },\n  errorText: {\n    fontSize: 14,\n  },\n  input: {\n    borderWidth: 1,\n    padding: 12,\n    fontSize: 16,\n    marginBottom: 12,\n  },\n  button: {\n    padding: 12,\n    alignItems: \"center\",\n    justifyContent: \"center\",\n  },\n  buttonText: {\n    color: \"#ffffff\",\n    fontSize: 16,\n  },\n});\n\nexport { SignUp };\n`],\n  [\"auth/better-auth/convex/native/base/lib/auth-client.ts.hbs\", `import { createAuthClient } from \"better-auth/react\";\nimport { convexClient, crossDomainClient } from \"@convex-dev/better-auth/client/plugins\";\nimport { expoClient } from \"@better-auth/expo/client\";\nimport Constants from \"expo-constants\";\nimport * as SecureStore from \"expo-secure-store\";\nimport { Platform } from \"react-native\";\nimport { env } from \"@{{projectName}}/env/native\";\n\nexport const authClient = createAuthClient({\n\tbaseURL: env.EXPO_PUBLIC_CONVEX_SITE_URL,\n\tplugins: [\n\t\tconvexClient(),\n\t\tPlatform.OS === \"web\"\n\t\t\t? crossDomainClient()\n\t\t\t: expoClient({\n\t\t\t\tscheme: Constants.expoConfig?.scheme as string,\n\t\t\t\tstoragePrefix: Constants.expoConfig?.scheme as string,\n\t\t\t\tstorage: SecureStore,\n\t\t\t}),\n\t],\n});\n`],\n  [\"auth/better-auth/convex/native/unistyles/components/sign-in.tsx.hbs\", `import { authClient } from \"@/lib/auth-client\";\nimport { useForm } from \"@tanstack/react-form\";\nimport { useState } from \"react\";\nimport {\n  ActivityIndicator,\n  Text,\n  TextInput,\n  TouchableOpacity,\n  View,\n} from \"react-native\";\nimport { StyleSheet } from \"react-native-unistyles\";\nimport z from \"zod\";\n\nconst signInSchema = z.object({\n  email: z\n    .string()\n    .trim()\n    .min(1, \"Email is required\")\n    .email(\"Enter a valid email address\"),\n  password: z\n    .string()\n    .min(1, \"Password is required\")\n    .min(8, \"Use at least 8 characters\"),\n});\n\nfunction getErrorMessage(error: unknown): string | null {\n  if (!error) return null;\n\n  if (typeof error === \"string\") {\n    return error;\n  }\n\n  if (Array.isArray(error)) {\n    for (const issue of error) {\n      const message = getErrorMessage(issue);\n      if (message) {\n        return message;\n      }\n    }\n    return null;\n  }\n\n  if (typeof error === \"object\" && error !== null) {\n    const maybeError = error as { message?: unknown };\n    if (typeof maybeError.message === \"string\") {\n      return maybeError.message;\n    }\n  }\n\n  return null;\n}\n\nexport function SignIn() {\n  const [error, setError] = useState<string | null>(null);\n\n  const form = useForm({\n    defaultValues: {\n      email: \"\",\n      password: \"\",\n    },\n    validators: {\n      onSubmit: signInSchema,\n    },\n    onSubmit: async ({ value, formApi }) => {\n      await authClient.signIn.email(\n        {\n          email: value.email.trim(),\n          password: value.password,\n        },\n        {\n          onError(error) {\n            setError(error.error?.message || \"Failed to sign in\");\n          },\n          onSuccess() {\n            setError(null);\n            formApi.reset();\n          },\n        },\n      );\n    },\n  });\n\n  return (\n    <View style={styles.container}>\n      <Text style={styles.title}>Sign In</Text>\n\n      <form.Subscribe\n        selector={(state) => ({\n          isSubmitting: state.isSubmitting,\n          validationError: getErrorMessage(state.errorMap.onSubmit),\n        })}\n      >\n        {({ isSubmitting, validationError }) => {\n          const formError = error ?? validationError;\n\n          return (\n            <>\n              {formError ? (\n                <View style={styles.errorContainer}>\n                  <Text style={styles.errorText}>{formError}</Text>\n                </View>\n              ) : null}\n\n              <form.Field name=\"email\">\n                {(field) => (\n                  <TextInput\n                    style={styles.input}\n                    placeholder=\"Email\"\n                    value={field.state.value}\n                    onBlur={field.handleBlur}\n                    onChangeText={(value) => {\n                      field.handleChange(value);\n                      if (error) {\n                        setError(null);\n                      }\n                    }}\n                    keyboardType=\"email-address\"\n                    autoCapitalize=\"none\"\n                  />\n                )}\n              </form.Field>\n\n              <form.Field name=\"password\">\n                {(field) => (\n                  <TextInput\n                    style={styles.input}\n                    placeholder=\"Password\"\n                    value={field.state.value}\n                    onBlur={field.handleBlur}\n                    onChangeText={(value) => {\n                      field.handleChange(value);\n                      if (error) {\n                        setError(null);\n                      }\n                    }}\n                    secureTextEntry\n                    onSubmitEditing={form.handleSubmit}\n                  />\n                )}\n              </form.Field>\n\n              <TouchableOpacity\n                onPress={form.handleSubmit}\n                disabled={isSubmitting}\n                style={styles.button}\n              >\n                {isSubmitting ? (\n                  <ActivityIndicator size=\"small\" color=\"#fff\" />\n                ) : (\n                  <Text style={styles.buttonText}>Sign In</Text>\n                )}\n              </TouchableOpacity>\n            </>\n          );\n        }}\n      </form.Subscribe>\n    </View>\n  );\n}\n\nconst styles = StyleSheet.create((theme) => ({\n  container: {\n    marginTop: 24,\n    padding: 16,\n    borderRadius: 8,\n    borderWidth: 1,\n    borderColor: theme.colors.border,\n  },\n  title: {\n    fontSize: 18,\n    fontWeight: \"600\",\n    color: theme.colors.typography,\n    marginBottom: 16,\n  },\n  errorContainer: {\n    marginBottom: 16,\n    padding: 12,\n    borderRadius: 6,\n  },\n  errorText: {\n    color: theme.colors.destructive,\n    fontSize: 14,\n  },\n  input: {\n    marginBottom: 12,\n    padding: 16,\n    borderRadius: 6,\n    color: theme.colors.typography,\n    borderWidth: 1,\n    borderColor: theme.colors.border,\n  },\n  button: {\n    backgroundColor: theme.colors.primary,\n    padding: 16,\n    borderRadius: 6,\n    flexDirection: \"row\",\n    justifyContent: \"center\",\n    alignItems: \"center\",\n  },\n  buttonText: {\n    fontWeight: \"500\",\n  },\n}));\n`],\n  [\"auth/better-auth/convex/native/unistyles/components/sign-up.tsx.hbs\", `import { authClient } from \"@/lib/auth-client\";\nimport { useForm } from \"@tanstack/react-form\";\nimport { useState } from \"react\";\nimport {\n  ActivityIndicator,\n  Text,\n  TextInput,\n  TouchableOpacity,\n  View,\n} from \"react-native\";\nimport { StyleSheet } from \"react-native-unistyles\";\nimport z from \"zod\";\n\nconst signUpSchema = z.object({\n  name: z\n    .string()\n    .trim()\n    .min(1, \"Name is required\")\n    .min(2, \"Name must be at least 2 characters\"),\n  email: z\n    .string()\n    .trim()\n    .min(1, \"Email is required\")\n    .email(\"Enter a valid email address\"),\n  password: z\n    .string()\n    .min(1, \"Password is required\")\n    .min(8, \"Use at least 8 characters\"),\n});\n\nfunction getErrorMessage(error: unknown): string | null {\n  if (!error) return null;\n\n  if (typeof error === \"string\") {\n    return error;\n  }\n\n  if (Array.isArray(error)) {\n    for (const issue of error) {\n      const message = getErrorMessage(issue);\n      if (message) {\n        return message;\n      }\n    }\n    return null;\n  }\n\n  if (typeof error === \"object\" && error !== null) {\n    const maybeError = error as { message?: unknown };\n    if (typeof maybeError.message === \"string\") {\n      return maybeError.message;\n    }\n  }\n\n  return null;\n}\n\nexport function SignUp() {\n  const [error, setError] = useState<string | null>(null);\n\n  const form = useForm({\n    defaultValues: {\n      name: \"\",\n      email: \"\",\n      password: \"\",\n    },\n    validators: {\n      onSubmit: signUpSchema,\n    },\n    onSubmit: async ({ value, formApi }) => {\n      await authClient.signUp.email(\n        {\n          name: value.name.trim(),\n          email: value.email.trim(),\n          password: value.password,\n        },\n        {\n          onError(error) {\n            setError(error.error?.message || \"Failed to sign up\");\n          },\n          onSuccess() {\n            setError(null);\n            formApi.reset();\n          },\n        },\n      );\n    },\n  });\n\n  return (\n    <View style={styles.container}>\n      <Text style={styles.title}>Create Account</Text>\n\n      <form.Subscribe\n        selector={(state) => ({\n          isSubmitting: state.isSubmitting,\n          validationError: getErrorMessage(state.errorMap.onSubmit),\n        })}\n      >\n        {({ isSubmitting, validationError }) => {\n          const formError = error ?? validationError;\n\n          return (\n            <>\n              {formError ? (\n                <View style={styles.errorContainer}>\n                  <Text style={styles.errorText}>{formError}</Text>\n                </View>\n              ) : null}\n\n              <form.Field name=\"name\">\n                {(field) => (\n                  <TextInput\n                    style={styles.input}\n                    placeholder=\"Name\"\n                    value={field.state.value}\n                    onBlur={field.handleBlur}\n                    onChangeText={(value) => {\n                      field.handleChange(value);\n                      if (error) {\n                        setError(null);\n                      }\n                    }}\n                  />\n                )}\n              </form.Field>\n\n              <form.Field name=\"email\">\n                {(field) => (\n                  <TextInput\n                    style={styles.input}\n                    placeholder=\"Email\"\n                    value={field.state.value}\n                    onBlur={field.handleBlur}\n                    onChangeText={(value) => {\n                      field.handleChange(value);\n                      if (error) {\n                        setError(null);\n                      }\n                    }}\n                    keyboardType=\"email-address\"\n                    autoCapitalize=\"none\"\n                  />\n                )}\n              </form.Field>\n\n              <form.Field name=\"password\">\n                {(field) => (\n                  <TextInput\n                    style={styles.inputLast}\n                    placeholder=\"Password\"\n                    value={field.state.value}\n                    onBlur={field.handleBlur}\n                    onChangeText={(value) => {\n                      field.handleChange(value);\n                      if (error) {\n                        setError(null);\n                      }\n                    }}\n                    secureTextEntry\n                    onSubmitEditing={form.handleSubmit}\n                  />\n                )}\n              </form.Field>\n\n              <TouchableOpacity\n                onPress={form.handleSubmit}\n                disabled={isSubmitting}\n                style={styles.button}\n              >\n                {isSubmitting ? (\n                  <ActivityIndicator size=\"small\" color=\"#fff\" />\n                ) : (\n                  <Text style={styles.buttonText}>Sign Up</Text>\n                )}\n              </TouchableOpacity>\n            </>\n          );\n        }}\n      </form.Subscribe>\n    </View>\n  );\n}\n\nconst styles = StyleSheet.create((theme) => ({\n  container: {\n    marginTop: 24,\n    padding: 16,\n    borderRadius: 8,\n    borderWidth: 1,\n    borderColor: theme.colors.border,\n  },\n  title: {\n    fontSize: 18,\n    fontWeight: \"600\",\n    color: theme.colors.typography,\n    marginBottom: 16,\n  },\n  errorContainer: {\n    marginBottom: 16,\n    padding: 12,\n    borderRadius: 6,\n  },\n  errorText: {\n    color: theme.colors.destructive,\n    fontSize: 14,\n  },\n  input: {\n    marginBottom: 12,\n    padding: 16,\n    borderRadius: 6,\n    color: theme.colors.typography,\n    borderWidth: 1,\n    borderColor: theme.colors.border,\n  },\n  inputLast: {\n    marginBottom: 16,\n    padding: 16,\n    borderRadius: 6,\n    color: theme.colors.typography,\n    borderWidth: 1,\n    borderColor: theme.colors.border,\n  },\n  button: {\n    backgroundColor: theme.colors.primary,\n    padding: 16,\n    borderRadius: 6,\n    flexDirection: \"row\",\n    justifyContent: \"center\",\n    alignItems: \"center\",\n  },\n  buttonText: {\n    fontWeight: \"500\",\n  },\n}));\n`],\n  [\"auth/better-auth/convex/native/uniwind/components/sign-in.tsx.hbs\", `import { authClient } from \"@/lib/auth-client\";\nimport { useForm } from \"@tanstack/react-form\";\nimport { useRef } from \"react\";\nimport { Text, TextInput, View } from \"react-native\";\nimport { Button, FieldError, Input, Label, Spinner, Surface, TextField, useToast } from \"heroui-native\";\nimport z from \"zod\";\n\nconst signInSchema = z.object({\n  email: z\n    .string()\n    .trim()\n    .min(1, \"Email is required\")\n    .email(\"Enter a valid email address\"),\n  password: z\n    .string()\n    .min(1, \"Password is required\")\n    .min(8, \"Use at least 8 characters\"),\n});\n\nfunction getErrorMessage(error: unknown): string | null {\n  if (!error) return null;\n\n  if (typeof error === \"string\") {\n    return error;\n  }\n\n  if (Array.isArray(error)) {\n    for (const issue of error) {\n      const message = getErrorMessage(issue);\n      if (message) {\n        return message;\n      }\n    }\n    return null;\n  }\n\n  if (typeof error === \"object\" && error !== null) {\n    const maybeError = error as { message?: unknown };\n    if (typeof maybeError.message === \"string\") {\n      return maybeError.message;\n    }\n  }\n\n  return null;\n}\n\nexport function SignIn() {\n  const passwordInputRef = useRef<TextInput>(null);\n  const { toast } = useToast();\n\n  const form = useForm({\n    defaultValues: {\n      email: \"\",\n      password: \"\",\n    },\n    validators: {\n      onSubmit: signInSchema,\n    },\n    onSubmit: async ({ value, formApi }) => {\n      await authClient.signIn.email(\n        {\n          email: value.email.trim(),\n          password: value.password,\n        },\n        {\n          onError(error) {\n            toast.show({\n              variant: \"danger\",\n              label: error.error?.message || \"Failed to sign in\",\n            });\n          },\n          onSuccess() {\n            formApi.reset();\n            toast.show({\n              variant: \"success\",\n              label: \"Signed in successfully\",\n            });\n          },\n        },\n      );\n    },\n  });\n\n  return (\n    <Surface variant=\"secondary\" className=\"p-4 rounded-lg\">\n      <Text className=\"text-foreground font-medium mb-4\">Sign In</Text>\n\n      <form.Subscribe\n        selector={(state) => ({\n          isSubmitting: state.isSubmitting,\n          validationError: getErrorMessage(state.errorMap.onSubmit),\n        })}\n      >\n        {({ isSubmitting, validationError }) => {\n          const formError = validationError;\n\n          return (\n            <>\n              <FieldError isInvalid={!!formError} className=\"mb-3\">\n                {formError}\n              </FieldError>\n\n              <View className=\"gap-3\">\n                <form.Field name=\"email\">\n                  {(field) => (\n                    <TextField>\n                      <Label>Email</Label>\n                      <Input\n                        value={field.state.value}\n                        onBlur={field.handleBlur}\n                        onChangeText={field.handleChange}\n                        placeholder=\"email@example.com\"\n                        keyboardType=\"email-address\"\n                        autoCapitalize=\"none\"\n                        autoComplete=\"email\"\n                        textContentType=\"emailAddress\"\n                        returnKeyType=\"next\"\n                        blurOnSubmit={false}\n                        onSubmitEditing={() => {\n                          passwordInputRef.current?.focus();\n                        }}\n                      />\n                    </TextField>\n                  )}\n                </form.Field>\n\n                <form.Field name=\"password\">\n                  {(field) => (\n                    <TextField>\n                      <Label>Password</Label>\n                      <Input\n                        ref={passwordInputRef}\n                        value={field.state.value}\n                        onBlur={field.handleBlur}\n                        onChangeText={field.handleChange}\n                        placeholder=\"••••••••\"\n                        secureTextEntry\n                        autoComplete=\"password\"\n                        textContentType=\"password\"\n                        returnKeyType=\"go\"\n                        onSubmitEditing={form.handleSubmit}\n                      />\n                    </TextField>\n                  )}\n                </form.Field>\n\n                <Button onPress={form.handleSubmit} isDisabled={isSubmitting} className=\"mt-1\">\n                  {isSubmitting ? <Spinner size=\"sm\" color=\"default\" /> : <Button.Label>Sign In</Button.Label>}\n                </Button>\n              </View>\n            </>\n          );\n        }}\n      </form.Subscribe>\n    </Surface>\n  );\n}\n`],\n  [\"auth/better-auth/convex/native/uniwind/components/sign-up.tsx.hbs\", `import { authClient } from \"@/lib/auth-client\";\nimport { useForm } from \"@tanstack/react-form\";\nimport { useRef } from \"react\";\nimport { Text, TextInput, View } from \"react-native\";\nimport { Button, FieldError, Input, Label, Spinner, Surface, TextField, useToast } from \"heroui-native\";\nimport z from \"zod\";\n\nconst signUpSchema = z.object({\n  name: z\n    .string()\n    .trim()\n    .min(1, \"Name is required\")\n    .min(2, \"Name must be at least 2 characters\"),\n  email: z\n    .string()\n    .trim()\n    .min(1, \"Email is required\")\n    .email(\"Enter a valid email address\"),\n  password: z\n    .string()\n    .min(1, \"Password is required\")\n    .min(8, \"Use at least 8 characters\"),\n});\n\nfunction getErrorMessage(error: unknown): string | null {\n  if (!error) return null;\n\n  if (typeof error === \"string\") {\n    return error;\n  }\n\n  if (Array.isArray(error)) {\n    for (const issue of error) {\n      const message = getErrorMessage(issue);\n      if (message) {\n        return message;\n      }\n    }\n    return null;\n  }\n\n  if (typeof error === \"object\" && error !== null) {\n    const maybeError = error as { message?: unknown };\n    if (typeof maybeError.message === \"string\") {\n      return maybeError.message;\n    }\n  }\n\n  return null;\n}\n\nexport function SignUp() {\n  const emailInputRef = useRef<TextInput>(null);\n  const passwordInputRef = useRef<TextInput>(null);\n  const { toast } = useToast();\n\n  const form = useForm({\n    defaultValues: {\n      name: \"\",\n      email: \"\",\n      password: \"\",\n    },\n    validators: {\n      onSubmit: signUpSchema,\n    },\n    onSubmit: async ({ value, formApi }) => {\n      await authClient.signUp.email(\n        {\n          name: value.name.trim(),\n          email: value.email.trim(),\n          password: value.password,\n        },\n        {\n          onError(error) {\n            toast.show({\n              variant: \"danger\",\n              label: error.error?.message || \"Failed to sign up\",\n            });\n          },\n          onSuccess() {\n            formApi.reset();\n            toast.show({\n              variant: \"success\",\n              label: \"Account created successfully\",\n            });\n          },\n        },\n      );\n    },\n  });\n\n  return (\n    <Surface variant=\"secondary\" className=\"p-4 rounded-lg\">\n      <Text className=\"text-foreground font-medium mb-4\">Create Account</Text>\n\n      <form.Subscribe\n        selector={(state) => ({\n          isSubmitting: state.isSubmitting,\n          validationError: getErrorMessage(state.errorMap.onSubmit),\n        })}\n      >\n        {({ isSubmitting, validationError }) => {\n          const formError = validationError;\n\n          return (\n            <>\n              <FieldError isInvalid={!!formError} className=\"mb-3\">\n                {formError}\n              </FieldError>\n\n              <View className=\"gap-3\">\n                <form.Field name=\"name\">\n                  {(field) => (\n                    <TextField>\n                      <Label>Name</Label>\n                      <Input\n                        value={field.state.value}\n                        onBlur={field.handleBlur}\n                        onChangeText={field.handleChange}\n                        placeholder=\"John Doe\"\n                        autoComplete=\"name\"\n                        textContentType=\"name\"\n                        returnKeyType=\"next\"\n                        blurOnSubmit={false}\n                        onSubmitEditing={() => {\n                          emailInputRef.current?.focus();\n                        }}\n                      />\n                    </TextField>\n                  )}\n                </form.Field>\n\n                <form.Field name=\"email\">\n                  {(field) => (\n                    <TextField>\n                      <Label>Email</Label>\n                      <Input\n                        ref={emailInputRef}\n                        value={field.state.value}\n                        onBlur={field.handleBlur}\n                        onChangeText={field.handleChange}\n                        placeholder=\"email@example.com\"\n                        keyboardType=\"email-address\"\n                        autoCapitalize=\"none\"\n                        autoComplete=\"email\"\n                        textContentType=\"emailAddress\"\n                        returnKeyType=\"next\"\n                        blurOnSubmit={false}\n                        onSubmitEditing={() => {\n                          passwordInputRef.current?.focus();\n                        }}\n                      />\n                    </TextField>\n                  )}\n                </form.Field>\n\n                <form.Field name=\"password\">\n                  {(field) => (\n                    <TextField>\n                      <Label>Password</Label>\n                      <Input\n                        ref={passwordInputRef}\n                        value={field.state.value}\n                        onBlur={field.handleBlur}\n                        onChangeText={field.handleChange}\n                        placeholder=\"••••••••\"\n                        secureTextEntry\n                        autoComplete=\"new-password\"\n                        textContentType=\"newPassword\"\n                        returnKeyType=\"go\"\n                        onSubmitEditing={form.handleSubmit}\n                      />\n                    </TextField>\n                  )}\n                </form.Field>\n\n                <Button onPress={form.handleSubmit} isDisabled={isSubmitting} className=\"mt-1\">\n                  {isSubmitting ? (\n                    <Spinner size=\"sm\" color=\"default\" />\n                  ) : (\n                    <Button.Label>Create Account</Button.Label>\n                  )}\n                </Button>\n              </View>\n            </>\n          );\n        }}\n      </form.Subscribe>\n    </Surface>\n  );\n}\n`],\n  [\"auth/better-auth/convex/web/react/next/src/app/api/auth/[...all]/route.ts.hbs\", `import { handler } from \"@/lib/auth-server\";\n\nexport const { GET, POST } = handler;\n`],\n  [\"auth/better-auth/convex/web/react/next/src/app/dashboard/page.tsx.hbs\", `\"use client\"\n\nimport SignInForm from \"@/components/sign-in-form\";\nimport SignUpForm from \"@/components/sign-up-form\";\nimport UserMenu from \"@/components/user-menu\";\nimport { api } from \"@{{projectName}}/backend/convex/_generated/api\";\nimport {\n    Authenticated,\n    AuthLoading,\n    Unauthenticated,\n    useQuery,\n} from \"convex/react\";\nimport { useState } from \"react\";\n\nexport default function DashboardPage() {\n    const [showSignIn, setShowSignIn] = useState(false);\n    const privateData = useQuery(api.privateData.get);\n\n    return (\n        <>\n            <Authenticated>\n                <div>\n                    <h1>Dashboard</h1>\n                    <p>privateData: {privateData?.message}</p>\n                    <UserMenu />\n                </div>\n            </Authenticated>\n            <Unauthenticated>\n                {showSignIn ? (\n                    <SignInForm onSwitchToSignUp={() => setShowSignIn(false)} />\n                ) : (\n                    <SignUpForm onSwitchToSignIn={() => setShowSignIn(true)} />\n                )}\n            </Unauthenticated>\n            <AuthLoading>\n                <div>Loading...</div>\n            </AuthLoading>\n        </>\n    );\n}\n`],\n  [\"auth/better-auth/convex/web/react/next/src/components/sign-in-form.tsx.hbs\", `import { authClient } from \"@/lib/auth-client\";\nimport { useForm } from \"@tanstack/react-form\";\nimport { toast } from \"sonner\";\nimport z from \"zod\";\nimport { Button } from \"@{{projectName}}/ui/components/button\";\nimport { Input } from \"@{{projectName}}/ui/components/input\";\nimport { Label } from \"@{{projectName}}/ui/components/label\";\nimport { useRouter } from \"next/navigation\";\n\nexport default function SignInForm({\n\tonSwitchToSignUp,\n}: {\n\tonSwitchToSignUp: () => void;\n}) {\n\tconst router = useRouter();\n\n\tconst form = useForm({\n\t\tdefaultValues: {\n\t\t\temail: \"\",\n\t\t\tpassword: \"\",\n\t\t},\n\t\tonSubmit: async ({ value }) => {\n\t\t\tawait authClient.signIn.email(\n\t\t\t\t{\n\t\t\t\t\temail: value.email,\n\t\t\t\t\tpassword: value.password,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tonSuccess: () => {\n\t\t\t\t\t\trouter.push(\"/dashboard\");\n\t\t\t\t\t\ttoast.success(\"Sign in successful\");\n\t\t\t\t\t},\n\t\t\t\t\tonError: (error) => {\n\t\t\t\t\t\ttoast.error(error.error.message || error.error.statusText);\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t);\n\t\t},\n\t\tvalidators: {\n\t\t\tonSubmit: z.object({\n\t\t\t\temail: z.email(\"Invalid email address\"),\n\t\t\t\tpassword: z.string().min(8, \"Password must be at least 8 characters\"),\n\t\t\t}),\n\t\t},\n\t});\n\n\treturn (\n\t\t<div className=\"mx-auto w-full mt-10 max-w-md p-6\">\n\t\t\t<h1 className=\"mb-6 text-center text-3xl font-bold\">Welcome Back</h1>\n\n\t\t\t<form\n\t\t\t\tonSubmit={(e) => {\n\t\t\t\t\te.preventDefault();\n\t\t\t\t\te.stopPropagation();\n\t\t\t\t\tform.handleSubmit();\n\t\t\t\t}}\n\t\t\t\tclassName=\"space-y-4\"\n\t\t\t>\n\t\t\t\t<div>\n\t\t\t\t\t<form.Field name=\"email\">\n\t\t\t\t\t\t{(field) => (\n\t\t\t\t\t\t\t<div className=\"space-y-2\">\n\t\t\t\t\t\t\t\t<Label htmlFor={field.name}>Email</Label>\n\t\t\t\t\t\t\t\t<Input\n\t\t\t\t\t\t\t\t\tid={field.name}\n\t\t\t\t\t\t\t\t\tname={field.name}\n\t\t\t\t\t\t\t\t\ttype=\"email\"\n\t\t\t\t\t\t\t\t\tvalue={field.state.value}\n\t\t\t\t\t\t\t\t\tonBlur={field.handleBlur}\n\t\t\t\t\t\t\t\t\tonChange={(e) => field.handleChange(e.target.value)}\n\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t{field.state.meta.errors.map((error) => (\n\t\t\t\t\t\t\t\t\t<p key={error?.message} className=\"text-red-500\">\n\t\t\t\t\t\t\t\t\t\t{error?.message}\n\t\t\t\t\t\t\t\t\t</p>\n\t\t\t\t\t\t\t\t))}\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t)}\n\t\t\t\t\t</form.Field>\n\t\t\t\t</div>\n\n\t\t\t\t<div>\n\t\t\t\t\t<form.Field name=\"password\">\n\t\t\t\t\t\t{(field) => (\n\t\t\t\t\t\t\t<div className=\"space-y-2\">\n\t\t\t\t\t\t\t\t<Label htmlFor={field.name}>Password</Label>\n\t\t\t\t\t\t\t\t<Input\n\t\t\t\t\t\t\t\t\tid={field.name}\n\t\t\t\t\t\t\t\t\tname={field.name}\n\t\t\t\t\t\t\t\t\ttype=\"password\"\n\t\t\t\t\t\t\t\t\tvalue={field.state.value}\n\t\t\t\t\t\t\t\t\tonBlur={field.handleBlur}\n\t\t\t\t\t\t\t\t\tonChange={(e) => field.handleChange(e.target.value)}\n\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t{field.state.meta.errors.map((error) => (\n\t\t\t\t\t\t\t\t\t<p key={error?.message} className=\"text-red-500\">\n\t\t\t\t\t\t\t\t\t\t{error?.message}\n\t\t\t\t\t\t\t\t\t</p>\n\t\t\t\t\t\t\t\t))}\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t)}\n\t\t\t\t\t</form.Field>\n\t\t\t\t</div>\n\n\t\t\t\t<form.Subscribe selector={(state) => ({ canSubmit: state.canSubmit, isSubmitting: state.isSubmitting })}>\n          {({ canSubmit, isSubmitting }) => (\n\t\t\t\t\t\t<Button\n\t\t\t\t\t\t\ttype=\"submit\"\n\t\t\t\t\t\t\tclassName=\"w-full\"\n\t\t\t\t\t\t\tdisabled={!canSubmit || isSubmitting}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t{isSubmitting ? \"Submitting...\" : \"Sign In\"}\n\t\t\t\t\t\t</Button>\n\t\t\t\t\t)}\n\t\t\t\t</form.Subscribe>\n\t\t\t</form>\n\n\t\t\t<div className=\"mt-4 text-center\">\n\t\t\t\t<Button\n\t\t\t\t\tvariant=\"link\"\n\t\t\t\t\tonClick={onSwitchToSignUp}\n\t\t\t\t\tclassName=\"text-indigo-600 hover:text-indigo-800\"\n\t\t\t\t>\n\t\t\t\t\tNeed an account? Sign Up\n\t\t\t\t</Button>\n\t\t\t</div>\n\t\t</div>\n\t);\n}\n`],\n  [\"auth/better-auth/convex/web/react/next/src/components/sign-up-form.tsx.hbs\", `import { authClient } from \"@/lib/auth-client\";\nimport { useForm } from \"@tanstack/react-form\";\nimport { toast } from \"sonner\";\nimport z from \"zod\";\nimport { Button } from \"@{{projectName}}/ui/components/button\";\nimport { Input } from \"@{{projectName}}/ui/components/input\";\nimport { Label } from \"@{{projectName}}/ui/components/label\";\nimport { useRouter } from \"next/navigation\";\n\nexport default function SignUpForm({\n\tonSwitchToSignIn,\n}: {\n\tonSwitchToSignIn: () => void;\n}) {\n\tconst router = useRouter();\n\n\tconst form = useForm({\n\t\tdefaultValues: {\n\t\t\temail: \"\",\n\t\t\tpassword: \"\",\n\t\t\tname: \"\",\n\t\t},\n\t\tonSubmit: async ({ value }) => {\n\t\t\tawait authClient.signUp.email(\n\t\t\t\t{\n\t\t\t\t\temail: value.email,\n\t\t\t\t\tpassword: value.password,\n\t\t\t\t\tname: value.name,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tonSuccess: () => {\n\t\t\t\t\t\trouter.push(\"/dashboard\");\n\t\t\t\t\t\ttoast.success(\"Sign up successful\");\n\t\t\t\t\t},\n\t\t\t\t\tonError: (error) => {\n\t\t\t\t\t\ttoast.error(error.error.message || error.error.statusText);\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t);\n\t\t},\n\t\tvalidators: {\n\t\t\tonSubmit: z.object({\n\t\t\t\tname: z.string().min(2, \"Name must be at least 2 characters\"),\n\t\t\t\temail: z.email(\"Invalid email address\"),\n\t\t\t\tpassword: z.string().min(8, \"Password must be at least 8 characters\"),\n\t\t\t}),\n\t\t},\n\t});\n\n\treturn (\n\t\t<div className=\"mx-auto w-full mt-10 max-w-md p-6\">\n\t\t\t<h1 className=\"mb-6 text-center text-3xl font-bold\">Create Account</h1>\n\n\t\t\t<form\n\t\t\t\tonSubmit={(e) => {\n\t\t\t\t\te.preventDefault();\n\t\t\t\t\te.stopPropagation();\n\t\t\t\t\tform.handleSubmit();\n\t\t\t\t}}\n\t\t\t\tclassName=\"space-y-4\"\n\t\t\t>\n\t\t\t\t<div>\n\t\t\t\t\t<form.Field name=\"name\">\n\t\t\t\t\t\t{(field) => (\n\t\t\t\t\t\t\t<div className=\"space-y-2\">\n\t\t\t\t\t\t\t\t<Label htmlFor={field.name}>Name</Label>\n\t\t\t\t\t\t\t\t<Input\n\t\t\t\t\t\t\t\t\tid={field.name}\n\t\t\t\t\t\t\t\t\tname={field.name}\n\t\t\t\t\t\t\t\t\tvalue={field.state.value}\n\t\t\t\t\t\t\t\t\tonBlur={field.handleBlur}\n\t\t\t\t\t\t\t\t\tonChange={(e) => field.handleChange(e.target.value)}\n\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t{field.state.meta.errors.map((error) => (\n\t\t\t\t\t\t\t\t\t<p key={error?.message} className=\"text-red-500\">\n\t\t\t\t\t\t\t\t\t\t{error?.message}\n\t\t\t\t\t\t\t\t\t</p>\n\t\t\t\t\t\t\t\t))}\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t)}\n\t\t\t\t\t</form.Field>\n\t\t\t\t</div>\n\n\t\t\t\t<div>\n\t\t\t\t\t<form.Field name=\"email\">\n\t\t\t\t\t\t{(field) => (\n\t\t\t\t\t\t\t<div className=\"space-y-2\">\n\t\t\t\t\t\t\t\t<Label htmlFor={field.name}>Email</Label>\n\t\t\t\t\t\t\t\t<Input\n\t\t\t\t\t\t\t\t\tid={field.name}\n\t\t\t\t\t\t\t\t\tname={field.name}\n\t\t\t\t\t\t\t\t\ttype=\"email\"\n\t\t\t\t\t\t\t\t\tvalue={field.state.value}\n\t\t\t\t\t\t\t\t\tonBlur={field.handleBlur}\n\t\t\t\t\t\t\t\t\tonChange={(e) => field.handleChange(e.target.value)}\n\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t{field.state.meta.errors.map((error) => (\n\t\t\t\t\t\t\t\t\t<p key={error?.message} className=\"text-red-500\">\n\t\t\t\t\t\t\t\t\t\t{error?.message}\n\t\t\t\t\t\t\t\t\t</p>\n\t\t\t\t\t\t\t\t))}\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t)}\n\t\t\t\t\t</form.Field>\n\t\t\t\t</div>\n\n\t\t\t\t<div>\n\t\t\t\t\t<form.Field name=\"password\">\n\t\t\t\t\t\t{(field) => (\n\t\t\t\t\t\t\t<div className=\"space-y-2\">\n\t\t\t\t\t\t\t\t<Label htmlFor={field.name}>Password</Label>\n\t\t\t\t\t\t\t\t<Input\n\t\t\t\t\t\t\t\t\tid={field.name}\n\t\t\t\t\t\t\t\t\tname={field.name}\n\t\t\t\t\t\t\t\t\ttype=\"password\"\n\t\t\t\t\t\t\t\t\tvalue={field.state.value}\n\t\t\t\t\t\t\t\t\tonBlur={field.handleBlur}\n\t\t\t\t\t\t\t\t\tonChange={(e) => field.handleChange(e.target.value)}\n\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t{field.state.meta.errors.map((error) => (\n\t\t\t\t\t\t\t\t\t<p key={error?.message} className=\"text-red-500\">\n\t\t\t\t\t\t\t\t\t\t{error?.message}\n\t\t\t\t\t\t\t\t\t</p>\n\t\t\t\t\t\t\t\t))}\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t)}\n\t\t\t\t\t</form.Field>\n\t\t\t\t</div>\n\n\t\t\t\t<form.Subscribe selector={(state) => ({ canSubmit: state.canSubmit, isSubmitting: state.isSubmitting })}>\n          {({ canSubmit, isSubmitting }) => (\n\t\t\t\t\t\t<Button\n\t\t\t\t\t\t\ttype=\"submit\"\n\t\t\t\t\t\t\tclassName=\"w-full\"\n\t\t\t\t\t\t\tdisabled={!canSubmit || isSubmitting}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t{isSubmitting ? \"Submitting...\" : \"Sign Up\"}\n\t\t\t\t\t\t</Button>\n\t\t\t\t\t)}\n\t\t\t\t</form.Subscribe>\n\t\t\t</form>\n\n\t\t\t<div className=\"mt-4 text-center\">\n\t\t\t\t<Button\n\t\t\t\t\tvariant=\"link\"\n\t\t\t\t\tonClick={onSwitchToSignIn}\n\t\t\t\t\tclassName=\"text-indigo-600 hover:text-indigo-800\"\n\t\t\t\t>\n\t\t\t\t\tAlready have an account? Sign In\n\t\t\t\t</Button>\n\t\t\t</div>\n\t\t</div>\n\t);\n}\n`],\n  [\"auth/better-auth/convex/web/react/next/src/components/user-menu.tsx.hbs\", `import {\n\tDropdownMenu,\n\tDropdownMenuContent,\n\tDropdownMenuGroup,\n\tDropdownMenuItem,\n\tDropdownMenuLabel,\n\tDropdownMenuSeparator,\n\tDropdownMenuTrigger,\n} from \"@{{projectName}}/ui/components/dropdown-menu\";\nimport { authClient } from \"@/lib/auth-client\";\nimport { Button } from \"@{{projectName}}/ui/components/button\";\nimport { useRouter } from \"next/navigation\";\nimport { useQuery } from \"convex/react\";\nimport { api } from \"@{{projectName}}/backend/convex/_generated/api\";\n\nexport default function UserMenu() {\n\tconst router = useRouter();\n\tconst user = useQuery(api.auth.getCurrentUser)\n\n\treturn (\n\t\t<DropdownMenu>\n\t\t\t<DropdownMenuTrigger render={<Button variant=\"outline\" />}>\n\t\t\t\t{user?.name}\n\t\t\t</DropdownMenuTrigger>\n\t\t\t<DropdownMenuContent className=\"bg-card\">\n\t\t\t\t<DropdownMenuGroup>\n\t\t\t\t\t<DropdownMenuLabel>My Account</DropdownMenuLabel>\n\t\t\t\t\t<DropdownMenuSeparator />\n\t\t\t\t\t<DropdownMenuItem>{user?.email}</DropdownMenuItem>\n\t\t\t\t\t<DropdownMenuItem\n\t\t\t\t\t\tvariant=\"destructive\"\n\t\t\t\t\t\tonClick={() => {\n\t\t\t\t\t\t\tauthClient.signOut({\n\t\t\t\t\t\t\t\tfetchOptions: {\n\t\t\t\t\t\t\t\t\tonSuccess: () => {\n\t\t\t\t\t\t\t\t\t\trouter.push(\"/dashboard\");\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}}\n\t\t\t\t\t>\n\t\t\t\t\t\tSign Out\n\t\t\t\t\t</DropdownMenuItem>\n\t\t\t\t</DropdownMenuGroup>\n\t\t\t</DropdownMenuContent>\n\t\t</DropdownMenu>\n\t);\n}\n`],\n  [\"auth/better-auth/convex/web/react/next/src/lib/auth-client.ts.hbs\", `import { createAuthClient } from \"better-auth/react\";\nimport { convexClient } from \"@convex-dev/better-auth/client/plugins\";\n\nexport const authClient = createAuthClient({\n  plugins: [convexClient()],\n});\n`],\n  [\"auth/better-auth/convex/web/react/next/src/lib/auth-server.ts.hbs\", `import { convexBetterAuthNextJs } from \"@convex-dev/better-auth/nextjs\";\nimport { env } from \"@{{projectName}}/env/web\";\n\nexport const {\n\thandler,\n\tpreloadAuthQuery,\n\tisAuthenticated,\n\tgetToken,\n\tfetchAuthQuery,\n\tfetchAuthMutation,\n\tfetchAuthAction,\n} = convexBetterAuthNextJs({\n\tconvexUrl: env.NEXT_PUBLIC_CONVEX_URL,\n\tconvexSiteUrl: env.NEXT_PUBLIC_CONVEX_SITE_URL,\n});\n`],\n  [\"auth/better-auth/convex/web/react/react-router/src/components/sign-in-form.tsx.hbs\", `import { authClient } from \"@/lib/auth-client\";\nimport { useForm } from \"@tanstack/react-form\";\nimport { useNavigate } from \"react-router\";\nimport { toast } from \"sonner\";\nimport z from \"zod\";\nimport { Button } from \"@{{projectName}}/ui/components/button\";\nimport { Input } from \"@{{projectName}}/ui/components/input\";\nimport { Label } from \"@{{projectName}}/ui/components/label\";\n\nexport default function SignInForm({\n  onSwitchToSignUp,\n}: {\n  onSwitchToSignUp: () => void;\n}) {\n  const navigate = useNavigate();\n\n  const form = useForm({\n    defaultValues: {\n      email: \"\",\n      password: \"\",\n    },\n    onSubmit: async ({ value }) => {\n      await authClient.signIn.email(\n        {\n          email: value.email,\n          password: value.password,\n        },\n        {\n          onSuccess: () => {\n            navigate(\"/dashboard\");\n            toast.success(\"Sign in successful\");\n          },\n          onError: (error) => {\n            toast.error(error.error.message || error.error.statusText);\n          },\n        },\n      );\n    },\n    validators: {\n      onSubmit: z.object({\n        email: z.email(\"Invalid email address\"),\n        password: z.string().min(8, \"Password must be at least 8 characters\"),\n      }),\n    },\n  });\n\n  return (\n    <div className=\"mx-auto mt-10 w-full max-w-md p-6\">\n      <h1 className=\"mb-6 text-center text-3xl font-bold\">Welcome Back</h1>\n\n      <form\n        onSubmit={(e) => {\n          e.preventDefault();\n          e.stopPropagation();\n          form.handleSubmit();\n        }}\n        className=\"space-y-4\"\n      >\n        <div>\n          <form.Field name=\"email\">\n            {(field) => (\n              <div className=\"space-y-2\">\n                <Label htmlFor={field.name}>Email</Label>\n                <Input\n                  id={field.name}\n                  name={field.name}\n                  type=\"email\"\n                  value={field.state.value}\n                  onBlur={field.handleBlur}\n                  onChange={(e) => field.handleChange(e.target.value)}\n                />\n                {field.state.meta.errors.map((error, index) => (\n                  <p key={\\`\\${field.name}-error-\\${index}\\`} className=\"text-red-500\">\n                    {error?.message}\n                  </p>\n                ))}\n              </div>\n            )}\n          </form.Field>\n        </div>\n\n        <div>\n          <form.Field name=\"password\">\n            {(field) => (\n              <div className=\"space-y-2\">\n                <Label htmlFor={field.name}>Password</Label>\n                <Input\n                  id={field.name}\n                  name={field.name}\n                  type=\"password\"\n                  value={field.state.value}\n                  onBlur={field.handleBlur}\n                  onChange={(e) => field.handleChange(e.target.value)}\n                />\n                {field.state.meta.errors.map((error, index) => (\n                  <p key={\\`\\${field.name}-error-\\${index}\\`} className=\"text-red-500\">\n                    {error?.message}\n                  </p>\n                ))}\n              </div>\n            )}\n          </form.Field>\n        </div>\n\n        <form.Subscribe\n          selector={(state) => ({\n            canSubmit: state.canSubmit,\n            isSubmitting: state.isSubmitting,\n          })}\n        >\n          {({ canSubmit, isSubmitting }) => (\n            <Button type=\"submit\" className=\"w-full\" disabled={!canSubmit || isSubmitting}>\n              {isSubmitting ? \"Submitting...\" : \"Sign In\"}\n            </Button>\n          )}\n        </form.Subscribe>\n      </form>\n\n      <div className=\"mt-4 text-center\">\n        <Button\n          variant=\"link\"\n          onClick={onSwitchToSignUp}\n          className=\"text-indigo-600 hover:text-indigo-800\"\n        >\n          Need an account? Sign Up\n        </Button>\n      </div>\n    </div>\n  );\n}\n`],\n  [\"auth/better-auth/convex/web/react/react-router/src/components/sign-up-form.tsx.hbs\", `import { authClient } from \"@/lib/auth-client\";\nimport { useForm } from \"@tanstack/react-form\";\nimport { useNavigate } from \"react-router\";\nimport { toast } from \"sonner\";\nimport z from \"zod\";\nimport { Button } from \"@{{projectName}}/ui/components/button\";\nimport { Input } from \"@{{projectName}}/ui/components/input\";\nimport { Label } from \"@{{projectName}}/ui/components/label\";\n\nexport default function SignUpForm({\n  onSwitchToSignIn,\n}: {\n  onSwitchToSignIn: () => void;\n}) {\n  const navigate = useNavigate();\n\n  const form = useForm({\n    defaultValues: {\n      email: \"\",\n      password: \"\",\n      name: \"\",\n    },\n    onSubmit: async ({ value }) => {\n      await authClient.signUp.email(\n        {\n          email: value.email,\n          password: value.password,\n          name: value.name,\n        },\n        {\n          onSuccess: () => {\n            navigate(\"/dashboard\");\n            toast.success(\"Sign up successful\");\n          },\n          onError: (error) => {\n            toast.error(error.error.message || error.error.statusText);\n          },\n        },\n      );\n    },\n    validators: {\n      onSubmit: z.object({\n        name: z.string().min(2, \"Name must be at least 2 characters\"),\n        email: z.email(\"Invalid email address\"),\n        password: z.string().min(8, \"Password must be at least 8 characters\"),\n      }),\n    },\n  });\n\n  return (\n    <div className=\"mx-auto mt-10 w-full max-w-md p-6\">\n      <h1 className=\"mb-6 text-center text-3xl font-bold\">Create Account</h1>\n\n      <form\n        onSubmit={(e) => {\n          e.preventDefault();\n          e.stopPropagation();\n          form.handleSubmit();\n        }}\n        className=\"space-y-4\"\n      >\n        <div>\n          <form.Field name=\"name\">\n            {(field) => (\n              <div className=\"space-y-2\">\n                <Label htmlFor={field.name}>Name</Label>\n                <Input\n                  id={field.name}\n                  name={field.name}\n                  value={field.state.value}\n                  onBlur={field.handleBlur}\n                  onChange={(e) => field.handleChange(e.target.value)}\n                />\n                {field.state.meta.errors.map((error, index) => (\n                  <p key={\\`\\${field.name}-error-\\${index}\\`} className=\"text-red-500\">\n                    {error?.message}\n                  </p>\n                ))}\n              </div>\n            )}\n          </form.Field>\n        </div>\n\n        <div>\n          <form.Field name=\"email\">\n            {(field) => (\n              <div className=\"space-y-2\">\n                <Label htmlFor={field.name}>Email</Label>\n                <Input\n                  id={field.name}\n                  name={field.name}\n                  type=\"email\"\n                  value={field.state.value}\n                  onBlur={field.handleBlur}\n                  onChange={(e) => field.handleChange(e.target.value)}\n                />\n                {field.state.meta.errors.map((error, index) => (\n                  <p key={\\`\\${field.name}-error-\\${index}\\`} className=\"text-red-500\">\n                    {error?.message}\n                  </p>\n                ))}\n              </div>\n            )}\n          </form.Field>\n        </div>\n\n        <div>\n          <form.Field name=\"password\">\n            {(field) => (\n              <div className=\"space-y-2\">\n                <Label htmlFor={field.name}>Password</Label>\n                <Input\n                  id={field.name}\n                  name={field.name}\n                  type=\"password\"\n                  value={field.state.value}\n                  onBlur={field.handleBlur}\n                  onChange={(e) => field.handleChange(e.target.value)}\n                />\n                {field.state.meta.errors.map((error, index) => (\n                  <p key={\\`\\${field.name}-error-\\${index}\\`} className=\"text-red-500\">\n                    {error?.message}\n                  </p>\n                ))}\n              </div>\n            )}\n          </form.Field>\n        </div>\n\n        <form.Subscribe\n          selector={(state) => ({\n            canSubmit: state.canSubmit,\n            isSubmitting: state.isSubmitting,\n          })}\n        >\n          {({ canSubmit, isSubmitting }) => (\n            <Button type=\"submit\" className=\"w-full\" disabled={!canSubmit || isSubmitting}>\n              {isSubmitting ? \"Submitting...\" : \"Sign Up\"}\n            </Button>\n          )}\n        </form.Subscribe>\n      </form>\n\n      <div className=\"mt-4 text-center\">\n        <Button\n          variant=\"link\"\n          onClick={onSwitchToSignIn}\n          className=\"text-indigo-600 hover:text-indigo-800\"\n        >\n          Already have an account? Sign In\n        </Button>\n      </div>\n    </div>\n  );\n}\n`],\n  [\"auth/better-auth/convex/web/react/react-router/src/components/user-menu.tsx.hbs\", `import { useNavigate } from \"react-router\";\n\nimport {\n  DropdownMenu,\n  DropdownMenuContent,\n  DropdownMenuGroup,\n  DropdownMenuItem,\n  DropdownMenuLabel,\n  DropdownMenuSeparator,\n  DropdownMenuTrigger,\n} from \"@{{projectName}}/ui/components/dropdown-menu\";\nimport { authClient } from \"@/lib/auth-client\";\nimport { useQuery } from \"convex/react\";\nimport { api } from \"@{{projectName}}/backend/convex/_generated/api\";\n\nimport { Button } from \"@{{projectName}}/ui/components/button\";\n\nexport default function UserMenu() {\n  const navigate = useNavigate();\n  const user = useQuery(api.auth.getCurrentUser);\n\n  return (\n    <DropdownMenu>\n      <DropdownMenuTrigger render={<Button variant=\"outline\" />}>\n        {user?.name}\n      </DropdownMenuTrigger>\n      <DropdownMenuContent className=\"bg-card\">\n        <DropdownMenuGroup>\n          <DropdownMenuLabel>My Account</DropdownMenuLabel>\n          <DropdownMenuSeparator />\n          <DropdownMenuItem>{user?.email}</DropdownMenuItem>\n          <DropdownMenuItem\n            variant=\"destructive\"\n            onClick={() => {\n              authClient.signOut({\n                fetchOptions: {\n                  onSuccess: () => {\n                    navigate(\"/dashboard\");\n                  },\n                },\n              });\n            }}\n          >\n            Sign Out\n          </DropdownMenuItem>\n        </DropdownMenuGroup>\n      </DropdownMenuContent>\n    </DropdownMenu>\n  );\n}\n`],\n  [\"auth/better-auth/convex/web/react/react-router/src/lib/auth-client.ts.hbs\", `import { createAuthClient } from \"better-auth/react\";\nimport {\n  convexClient,\n  crossDomainClient,\n} from \"@convex-dev/better-auth/client/plugins\";\nimport { env } from \"@{{projectName}}/env/web\";\n\nexport const authClient = createAuthClient({\n  baseURL: env.VITE_CONVEX_SITE_URL,\n  plugins: [crossDomainClient(), convexClient()],\n});\n`],\n  [\"auth/better-auth/convex/web/react/react-router/src/routes/dashboard.tsx.hbs\", `import SignInForm from \"@/components/sign-in-form\";\nimport SignUpForm from \"@/components/sign-up-form\";\nimport UserMenu from \"@/components/user-menu\";\nimport { api } from \"@{{projectName}}/backend/convex/_generated/api\";\nimport {\n  Authenticated,\n  AuthLoading,\n  Unauthenticated,\n  useQuery,\n} from \"convex/react\";\nimport { useState } from \"react\";\n\nfunction PrivateDashboardContent() {\n  const privateData = useQuery(api.privateData.get);\n\n  return (\n    <div>\n      <h1>Dashboard</h1>\n      <p>privateData: {privateData?.message}</p>\n      <UserMenu />\n    </div>\n  );\n}\n\nexport default function Dashboard() {\n  const [showSignIn, setShowSignIn] = useState(false);\n\n  return (\n    <>\n      <Authenticated>\n        <PrivateDashboardContent />\n      </Authenticated>\n      <Unauthenticated>\n        {showSignIn ? (\n          <SignInForm onSwitchToSignUp={() => setShowSignIn(false)} />\n        ) : (\n          <SignUpForm onSwitchToSignIn={() => setShowSignIn(true)} />\n        )}\n      </Unauthenticated>\n      <AuthLoading>\n        <div>Loading...</div>\n      </AuthLoading>\n    </>\n  );\n}\n`],\n  [\"auth/better-auth/convex/web/react/tanstack-router/src/components/sign-in-form.tsx.hbs\", `import { authClient } from \"@/lib/auth-client\";\nimport { useForm } from \"@tanstack/react-form\";\nimport { useNavigate } from \"@tanstack/react-router\";\nimport { toast } from \"sonner\";\nimport z from \"zod\";\nimport { Button } from \"@{{projectName}}/ui/components/button\";\nimport { Input } from \"@{{projectName}}/ui/components/input\";\nimport { Label } from \"@{{projectName}}/ui/components/label\";\n\nexport default function SignInForm({\n    onSwitchToSignUp,\n}: {\n    onSwitchToSignUp: () => void;\n}) {\n    const navigate = useNavigate({\n        from: \"/\",\n    });\n\n    const form = useForm({\n        defaultValues: {\n            email: \"\",\n            password: \"\",\n        },\n        onSubmit: async ({ value }) => {\n            await authClient.signIn.email(\n                {\n                    email: value.email,\n                    password: value.password,\n                },\n                {\n                    onSuccess: () => {\n                        navigate({\n                            to: \"/dashboard\",\n                        });\n                        toast.success(\"Sign in successful\");\n                    },\n                    onError: (error) => {\n                        toast.error(error.error.message || error.error.statusText);\n                    },\n                },\n            );\n        },\n        validators: {\n            onSubmit: z.object({\n                email: z.email(\"Invalid email address\"),\n                password: z.string().min(8, \"Password must be at least 8 characters\"),\n            }),\n        },\n    });\n\n    return (\n        <div className=\"mx-auto w-full mt-10 max-w-md p-6\">\n            <h1 className=\"mb-6 text-center text-3xl font-bold\">Welcome Back</h1>\n\n            <form\n                onSubmit={(e) => {\n                    e.preventDefault();\n                    e.stopPropagation();\n                    form.handleSubmit();\n                }}\n                className=\"space-y-4\"\n            >\n                <div>\n                    <form.Field name=\"email\">\n                        {(field) => (\n                            <div className=\"space-y-2\">\n                                <Label htmlFor={field.name}>Email</Label>\n                                <Input\n                                    id={field.name}\n                                    name={field.name}\n                                    type=\"email\"\n                                    value={field.state.value}\n                                    onBlur={field.handleBlur}\n                                    onChange={(e) => field.handleChange(e.target.value)}\n                                />\n                                {field.state.meta.errors.map((error, index) => (\n                                    <p key={\\`\\${field.name}-error-\\${index}\\`} className=\"text-red-500\">\n                                        {error?.message}\n                                    </p>\n                                ))}\n                            </div>\n                        )}\n                    </form.Field>\n                </div>\n\n                <div>\n                    <form.Field name=\"password\">\n                        {(field) => (\n                            <div className=\"space-y-2\">\n                                <Label htmlFor={field.name}>Password</Label>\n                                <Input\n                                    id={field.name}\n                                    name={field.name}\n                                    type=\"password\"\n                                    value={field.state.value}\n                                    onBlur={field.handleBlur}\n                                    onChange={(e) => field.handleChange(e.target.value)}\n                                />\n                                {field.state.meta.errors.map((error, index) => (\n                                    <p key={\\`\\${field.name}-error-\\${index}\\`} className=\"text-red-500\">\n                                        {error?.message}\n                                    </p>\n                                ))}\n                            </div>\n                        )}\n                    </form.Field>\n                </div>\n\n                <form.Subscribe selector={(state) => ({ canSubmit: state.canSubmit, isSubmitting: state.isSubmitting })}>\n          {({ canSubmit, isSubmitting }) => (\n                        <Button\n                            type=\"submit\"\n                            className=\"w-full\"\n                            disabled={!canSubmit || isSubmitting}\n                        >\n                            {isSubmitting ? \"Submitting...\" : \"Sign In\"}\n                        </Button>\n                    )}\n                </form.Subscribe>\n            </form>\n\n            <div className=\"mt-4 text-center\">\n                <Button\n                    variant=\"link\"\n                    onClick={onSwitchToSignUp}\n                    className=\"text-indigo-600 hover:text-indigo-800\"\n                >\n                    Need an account? Sign Up\n                </Button>\n            </div>\n        </div>\n    );\n}\n`],\n  [\"auth/better-auth/convex/web/react/tanstack-router/src/components/sign-up-form.tsx.hbs\", `import { authClient } from \"@/lib/auth-client\";\nimport { useForm } from \"@tanstack/react-form\";\nimport { useNavigate } from \"@tanstack/react-router\";\nimport { toast } from \"sonner\";\nimport z from \"zod\";\nimport { Button } from \"@{{projectName}}/ui/components/button\";\nimport { Input } from \"@{{projectName}}/ui/components/input\";\nimport { Label } from \"@{{projectName}}/ui/components/label\";\n\nexport default function SignUpForm({\n    onSwitchToSignIn,\n}: {\n    onSwitchToSignIn: () => void;\n}) {\n    const navigate = useNavigate({\n        from: \"/\",\n    });\n\n    const form = useForm({\n        defaultValues: {\n            email: \"\",\n            password: \"\",\n            name: \"\",\n        },\n        onSubmit: async ({ value }) => {\n            await authClient.signUp.email(\n                {\n                    email: value.email,\n                    password: value.password,\n                    name: value.name,\n                },\n                {\n                    onSuccess: () => {\n                        navigate({\n                            to: \"/dashboard\",\n                        });\n                        toast.success(\"Sign up successful\");\n                    },\n                    onError: (error) => {\n                        toast.error(error.error.message || error.error.statusText);\n                    },\n                },\n            );\n        },\n        validators: {\n            onSubmit: z.object({\n                name: z.string().min(2, \"Name must be at least 2 characters\"),\n                email: z.email(\"Invalid email address\"),\n                password: z.string().min(8, \"Password must be at least 8 characters\"),\n            }),\n        },\n    });\n\n    return (\n        <div className=\"mx-auto w-full mt-10 max-w-md p-6\">\n            <h1 className=\"mb-6 text-center text-3xl font-bold\">Create Account</h1>\n\n            <form\n                onSubmit={(e) => {\n                    e.preventDefault();\n                    e.stopPropagation();\n                    form.handleSubmit();\n                }}\n                className=\"space-y-4\"\n            >\n                <div>\n                    <form.Field name=\"name\">\n                        {(field) => (\n                            <div className=\"space-y-2\">\n                                <Label htmlFor={field.name}>Name</Label>\n                                <Input\n                                    id={field.name}\n                                    name={field.name}\n                                    value={field.state.value}\n                                    onBlur={field.handleBlur}\n                                    onChange={(e) => field.handleChange(e.target.value)}\n                                />\n                                {field.state.meta.errors.map((error, index) => (\n                                    <p key={\\`\\${field.name}-error-\\${index}\\`} className=\"text-red-500\">\n                                        {error?.message}\n                                    </p>\n                                ))}\n                            </div>\n                        )}\n                    </form.Field>\n                </div>\n\n                <div>\n                    <form.Field name=\"email\">\n                        {(field) => (\n                            <div className=\"space-y-2\">\n                                <Label htmlFor={field.name}>Email</Label>\n                                <Input\n                                    id={field.name}\n                                    name={field.name}\n                                    type=\"email\"\n                                    value={field.state.value}\n                                    onBlur={field.handleBlur}\n                                    onChange={(e) => field.handleChange(e.target.value)}\n                                />\n                                {field.state.meta.errors.map((error, index) => (\n                                    <p key={\\`\\${field.name}-error-\\${index}\\`} className=\"text-red-500\">\n                                        {error?.message}\n                                    </p>\n                                ))}\n                            </div>\n                        )}\n                    </form.Field>\n                </div>\n\n                <div>\n                    <form.Field name=\"password\">\n                        {(field) => (\n                            <div className=\"space-y-2\">\n                                <Label htmlFor={field.name}>Password</Label>\n                                <Input\n                                    id={field.name}\n                                    name={field.name}\n                                    type=\"password\"\n                                    value={field.state.value}\n                                    onBlur={field.handleBlur}\n                                    onChange={(e) => field.handleChange(e.target.value)}\n                                />\n                                {field.state.meta.errors.map((error, index) => (\n                                    <p key={\\`\\${field.name}-error-\\${index}\\`} className=\"text-red-500\">\n                                        {error?.message}\n                                    </p>\n                                ))}\n                            </div>\n                        )}\n                    </form.Field>\n                </div>\n\n                <form.Subscribe selector={(state) => ({ canSubmit: state.canSubmit, isSubmitting: state.isSubmitting })}>\n          {({ canSubmit, isSubmitting }) => (\n                        <Button\n                            type=\"submit\"\n                            className=\"w-full\"\n                            disabled={!canSubmit || isSubmitting}\n                        >\n                            {isSubmitting ? \"Submitting...\" : \"Sign Up\"}\n                        </Button>\n                    )}\n                </form.Subscribe>\n            </form>\n\n            <div className=\"mt-4 text-center\">\n                <Button\n                    variant=\"link\"\n                    onClick={onSwitchToSignIn}\n                    className=\"text-indigo-600 hover:text-indigo-800\"\n                >\n                    Already have an account? Sign In\n                </Button>\n            </div>\n        </div>\n    );\n}\n`],\n  [\"auth/better-auth/convex/web/react/tanstack-router/src/components/user-menu.tsx.hbs\", `import { useNavigate } from \"@tanstack/react-router\";\n\nimport {\n    DropdownMenu,\n    DropdownMenuContent,\n    DropdownMenuGroup,\n    DropdownMenuItem,\n    DropdownMenuLabel,\n    DropdownMenuSeparator,\n    DropdownMenuTrigger,\n} from \"@{{projectName}}/ui/components/dropdown-menu\";\nimport { authClient } from \"@/lib/auth-client\";\nimport { useQuery } from \"convex/react\";\nimport { api } from \"@{{projectName}}/backend/convex/_generated/api\";\n\nimport { Button } from \"@{{projectName}}/ui/components/button\";\n\nexport default function UserMenu() {\n    const navigate = useNavigate();\n    const user = useQuery(api.auth.getCurrentUser)\n\n    return (\n        <DropdownMenu>\n            <DropdownMenuTrigger render={<Button variant=\"outline\" />}>\n                {user?.name}\n            </DropdownMenuTrigger>\n            <DropdownMenuContent className=\"bg-card\">\n                <DropdownMenuGroup>\n                    <DropdownMenuLabel>My Account</DropdownMenuLabel>\n                    <DropdownMenuSeparator />\n                    <DropdownMenuItem>{user?.email}</DropdownMenuItem>\n                    <DropdownMenuItem\n                        variant=\"destructive\"\n                        onClick={() => {\n                            authClient.signOut({\n                                fetchOptions: {\n                                    onSuccess: () => {\n                                        navigate({\n                                            to: \"/dashboard\",\n                                        });\n                                    },\n                                },\n                            });\n                        }}\n                    >\n                        Sign Out\n                    </DropdownMenuItem>\n                </DropdownMenuGroup>\n            </DropdownMenuContent>\n        </DropdownMenu>\n    );\n}\n`],\n  [\"auth/better-auth/convex/web/react/tanstack-router/src/lib/auth-client.ts.hbs\", `import { createAuthClient } from \"better-auth/react\";\nimport {\n\tconvexClient,\n\tcrossDomainClient,\n} from \"@convex-dev/better-auth/client/plugins\";\nimport { env } from \"@{{projectName}}/env/web\";\n\nexport const authClient = createAuthClient({\n\tbaseURL: env.VITE_CONVEX_SITE_URL,\n\tplugins: [crossDomainClient(), convexClient()],\n});\n`],\n  [\"auth/better-auth/convex/web/react/tanstack-router/src/routes/dashboard.tsx.hbs\", `import SignInForm from \"@/components/sign-in-form\";\nimport SignUpForm from \"@/components/sign-up-form\";\nimport UserMenu from \"@/components/user-menu\";\nimport { api } from \"@{{projectName}}/backend/convex/_generated/api\";\nimport { createFileRoute } from \"@tanstack/react-router\";\nimport {\n  Authenticated,\n  AuthLoading,\n  Unauthenticated,\n  useQuery,\n} from \"convex/react\";\nimport { useState } from \"react\";\n\nexport const Route = createFileRoute(\"/dashboard\")({\n  component: RouteComponent,\n});\n\nfunction PrivateDashboardContent() {\n  const privateData = useQuery(api.privateData.get);\n\n  return (\n    <div>\n      <h1>Dashboard</h1>\n      <p>privateData: {privateData?.message}</p>\n      <UserMenu />\n    </div>\n  );\n}\n\nfunction RouteComponent() {\n  const [showSignIn, setShowSignIn] = useState(false);\n\n  return (\n    <>\n      <Authenticated>\n        <PrivateDashboardContent />\n      </Authenticated>\n      <Unauthenticated>\n        {showSignIn ? (\n          <SignInForm onSwitchToSignUp={() => setShowSignIn(false)} />\n        ) : (\n          <SignUpForm onSwitchToSignIn={() => setShowSignIn(true)} />\n        )}\n      </Unauthenticated>\n      <AuthLoading>\n        <div>Loading...</div>\n      </AuthLoading>\n    </>\n  );\n}\n`],\n  [\"auth/better-auth/convex/web/react/tanstack-start/src/components/sign-in-form.tsx.hbs\", `import { authClient } from \"@/lib/auth-client\";\nimport { useForm } from \"@tanstack/react-form\";\nimport { useNavigate } from \"@tanstack/react-router\";\nimport { toast } from \"sonner\";\nimport z from \"zod\";\nimport { Button } from \"@{{projectName}}/ui/components/button\";\nimport { Input } from \"@{{projectName}}/ui/components/input\";\nimport { Label } from \"@{{projectName}}/ui/components/label\";\n\nexport default function SignInForm({\n    onSwitchToSignUp,\n}: {\n    onSwitchToSignUp: () => void;\n}) {\n    const navigate = useNavigate({\n        from: \"/\",\n    });\n\n    const form = useForm({\n        defaultValues: {\n            email: \"\",\n            password: \"\",\n        },\n        onSubmit: async ({ value }) => {\n            await authClient.signIn.email(\n                {\n                    email: value.email,\n                    password: value.password,\n                },\n                {\n                    onSuccess: () => {\n                        navigate({\n                            to: \"/dashboard\",\n                        });\n                        toast.success(\"Sign in successful\");\n                    },\n                    onError: (error) => {\n                        toast.error(error.error.message || error.error.statusText);\n                    },\n                },\n            );\n        },\n        validators: {\n            onSubmit: z.object({\n                email: z.email(\"Invalid email address\"),\n                password: z.string().min(8, \"Password must be at least 8 characters\"),\n            }),\n        },\n    });\n\n    return (\n        <div className=\"mx-auto w-full mt-10 max-w-md p-6\">\n            <h1 className=\"mb-6 text-center text-3xl font-bold\">Welcome Back</h1>\n\n            <form\n                onSubmit={(e) => {\n                    e.preventDefault();\n                    e.stopPropagation();\n                    form.handleSubmit();\n                }}\n                className=\"space-y-4\"\n            >\n                <div>\n                    <form.Field name=\"email\">\n                        {(field) => (\n                            <div className=\"space-y-2\">\n                                <Label htmlFor={field.name}>Email</Label>\n                                <Input\n                                    id={field.name}\n                                    name={field.name}\n                                    type=\"email\"\n                                    value={field.state.value}\n                                    onBlur={field.handleBlur}\n                                    onChange={(e) => field.handleChange(e.target.value)}\n                                />\n                                {field.state.meta.errors.map((error) => (\n                                    <p key={error?.message} className=\"text-red-500\">\n                                        {error?.message}\n                                    </p>\n                                ))}\n                            </div>\n                        )}\n                    </form.Field>\n                </div>\n\n                <div>\n                    <form.Field name=\"password\">\n                        {(field) => (\n                            <div className=\"space-y-2\">\n                                <Label htmlFor={field.name}>Password</Label>\n                                <Input\n                                    id={field.name}\n                                    name={field.name}\n                                    type=\"password\"\n                                    value={field.state.value}\n                                    onBlur={field.handleBlur}\n                                    onChange={(e) => field.handleChange(e.target.value)}\n                                />\n                                {field.state.meta.errors.map((error) => (\n                                    <p key={error?.message} className=\"text-red-500\">\n                                        {error?.message}\n                                    </p>\n                                ))}\n                            </div>\n                        )}\n                    </form.Field>\n                </div>\n\n                <form.Subscribe selector={(state) => ({ canSubmit: state.canSubmit, isSubmitting: state.isSubmitting })}>\n          {({ canSubmit, isSubmitting }) => (\n                        <Button\n                            type=\"submit\"\n                            className=\"w-full\"\n                            disabled={!canSubmit || isSubmitting}\n                        >\n                            {isSubmitting ? \"Submitting...\" : \"Sign In\"}\n                        </Button>\n                    )}\n                </form.Subscribe>\n            </form>\n\n            <div className=\"mt-4 text-center\">\n                <Button\n                    variant=\"link\"\n                    onClick={onSwitchToSignUp}\n                    className=\"text-indigo-600 hover:text-indigo-800\"\n                >\n                    Need an account? Sign Up\n                </Button>\n            </div>\n        </div>\n    );\n}\n`],\n  [\"auth/better-auth/convex/web/react/tanstack-start/src/components/sign-up-form.tsx.hbs\", `import { authClient } from \"@/lib/auth-client\";\nimport { useForm } from \"@tanstack/react-form\";\nimport { useNavigate } from \"@tanstack/react-router\";\nimport { toast } from \"sonner\";\nimport z from \"zod\";\nimport { Button } from \"@{{projectName}}/ui/components/button\";\nimport { Input } from \"@{{projectName}}/ui/components/input\";\nimport { Label } from \"@{{projectName}}/ui/components/label\";\n\nexport default function SignUpForm({\n    onSwitchToSignIn,\n}: {\n    onSwitchToSignIn: () => void;\n}) {\n    const navigate = useNavigate({\n        from: \"/\",\n    });\n\n    const form = useForm({\n        defaultValues: {\n            email: \"\",\n            password: \"\",\n            name: \"\",\n        },\n        onSubmit: async ({ value }) => {\n            await authClient.signUp.email(\n                {\n                    email: value.email,\n                    password: value.password,\n                    name: value.name,\n                },\n                {\n                    onSuccess: () => {\n                        navigate({\n                            to: \"/dashboard\",\n                        });\n                        toast.success(\"Sign up successful\");\n                    },\n                    onError: (error) => {\n                        toast.error(error.error.message || error.error.statusText);\n                    },\n                },\n            );\n        },\n        validators: {\n            onSubmit: z.object({\n                name: z.string().min(2, \"Name must be at least 2 characters\"),\n                email: z.email(\"Invalid email address\"),\n                password: z.string().min(8, \"Password must be at least 8 characters\"),\n            }),\n        },\n    });\n\n    return (\n        <div className=\"mx-auto w-full mt-10 max-w-md p-6\">\n            <h1 className=\"mb-6 text-center text-3xl font-bold\">Create Account</h1>\n\n            <form\n                onSubmit={(e) => {\n                    e.preventDefault();\n                    e.stopPropagation();\n                    form.handleSubmit();\n                }}\n                className=\"space-y-4\"\n            >\n                <div>\n                    <form.Field name=\"name\">\n                        {(field) => (\n                            <div className=\"space-y-2\">\n                                <Label htmlFor={field.name}>Name</Label>\n                                <Input\n                                    id={field.name}\n                                    name={field.name}\n                                    value={field.state.value}\n                                    onBlur={field.handleBlur}\n                                    onChange={(e) => field.handleChange(e.target.value)}\n                                />\n                                {field.state.meta.errors.map((error) => (\n                                    <p key={error?.message} className=\"text-red-500\">\n                                        {error?.message}\n                                    </p>\n                                ))}\n                            </div>\n                        )}\n                    </form.Field>\n                </div>\n\n                <div>\n                    <form.Field name=\"email\">\n                        {(field) => (\n                            <div className=\"space-y-2\">\n                                <Label htmlFor={field.name}>Email</Label>\n                                <Input\n                                    id={field.name}\n                                    name={field.name}\n                                    type=\"email\"\n                                    value={field.state.value}\n                                    onBlur={field.handleBlur}\n                                    onChange={(e) => field.handleChange(e.target.value)}\n                                />\n                                {field.state.meta.errors.map((error) => (\n                                    <p key={error?.message} className=\"text-red-500\">\n                                        {error?.message}\n                                    </p>\n                                ))}\n                            </div>\n                        )}\n                    </form.Field>\n                </div>\n\n                <div>\n                    <form.Field name=\"password\">\n                        {(field) => (\n                            <div className=\"space-y-2\">\n                                <Label htmlFor={field.name}>Password</Label>\n                                <Input\n                                    id={field.name}\n                                    name={field.name}\n                                    type=\"password\"\n                                    value={field.state.value}\n                                    onBlur={field.handleBlur}\n                                    onChange={(e) => field.handleChange(e.target.value)}\n                                />\n                                {field.state.meta.errors.map((error) => (\n                                    <p key={error?.message} className=\"text-red-500\">\n                                        {error?.message}\n                                    </p>\n                                ))}\n                            </div>\n                        )}\n                    </form.Field>\n                </div>\n\n                <form.Subscribe selector={(state) => ({ canSubmit: state.canSubmit, isSubmitting: state.isSubmitting })}>\n          {({ canSubmit, isSubmitting }) => (\n                        <Button\n                            type=\"submit\"\n                            className=\"w-full\"\n                            disabled={!canSubmit || isSubmitting}\n                        >\n                            {isSubmitting ? \"Submitting...\" : \"Sign Up\"}\n                        </Button>\n                    )}\n                </form.Subscribe>\n            </form>\n\n            <div className=\"mt-4 text-center\">\n                <Button\n                    variant=\"link\"\n                    onClick={onSwitchToSignIn}\n                    className=\"text-indigo-600 hover:text-indigo-800\"\n                >\n                    Already have an account? Sign In\n                </Button>\n            </div>\n        </div>\n    );\n}\n`],\n  [\"auth/better-auth/convex/web/react/tanstack-start/src/components/user-menu.tsx.hbs\", `import {\n    DropdownMenu,\n    DropdownMenuContent,\n    DropdownMenuGroup,\n    DropdownMenuItem,\n    DropdownMenuLabel,\n    DropdownMenuSeparator,\n    DropdownMenuTrigger,\n} from \"@{{projectName}}/ui/components/dropdown-menu\";\nimport { authClient } from \"@/lib/auth-client\";\nimport { useQuery } from \"convex/react\";\nimport { api } from \"@{{projectName}}/backend/convex/_generated/api\";\n\nimport { Button } from \"@{{projectName}}/ui/components/button\";\n\nexport default function UserMenu() {\n    const user = useQuery(api.auth.getCurrentUser)\n\n    return (\n        <DropdownMenu>\n            <DropdownMenuTrigger render={<Button variant=\"outline\" />}>\n                {user?.name}\n            </DropdownMenuTrigger>\n            <DropdownMenuContent className=\"bg-card\">\n                <DropdownMenuGroup>\n                    <DropdownMenuLabel>My Account</DropdownMenuLabel>\n                    <DropdownMenuSeparator />\n                    <DropdownMenuItem>{user?.email}</DropdownMenuItem>\n                    <DropdownMenuItem\n                        variant=\"destructive\"\n                        onClick={() => {\n                            authClient.signOut({\n                                fetchOptions: {\n                                    onSuccess: () => {\n                                        location.reload();\n                                    },\n                                },\n                            });\n                        }}\n                    >\n                        Sign Out\n                    </DropdownMenuItem>\n                </DropdownMenuGroup>\n            </DropdownMenuContent>\n        </DropdownMenu>\n    );\n}\n`],\n  [\"auth/better-auth/convex/web/react/tanstack-start/src/lib/auth-client.ts.hbs\", `import { createAuthClient } from \"better-auth/react\";\nimport { convexClient } from \"@convex-dev/better-auth/client/plugins\";\n\nexport const authClient = createAuthClient({\n  plugins: [convexClient()],\n});`],\n  [\"auth/better-auth/convex/web/react/tanstack-start/src/lib/auth-server.ts.hbs\", `import { convexBetterAuthReactStart } from \"@convex-dev/better-auth/react-start\";\nimport { env } from \"@{{projectName}}/env/web\";\n\nexport const {\n\thandler,\n\tgetToken,\n\tfetchAuthQuery,\n\tfetchAuthMutation,\n\tfetchAuthAction,\n} = convexBetterAuthReactStart({\n\tconvexUrl: env.VITE_CONVEX_URL,\n\tconvexSiteUrl: env.VITE_CONVEX_SITE_URL,\n});\n`],\n  [\"auth/better-auth/convex/web/react/tanstack-start/src/routes/api/auth/$.ts.hbs\", `import { createFileRoute } from \"@tanstack/react-router\";\nimport { handler } from \"@/lib/auth-server\";\n\nexport const Route = createFileRoute(\"/api/auth/$\")({\n  server: {\n    handlers: {\n      GET: ({ request }) => handler(request),\n      POST: ({ request }) => handler(request),\n    },\n  },\n});\n`],\n  [\"auth/better-auth/convex/web/react/tanstack-start/src/routes/dashboard.tsx.hbs\", `import SignInForm from \"@/components/sign-in-form\";\nimport SignUpForm from \"@/components/sign-up-form\";\nimport UserMenu from \"@/components/user-menu\";\nimport { api } from \"@{{projectName}}/backend/convex/_generated/api\";\nimport { createFileRoute } from \"@tanstack/react-router\";\nimport {\n  Authenticated,\n  AuthLoading,\n  Unauthenticated,\n  useQuery,\n} from \"convex/react\";\nimport { useState } from \"react\";\n\nexport const Route = createFileRoute(\"/dashboard\")({\n  component: RouteComponent,\n});\n\nfunction RouteComponent() {\n  const [showSignIn, setShowSignIn] = useState(false);\n  const privateData = useQuery(api.privateData.get);\n\n  return (\n    <>\n      <Authenticated>\n        <div>\n          <h1>Dashboard</h1>\n          <p>privateData: {privateData?.message}</p>\n          <UserMenu />\n        </div>\n      </Authenticated>\n      <Unauthenticated>\n        {showSignIn ? (\n          <SignInForm onSwitchToSignUp={() => setShowSignIn(false)} />\n        ) : (\n          <SignUpForm onSwitchToSignIn={() => setShowSignIn(true)} />\n        )}\n      </Unauthenticated>\n      <AuthLoading>\n        <div>Loading...</div>\n      </AuthLoading>\n    </>\n  );\n}\n`],\n  [\"auth/better-auth/fullstack/astro/src/env.d.ts.hbs\", `/// <reference path=\"../.astro/types.d.ts\" />\n\ndeclare namespace App {\n  interface Locals {\n    user: import(\"better-auth\").User | null;\n    session: import(\"better-auth\").Session | null;\n  }\n}\n`],\n  [\"auth/better-auth/fullstack/astro/src/middleware.ts.hbs\", `{{#if (or (eq runtime \"workers\") (eq serverDeploy \"cloudflare\") (and (eq backend \"self\") (eq webDeploy \"cloudflare\")))}}\nimport { createAuth } from \"@{{projectName}}/auth\";\n{{else}}\nimport { auth } from \"@{{projectName}}/auth\";\n{{/if}}\nimport { defineMiddleware } from \"astro:middleware\";\n\nexport const onRequest = defineMiddleware(async (context, next) => {\n{{#if (or (eq runtime \"workers\") (eq serverDeploy \"cloudflare\") (and (eq backend \"self\") (eq webDeploy \"cloudflare\")))}}\n  const auth = createAuth();\n{{/if}}\n  const isAuthed = await auth.api.getSession({\n    headers: context.request.headers,\n  });\n\n  if (isAuthed) {\n    context.locals.user = isAuthed.user;\n    context.locals.session = isAuthed.session;\n  } else {\n    context.locals.user = null;\n    context.locals.session = null;\n  }\n\n  return next();\n});\n`],\n  [\"auth/better-auth/fullstack/astro/src/pages/api/auth/[...all].ts.hbs\", `{{#if (or (eq runtime \"workers\") (eq serverDeploy \"cloudflare\") (and (eq backend \"self\") (eq webDeploy \"cloudflare\")))}}\nimport { createAuth } from \"@{{projectName}}/auth\";\n{{else}}\nimport { auth } from \"@{{projectName}}/auth\";\n{{/if}}\nimport type { APIRoute } from \"astro\";\n\nexport const ALL: APIRoute = async (ctx) => {\n{{#if (or (eq runtime \"workers\") (eq serverDeploy \"cloudflare\") (and (eq backend \"self\") (eq webDeploy \"cloudflare\")))}}\n  const auth = createAuth();\n{{/if}}\n  return auth.handler(ctx.request);\n};\n`],\n  [\"auth/better-auth/fullstack/next/src/app/api/auth/[...all]/route.ts.hbs\", `{{#if (or (eq runtime \"workers\") (eq serverDeploy \"cloudflare\") (and (eq backend \"self\") (eq webDeploy \"cloudflare\")))}}\nimport { createAuth } from \"@{{projectName}}/auth\";\n{{else}}\nimport { auth } from \"@{{projectName}}/auth\";\n{{/if}}\nimport { toNextJsHandler } from \"better-auth/next-js\";\n\n{{#if (or (eq runtime \"workers\") (eq serverDeploy \"cloudflare\") (and (eq backend \"self\") (eq webDeploy \"cloudflare\")))}}\nexport async function GET(request: Request) {\n\treturn toNextJsHandler(createAuth()).GET(request);\n}\n\nexport async function POST(request: Request) {\n\treturn toNextJsHandler(createAuth()).POST(request);\n}\n{{else}}\nexport const { GET, POST } = toNextJsHandler(auth);\n{{/if}}\n`],\n  [\"auth/better-auth/fullstack/nuxt/server/api/auth/[...all].ts.hbs\", `{{#if (or (eq runtime \"workers\") (eq serverDeploy \"cloudflare\") (and (eq backend \"self\") (eq webDeploy \"cloudflare\")))}}\nimport { createAuth } from \"@{{projectName}}/auth\";\n{{else}}\nimport { auth } from \"@{{projectName}}/auth\";\n{{/if}}\n\nexport default defineEventHandler((event) => {\n{{#if (or (eq runtime \"workers\") (eq serverDeploy \"cloudflare\") (and (eq backend \"self\") (eq webDeploy \"cloudflare\")))}}\n  const auth = createAuth();\n{{/if}}\n  return auth.handler(toWebRequest(event));\n});\n`],\n  [\"auth/better-auth/fullstack/svelte/src/hooks.server.ts.hbs\", `{{#if (eq api \"orpc\")}}\nimport \"./lib/orpc.server\";\n{{/if}}\nimport { building } from \"$app/environment\";\n{{#if (or (eq runtime \"workers\") (eq serverDeploy \"cloudflare\") (and (eq backend \"self\") (eq webDeploy \"cloudflare\")))}}\nimport { createAuth } from \"@{{projectName}}/auth\";\n{{#if (and (eq backend \"self\") (eq webDeploy \"cloudflare\"))}}\nimport { env as localEnv } from \"@{{projectName}}/env/server\";\n{{/if}}\n{{else}}\nimport { auth } from \"@{{projectName}}/auth\";\n{{/if}}\nimport { svelteKitHandler } from \"better-auth/svelte-kit\";\nimport type { Handle } from \"@sveltejs/kit\";\n\nexport const handle: Handle = async ({ event, resolve }) => {\n{{#if (or (eq runtime \"workers\") (eq serverDeploy \"cloudflare\") (and (eq backend \"self\") (eq webDeploy \"cloudflare\")))}}\n{{#if (and (eq backend \"self\") (eq webDeploy \"cloudflare\"))}}\n\tif (building) {\n\t\treturn resolve(event);\n\t}\n\n\tconst authEnv = event.platform?.env ?? localEnv;\n\tconst authInstance = createAuth(authEnv);\n{{else}}\n\tconst authInstance = createAuth();\n{{/if}}\n{{else}}\n\tconst authInstance = auth;\n{{/if}}\n\n\treturn svelteKitHandler({\n\t\tevent,\n\t\tresolve,\n\t\tauth: authInstance,\n\t\tbuilding,\n\t});\n};\n`],\n  [\"auth/better-auth/fullstack/tanstack-start/src/routes/api/auth/$.ts.hbs\", `{{#if (or (eq runtime \"workers\") (eq serverDeploy \"cloudflare\") (and (eq backend \"self\") (eq webDeploy \"cloudflare\")))}}\nimport { createAuth } from '@{{projectName}}/auth'\n{{else}}\nimport { auth } from '@{{projectName}}/auth'\n{{/if}}\nimport { createFileRoute } from '@tanstack/react-router'\n\nexport const Route = createFileRoute('/api/auth/$')({\n  server: {\n    handlers: {\n      GET: ({ request }) => {\n        {{#if (or (eq runtime \"workers\") (eq serverDeploy \"cloudflare\") (and (eq backend \"self\") (eq webDeploy \"cloudflare\")))}}\n        const auth = createAuth()\n        {{/if}}\n        return auth.handler(request)\n      },\n      POST: ({ request }) => {\n        {{#if (or (eq runtime \"workers\") (eq serverDeploy \"cloudflare\") (and (eq backend \"self\") (eq webDeploy \"cloudflare\")))}}\n        const auth = createAuth()\n        {{/if}}\n        return auth.handler(request)\n      },\n    },\n  },\n})\n`],\n  [\"auth/better-auth/native/bare/app/(drawer)/index.tsx.hbs\", `import { View, Text, ScrollView, TouchableOpacity, StyleSheet } from \"react-native\";\nimport { Container } from \"@/components/container\";\nimport { useColorScheme } from \"@/lib/use-color-scheme\";\nimport { NAV_THEME } from \"@/lib/constants\";\nimport { authClient } from \"@/lib/auth-client\";\nimport { SignIn } from \"@/components/sign-in\";\nimport { SignUp } from \"@/components/sign-up\";\n{{#if (eq api \"orpc\")}}\nimport { useQuery } from \"@tanstack/react-query\";\nimport { queryClient, orpc } from \"@/utils/orpc\";\n{{/if}}\n{{#if (eq api \"trpc\")}}\nimport { useQuery } from \"@tanstack/react-query\";\nimport { queryClient, trpc } from \"@/utils/trpc\";\n{{/if}}\n\nexport default function Home() {\nconst { colorScheme } = useColorScheme();\nconst theme = colorScheme === \"dark\" ? NAV_THEME.dark : NAV_THEME.light;\n{{#if (eq api \"orpc\")}}\nconst healthCheck = useQuery(orpc.healthCheck.queryOptions());\nconst privateData = useQuery(orpc.privateData.queryOptions());\nconst isConnected = healthCheck?.data === \"OK\";\nconst isLoading = healthCheck?.isLoading;\n{{/if}}\n{{#if (eq api \"trpc\")}}\nconst healthCheck = useQuery(trpc.healthCheck.queryOptions());\nconst privateData = useQuery(trpc.privateData.queryOptions());\nconst isConnected = healthCheck?.data === \"OK\";\nconst isLoading = healthCheck?.isLoading;\n{{/if}}\nconst { data: session } = authClient.useSession();\n\nreturn (\n<Container>\n  <ScrollView style={styles.scrollView}>\n    <View style={styles.content}>\n      <Text style={[styles.title, { color: theme.text }]}>\n        BETTER T STACK\n      </Text>\n\n      {session?.user ? (\n      <View style={[styles.userCard, { backgroundColor: theme.card, borderColor: theme.border }]}>\n        <View style={styles.userHeader}>\n          <Text style={[styles.userText, { color: theme.text }]}>\n            Welcome, <Text style={styles.userName}>{session.user.name}</Text>\n          </Text>\n        </View>\n        <Text style={[styles.userEmail, { color: theme.text, opacity: 0.7 }]}>\n          {session.user.email}\n        </Text>\n        <TouchableOpacity style={[styles.signOutButton, { backgroundColor: theme.notification }]} onPress={()=> {\n          authClient.signOut();\n          {{#if (eq api \"orpc\")}}\n          queryClient.invalidateQueries();\n          {{/if}}\n          {{#if (eq api \"trpc\")}}\n          queryClient.invalidateQueries();\n          {{/if}}\n          }}\n          >\n          <Text style={styles.signOutText}>Sign Out</Text>\n        </TouchableOpacity>\n      </View>\n      ) : null}\n\n      {{#unless (eq api \"none\")}}\n      <View style={[styles.statusCard, { backgroundColor: theme.card, borderColor: theme.border }]}>\n        <Text style={[styles.cardTitle, { color: theme.text }]}>\n          System Status\n        </Text>\n        <View style={styles.statusRow}>\n          <View style={[styles.statusIndicator, { backgroundColor: isConnected ? \"#10b981\" : \"#ef4444\" }]} />\n          <View style={styles.statusContent}>\n            <Text style={[styles.statusTitle, { color: theme.text }]}>\n              {{#if (eq api \"orpc\")}}ORPC{{else}}TRPC{{/if}} Backend\n            </Text>\n            <Text style={[styles.statusText, { color: theme.text, opacity: 0.7 }]}>\n              {isLoading\n              ? \"Checking connection...\"\n              : isConnected\n              ? \"Connected to API\"\n              : \"API Disconnected\"}\n            </Text>\n          </View>\n        </View>\n      </View>\n\n      <View style={[styles.privateDataCard, { backgroundColor: theme.card, borderColor: theme.border }]}>\n        <Text style={[styles.cardTitle, { color: theme.text }]}>\n          Private Data\n        </Text>\n        {privateData && (\n        <Text style={[styles.privateDataText, { color: theme.text, opacity: 0.7 }]}>\n          {privateData.data?.message}\n        </Text>\n        )}\n      </View>\n      {{/unless}}\n\n      {!session?.user && (\n      <>\n        <SignIn />\n        <SignUp />\n      </>\n      )}\n    </View>\n  </ScrollView>\n</Container>\n);\n}\n\nconst styles = StyleSheet.create({\nscrollView: {\nflex: 1,\n},\ncontent: {\npadding: 16,\n},\ntitle: {\nfontSize: 24,\nfontWeight: \"bold\",\nmarginBottom: 16,\n},\nuserCard: {\nmarginBottom: 16,\npadding: 16,\nborderWidth: 1,\n},\nuserHeader: {\nmarginBottom: 8,\n},\nuserText: {\nfontSize: 16,\n},\nuserName: {\nfontWeight: \"bold\",\n},\nuserEmail: {\nfontSize: 14,\nmarginBottom: 12,\n},\nsignOutButton: {\npadding: 12,\n},\nsignOutText: {\ncolor: \"#ffffff\",\n},\nstatusCard: {\nmarginBottom: 16,\npadding: 16,\nborderWidth: 1,\n},\ncardTitle: {\nfontSize: 16,\nfontWeight: \"bold\",\nmarginBottom: 12,\n},\nstatusRow: {\nflexDirection: \"row\",\nalignItems: \"center\",\ngap: 8,\n},\nstatusIndicator: {\nheight: 8,\nwidth: 8,\n},\nstatusContent: {\nflex: 1,\n},\nstatusTitle: {\nfontSize: 14,\nfontWeight: \"bold\",\n},\nstatusText: {\nfontSize: 12,\n},\nprivateDataCard: {\nmarginBottom: 16,\npadding: 16,\nborderWidth: 1,\n},\nprivateDataText: {\nfontSize: 14,\n},\n});`],\n  [\"auth/better-auth/native/bare/components/sign-in.tsx.hbs\", `import { authClient } from \"@/lib/auth-client\";\n{{#if (eq api \"trpc\")}}\nimport { queryClient } from \"@/utils/trpc\";\n{{/if}}\n{{#if (eq api \"orpc\")}}\nimport { queryClient } from \"@/utils/orpc\";\n{{/if}}\nimport { useForm } from \"@tanstack/react-form\";\nimport { useState } from \"react\";\nimport {\n  ActivityIndicator,\n  StyleSheet,\n  Text,\n  TextInput,\n  TouchableOpacity,\n  View,\n} from \"react-native\";\nimport { NAV_THEME } from \"@/lib/constants\";\nimport { useColorScheme } from \"@/lib/use-color-scheme\";\nimport z from \"zod\";\n\nconst signInSchema = z.object({\n  email: z\n    .string()\n    .trim()\n    .min(1, \"Email is required\")\n    .email(\"Enter a valid email address\"),\n  password: z\n    .string()\n    .min(1, \"Password is required\")\n    .min(8, \"Use at least 8 characters\"),\n});\n\nfunction getErrorMessage(error: unknown): string | null {\n  if (!error) return null;\n\n  if (typeof error === \"string\") {\n    return error;\n  }\n\n  if (Array.isArray(error)) {\n    for (const issue of error) {\n      const message = getErrorMessage(issue);\n      if (message) {\n        return message;\n      }\n    }\n    return null;\n  }\n\n  if (typeof error === \"object\" && error !== null) {\n    const maybeError = error as { message?: unknown };\n    if (typeof maybeError.message === \"string\") {\n      return maybeError.message;\n    }\n  }\n\n  return null;\n}\n\nfunction SignIn() {\n  const { colorScheme } = useColorScheme();\n  const theme = colorScheme === \"dark\" ? NAV_THEME.dark : NAV_THEME.light;\n  const [error, setError] = useState<string | null>(null);\n\n  const form = useForm({\n    defaultValues: {\n      email: \"\",\n      password: \"\",\n    },\n    validators: {\n      onSubmit: signInSchema,\n    },\n    onSubmit: async ({ value, formApi }) => {\n      await authClient.signIn.email(\n        {\n          email: value.email.trim(),\n          password: value.password,\n        },\n        {\n          onError(error) {\n            setError(error.error?.message || \"Failed to sign in\");\n          },\n          onSuccess() {\n            setError(null);\n            formApi.reset();\n            {{#if (eq api \"orpc\")}}\n            queryClient.refetchQueries();\n            {{/if}}\n            {{#if (eq api \"trpc\")}}\n            queryClient.refetchQueries();\n            {{/if}}\n          },\n        },\n      );\n    },\n  });\n\n  return (\n    <View style={[styles.card, { backgroundColor: theme.card, borderColor: theme.border }]}>\n      <Text style={[styles.title, { color: theme.text }]}>Sign In</Text>\n\n      <form.Subscribe\n        selector={(state) => ({\n          isSubmitting: state.isSubmitting,\n          validationError: getErrorMessage(state.errorMap.onSubmit),\n        })}\n      >\n        {({ isSubmitting, validationError }) => {\n          const formError = error ?? validationError;\n\n          return (\n            <>\n              {formError ? (\n                <View style={[styles.errorContainer, { backgroundColor: theme.notification + \"20\" }]}>\n                  <Text style={[styles.errorText, { color: theme.notification }]}>{formError}</Text>\n                </View>\n              ) : null}\n\n              <form.Field name=\"email\">\n                {(field) => (\n                  <TextInput\n                    style={[\n                      styles.input,\n                      {\n                        color: theme.text,\n                        borderColor: theme.border,\n                        backgroundColor: theme.background,\n                      },\n                    ]}\n                    placeholder=\"Email\"\n                    placeholderTextColor={theme.text}\n                    value={field.state.value}\n                    onBlur={field.handleBlur}\n                    onChangeText={(value) => {\n                      field.handleChange(value);\n                      if (error) {\n                        setError(null);\n                      }\n                    }}\n                    keyboardType=\"email-address\"\n                    autoCapitalize=\"none\"\n                  />\n                )}\n              </form.Field>\n\n              <form.Field name=\"password\">\n                {(field) => (\n                  <TextInput\n                    style={[\n                      styles.input,\n                      {\n                        color: theme.text,\n                        borderColor: theme.border,\n                        backgroundColor: theme.background,\n                      },\n                    ]}\n                    placeholder=\"Password\"\n                    placeholderTextColor={theme.text}\n                    value={field.state.value}\n                    onBlur={field.handleBlur}\n                    onChangeText={(value) => {\n                      field.handleChange(value);\n                      if (error) {\n                        setError(null);\n                      }\n                    }}\n                    secureTextEntry\n                    onSubmitEditing={form.handleSubmit}\n                  />\n                )}\n              </form.Field>\n\n              <TouchableOpacity\n                onPress={form.handleSubmit}\n                disabled={isSubmitting}\n                style={[\n                  styles.button,\n                  {\n                    backgroundColor: theme.primary,\n                    opacity: isSubmitting ? 0.5 : 1,\n                  },\n                ]}\n              >\n                {isSubmitting ? (\n                  <ActivityIndicator size=\"small\" color=\"#ffffff\" />\n                ) : (\n                  <Text style={styles.buttonText}>Sign In</Text>\n                )}\n              </TouchableOpacity>\n            </>\n          );\n        }}\n      </form.Subscribe>\n    </View>\n  );\n}\n\nconst styles = StyleSheet.create({\n  card: {\n    marginTop: 16,\n    padding: 16,\n    borderWidth: 1,\n  },\n  title: {\n    fontSize: 18,\n    fontWeight: \"bold\",\n    marginBottom: 12,\n  },\n  errorContainer: {\n    marginBottom: 12,\n    padding: 8,\n  },\n  errorText: {\n    fontSize: 14,\n  },\n  input: {\n    borderWidth: 1,\n    padding: 12,\n    fontSize: 16,\n    marginBottom: 12,\n  },\n  button: {\n    padding: 12,\n    alignItems: \"center\",\n    justifyContent: \"center\",\n  },\n  buttonText: {\n    color: \"#ffffff\",\n    fontSize: 16,\n  },\n});\n\nexport { SignIn };\n`],\n  [\"auth/better-auth/native/bare/components/sign-up.tsx.hbs\", `import { authClient } from \"@/lib/auth-client\";\n{{#if (eq api \"trpc\")}}\nimport { queryClient } from \"@/utils/trpc\";\n{{/if}}\n{{#if (eq api \"orpc\")}}\nimport { queryClient } from \"@/utils/orpc\";\n{{/if}}\nimport { useForm } from \"@tanstack/react-form\";\nimport { useState } from \"react\";\nimport {\n  ActivityIndicator,\n  StyleSheet,\n  Text,\n  TextInput,\n  TouchableOpacity,\n  View,\n} from \"react-native\";\nimport { NAV_THEME } from \"@/lib/constants\";\nimport { useColorScheme } from \"@/lib/use-color-scheme\";\nimport z from \"zod\";\n\nconst signUpSchema = z.object({\n  name: z\n    .string()\n    .trim()\n    .min(1, \"Name is required\")\n    .min(2, \"Name must be at least 2 characters\"),\n  email: z\n    .string()\n    .trim()\n    .min(1, \"Email is required\")\n    .email(\"Enter a valid email address\"),\n  password: z\n    .string()\n    .min(1, \"Password is required\")\n    .min(8, \"Use at least 8 characters\"),\n});\n\nfunction getErrorMessage(error: unknown): string | null {\n  if (!error) return null;\n\n  if (typeof error === \"string\") {\n    return error;\n  }\n\n  if (Array.isArray(error)) {\n    for (const issue of error) {\n      const message = getErrorMessage(issue);\n      if (message) {\n        return message;\n      }\n    }\n    return null;\n  }\n\n  if (typeof error === \"object\" && error !== null) {\n    const maybeError = error as { message?: unknown };\n    if (typeof maybeError.message === \"string\") {\n      return maybeError.message;\n    }\n  }\n\n  return null;\n}\n\nfunction SignUp() {\n  const { colorScheme } = useColorScheme();\n  const theme = colorScheme === \"dark\" ? NAV_THEME.dark : NAV_THEME.light;\n  const [error, setError] = useState<string | null>(null);\n\n  const form = useForm({\n    defaultValues: {\n      name: \"\",\n      email: \"\",\n      password: \"\",\n    },\n    validators: {\n      onSubmit: signUpSchema,\n    },\n    onSubmit: async ({ value, formApi }) => {\n      await authClient.signUp.email(\n        {\n          name: value.name.trim(),\n          email: value.email.trim(),\n          password: value.password,\n        },\n        {\n          onError(error) {\n            setError(error.error?.message || \"Failed to sign up\");\n          },\n          onSuccess() {\n            setError(null);\n            formApi.reset();\n            {{#if (eq api \"orpc\")}}\n            queryClient.refetchQueries();\n            {{/if}}\n            {{#if (eq api \"trpc\")}}\n            queryClient.refetchQueries();\n            {{/if}}\n          },\n        },\n      );\n    },\n  });\n\n  return (\n    <View style={[styles.card, { backgroundColor: theme.card, borderColor: theme.border }]}>\n      <Text style={[styles.title, { color: theme.text }]}>Create Account</Text>\n\n      <form.Subscribe\n        selector={(state) => ({\n          isSubmitting: state.isSubmitting,\n          validationError: getErrorMessage(state.errorMap.onSubmit),\n        })}\n      >\n        {({ isSubmitting, validationError }) => {\n          const formError = error ?? validationError;\n\n          return (\n            <>\n              {formError ? (\n                <View style={[styles.errorContainer, { backgroundColor: theme.notification + \"20\" }]}>\n                  <Text style={[styles.errorText, { color: theme.notification }]}>{formError}</Text>\n                </View>\n              ) : null}\n\n              <form.Field name=\"name\">\n                {(field) => (\n                  <TextInput\n                    style={[\n                      styles.input,\n                      {\n                        color: theme.text,\n                        borderColor: theme.border,\n                        backgroundColor: theme.background,\n                      },\n                    ]}\n                    placeholder=\"Name\"\n                    placeholderTextColor={theme.text}\n                    value={field.state.value}\n                    onBlur={field.handleBlur}\n                    onChangeText={(value) => {\n                      field.handleChange(value);\n                      if (error) {\n                        setError(null);\n                      }\n                    }}\n                  />\n                )}\n              </form.Field>\n\n              <form.Field name=\"email\">\n                {(field) => (\n                  <TextInput\n                    style={[\n                      styles.input,\n                      {\n                        color: theme.text,\n                        borderColor: theme.border,\n                        backgroundColor: theme.background,\n                      },\n                    ]}\n                    placeholder=\"Email\"\n                    placeholderTextColor={theme.text}\n                    value={field.state.value}\n                    onBlur={field.handleBlur}\n                    onChangeText={(value) => {\n                      field.handleChange(value);\n                      if (error) {\n                        setError(null);\n                      }\n                    }}\n                    keyboardType=\"email-address\"\n                    autoCapitalize=\"none\"\n                  />\n                )}\n              </form.Field>\n\n              <form.Field name=\"password\">\n                {(field) => (\n                  <TextInput\n                    style={[\n                      styles.input,\n                      {\n                        color: theme.text,\n                        borderColor: theme.border,\n                        backgroundColor: theme.background,\n                      },\n                    ]}\n                    placeholder=\"Password\"\n                    placeholderTextColor={theme.text}\n                    value={field.state.value}\n                    onBlur={field.handleBlur}\n                    onChangeText={(value) => {\n                      field.handleChange(value);\n                      if (error) {\n                        setError(null);\n                      }\n                    }}\n                    secureTextEntry\n                    onSubmitEditing={form.handleSubmit}\n                  />\n                )}\n              </form.Field>\n\n              <TouchableOpacity\n                onPress={form.handleSubmit}\n                disabled={isSubmitting}\n                style={[\n                  styles.button,\n                  {\n                    backgroundColor: theme.primary,\n                    opacity: isSubmitting ? 0.5 : 1,\n                  },\n                ]}\n              >\n                {isSubmitting ? (\n                  <ActivityIndicator size=\"small\" color=\"#ffffff\" />\n                ) : (\n                  <Text style={styles.buttonText}>Sign Up</Text>\n                )}\n              </TouchableOpacity>\n            </>\n          );\n        }}\n      </form.Subscribe>\n    </View>\n  );\n}\n\nconst styles = StyleSheet.create({\n  card: {\n    marginTop: 16,\n    padding: 16,\n    borderWidth: 1,\n  },\n  title: {\n    fontSize: 18,\n    fontWeight: \"bold\",\n    marginBottom: 12,\n  },\n  errorContainer: {\n    marginBottom: 12,\n    padding: 8,\n  },\n  errorText: {\n    fontSize: 14,\n  },\n  input: {\n    borderWidth: 1,\n    padding: 12,\n    fontSize: 16,\n    marginBottom: 12,\n  },\n  button: {\n    padding: 12,\n    alignItems: \"center\",\n    justifyContent: \"center\",\n  },\n  buttonText: {\n    color: \"#ffffff\",\n    fontSize: 16,\n  },\n});\n\nexport { SignUp };\n`],\n  [\"auth/better-auth/native/base/lib/auth-client.ts.hbs\", `import { expoClient } from \"@better-auth/expo/client\";\nimport { createAuthClient } from \"better-auth/react\";\nimport * as SecureStore from \"expo-secure-store\";\nimport Constants from \"expo-constants\";\nimport { env } from \"@{{projectName}}/env/native\";\n\nexport const authClient = createAuthClient({\n\tbaseURL: env.EXPO_PUBLIC_SERVER_URL,\n\tplugins: [\n\t\texpoClient({\n\t\t\tscheme: Constants.expoConfig?.scheme as string,\n\t\t\tstoragePrefix: Constants.expoConfig?.scheme as string,\n\t\t\tstorage: SecureStore,\n\t\t}),\n\t],\n});\n`],\n  [\"auth/better-auth/native/unistyles/app/(drawer)/index.tsx.hbs\", `import { authClient } from \"@/lib/auth-client\";\nimport { ScrollView, Text, TouchableOpacity, View } from \"react-native\";\nimport { StyleSheet } from \"react-native-unistyles\";\n\nimport { Container } from \"@/components/container\";\nimport { SignIn } from \"@/components/sign-in\";\nimport { SignUp } from \"@/components/sign-up\";\n{{#if (eq api \"orpc\")}}\nimport { useQuery } from \"@tanstack/react-query\";\nimport { queryClient, orpc } from \"@/utils/orpc\";\n{{/if}}\n{{#if (eq api \"trpc\")}}\nimport { useQuery } from \"@tanstack/react-query\";\nimport { queryClient, trpc } from \"@/utils/trpc\";\n{{/if}}\n\nexport default function Home() {\n    {{#if (eq api \"orpc\")}}\n    const healthCheck = useQuery(orpc.healthCheck.queryOptions());\n    const privateData = useQuery(orpc.privateData.queryOptions());\n    {{/if}}\n    {{#if (eq api \"trpc\")}}\n    const healthCheck = useQuery(trpc.healthCheck.queryOptions());\n    const privateData = useQuery(trpc.privateData.queryOptions());\n    {{/if}}\n  const { data: session } = authClient.useSession();\n\n  return (\n    <Container>\n      <ScrollView>\n        <View style={styles.pageContainer}>\n          <Text style={styles.headerTitle}>BETTER T STACK</Text>\n          {session?.user ? (\n            <View style={styles.sessionInfoCard}>\n              <View style={styles.sessionUserRow}>\n                <Text style={styles.welcomeText}>\n                  Welcome,{\" \"}\n                  <Text style={styles.userNameText}>{session.user.name}</Text>\n                </Text>\n              </View>\n              <Text style={styles.emailText}>{session.user.email}</Text>\n\n              <TouchableOpacity\n                style={styles.signOutButton}\n                onPress={() => {\n                  authClient.signOut();\n                  {{#if (eq api \"orpc\")}}\n                  queryClient.invalidateQueries();\n                  {{/if}}\n                  {{#if (eq api \"trpc\")}}\n                  queryClient.invalidateQueries();\n                  {{/if}}\n                }}\n              >\n                <Text style={styles.signOutButtonText}>Sign Out</Text>\n              </TouchableOpacity>\n            </View>\n          ) : null}\n          {{#unless (eq api \"none\")}}\n          <View style={styles.apiStatusCard}>\n            <Text style={styles.cardTitle}>API Status</Text>\n            <View style={styles.apiStatusRow}>\n              <View\n                style={[\n                  styles.statusIndicatorDot,\n                  healthCheck.data\n                    ? styles.statusIndicatorGreen\n                    : styles.statusIndicatorRed,\n                ]}\n              />\n              <Text style={styles.mutedText}>\n                {healthCheck.isLoading\n                  ? \"Checking...\"\n                  : healthCheck.data\n                    ? \"Connected to API\"\n                    : \"API Disconnected\"}\n              </Text>\n            </View>\n          </View>\n          <View style={styles.privateDataCard}>\n            <Text style={styles.cardTitle}>Private Data</Text>\n            {privateData && (\n              <View>\n                <Text style={styles.mutedText}>\n                  {privateData.data?.message}\n                </Text>\n              </View>\n            )}\n          </View>\n          {{/unless}}\n          {!session?.user && (\n            <>\n              <SignIn />\n              <SignUp />\n            </>\n          )}\n        </View>\n      </ScrollView>\n    </Container>\n  );\n}\n\nconst styles = StyleSheet.create((theme) => ({\n  pageContainer: {\n    paddingHorizontal: 8,\n  },\n  headerTitle: {\n    color: theme?.colors?.typography,\n    fontSize: 30,\n    fontWeight: \"bold\",\n    marginBottom: 16,\n  },\n  sessionInfoCard: {\n    marginBottom: 24,\n    padding: 16,\n    borderRadius: 8,\n    borderWidth: 1,\n    borderColor: theme?.colors?.border,\n  },\n  sessionUserRow: {\n    flexDirection: \"row\",\n    justifyContent: \"space-between\",\n    alignItems: \"center\",\n    marginBottom: 8,\n  },\n  welcomeText: {\n    color: theme?.colors?.typography,\n    fontSize: 16,\n  },\n  userNameText: {\n    fontWeight: \"500\",\n    color: theme?.colors?.typography,\n  },\n  emailText: {\n    color: theme?.colors?.typography,\n    fontSize: 14,\n    marginBottom: 16,\n  },\n  signOutButton: {\n    backgroundColor: theme?.colors?.destructive,\n    paddingVertical: 8,\n    paddingHorizontal: 16,\n    borderRadius: 6,\n    alignSelf: \"flex-start\",\n  },\n  signOutButtonText: {\n    fontWeight: \"500\",\n  },\n  apiStatusCard: {\n    marginBottom: 24,\n    borderRadius: 8,\n    borderWidth: 1,\n    borderColor: theme?.colors?.border,\n    padding: 16,\n  },\n  cardTitle: {\n    marginBottom: 12,\n    fontWeight: \"500\",\n    color: theme?.colors?.typography,\n  },\n  apiStatusRow: {\n    flexDirection: \"row\",\n    alignItems: \"center\",\n    gap: 8,\n  },\n  statusIndicatorDot: {\n    height: 12,\n    width: 12,\n    borderRadius: 9999,\n  },\n  statusIndicatorGreen: {\n    backgroundColor: theme.colors.success,\n  },\n  statusIndicatorRed: {\n    backgroundColor: theme.colors.destructive,\n  },\n  mutedText: {\n    color: theme?.colors?.typography,\n  },\n  privateDataCard: {\n    marginBottom: 24,\n    borderRadius: 8,\n    borderWidth: 1,\n    borderColor: theme?.colors?.border,\n    padding: 16,\n  },\n}));\n`],\n  [\"auth/better-auth/native/unistyles/components/sign-in.tsx.hbs\", `import { authClient } from \"@/lib/auth-client\";\n{{#if (eq api \"trpc\")}}\nimport { queryClient } from \"@/utils/trpc\";\n{{/if}}\n{{#if (eq api \"orpc\")}}\nimport { queryClient } from \"@/utils/orpc\";\n{{/if}}\nimport { useForm } from \"@tanstack/react-form\";\nimport { useState } from \"react\";\nimport {\n  ActivityIndicator,\n  Text,\n  TextInput,\n  TouchableOpacity,\n  View,\n} from \"react-native\";\nimport { StyleSheet } from \"react-native-unistyles\";\nimport z from \"zod\";\n\nconst signInSchema = z.object({\n  email: z\n    .string()\n    .trim()\n    .min(1, \"Email is required\")\n    .email(\"Enter a valid email address\"),\n  password: z\n    .string()\n    .min(1, \"Password is required\")\n    .min(8, \"Use at least 8 characters\"),\n});\n\nfunction getErrorMessage(error: unknown): string | null {\n  if (!error) return null;\n\n  if (typeof error === \"string\") {\n    return error;\n  }\n\n  if (Array.isArray(error)) {\n    for (const issue of error) {\n      const message = getErrorMessage(issue);\n      if (message) {\n        return message;\n      }\n    }\n    return null;\n  }\n\n  if (typeof error === \"object\" && error !== null) {\n    const maybeError = error as { message?: unknown };\n    if (typeof maybeError.message === \"string\") {\n      return maybeError.message;\n    }\n  }\n\n  return null;\n}\n\nexport function SignIn() {\n  const [error, setError] = useState<string | null>(null);\n\n  const form = useForm({\n    defaultValues: {\n      email: \"\",\n      password: \"\",\n    },\n    validators: {\n      onSubmit: signInSchema,\n    },\n    onSubmit: async ({ value, formApi }) => {\n      await authClient.signIn.email(\n        {\n          email: value.email.trim(),\n          password: value.password,\n        },\n        {\n          onError(error) {\n            setError(error.error?.message || \"Failed to sign in\");\n          },\n          onSuccess() {\n            setError(null);\n            formApi.reset();\n            {{#if (eq api \"orpc\")}}\n            queryClient.refetchQueries();\n            {{/if}}\n            {{#if (eq api \"trpc\")}}\n            queryClient.refetchQueries();\n            {{/if}}\n          },\n        },\n      );\n    },\n  });\n\n  return (\n    <View style={styles.container}>\n      <Text style={styles.title}>Sign In</Text>\n\n      <form.Subscribe\n        selector={(state) => ({\n          isSubmitting: state.isSubmitting,\n          validationError: getErrorMessage(state.errorMap.onSubmit),\n        })}\n      >\n        {({ isSubmitting, validationError }) => {\n          const formError = error ?? validationError;\n\n          return (\n            <>\n              {formError ? (\n                <View style={styles.errorContainer}>\n                  <Text style={styles.errorText}>{formError}</Text>\n                </View>\n              ) : null}\n\n              <form.Field name=\"email\">\n                {(field) => (\n                  <TextInput\n                    style={styles.input}\n                    placeholder=\"Email\"\n                    value={field.state.value}\n                    onBlur={field.handleBlur}\n                    onChangeText={(value) => {\n                      field.handleChange(value);\n                      if (error) {\n                        setError(null);\n                      }\n                    }}\n                    keyboardType=\"email-address\"\n                    autoCapitalize=\"none\"\n                  />\n                )}\n              </form.Field>\n\n              <form.Field name=\"password\">\n                {(field) => (\n                  <TextInput\n                    style={styles.input}\n                    placeholder=\"Password\"\n                    value={field.state.value}\n                    onBlur={field.handleBlur}\n                    onChangeText={(value) => {\n                      field.handleChange(value);\n                      if (error) {\n                        setError(null);\n                      }\n                    }}\n                    secureTextEntry\n                    onSubmitEditing={form.handleSubmit}\n                  />\n                )}\n              </form.Field>\n\n              <TouchableOpacity\n                onPress={form.handleSubmit}\n                disabled={isSubmitting}\n                style={styles.button}\n              >\n                {isSubmitting ? (\n                  <ActivityIndicator size=\"small\" color=\"#fff\" />\n                ) : (\n                  <Text style={styles.buttonText}>Sign In</Text>\n                )}\n              </TouchableOpacity>\n            </>\n          );\n        }}\n      </form.Subscribe>\n    </View>\n  );\n}\n\nconst styles = StyleSheet.create((theme) => ({\n  container: {\n    marginTop: 24,\n    padding: 16,\n    borderRadius: 8,\n    borderWidth: 1,\n    borderColor: theme.colors.border,\n  },\n  title: {\n    fontSize: 18,\n    fontWeight: \"600\",\n    color: theme.colors.typography,\n    marginBottom: 16,\n  },\n  errorContainer: {\n    marginBottom: 16,\n    padding: 12,\n    borderRadius: 6,\n  },\n  errorText: {\n    color: theme.colors.destructive,\n    fontSize: 14,\n  },\n  input: {\n    marginBottom: 12,\n    padding: 16,\n    borderRadius: 6,\n    color: theme.colors.typography,\n    borderWidth: 1,\n    borderColor: theme.colors.border,\n  },\n  button: {\n    backgroundColor: theme.colors.primary,\n    padding: 16,\n    borderRadius: 6,\n    flexDirection: \"row\",\n    justifyContent: \"center\",\n    alignItems: \"center\",\n  },\n  buttonText: {\n    fontWeight: \"500\",\n  },\n}));\n`],\n  [\"auth/better-auth/native/unistyles/components/sign-up.tsx.hbs\", `import { authClient } from \"@/lib/auth-client\";\n{{#if (eq api \"trpc\")}}\nimport { queryClient } from \"@/utils/trpc\";\n{{/if}}\n{{#if (eq api \"orpc\")}}\nimport { queryClient } from \"@/utils/orpc\";\n{{/if}}\nimport { useForm } from \"@tanstack/react-form\";\nimport { useState } from \"react\";\nimport {\n  ActivityIndicator,\n  Text,\n  TextInput,\n  TouchableOpacity,\n  View,\n} from \"react-native\";\nimport { StyleSheet } from \"react-native-unistyles\";\nimport z from \"zod\";\n\nconst signUpSchema = z.object({\n  name: z\n    .string()\n    .trim()\n    .min(1, \"Name is required\")\n    .min(2, \"Name must be at least 2 characters\"),\n  email: z\n    .string()\n    .trim()\n    .min(1, \"Email is required\")\n    .email(\"Enter a valid email address\"),\n  password: z\n    .string()\n    .min(1, \"Password is required\")\n    .min(8, \"Use at least 8 characters\"),\n});\n\nfunction getErrorMessage(error: unknown): string | null {\n  if (!error) return null;\n\n  if (typeof error === \"string\") {\n    return error;\n  }\n\n  if (Array.isArray(error)) {\n    for (const issue of error) {\n      const message = getErrorMessage(issue);\n      if (message) {\n        return message;\n      }\n    }\n    return null;\n  }\n\n  if (typeof error === \"object\" && error !== null) {\n    const maybeError = error as { message?: unknown };\n    if (typeof maybeError.message === \"string\") {\n      return maybeError.message;\n    }\n  }\n\n  return null;\n}\n\nexport function SignUp() {\n  const [error, setError] = useState<string | null>(null);\n\n  const form = useForm({\n    defaultValues: {\n      name: \"\",\n      email: \"\",\n      password: \"\",\n    },\n    validators: {\n      onSubmit: signUpSchema,\n    },\n    onSubmit: async ({ value, formApi }) => {\n      await authClient.signUp.email(\n        {\n          name: value.name.trim(),\n          email: value.email.trim(),\n          password: value.password,\n        },\n        {\n          onError(error) {\n            setError(error.error?.message || \"Failed to sign up\");\n          },\n          onSuccess() {\n            setError(null);\n            formApi.reset();\n            {{#if (eq api \"orpc\")}}\n            queryClient.refetchQueries();\n            {{/if}}\n            {{#if (eq api \"trpc\")}}\n            queryClient.refetchQueries();\n            {{/if}}\n          },\n        },\n      );\n    },\n  });\n\n  return (\n    <View style={styles.container}>\n      <Text style={styles.title}>Create Account</Text>\n\n      <form.Subscribe\n        selector={(state) => ({\n          isSubmitting: state.isSubmitting,\n          validationError: getErrorMessage(state.errorMap.onSubmit),\n        })}\n      >\n        {({ isSubmitting, validationError }) => {\n          const formError = error ?? validationError;\n\n          return (\n            <>\n              {formError ? (\n                <View style={styles.errorContainer}>\n                  <Text style={styles.errorText}>{formError}</Text>\n                </View>\n              ) : null}\n\n              <form.Field name=\"name\">\n                {(field) => (\n                  <TextInput\n                    style={styles.input}\n                    placeholder=\"Name\"\n                    value={field.state.value}\n                    onBlur={field.handleBlur}\n                    onChangeText={(value) => {\n                      field.handleChange(value);\n                      if (error) {\n                        setError(null);\n                      }\n                    }}\n                  />\n                )}\n              </form.Field>\n\n              <form.Field name=\"email\">\n                {(field) => (\n                  <TextInput\n                    style={styles.input}\n                    placeholder=\"Email\"\n                    value={field.state.value}\n                    onBlur={field.handleBlur}\n                    onChangeText={(value) => {\n                      field.handleChange(value);\n                      if (error) {\n                        setError(null);\n                      }\n                    }}\n                    keyboardType=\"email-address\"\n                    autoCapitalize=\"none\"\n                  />\n                )}\n              </form.Field>\n\n              <form.Field name=\"password\">\n                {(field) => (\n                  <TextInput\n                    style={styles.inputLast}\n                    placeholder=\"Password\"\n                    value={field.state.value}\n                    onBlur={field.handleBlur}\n                    onChangeText={(value) => {\n                      field.handleChange(value);\n                      if (error) {\n                        setError(null);\n                      }\n                    }}\n                    secureTextEntry\n                    onSubmitEditing={form.handleSubmit}\n                  />\n                )}\n              </form.Field>\n\n              <TouchableOpacity\n                onPress={form.handleSubmit}\n                disabled={isSubmitting}\n                style={styles.button}\n              >\n                {isSubmitting ? (\n                  <ActivityIndicator size=\"small\" color=\"#fff\" />\n                ) : (\n                  <Text style={styles.buttonText}>Sign Up</Text>\n                )}\n              </TouchableOpacity>\n            </>\n          );\n        }}\n      </form.Subscribe>\n    </View>\n  );\n}\n\nconst styles = StyleSheet.create((theme) => ({\n  container: {\n    marginTop: 24,\n    padding: 16,\n    borderRadius: 8,\n    borderWidth: 1,\n    borderColor: theme.colors.border,\n  },\n  title: {\n    fontSize: 18,\n    fontWeight: \"600\",\n    color: theme.colors.typography,\n    marginBottom: 16,\n  },\n  errorContainer: {\n    marginBottom: 16,\n    padding: 12,\n    borderRadius: 6,\n  },\n  errorText: {\n    color: theme.colors.destructive,\n    fontSize: 14,\n  },\n  input: {\n    marginBottom: 12,\n    padding: 16,\n    borderRadius: 6,\n    color: theme.colors.typography,\n    borderWidth: 1,\n    borderColor: theme.colors.border,\n  },\n  inputLast: {\n    marginBottom: 16,\n    padding: 16,\n    borderRadius: 6,\n    color: theme.colors.typography,\n    borderWidth: 1,\n    borderColor: theme.colors.border,\n  },\n  button: {\n    backgroundColor: theme.colors.primary,\n    padding: 16,\n    borderRadius: 6,\n    flexDirection: \"row\",\n    justifyContent: \"center\",\n    alignItems: \"center\",\n  },\n  buttonText: {\n    fontWeight: \"500\",\n  },\n}));\n`],\n  [\"auth/better-auth/native/uniwind/app/(drawer)/index.tsx.hbs\", `import { Text, View, Pressable } from \"react-native\";\nimport { Container } from \"@/components/container\";\nimport { authClient } from \"@/lib/auth-client\";\nimport { Ionicons } from \"@expo/vector-icons\";\nimport { Card, Chip, useThemeColor } from \"heroui-native\";\nimport { SignIn } from \"@/components/sign-in\";\nimport { SignUp } from \"@/components/sign-up\";\n{{#if (eq api \"orpc\")}}\nimport { useQuery } from \"@tanstack/react-query\";\nimport { queryClient, orpc } from \"@/utils/orpc\";\n{{/if}}\n{{#if (eq api \"trpc\")}}\nimport { useQuery } from \"@tanstack/react-query\";\nimport { queryClient, trpc } from \"@/utils/trpc\";\n{{/if}}\n\nexport default function Home() {\n{{#if (eq api \"orpc\")}}\nconst healthCheck = useQuery(orpc.healthCheck.queryOptions());\nconst privateData = useQuery(orpc.privateData.queryOptions());\nconst isConnected = healthCheck?.data === \"OK\";\nconst isLoading = healthCheck?.isLoading;\n{{/if}}\n{{#if (eq api \"trpc\")}}\nconst healthCheck = useQuery(trpc.healthCheck.queryOptions());\nconst privateData = useQuery(trpc.privateData.queryOptions());\nconst isConnected = healthCheck?.data === \"OK\";\nconst isLoading = healthCheck?.isLoading;\n{{/if}}\nconst { data: session } = authClient.useSession();\n\nconst mutedColor = useThemeColor(\"muted\");\nconst successColor = useThemeColor(\"success\");\nconst dangerColor = useThemeColor(\"danger\");\nconst foregroundColor = useThemeColor(\"foreground\");\n\nreturn (\n<Container className=\"p-6\">\n  <View className=\"py-4 mb-6\">\n    <Text className=\"text-4xl font-bold text-foreground mb-2\">\n      BETTER T STACK\n    </Text>\n  </View>\n\n  {session?.user ? (\n  <Card variant=\"secondary\" className=\"mb-6 p-4\">\n    <Text className=\"text-foreground text-base mb-2\">\n      Welcome, <Text className=\"font-medium\">{session.user.name}</Text>\n    </Text>\n    <Text className=\"text-muted text-sm mb-4\">\n      {session.user.email}\n    </Text>\n    <Pressable className=\"bg-danger py-3 px-4 rounded-lg self-start active:opacity-70\" onPress={()=> {\n      authClient.signOut();\n      {{#if (eq api \"orpc\")}}\n      queryClient.invalidateQueries();\n      {{/if}}\n      {{#if (eq api \"trpc\")}}\n      queryClient.invalidateQueries();\n      {{/if}}\n      }}\n      >\n      <Text className=\"text-foreground font-medium\">Sign Out</Text>\n    </Pressable>\n  </Card>\n  ) : null}\n\n  {{#unless (eq api \"none\")}}\n  <Card variant=\"secondary\" className=\"p-6\">\n    <View className=\"flex-row items-center justify-between mb-4\">\n      <Card.Title>System Status</Card.Title>\n      <Chip variant=\"secondary\" color={isConnected ? \"success\" : \"danger\" } size=\"sm\">\n        <Chip.Label>{isConnected ? \"LIVE\" : \"OFFLINE\"}</Chip.Label>\n      </Chip>\n    </View>\n\n    <Card className=\"p-4\">\n      <View className=\"flex-row items-center\">\n        <View className={\\`w-3 h-3 rounded-full mr-3 \\${isConnected ? \"bg-success\" : \"bg-muted\" }\\`} />\n        <View className=\"flex-1\">\n          <Text className=\"text-foreground font-medium mb-1\">\n            {{#if (eq api \"orpc\")}}ORPC{{else}}TRPC{{/if}} Backend\n          </Text>\n          <Card.Description>\n            {isLoading\n            ? \"Checking connection...\"\n            : isConnected\n            ? \"Connected to API\"\n            : \"API Disconnected\"}\n          </Card.Description>\n        </View>\n        {isLoading && (\n        <Ionicons name=\"hourglass-outline\" size={20} color={mutedColor} />\n        )}\n        {!isLoading && isConnected && (\n        <Ionicons name=\"checkmark-circle\" size={20} color={successColor} />\n        )}\n        {!isLoading && !isConnected && (\n        <Ionicons name=\"close-circle\" size={20} color={dangerColor} />\n        )}\n      </View>\n    </Card>\n  </Card>\n\n  <Card variant=\"secondary\" className=\"mt-6 p-4\">\n    <Card.Title className=\"mb-3\">Private Data</Card.Title>\n    {privateData && (\n    <Card.Description>\n      {privateData.data?.message}\n    </Card.Description>\n    )}\n  </Card>\n  {{/unless}}\n\n  {!session?.user && (\n  <>\n    <SignIn />\n    <SignUp />\n  </>\n  )}\n</Container>\n);\n}`],\n  [\"auth/better-auth/native/uniwind/components/sign-in.tsx.hbs\", `import { authClient } from \"@/lib/auth-client\";\n{{#if (eq api \"trpc\")}}\nimport { queryClient } from \"@/utils/trpc\";\n{{/if}}\n{{#if (eq api \"orpc\")}}\nimport { queryClient } from \"@/utils/orpc\";\n{{/if}}\nimport { useForm } from \"@tanstack/react-form\";\nimport { useRef } from \"react\";\nimport { Text, TextInput, View } from \"react-native\";\nimport { Button, FieldError, Input, Label, Spinner, Surface, TextField, useToast } from \"heroui-native\";\nimport z from \"zod\";\n\nconst signInSchema = z.object({\n  email: z\n    .string()\n    .trim()\n    .min(1, \"Email is required\")\n    .email(\"Enter a valid email address\"),\n  password: z\n    .string()\n    .min(1, \"Password is required\")\n    .min(8, \"Use at least 8 characters\"),\n});\n\nfunction getErrorMessage(error: unknown): string | null {\n  if (!error) return null;\n\n  if (typeof error === \"string\") {\n    return error;\n  }\n\n  if (Array.isArray(error)) {\n    for (const issue of error) {\n      const message = getErrorMessage(issue);\n      if (message) {\n        return message;\n      }\n    }\n    return null;\n  }\n\n  if (typeof error === \"object\" && error !== null) {\n    const maybeError = error as { message?: unknown };\n    if (typeof maybeError.message === \"string\") {\n      return maybeError.message;\n    }\n  }\n\n  return null;\n}\n\nfunction SignIn() {\n  const passwordInputRef = useRef<TextInput>(null);\n  const { toast } = useToast();\n\n  const form = useForm({\n    defaultValues: {\n      email: \"\",\n      password: \"\",\n    },\n    validators: {\n      onSubmit: signInSchema,\n    },\n    onSubmit: async ({ value, formApi }) => {\n      await authClient.signIn.email(\n        {\n          email: value.email.trim(),\n          password: value.password,\n        },\n        {\n          onError(error) {\n            toast.show({\n              variant: \"danger\",\n              label: error.error?.message || \"Failed to sign in\",\n            });\n          },\n          onSuccess() {\n            formApi.reset();\n            toast.show({\n              variant: \"success\",\n              label: \"Signed in successfully\",\n            });\n            {{#if (eq api \"orpc\")}}\n            queryClient.refetchQueries();\n            {{/if}}\n            {{#if (eq api \"trpc\")}}\n            queryClient.refetchQueries();\n            {{/if}}\n          },\n        },\n      );\n    },\n  });\n\n  return (\n    <Surface variant=\"secondary\" className=\"p-4 rounded-lg\">\n      <Text className=\"text-foreground font-medium mb-4\">Sign In</Text>\n\n      <form.Subscribe\n        selector={(state) => ({\n          isSubmitting: state.isSubmitting,\n          validationError: getErrorMessage(state.errorMap.onSubmit),\n        })}\n      >\n        {({ isSubmitting, validationError }) => {\n          const formError = validationError;\n\n          return (\n            <>\n              <FieldError isInvalid={!!formError} className=\"mb-3\">\n                {formError}\n              </FieldError>\n\n              <View className=\"gap-3\">\n                <form.Field name=\"email\">\n                  {(field) => (\n                    <TextField>\n                      <Label>Email</Label>\n                      <Input\n                        value={field.state.value}\n                        onBlur={field.handleBlur}\n                        onChangeText={field.handleChange}\n                        placeholder=\"email@example.com\"\n                        keyboardType=\"email-address\"\n                        autoCapitalize=\"none\"\n                        autoComplete=\"email\"\n                        textContentType=\"emailAddress\"\n                        returnKeyType=\"next\"\n                        blurOnSubmit={false}\n                        onSubmitEditing={() => {\n                          passwordInputRef.current?.focus();\n                        }}\n                      />\n                    </TextField>\n                  )}\n                </form.Field>\n\n                <form.Field name=\"password\">\n                  {(field) => (\n                    <TextField>\n                      <Label>Password</Label>\n                      <Input\n                        ref={passwordInputRef}\n                        value={field.state.value}\n                        onBlur={field.handleBlur}\n                        onChangeText={field.handleChange}\n                        placeholder=\"••••••••\"\n                        secureTextEntry\n                        autoComplete=\"password\"\n                        textContentType=\"password\"\n                        returnKeyType=\"go\"\n                        onSubmitEditing={form.handleSubmit}\n                      />\n                    </TextField>\n                  )}\n                </form.Field>\n\n                <Button onPress={form.handleSubmit} isDisabled={isSubmitting} className=\"mt-1\">\n                  {isSubmitting ? <Spinner size=\"sm\" color=\"default\" /> : <Button.Label>Sign In</Button.Label>}\n                </Button>\n              </View>\n            </>\n          );\n        }}\n      </form.Subscribe>\n    </Surface>\n  );\n}\n\nexport { SignIn };\n`],\n  [\"auth/better-auth/native/uniwind/components/sign-up.tsx.hbs\", `import { authClient } from \"@/lib/auth-client\";\n{{#if (eq api \"trpc\")}}\nimport { queryClient } from \"@/utils/trpc\";\n{{/if}}\n{{#if (eq api \"orpc\")}}\nimport { queryClient } from \"@/utils/orpc\";\n{{/if}}\nimport { useForm } from \"@tanstack/react-form\";\nimport { useRef } from \"react\";\nimport { Text, TextInput, View } from \"react-native\";\nimport { Button, FieldError, Input, Label, Spinner, Surface, TextField, useToast } from \"heroui-native\";\nimport z from \"zod\";\n\nconst signUpSchema = z.object({\n  name: z\n    .string()\n    .trim()\n    .min(1, \"Name is required\")\n    .min(2, \"Name must be at least 2 characters\"),\n  email: z\n    .string()\n    .trim()\n    .min(1, \"Email is required\")\n    .email(\"Enter a valid email address\"),\n  password: z\n    .string()\n    .min(1, \"Password is required\")\n    .min(8, \"Use at least 8 characters\"),\n});\n\nfunction getErrorMessage(error: unknown): string | null {\n  if (!error) return null;\n\n  if (typeof error === \"string\") {\n    return error;\n  }\n\n  if (Array.isArray(error)) {\n    for (const issue of error) {\n      const message = getErrorMessage(issue);\n      if (message) {\n        return message;\n      }\n    }\n    return null;\n  }\n\n  if (typeof error === \"object\" && error !== null) {\n    const maybeError = error as { message?: unknown };\n    if (typeof maybeError.message === \"string\") {\n      return maybeError.message;\n    }\n  }\n\n  return null;\n}\n\nexport function SignUp() {\n  const emailInputRef = useRef<TextInput>(null);\n  const passwordInputRef = useRef<TextInput>(null);\n  const { toast } = useToast();\n\n  const form = useForm({\n    defaultValues: {\n      name: \"\",\n      email: \"\",\n      password: \"\",\n    },\n    validators: {\n      onSubmit: signUpSchema,\n    },\n    onSubmit: async ({ value, formApi }) => {\n      await authClient.signUp.email(\n        {\n          name: value.name.trim(),\n          email: value.email.trim(),\n          password: value.password,\n        },\n        {\n          onError(error) {\n            toast.show({\n              variant: \"danger\",\n              label: error.error?.message || \"Failed to sign up\",\n            });\n          },\n          onSuccess() {\n            formApi.reset();\n            toast.show({\n              variant: \"success\",\n              label: \"Account created successfully\",\n            });\n            {{#if (eq api \"orpc\")}}\n            queryClient.refetchQueries();\n            {{/if}}\n            {{#if (eq api \"trpc\")}}\n            queryClient.refetchQueries();\n            {{/if}}\n          },\n        },\n      );\n    },\n  });\n\n  return (\n    <Surface variant=\"secondary\" className=\"p-4 rounded-lg\">\n      <Text className=\"text-foreground font-medium mb-4\">Create Account</Text>\n\n      <form.Subscribe\n        selector={(state) => ({\n          isSubmitting: state.isSubmitting,\n          validationError: getErrorMessage(state.errorMap.onSubmit),\n        })}\n      >\n        {({ isSubmitting, validationError }) => {\n          const formError = validationError;\n\n          return (\n            <>\n              <FieldError isInvalid={!!formError} className=\"mb-3\">\n                {formError}\n              </FieldError>\n\n              <View className=\"gap-3\">\n                <form.Field name=\"name\">\n                  {(field) => (\n                    <TextField>\n                      <Label>Name</Label>\n                      <Input\n                        value={field.state.value}\n                        onBlur={field.handleBlur}\n                        onChangeText={field.handleChange}\n                        placeholder=\"John Doe\"\n                        autoComplete=\"name\"\n                        textContentType=\"name\"\n                        returnKeyType=\"next\"\n                        blurOnSubmit={false}\n                        onSubmitEditing={() => {\n                          emailInputRef.current?.focus();\n                        }}\n                      />\n                    </TextField>\n                  )}\n                </form.Field>\n\n                <form.Field name=\"email\">\n                  {(field) => (\n                    <TextField>\n                      <Label>Email</Label>\n                      <Input\n                        ref={emailInputRef}\n                        value={field.state.value}\n                        onBlur={field.handleBlur}\n                        onChangeText={field.handleChange}\n                        placeholder=\"email@example.com\"\n                        keyboardType=\"email-address\"\n                        autoCapitalize=\"none\"\n                        autoComplete=\"email\"\n                        textContentType=\"emailAddress\"\n                        returnKeyType=\"next\"\n                        blurOnSubmit={false}\n                        onSubmitEditing={() => {\n                          passwordInputRef.current?.focus();\n                        }}\n                      />\n                    </TextField>\n                  )}\n                </form.Field>\n\n                <form.Field name=\"password\">\n                  {(field) => (\n                    <TextField>\n                      <Label>Password</Label>\n                      <Input\n                        ref={passwordInputRef}\n                        value={field.state.value}\n                        onBlur={field.handleBlur}\n                        onChangeText={field.handleChange}\n                        placeholder=\"••••••••\"\n                        secureTextEntry\n                        autoComplete=\"new-password\"\n                        textContentType=\"newPassword\"\n                        returnKeyType=\"go\"\n                        onSubmitEditing={form.handleSubmit}\n                      />\n                    </TextField>\n                  )}\n                </form.Field>\n\n                <Button onPress={form.handleSubmit} isDisabled={isSubmitting} className=\"mt-1\">\n                  {isSubmitting ? (\n                    <Spinner size=\"sm\" color=\"default\" />\n                  ) : (\n                    <Button.Label>Create Account</Button.Label>\n                  )}\n                </Button>\n              </View>\n            </>\n          );\n        }}\n      </form.Subscribe>\n    </Surface>\n  );\n}\n`],\n  [\"auth/better-auth/server/base/_gitignore\", `# dependencies (bun install)\nnode_modules\n\n# output\nout\ndist\n*.tgz\n\n# code coverage\ncoverage\n*.lcov\n\n# logs\nlogs\n_.log\nreport.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json\n\n# dotenv environment variable files\n.env\n.env.development.local\n.env.test.local\n.env.production.local\n.env.local\n\n# caches\n.eslintcache\n.cache\n*.tsbuildinfo\n\n# IntelliJ based IDEs\n.idea\n\n# Finder (MacOS) folder config\n.DS_Store\n`],\n  [\"auth/better-auth/server/base/package.json.hbs\", `{\n  \"name\": \"@{{projectName}}/auth\",\n  \"exports\": {\n    \".\": {\n      \"default\": \"./src/index.ts\"\n    },\n    \"./*\": {\n      \"default\": \"./src/*.ts\"\n    }\n  },\n  \"type\": \"module\",\n  \"scripts\": {},\n  \"devDependencies\": {}\n}`],\n  [\"auth/better-auth/server/base/src/index.ts.hbs\", `{{#if (eq orm \"prisma\")}}\nimport { betterAuth } from \"better-auth\";\nimport { prismaAdapter } from \"better-auth/adapters/prisma\";\n{{#if (and (eq backend \"self\") (eq webDeploy \"cloudflare\") (includes frontend \"svelte\"))}}\nimport type {} from \"@{{projectName}}/env/server\";\n{{else}}\nimport { env } from \"@{{projectName}}/env/server\";\n{{/if}}\n{{#if (eq payments \"polar\")}}\nimport { polar, checkout, portal } from \"@polar-sh/better-auth\";\n{{#if (and (eq backend \"self\") (eq webDeploy \"cloudflare\") (includes frontend \"svelte\"))}}\nimport { createPolarClient } from \"./lib/payments\";\n{{else}}\nimport { polarClient } from \"./lib/payments\";\n{{/if}}\n{{/if}}\nimport { createPrismaClient } from \"@{{projectName}}/db\";\n\nexport function createAuth({{#if (and (eq backend \"self\") (eq webDeploy \"cloudflare\") (includes frontend \"svelte\"))}}env: Env{{/if}}) {\n\tconst prisma = createPrismaClient({{#if (and (eq backend \"self\") (eq webDeploy \"cloudflare\") (includes frontend \"svelte\"))}}env{{/if}});\n\n\treturn betterAuth({\n\t\tdatabase: prismaAdapter(prisma, {\n{{#if (eq database \"postgres\")}}provider: \"postgresql\",{{/if}}\n{{#if (eq database \"sqlite\")}}provider: \"sqlite\",{{/if}}\n{{#if (eq database \"mysql\")}}provider: \"mysql\",{{/if}}\n{{#if (eq database \"mongodb\")}}provider: \"mongodb\",{{/if}}\n\t\t}),\n\n\t\ttrustedOrigins: [\n\t\t\tenv.CORS_ORIGIN,\n{{#if (or (includes frontend \"native-bare\") (includes frontend \"native-uniwind\") (includes frontend \"native-unistyles\"))}}\n\t\t\t\"{{projectName}}://\",\n\t\t\t...(env.NODE_ENV === \"development\"\n\t\t\t\t? [\n\t\t\t\t\t\"exp://\",\n\t\t\t\t\t\"exp://**\",\n\t\t\t\t\t\"exp://192.168.*.*:*/**\",\n\t\t\t\t\t\"http://localhost:8081\",\n\t\t\t\t]\n\t\t\t\t: []),\n{{/if}}\n\t\t],\n\t\temailAndPassword: {\n\t\t\tenabled: true,\n\t\t},\n\t\tsecret: env.BETTER_AUTH_SECRET,\n\t\tbaseURL: env.BETTER_AUTH_URL,\n{{#if (ne backend \"self\")}}\n\t\tadvanced: {\n\t\t\tdefaultCookieAttributes: {\n\t\t\t\tsameSite: \"none\",\n\t\t\t\tsecure: true,\n\t\t\t\thttpOnly: true,\n\t\t\t},\n\t\t},\n{{/if}}\n\t\tplugins: [\n{{#if (eq payments \"polar\")}}\n\t\t\tpolar({\n\t\t\t\tclient: {{#if (and (eq backend \"self\") (eq webDeploy \"cloudflare\") (includes frontend \"svelte\"))}}createPolarClient(env){{else}}polarClient{{/if}},\n\t\t\t\tcreateCustomerOnSignUp: true,\n\t\t\t\tenableCustomerPortal: true,\n\t\t\t\tuse: [\n\t\t\t\t\tcheckout({\n\t\t\t\t\t\tproducts: [\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tproductId: \"your-product-id\",\n\t\t\t\t\t\t\t\tslug: \"pro\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t],\n\t\t\t\t\t\tsuccessUrl: env.POLAR_SUCCESS_URL,\n\t\t\t\t\t\tauthenticatedUsersOnly: true,\n\t\t\t\t\t}),\n\t\t\t\t\tportal(),\n\t\t\t\t],\n\t\t\t}),\n{{/if}}\n\t\t],\n\t});\n}\n\n{{#if (and (ne runtime \"workers\") (ne serverDeploy \"cloudflare\") (or (ne backend \"self\") (ne webDeploy \"cloudflare\")))}}\nexport const auth = createAuth();\n{{/if}}\n{{/if}}\n\n{{#if (eq orm \"drizzle\")}}\n{{#if (or (eq runtime \"bun\") (eq runtime \"node\") (eq runtime \"none\"))}}\nimport { betterAuth } from \"better-auth\";\nimport { drizzleAdapter } from \"better-auth/adapters/drizzle\";\n{{#if (and (eq backend \"self\") (eq webDeploy \"cloudflare\") (includes frontend \"svelte\"))}}\nimport type {} from \"@{{projectName}}/env/server\";\n{{else}}\nimport { env } from \"@{{projectName}}/env/server\";\n{{/if}}\n{{#if (eq payments \"polar\")}}\nimport { polar, checkout, portal } from \"@polar-sh/better-auth\";\n{{#if (and (eq backend \"self\") (eq webDeploy \"cloudflare\") (includes frontend \"svelte\"))}}\nimport { createPolarClient } from \"./lib/payments\";\n{{else}}\nimport { polarClient } from \"./lib/payments\";\n{{/if}}\n{{/if}}\nimport { createDb } from \"@{{projectName}}/db\";\nimport * as schema from \"@{{projectName}}/db/schema/auth\";\n\n\nexport function createAuth({{#if (and (eq backend \"self\") (eq webDeploy \"cloudflare\") (includes frontend \"svelte\"))}}env: Env{{/if}}) {\n\tconst db = createDb({{#if (and (eq backend \"self\") (eq webDeploy \"cloudflare\") (includes frontend \"svelte\"))}}env{{/if}});\n\n\treturn betterAuth({\n\t\tdatabase: drizzleAdapter(db, {\n{{#if (eq database \"postgres\")}}provider: \"pg\",{{/if}}\n{{#if (eq database \"sqlite\")}}provider: \"sqlite\",{{/if}}\n{{#if (eq database \"mysql\")}}provider: \"mysql\",{{/if}}\n\t\t\tschema: schema,\n\t\t}),\n\t\ttrustedOrigins: [\n\t\t\tenv.CORS_ORIGIN,\n{{#if (or (includes frontend \"native-bare\") (includes frontend \"native-uniwind\") (includes frontend \"native-unistyles\"))}}\n\t\t\t\"{{projectName}}://\",\n\t\t\t...(env.NODE_ENV === \"development\"\n\t\t\t\t? [\n\t\t\t\t\t\"exp://\",\n\t\t\t\t\t\"exp://**\",\n\t\t\t\t\t\"exp://192.168.*.*:*/**\",\n\t\t\t\t\t\"http://localhost:8081\",\n\t\t\t\t]\n\t\t\t\t: []),\n{{/if}}\n\t\t],\n\t\temailAndPassword: {\n\t\t\tenabled: true,\n\t\t},\n\t\tsecret: env.BETTER_AUTH_SECRET,\n\t\tbaseURL: env.BETTER_AUTH_URL,\n{{#if (ne backend \"self\")}}\n\t\tadvanced: {\n\t\t\tdefaultCookieAttributes: {\n\t\t\t\tsameSite: \"none\",\n\t\t\t\tsecure: true,\n\t\t\t\thttpOnly: true,\n\t\t\t},\n\t\t},\n{{/if}}\n\t\tplugins: [\n{{#if (eq payments \"polar\")}}\n\t\t\tpolar({\n\t\t\t\tclient: {{#if (and (eq backend \"self\") (eq webDeploy \"cloudflare\") (includes frontend \"svelte\"))}}createPolarClient(env){{else}}polarClient{{/if}},\n\t\t\t\tcreateCustomerOnSignUp: true,\n\t\t\t\tenableCustomerPortal: true,\n\t\t\t\tuse: [\n\t\t\t\t\tcheckout({\n\t\t\t\t\t\tproducts: [\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tproductId: \"your-product-id\",\n\t\t\t\t\t\t\t\tslug: \"pro\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t],\n\t\t\t\t\t\tsuccessUrl: env.POLAR_SUCCESS_URL,\n\t\t\t\t\t\tauthenticatedUsersOnly: true,\n\t\t\t\t\t}),\n\t\t\t\t\tportal(),\n\t\t\t\t],\n\t\t\t}),\n{{/if}}\n\t\t],\n\t});\n}\n\n{{#if (and (ne runtime \"workers\") (ne serverDeploy \"cloudflare\") (or (ne backend \"self\") (ne webDeploy \"cloudflare\")))}}\nexport const auth = createAuth();\n{{/if}}\n{{/if}}\n\n{{#if (eq runtime \"workers\")}}\nimport { betterAuth } from \"better-auth\";\nimport { drizzleAdapter } from \"better-auth/adapters/drizzle\";\nimport { env } from \"@{{projectName}}/env/server\";\n{{#if (eq payments \"polar\")}}\nimport { polar, checkout, portal } from \"@polar-sh/better-auth\";\nimport { polarClient } from \"./lib/payments\";\n{{/if}}\nimport { createDb } from \"@{{projectName}}/db\";\nimport * as schema from \"@{{projectName}}/db/schema/auth\";\n\n\nexport function createAuth() {\n\tconst db = createDb();\n\n\treturn betterAuth({\n\t\tdatabase: drizzleAdapter(db, {\n{{#if (eq database \"postgres\")}}provider: \"pg\",{{/if}}\n{{#if (eq database \"sqlite\")}}provider: \"sqlite\",{{/if}}\n{{#if (eq database \"mysql\")}}provider: \"mysql\",{{/if}}\n\t\t\tschema: schema,\n\t\t}),\n\t\ttrustedOrigins: [\n\t\t\tenv.CORS_ORIGIN,\n{{#if (or (includes frontend \"native-bare\") (includes frontend \"native-uniwind\") (includes frontend \"native-unistyles\"))}}\n\t\t\t\"{{projectName}}://\",\n\t\t\t...(env.NODE_ENV === \"development\"\n\t\t\t\t? [\n\t\t\t\t\t\"exp://\",\n\t\t\t\t\t\"exp://**\",\n\t\t\t\t\t\"exp://192.168.*.*:*/**\",\n\t\t\t\t\t\"http://localhost:8081\",\n\t\t\t\t]\n\t\t\t\t: []),\n{{/if}}\n\t\t],\n\t\temailAndPassword: {\n\t\t\tenabled: true,\n\t\t},\n\t\t// uncomment cookieCache setting when ready to deploy to Cloudflare using *.workers.dev domains\n\t\t// session: {\n\t\t//   cookieCache: {\n\t\t//     enabled: true,\n\t\t//     maxAge: 60,\n\t\t//   },\n\t\t// },\n\t\tsecret: env.BETTER_AUTH_SECRET,\n\t\tbaseURL: env.BETTER_AUTH_URL,\n\t\tadvanced: {\n\t\t\tdefaultCookieAttributes: {\n\t\t\t\tsameSite: \"none\",\n\t\t\t\tsecure: true,\n\t\t\t\thttpOnly: true,\n\t\t\t},\n\t\t\t// uncomment crossSubDomainCookies setting when ready to deploy and replace <your-workers-subdomain> with your actual workers subdomain\n\t\t\t// https://developers.cloudflare.com/workers/wrangler/configuration/#workersdev\n\t\t\t// crossSubDomainCookies: {\n\t\t\t//   enabled: true,\n\t\t\t//   domain: \"<your-workers-subdomain>\",\n\t\t\t// },\n\t\t},\n{{#if (eq payments \"polar\")}}\n\t\tplugins: [\n\t\t\tpolar({\n\t\t\t\tclient: polarClient,\n\t\t\t\tcreateCustomerOnSignUp: true,\n\t\t\t\tenableCustomerPortal: true,\n\t\t\t\tuse: [\n\t\t\t\t\tcheckout({\n\t\t\t\t\t\tproducts: [\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tproductId: \"your-product-id\",\n\t\t\t\t\t\t\t\tslug: \"pro\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t],\n\t\t\t\t\t\tsuccessUrl: env.POLAR_SUCCESS_URL,\n\t\t\t\t\t\tauthenticatedUsersOnly: true,\n\t\t\t\t\t}),\n\t\t\t\t\tportal(),\n\t\t\t\t],\n\t\t\t}),\n\t\t],\n{{/if}}\n\t});\n}\n{{/if}}\n{{/if}}\n\n{{#if (eq orm \"mongoose\")}}\nimport { betterAuth } from \"better-auth\";\nimport { mongodbAdapter } from \"better-auth/adapters/mongodb\";\n{{#if (and (eq backend \"self\") (eq webDeploy \"cloudflare\") (includes frontend \"svelte\"))}}\nimport type {} from \"@{{projectName}}/env/server\";\n{{else}}\nimport { env } from \"@{{projectName}}/env/server\";\n{{/if}}\n{{#if (eq payments \"polar\")}}\nimport { polar, checkout, portal } from \"@polar-sh/better-auth\";\n{{#if (and (eq backend \"self\") (eq webDeploy \"cloudflare\") (includes frontend \"svelte\"))}}\nimport { createPolarClient } from \"./lib/payments\";\n{{else}}\nimport { polarClient } from \"./lib/payments\";\n{{/if}}\n{{/if}}\nimport { client } from \"@{{projectName}}/db\";\n\nexport function createAuth({{#if (and (eq backend \"self\") (eq webDeploy \"cloudflare\") (includes frontend \"svelte\"))}}env: Env{{/if}}) {\n\treturn betterAuth({\n\t\tdatabase: mongodbAdapter(client),\n\t\ttrustedOrigins: [\n\t\t\tenv.CORS_ORIGIN,\n{{#if (or (includes frontend \"native-bare\") (includes frontend \"native-uniwind\") (includes frontend \"native-unistyles\"))}}\n\t\t\t\"{{projectName}}://\",\n\t\t\t...(env.NODE_ENV === \"development\"\n\t\t\t\t? [\n\t\t\t\t\t\"exp://\",\n\t\t\t\t\t\"exp://**\",\n\t\t\t\t\t\"exp://192.168.*.*:*/**\",\n\t\t\t\t\t\"http://localhost:8081\",\n\t\t\t\t]\n\t\t\t\t: []),\n{{/if}}\n\t\t],\n\t\temailAndPassword: {\n\t\t\tenabled: true,\n\t\t},\n\t\tsecret: env.BETTER_AUTH_SECRET,\n\t\tbaseURL: env.BETTER_AUTH_URL,\n{{#if (ne backend \"self\")}}\n\t\tadvanced: {\n\t\t\tdefaultCookieAttributes: {\n\t\t\t\tsameSite: \"none\",\n\t\t\t\tsecure: true,\n\t\t\t\thttpOnly: true,\n\t\t\t},\n\t\t},\n{{/if}}\n{{#if (eq payments \"polar\")}}\n\t\tplugins: [\n\t\t\tpolar({\n\t\t\t\tclient: {{#if (and (eq backend \"self\") (eq webDeploy \"cloudflare\") (includes frontend \"svelte\"))}}createPolarClient(env){{else}}polarClient{{/if}},\n\t\t\t\tcreateCustomerOnSignUp: true,\n\t\t\t\tenableCustomerPortal: true,\n\t\t\t\tuse: [\n\t\t\t\t\tcheckout({\n\t\t\t\t\t\tproducts: [\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tproductId: \"your-product-id\",\n\t\t\t\t\t\t\t\tslug: \"pro\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t],\n\t\t\t\t\t\tsuccessUrl: env.POLAR_SUCCESS_URL,\n\t\t\t\t\t\tauthenticatedUsersOnly: true,\n\t\t\t\t\t}),\n\t\t\t\t\tportal(),\n\t\t\t\t],\n\t\t\t}),\n\t\t],\n{{/if}}\n\t});\n}\n\n{{#if (and (ne runtime \"workers\") (ne serverDeploy \"cloudflare\") (or (ne backend \"self\") (ne webDeploy \"cloudflare\")))}}\nexport const auth = createAuth();\n{{/if}}\n{{/if}}\n\n{{#if (eq orm \"none\")}}\nimport { betterAuth } from \"better-auth\";\n{{#if (and (eq backend \"self\") (eq webDeploy \"cloudflare\") (includes frontend \"svelte\"))}}\nimport type {} from \"@{{projectName}}/env/server\";\n{{else}}\nimport { env } from \"@{{projectName}}/env/server\";\n{{/if}}\n{{#if (eq payments \"polar\")}}\nimport { polar, checkout, portal } from \"@polar-sh/better-auth\";\n{{#if (and (eq backend \"self\") (eq webDeploy \"cloudflare\") (includes frontend \"svelte\"))}}\nimport { createPolarClient } from \"./lib/payments\";\n{{else}}\nimport { polarClient } from \"./lib/payments\";\n{{/if}}\n{{/if}}\n\n\nexport function createAuth({{#if (and (eq backend \"self\") (eq webDeploy \"cloudflare\") (includes frontend \"svelte\"))}}env: Env{{/if}}) {\n\treturn betterAuth({\n\t\tdatabase: \"\", // Invalid configuration\n\t\ttrustedOrigins: [\n\t\t\tenv.CORS_ORIGIN,\n{{#if (or (includes frontend \"native-bare\") (includes frontend \"native-uniwind\") (includes frontend \"native-unistyles\"))}}\n\t\t\t\"{{projectName}}://\",\n\t\t\t...(env.NODE_ENV === \"development\"\n\t\t\t\t? [\n\t\t\t\t\t\"exp://\",\n\t\t\t\t\t\"exp://**\",\n\t\t\t\t\t\"exp://192.168.*.*:*/**\",\n\t\t\t\t\t\"http://localhost:8081\",\n\t\t\t\t]\n\t\t\t\t: []),\n{{/if}}\n\t\t],\n\t\temailAndPassword: {\n\t\t\tenabled: true,\n\t\t},\n\t\tsecret: env.BETTER_AUTH_SECRET,\n\t\tbaseURL: env.BETTER_AUTH_URL,\n{{#if (ne backend \"self\")}}\n\t\tadvanced: {\n\t\t\tdefaultCookieAttributes: {\n\t\t\t\tsameSite: \"none\",\n\t\t\t\tsecure: true,\n\t\t\t\thttpOnly: true,\n\t\t\t},\n\t\t},\n{{/if}}\n{{#if (eq payments \"polar\")}}\n\t\tplugins: [\n\t\t\tpolar({\n\t\t\t\tclient: {{#if (and (eq backend \"self\") (eq webDeploy \"cloudflare\") (includes frontend \"svelte\"))}}createPolarClient(env){{else}}polarClient{{/if}},\n\t\t\t\tcreateCustomerOnSignUp: true,\n\t\t\t\tenableCustomerPortal: true,\n\t\t\t\tuse: [\n\t\t\t\t\tcheckout({\n\t\t\t\t\t\tproducts: [\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tproductId: \"your-product-id\",\n\t\t\t\t\t\t\t\tslug: \"pro\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t],\n\t\t\t\t\t\tsuccessUrl: env.POLAR_SUCCESS_URL,\n\t\t\t\t\t\tauthenticatedUsersOnly: true,\n\t\t\t\t\t}),\n\t\t\t\t\tportal(),\n\t\t\t\t],\n\t\t\t}),\n\t\t],\n{{/if}}\n\t});\n}\n\n{{#if (and (ne runtime \"workers\") (ne serverDeploy \"cloudflare\") (or (ne backend \"self\") (ne webDeploy \"cloudflare\")))}}\nexport const auth = createAuth();\n{{/if}}\n{{/if}}\n`],\n  [\"auth/better-auth/server/base/tsconfig.json.hbs\", `{\n  \"extends\": \"@{{projectName}}/config/tsconfig.base.json\",\n  \"compilerOptions\": {\n    \"declaration\": true,\n    \"declarationMap\": true,\n    \"sourceMap\": true,\n    \"outDir\": \"dist\",\n    \"composite\": true\n  }\n}`],\n  [\"auth/better-auth/server/db/drizzle/mysql/src/schema/auth.ts.hbs\", `import { relations } from \"drizzle-orm\";\nimport {\n  mysqlTable,\n  varchar,\n  text,\n  timestamp,\n  boolean,\n  index,\n} from \"drizzle-orm/mysql-core\";\n\nexport const user = mysqlTable(\"user\", {\n  id: varchar(\"id\", { length: 36 }).primaryKey(),\n  name: varchar(\"name\", { length: 255 }).notNull(),\n  email: varchar(\"email\", { length: 255 }).notNull().unique(),\n  emailVerified: boolean(\"email_verified\").default(false).notNull(),\n  image: text(\"image\"),\n  createdAt: timestamp(\"created_at\", { fsp: 3 }).defaultNow().notNull(),\n  updatedAt: timestamp(\"updated_at\", { fsp: 3 })\n    .defaultNow()\n    .$onUpdate(() => /* @__PURE__ */ new Date())\n    .notNull(),\n});\n\nexport const session = mysqlTable(\n  \"session\",\n  {\n    id: varchar(\"id\", { length: 36 }).primaryKey(),\n    expiresAt: timestamp(\"expires_at\", { fsp: 3 }).notNull(),\n    token: varchar(\"token\", { length: 255 }).notNull().unique(),\n    createdAt: timestamp(\"created_at\", { fsp: 3 }).defaultNow().notNull(),\n    updatedAt: timestamp(\"updated_at\", { fsp: 3 })\n      .$onUpdate(() => /* @__PURE__ */ new Date())\n      .notNull(),\n    ipAddress: text(\"ip_address\"),\n    userAgent: text(\"user_agent\"),\n    userId: varchar(\"user_id\", { length: 36 })\n      .notNull()\n      .references(() => user.id, { onDelete: \"cascade\" }),\n  },\n  (table) => [index(\"session_userId_idx\").on(table.userId)],\n);\n\nexport const account = mysqlTable(\n  \"account\",\n  {\n    id: varchar(\"id\", { length: 36 }).primaryKey(),\n    accountId: text(\"account_id\").notNull(),\n    providerId: text(\"provider_id\").notNull(),\n    userId: varchar(\"user_id\", { length: 36 })\n      .notNull()\n      .references(() => user.id, { onDelete: \"cascade\" }),\n    accessToken: text(\"access_token\"),\n    refreshToken: text(\"refresh_token\"),\n    idToken: text(\"id_token\"),\n    accessTokenExpiresAt: timestamp(\"access_token_expires_at\", { fsp: 3 }),\n    refreshTokenExpiresAt: timestamp(\"refresh_token_expires_at\", { fsp: 3 }),\n    scope: text(\"scope\"),\n    password: text(\"password\"),\n    createdAt: timestamp(\"created_at\", { fsp: 3 }).defaultNow().notNull(),\n    updatedAt: timestamp(\"updated_at\", { fsp: 3 })\n      .$onUpdate(() => /* @__PURE__ */ new Date())\n      .notNull(),\n  },\n  (table) => [index(\"account_userId_idx\").on(table.userId)],\n);\n\nexport const verification = mysqlTable(\n  \"verification\",\n  {\n    id: varchar(\"id\", { length: 36 }).primaryKey(),\n    identifier: varchar(\"identifier\", { length: 255 }).notNull(),\n    value: text(\"value\").notNull(),\n    expiresAt: timestamp(\"expires_at\", { fsp: 3 }).notNull(),\n    createdAt: timestamp(\"created_at\", { fsp: 3 }).defaultNow().notNull(),\n    updatedAt: timestamp(\"updated_at\", { fsp: 3 })\n      .defaultNow()\n      .$onUpdate(() => /* @__PURE__ */ new Date())\n      .notNull(),\n  },\n  (table) => [index(\"verification_identifier_idx\").on(table.identifier)],\n);\n\nexport const userRelations = relations(user, ({ many }) => ({\n  sessions: many(session),\n  accounts: many(account),\n}));\n\nexport const sessionRelations = relations(session, ({ one }) => ({\n  user: one(user, {\n    fields: [session.userId],\n    references: [user.id],\n  }),\n}));\n\nexport const accountRelations = relations(account, ({ one }) => ({\n  user: one(user, {\n    fields: [account.userId],\n    references: [user.id],\n  }),\n}));\n`],\n  [\"auth/better-auth/server/db/drizzle/postgres/src/schema/auth.ts.hbs\", `import { relations } from \"drizzle-orm\";\nimport { pgTable, text, timestamp, boolean, index } from \"drizzle-orm/pg-core\";\n\nexport const user = pgTable(\"user\", {\n  id: text(\"id\").primaryKey(),\n  name: text(\"name\").notNull(),\n  email: text(\"email\").notNull().unique(),\n  emailVerified: boolean(\"email_verified\").default(false).notNull(),\n  image: text(\"image\"),\n  createdAt: timestamp(\"created_at\").defaultNow().notNull(),\n  updatedAt: timestamp(\"updated_at\")\n    .defaultNow()\n    .$onUpdate(() => /* @__PURE__ */ new Date())\n    .notNull(),\n});\n\nexport const session = pgTable(\n  \"session\",\n  {\n    id: text(\"id\").primaryKey(),\n    expiresAt: timestamp(\"expires_at\").notNull(),\n    token: text(\"token\").notNull().unique(),\n    createdAt: timestamp(\"created_at\").defaultNow().notNull(),\n    updatedAt: timestamp(\"updated_at\")\n      .$onUpdate(() => /* @__PURE__ */ new Date())\n      .notNull(),\n    ipAddress: text(\"ip_address\"),\n    userAgent: text(\"user_agent\"),\n    userId: text(\"user_id\")\n      .notNull()\n      .references(() => user.id, { onDelete: \"cascade\" }),\n  },\n  (table) => [index(\"session_userId_idx\").on(table.userId)],\n);\n\nexport const account = pgTable(\n  \"account\",\n  {\n    id: text(\"id\").primaryKey(),\n    accountId: text(\"account_id\").notNull(),\n    providerId: text(\"provider_id\").notNull(),\n    userId: text(\"user_id\")\n      .notNull()\n      .references(() => user.id, { onDelete: \"cascade\" }),\n    accessToken: text(\"access_token\"),\n    refreshToken: text(\"refresh_token\"),\n    idToken: text(\"id_token\"),\n    accessTokenExpiresAt: timestamp(\"access_token_expires_at\"),\n    refreshTokenExpiresAt: timestamp(\"refresh_token_expires_at\"),\n    scope: text(\"scope\"),\n    password: text(\"password\"),\n    createdAt: timestamp(\"created_at\").defaultNow().notNull(),\n    updatedAt: timestamp(\"updated_at\")\n      .$onUpdate(() => /* @__PURE__ */ new Date())\n      .notNull(),\n  },\n  (table) => [index(\"account_userId_idx\").on(table.userId)],\n);\n\nexport const verification = pgTable(\n  \"verification\",\n  {\n    id: text(\"id\").primaryKey(),\n    identifier: text(\"identifier\").notNull(),\n    value: text(\"value\").notNull(),\n    expiresAt: timestamp(\"expires_at\").notNull(),\n    createdAt: timestamp(\"created_at\").defaultNow().notNull(),\n    updatedAt: timestamp(\"updated_at\")\n      .defaultNow()\n      .$onUpdate(() => /* @__PURE__ */ new Date())\n      .notNull(),\n  },\n  (table) => [index(\"verification_identifier_idx\").on(table.identifier)],\n);\n\nexport const userRelations = relations(user, ({ many }) => ({\n  sessions: many(session),\n  accounts: many(account),\n}));\n\nexport const sessionRelations = relations(session, ({ one }) => ({\n  user: one(user, {\n    fields: [session.userId],\n    references: [user.id],\n  }),\n}));\n\nexport const accountRelations = relations(account, ({ one }) => ({\n  user: one(user, {\n    fields: [account.userId],\n    references: [user.id],\n  }),\n}));\n`],\n  [\"auth/better-auth/server/db/drizzle/sqlite/src/schema/auth.ts.hbs\", `import { relations, sql } from \"drizzle-orm\";\nimport { sqliteTable, text, integer, index } from \"drizzle-orm/sqlite-core\";\n\nexport const user = sqliteTable(\"user\", {\n  id: text(\"id\").primaryKey(),\n  name: text(\"name\").notNull(),\n  email: text(\"email\").notNull().unique(),\n  emailVerified: integer(\"email_verified\", { mode: \"boolean\" })\n    .default(false)\n    .notNull(),\n  image: text(\"image\"),\n  createdAt: integer(\"created_at\", { mode: \"timestamp_ms\" })\n    .default(sql\\`(cast(unixepoch('subsecond') * 1000 as integer))\\`)\n    .notNull(),\n  updatedAt: integer(\"updated_at\", { mode: \"timestamp_ms\" })\n    .default(sql\\`(cast(unixepoch('subsecond') * 1000 as integer))\\`)\n    .$onUpdate(() => /* @__PURE__ */ new Date())\n    .notNull(),\n});\n\nexport const session = sqliteTable(\n  \"session\",\n  {\n    id: text(\"id\").primaryKey(),\n    expiresAt: integer(\"expires_at\", { mode: \"timestamp_ms\" }).notNull(),\n    token: text(\"token\").notNull().unique(),\n    createdAt: integer(\"created_at\", { mode: \"timestamp_ms\" })\n      .default(sql\\`(cast(unixepoch('subsecond') * 1000 as integer))\\`)\n      .notNull(),\n    updatedAt: integer(\"updated_at\", { mode: \"timestamp_ms\" })\n      .$onUpdate(() => /* @__PURE__ */ new Date())\n      .notNull(),\n    ipAddress: text(\"ip_address\"),\n    userAgent: text(\"user_agent\"),\n    userId: text(\"user_id\")\n      .notNull()\n      .references(() => user.id, { onDelete: \"cascade\" }),\n  },\n  (table) => [index(\"session_userId_idx\").on(table.userId)],\n);\n\nexport const account = sqliteTable(\n  \"account\",\n  {\n    id: text(\"id\").primaryKey(),\n    accountId: text(\"account_id\").notNull(),\n    providerId: text(\"provider_id\").notNull(),\n    userId: text(\"user_id\")\n      .notNull()\n      .references(() => user.id, { onDelete: \"cascade\" }),\n    accessToken: text(\"access_token\"),\n    refreshToken: text(\"refresh_token\"),\n    idToken: text(\"id_token\"),\n    accessTokenExpiresAt: integer(\"access_token_expires_at\", {\n      mode: \"timestamp_ms\",\n    }),\n    refreshTokenExpiresAt: integer(\"refresh_token_expires_at\", {\n      mode: \"timestamp_ms\",\n    }),\n    scope: text(\"scope\"),\n    password: text(\"password\"),\n    createdAt: integer(\"created_at\", { mode: \"timestamp_ms\" })\n      .default(sql\\`(cast(unixepoch('subsecond') * 1000 as integer))\\`)\n      .notNull(),\n    updatedAt: integer(\"updated_at\", { mode: \"timestamp_ms\" })\n      .$onUpdate(() => /* @__PURE__ */ new Date())\n      .notNull(),\n  },\n  (table) => [index(\"account_userId_idx\").on(table.userId)],\n);\n\nexport const verification = sqliteTable(\n  \"verification\",\n  {\n    id: text(\"id\").primaryKey(),\n    identifier: text(\"identifier\").notNull(),\n    value: text(\"value\").notNull(),\n    expiresAt: integer(\"expires_at\", { mode: \"timestamp_ms\" }).notNull(),\n    createdAt: integer(\"created_at\", { mode: \"timestamp_ms\" })\n      .default(sql\\`(cast(unixepoch('subsecond') * 1000 as integer))\\`)\n      .notNull(),\n    updatedAt: integer(\"updated_at\", { mode: \"timestamp_ms\" })\n      .default(sql\\`(cast(unixepoch('subsecond') * 1000 as integer))\\`)\n      .$onUpdate(() => /* @__PURE__ */ new Date())\n      .notNull(),\n  },\n  (table) => [index(\"verification_identifier_idx\").on(table.identifier)],\n);\n\nexport const userRelations = relations(user, ({ many }) => ({\n  sessions: many(session),\n  accounts: many(account),\n}));\n\nexport const sessionRelations = relations(session, ({ one }) => ({\n  user: one(user, {\n    fields: [session.userId],\n    references: [user.id],\n  }),\n}));\n\nexport const accountRelations = relations(account, ({ one }) => ({\n  user: one(user, {\n    fields: [account.userId],\n    references: [user.id],\n  }),\n}));\n`],\n  [\"auth/better-auth/server/db/mongoose/mongodb/src/models/auth.model.ts.hbs\", `import mongoose from 'mongoose';\n\nconst { Schema, model } = mongoose;\n\nconst userSchema = new Schema(\n    {\n        _id: { type: String },\n        name: { type: String, required: true },\n        email: { type: String, required: true, unique: true },\n        emailVerified: { type: Boolean, required: true },\n        image: { type: String },\n        createdAt: { type: Date, required: true },\n        updatedAt: { type: Date, required: true },\n    },\n    { collection: 'user' }\n);\n\nconst sessionSchema = new Schema(\n    {\n        _id: { type: String },\n        expiresAt: { type: Date, required: true },\n        token: { type: String, required: true, unique: true },\n        createdAt: { type: Date, required: true },\n        updatedAt: { type: Date, required: true },\n        ipAddress: { type: String },\n        userAgent: { type: String },\n        userId: { type: String, ref: 'User', required: true },\n    },\n    { collection: 'session' }\n);\n\nconst accountSchema = new Schema(\n    {\n        _id: { type: String },\n        accountId: { type: String, required: true },\n        providerId: { type: String, required: true },\n        userId: { type: String, ref: 'User', required: true },\n        accessToken: { type: String },\n        refreshToken: { type: String },\n        idToken: { type: String },\n        accessTokenExpiresAt: { type: Date },\n        refreshTokenExpiresAt: { type: Date },\n        scope: { type: String },\n        password: { type: String },\n        createdAt: { type: Date, required: true },\n        updatedAt: { type: Date, required: true },\n    },\n    { collection: 'account' }\n);\n\nconst verificationSchema = new Schema(\n    {\n        _id: { type: String },\n        identifier: { type: String, required: true },\n        value: { type: String, required: true },\n        expiresAt: { type: Date, required: true },\n        createdAt: { type: Date },\n        updatedAt: { type: Date },\n    },\n    { collection: 'verification' }\n);\n\nconst User = model('User', userSchema);\nconst Session = model('Session', sessionSchema);\nconst Account = model('Account', accountSchema);\nconst Verification = model('Verification', verificationSchema);\n\nexport { User, Session, Account, Verification };\n`],\n  [\"auth/better-auth/server/db/prisma/mongodb/prisma/schema/auth.prisma.hbs\", `model User {\n  id            String    @id @map(\"_id\")\n  name          String\n  email         String\n  emailVerified Boolean   @default(false)\n  image         String?\n  createdAt     DateTime  @default(now())\n  updatedAt     DateTime  @updatedAt\n  sessions      Session[]\n  accounts      Account[]\n\n  @@unique([email])\n  @@map(\"user\")\n}\n\nmodel Session {\n  id        String   @id @map(\"_id\")\n  expiresAt DateTime\n  token     String\n  createdAt DateTime @default(now())\n  updatedAt DateTime @updatedAt\n  ipAddress String?\n  userAgent String?\n  userId    String\n  user      User     @relation(fields: [userId], references: [id], onDelete: Cascade)\n\n  @@unique([token])\n  @@index([userId])\n  @@map(\"session\")\n}\n\nmodel Account {\n  id                    String    @id @map(\"_id\")\n  accountId             String\n  providerId            String\n  userId                String\n  user                  User      @relation(fields: [userId], references: [id], onDelete: Cascade)\n  accessToken           String?\n  refreshToken          String?\n  idToken               String?\n  accessTokenExpiresAt  DateTime?\n  refreshTokenExpiresAt DateTime?\n  scope                 String?\n  password              String?\n  createdAt             DateTime  @default(now())\n  updatedAt             DateTime  @updatedAt\n\n  @@index([userId])\n  @@map(\"account\")\n}\n\nmodel Verification {\n  id         String   @id @map(\"_id\")\n  identifier String\n  value      String\n  expiresAt  DateTime\n  createdAt  DateTime @default(now())\n  updatedAt  DateTime @updatedAt\n\n  @@index([identifier])\n  @@map(\"verification\")\n}\n`],\n  [\"auth/better-auth/server/db/prisma/mysql/prisma/schema/auth.prisma.hbs\", `model User {\n  id            String    @id\n  name          String    @db.Text\n  email         String\n  emailVerified Boolean   @default(false)\n  image         String?   @db.Text\n  createdAt     DateTime  @default(now())\n  updatedAt     DateTime  @updatedAt\n  sessions      Session[]\n  accounts      Account[]\n\n  @@unique([email])\n  @@map(\"user\")\n}\n\nmodel Session {\n  id        String   @id\n  expiresAt DateTime\n  token     String\n  createdAt DateTime @default(now())\n  updatedAt DateTime @updatedAt\n  ipAddress String?  @db.Text\n  userAgent String?  @db.Text\n  userId    String\n  user      User     @relation(fields: [userId], references: [id], onDelete: Cascade)\n\n  @@unique([token])\n  @@index([userId(length: 191)])\n  @@map(\"session\")\n}\n\nmodel Account {\n  id                    String    @id\n  accountId             String    @db.Text\n  providerId            String    @db.Text\n  userId                String\n  user                  User      @relation(fields: [userId], references: [id], onDelete: Cascade)\n  accessToken           String?   @db.Text\n  refreshToken          String?   @db.Text\n  idToken               String?   @db.Text\n  accessTokenExpiresAt  DateTime?\n  refreshTokenExpiresAt DateTime?\n  scope                 String?   @db.Text\n  password              String?   @db.Text\n  createdAt             DateTime  @default(now())\n  updatedAt             DateTime  @updatedAt\n\n  @@index([userId(length: 191)])\n  @@map(\"account\")\n}\n\nmodel Verification {\n  id         String   @id\n  identifier String   @db.Text\n  value      String   @db.Text\n  expiresAt  DateTime\n  createdAt  DateTime @default(now())\n  updatedAt  DateTime @updatedAt\n\n  @@index([identifier(length: 191)])\n  @@map(\"verification\")\n}\n`],\n  [\"auth/better-auth/server/db/prisma/postgres/prisma/schema/auth.prisma.hbs\", `model User {\n  id            String    @id\n  name          String\n  email         String\n  emailVerified Boolean   @default(false)\n  image         String?\n  createdAt     DateTime  @default(now())\n  updatedAt     DateTime  @updatedAt\n  sessions      Session[]\n  accounts      Account[]\n\n  @@unique([email])\n  @@map(\"user\")\n}\n\nmodel Session {\n  id        String   @id\n  expiresAt DateTime\n  token     String\n  createdAt DateTime @default(now())\n  updatedAt DateTime @updatedAt\n  ipAddress String?\n  userAgent String?\n  userId    String\n  user      User     @relation(fields: [userId], references: [id], onDelete: Cascade)\n\n  @@unique([token])\n  @@index([userId])\n  @@map(\"session\")\n}\n\nmodel Account {\n  id                    String    @id\n  accountId             String\n  providerId            String\n  userId                String\n  user                  User      @relation(fields: [userId], references: [id], onDelete: Cascade)\n  accessToken           String?\n  refreshToken          String?\n  idToken               String?\n  accessTokenExpiresAt  DateTime?\n  refreshTokenExpiresAt DateTime?\n  scope                 String?\n  password              String?\n  createdAt             DateTime  @default(now())\n  updatedAt             DateTime  @updatedAt\n\n  @@index([userId])\n  @@map(\"account\")\n}\n\nmodel Verification {\n  id         String   @id\n  identifier String\n  value      String\n  expiresAt  DateTime\n  createdAt  DateTime @default(now())\n  updatedAt  DateTime @updatedAt\n\n  @@index([identifier])\n  @@map(\"verification\")\n}\n`],\n  [\"auth/better-auth/server/db/prisma/sqlite/prisma/schema/auth.prisma.hbs\", `model User {\n  id            String    @id\n  name          String\n  email         String\n  emailVerified Boolean   @default(false)\n  image         String?\n  createdAt     DateTime  @default(now())\n  updatedAt     DateTime  @updatedAt\n  sessions      Session[]\n  accounts      Account[]\n\n  @@unique([email])\n  @@map(\"user\")\n}\n\nmodel Session {\n  id        String   @id\n  expiresAt DateTime\n  token     String\n  createdAt DateTime @default(now())\n  updatedAt DateTime @updatedAt\n  ipAddress String?\n  userAgent String?\n  userId    String\n  user      User     @relation(fields: [userId], references: [id], onDelete: Cascade)\n\n  @@unique([token])\n  @@index([userId])\n  @@map(\"session\")\n}\n\nmodel Account {\n  id                    String    @id\n  accountId             String\n  providerId            String\n  userId                String\n  user                  User      @relation(fields: [userId], references: [id], onDelete: Cascade)\n  accessToken           String?\n  refreshToken          String?\n  idToken               String?\n  accessTokenExpiresAt  DateTime?\n  refreshTokenExpiresAt DateTime?\n  scope                 String?\n  password              String?\n  createdAt             DateTime  @default(now())\n  updatedAt             DateTime  @updatedAt\n\n  @@index([userId])\n  @@map(\"account\")\n}\n\nmodel Verification {\n  id         String   @id\n  identifier String\n  value      String\n  expiresAt  DateTime\n  createdAt  DateTime @default(now())\n  updatedAt  DateTime @updatedAt\n\n  @@index([identifier])\n  @@map(\"verification\")\n}\n`],\n  [\"auth/better-auth/web/astro/src/components/SignInForm.astro.hbs\", `---\nimport { authClient } from \"../lib/auth-client\";\n---\n\n<div class=\"mx-auto mt-10 w-full max-w-md p-6\">\n  <h1 class=\"mb-6 text-center font-bold text-3xl text-white\">Welcome Back</h1>\n\n  <form id=\"signin-form\" class=\"space-y-4\">\n    <div class=\"space-y-1\">\n      <label for=\"email\" class=\"block text-sm font-medium text-neutral-300\">Email</label>\n      <input\n        id=\"email\"\n        name=\"email\"\n        type=\"email\"\n        required\n        class=\"w-full rounded-lg border border-neutral-700 bg-neutral-800 px-4 py-2.5 text-white placeholder-neutral-500 focus:border-indigo-500 focus:outline-none focus:ring-1 focus:ring-indigo-500\"\n        placeholder=\"you@example.com\"\n      />\n      <p id=\"email-error\" class=\"text-sm text-red-500 hidden\"></p>\n    </div>\n\n    <div class=\"space-y-1\">\n      <label for=\"password\" class=\"block text-sm font-medium text-neutral-300\">Password</label>\n      <input\n        id=\"password\"\n        name=\"password\"\n        type=\"password\"\n        required\n        class=\"w-full rounded-lg border border-neutral-700 bg-neutral-800 px-4 py-2.5 text-white placeholder-neutral-500 focus:border-indigo-500 focus:outline-none focus:ring-1 focus:ring-indigo-500\"\n        placeholder=\"••••••••\"\n      />\n      <p id=\"password-error\" class=\"text-sm text-red-500 hidden\"></p>\n    </div>\n\n    <p id=\"form-error\" class=\"text-sm text-red-500 hidden\"></p>\n\n    <button\n      type=\"submit\"\n      class=\"w-full rounded-lg bg-indigo-600 px-4 py-2.5 text-sm font-medium text-white hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 focus:ring-offset-neutral-900 disabled:opacity-50 disabled:cursor-not-allowed transition-colors\"\n    >\n      Sign In\n    </button>\n  </form>\n\n  <div class=\"mt-4 text-center\">\n    <a href=\"/signup\" class=\"text-indigo-400 hover:text-indigo-300 text-sm\">\n      Need an account? Sign Up\n    </a>\n  </div>\n</div>\n\n<script>\n  import { authClient } from \"../lib/auth-client\";\n\n  const form = document.getElementById(\"signin-form\") as HTMLFormElement;\n  const emailInput = document.getElementById(\"email\") as HTMLInputElement;\n  const passwordInput = document.getElementById(\"password\") as HTMLInputElement;\n  const formError = document.getElementById(\"form-error\") as HTMLParagraphElement;\n  const submitButton = form.querySelector('button[type=\"submit\"]') as HTMLButtonElement;\n\n  form.addEventListener(\"submit\", async (e) => {\n    e.preventDefault();\n    \n    formError.classList.add(\"hidden\");\n    submitButton.disabled = true;\n    submitButton.textContent = \"Signing in...\";\n\n    try {\n      await authClient.signIn.email(\n        {\n          email: emailInput.value,\n          password: passwordInput.value,\n        },\n        {\n          onSuccess: () => {\n            window.location.href = \"/dashboard\";\n          },\n          onError: (ctx) => {\n            formError.textContent = ctx.error.message || \"Sign in failed. Please try again.\";\n            formError.classList.remove(\"hidden\");\n          },\n        }\n      );\n    } catch (error) {\n      formError.textContent = \"An unexpected error occurred.\";\n      formError.classList.remove(\"hidden\");\n    } finally {\n      submitButton.disabled = false;\n      submitButton.textContent = \"Sign In\";\n    }\n  });\n</script>\n`],\n  [\"auth/better-auth/web/astro/src/components/SignUpForm.astro.hbs\", `---\nimport { authClient } from \"../lib/auth-client\";\n---\n\n<div class=\"mx-auto mt-10 w-full max-w-md p-6\">\n  <h1 class=\"mb-6 text-center font-bold text-3xl text-white\">Create Account</h1>\n\n  <form id=\"signup-form\" class=\"space-y-4\">\n    <div class=\"space-y-1\">\n      <label for=\"name\" class=\"block text-sm font-medium text-neutral-300\">Name</label>\n      <input\n        id=\"name\"\n        name=\"name\"\n        type=\"text\"\n        required\n        class=\"w-full rounded-lg border border-neutral-700 bg-neutral-800 px-4 py-2.5 text-white placeholder-neutral-500 focus:border-indigo-500 focus:outline-none focus:ring-1 focus:ring-indigo-500\"\n        placeholder=\"John Doe\"\n      />\n    </div>\n\n    <div class=\"space-y-1\">\n      <label for=\"email\" class=\"block text-sm font-medium text-neutral-300\">Email</label>\n      <input\n        id=\"email\"\n        name=\"email\"\n        type=\"email\"\n        required\n        class=\"w-full rounded-lg border border-neutral-700 bg-neutral-800 px-4 py-2.5 text-white placeholder-neutral-500 focus:border-indigo-500 focus:outline-none focus:ring-1 focus:ring-indigo-500\"\n        placeholder=\"you@example.com\"\n      />\n    </div>\n\n    <div class=\"space-y-1\">\n      <label for=\"password\" class=\"block text-sm font-medium text-neutral-300\">Password</label>\n      <input\n        id=\"password\"\n        name=\"password\"\n        type=\"password\"\n        required\n        minlength=\"8\"\n        class=\"w-full rounded-lg border border-neutral-700 bg-neutral-800 px-4 py-2.5 text-white placeholder-neutral-500 focus:border-indigo-500 focus:outline-none focus:ring-1 focus:ring-indigo-500\"\n        placeholder=\"••••••••\"\n      />\n      <p class=\"text-xs text-neutral-500\">Must be at least 8 characters</p>\n    </div>\n\n    <p id=\"form-error\" class=\"text-sm text-red-500 hidden\"></p>\n\n    <button\n      type=\"submit\"\n      class=\"w-full rounded-lg bg-indigo-600 px-4 py-2.5 text-sm font-medium text-white hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 focus:ring-offset-neutral-900 disabled:opacity-50 disabled:cursor-not-allowed transition-colors\"\n    >\n      Sign Up\n    </button>\n  </form>\n\n  <div class=\"mt-4 text-center\">\n    <a href=\"/login\" class=\"text-indigo-400 hover:text-indigo-300 text-sm\">\n      Already have an account? Sign In\n    </a>\n  </div>\n</div>\n\n<script>\n  import { authClient } from \"../lib/auth-client\";\n\n  const form = document.getElementById(\"signup-form\") as HTMLFormElement;\n  const nameInput = document.getElementById(\"name\") as HTMLInputElement;\n  const emailInput = document.getElementById(\"email\") as HTMLInputElement;\n  const passwordInput = document.getElementById(\"password\") as HTMLInputElement;\n  const formError = document.getElementById(\"form-error\") as HTMLParagraphElement;\n  const submitButton = form.querySelector('button[type=\"submit\"]') as HTMLButtonElement;\n\n  form.addEventListener(\"submit\", async (e) => {\n    e.preventDefault();\n    \n    formError.classList.add(\"hidden\");\n    submitButton.disabled = true;\n    submitButton.textContent = \"Creating account...\";\n\n    try {\n      await authClient.signUp.email(\n        {\n          name: nameInput.value,\n          email: emailInput.value,\n          password: passwordInput.value,\n        },\n        {\n          onSuccess: () => {\n            window.location.href = \"/dashboard\";\n          },\n          onError: (ctx) => {\n            formError.textContent = ctx.error.message || \"Sign up failed. Please try again.\";\n            formError.classList.remove(\"hidden\");\n          },\n        }\n      );\n    } catch (error) {\n      formError.textContent = \"An unexpected error occurred.\";\n      formError.classList.remove(\"hidden\");\n    } finally {\n      submitButton.disabled = false;\n      submitButton.textContent = \"Sign Up\";\n    }\n  });\n</script>\n`],\n  [\"auth/better-auth/web/astro/src/lib/auth-client.ts.hbs\", `import { createAuthClient } from \"better-auth/client\";\n{{#if (eq payments \"polar\")}}\nimport { polarClient } from \"@polar-sh/better-auth\";\n{{/if}}\n{{#if (ne backend \"self\")}}\nimport { PUBLIC_SERVER_URL } from \"astro:env/client\";\n{{/if}}\n\nexport const authClient = createAuthClient({\n{{#if (ne backend \"self\")}}\n  baseURL: PUBLIC_SERVER_URL,\n{{/if}}\n{{#if (eq payments \"polar\")}}\n  plugins: [polarClient()],\n{{/if}}\n});\n`],\n  [\"auth/better-auth/web/astro/src/pages/dashboard.astro.hbs\", `---\nimport Layout from \"../layouts/Layout.astro\";\n---\n\n<Layout title=\"Dashboard - {{projectName}}\">\n  <div id=\"dashboard-content\" class=\"hidden\">\n    <main class=\"mx-auto max-w-4xl px-4 py-8\">\n      <div class=\"rounded-xl border border-neutral-800 bg-neutral-900/50 p-8\">\n        <h1 class=\"text-3xl font-bold text-white mb-6\">Dashboard</h1>\n        \n        <div class=\"space-y-4\">\n          <div class=\"rounded-lg bg-neutral-800/50 p-4\">\n            <p class=\"text-sm text-neutral-400\">Welcome back,</p>\n            <p id=\"user-name\" class=\"text-xl font-medium text-white\">Loading...</p>\n          </div>\n          \n          <div class=\"rounded-lg bg-neutral-800/50 p-4\">\n            <p class=\"text-sm text-neutral-400 mb-2\">Email</p>\n            <p id=\"user-email\" class=\"text-white\">Loading...</p>\n          </div>\n\n          {{#if (eq api \"orpc\")}}\n          <div class=\"rounded-lg bg-neutral-800/50 p-4\">\n            <p class=\"text-sm text-neutral-400 mb-2\">Server Message</p>\n            <p id=\"api-message\" class=\"text-white\">Loading...</p>\n          </div>\n          {{/if}}\n\n          {{#if (eq payments \"polar\")}}\n          <div class=\"rounded-lg bg-neutral-800/50 p-4\">\n            <p class=\"text-sm text-neutral-400 mb-2\">Subscription</p>\n            <div id=\"subscription-info\" class=\"space-y-2\">\n              <p class=\"text-white\">Loading...</p>\n            </div>\n          </div>\n          {{/if}}\n        </div>\n      </div>\n    </main>\n  </div>\n\n  <div id=\"loading\" class=\"flex h-[calc(100vh-4rem)] items-center justify-center\">\n    <p class=\"text-neutral-400\">Loading...</p>\n  </div>\n\n  <div id=\"redirect\" class=\"hidden flex h-[calc(100vh-4rem)] items-center justify-center\">\n    <p class=\"text-neutral-400\">Redirecting to login...</p>\n  </div>\n</Layout>\n\n<script>\n  import { authClient } from \"../lib/auth-client\";\n  {{#if (eq api \"orpc\")}}\n  import { orpc } from \"../lib/orpc\";\n  {{/if}}\n\n  const dashboardContent = document.getElementById(\"dashboard-content\")!;\n  const loading = document.getElementById(\"loading\")!;\n  const redirect = document.getElementById(\"redirect\")!;\n  const userName = document.getElementById(\"user-name\")!;\n  const userEmail = document.getElementById(\"user-email\")!;\n  {{#if (eq api \"orpc\")}}\n  const apiMessage = document.getElementById(\"api-message\")!;\n  {{/if}}\n\n  async function init() {\n    try {\n      const { data: session } = await authClient.getSession();\n      \n      if (!session?.user) {\n        loading.classList.add(\"hidden\");\n        redirect.classList.remove(\"hidden\");\n        window.location.href = \"/login\";\n        return;\n      }\n\n      userName.textContent = session.user.name || \"User\";\n      userEmail.textContent = session.user.email || \"\";\n\n      {{#if (eq api \"orpc\")}}\n      try {\n        const data = await orpc.privateData();\n        apiMessage.textContent = data.message || \"Connected to server\";\n      } catch (e) {\n        apiMessage.textContent = \"Failed to load server data\";\n      }\n      {{/if}}\n\n      {{#if (eq payments \"polar\")}}\n      try {\n        const { data: customerState } = await authClient.customer.state();\n        const subscriptionInfo = document.getElementById(\"subscription-info\")!;\n        if (customerState?.activeSubscriptions?.length > 0) {\n          subscriptionInfo.innerHTML = \\`\n            <p class=\"text-white\">Plan: <span class=\"text-green-400\">Pro</span></p>\n            <button\n              id=\"manage-subscription\"\n              class=\"mt-2 rounded px-3 py-1.5 text-sm bg-neutral-700 hover:bg-neutral-600 text-white transition-colors\"\n            >\n              Manage Subscription\n            </button>\n          \\`;\n          document.getElementById(\"manage-subscription\")?.addEventListener(\"click\", async () => {\n            await authClient.customer.portal();\n          });\n        } else {\n          subscriptionInfo.innerHTML = \\`\n            <p class=\"text-white\">Plan: <span class=\"text-neutral-400\">Free</span></p>\n            <button\n              id=\"upgrade-button\"\n              class=\"mt-2 rounded px-3 py-1.5 text-sm bg-indigo-600 hover:bg-indigo-700 text-white transition-colors\"\n            >\n              Upgrade to Pro\n            </button>\n          \\`;\n          document.getElementById(\"upgrade-button\")?.addEventListener(\"click\", async () => {\n            await authClient.checkout({ slug: \"pro\" });\n          });\n        }\n      } catch (e) {\n        console.error(\"Failed to load subscription info\", e);\n      }\n      {{/if}}\n\n      loading.classList.add(\"hidden\");\n      dashboardContent.classList.remove(\"hidden\");\n    } catch (error) {\n      loading.classList.add(\"hidden\");\n      redirect.classList.remove(\"hidden\");\n      window.location.href = \"/login\";\n    }\n  }\n\n  init();\n</script>\n`],\n  [\"auth/better-auth/web/astro/src/pages/login.astro.hbs\", `---\nimport SignInForm from \"../components/SignInForm.astro\";\nimport Layout from \"../layouts/Layout.astro\";\n---\n\n<Layout title=\"Sign In - {{projectName}}\">\n  <div class=\"flex min-h-[calc(100vh-4rem)] items-center justify-center\">\n    <SignInForm />\n  </div>\n</Layout>\n`],\n  [\"auth/better-auth/web/astro/src/pages/signup.astro.hbs\", `---\nimport SignUpForm from \"../components/SignUpForm.astro\";\nimport Layout from \"../layouts/Layout.astro\";\n---\n\n<Layout title=\"Sign Up - {{projectName}}\">\n  <div class=\"flex min-h-[calc(100vh-4rem)] items-center justify-center\">\n    <SignUpForm />\n  </div>\n</Layout>\n`],\n  [\"auth/better-auth/web/nuxt/app/components/SignInForm.vue.hbs\", `<script setup lang=\"ts\">\nimport * as z from 'zod'\nimport type { FormSubmitEvent, AuthFormField } from '@nuxt/ui'\n\nconst { $authClient } = useNuxtApp()\n\nconst emit = defineEmits(['switchToSignUp'])\n\nconst toast = useToast()\nconst loading = ref(false)\n\nconst fields: AuthFormField[] = [\n  {\n    name: 'email',\n    type: 'email',\n    label: 'Email',\n    placeholder: 'Enter your email',\n    required: true\n  },\n  {\n    name: 'password',\n    type: 'password',\n    label: 'Password',\n    placeholder: 'Enter your password',\n    required: true\n  }\n]\n\nconst schema = z.object({\n  email: z.email('Invalid email address'),\n  password: z.string().min(8, 'Password must be at least 8 characters'),\n})\n\ntype Schema = z.output<typeof schema>\n\nasync function onSubmit(event: FormSubmitEvent<Schema>) {\n  loading.value = true\n  try {\n    await $authClient.signIn.email(\n      {\n        email: event.data.email,\n        password: event.data.password,\n      },\n      {\n        onSuccess: () => {\n          toast.add({ title: 'Sign in successful' })\n          navigateTo('/dashboard', { replace: true })\n        },\n        onError: (error) => {\n          toast.add({ title: 'Sign in failed', description: error.error.message })\n        },\n      },\n    )\n  } catch (error: any) {\n    toast.add({ title: 'An unexpected error occurred', description: error.message || 'Please try again.' })\n  } finally {\n    loading.value = false\n  }\n}\n</script>\n\n<template>\n  <div class=\"flex flex-col items-center justify-center gap-4 p-4\">\n    <UPageCard class=\"w-full max-w-md\">\n      <UAuthForm\n        :schema=\"schema\"\n        :fields=\"fields\"\n        title=\"Welcome Back\"\n        icon=\"i-lucide-log-in\"\n        :submit=\"{ label: 'Sign In', loading }\"\n        @submit=\"onSubmit\"\n      >\n        <template #description>\n          Need an account?\n          <ULink class=\"text-primary font-medium\" @click=\"$emit('switchToSignUp')\">\n            Sign Up\n          </ULink>\n        </template>\n      </UAuthForm>\n    </UPageCard>\n  </div>\n</template>\n`],\n  [\"auth/better-auth/web/nuxt/app/components/SignUpForm.vue.hbs\", `<script setup lang=\"ts\">\nimport * as z from 'zod'\nimport type { FormSubmitEvent, AuthFormField } from '@nuxt/ui'\n\nconst { $authClient } = useNuxtApp()\n\nconst emit = defineEmits(['switchToSignIn'])\n\nconst toast = useToast()\nconst loading = ref(false)\n\nconst fields: AuthFormField[] = [\n  {\n    name: 'name',\n    type: 'text',\n    label: 'Name',\n    placeholder: 'Enter your name',\n    required: true\n  },\n  {\n    name: 'email',\n    type: 'email',\n    label: 'Email',\n    placeholder: 'Enter your email',\n    required: true\n  },\n  {\n    name: 'password',\n    type: 'password',\n    label: 'Password',\n    placeholder: 'Enter your password',\n    required: true\n  }\n]\n\nconst schema = z.object({\n  name: z.string().min(2, 'Name must be at least 2 characters'),\n  email: z.email('Invalid email address'),\n  password: z.string().min(8, 'Password must be at least 8 characters'),\n})\n\ntype Schema = z.output<typeof schema>\n\nasync function onSubmit(event: FormSubmitEvent<Schema>) {\n  loading.value = true\n  try {\n    await $authClient.signUp.email(\n      {\n        name: event.data.name,\n        email: event.data.email,\n        password: event.data.password,\n      },\n      {\n        onSuccess: () => {\n          toast.add({ title: 'Sign up successful' })\n          navigateTo('/dashboard', { replace: true })\n        },\n        onError: (error) => {\n          toast.add({ title: 'Sign up failed', description: error.error.message })\n        },\n      },\n    )\n  } catch (error: any) {\n    toast.add({ title: 'An unexpected error occurred', description: error.message || 'Please try again.' })\n  } finally {\n    loading.value = false\n  }\n}\n</script>\n\n<template>\n  <div class=\"flex flex-col items-center justify-center gap-4 p-4\">\n    <UPageCard class=\"w-full max-w-md\">\n      <UAuthForm\n        :schema=\"schema\"\n        :fields=\"fields\"\n        title=\"Create Account\"\n        icon=\"i-lucide-user-plus\"\n        :submit=\"{ label: 'Sign Up', loading }\"\n        @submit=\"onSubmit\"\n      >\n        <template #description>\n          Already have an account?\n          <ULink class=\"text-primary font-medium\" @click=\"$emit('switchToSignIn')\">\n            Sign In\n          </ULink>\n        </template>\n      </UAuthForm>\n    </UPageCard>\n  </div>\n</template>\n`],\n  [\"auth/better-auth/web/nuxt/app/components/UserMenu.vue.hbs\", `<script setup lang=\"ts\">\n\nconst {$authClient} = useNuxtApp()\nconst session = $authClient.useSession()\nconst toast = useToast()\n\nconst handleSignOut = async () => {\n  try {\n    await $authClient.signOut({\n      fetchOptions: {\n        onSuccess: async () => {\n          toast.add({ title: 'Signed out successfully' })\n          await navigateTo('/', { replace: true, external: true })\n        },\n        onError: (error) => {\n           toast.add({ title: 'Sign out failed', description: error?.error?.message || 'Unknown error'})\n        }\n      },\n    })\n  } catch (error: any) {\n     toast.add({ title: 'An unexpected error occurred during sign out', description: error.message || 'Please try again.'})\n  }\n}\n</script>\n\n<template>\n  <div>\n    <USkeleton v-if=\"session.isPending\" class=\"h-9 w-24\" />\n\n    <UButton v-else-if=\"!session.data\" variant=\"outline\" to=\"/login\">\n      Sign In\n    </UButton>\n\n    <UButton\n      v-else\n      variant=\"solid\"\n      icon=\"i-lucide-log-out\"\n      label=\"Sign out\"\n      @click=\"handleSignOut()\"\n    />\n  </div>\n</template>\n`],\n  [\"auth/better-auth/web/nuxt/app/middleware/auth.ts.hbs\", `export default defineNuxtRouteMiddleware(async (to, from) => {\n  if (import.meta.server) return;\n\n  const { $authClient } = useNuxtApp();\n  const session = $authClient.useSession();\n\n  if (session.value.isPending) {\n    return;\n  }\n\n  if (!session.value.data) {\n    return navigateTo(\"/login\");\n  }\n});\n`],\n  [\"auth/better-auth/web/nuxt/app/pages/dashboard.vue.hbs\", `<script setup lang=\"ts\">\n{{#if (eq api \"orpc\")}}\nimport { useQuery } from '@tanstack/vue-query'\n{{/if}}\n\nconst { $authClient, $orpc } = useNuxtApp()\n\ndefinePageMeta({\n  middleware: ['auth']\n})\n\nconst session = $authClient.useSession()\n\n{{#if (eq payments \"polar\")}}\nconst customerState = ref<any>(null)\n{{/if}}\n\n{{#if (eq api \"orpc\")}}\nconst privateData = useQuery({\n  ...$orpc.privateData.queryOptions(),\n  enabled: computed(() => !!session.value?.data?.user)\n})\n{{/if}}\n\n{{#if (eq payments \"polar\")}}\nonMounted(async () => {\n  if (session.value?.data) {\n    const { data } = await $authClient.customer.state()\n    customerState.value = data\n  }\n})\n\nconst hasProSubscription = computed(() => \n  customerState.value?.activeSubscriptions?.length! > 0\n)\n{{/if}}\n</script>\n\n<template>\n  <UContainer class=\"py-8\">\n    <UPageHeader\n      title=\"Dashboard\"\n      :description=\"session?.data?.user ? \\`Welcome back, \\${session.data.user.name}!\\` : 'Loading...'\"\n    />\n\n    <div class=\"mt-6 space-y-4\">\n      {{#if (eq api \"orpc\")}}\n      <UCard>\n        <template #header>\n          <div class=\"font-medium\">Private Data</div>\n        </template>\n\n        <USkeleton v-if=\"privateData.status.value === 'pending'\" class=\"h-6 w-48\" />\n\n        <UAlert\n          v-else-if=\"privateData.status.value === 'error'\"\n          color=\"error\"\n          icon=\"i-lucide-alert-circle\"\n          title=\"Error loading data\"\n          :description=\"privateData.error.value?.message || 'Failed to load private data'\"\n        />\n\n        <div v-else-if=\"privateData.data.value\" class=\"flex items-center gap-2\">\n          <UIcon name=\"i-lucide-check-circle\" class=\"text-success\" />\n          <span>\\\\{{ privateData.data.value.message }}</span>\n        </div>\n      </UCard>\n      {{/if}}\n\n      {{#if (eq payments \"polar\")}}\n      <UCard>\n        <template #header>\n          <div class=\"font-medium\">Subscription</div>\n        </template>\n\n        <div class=\"flex items-center justify-between\">\n          <div class=\"flex items-center gap-2\">\n            <UIcon :name=\"hasProSubscription ? 'i-lucide-crown' : 'i-lucide-user'\" :class=\"hasProSubscription ? 'text-warning' : 'text-muted'\" />\n            <span>Plan: \\\\{{ hasProSubscription ? \"Pro\" : \"Free\" }}</span>\n          </div>\n          <UButton \n            v-if=\"hasProSubscription\"\n            variant=\"outline\"\n            @click=\"() => { $authClient.customer.portal() }\"\n          >\n            Manage Subscription\n          </UButton>\n          <UButton \n            v-else\n            @click=\"() => { $authClient.checkout({ slug: 'pro' }) }\"\n          >\n            Upgrade to Pro\n          </UButton>\n        </div>\n      </UCard>\n      {{/if}}\n    </div>\n  </UContainer>\n</template>\n`],\n  [\"auth/better-auth/web/nuxt/app/pages/login.vue.hbs\", `<script setup lang=\"ts\">\nconst { $authClient } = useNuxtApp();\nimport SignInForm from \"~/components/SignInForm.vue\";\nimport SignUpForm from \"~/components/SignUpForm.vue\";\n\nconst session = $authClient.useSession();\nconst showSignIn = ref(true);\n\nwatchEffect(() => {\n  if (!session?.value.isPending && session?.value.data) {\n    navigateTo(\"/dashboard\", { replace: true });\n  }\n});\n</script>\n\n<template>\n  <UContainer class=\"py-8\">\n    <div v-if=\"session.isPending\" class=\"flex flex-col items-center justify-center gap-4 py-12\">\n      <UIcon name=\"i-lucide-loader-2\" class=\"animate-spin text-4xl text-primary\" />\n      <span class=\"text-muted\">Loading...</span>\n    </div>\n    <div v-else-if=\"!session.data\">\n      <SignInForm v-if=\"showSignIn\" @switch-to-sign-up=\"showSignIn = false\" />\n      <SignUpForm v-else @switch-to-sign-in=\"showSignIn = true\" />\n    </div>\n  </UContainer>\n</template>\n`],\n  [\"auth/better-auth/web/nuxt/app/plugins/auth-client.ts.hbs\", `import { createAuthClient } from \"better-auth/vue\";\n{{#if (eq payments \"polar\")}}\nimport { polarClient } from \"@polar-sh/better-auth\";\n{{/if}}\n\nexport default defineNuxtPlugin(() => {\n  {{#if (ne backend \"self\")}}\n  const config = useRuntimeConfig();\n  {{/if}}\n\n  const authClient = createAuthClient({\n    {{#if (ne backend \"self\")}}\n    baseURL: config.public.serverUrl,\n    {{/if}}\n    {{#if (eq payments \"polar\")}}\n    plugins: [polarClient()],\n    {{/if}}\n  });\n\n  return {\n    provide: {\n      authClient: authClient,\n    },\n  };\n});\n`],\n  [\"auth/better-auth/web/react/base/src/lib/auth-client.ts.hbs\", `import { createAuthClient } from \"better-auth/react\";\n{{#if (eq payments \"polar\")}}\nimport { polarClient } from \"@polar-sh/better-auth\";\n{{/if}}\n{{#unless (eq backend \"self\")}}\nimport { env } from \"@{{projectName}}/env/web\";\n{{/unless}}\n\nexport const authClient = createAuthClient({\n{{#unless (eq backend \"self\")}}\n\tbaseURL: env.{{#if (includes frontend \"next\")}}NEXT_PUBLIC_SERVER_URL{{else}}VITE_SERVER_URL{{/if}},\n{{/unless}}\n{{#if (eq payments \"polar\")}}\n\tplugins: [polarClient()]\n{{/if}}\n});\n`],\n  [\"auth/better-auth/web/react/next/src/app/dashboard/dashboard.tsx.hbs\", `\"use client\";\n{{#if (eq payments \"polar\")}}\nimport { Button } from \"@{{projectName}}/ui/components/button\";\n{{/if}}\nimport { authClient } from \"@/lib/auth-client\";\n{{#if (eq api \"orpc\")}}\nimport { useQuery } from \"@tanstack/react-query\";\nimport { orpc } from \"@/utils/orpc\";\n{{/if}}\n{{#if (eq api \"trpc\")}}\nimport { useQuery } from \"@tanstack/react-query\";\nimport { trpc } from \"@/utils/trpc\";\n{{/if}}\n\nexport default function Dashboard({\n\t{{#if (eq payments \"polar\")}}\n\tcustomerState,\n\t{{/if}}\n\tsession\n}: {\n\t{{#if (eq payments \"polar\")}}\n\tcustomerState: ReturnType<typeof authClient.customer.state>;\n\t{{/if}}\n\tsession: typeof authClient.$Infer.Session;\n}) {\n\t{{#if (eq api \"orpc\")}}\n\tconst privateData = useQuery(orpc.privateData.queryOptions());\n\t{{/if}}\n\t{{#if (eq api \"trpc\")}}\n\tconst privateData = useQuery(trpc.privateData.queryOptions());\n\t{{/if}}\n\n\t{{#if (eq payments \"polar\")}}\n\tconst hasProSubscription = customerState?.activeSubscriptions?.length! > 0;\n\tconsole.log(\"Active subscriptions:\", customerState?.activeSubscriptions);\n\t{{/if}}\n\n\treturn (\n\t\t<>\n\t\t\t{{#if (eq api \"orpc\")}}\n\t\t\t<p>API: {privateData.data?.message}</p>\n\t\t\t{{/if}}\n\t\t\t{{#if (eq api \"trpc\")}}\n\t\t\t<p>API: {privateData.data?.message}</p>\n\t\t\t{{/if}}\n\t\t\t{{#if (eq payments \"polar\")}}\n\t\t\t<p>Plan: {hasProSubscription ? \"Pro\" : \"Free\"}</p>\n\t\t\t{hasProSubscription ? (\n\t\t\t\t<Button onClick={async () => await authClient.customer.portal()}>\n\t\t\t\t\tManage Subscription\n\t\t\t\t</Button>\n\t\t\t) : (\n\t\t\t\t<Button onClick={async () => await authClient.checkout({ slug: \"pro\" })}>\n\t\t\t\t\tUpgrade to Pro\n\t\t\t\t</Button>\n\t\t\t)}\n\t\t\t{{/if}}\n\t\t</>\n\t);\n}\n`],\n  [\"auth/better-auth/web/react/next/src/app/dashboard/page.tsx.hbs\", `import { redirect } from \"next/navigation\";\nimport Dashboard from \"./dashboard\";\nimport { headers } from \"next/headers\";\n{{#if (eq backend \"self\")}}\n{{#if (or (eq runtime \"workers\") (eq serverDeploy \"cloudflare\") (and (eq backend \"self\") (eq webDeploy \"cloudflare\")))}}\nimport { createAuth } from \"@{{projectName}}/auth\";\n{{else}}\nimport { auth } from \"@{{projectName}}/auth\";\n{{/if}}\n{{/if}}\n{{#if (or (ne backend \"self\") (eq payments \"polar\"))}}\nimport { authClient } from \"@/lib/auth-client\";\n{{/if}}\n\nexport default async function DashboardPage() {\n\t{{#if (eq backend \"self\")}}\n\tconst session = await {{#if (or (eq runtime \"workers\") (eq serverDeploy \"cloudflare\") (and (eq backend \"self\") (eq webDeploy \"cloudflare\")))}}createAuth(){{else}}auth{{/if}}.api.getSession({\n\t\theaders: await headers(),\n\t});\n\t{{else}}\n\tconst session = await authClient.getSession({\n\t\tfetchOptions: {\n\t\t\theaders: await headers(),\n\t\t\tthrow: true\n\t\t}\n\t});\n\t{{/if}}\n\n\tif (!session?.user) {\n\t\tredirect(\"/login\");\n\t}\n\n\t{{#if (eq payments \"polar\")}}\n\tconst { data: customerState } = await authClient.customer.state({\n\t\tfetchOptions: {\n\t\t\theaders: await headers(),\n\t\t},\n\t});\n\t{{/if}}\n\n\treturn (\n\t\t<div>\n\t\t\t<h1>Dashboard</h1>\n\t\t\t<p>Welcome {session.user.name}</p>\n\t\t\t<Dashboard session={session} {{#if (eq payments \"polar\")}}customerState={customerState}{{/if}} />\n\t\t</div>\n\t);\n}\n`],\n  [\"auth/better-auth/web/react/next/src/app/login/page.tsx.hbs\", `\"use client\"\n\nimport SignInForm from \"@/components/sign-in-form\";\nimport SignUpForm from \"@/components/sign-up-form\";\nimport { useState } from \"react\";\n\n\nexport default function LoginPage() {\n  const [showSignIn, setShowSignIn] = useState(false);\n\n  return showSignIn ? (\n    <SignInForm onSwitchToSignUp={() => setShowSignIn(false)} />\n  ) : (\n    <SignUpForm onSwitchToSignIn={() => setShowSignIn(true)} />\n  );\n}\n`],\n  [\"auth/better-auth/web/react/next/src/components/sign-in-form.tsx.hbs\", `import { authClient } from \"@/lib/auth-client\";\nimport { useForm } from \"@tanstack/react-form\";\nimport { toast } from \"sonner\";\nimport z from \"zod\";\nimport Loader from \"./loader\";\nimport { Button } from \"@{{projectName}}/ui/components/button\";\nimport { Input } from \"@{{projectName}}/ui/components/input\";\nimport { Label } from \"@{{projectName}}/ui/components/label\";\nimport { useRouter } from \"next/navigation\";\n\nexport default function SignInForm({\n  onSwitchToSignUp,\n}: {\n  onSwitchToSignUp: () => void;\n}) {\n  const router = useRouter()\n  const { isPending } = authClient.useSession();\n\n  const form = useForm({\n    defaultValues: {\n      email: \"\",\n      password: \"\",\n    },\n    onSubmit: async ({ value }) => {\n      await authClient.signIn.email(\n        {\n          email: value.email,\n          password: value.password,\n        },\n        {\n          onSuccess: () => {\n            router.push(\"/dashboard\")\n            toast.success(\"Sign in successful\");\n          },\n          onError: (error) => {\n            toast.error(error.error.message || error.error.statusText);\n          },\n        },\n      );\n    },\n    validators: {\n      onSubmit: z.object({\n        email: z.email(\"Invalid email address\"),\n        password: z.string().min(8, \"Password must be at least 8 characters\"),\n      }),\n    },\n  });\n\n  if (isPending) {\n    return <Loader />;\n  }\n\n  return (\n    <div className=\"mx-auto w-full mt-10 max-w-md p-6\">\n      <h1 className=\"mb-6 text-center text-3xl font-bold\">Welcome Back</h1>\n\n      <form\n        onSubmit={(e) => {\n          e.preventDefault();\n          e.stopPropagation();\n          form.handleSubmit();\n        }}\n        className=\"space-y-4\"\n      >\n        <div>\n          <form.Field name=\"email\">\n            {(field) => (\n              <div className=\"space-y-2\">\n                <Label htmlFor={field.name}>Email</Label>\n                <Input\n                  id={field.name}\n                  name={field.name}\n                  type=\"email\"\n                  value={field.state.value}\n                  onBlur={field.handleBlur}\n                  onChange={(e) => field.handleChange(e.target.value)}\n                />\n                {field.state.meta.errors.map((error) => (\n                  <p key={error?.message} className=\"text-red-500\">\n                    {error?.message}\n                  </p>\n                ))}\n              </div>\n            )}\n          </form.Field>\n        </div>\n\n        <div>\n          <form.Field name=\"password\">\n            {(field) => (\n              <div className=\"space-y-2\">\n                <Label htmlFor={field.name}>Password</Label>\n                <Input\n                  id={field.name}\n                  name={field.name}\n                  type=\"password\"\n                  value={field.state.value}\n                  onBlur={field.handleBlur}\n                  onChange={(e) => field.handleChange(e.target.value)}\n                />\n                {field.state.meta.errors.map((error) => (\n                  <p key={error?.message} className=\"text-red-500\">\n                    {error?.message}\n                  </p>\n                ))}\n              </div>\n            )}\n          </form.Field>\n        </div>\n\n        <form.Subscribe selector={(state) => ({ canSubmit: state.canSubmit, isSubmitting: state.isSubmitting })}>\n          {({ canSubmit, isSubmitting }) => (\n            <Button\n              type=\"submit\"\n              className=\"w-full\"\n              disabled={!canSubmit || isSubmitting}\n            >\n              {isSubmitting ? \"Submitting...\" : \"Sign In\"}\n            </Button>\n          )}\n        </form.Subscribe>\n      </form>\n\n      <div className=\"mt-4 text-center\">\n        <Button\n          variant=\"link\"\n          onClick={onSwitchToSignUp}\n          className=\"text-indigo-600 hover:text-indigo-800\"\n        >\n          Need an account? Sign Up\n        </Button>\n      </div>\n    </div>\n  );\n}\n`],\n  [\"auth/better-auth/web/react/next/src/components/sign-up-form.tsx.hbs\", `import { authClient } from \"@/lib/auth-client\";\nimport { useForm } from \"@tanstack/react-form\";\nimport { toast } from \"sonner\";\nimport z from \"zod\";\nimport Loader from \"./loader\";\nimport { Button } from \"@{{projectName}}/ui/components/button\";\nimport { Input } from \"@{{projectName}}/ui/components/input\";\nimport { Label } from \"@{{projectName}}/ui/components/label\";\nimport { useRouter } from \"next/navigation\";\n\nexport default function SignUpForm({\n  onSwitchToSignIn,\n}: {\n  onSwitchToSignIn: () => void;\n}) {\n  const router = useRouter();\n  const { isPending } = authClient.useSession();\n\n  const form = useForm({\n    defaultValues: {\n      email: \"\",\n      password: \"\",\n      name: \"\",\n    },\n    onSubmit: async ({ value }) => {\n      await authClient.signUp.email(\n        {\n          email: value.email,\n          password: value.password,\n          name: value.name,\n        },\n        {\n          onSuccess: () => {\n            router.push(\"/dashboard\");\n            toast.success(\"Sign up successful\");\n          },\n          onError: (error) => {\n            toast.error(error.error.message || error.error.statusText);\n          },\n        },\n      );\n    },\n    validators: {\n      onSubmit: z.object({\n        name: z.string().min(2, \"Name must be at least 2 characters\"),\n        email: z.email(\"Invalid email address\"),\n        password: z.string().min(8, \"Password must be at least 8 characters\"),\n      }),\n    },\n  });\n\n  if (isPending) {\n    return <Loader />;\n  }\n\n  return (\n    <div className=\"mx-auto w-full mt-10 max-w-md p-6\">\n      <h1 className=\"mb-6 text-center text-3xl font-bold\">Create Account</h1>\n\n      <form\n        onSubmit={(e) => {\n          e.preventDefault();\n          e.stopPropagation();\n          form.handleSubmit();\n        }}\n        className=\"space-y-4\"\n      >\n        <div>\n          <form.Field name=\"name\">\n            {(field) => (\n              <div className=\"space-y-2\">\n                <Label htmlFor={field.name}>Name</Label>\n                <Input\n                  id={field.name}\n                  name={field.name}\n                  value={field.state.value}\n                  onBlur={field.handleBlur}\n                  onChange={(e) => field.handleChange(e.target.value)}\n                />\n                {field.state.meta.errors.map((error) => (\n                  <p key={error?.message} className=\"text-red-500\">\n                    {error?.message}\n                  </p>\n                ))}\n              </div>\n            )}\n          </form.Field>\n        </div>\n\n        <div>\n          <form.Field name=\"email\">\n            {(field) => (\n              <div className=\"space-y-2\">\n                <Label htmlFor={field.name}>Email</Label>\n                <Input\n                  id={field.name}\n                  name={field.name}\n                  type=\"email\"\n                  value={field.state.value}\n                  onBlur={field.handleBlur}\n                  onChange={(e) => field.handleChange(e.target.value)}\n                />\n                {field.state.meta.errors.map((error) => (\n                  <p key={error?.message} className=\"text-red-500\">\n                    {error?.message}\n                  </p>\n                ))}\n              </div>\n            )}\n          </form.Field>\n        </div>\n\n        <div>\n          <form.Field name=\"password\">\n            {(field) => (\n              <div className=\"space-y-2\">\n                <Label htmlFor={field.name}>Password</Label>\n                <Input\n                  id={field.name}\n                  name={field.name}\n                  type=\"password\"\n                  value={field.state.value}\n                  onBlur={field.handleBlur}\n                  onChange={(e) => field.handleChange(e.target.value)}\n                />\n                {field.state.meta.errors.map((error) => (\n                  <p key={error?.message} className=\"text-red-500\">\n                    {error?.message}\n                  </p>\n                ))}\n              </div>\n            )}\n          </form.Field>\n        </div>\n\n        <form.Subscribe selector={(state) => ({ canSubmit: state.canSubmit, isSubmitting: state.isSubmitting })}>\n          {({ canSubmit, isSubmitting }) => (\n            <Button\n              type=\"submit\"\n              className=\"w-full\"\n              disabled={!canSubmit || isSubmitting}\n            >\n              {isSubmitting ? \"Submitting...\" : \"Sign Up\"}\n            </Button>\n          )}\n        </form.Subscribe>\n      </form>\n\n      <div className=\"mt-4 text-center\">\n        <Button\n          variant=\"link\"\n          onClick={onSwitchToSignIn}\n          className=\"text-indigo-600 hover:text-indigo-800\"\n        >\n          Already have an account? Sign In\n        </Button>\n      </div>\n    </div>\n  );\n}\n`],\n  [\"auth/better-auth/web/react/next/src/components/user-menu.tsx.hbs\", `import Link from \"next/link\";\nimport { useRouter } from \"next/navigation\";\n\nimport {\n  DropdownMenu,\n  DropdownMenuContent,\n  DropdownMenuGroup,\n  DropdownMenuItem,\n  DropdownMenuLabel,\n  DropdownMenuSeparator,\n  DropdownMenuTrigger,\n} from \"@{{projectName}}/ui/components/dropdown-menu\";\nimport { authClient } from \"@/lib/auth-client\";\n\nimport { Button } from \"@{{projectName}}/ui/components/button\";\nimport { Skeleton } from \"@{{projectName}}/ui/components/skeleton\";\n\nexport default function UserMenu() {\n  const router = useRouter();\n  const { data: session, isPending } = authClient.useSession();\n\n  if (isPending) {\n    return <Skeleton className=\"h-9 w-24\" />;\n  }\n\n  if (!session) {\n    return (\n      <Link href=\"/login\">\n        <Button variant=\"outline\">Sign In</Button>\n      </Link>\n    );\n  }\n\n  return (\n    <DropdownMenu>\n      <DropdownMenuTrigger render={<Button variant=\"outline\" />}>\n        {session.user.name}\n      </DropdownMenuTrigger>\n      <DropdownMenuContent className=\"bg-card\">\n        <DropdownMenuGroup>\n          <DropdownMenuLabel>My Account</DropdownMenuLabel>\n          <DropdownMenuSeparator />\n          <DropdownMenuItem>{session.user.email}</DropdownMenuItem>\n          <DropdownMenuItem\n            variant=\"destructive\"\n            onClick={() => {\n              authClient.signOut({\n                fetchOptions: {\n                  onSuccess: () => {\n                    router.push(\"/\");\n                  },\n                },\n              });\n            }}\n          >\n            Sign Out\n          </DropdownMenuItem>\n        </DropdownMenuGroup>\n      </DropdownMenuContent>\n    </DropdownMenu>\n  );\n}\n`],\n  [\"auth/better-auth/web/react/react-router/src/components/sign-in-form.tsx.hbs\", `import { authClient } from \"@/lib/auth-client\";\nimport { useForm } from \"@tanstack/react-form\";\nimport { useNavigate } from \"react-router\";\nimport { toast } from \"sonner\";\nimport z from \"zod\";\nimport Loader from \"./loader\";\nimport { Button } from \"@{{projectName}}/ui/components/button\";\nimport { Input } from \"@{{projectName}}/ui/components/input\";\nimport { Label } from \"@{{projectName}}/ui/components/label\";\n\nexport default function SignInForm({\n  onSwitchToSignUp,\n}: {\n  onSwitchToSignUp: () => void;\n}) {\n  const navigate = useNavigate();\n  const { isPending } = authClient.useSession();\n\n  const form = useForm({\n    defaultValues: {\n      email: \"\",\n      password: \"\",\n    },\n    onSubmit: async ({ value }) => {\n      await authClient.signIn.email(\n        {\n          email: value.email,\n          password: value.password,\n        },\n        {\n          onSuccess: () => {\n            navigate(\"/dashboard\");\n            toast.success(\"Sign in successful\");\n          },\n          onError: (error) => {\n            toast.error(error.error.message || error.error.statusText);\n          },\n        }\n      );\n    },\n    validators: {\n      onSubmit: z.object({\n        email: z.email(\"Invalid email address\"),\n        password: z.string().min(8, \"Password must be at least 8 characters\"),\n      }),\n    },\n  });\n\n  if (isPending) {\n    return <Loader />;\n  }\n\n  return (\n    <div className=\"mx-auto w-full mt-10 max-w-md p-6\">\n      <h1 className=\"mb-6 text-center text-3xl font-bold\">Welcome Back</h1>\n\n      <form\n        onSubmit={(e) => {\n          e.preventDefault();\n          e.stopPropagation();\n          form.handleSubmit();\n        }}\n        className=\"space-y-4\"\n      >\n        <div>\n          <form.Field name=\"email\">\n            {(field) => (\n              <div className=\"space-y-2\">\n                <Label htmlFor={field.name}>Email</Label>\n                <Input\n                  id={field.name}\n                  name={field.name}\n                  type=\"email\"\n                  value={field.state.value}\n                  onBlur={field.handleBlur}\n                  onChange={(e) => field.handleChange(e.target.value)}\n                />\n                {field.state.meta.errors.map((error) => (\n                  <p key={error?.message} className=\"text-red-500\">\n                    {error?.message}\n                  </p>\n                ))}\n              </div>\n            )}\n          </form.Field>\n        </div>\n\n        <div>\n          <form.Field name=\"password\">\n            {(field) => (\n              <div className=\"space-y-2\">\n                <Label htmlFor={field.name}>Password</Label>\n                <Input\n                  id={field.name}\n                  name={field.name}\n                  type=\"password\"\n                  value={field.state.value}\n                  onBlur={field.handleBlur}\n                  onChange={(e) => field.handleChange(e.target.value)}\n                />\n                {field.state.meta.errors.map((error) => (\n                  <p key={error?.message} className=\"text-red-500\">\n                    {error?.message}\n                  </p>\n                ))}\n              </div>\n            )}\n          </form.Field>\n        </div>\n\n        <form.Subscribe selector={(state) => ({ canSubmit: state.canSubmit, isSubmitting: state.isSubmitting })}>\n          {({ canSubmit, isSubmitting }) => (\n            <Button\n              type=\"submit\"\n              className=\"w-full\"\n              disabled={!canSubmit || isSubmitting}\n            >\n              {isSubmitting ? \"Submitting...\" : \"Sign In\"}\n            </Button>\n          )}\n        </form.Subscribe>\n      </form>\n\n      <div className=\"mt-4 text-center\">\n        <Button\n          variant=\"link\"\n          onClick={onSwitchToSignUp}\n          className=\"text-indigo-600 hover:text-indigo-800\"\n        >\n          Need an account? Sign Up\n        </Button>\n      </div>\n    </div>\n  );\n}\n`],\n  [\"auth/better-auth/web/react/react-router/src/components/sign-up-form.tsx.hbs\", `import { authClient } from \"@/lib/auth-client\";\nimport { useForm } from \"@tanstack/react-form\";\nimport { useNavigate } from \"react-router\";\nimport { toast } from \"sonner\";\nimport z from \"zod\";\nimport Loader from \"./loader\";\nimport { Button } from \"@{{projectName}}/ui/components/button\";\nimport { Input } from \"@{{projectName}}/ui/components/input\";\nimport { Label } from \"@{{projectName}}/ui/components/label\";\n\nexport default function SignUpForm({\n  onSwitchToSignIn,\n}: {\n  onSwitchToSignIn: () => void;\n}) {\n  const navigate = useNavigate();\n  const { isPending } = authClient.useSession();\n\n  const form = useForm({\n    defaultValues: {\n      email: \"\",\n      password: \"\",\n      name: \"\",\n    },\n    onSubmit: async ({ value }) => {\n      await authClient.signUp.email(\n        {\n          email: value.email,\n          password: value.password,\n          name: value.name,\n        },\n        {\n          onSuccess: () => {\n            navigate(\"/dashboard\");\n            toast.success(\"Sign up successful\");\n          },\n          onError: (error) => {\n            toast.error(error.error.message || error.error.statusText);\n          },\n        }\n      );\n    },\n    validators: {\n      onSubmit: z.object({\n        name: z.string().min(2, \"Name must be at least 2 characters\"),\n        email: z.email(\"Invalid email address\"),\n        password: z.string().min(8, \"Password must be at least 8 characters\"),\n      }),\n    },\n  });\n\n  if (isPending) {\n    return <Loader />;\n  }\n\n  return (\n    <div className=\"mx-auto w-full mt-10 max-w-md p-6\">\n      <h1 className=\"mb-6 text-center text-3xl font-bold\">Create Account</h1>\n\n      <form\n        onSubmit={(e) => {\n          e.preventDefault();\n          e.stopPropagation();\n          form.handleSubmit();\n        }}\n        className=\"space-y-4\"\n      >\n        <div>\n          <form.Field name=\"name\">\n            {(field) => (\n              <div className=\"space-y-2\">\n                <Label htmlFor={field.name}>Name</Label>\n                <Input\n                  id={field.name}\n                  name={field.name}\n                  value={field.state.value}\n                  onBlur={field.handleBlur}\n                  onChange={(e) => field.handleChange(e.target.value)}\n                />\n                {field.state.meta.errors.map((error) => (\n                  <p key={error?.message} className=\"text-red-500\">\n                    {error?.message}\n                  </p>\n                ))}\n              </div>\n            )}\n          </form.Field>\n        </div>\n\n        <div>\n          <form.Field name=\"email\">\n            {(field) => (\n              <div className=\"space-y-2\">\n                <Label htmlFor={field.name}>Email</Label>\n                <Input\n                  id={field.name}\n                  name={field.name}\n                  type=\"email\"\n                  value={field.state.value}\n                  onBlur={field.handleBlur}\n                  onChange={(e) => field.handleChange(e.target.value)}\n                />\n                {field.state.meta.errors.map((error) => (\n                  <p key={error?.message} className=\"text-red-500\">\n                    {error?.message}\n                  </p>\n                ))}\n              </div>\n            )}\n          </form.Field>\n        </div>\n\n        <div>\n          <form.Field name=\"password\">\n            {(field) => (\n              <div className=\"space-y-2\">\n                <Label htmlFor={field.name}>Password</Label>\n                <Input\n                  id={field.name}\n                  name={field.name}\n                  type=\"password\"\n                  value={field.state.value}\n                  onBlur={field.handleBlur}\n                  onChange={(e) => field.handleChange(e.target.value)}\n                />\n                {field.state.meta.errors.map((error) => (\n                  <p key={error?.message} className=\"text-red-500\">\n                    {error?.message}\n                  </p>\n                ))}\n              </div>\n            )}\n          </form.Field>\n        </div>\n\n        <form.Subscribe selector={(state) => ({ canSubmit: state.canSubmit, isSubmitting: state.isSubmitting })}>\n          {({ canSubmit, isSubmitting }) => (\n            <Button\n              type=\"submit\"\n              className=\"w-full\"\n              disabled={!canSubmit || isSubmitting}\n            >\n              {isSubmitting ? \"Submitting...\" : \"Sign Up\"}\n            </Button>\n          )}\n        </form.Subscribe>\n      </form>\n\n      <div className=\"mt-4 text-center\">\n        <Button\n          variant=\"link\"\n          onClick={onSwitchToSignIn}\n          className=\"text-indigo-600 hover:text-indigo-800\"\n        >\n          Already have an account? Sign In\n        </Button>\n      </div>\n    </div>\n  );\n}\n`],\n  [\"auth/better-auth/web/react/react-router/src/components/user-menu.tsx.hbs\", `import { Link, useNavigate } from \"react-router\";\n\nimport {\n  DropdownMenu,\n  DropdownMenuContent,\n  DropdownMenuGroup,\n  DropdownMenuItem,\n  DropdownMenuLabel,\n  DropdownMenuSeparator,\n  DropdownMenuTrigger,\n} from \"@{{projectName}}/ui/components/dropdown-menu\";\nimport { authClient } from \"@/lib/auth-client\";\n\nimport { Button } from \"@{{projectName}}/ui/components/button\";\nimport { Skeleton } from \"@{{projectName}}/ui/components/skeleton\";\n\nexport default function UserMenu() {\n  const navigate = useNavigate();\n  const { data: session, isPending } = authClient.useSession();\n\n  if (isPending) {\n    return <Skeleton className=\"h-9 w-24\" />;\n  }\n\n  if (!session) {\n    return (\n      <Link to=\"/login\">\n        <Button variant=\"outline\">Sign In</Button>\n      </Link>\n    );\n  }\n\n  return (\n    <DropdownMenu>\n      <DropdownMenuTrigger render={<Button variant=\"outline\" />}>\n        {session.user.name}\n      </DropdownMenuTrigger>\n      <DropdownMenuContent className=\"bg-card\">\n        <DropdownMenuGroup>\n          <DropdownMenuLabel>My Account</DropdownMenuLabel>\n          <DropdownMenuSeparator />\n          <DropdownMenuItem>{session.user.email}</DropdownMenuItem>\n          <DropdownMenuItem\n            variant=\"destructive\"\n            onClick={() => {\n              authClient.signOut({\n                fetchOptions: {\n                  onSuccess: () => {\n                    navigate(\"/\");\n                  },\n                },\n              });\n            }}\n          >\n            Sign Out\n          </DropdownMenuItem>\n        </DropdownMenuGroup>\n      </DropdownMenuContent>\n    </DropdownMenu>\n  );\n}\n`],\n  [\"auth/better-auth/web/react/react-router/src/routes/dashboard.tsx.hbs\", `{{#if (eq payments \"polar\")}}\nimport { Button } from \"@{{projectName}}/ui/components/button\";\n{{/if}}\nimport { authClient } from \"@/lib/auth-client\";\n{{#if (eq api \"orpc\")}}\nimport { orpc } from \"@/utils/orpc\";\n{{/if}}\n{{#if (eq api \"trpc\")}}\nimport { trpc } from \"@/utils/trpc\";\n{{/if}}\n{{#if ( or (eq api \"orpc\") (eq api \"trpc\"))}}\nimport { useQuery } from \"@tanstack/react-query\";\n{{/if}}\nimport { useEffect, useState } from \"react\";\nimport { useNavigate } from \"react-router\";\n\nexport default function Dashboard() {\n  const { data: session, isPending } = authClient.useSession();\n  const navigate = useNavigate();\n  {{#if (eq payments \"polar\")}}\n  const [customerState, setCustomerState] = useState<any>(null);\n  {{/if}}\n\n  {{#if (eq api \"orpc\")}}\n  const privateData = useQuery(orpc.privateData.queryOptions());\n  {{/if}}\n  {{#if (eq api \"trpc\")}}\n  const privateData = useQuery(trpc.privateData.queryOptions());\n  {{/if}}\n\n  useEffect(() => {\n    if (!session && !isPending) {\n      navigate(\"/login\");\n    }\n  }, [session, isPending, navigate]);\n\n  {{#if (eq payments \"polar\")}}\n  useEffect(() => {\n    async function fetchCustomerState() {\n      if (session) {\n        const { data } = await authClient.customer.state();\n        setCustomerState(data);\n      }\n    }\n\n    fetchCustomerState();\n  }, [session]);\n  {{/if}}\n\n  if (isPending) {\n    return <div>Loading...</div>;\n  }\n\n  {{#if (eq payments \"polar\")}}\n  const hasProSubscription = customerState?.activeSubscriptions?.length! > 0;\n  console.log(\"Active subscriptions:\", customerState?.activeSubscriptions);\n  {{/if}}\n\n  return (\n    <div>\n      <h1>Dashboard</h1>\n      <p>Welcome {session?.user.name}</p>\n      {{#if ( or (eq api \"orpc\") (eq api \"trpc\"))}}\n      <p>API: {privateData.data?.message}</p>\n      {{/if}}\n      {{#if (eq payments \"polar\")}}\n      <p>Plan: {hasProSubscription ? \"Pro\" : \"Free\"}</p>\n      {hasProSubscription ? (\n        <Button onClick={async () => await authClient.customer.portal()}>\n          Manage Subscription\n        </Button>\n      ) : (\n        <Button onClick={async () => await authClient.checkout({ slug: \"pro\" })}>\n          Upgrade to Pro\n        </Button>\n      )}\n      {{/if}}\n    </div>\n  );\n}\n`],\n  [\"auth/better-auth/web/react/react-router/src/routes/login.tsx.hbs\", `import SignInForm from \"@/components/sign-in-form\";\nimport SignUpForm from \"@/components/sign-up-form\";\nimport { useState } from \"react\";\n\nexport default function Login() {\n  const [showSignIn, setShowSignIn] = useState(false);\n\n  return showSignIn ? (\n    <SignInForm onSwitchToSignUp={() => setShowSignIn(false)} />\n  ) : (\n    <SignUpForm onSwitchToSignIn={() => setShowSignIn(true)} />\n  );\n}\n`],\n  [\"auth/better-auth/web/react/tanstack-router/src/components/sign-in-form.tsx.hbs\", `import { authClient } from \"@/lib/auth-client\";\nimport { useForm } from \"@tanstack/react-form\";\nimport { useNavigate } from \"@tanstack/react-router\";\nimport { toast } from \"sonner\";\nimport z from \"zod\";\nimport Loader from \"./loader\";\nimport { Button } from \"@{{projectName}}/ui/components/button\";\nimport { Input } from \"@{{projectName}}/ui/components/input\";\nimport { Label } from \"@{{projectName}}/ui/components/label\";\n\nexport default function SignInForm({ onSwitchToSignUp }: { onSwitchToSignUp: () => void }) {\n  const navigate = useNavigate({\n    from: \"/\",\n  });\n  const { isPending } = authClient.useSession();\n\n  const form = useForm({\n    defaultValues: {\n      email: \"\",\n      password: \"\",\n    },\n    onSubmit: async ({ value }) => {\n      await authClient.signIn.email(\n        {\n          email: value.email,\n          password: value.password,\n        },\n        {\n          onSuccess: () => {\n            navigate({\n              to: \"/dashboard\",\n            });\n            toast.success(\"Sign in successful\");\n          },\n          onError: (error) => {\n            toast.error(error.error.message || error.error.statusText);\n          },\n        },\n      );\n    },\n    validators: {\n      onSubmit: z.object({\n        email: z.email(\"Invalid email address\"),\n        password: z.string().min(8, \"Password must be at least 8 characters\"),\n      }),\n    },\n  });\n\n  if (isPending) {\n    return <Loader />;\n  }\n\n  return (\n    <div className=\"mx-auto w-full mt-10 max-w-md p-6\">\n      <h1 className=\"mb-6 text-center text-3xl font-bold\">Welcome Back</h1>\n\n      <form\n        onSubmit={(e) => {\n          e.preventDefault();\n          e.stopPropagation();\n          form.handleSubmit();\n        }}\n        className=\"space-y-4\"\n      >\n        <div>\n          <form.Field name=\"email\">\n            {(field) => (\n              <div className=\"space-y-2\">\n                <Label htmlFor={field.name}>Email</Label>\n                <Input\n                  id={field.name}\n                  name={field.name}\n                  type=\"email\"\n                  value={field.state.value}\n                  onBlur={field.handleBlur}\n                  onChange={(e) => field.handleChange(e.target.value)}\n                />\n                {field.state.meta.errors.map((error) => (\n                  <p key={error?.message} className=\"text-red-500\">\n                    {error?.message}\n                  </p>\n                ))}\n              </div>\n            )}\n          </form.Field>\n        </div>\n\n        <div>\n          <form.Field name=\"password\">\n            {(field) => (\n              <div className=\"space-y-2\">\n                <Label htmlFor={field.name}>Password</Label>\n                <Input\n                  id={field.name}\n                  name={field.name}\n                  type=\"password\"\n                  value={field.state.value}\n                  onBlur={field.handleBlur}\n                  onChange={(e) => field.handleChange(e.target.value)}\n                />\n                {field.state.meta.errors.map((error) => (\n                  <p key={error?.message} className=\"text-red-500\">\n                    {error?.message}\n                  </p>\n                ))}\n              </div>\n            )}\n          </form.Field>\n        </div>\n\n        <form.Subscribe selector={(state) => ({ canSubmit: state.canSubmit, isSubmitting: state.isSubmitting })}>\n          {({ canSubmit, isSubmitting }) => (\n            <Button\n              type=\"submit\"\n              className=\"w-full\"\n              disabled={!canSubmit || isSubmitting}\n            >\n              {isSubmitting ? \"Submitting...\" : \"Sign In\"}\n            </Button>\n          )}\n        </form.Subscribe>\n      </form>\n\n      <div className=\"mt-4 text-center\">\n        <Button\n          variant=\"link\"\n          onClick={onSwitchToSignUp}\n          className=\"text-indigo-600 hover:text-indigo-800\"\n        >\n          Need an account? Sign Up\n        </Button>\n      </div>\n    </div>\n  );\n}\n`],\n  [\"auth/better-auth/web/react/tanstack-router/src/components/sign-up-form.tsx.hbs\", `import { authClient } from \"@/lib/auth-client\";\nimport { useForm } from \"@tanstack/react-form\";\nimport { useNavigate } from \"@tanstack/react-router\";\nimport { toast } from \"sonner\";\nimport z from \"zod\";\nimport Loader from \"./loader\";\nimport { Button } from \"@{{projectName}}/ui/components/button\";\nimport { Input } from \"@{{projectName}}/ui/components/input\";\nimport { Label } from \"@{{projectName}}/ui/components/label\";\n\nexport default function SignUpForm({ onSwitchToSignIn }: { onSwitchToSignIn: () => void }) {\n  const navigate = useNavigate({\n    from: \"/\",\n  });\n  const { isPending } = authClient.useSession();\n\n  const form = useForm({\n    defaultValues: {\n      email: \"\",\n      password: \"\",\n      name: \"\",\n    },\n    onSubmit: async ({ value }) => {\n      await authClient.signUp.email(\n        {\n          email: value.email,\n          password: value.password,\n          name: value.name,\n        },\n        {\n          onSuccess: () => {\n            navigate({\n              to: \"/dashboard\",\n            });\n            toast.success(\"Sign up successful\");\n          },\n          onError: (error) => {\n            toast.error(error.error.message || error.error.statusText);\n          },\n        },\n      );\n    },\n    validators: {\n      onSubmit: z.object({\n        name: z.string().min(2, \"Name must be at least 2 characters\"),\n        email: z.email(\"Invalid email address\"),\n        password: z.string().min(8, \"Password must be at least 8 characters\"),\n      }),\n    },\n  });\n\n  if (isPending) {\n    return <Loader />;\n  }\n\n  return (\n    <div className=\"mx-auto w-full mt-10 max-w-md p-6\">\n      <h1 className=\"mb-6 text-center text-3xl font-bold\">Create Account</h1>\n\n      <form\n        onSubmit={(e) => {\n          e.preventDefault();\n          e.stopPropagation();\n          form.handleSubmit();\n        }}\n        className=\"space-y-4\"\n      >\n        <div>\n          <form.Field name=\"name\">\n            {(field) => (\n              <div className=\"space-y-2\">\n                <Label htmlFor={field.name}>Name</Label>\n                <Input\n                  id={field.name}\n                  name={field.name}\n                  value={field.state.value}\n                  onBlur={field.handleBlur}\n                  onChange={(e) => field.handleChange(e.target.value)}\n                />\n                {field.state.meta.errors.map((error) => (\n                  <p key={error?.message} className=\"text-red-500\">\n                    {error?.message}\n                  </p>\n                ))}\n              </div>\n            )}\n          </form.Field>\n        </div>\n\n        <div>\n          <form.Field name=\"email\">\n            {(field) => (\n              <div className=\"space-y-2\">\n                <Label htmlFor={field.name}>Email</Label>\n                <Input\n                  id={field.name}\n                  name={field.name}\n                  type=\"email\"\n                  value={field.state.value}\n                  onBlur={field.handleBlur}\n                  onChange={(e) => field.handleChange(e.target.value)}\n                />\n                {field.state.meta.errors.map((error) => (\n                  <p key={error?.message} className=\"text-red-500\">\n                    {error?.message}\n                  </p>\n                ))}\n              </div>\n            )}\n          </form.Field>\n        </div>\n\n        <div>\n          <form.Field name=\"password\">\n            {(field) => (\n              <div className=\"space-y-2\">\n                <Label htmlFor={field.name}>Password</Label>\n                <Input\n                  id={field.name}\n                  name={field.name}\n                  type=\"password\"\n                  value={field.state.value}\n                  onBlur={field.handleBlur}\n                  onChange={(e) => field.handleChange(e.target.value)}\n                />\n                {field.state.meta.errors.map((error) => (\n                  <p key={error?.message} className=\"text-red-500\">\n                    {error?.message}\n                  </p>\n                ))}\n              </div>\n            )}\n          </form.Field>\n        </div>\n\n        <form.Subscribe selector={(state) => ({ canSubmit: state.canSubmit, isSubmitting: state.isSubmitting })}>\n          {({ canSubmit, isSubmitting }) => (\n            <Button\n              type=\"submit\"\n              className=\"w-full\"\n              disabled={!canSubmit || isSubmitting}\n            >\n              {isSubmitting ? \"Submitting...\" : \"Sign Up\"}\n            </Button>\n          )}\n        </form.Subscribe>\n      </form>\n\n      <div className=\"mt-4 text-center\">\n        <Button\n          variant=\"link\"\n          onClick={onSwitchToSignIn}\n          className=\"text-indigo-600 hover:text-indigo-800\"\n        >\n          Already have an account? Sign In\n        </Button>\n      </div>\n    </div>\n  );\n}\n`],\n  [\"auth/better-auth/web/react/tanstack-router/src/components/user-menu.tsx.hbs\", `import { Link, useNavigate } from \"@tanstack/react-router\";\n\nimport {\n  DropdownMenu,\n  DropdownMenuContent,\n  DropdownMenuGroup,\n  DropdownMenuItem,\n  DropdownMenuLabel,\n  DropdownMenuSeparator,\n  DropdownMenuTrigger,\n} from \"@{{projectName}}/ui/components/dropdown-menu\";\nimport { authClient } from \"@/lib/auth-client\";\n\nimport { Button } from \"@{{projectName}}/ui/components/button\";\nimport { Skeleton } from \"@{{projectName}}/ui/components/skeleton\";\n\nexport default function UserMenu() {\n  const navigate = useNavigate();\n  const { data: session, isPending } = authClient.useSession();\n\n  if (isPending) {\n    return <Skeleton className=\"h-9 w-24\" />;\n  }\n\n  if (!session) {\n    return (\n      <Link to=\"/login\">\n        <Button variant=\"outline\">Sign In</Button>\n      </Link>\n    );\n  }\n\n  return (\n    <DropdownMenu>\n      <DropdownMenuTrigger render={<Button variant=\"outline\" />}>\n        {session.user.name}\n      </DropdownMenuTrigger>\n      <DropdownMenuContent className=\"bg-card\">\n        <DropdownMenuGroup>\n          <DropdownMenuLabel>My Account</DropdownMenuLabel>\n          <DropdownMenuSeparator />\n          <DropdownMenuItem>{session.user.email}</DropdownMenuItem>\n          <DropdownMenuItem\n            variant=\"destructive\"\n            onClick={() => {\n              authClient.signOut({\n                fetchOptions: {\n                  onSuccess: () => {\n                    navigate({\n                      to: \"/\",\n                    });\n                  },\n                },\n              });\n            }}\n          >\n            Sign Out\n          </DropdownMenuItem>\n        </DropdownMenuGroup>\n      </DropdownMenuContent>\n    </DropdownMenu>\n  );\n}\n`],\n  [\"auth/better-auth/web/react/tanstack-router/src/routes/dashboard.tsx.hbs\", `{{#if (eq payments \"polar\")}}\nimport { Button } from \"@{{projectName}}/ui/components/button\";\n{{/if}}\nimport { authClient } from \"@/lib/auth-client\";\n{{#if (eq api \"orpc\")}}\nimport { orpc } from \"@/utils/orpc\";\n{{/if}}\n{{#if (eq api \"trpc\")}}\nimport { trpc } from \"@/utils/trpc\";\n{{/if}}\n{{#if ( or (eq api \"orpc\") (eq api \"trpc\"))}}\nimport { useQuery } from \"@tanstack/react-query\";\n{{/if}}\nimport { createFileRoute, redirect } from \"@tanstack/react-router\";\n\nexport const Route = createFileRoute(\"/dashboard\")({\n\tcomponent: RouteComponent,\n\tbeforeLoad: async () => {\n\t\tconst session = await authClient.getSession();\n\t\tif (!session.data) {\n\t\t\tredirect({\n\t\t\t\tto: \"/login\",\n\t\t\t\tthrow: true\n\t\t\t});\n\t\t}\n\t\t{{#if (eq payments \"polar\")}}\n\t\tconst {data: customerState} = await authClient.customer.state()\n\t\treturn { session, customerState };\n\t\t{{else}}\n\t\treturn { session };\n\t\t{{/if}}\n\t}\n});\n\nfunction RouteComponent() {\n\tconst { session{{#if (eq payments \"polar\")}}, customerState{{/if}} } = Route.useRouteContext();\n\n\t{{#if (eq api \"orpc\")}}\n\tconst privateData = useQuery(orpc.privateData.queryOptions());\n\t{{/if}}\n\t{{#if (eq api \"trpc\")}}\n\tconst privateData = useQuery(trpc.privateData.queryOptions());\n\t{{/if}}\n\n\t{{#if (eq payments \"polar\")}}\n\tconst hasProSubscription = customerState?.activeSubscriptions?.length! > 0\n    console.log(\"Active subscriptions:\", customerState?.activeSubscriptions)\n\t{{/if}}\n\n\treturn (\n\t\t<div>\n\t\t\t<h1>Dashboard</h1>\n\t\t\t<p>Welcome {session.data?.user.name}</p>\n\t\t\t{{#if ( or (eq api \"orpc\") (eq api \"trpc\"))}}\n\t\t\t<p>API: {privateData.data?.message}</p>\n\t\t\t{{/if}}\n\t\t\t{{#if (eq payments \"polar\")}}\n\t\t\t<p>Plan: {hasProSubscription ? \"Pro\" : \"Free\"}</p>\n\t\t\t{hasProSubscription ? (\n\t\t\t\t<Button onClick={async () => await authClient.customer.portal()}>\n\t\t\t\t\tManage Subscription\n\t\t\t\t</Button>\n\t\t\t) : (\n\t\t\t\t<Button onClick={async () => await authClient.checkout({ slug: \"pro\" })}>\n\t\t\t\t\tUpgrade to Pro\n\t\t\t\t</Button>\n\t\t\t)}\n\t\t\t{{/if}}\n\t\t</div>\n\t);\n}\n`],\n  [\"auth/better-auth/web/react/tanstack-router/src/routes/login.tsx.hbs\", `import SignInForm from \"@/components/sign-in-form\";\nimport SignUpForm from \"@/components/sign-up-form\";\nimport { createFileRoute } from \"@tanstack/react-router\";\nimport { useState } from \"react\";\n\nexport const Route = createFileRoute(\"/login\")({\n  component: RouteComponent,\n});\n\nfunction RouteComponent() {\n  const [showSignIn, setShowSignIn] = useState(false);\n\n  return showSignIn ? (\n    <SignInForm onSwitchToSignUp={() => setShowSignIn(false)} />\n  ) : (\n    <SignUpForm onSwitchToSignIn={() => setShowSignIn(true)} />\n  );\n}\n`],\n  [\"auth/better-auth/web/react/tanstack-start/src/components/sign-in-form.tsx.hbs\", `import { authClient } from \"@/lib/auth-client\";\nimport { useForm } from \"@tanstack/react-form\";\nimport { useNavigate } from \"@tanstack/react-router\";\nimport { toast } from \"sonner\";\nimport z from \"zod\";\nimport Loader from \"./loader\";\nimport { Button } from \"@{{projectName}}/ui/components/button\";\nimport { Input } from \"@{{projectName}}/ui/components/input\";\nimport { Label } from \"@{{projectName}}/ui/components/label\";\n\nexport default function SignInForm({ onSwitchToSignUp }: { onSwitchToSignUp: () => void }) {\n  const navigate = useNavigate({\n    from: \"/\",\n  });\n  const { isPending } = authClient.useSession();\n\n  const form = useForm({\n    defaultValues: {\n      email: \"\",\n      password: \"\",\n    },\n    onSubmit: async ({ value }) => {\n      await authClient.signIn.email(\n        {\n          email: value.email,\n          password: value.password,\n        },\n        {\n          onSuccess: () => {\n            navigate({\n              to: \"/dashboard\",\n            });\n            toast.success(\"Sign in successful\");\n          },\n          onError: (error) => {\n            toast.error(error.error.message || error.error.statusText);\n          },\n        },\n      );\n    },\n    validators: {\n      onSubmit: z.object({\n        email: z.email(\"Invalid email address\"),\n        password: z.string().min(8, \"Password must be at least 8 characters\"),\n      }),\n    },\n  });\n\n  if (isPending) {\n    return <Loader />;\n  }\n\n  return (\n    <div className=\"mx-auto w-full mt-10 max-w-md p-6\">\n      <h1 className=\"mb-6 text-center text-3xl font-bold\">Welcome Back</h1>\n\n      <form\n        onSubmit={(e) => {\n          e.preventDefault();\n          e.stopPropagation();\n          form.handleSubmit();\n        }}\n        className=\"space-y-4\"\n      >\n        <div>\n          <form.Field name=\"email\">\n            {(field) => (\n              <div className=\"space-y-2\">\n                <Label htmlFor={field.name}>Email</Label>\n                <Input\n                  id={field.name}\n                  name={field.name}\n                  type=\"email\"\n                  value={field.state.value}\n                  onBlur={field.handleBlur}\n                  onChange={(e) => field.handleChange(e.target.value)}\n                />\n                {field.state.meta.errors.map((error) => (\n                  <p key={error?.message} className=\"text-red-500\">\n                    {error?.message}\n                  </p>\n                ))}\n              </div>\n            )}\n          </form.Field>\n        </div>\n\n        <div>\n          <form.Field name=\"password\">\n            {(field) => (\n              <div className=\"space-y-2\">\n                <Label htmlFor={field.name}>Password</Label>\n                <Input\n                  id={field.name}\n                  name={field.name}\n                  type=\"password\"\n                  value={field.state.value}\n                  onBlur={field.handleBlur}\n                  onChange={(e) => field.handleChange(e.target.value)}\n                />\n                {field.state.meta.errors.map((error) => (\n                  <p key={error?.message} className=\"text-red-500\">\n                    {error?.message}\n                  </p>\n                ))}\n              </div>\n            )}\n          </form.Field>\n        </div>\n\n        <form.Subscribe selector={(state) => ({ canSubmit: state.canSubmit, isSubmitting: state.isSubmitting })}>\n          {({ canSubmit, isSubmitting }) => (\n            <Button\n              type=\"submit\"\n              className=\"w-full\"\n              disabled={!canSubmit || isSubmitting}\n            >\n              {isSubmitting ? \"Submitting...\" : \"Sign In\"}\n            </Button>\n          )}\n        </form.Subscribe>\n      </form>\n\n      <div className=\"mt-4 text-center\">\n        <Button\n          variant=\"link\"\n          onClick={onSwitchToSignUp}\n          className=\"text-indigo-600 hover:text-indigo-800\"\n        >\n          Need an account? Sign Up\n        </Button>\n      </div>\n    </div>\n  );\n}\n`],\n  [\"auth/better-auth/web/react/tanstack-start/src/components/sign-up-form.tsx.hbs\", `import { authClient } from \"@/lib/auth-client\";\nimport { useForm } from \"@tanstack/react-form\";\nimport { useNavigate } from \"@tanstack/react-router\";\nimport { toast } from \"sonner\";\nimport z from \"zod\";\nimport Loader from \"./loader\";\nimport { Button } from \"@{{projectName}}/ui/components/button\";\nimport { Input } from \"@{{projectName}}/ui/components/input\";\nimport { Label } from \"@{{projectName}}/ui/components/label\";\n\nexport default function SignUpForm({ onSwitchToSignIn }: { onSwitchToSignIn: () => void }) {\n  const navigate = useNavigate({\n    from: \"/\",\n  });\n  const { isPending } = authClient.useSession();\n\n  const form = useForm({\n    defaultValues: {\n      email: \"\",\n      password: \"\",\n      name: \"\",\n    },\n    onSubmit: async ({ value }) => {\n      await authClient.signUp.email(\n        {\n          email: value.email,\n          password: value.password,\n          name: value.name,\n        },\n        {\n          onSuccess: () => {\n            navigate({\n              to: \"/dashboard\",\n            });\n            toast.success(\"Sign up successful\");\n          },\n          onError: (error) => {\n            toast.error(error.error.message || error.error.statusText);\n          },\n        },\n      );\n    },\n    validators: {\n      onSubmit: z.object({\n        name: z.string().min(2, \"Name must be at least 2 characters\"),\n        email: z.email(\"Invalid email address\"),\n        password: z.string().min(8, \"Password must be at least 8 characters\"),\n      }),\n    },\n  });\n\n  if (isPending) {\n    return <Loader />;\n  }\n\n  return (\n    <div className=\"mx-auto w-full mt-10 max-w-md p-6\">\n      <h1 className=\"mb-6 text-center text-3xl font-bold\">Create Account</h1>\n\n      <form\n        onSubmit={(e) => {\n          e.preventDefault();\n          e.stopPropagation();\n          form.handleSubmit();\n        }}\n        className=\"space-y-4\"\n      >\n        <div>\n          <form.Field name=\"name\">\n            {(field) => (\n              <div className=\"space-y-2\">\n                <Label htmlFor={field.name}>Name</Label>\n                <Input\n                  id={field.name}\n                  name={field.name}\n                  value={field.state.value}\n                  onBlur={field.handleBlur}\n                  onChange={(e) => field.handleChange(e.target.value)}\n                />\n                {field.state.meta.errors.map((error) => (\n                  <p key={error?.message} className=\"text-red-500\">\n                    {error?.message}\n                  </p>\n                ))}\n              </div>\n            )}\n          </form.Field>\n        </div>\n\n        <div>\n          <form.Field name=\"email\">\n            {(field) => (\n              <div className=\"space-y-2\">\n                <Label htmlFor={field.name}>Email</Label>\n                <Input\n                  id={field.name}\n                  name={field.name}\n                  type=\"email\"\n                  value={field.state.value}\n                  onBlur={field.handleBlur}\n                  onChange={(e) => field.handleChange(e.target.value)}\n                />\n                {field.state.meta.errors.map((error) => (\n                  <p key={error?.message} className=\"text-red-500\">\n                    {error?.message}\n                  </p>\n                ))}\n              </div>\n            )}\n          </form.Field>\n        </div>\n\n        <div>\n          <form.Field name=\"password\">\n            {(field) => (\n              <div className=\"space-y-2\">\n                <Label htmlFor={field.name}>Password</Label>\n                <Input\n                  id={field.name}\n                  name={field.name}\n                  type=\"password\"\n                  value={field.state.value}\n                  onBlur={field.handleBlur}\n                  onChange={(e) => field.handleChange(e.target.value)}\n                />\n                {field.state.meta.errors.map((error) => (\n                  <p key={error?.message} className=\"text-red-500\">\n                    {error?.message}\n                  </p>\n                ))}\n              </div>\n            )}\n          </form.Field>\n        </div>\n\n        <form.Subscribe selector={(state) => ({ canSubmit: state.canSubmit, isSubmitting: state.isSubmitting })}>\n          {({ canSubmit, isSubmitting }) => (\n            <Button\n              type=\"submit\"\n              className=\"w-full\"\n              disabled={!canSubmit || isSubmitting}\n            >\n              {isSubmitting ? \"Submitting...\" : \"Sign Up\"}\n            </Button>\n          )}\n        </form.Subscribe>\n      </form>\n\n      <div className=\"mt-4 text-center\">\n        <Button\n          variant=\"link\"\n          onClick={onSwitchToSignIn}\n          className=\"text-indigo-600 hover:text-indigo-800\"\n        >\n          Already have an account? Sign In\n        </Button>\n      </div>\n    </div>\n  );\n}\n`],\n  [\"auth/better-auth/web/react/tanstack-start/src/components/user-menu.tsx.hbs\", `import { Link, useNavigate } from \"@tanstack/react-router\";\n\nimport {\n  DropdownMenu,\n  DropdownMenuContent,\n  DropdownMenuGroup,\n  DropdownMenuItem,\n  DropdownMenuLabel,\n  DropdownMenuSeparator,\n  DropdownMenuTrigger,\n} from \"@{{projectName}}/ui/components/dropdown-menu\";\nimport { authClient } from \"@/lib/auth-client\";\n\nimport { Button } from \"@{{projectName}}/ui/components/button\";\nimport { Skeleton } from \"@{{projectName}}/ui/components/skeleton\";\n\nexport default function UserMenu() {\n  const navigate = useNavigate();\n  const { data: session, isPending } = authClient.useSession();\n\n  if (isPending) {\n    return <Skeleton className=\"h-9 w-24\" />;\n  }\n\n  if (!session) {\n    return (\n      <Link to=\"/login\">\n        <Button variant=\"outline\">Sign In</Button>\n      </Link>\n    );\n  }\n\n  return (\n    <DropdownMenu>\n      <DropdownMenuTrigger render={<Button variant=\"outline\" />}>\n        {session.user.name}\n      </DropdownMenuTrigger>\n      <DropdownMenuContent className=\"bg-card\">\n        <DropdownMenuGroup>\n          <DropdownMenuLabel>My Account</DropdownMenuLabel>\n          <DropdownMenuSeparator />\n          <DropdownMenuItem>{session.user.email}</DropdownMenuItem>\n          <DropdownMenuItem\n            variant=\"destructive\"\n            onClick={() => {\n              authClient.signOut({\n                fetchOptions: {\n                  onSuccess: () => {\n                    navigate({\n                      to: \"/\",\n                    });\n                  },\n                },\n              });\n            }}\n          >\n            Sign Out\n          </DropdownMenuItem>\n        </DropdownMenuGroup>\n      </DropdownMenuContent>\n    </DropdownMenu>\n  );\n}\n`],\n  [\"auth/better-auth/web/react/tanstack-start/src/functions/get-user.ts.hbs\", `import { authMiddleware } from \"@/middleware/auth\";\nimport { createServerFn } from \"@tanstack/react-start\";\n\nexport const getUser = createServerFn({ method: \"GET\" }).middleware([authMiddleware]).handler(async ({ context }) => {\n    return context.session\n})`],\n  [\"auth/better-auth/web/react/tanstack-start/src/middleware/auth.ts.hbs\", `{{#if (eq backend \"self\")}}\n{{#if (or (eq runtime \"workers\") (eq serverDeploy \"cloudflare\") (and (eq backend \"self\") (eq webDeploy \"cloudflare\")))}}\nimport { createAuth } from \"@{{projectName}}/auth\";\n{{else}}\nimport { auth } from \"@{{projectName}}/auth\";\n{{/if}}\nimport { createMiddleware } from \"@tanstack/react-start\";\n\n\nexport const authMiddleware = createMiddleware().server(async ({ next, request }) => {\n    const session = await {{#if (or (eq runtime \"workers\") (eq serverDeploy \"cloudflare\") (and (eq backend \"self\") (eq webDeploy \"cloudflare\")))}}createAuth(){{else}}auth{{/if}}.api.getSession({\n        headers: request.headers,\n    })\n    return next({\n        context: { session }\n    })\n})\n{{else}}\nimport { authClient } from \"@/lib/auth-client\";\nimport { createMiddleware } from \"@tanstack/react-start\";\n\nexport const authMiddleware = createMiddleware().server(\n\tasync ({ next, request }) => {\n\t\tconst session = await authClient.getSession({\n\t\t\tfetchOptions: {\n\t\t\t\theaders: request.headers,\n\t\t\t\tthrow: true\n\t\t\t}\n\t\t})\n\t\treturn next({\n\t\t\tcontext: { session },\n\t\t});\n\t},\n);\n{{/if}}\n`],\n  [\"auth/better-auth/web/react/tanstack-start/src/routes/dashboard.tsx.hbs\", `import { getUser } from \"@/functions/get-user\";\n{{#if (eq payments \"polar\") }}\nimport { Button } from \"@{{projectName}}/ui/components/button\";\nimport { authClient } from \"@/lib/auth-client\";\nimport { getPayment } from \"@/functions/get-payment\";\n{{/if}}\n{{#if (eq api \"trpc\") }}\nimport { useTRPC } from \"@/utils/trpc\";\nimport { useQuery } from \"@tanstack/react-query\";\n{{/if}}\n{{#if (eq api \"orpc\") }}\nimport { orpc } from \"@/utils/orpc\";\nimport { useQuery } from \"@tanstack/react-query\";\n{{/if}}\nimport { createFileRoute, redirect } from \"@tanstack/react-router\";\n\nexport const Route = createFileRoute(\"/dashboard\")({\n  component: RouteComponent,\n  beforeLoad: async () => {\n    const session = await getUser();\n    {{#if (eq payments \"polar\") }}\n    const customerState = await getPayment();\n    return { session, customerState };\n    {{else}}\n    return { session };\n    {{/if}}\n  },\n  loader: async ({ context }) => {\n    if (!context.session) {\n      throw redirect({\n        to: \"/login\",\n      });\n    }\n  },\n});\n\nfunction RouteComponent() {\n  const { session{{#if (eq payments \"polar\") }}, customerState{{/if}} } = Route.useRouteContext();\n\n  {{#if (eq api \"trpc\") }}\n  const trpc = useTRPC();\n  const privateData = useQuery(trpc.privateData.queryOptions());\n  {{/if}}\n  {{#if (eq api \"orpc\") }}\n  const privateData = useQuery(orpc.privateData.queryOptions());\n  {{/if}}\n\n  {{#if (eq payments \"polar\") }}\n  const hasProSubscription = (customerState?.activeSubscriptions?.length ?? 0) > 0;\n  // For debugging: console.log(\"Active subscriptions:\", customerState?.activeSubscriptions);\n  {{/if}}\n\n  return (\n    <div>\n      <h1>Dashboard</h1>\n      <p>Welcome {session?.user.name}</p>\n      {{#if (eq api \"trpc\") }}\n      <p>API: {privateData.data?.message}</p>\n      {{else if (eq api \"orpc\") }}\n      <p>API: {privateData.data?.message}</p>\n      {{/if}}\n      {{#if (eq payments \"polar\") }}\n      <p>Plan: {hasProSubscription ? \"Pro\" : \"Free\"}</p>\n      {hasProSubscription ? (\n        <Button\n          onClick={async function handlePortal() {\n            await authClient.customer.portal();\n          }}\n        >\n          Manage Subscription\n        </Button>\n      ) : (\n        <Button\n          onClick={async function handleUpgrade() {\n            await authClient.checkout({ slug: \"pro\" });\n          }}\n        >\n          Upgrade to Pro\n        </Button>\n      )}\n      {{/if}}\n    </div>\n  );\n}`],\n  [\"auth/better-auth/web/react/tanstack-start/src/routes/login.tsx.hbs\", `import SignInForm from \"@/components/sign-in-form\";\nimport SignUpForm from \"@/components/sign-up-form\";\nimport { createFileRoute } from \"@tanstack/react-router\";\nimport { useState } from \"react\";\n\nexport const Route = createFileRoute(\"/login\")({\n  component: RouteComponent,\n});\n\nfunction RouteComponent() {\n  const [showSignIn, setShowSignIn] = useState(false);\n\n  return showSignIn ? (\n    <SignInForm onSwitchToSignUp={() => setShowSignIn(false)} />\n  ) : (\n    <SignUpForm onSwitchToSignIn={() => setShowSignIn(true)} />\n  );\n}\n`],\n  [\"auth/better-auth/web/solid/src/components/sign-in-form.tsx.hbs\", `import { authClient } from \"@/lib/auth-client\";\nimport { createForm } from \"@tanstack/solid-form\";\nimport { useNavigate } from \"@tanstack/solid-router\";\nimport z from \"zod\";\nimport { For } from \"solid-js\";\n\nexport default function SignInForm({ onSwitchToSignUp }: { onSwitchToSignUp: () => void }) {\n  const navigate = useNavigate({\n    from: \"/\",\n  });\n\n  const form = createForm(() => ({\n    defaultValues: {\n      email: \"\",\n      password: \"\",\n    },\n    onSubmit: async ({ value }) => {\n      await authClient.signIn.email(\n        {\n          email: value.email,\n          password: value.password,\n        },\n        {\n          onSuccess: () => {\n            navigate({\n              to: \"/dashboard\",\n            });\n            console.log(\"Sign in successful\");\n          },\n          onError: (error) => {\n            console.error(error.error.message);\n          },\n        },\n      );\n    },\n    validators: {\n      onSubmit: z.object({\n        email: z.email(\"Invalid email address\"),\n        password: z.string().min(8, \"Password must be at least 8 characters\"),\n      }),\n    },\n  }));\n\n  return (\n    <div class=\"mx-auto w-full mt-10 max-w-md p-6\">\n      <h1 class=\"mb-6 text-center text-3xl font-bold\">Welcome Back</h1>\n\n      <form\n        onSubmit={(e) => {\n          e.preventDefault();\n          e.stopPropagation();\n          form.handleSubmit();\n        }}\n        class=\"space-y-4\"\n      >\n        <div>\n          <form.Field name=\"email\">\n            {(field) => (\n              <div class=\"space-y-2\">\n                <label for={field().name}>Email</label>\n                <input\n                  id={field().name}\n                  name={field().name}\n                  type=\"email\"\n                  value={field().state.value}\n                  onBlur={field().handleBlur}\n                  onInput={(e) => field().handleChange(e.currentTarget.value)}\n                  class=\"w-full rounded border p-2\"\n                />\n                <For each={field().state.meta.errors}>\n                  {(error) => <p class=\"text-sm text-red-600\">{error?.message}</p>}\n                </For>\n              </div>\n            )}\n          </form.Field>\n        </div>\n\n        <div>\n          <form.Field name=\"password\">\n            {(field) => (\n              <div class=\"space-y-2\">\n                <label for={field().name}>Password</label>\n                <input\n                  id={field().name}\n                  name={field().name}\n                  type=\"password\"\n                  value={field().state.value}\n                  onBlur={field().handleBlur}\n                  onInput={(e) => field().handleChange(e.currentTarget.value)}\n                  class=\"w-full rounded border p-2\"\n                />\n                <For each={field().state.meta.errors}>\n                  {(error) => <p class=\"text-sm text-red-600\">{error?.message}</p>}\n                </For>\n              </div>\n            )}\n          </form.Field>\n        </div>\n\n        <form.Subscribe>\n          {(state) => (\n            <button\n              type=\"submit\"\n              class=\"w-full rounded bg-indigo-600 p-2 text-white hover:bg-indigo-700 disabled:opacity-50\"\n              disabled={!state().canSubmit || state().isSubmitting}\n            >\n              {state().isSubmitting ? \"Submitting...\" : \"Sign In\"}\n            </button>\n          )}\n        </form.Subscribe>\n      </form>\n\n      <div class=\"mt-4 text-center\">\n        <button\n          type=\"button\"\n          onClick={onSwitchToSignUp}\n          class=\"text-sm text-indigo-600 hover:text-indigo-800 hover:underline\"\n        >\n          Need an account? Sign Up\n        </button>\n      </div>\n    </div>\n  );\n}\n`],\n  [\"auth/better-auth/web/solid/src/components/sign-up-form.tsx.hbs\", `import { authClient } from \"@/lib/auth-client\";\nimport { createForm } from \"@tanstack/solid-form\";\nimport { useNavigate } from \"@tanstack/solid-router\";\nimport z from \"zod\";\nimport { For } from \"solid-js\";\n\nexport default function SignUpForm({ onSwitchToSignIn }: { onSwitchToSignIn: () => void }) {\n  const navigate = useNavigate({\n    from: \"/\",\n  });\n\n  const form = createForm(() => ({\n    defaultValues: {\n      email: \"\",\n      password: \"\",\n      name: \"\",\n    },\n    onSubmit: async ({ value }) => {\n      await authClient.signUp.email(\n        {\n          email: value.email,\n          password: value.password,\n          name: value.name,\n        },\n        {\n          onSuccess: () => {\n            navigate({\n              to: \"/dashboard\",\n            });\n            console.log(\"Sign up successful\");\n          },\n          onError: (error) => {\n            console.error(error.error.message);\n          },\n        },\n      );\n    },\n    validators: {\n      onSubmit: z.object({\n        name: z.string().min(2, \"Name must be at least 2 characters\"),\n        email: z.email(\"Invalid email address\"),\n        password: z.string().min(8, \"Password must be at least 8 characters\"),\n      }),\n    },\n  }));\n\n  return (\n    <div class=\"mx-auto w-full mt-10 max-w-md p-6\">\n      <h1 class=\"mb-6 text-center text-3xl font-bold\">Create Account</h1>\n\n      <form\n        onSubmit={(e) => {\n          e.preventDefault();\n          e.stopPropagation();\n          form.handleSubmit();\n        }}\n        class=\"space-y-4\"\n      >\n        <div>\n          <form.Field name=\"name\">\n            {(field) => (\n              <div class=\"space-y-2\">\n                <label for={field().name}>Name</label>\n                <input\n                  id={field().name}\n                  name={field().name}\n                  value={field().state.value}\n                  onBlur={field().handleBlur}\n                  onInput={(e) => field().handleChange(e.currentTarget.value)}\n                  class=\"w-full rounded border p-2\"\n                />\n                <For each={field().state.meta.errors}>\n                  {(error) => <p class=\"text-sm text-red-600\">{error?.message}</p>}\n                </For>\n              </div>\n            )}\n          </form.Field>\n        </div>\n\n        <div>\n          <form.Field name=\"email\">\n            {(field) => (\n              <div class=\"space-y-2\">\n                <label for={field().name}>Email</label>\n                <input\n                  id={field().name}\n                  name={field().name}\n                  type=\"email\"\n                  value={field().state.value}\n                  onBlur={field().handleBlur}\n                  onInput={(e) => field().handleChange(e.currentTarget.value)}\n                  class=\"w-full rounded border p-2\"\n                />\n                <For each={field().state.meta.errors}>\n                  {(error) => <p class=\"text-sm text-red-600\">{error?.message}</p>}\n                </For>\n              </div>\n            )}\n          </form.Field>\n        </div>\n\n        <div>\n          <form.Field name=\"password\">\n            {(field) => (\n              <div class=\"space-y-2\">\n                <label for={field().name}>Password</label>\n                <input\n                  id={field().name}\n                  name={field().name}\n                  type=\"password\"\n                  value={field().state.value}\n                  onBlur={field().handleBlur}\n                  onInput={(e) => field().handleChange(e.currentTarget.value)}\n                  class=\"w-full rounded border p-2\"\n                />\n                <For each={field().state.meta.errors}>\n                  {(error) => <p class=\"text-sm text-red-600\">{error?.message}</p>}\n                </For>\n              </div>\n            )}\n          </form.Field>\n        </div>\n\n        <form.Subscribe>\n          {(state) => (\n            <button\n              type=\"submit\"\n              class=\"w-full rounded bg-indigo-600 p-2 text-white hover:bg-indigo-700 disabled:opacity-50\"\n              disabled={!state().canSubmit || state().isSubmitting}\n            >\n              {state().isSubmitting ? \"Submitting...\" : \"Sign Up\"}\n            </button>\n          )}\n        </form.Subscribe>\n      </form>\n\n      <div class=\"mt-4 text-center\">\n        <button\n          type=\"button\"\n          onClick={onSwitchToSignIn}\n          class=\"text-sm text-indigo-600 hover:text-indigo-800 hover:underline\"\n        >\n          Already have an account? Sign In\n        </button>\n      </div>\n    </div>\n  );\n}\n`],\n  [\"auth/better-auth/web/solid/src/components/user-menu.tsx.hbs\", `import { authClient } from \"@/lib/auth-client\";\nimport { useNavigate, Link } from \"@tanstack/solid-router\";\nimport { createSignal, Show } from \"solid-js\";\n\nexport default function UserMenu() {\n  const navigate = useNavigate();\n  const session = authClient.useSession();\n  const [isMenuOpen, setIsMenuOpen] = createSignal(false);\n\n  return (\n    <div class=\"relative inline-block text-left\">\n      <Show when={session().isPending}>\n        <div class=\"h-9 w-24 animate-pulse rounded\" />\n      </Show>\n\n      <Show when={!session().isPending && !session().data}>\n        <Link to=\"/login\" class=\"inline-block border rounded px-4  text-sm\">\n          Sign In\n        </Link>\n      </Show>\n\n      <Show when={!session().isPending && session().data}>\n        <button\n          type=\"button\"\n          class=\"inline-block border rounded px-4  text-sm\"\n          onClick={() => setIsMenuOpen(!isMenuOpen())}\n        >\n          {session().data?.user.name}\n        </button>\n\n        <Show when={isMenuOpen()}>\n          <div class=\"absolute right-0 mt-2 w-56 rounded p-1 shadow-sm\">\n            <div class=\"px-4  text-sm\">{session().data?.user.email}</div>\n            <button\n              type=\"button\"\n              class=\"mt-1 w-full border rounded px-4  text-center text-sm\"\n              onClick={() => {\n                setIsMenuOpen(false);\n                authClient.signOut({\n                  fetchOptions: {\n                    onSuccess: () => {\n                      navigate({ to: \"/\" });\n                    },\n                  },\n                });\n              }}\n            >\n              Sign Out\n            </button>\n          </div>\n        </Show>\n      </Show>\n    </div>\n  );\n}\n`],\n  [\"auth/better-auth/web/solid/src/lib/auth-client.ts.hbs\", `import { createAuthClient } from \"better-auth/solid\";\n{{#if (eq payments \"polar\")}}\nimport { polarClient } from \"@polar-sh/better-auth\";\n{{/if}}\nimport { env } from \"@{{projectName}}/env/web\";\n\nexport const authClient = createAuthClient({\n\tbaseURL: env.VITE_SERVER_URL,\n{{#if (eq payments \"polar\")}}\n\tplugins: [polarClient()]\n{{/if}}\n});\n`],\n  [\"auth/better-auth/web/solid/src/routes/dashboard.tsx.hbs\", `import { authClient } from \"@/lib/auth-client\";\n{{#if (eq api \"orpc\")}}\nimport { orpc } from \"@/utils/orpc\";\nimport { useQuery } from \"@tanstack/solid-query\";\n{{/if}}\nimport { createFileRoute, redirect } from \"@tanstack/solid-router\";\n\nexport const Route = createFileRoute(\"/dashboard\")({\n\tcomponent: RouteComponent,\n\tbeforeLoad: async () => {\n\t\tconst session = await authClient.getSession();\n\t\tif (!session.data) {\n\t\t\tredirect({\n\t\t\t\tto: \"/login\",\n\t\t\t\tthrow: true,\n\t\t\t});\n\t\t}\n\t\t{{#if (eq payments \"polar\")}}\n\t\tconst { data: customerState } = await authClient.customer.state();\n\t\treturn { session, customerState };\n\t\t{{else}}\n\t\treturn { session };\n\t\t{{/if}}\n\t},\n});\n\nfunction RouteComponent() {\n\tconst context = Route.useRouteContext();\n\n\tconst session = context().session;\n\t{{#if (eq payments \"polar\")}}\n\tconst customerState = context().customerState;\n\t{{/if}}\n\n\t{{#if (eq api \"orpc\")}}\n\tconst privateData = useQuery(() => orpc.privateData.queryOptions());\n\t{{/if}}\n\n\t{{#if (eq payments \"polar\")}}\n\tconst hasProSubscription = () =>\n\t\tcustomerState?.activeSubscriptions?.length! > 0;\n\t{{/if}}\n\n\treturn (\n\t\t<div>\n\t\t\t<h1>Dashboard</h1>\n\t\t\t<p>Welcome {session.data?.user.name}</p>\n\t\t\t{{#if (eq api \"orpc\")}}\n\t\t\t<p>API: {privateData.data?.message}</p>\n\t\t\t{{/if}}\n\t\t\t{{#if (eq payments \"polar\")}}\n\t\t\t<p>Plan: {hasProSubscription() ? \"Pro\" : \"Free\"}</p>\n\t\t\t{hasProSubscription() ? (\n\t\t\t\t<button onClick={async () => await authClient.customer.portal()}>\n\t\t\t\t\tManage Subscription\n\t\t\t\t</button>\n\t\t\t) : (\n\t\t\t\t<button\n\t\t\t\t\tonClick={async () => await authClient.checkout({ slug: \"pro\" })}\n\t\t\t\t>\n\t\t\t\t\tUpgrade to Pro\n\t\t\t\t</button>\n\t\t\t)}\n\t\t\t{{/if}}\n\t\t</div>\n\t);\n}\n`],\n  [\"auth/better-auth/web/solid/src/routes/login.tsx.hbs\", `import SignInForm from \"@/components/sign-in-form\";\nimport SignUpForm from \"@/components/sign-up-form\";\nimport { createFileRoute } from \"@tanstack/solid-router\";\nimport { createSignal, Match, Switch } from \"solid-js\";\n\nexport const Route = createFileRoute(\"/login\")({\n  component: RouteComponent,\n});\n\nfunction RouteComponent() {\n  const [showSignIn, setShowSignIn] = createSignal(false);\n\n  return (\n    <Switch>\n      <Match when={showSignIn()}>\n        <SignInForm onSwitchToSignUp={() => setShowSignIn(false)} />\n      </Match>\n      <Match when={!showSignIn()}>\n        <SignUpForm onSwitchToSignIn={() => setShowSignIn(true)} />\n      </Match>\n    </Switch>\n  );\n}\n`],\n  [\"auth/better-auth/web/svelte/src/components/SignInForm.svelte.hbs\", `<script lang=\"ts\">\n\timport { createForm } from '@tanstack/svelte-form';\n\timport { z } from 'zod';\n\timport { authClient } from '$lib/auth-client';\n\timport { goto } from '$app/navigation';\n\n\tlet { switchToSignUp } = $props<{ switchToSignUp: () => void }>();\n\n\tconst validationSchema = z.object({\n\t\temail: z.email('Invalid email address'),\n\t\tpassword: z.string().min(1, 'Password is required'),\n\t});\n\n\tconst form = createForm(() => ({\n\t\tdefaultValues: { email: '', password: '' },\n\t\tonSubmit: async ({ value }) => {\n\t\t\t\tawait authClient.signIn.email(\n\t\t\t\t\t{ email: value.email, password: value.password },\n\t\t\t\t\t{\n\t\t\t\t\t\tonSuccess: () => goto('/dashboard'),\n\t\t\t\t\t\tonError: (error) => {\n\t\t\t\t\t\t\tconsole.log(error.error.message || 'Sign in failed. Please try again.');\n\t\t\t\t\t\t},\n\t\t\t\t\t}\n\t\t\t\t);\n\n\t\t},\n\t\tvalidators: {\n\t\t\tonSubmit: validationSchema,\n\t\t},\n\t}));\n\n\ttype SubmitState = Pick<typeof form.state, 'canSubmit' | 'isSubmitting'>;\n</script>\n\n<div class=\"mx-auto mt-10 w-full max-w-md p-6\">\n\t<h1 class=\"mb-6 text-center font-bold text-3xl\">Welcome Back</h1>\n\n\t<form\n\t\tclass=\"space-y-4\"\n\t\tonsubmit={(e) => {\n\t\t\te.preventDefault();\n\t\t\te.stopPropagation();\n\t\t\tform.handleSubmit();\n\t\t}}\n\t>\n\t\t<form.Field name=\"email\">\n\t\t\t{#snippet children(field)}\n\t\t\t\t<div class=\"space-y-1\">\n\t\t\t\t\t<label for={field.name}>Email</label>\n\t\t\t\t\t<input\n\t\t\t\t\t\tid={field.name}\n\t\t\t\t\t\tname={field.name}\n\t\t\t\t\t\ttype=\"email\"\n\t\t\t\t\t\tclass=\"w-full border\"\n\t\t\t\t\t\tonblur={field.handleBlur}\n\t\t\t\t\t\tvalue={field.state.value}\n\t\t\t\t\t\toninput={(e: Event) => {\n\t\t\t\t\t\t\tconst target = e.target as HTMLInputElement;\n\t\t\t\t\t\t\tfield.handleChange(target.value);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t\t{#if field.state.meta.isTouched}\n\t\t\t\t\t\t{#each field.state.meta.errors as error}\n\t\t\t\t\t\t\t<p class=\"text-sm text-red-500\" role=\"alert\">{error}</p>\n\t\t\t\t\t\t{/each}\n\t\t\t\t\t{/if}\n\t\t\t\t</div>\n\t\t\t{/snippet}\n\t\t</form.Field>\n\n\t\t<form.Field name=\"password\">\n\t\t\t{#snippet children(field)}\n\t\t\t\t<div class=\"space-y-1\">\n\t\t\t\t\t<label for={field.name}>Password</label>\n\t\t\t\t\t<input\n\t\t\t\t\t\tid={field.name}\n\t\t\t\t\t\tname={field.name}\n\t\t\t\t\t\ttype=\"password\"\n\t\t\t\t\t\tclass=\"w-full border\"\n\t\t\t\t\t\tonblur={field.handleBlur}\n\t\t\t\t\t\tvalue={field.state.value}\n\t\t\t\t\t\toninput={(e: Event) => {\n\t\t\t\t\t\t\tconst target = e.target as HTMLInputElement;\n\t\t\t\t\t\t\tfield.handleChange(target.value);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t\t{#if field.state.meta.isTouched}\n\t\t\t\t\t\t{#each field.state.meta.errors as error}\n\t\t\t\t\t\t\t<p class=\"text-sm text-red-500\" role=\"alert\">{error}</p>\n\t\t\t\t\t\t{/each}\n\t\t\t\t\t{/if}\n\t\t\t\t</div>\n\t\t\t{/snippet}\n\t\t</form.Field>\n\n\t\t<form.Subscribe selector={(state: typeof form.state): SubmitState => ({ canSubmit: state.canSubmit, isSubmitting: state.isSubmitting })}>\n\t\t\t{#snippet children(state: SubmitState)}\n\t\t\t\t<button type=\"submit\" class=\"w-full\" disabled={!state.canSubmit || state.isSubmitting}>\n\t\t\t\t\t{state.isSubmitting ? 'Submitting...' : 'Sign In'}\n\t\t\t\t</button>\n\t\t\t{/snippet}\n\t\t</form.Subscribe>\n\t</form>\n\n\t<div class=\"mt-4 text-center\">\n\t\t<button type=\"button\" class=\"text-indigo-600 hover:text-indigo-800\" onclick={switchToSignUp}>\n\t\t\tNeed an account? Sign Up\n\t\t</button>\n\t</div>\n</div>\n`],\n  [\"auth/better-auth/web/svelte/src/components/SignUpForm.svelte.hbs\", `<script lang=\"ts\">\n\timport { createForm } from '@tanstack/svelte-form';\n\timport { z } from 'zod';\n\timport { authClient } from '$lib/auth-client';\n\timport { goto } from '$app/navigation';\n\n\tlet { switchToSignIn } = $props<{ switchToSignIn: () => void }>();\n\n\tconst validationSchema = z.object({\n\t\tname: z.string().min(2, 'Name must be at least 2 characters'),\n\t\temail: z.email('Invalid email address'),\n\t\tpassword: z.string().min(8, 'Password must be at least 8 characters'),\n\t});\n\n\n\tconst form = createForm(() => ({\n\t\tdefaultValues: { name: '', email: '', password: '' },\n\t\tonSubmit: async ({ value }) => {\n\t\t\t\tawait authClient.signUp.email(\n\t\t\t\t\t{\n\t\t\t\t\t\temail: value.email,\n\t\t\t\t\t\tpassword: value.password,\n\t\t\t\t\t\tname: value.name,\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tonSuccess: () => {\n\t\t\t\t\t\t\tgoto('/dashboard');\n\t\t\t\t\t\t},\n\t\t\t\t\t\tonError: (error) => {\n\t\t\t\t\t\t\tconsole.log(error.error.message || 'Sign up failed. Please try again.');\n\t\t\t\t\t\t},\n\t\t\t\t\t}\n\t\t\t\t);\n\n\t\t},\n\t\tvalidators: {\n\t\t\tonSubmit: validationSchema,\n\t\t},\n\t}));\n\n\ttype SubmitState = Pick<typeof form.state, 'canSubmit' | 'isSubmitting'>;\n</script>\n\n<div class=\"mx-auto mt-10 w-full max-w-md p-6\">\n\t<h1 class=\"mb-6 text-center font-bold text-3xl\">Create Account</h1>\n\n\t<form\n\t\tid=\"form\"\n\t\tclass=\"space-y-4\"\n\t\tonsubmit={(e) => {\n\t\t\te.preventDefault();\n\t\t\te.stopPropagation();\n\t\t\tform.handleSubmit();\n\t\t}}\n\t>\n\t\t<form.Field name=\"name\">\n\t\t\t{#snippet children(field)}\n\t\t\t\t<div class=\"space-y-1\">\n\t\t\t\t\t<label for={field.name}>Name</label>\n\t\t\t\t\t<input\n\t\t\t\t\t\tid={field.name}\n\t\t\t\t\t\tname={field.name}\n\t\t\t\t\t\tclass=\"w-full border\"\n\t\t\t\t\t\tonblur={field.handleBlur}\n\t\t\t\t\t\tvalue={field.state.value}\n\t\t\t\t\t\toninput={(e: Event) => {\n\t\t\t\t\t\t\tconst target = e.target as HTMLInputElement;\n\t\t\t\t\t\t\tfield.handleChange(target.value);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t\t{#if field.state.meta.isTouched}\n\t\t\t\t\t\t{#each field.state.meta.errors as error}\n\t\t\t\t\t\t\t<p class=\"text-sm text-red-500\" role=\"alert\">{error}</p>\n\t\t\t\t\t\t{/each}\n\t\t\t\t\t{/if}\n\t\t\t\t</div>\n\t\t\t{/snippet}\n\t\t</form.Field>\n\n\t\t<form.Field name=\"email\">\n\t\t\t{#snippet children(field)}\n\t\t\t\t<div class=\"space-y-1\">\n\t\t\t\t\t<label for={field.name}>Email</label>\n\t\t\t\t\t<input\n\t\t\t\t\t\tid={field.name}\n\t\t\t\t\t\tname={field.name}\n\t\t\t\t\t\ttype=\"email\"\n\t\t\t\t\t\tclass=\"w-full border\"\n\t\t\t\t\t\tonblur={field.handleBlur}\n\t\t\t\t\t\tvalue={field.state.value}\n\t\t\t\t\t\toninput={(e: Event) => {\n\t\t\t\t\t\t\tconst target = e.target as HTMLInputElement;\n\t\t\t\t\t\t\tfield.handleChange(target.value);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t\t{#if field.state.meta.isTouched}\n\t\t\t\t\t\t{#each field.state.meta.errors as error}\n\t\t\t\t\t\t\t<p class=\"text-sm text-red-500\" role=\"alert\">{error}</p>\n\t\t\t\t\t\t{/each}\n\t\t\t\t\t{/if}\n\t\t\t\t</div>\n\t\t\t{/snippet}\n\t\t</form.Field>\n\n\t\t<form.Field name=\"password\">\n\t\t\t{#snippet children(field)}\n\t\t\t\t<div class=\"space-y-1\">\n\t\t\t\t\t<label for={field.name}>Password</label>\n\t\t\t\t\t<input\n\t\t\t\t\t\tid={field.name}\n\t\t\t\t\t\tname={field.name}\n\t\t\t\t\t\ttype=\"password\"\n\t\t\t\t\t\tclass=\"w-full border\"\n\t\t\t\t\t\tonblur={field.handleBlur}\n\t\t\t\t\t\tvalue={field.state.value}\n\t\t\t\t\t\toninput={(e: Event) => {\n\t\t\t\t\t\t\tconst target = e.target as HTMLInputElement;\n\t\t\t\t\t\t\tfield.handleChange(target.value);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t\t{#if field.state.meta.isTouched}\n\t\t\t\t\t\t{#each field.state.meta.errors as error}\n\t\t\t\t\t\t\t<p class=\"text-sm text-red-500\" role=\"alert\">{error}</p>\n\t\t\t\t\t\t{/each}\n\t\t\t\t\t{/if}\n\t\t\t\t</div>\n\t\t\t{/snippet}\n\t\t</form.Field>\n\n\t\t<form.Subscribe selector={(state: typeof form.state): SubmitState => ({ canSubmit: state.canSubmit, isSubmitting: state.isSubmitting })}>\n\t\t\t{#snippet children(state: SubmitState)}\n\t\t\t\t<button type=\"submit\" class=\"w-full\" disabled={!state.canSubmit || state.isSubmitting}>\n\t\t\t\t\t{state.isSubmitting ? 'Submitting...' : 'Sign Up'}\n\t\t\t\t</button>\n\t\t\t{/snippet}\n\t\t</form.Subscribe>\n\t</form>\n\n\t<div class=\"mt-4 text-center\">\n\t\t<button type=\"button\" class=\"text-indigo-600 hover:text-indigo-800\" onclick={switchToSignIn}>\n\t\t\tAlready have an account? Sign In\n\t\t</button>\n\t</div>\n</div>\n`],\n  [\"auth/better-auth/web/svelte/src/components/UserMenu.svelte.hbs\", `<script lang=\"ts\">\n\timport { authClient } from '$lib/auth-client';\n\timport { goto } from '$app/navigation';\n\n\tconst sessionQuery = authClient.useSession();\n\n\tasync function handleSignOut() {\n\t\tawait authClient.signOut({\n\t\tfetchOptions: {\n\t\t\tonSuccess: () => {\n\t\t\t\tgoto('/');\n\t\t\t},\n\t\t\tonError: (error) => {\n\t\t\t\tconsole.error('Sign out failed:', error);\n\t\t\t}\n\t\t}\n\t\t});\n\t}\n\n\tfunction goToLogin() {\n\t\tgoto('/login');\n\t}\n\n</script>\n\n<div class=\"relative\">\n\t{#if $sessionQuery.isPending}\n\t\t<div class=\"h-8 w-24 animate-pulse rounded bg-neutral-700\"></div>\n\t{:else if $sessionQuery.data?.user}\n\t\t{@const user = $sessionQuery.data.user}\n\t\t<div class=\"flex items-center gap-3\">\n\t\t\t<span class=\"text-sm text-neutral-300 hidden sm:inline\" title={user.email}>\n\t\t\t\t{user.name || user.email?.split('@')[0] || 'User'}\n\t\t\t</span>\n\t\t\t<button\n\t\t\t\tonclick={handleSignOut}\n\t\t\t\tclass=\"rounded px-3 py-1 text-sm bg-red-600 hover:bg-red-700 text-white transition-colors\"\n\t\t\t>\n\t\t\t\tSign Out\n\t\t\t</button>\n\t\t</div>\n\t{:else}\n\t\t<div class=\"flex items-center gap-2\">\n\t\t\t<button\n\t\t\t\tonclick={goToLogin}\n\t\t\t\tclass=\"rounded px-3 py-1 text-sm bg-indigo-600 hover:bg-indigo-700 text-white transition-colors\"\n\t\t\t>\n\t\t\t\tSign In\n\t\t\t</button>\n\t\t</div>\n\t{/if}\n</div>\n`],\n  [\"auth/better-auth/web/svelte/src/lib/auth-client.ts.hbs\", `{{#unless (eq backend \"self\")}}\nimport { PUBLIC_SERVER_URL } from \"$env/static/public\";\n{{/unless}}\nimport { createAuthClient } from \"better-auth/svelte\";\n{{#if (eq payments \"polar\")}}\nimport { polarClient } from \"@polar-sh/better-auth\";\n{{/if}}\n\nexport const authClient = createAuthClient({\n{{#unless (eq backend \"self\")}}\n\tbaseURL: PUBLIC_SERVER_URL,\n{{/unless}}\n{{#if (eq payments \"polar\")}}\n\tplugins: [polarClient()]\n{{/if}}\n});\n`],\n  [\"auth/better-auth/web/svelte/src/routes/dashboard/+page.svelte.hbs\", `<script lang=\"ts\">\n\timport { goto } from '$app/navigation';\n\timport { authClient } from '$lib/auth-client';\n\t{{#if (eq api \"orpc\")}}\n\timport { orpc } from '$lib/orpc';\n\timport { createQuery } from '@tanstack/svelte-query';\n\t{{/if}}\n\t{{#if (eq payments \"polar\")}}\n\tlet customerState = $state<{ activeSubscriptions?: unknown[] } | null>(null);\n\t{{/if}}\n\n\tconst sessionQuery = authClient.useSession();\n\n\t{{#if (eq api \"orpc\")}}\n\tconst privateDataQuery = createQuery(orpc.privateData.queryOptions());\n\t{{/if}}\n\n\t$effect(() => {\n\t\tif (!$sessionQuery.isPending && !$sessionQuery.data) {\n\t\t\tgoto('/login');\n\t\t}\n\t});\n\n\t{{#if (eq payments \"polar\")}}\n\t$effect(() => {\n\t\tif ($sessionQuery.data) {\n\t\t\tauthClient.customer.state().then(({ data }) => {\n\t\t\t\tcustomerState = data;\n\t\t\t});\n\t\t}\n\t});\n\t{{/if}}\n</script>\n\n{#if $sessionQuery.isPending}\n\t<div>Loading...</div>\n{:else if !$sessionQuery.data}\n\t<div>Redirecting to login...</div>\n{:else}\n\t<div>\n\t\t<h1>Dashboard</h1>\n\t\t<p>Welcome {$sessionQuery.data.user.name}</p>\n\t\t{{#if (eq api \"orpc\")}}\n\t\t<p>API: {$privateDataQuery.data?.message}</p>\n\t\t{{/if}}\n\t\t{{#if (eq payments \"polar\")}}\n\t\t<p>Plan: {customerState?.activeSubscriptions?.length > 0 ? \"Pro\" : \"Free\"}</p>\n\t\t{#if customerState?.activeSubscriptions?.length > 0}\n\t\t\t<button onclick={async () => await authClient.customer.portal()}>\n\t\t\t\tManage Subscription\n\t\t\t</button>\n\t\t{:else}\n\t\t\t<button onclick={async () => await authClient.checkout({ slug: \"pro\" })}>\n\t\t\t\tUpgrade to Pro\n\t\t\t</button>\n\t\t{/if}\n\t\t{{/if}}\n\t</div>\n{/if}\n`],\n  [\"auth/better-auth/web/svelte/src/routes/login/+page.svelte.hbs\", `<script lang=\"ts\">\n\timport SignInForm from '../../components/SignInForm.svelte';\n\timport SignUpForm from '../../components/SignUpForm.svelte';\n\n\tlet showSignIn = $state(true);\n</script>\n\n{#if showSignIn}\n\t<SignInForm switchToSignUp={() => showSignIn = false} />\n{:else}\n\t<SignUpForm switchToSignIn={() => showSignIn = true} />\n{/if}\n`],\n  [\"auth/clerk/convex/backend/convex/auth.config.ts.hbs\", `export default {\n\tproviders: [\n\t\t{\n\t\t\t// Replace with your own Clerk Issuer URL from your \"convex\" JWT template\n\t\t\t// or with \\`process.env.CLERK_JWT_ISSUER_DOMAIN\\`\n\t\t\t// and configure CLERK_JWT_ISSUER_DOMAIN on the Convex Dashboard\n\t\t\t// See https://docs.convex.dev/auth/clerk#configuring-dev-and-prod-instances\n\t\t\tdomain: process.env.CLERK_JWT_ISSUER_DOMAIN,\n\t\t\tapplicationID: \"convex\",\n\t\t},\n\t],\n};\n`],\n  [\"auth/clerk/convex/backend/convex/privateData.ts.hbs\", `import { query } from \"./_generated/server\";\n\nexport const get = query({\n\targs: {},\n\thandler: async (ctx) => {\n\t\tconst identity = await ctx.auth.getUserIdentity();\n\t\tif (identity === null) {\n\t\t\treturn {\n\t\t\t\tmessage: \"Not authenticated\",\n\t\t\t};\n\t\t}\n\t\treturn {\n\t\t\tmessage: \"This is private\",\n\t\t};\n\t},\n});\n`],\n  [\"auth/clerk/convex/native/base/app/(auth)/_layout.tsx.hbs\", `import { Redirect, Stack } from \"expo-router\";\nimport { useAuth } from \"@clerk/expo\";\n\nexport default function AuthRoutesLayout() {\n  const { isLoaded, isSignedIn } = useAuth();\n\n  if (!isLoaded) {\n    return null;\n  }\n\n  if (isSignedIn) {\n    return <Redirect href={\"/\"} />;\n  }\n\n  return <Stack />;\n}\n`],\n  [\"auth/clerk/convex/native/base/app/(auth)/sign-in.tsx.hbs\", `import { useSignIn } from \"@clerk/expo\";\nimport { type Href, Link, useRouter } from \"expo-router\";\nimport React from \"react\";\nimport { Pressable, StyleSheet, Text, TextInput, View } from \"react-native\";\n\nfunction pushDecoratedUrl(router: ReturnType<typeof useRouter>, decorateUrl: (url: string) => string, href: string) {\n  const url = decorateUrl(href);\n  const nextHref = url.startsWith(\"http\") ? new URL(url).pathname : url;\n  router.push(nextHref as Href);\n}\n\nexport default function Page() {\n  const { signIn, errors, fetchStatus } = useSignIn();\n  const router = useRouter();\n  const [emailAddress, setEmailAddress] = React.useState(\"\");\n  const [password, setPassword] = React.useState(\"\");\n  const [code, setCode] = React.useState(\"\");\n  const [statusMessage, setStatusMessage] = React.useState<string | null>(null);\n\n  const emailCodeFactor = signIn.supportedSecondFactors.find(\n    (factor) => factor.strategy === \"email_code\",\n  );\n  const requiresEmailCode =\n    signIn.status === \"needs_client_trust\" ||\n    (signIn.status === \"needs_second_factor\" && !!emailCodeFactor);\n\n  const handleSubmit = async () => {\n    setStatusMessage(null);\n\n    const { error } = await signIn.password({\n      emailAddress,\n      password,\n    });\n\n    if (error) {\n      console.error(JSON.stringify(error, null, 2));\n      setStatusMessage(error.longMessage ?? \"Unable to sign in. Please try again.\");\n      return;\n    }\n\n    if (signIn.status === \"complete\") {\n      await signIn.finalize({\n        navigate: ({ session, decorateUrl }) => {\n          if (session?.currentTask) {\n            console.log(session.currentTask);\n            return;\n          }\n\n          pushDecoratedUrl(router, decorateUrl, \"/\");\n        },\n      });\n    } else if (signIn.status === \"needs_second_factor\" || signIn.status === \"needs_client_trust\") {\n      if (emailCodeFactor) {\n        await signIn.mfa.sendEmailCode();\n        setStatusMessage(\\`We sent a verification code to \\${emailCodeFactor.safeIdentifier}.\\`);\n      } else {\n        console.error(\"Second factor is required, but email_code is not available:\", signIn);\n        setStatusMessage(\"A second factor is required, but this screen only supports email codes right now.\");\n      }\n    } else {\n      console.error(\"Sign-in attempt not complete:\", signIn);\n      setStatusMessage(\"Sign-in could not be completed. Check the logs for more details.\");\n    }\n  };\n\n  const handleVerify = async () => {\n    setStatusMessage(null);\n\n    await signIn.mfa.verifyEmailCode({ code });\n\n    if (signIn.status === \"complete\") {\n      await signIn.finalize({\n        navigate: ({ session, decorateUrl }) => {\n          if (session?.currentTask) {\n            console.log(session.currentTask);\n            return;\n          }\n\n          pushDecoratedUrl(router, decorateUrl, \"/\");\n        },\n      });\n    } else {\n      console.error(\"Sign-in attempt not complete:\", signIn);\n      setStatusMessage(\"That code did not complete sign-in. Please try again.\");\n    }\n  };\n\n  if (requiresEmailCode) {\n    return (\n      <View style={styles.container}>\n        <Text style={styles.title}>Verify your account</Text>\n        {statusMessage && <Text style={styles.helper}>{statusMessage}</Text>}\n        <TextInput\n          style={styles.input}\n          value={code}\n          placeholder=\"Enter your verification code\"\n          placeholderTextColor=\"#666666\"\n          onChangeText={(value) => setCode(value)}\n          keyboardType=\"numeric\"\n        />\n        {errors.fields.code && <Text style={styles.error}>{errors.fields.code.message}</Text>}\n        <Pressable\n          style={({ pressed }) => [\n            styles.button,\n            fetchStatus === \"fetching\" && styles.buttonDisabled,\n            pressed && styles.buttonPressed,\n          ]}\n          onPress={handleVerify}\n          disabled={fetchStatus === \"fetching\"}\n        >\n          <Text style={styles.buttonText}>Verify</Text>\n        </Pressable>\n        <Pressable\n          style={({ pressed }) => [styles.secondaryButton, pressed && styles.buttonPressed]}\n          onPress={() => signIn.mfa.sendEmailCode()}\n        >\n          <Text style={styles.secondaryButtonText}>I need a new code</Text>\n        </Pressable>\n      </View>\n    );\n  }\n\n  return (\n    <View style={styles.container}>\n      <Text style={styles.title}>Sign in</Text>\n      {statusMessage && <Text style={styles.helper}>{statusMessage}</Text>}\n      <Text style={styles.label}>Email address</Text>\n      <TextInput\n        style={styles.input}\n        autoCapitalize=\"none\"\n        value={emailAddress}\n        placeholder=\"Enter email\"\n        placeholderTextColor=\"#666666\"\n        onChangeText={(value) => setEmailAddress(value)}\n        keyboardType=\"email-address\"\n      />\n      {errors.fields.identifier && <Text style={styles.error}>{errors.fields.identifier.message}</Text>}\n      <Text style={styles.label}>Password</Text>\n      <TextInput\n        style={styles.input}\n        value={password}\n        placeholder=\"Enter password\"\n        placeholderTextColor=\"#666666\"\n        secureTextEntry={true}\n        onChangeText={(value) => setPassword(value)}\n      />\n      {errors.fields.password && <Text style={styles.error}>{errors.fields.password.message}</Text>}\n      <Pressable\n        style={({ pressed }) => [\n          styles.button,\n          (!emailAddress || !password || fetchStatus === \"fetching\") && styles.buttonDisabled,\n          pressed && styles.buttonPressed,\n        ]}\n        onPress={handleSubmit}\n        disabled={!emailAddress || !password || fetchStatus === \"fetching\"}\n      >\n        <Text style={styles.buttonText}>Sign in</Text>\n      </Pressable>\n      <View style={styles.linkContainer}>\n        <Text>Don't have an account? </Text>\n        <Link href=\"/sign-up\">\n          <Text style={styles.linkText}>Sign up</Text>\n        </Link>\n      </View>\n    </View>\n  );\n}\n\nconst styles = StyleSheet.create({\n  container: {\n    flex: 1,\n    padding: 20,\n    gap: 12,\n  },\n  title: {\n    marginBottom: 8,\n    fontSize: 24,\n    fontWeight: \"700\",\n  },\n  label: {\n    fontWeight: \"600\",\n    fontSize: 14,\n  },\n  input: {\n    borderWidth: 1,\n    borderColor: \"#ccc\",\n    borderRadius: 8,\n    padding: 12,\n    fontSize: 16,\n    backgroundColor: \"#fff\",\n  },\n  button: {\n    backgroundColor: \"#0a7ea4\",\n    paddingVertical: 12,\n    paddingHorizontal: 24,\n    borderRadius: 8,\n    alignItems: \"center\",\n    marginTop: 8,\n  },\n  buttonPressed: {\n    opacity: 0.7,\n  },\n  buttonDisabled: {\n    opacity: 0.5,\n  },\n  buttonText: {\n    color: \"#fff\",\n    fontWeight: \"600\",\n  },\n  secondaryButton: {\n    paddingVertical: 12,\n    paddingHorizontal: 24,\n    borderRadius: 8,\n    alignItems: \"center\",\n    marginTop: 8,\n  },\n  secondaryButtonText: {\n    color: \"#0a7ea4\",\n    fontWeight: \"600\",\n  },\n  linkContainer: {\n    flexDirection: \"row\",\n    gap: 4,\n    marginTop: 12,\n    alignItems: \"center\",\n  },\n  linkText: {\n    color: \"#0a7ea4\",\n    fontWeight: \"600\",\n  },\n  error: {\n    color: \"#d32f2f\",\n    fontSize: 12,\n    marginTop: -8,\n  },\n  helper: {\n    color: \"#555555\",\n    fontSize: 13,\n  },\n});\n`],\n  [\"auth/clerk/convex/native/base/app/(auth)/sign-up.tsx.hbs\", `import { useAuth, useSignUp } from \"@clerk/expo\";\nimport { type Href, Link, useRouter } from \"expo-router\";\nimport React from \"react\";\nimport { Pressable, StyleSheet, Text, TextInput, View } from \"react-native\";\n\nfunction pushDecoratedUrl(router: ReturnType<typeof useRouter>, decorateUrl: (url: string) => string, href: string) {\n  const url = decorateUrl(href);\n  const nextHref = url.startsWith(\"http\") ? new URL(url).pathname : url;\n  router.push(nextHref as Href);\n}\n\nexport default function Page() {\n  const { signUp, errors, fetchStatus } = useSignUp();\n  const { isSignedIn } = useAuth();\n  const router = useRouter();\n  const [emailAddress, setEmailAddress] = React.useState(\"\");\n  const [password, setPassword] = React.useState(\"\");\n  const [code, setCode] = React.useState(\"\");\n  const [statusMessage, setStatusMessage] = React.useState<string | null>(null);\n\n  const handleSubmit = async () => {\n    setStatusMessage(null);\n\n    const { error } = await signUp.password({\n      emailAddress,\n      password,\n    });\n\n    if (error) {\n      console.error(JSON.stringify(error, null, 2));\n      setStatusMessage(error.longMessage ?? \"Unable to sign up. Please try again.\");\n      return;\n    }\n\n    await signUp.verifications.sendEmailCode();\n    setStatusMessage(\\`We sent a verification code to \\${emailAddress}.\\`);\n  };\n\n  const handleVerify = async () => {\n    setStatusMessage(null);\n\n    await signUp.verifications.verifyEmailCode({\n      code,\n    });\n\n    if (signUp.status === \"complete\") {\n      await signUp.finalize({\n        navigate: ({ session, decorateUrl }) => {\n          if (session?.currentTask) {\n            console.log(session.currentTask);\n            return;\n          }\n\n          pushDecoratedUrl(router, decorateUrl, \"/\");\n        },\n      });\n    } else {\n      console.error(\"Sign-up attempt not complete:\", signUp);\n      setStatusMessage(\"That code did not complete sign-up. Please try again.\");\n    }\n  };\n\n  if (signUp.status === \"complete\" || isSignedIn) {\n    return null;\n  }\n\n  if (\n    signUp.status === \"missing_requirements\" &&\n    signUp.unverifiedFields.includes(\"email_address\") &&\n    signUp.missingFields.length === 0\n  ) {\n    return (\n      <View style={styles.container}>\n        <Text style={styles.title}>Verify your account</Text>\n        {statusMessage && <Text style={styles.helper}>{statusMessage}</Text>}\n        <TextInput\n          style={styles.input}\n          value={code}\n          placeholder=\"Enter your verification code\"\n          placeholderTextColor=\"#666666\"\n          onChangeText={(value) => setCode(value)}\n          keyboardType=\"numeric\"\n        />\n        {errors.fields.code && <Text style={styles.error}>{errors.fields.code.message}</Text>}\n        <Pressable\n          style={({ pressed }) => [\n            styles.button,\n            fetchStatus === \"fetching\" && styles.buttonDisabled,\n            pressed && styles.buttonPressed,\n          ]}\n          onPress={handleVerify}\n          disabled={fetchStatus === \"fetching\"}\n        >\n          <Text style={styles.buttonText}>Verify</Text>\n        </Pressable>\n        <Pressable\n          style={({ pressed }) => [styles.secondaryButton, pressed && styles.buttonPressed]}\n          onPress={() => signUp.verifications.sendEmailCode()}\n        >\n          <Text style={styles.secondaryButtonText}>I need a new code</Text>\n        </Pressable>\n      </View>\n    );\n  }\n\n  return (\n    <View style={styles.container}>\n      <Text style={styles.title}>Sign up</Text>\n      {statusMessage && <Text style={styles.helper}>{statusMessage}</Text>}\n      <Text style={styles.label}>Email address</Text>\n      <TextInput\n        style={styles.input}\n        autoCapitalize=\"none\"\n        value={emailAddress}\n        placeholder=\"Enter email\"\n        placeholderTextColor=\"#666666\"\n        onChangeText={(value) => setEmailAddress(value)}\n        keyboardType=\"email-address\"\n      />\n      {errors.fields.emailAddress && (\n        <Text style={styles.error}>{errors.fields.emailAddress.message}</Text>\n      )}\n      <Text style={styles.label}>Password</Text>\n      <TextInput\n        style={styles.input}\n        value={password}\n        placeholder=\"Enter password\"\n        placeholderTextColor=\"#666666\"\n        secureTextEntry={true}\n        onChangeText={(value) => setPassword(value)}\n      />\n      {errors.fields.password && <Text style={styles.error}>{errors.fields.password.message}</Text>}\n      <Pressable\n        style={({ pressed }) => [\n          styles.button,\n          (!emailAddress || !password || fetchStatus === \"fetching\") && styles.buttonDisabled,\n          pressed && styles.buttonPressed,\n        ]}\n        onPress={handleSubmit}\n        disabled={!emailAddress || !password || fetchStatus === \"fetching\"}\n      >\n        <Text style={styles.buttonText}>Sign up</Text>\n      </Pressable>\n      <View style={styles.linkContainer}>\n        <Text>Already have an account? </Text>\n        <Link href=\"/sign-in\">\n          <Text style={styles.linkText}>Sign in</Text>\n        </Link>\n      </View>\n      <View nativeID=\"clerk-captcha\" />\n    </View>\n  );\n}\n\nconst styles = StyleSheet.create({\n  container: {\n    flex: 1,\n    padding: 20,\n    gap: 12,\n  },\n  title: {\n    marginBottom: 8,\n    fontSize: 24,\n    fontWeight: \"700\",\n  },\n  label: {\n    fontWeight: \"600\",\n    fontSize: 14,\n  },\n  input: {\n    borderWidth: 1,\n    borderColor: \"#ccc\",\n    borderRadius: 8,\n    padding: 12,\n    fontSize: 16,\n    backgroundColor: \"#fff\",\n  },\n  button: {\n    backgroundColor: \"#0a7ea4\",\n    paddingVertical: 12,\n    paddingHorizontal: 24,\n    borderRadius: 8,\n    alignItems: \"center\",\n    marginTop: 8,\n  },\n  buttonPressed: {\n    opacity: 0.7,\n  },\n  buttonDisabled: {\n    opacity: 0.5,\n  },\n  buttonText: {\n    color: \"#fff\",\n    fontWeight: \"600\",\n  },\n  secondaryButton: {\n    paddingVertical: 12,\n    paddingHorizontal: 24,\n    borderRadius: 8,\n    alignItems: \"center\",\n    marginTop: 8,\n  },\n  secondaryButtonText: {\n    color: \"#0a7ea4\",\n    fontWeight: \"600\",\n  },\n  linkContainer: {\n    flexDirection: \"row\",\n    gap: 4,\n    marginTop: 12,\n    alignItems: \"center\",\n  },\n  linkText: {\n    color: \"#0a7ea4\",\n    fontWeight: \"600\",\n  },\n  error: {\n    color: \"#d32f2f\",\n    fontSize: 12,\n    marginTop: -8,\n  },\n  helper: {\n    color: \"#555555\",\n    fontSize: 13,\n  },\n});\n`],\n  [\"auth/clerk/convex/native/base/components/sign-out-button.tsx.hbs\", `import { useClerk } from \"@clerk/expo\";\nimport { useRouter } from \"expo-router\";\nimport { Text, TouchableOpacity } from \"react-native\";\n\nexport const SignOutButton = () => {\n  // Use \\`useClerk()\\` to access the \\`signOut()\\` function\n  const { signOut } = useClerk();\n  const router = useRouter();\n\n  const handleSignOut = async () => {\n    try {\n      await signOut();\n      // Redirect to your desired page\n      router.replace(\"/\");\n    } catch (err) {\n      // See https://clerk.com/docs/custom-flows/error-handling\n      // for more info on error handling\n      console.error(JSON.stringify(err, null, 2));\n    }\n  };\n\n  return (\n    <TouchableOpacity onPress={handleSignOut}>\n      <Text>Sign out</Text>\n    </TouchableOpacity>\n  );\n};\n`],\n  [\"auth/clerk/convex/web/react/next/src/app/dashboard/page.tsx.hbs\", `\"use client\";\n\nimport { api } from \"@{{projectName}}/backend/convex/_generated/api\";\nimport { SignInButton, UserButton, useUser } from \"@clerk/nextjs\";\nimport { Authenticated, AuthLoading, Unauthenticated, useQuery } from \"convex/react\";\n\nexport default function Dashboard() {\n  const user = useUser();\n  const privateData = useQuery(api.privateData.get);\n\n  return (\n    <>\n      <Authenticated>\n        <div>\n          <h1>Dashboard</h1>\n          <p>Welcome {user.user?.fullName}</p>\n          <p>privateData: {privateData?.message}</p>\n          <UserButton />\n        </div>\n      </Authenticated>\n      <Unauthenticated>\n        <SignInButton />\n      </Unauthenticated>\n      <AuthLoading>\n        <div>Loading...</div>\n      </AuthLoading>\n    </>\n  );\n}\n`],\n  [\"auth/clerk/convex/web/react/next/src/proxy.ts.hbs\", `import { clerkMiddleware } from \"@clerk/nextjs/server\";\n\nexport default clerkMiddleware();\n\nexport const config = {\n\tmatcher: [\n\t\t// Skip Next.js internals and all static files, unless found in search params\n\t\t\"/((?!_next|[^?]*\\\\\\\\.(?:html?|css|js(?!on)|jpe?g|webp|png|gif|svg|ttf|woff2?|ico|csv|docx?|xlsx?|zip|webmanifest)).*)\",\n\t\t// Always run for API routes\n\t\t\"/(api|trpc)(.*)\",\n\t],\n};\n`],\n  [\"auth/clerk/convex/web/react/react-router/src/routes/dashboard.tsx.hbs\", `import { SignInButton, UserButton, useUser } from \"@clerk/react-router\";\nimport { api } from \"@{{projectName}}/backend/convex/_generated/api\";\nimport {\n\tAuthenticated,\n\tAuthLoading,\n\tUnauthenticated,\n\tuseQuery,\n} from \"convex/react\";\n\nexport default function Dashboard() {\n\tconst privateData = useQuery(api.privateData.get);\n\tconst user = useUser();\n\n\treturn (\n\t\t<>\n\t\t\t<Authenticated>\n\t\t\t\t<div>\n\t\t\t\t\t<h1>Dashboard</h1>\n\t\t\t\t\t<p>Welcome {user.user?.fullName}</p>\n\t\t\t\t\t<p>privateData: {privateData?.message}</p>\n\t\t\t\t\t<UserButton />\n\t\t\t\t</div>\n\t\t\t</Authenticated>\n\t\t\t<Unauthenticated>\n\t\t\t\t<SignInButton />\n\t\t\t</Unauthenticated>\n\t\t\t<AuthLoading>\n\t\t\t\t<div>Loading...</div>\n\t\t\t</AuthLoading>\n\t\t</>\n\t);\n}\n`],\n  [\"auth/clerk/convex/web/react/tanstack-router/src/routes/dashboard.tsx.hbs\", `import { SignInButton, UserButton, useUser } from \"@clerk/react\";\nimport { api } from \"@{{projectName}}/backend/convex/_generated/api\";\nimport { createFileRoute } from \"@tanstack/react-router\";\nimport {\n\tAuthenticated,\n\tAuthLoading,\n\tUnauthenticated,\n\tuseQuery,\n} from \"convex/react\";\n\nexport const Route = createFileRoute(\"/dashboard\")({\n\tcomponent: RouteComponent,\n});\n\nfunction RouteComponent() {\n\tconst privateData = useQuery(api.privateData.get);\n\tconst user = useUser()\n\n\treturn (\n\t\t<>\n\t\t\t<Authenticated>\n\t\t\t\t<div>\n\t\t\t\t\t<h1>Dashboard</h1>\n\t\t\t\t\t<p>Welcome {user.user?.fullName}</p>\n\t\t\t\t\t<p>privateData: {privateData?.message}</p>\n\t\t\t\t\t<UserButton />\n\t\t\t\t</div>\n\t\t\t</Authenticated>\n\t\t\t<Unauthenticated>\n\t\t\t\t<SignInButton />\n\t\t\t</Unauthenticated>\n\t\t\t<AuthLoading>\n\t\t\t\t<div>Loading...</div>\n\t\t\t</AuthLoading>\n\t\t</>\n\t);\n}\n`],\n  [\"auth/clerk/convex/web/react/tanstack-start/src/routes/dashboard.tsx.hbs\", `import { SignInButton, UserButton, useUser } from \"@clerk/tanstack-react-start\";\nimport { api } from \"@{{projectName}}/backend/convex/_generated/api\";\nimport { createFileRoute } from \"@tanstack/react-router\";\nimport {\n\tAuthenticated,\n\tAuthLoading,\n\tUnauthenticated,\n\tuseQuery,\n} from \"convex/react\";\n\nexport const Route = createFileRoute(\"/dashboard\")({\n\tcomponent: RouteComponent,\n});\n\nfunction RouteComponent() {\n\tconst privateData = useQuery(api.privateData.get);\n\tconst user = useUser();\n\n\treturn (\n\t\t<>\n\t\t\t<Authenticated>\n\t\t\t\t<div>\n\t\t\t\t\t<h1>Dashboard</h1>\n\t\t\t\t\t<p>Welcome {user.user?.fullName}</p>\n\t\t\t\t\t<p>privateData: {privateData?.message}</p>\n\t\t\t\t\t<UserButton />\n\t\t\t\t</div>\n\t\t\t</Authenticated>\n\t\t\t<Unauthenticated>\n\t\t\t\t<SignInButton />\n\t\t\t</Unauthenticated>\n\t\t\t<AuthLoading>\n\t\t\t\t<div>Loading...</div>\n\t\t\t</AuthLoading>\n\t\t</>\n\t);\n}\n`],\n  [\"auth/clerk/convex/web/react/tanstack-start/src/start.ts.hbs\", `import { clerkMiddleware } from '@clerk/tanstack-react-start/server'\nimport { createStart } from '@tanstack/react-start'\n\nexport const startInstance = createStart(() => {\n\treturn {\n\t\trequestMiddleware: [clerkMiddleware()],\n\t}\n})`],\n  [\"auth/clerk/native/base/app/(auth)/_layout.tsx.hbs\", `import { Redirect, Stack } from \"expo-router\";\nimport { useAuth } from \"@clerk/expo\";\n\nexport default function AuthRoutesLayout() {\n  const { isLoaded, isSignedIn } = useAuth();\n\n  if (!isLoaded) {\n    return null;\n  }\n\n  if (isSignedIn) {\n    return <Redirect href={\"/\"} />;\n  }\n\n  return <Stack />;\n}\n`],\n  [\"auth/clerk/native/base/app/(auth)/sign-in.tsx.hbs\", `import { useSignIn } from \"@clerk/expo\";\nimport { type Href, Link, useRouter } from \"expo-router\";\nimport React from \"react\";\nimport { Pressable, StyleSheet, Text, TextInput, View } from \"react-native\";\n\nfunction pushDecoratedUrl(router: ReturnType<typeof useRouter>, decorateUrl: (url: string) => string, href: string) {\n  const url = decorateUrl(href);\n  const nextHref = url.startsWith(\"http\") ? new URL(url).pathname : url;\n  router.push(nextHref as Href);\n}\n\nexport default function Page() {\n  const { signIn, errors, fetchStatus } = useSignIn();\n  const router = useRouter();\n  const [emailAddress, setEmailAddress] = React.useState(\"\");\n  const [password, setPassword] = React.useState(\"\");\n  const [code, setCode] = React.useState(\"\");\n  const [statusMessage, setStatusMessage] = React.useState<string | null>(null);\n\n  const emailCodeFactor = signIn.supportedSecondFactors.find(\n    (factor) => factor.strategy === \"email_code\",\n  );\n  const requiresEmailCode =\n    signIn.status === \"needs_client_trust\" ||\n    (signIn.status === \"needs_second_factor\" && !!emailCodeFactor);\n\n  const handleSubmit = async () => {\n    setStatusMessage(null);\n\n    const { error } = await signIn.password({\n      emailAddress,\n      password,\n    });\n\n    if (error) {\n      console.error(JSON.stringify(error, null, 2));\n      setStatusMessage(error.longMessage ?? \"Unable to sign in. Please try again.\");\n      return;\n    }\n\n    if (signIn.status === \"complete\") {\n      await signIn.finalize({\n        navigate: ({ session, decorateUrl }) => {\n          if (session?.currentTask) {\n            console.log(session.currentTask);\n            return;\n          }\n\n          pushDecoratedUrl(router, decorateUrl, \"/\");\n        },\n      });\n    } else if (signIn.status === \"needs_second_factor\" || signIn.status === \"needs_client_trust\") {\n      if (emailCodeFactor) {\n        await signIn.mfa.sendEmailCode();\n        setStatusMessage(\\`We sent a verification code to \\${emailCodeFactor.safeIdentifier}.\\`);\n      } else {\n        console.error(\"Second factor is required, but email_code is not available:\", signIn);\n        setStatusMessage(\"A second factor is required, but this screen only supports email codes right now.\");\n      }\n    } else {\n      console.error(\"Sign-in attempt not complete:\", signIn);\n      setStatusMessage(\"Sign-in could not be completed. Check the logs for more details.\");\n    }\n  };\n\n  const handleVerify = async () => {\n    setStatusMessage(null);\n\n    await signIn.mfa.verifyEmailCode({ code });\n\n    if (signIn.status === \"complete\") {\n      await signIn.finalize({\n        navigate: ({ session, decorateUrl }) => {\n          if (session?.currentTask) {\n            console.log(session.currentTask);\n            return;\n          }\n\n          pushDecoratedUrl(router, decorateUrl, \"/\");\n        },\n      });\n    } else {\n      console.error(\"Sign-in attempt not complete:\", signIn);\n      setStatusMessage(\"That code did not complete sign-in. Please try again.\");\n    }\n  };\n\n  if (requiresEmailCode) {\n    return (\n      <View style={styles.container}>\n        <Text style={styles.title}>Verify your account</Text>\n        {statusMessage && <Text style={styles.helper}>{statusMessage}</Text>}\n        <TextInput\n          style={styles.input}\n          value={code}\n          placeholder=\"Enter your verification code\"\n          placeholderTextColor=\"#666666\"\n          onChangeText={(value) => setCode(value)}\n          keyboardType=\"numeric\"\n        />\n        {errors.fields.code && <Text style={styles.error}>{errors.fields.code.message}</Text>}\n        <Pressable\n          style={({ pressed }) => [\n            styles.button,\n            fetchStatus === \"fetching\" && styles.buttonDisabled,\n            pressed && styles.buttonPressed,\n          ]}\n          onPress={handleVerify}\n          disabled={fetchStatus === \"fetching\"}\n        >\n          <Text style={styles.buttonText}>Verify</Text>\n        </Pressable>\n        <Pressable\n          style={({ pressed }) => [styles.secondaryButton, pressed && styles.buttonPressed]}\n          onPress={() => signIn.mfa.sendEmailCode()}\n        >\n          <Text style={styles.secondaryButtonText}>I need a new code</Text>\n        </Pressable>\n      </View>\n    );\n  }\n\n  return (\n    <View style={styles.container}>\n      <Text style={styles.title}>Sign in</Text>\n      {statusMessage && <Text style={styles.helper}>{statusMessage}</Text>}\n      <Text style={styles.label}>Email address</Text>\n      <TextInput\n        style={styles.input}\n        autoCapitalize=\"none\"\n        value={emailAddress}\n        placeholder=\"Enter email\"\n        placeholderTextColor=\"#666666\"\n        onChangeText={(value) => setEmailAddress(value)}\n        keyboardType=\"email-address\"\n      />\n      {errors.fields.identifier && <Text style={styles.error}>{errors.fields.identifier.message}</Text>}\n      <Text style={styles.label}>Password</Text>\n      <TextInput\n        style={styles.input}\n        value={password}\n        placeholder=\"Enter password\"\n        placeholderTextColor=\"#666666\"\n        secureTextEntry={true}\n        onChangeText={(value) => setPassword(value)}\n      />\n      {errors.fields.password && <Text style={styles.error}>{errors.fields.password.message}</Text>}\n      <Pressable\n        style={({ pressed }) => [\n          styles.button,\n          (!emailAddress || !password || fetchStatus === \"fetching\") && styles.buttonDisabled,\n          pressed && styles.buttonPressed,\n        ]}\n        onPress={handleSubmit}\n        disabled={!emailAddress || !password || fetchStatus === \"fetching\"}\n      >\n        <Text style={styles.buttonText}>Sign in</Text>\n      </Pressable>\n      <View style={styles.linkContainer}>\n        <Text>Don't have an account? </Text>\n        <Link href=\"/sign-up\">\n          <Text style={styles.linkText}>Sign up</Text>\n        </Link>\n      </View>\n    </View>\n  );\n}\n\nconst styles = StyleSheet.create({\n  container: {\n    flex: 1,\n    padding: 20,\n    gap: 12,\n  },\n  title: {\n    marginBottom: 8,\n    fontSize: 24,\n    fontWeight: \"700\",\n  },\n  label: {\n    fontWeight: \"600\",\n    fontSize: 14,\n  },\n  input: {\n    borderWidth: 1,\n    borderColor: \"#ccc\",\n    borderRadius: 8,\n    padding: 12,\n    fontSize: 16,\n    backgroundColor: \"#fff\",\n  },\n  button: {\n    backgroundColor: \"#0a7ea4\",\n    paddingVertical: 12,\n    paddingHorizontal: 24,\n    borderRadius: 8,\n    alignItems: \"center\",\n    marginTop: 8,\n  },\n  buttonPressed: {\n    opacity: 0.7,\n  },\n  buttonDisabled: {\n    opacity: 0.5,\n  },\n  buttonText: {\n    color: \"#fff\",\n    fontWeight: \"600\",\n  },\n  secondaryButton: {\n    paddingVertical: 12,\n    paddingHorizontal: 24,\n    borderRadius: 8,\n    alignItems: \"center\",\n    marginTop: 8,\n  },\n  secondaryButtonText: {\n    color: \"#0a7ea4\",\n    fontWeight: \"600\",\n  },\n  linkContainer: {\n    flexDirection: \"row\",\n    gap: 4,\n    marginTop: 12,\n    alignItems: \"center\",\n  },\n  linkText: {\n    color: \"#0a7ea4\",\n    fontWeight: \"600\",\n  },\n  error: {\n    color: \"#d32f2f\",\n    fontSize: 12,\n    marginTop: -8,\n  },\n  helper: {\n    color: \"#555555\",\n    fontSize: 13,\n  },\n});\n`],\n  [\"auth/clerk/native/base/app/(auth)/sign-up.tsx.hbs\", `import { useAuth, useSignUp } from \"@clerk/expo\";\nimport { type Href, Link, useRouter } from \"expo-router\";\nimport React from \"react\";\nimport { Pressable, StyleSheet, Text, TextInput, View } from \"react-native\";\n\nfunction pushDecoratedUrl(router: ReturnType<typeof useRouter>, decorateUrl: (url: string) => string, href: string) {\n  const url = decorateUrl(href);\n  const nextHref = url.startsWith(\"http\") ? new URL(url).pathname : url;\n  router.push(nextHref as Href);\n}\n\nexport default function Page() {\n  const { signUp, errors, fetchStatus } = useSignUp();\n  const { isSignedIn } = useAuth();\n  const router = useRouter();\n  const [emailAddress, setEmailAddress] = React.useState(\"\");\n  const [password, setPassword] = React.useState(\"\");\n  const [code, setCode] = React.useState(\"\");\n  const [statusMessage, setStatusMessage] = React.useState<string | null>(null);\n\n  const handleSubmit = async () => {\n    setStatusMessage(null);\n\n    const { error } = await signUp.password({\n      emailAddress,\n      password,\n    });\n\n    if (error) {\n      console.error(JSON.stringify(error, null, 2));\n      setStatusMessage(error.longMessage ?? \"Unable to sign up. Please try again.\");\n      return;\n    }\n\n    await signUp.verifications.sendEmailCode();\n    setStatusMessage(\\`We sent a verification code to \\${emailAddress}.\\`);\n  };\n\n  const handleVerify = async () => {\n    setStatusMessage(null);\n\n    await signUp.verifications.verifyEmailCode({\n      code,\n    });\n\n    if (signUp.status === \"complete\") {\n      await signUp.finalize({\n        navigate: ({ session, decorateUrl }) => {\n          if (session?.currentTask) {\n            console.log(session.currentTask);\n            return;\n          }\n\n          pushDecoratedUrl(router, decorateUrl, \"/\");\n        },\n      });\n    } else {\n      console.error(\"Sign-up attempt not complete:\", signUp);\n      setStatusMessage(\"That code did not complete sign-up. Please try again.\");\n    }\n  };\n\n  if (signUp.status === \"complete\" || isSignedIn) {\n    return null;\n  }\n\n  if (\n    signUp.status === \"missing_requirements\" &&\n    signUp.unverifiedFields.includes(\"email_address\") &&\n    signUp.missingFields.length === 0\n  ) {\n    return (\n      <View style={styles.container}>\n        <Text style={styles.title}>Verify your account</Text>\n        {statusMessage && <Text style={styles.helper}>{statusMessage}</Text>}\n        <TextInput\n          style={styles.input}\n          value={code}\n          placeholder=\"Enter your verification code\"\n          placeholderTextColor=\"#666666\"\n          onChangeText={(value) => setCode(value)}\n          keyboardType=\"numeric\"\n        />\n        {errors.fields.code && <Text style={styles.error}>{errors.fields.code.message}</Text>}\n        <Pressable\n          style={({ pressed }) => [\n            styles.button,\n            fetchStatus === \"fetching\" && styles.buttonDisabled,\n            pressed && styles.buttonPressed,\n          ]}\n          onPress={handleVerify}\n          disabled={fetchStatus === \"fetching\"}\n        >\n          <Text style={styles.buttonText}>Verify</Text>\n        </Pressable>\n        <Pressable\n          style={({ pressed }) => [styles.secondaryButton, pressed && styles.buttonPressed]}\n          onPress={() => signUp.verifications.sendEmailCode()}\n        >\n          <Text style={styles.secondaryButtonText}>I need a new code</Text>\n        </Pressable>\n      </View>\n    );\n  }\n\n  return (\n    <View style={styles.container}>\n      <Text style={styles.title}>Sign up</Text>\n      {statusMessage && <Text style={styles.helper}>{statusMessage}</Text>}\n      <Text style={styles.label}>Email address</Text>\n      <TextInput\n        style={styles.input}\n        autoCapitalize=\"none\"\n        value={emailAddress}\n        placeholder=\"Enter email\"\n        placeholderTextColor=\"#666666\"\n        onChangeText={(value) => setEmailAddress(value)}\n        keyboardType=\"email-address\"\n      />\n      {errors.fields.emailAddress && (\n        <Text style={styles.error}>{errors.fields.emailAddress.message}</Text>\n      )}\n      <Text style={styles.label}>Password</Text>\n      <TextInput\n        style={styles.input}\n        value={password}\n        placeholder=\"Enter password\"\n        placeholderTextColor=\"#666666\"\n        secureTextEntry={true}\n        onChangeText={(value) => setPassword(value)}\n      />\n      {errors.fields.password && <Text style={styles.error}>{errors.fields.password.message}</Text>}\n      <Pressable\n        style={({ pressed }) => [\n          styles.button,\n          (!emailAddress || !password || fetchStatus === \"fetching\") && styles.buttonDisabled,\n          pressed && styles.buttonPressed,\n        ]}\n        onPress={handleSubmit}\n        disabled={!emailAddress || !password || fetchStatus === \"fetching\"}\n      >\n        <Text style={styles.buttonText}>Sign up</Text>\n      </Pressable>\n      <View style={styles.linkContainer}>\n        <Text>Already have an account? </Text>\n        <Link href=\"/sign-in\">\n          <Text style={styles.linkText}>Sign in</Text>\n        </Link>\n      </View>\n      <View nativeID=\"clerk-captcha\" />\n    </View>\n  );\n}\n\nconst styles = StyleSheet.create({\n  container: {\n    flex: 1,\n    padding: 20,\n    gap: 12,\n  },\n  title: {\n    marginBottom: 8,\n    fontSize: 24,\n    fontWeight: \"700\",\n  },\n  label: {\n    fontWeight: \"600\",\n    fontSize: 14,\n  },\n  input: {\n    borderWidth: 1,\n    borderColor: \"#ccc\",\n    borderRadius: 8,\n    padding: 12,\n    fontSize: 16,\n    backgroundColor: \"#fff\",\n  },\n  button: {\n    backgroundColor: \"#0a7ea4\",\n    paddingVertical: 12,\n    paddingHorizontal: 24,\n    borderRadius: 8,\n    alignItems: \"center\",\n    marginTop: 8,\n  },\n  buttonPressed: {\n    opacity: 0.7,\n  },\n  buttonDisabled: {\n    opacity: 0.5,\n  },\n  buttonText: {\n    color: \"#fff\",\n    fontWeight: \"600\",\n  },\n  secondaryButton: {\n    paddingVertical: 12,\n    paddingHorizontal: 24,\n    borderRadius: 8,\n    alignItems: \"center\",\n    marginTop: 8,\n  },\n  secondaryButtonText: {\n    color: \"#0a7ea4\",\n    fontWeight: \"600\",\n  },\n  linkContainer: {\n    flexDirection: \"row\",\n    gap: 4,\n    marginTop: 12,\n    alignItems: \"center\",\n  },\n  linkText: {\n    color: \"#0a7ea4\",\n    fontWeight: \"600\",\n  },\n  error: {\n    color: \"#d32f2f\",\n    fontSize: 12,\n    marginTop: -8,\n  },\n  helper: {\n    color: \"#555555\",\n    fontSize: 13,\n  },\n});\n`],\n  [\"auth/clerk/native/base/components/sign-out-button.tsx.hbs\", `import { useClerk } from \"@clerk/expo\";\nimport { useRouter } from \"expo-router\";\nimport { Text, TouchableOpacity } from \"react-native\";\n\nexport const SignOutButton = () => {\n  const { signOut } = useClerk();\n  const router = useRouter();\n\n  const handleSignOut = async () => {\n    try {\n      await signOut();\n      router.replace(\"/\");\n    } catch (err) {\n      console.error(JSON.stringify(err, null, 2));\n    }\n  };\n\n  return (\n    <TouchableOpacity onPress={handleSignOut}>\n      <Text>Sign out</Text>\n    </TouchableOpacity>\n  );\n};\n`],\n  [\"auth/clerk/native/base/utils/clerk-auth.ts.hbs\", `type ClerkTokenGetter = () => Promise<string | null>;\n\nlet clerkTokenGetter: ClerkTokenGetter | null = null;\n\nexport function setClerkAuthTokenGetter(getToken: ClerkTokenGetter | null) {\n\tclerkTokenGetter = getToken;\n}\n\nexport async function getClerkAuthToken() {\n\treturn (await clerkTokenGetter?.()) ?? null;\n}\n`],\n  [\"auth/clerk/web/react/base/src/utils/clerk-auth.ts.hbs\", `type ClerkTokenGetter = () => Promise<string | null>;\n\nlet clerkTokenGetter: ClerkTokenGetter | null = null;\n\nexport function setClerkAuthTokenGetter(getToken: ClerkTokenGetter | null) {\n\tclerkTokenGetter = getToken;\n}\n\nexport async function getClerkAuthToken() {\n\treturn (await clerkTokenGetter?.()) ?? null;\n}\n`],\n  [\"auth/clerk/web/react/next/src/app/dashboard/page.tsx.hbs\", `\"use client\";\n\n{{#if (eq api \"orpc\")}}\nimport { useQuery } from \"@tanstack/react-query\";\nimport { orpc } from \"@/utils/orpc\";\n{{/if}}\n{{#if (eq api \"trpc\")}}\nimport { useQuery } from \"@tanstack/react-query\";\nimport { trpc } from \"@/utils/trpc\";\n{{/if}}\nimport { SignInButton, UserButton, useUser } from \"@clerk/nextjs\";\n\nexport default function Dashboard() {\n  const user = useUser();\n  const nameFromParts = [user.user?.firstName, user.user?.lastName].filter(Boolean).join(\" \");\n  const displayName =\n    user.user?.fullName ||\n    nameFromParts ||\n    user.user?.username ||\n    user.user?.primaryEmailAddress?.emailAddress ||\n    user.user?.primaryPhoneNumber?.phoneNumber ||\n    \"User\";\n  {{#if (eq api \"orpc\")}}\n  const privateData = useQuery({\n    ...orpc.privateData.queryOptions(),\n    enabled: user.isLoaded && !!user.user,\n  });\n  {{/if}}\n  {{#if (eq api \"trpc\")}}\n  const privateData = useQuery({\n    ...trpc.privateData.queryOptions(),\n    enabled: user.isLoaded && !!user.user,\n  });\n  {{/if}}\n\n  if (!user.isLoaded) {\n    return <div className=\"p-6\">Loading...</div>;\n  }\n\n  if (!user.user) {\n    return (\n      <div className=\"p-6\">\n        <SignInButton />\n      </div>\n    );\n  }\n\n  return (\n    <div className=\"space-y-4 p-6\">\n      <h1 className=\"text-2xl font-semibold\">Dashboard</h1>\n      <p>Welcome {displayName}</p>\n      {{#if (or (eq api \"orpc\") (eq api \"trpc\"))}}\n      <p>API: {privateData.data?.message}</p>\n      {{/if}}\n      <UserButton />\n    </div>\n  );\n}\n`],\n  [\"auth/clerk/web/react/next/src/proxy.ts.hbs\", `import { clerkMiddleware } from \"@clerk/nextjs/server\";\n\nexport default clerkMiddleware();\n\nexport const config = {\n\tmatcher: [\n\t\t// Skip Next.js internals and all static files, unless found in search params\n\t\t\"/((?!_next|[^?]*\\\\\\\\.(?:html?|css|js(?!on)|jpe?g|webp|png|gif|svg|ttf|woff2?|ico|csv|docx?|xlsx?|zip|webmanifest)).*)\",\n\t\t// Always run for API routes\n\t\t\"/(api|trpc)(.*)\",\n\t],\n};\n`],\n  [\"auth/clerk/web/react/react-router/src/routes/dashboard.tsx.hbs\", `{{#if (eq api \"orpc\")}}\nimport { useQuery } from \"@tanstack/react-query\";\nimport { orpc } from \"@/utils/orpc\";\n{{/if}}\n{{#if (eq api \"trpc\")}}\nimport { useQuery } from \"@tanstack/react-query\";\nimport { trpc } from \"@/utils/trpc\";\n{{/if}}\nimport { SignInButton, UserButton, useUser } from \"@clerk/react-router\";\n\nexport default function Dashboard() {\n  const user = useUser();\n  const nameFromParts = [user.user?.firstName, user.user?.lastName].filter(Boolean).join(\" \");\n  const displayName =\n    user.user?.fullName ||\n    nameFromParts ||\n    user.user?.username ||\n    user.user?.primaryEmailAddress?.emailAddress ||\n    user.user?.primaryPhoneNumber?.phoneNumber ||\n    \"User\";\n  {{#if (eq api \"orpc\")}}\n  const privateData = useQuery({\n    ...orpc.privateData.queryOptions(),\n    enabled: user.isLoaded && !!user.user,\n  });\n  {{/if}}\n  {{#if (eq api \"trpc\")}}\n  const privateData = useQuery({\n    ...trpc.privateData.queryOptions(),\n    enabled: user.isLoaded && !!user.user,\n  });\n  {{/if}}\n\n  if (!user.isLoaded) {\n    return <div className=\"p-6\">Loading...</div>;\n  }\n\n  if (!user.user) {\n    return (\n      <div className=\"p-6\">\n        <SignInButton />\n      </div>\n    );\n  }\n\n  return (\n    <div className=\"space-y-4 p-6\">\n      <h1 className=\"text-2xl font-semibold\">Dashboard</h1>\n      <p>Welcome {displayName}</p>\n      {{#if (or (eq api \"orpc\") (eq api \"trpc\"))}}\n      <p>API: {privateData.data?.message}</p>\n      {{/if}}\n      <UserButton />\n    </div>\n  );\n}\n`],\n  [\"auth/clerk/web/react/tanstack-router/src/routes/dashboard.tsx.hbs\", `{{#if (eq api \"orpc\")}}\nimport { useQuery } from \"@tanstack/react-query\";\nimport { orpc } from \"@/utils/orpc\";\n{{/if}}\n{{#if (eq api \"trpc\")}}\nimport { useQuery } from \"@tanstack/react-query\";\nimport { trpc } from \"@/utils/trpc\";\n{{/if}}\nimport { SignInButton, UserButton, useUser } from \"@clerk/react\";\nimport { createFileRoute } from \"@tanstack/react-router\";\n\nexport const Route = createFileRoute(\"/dashboard\")({\n\tcomponent: RouteComponent,\n});\n\nfunction RouteComponent() {\n\tconst user = useUser();\n\tconst nameFromParts = [user.user?.firstName, user.user?.lastName].filter(Boolean).join(\" \");\n\tconst displayName =\n\t\tuser.user?.fullName ||\n\t\tnameFromParts ||\n\t\tuser.user?.username ||\n\t\tuser.user?.primaryEmailAddress?.emailAddress ||\n\t\tuser.user?.primaryPhoneNumber?.phoneNumber ||\n\t\t\"User\";\n\t{{#if (eq api \"orpc\")}}\n\tconst privateData = useQuery({\n\t\t...orpc.privateData.queryOptions(),\n\t\tenabled: user.isLoaded && !!user.user,\n\t});\n\t{{/if}}\n\t{{#if (eq api \"trpc\")}}\n\tconst privateData = useQuery({\n\t\t...trpc.privateData.queryOptions(),\n\t\tenabled: user.isLoaded && !!user.user,\n\t});\n\t{{/if}}\n\n\tif (!user.isLoaded) {\n\t\treturn <div className=\"p-6\">Loading...</div>;\n\t}\n\n\tif (!user.user) {\n\t\treturn (\n\t\t\t<div className=\"p-6\">\n\t\t\t\t<SignInButton />\n\t\t\t</div>\n\t\t);\n\t}\n\n\treturn (\n\t\t<div className=\"space-y-4 p-6\">\n\t\t\t<h1 className=\"text-2xl font-semibold\">Dashboard</h1>\n\t\t\t<p>Welcome {displayName}</p>\n\t\t\t{{#if (or (eq api \"orpc\") (eq api \"trpc\"))}}\n\t\t\t<p>API: {privateData.data?.message}</p>\n\t\t\t{{/if}}\n\t\t\t<UserButton />\n\t\t</div>\n\t);\n}\n`],\n  [\"auth/clerk/web/react/tanstack-start/src/routes/dashboard.tsx.hbs\", `{{#if (eq api \"trpc\")}}\nimport { useTRPC } from \"@/utils/trpc\";\nimport { useQuery } from \"@tanstack/react-query\";\n{{/if}}\n{{#if (eq api \"orpc\")}}\nimport { useQuery } from \"@tanstack/react-query\";\nimport { orpc } from \"@/utils/orpc\";\n{{/if}}\nimport { SignInButton, UserButton, useUser } from \"@clerk/tanstack-react-start\";\nimport { createFileRoute } from \"@tanstack/react-router\";\n\nexport const Route = createFileRoute(\"/dashboard\")({\n\tcomponent: RouteComponent,\n});\n\nfunction RouteComponent() {\n\tconst user = useUser();\n\tconst nameFromParts = [user.user?.firstName, user.user?.lastName].filter(Boolean).join(\" \");\n\tconst displayName =\n\t\tuser.user?.fullName ||\n\t\tnameFromParts ||\n\t\tuser.user?.username ||\n\t\tuser.user?.primaryEmailAddress?.emailAddress ||\n\t\tuser.user?.primaryPhoneNumber?.phoneNumber ||\n\t\t\"User\";\n\t{{#if (eq api \"trpc\")}}\n\tconst trpc = useTRPC();\n\tconst privateData = useQuery({\n\t\t...trpc.privateData.queryOptions(),\n\t\tenabled: user.isLoaded && !!user.user,\n\t});\n\t{{/if}}\n\t{{#if (eq api \"orpc\")}}\n\tconst privateData = useQuery({\n\t\t...orpc.privateData.queryOptions(),\n\t\tenabled: user.isLoaded && !!user.user,\n\t});\n\t{{/if}}\n\n\tif (!user.isLoaded) {\n\t\treturn <div className=\"p-6\">Loading...</div>;\n\t}\n\n\tif (!user.user) {\n\t\treturn (\n\t\t\t<div className=\"p-6\">\n\t\t\t\t<SignInButton />\n\t\t\t</div>\n\t\t);\n\t}\n\n\treturn (\n\t\t<div className=\"space-y-4 p-6\">\n\t\t\t<h1 className=\"text-2xl font-semibold\">Dashboard</h1>\n\t\t\t<p>Welcome {displayName}</p>\n\t\t\t{{#if (or (eq api \"orpc\") (eq api \"trpc\"))}}\n\t\t\t<p>API: {privateData.data?.message}</p>\n\t\t\t{{/if}}\n\t\t\t<UserButton />\n\t\t</div>\n\t);\n}\n`],\n  [\"auth/clerk/web/react/tanstack-start/src/start.ts.hbs\", `import { clerkMiddleware } from '@clerk/tanstack-react-start/server'\nimport { createStart } from '@tanstack/react-start'\n\nexport const startInstance = createStart(() => {\n\treturn {\n\t\trequestMiddleware: [clerkMiddleware()],\n\t}\n})\n`],\n  [\"backend/convex/packages/backend/_gitignore\", `\n.env.local\n`],\n  [\"backend/convex/packages/backend/convex/convex.config.ts.hbs\", `import { defineApp } from \"convex/server\";\n{{#if (eq auth \"better-auth\")}}\nimport betterAuth from \"@convex-dev/better-auth/convex.config\";\n{{/if}}\n{{#if (includes examples \"ai\")}}\nimport agent from \"@convex-dev/agent/convex.config\";\n{{/if}}\n\nconst app = defineApp();\n{{#if (eq auth \"better-auth\")}}\napp.use(betterAuth);\n{{/if}}\n{{#if (includes examples \"ai\")}}\napp.use(agent);\n{{/if}}\n\nexport default app;\n`],\n  [\"backend/convex/packages/backend/convex/healthCheck.ts.hbs\", `import { query } from \"./_generated/server\";\n\nexport const get = query({\n  handler: async () => {\n    return \"OK\";\n  },\n});\n`],\n  [\"backend/convex/packages/backend/convex/README.md\", `# Welcome to your Convex functions directory!\n\nWrite your Convex functions here.\nSee https://docs.convex.dev/functions for more.\n\nA query function that takes two arguments looks like:\n\n\\`\\`\\`ts\n// convex/myFunctions.ts\nimport { query } from \"./_generated/server\";\nimport { v } from \"convex/values\";\n\nexport const myQueryFunction = query({\n  // Validators for arguments.\n  args: {\n    first: v.number(),\n    second: v.string(),\n  },\n\n  // Function implementation.\n  handler: async (ctx, args) => {\n    // Read the database as many times as you need here.\n    // See https://docs.convex.dev/database/reading-data.\n    const documents = await ctx.db.query(\"tablename\").collect();\n\n    // Arguments passed from the client are properties of the args object.\n    console.log(args.first, args.second);\n\n    // Write arbitrary JavaScript here: filter, aggregate, build derived data,\n    // remove non-public properties, or create new objects.\n    return documents;\n  },\n});\n\\`\\`\\`\n\nUsing this query function in a React component looks like:\n\n\\`\\`\\`ts\nconst data = useQuery(api.myFunctions.myQueryFunction, {\n  first: 10,\n  second: \"hello\",\n});\n\\`\\`\\`\n\nA mutation function looks like:\n\n\\`\\`\\`ts\n// convex/myFunctions.ts\nimport { mutation } from \"./_generated/server\";\nimport { v } from \"convex/values\";\n\nexport const myMutationFunction = mutation({\n  // Validators for arguments.\n  args: {\n    first: v.string(),\n    second: v.string(),\n  },\n\n  // Function implementation.\n  handler: async (ctx, args) => {\n    // Insert or modify documents in the database here.\n    // Mutations can also read from the database like queries.\n    // See https://docs.convex.dev/database/writing-data.\n    const message = { body: args.first, author: args.second };\n    const id = await ctx.db.insert(\"messages\", message);\n\n    // Optionally, return a value from your mutation.\n    return await ctx.db.get(\"messages\", id);\n  },\n});\n\\`\\`\\`\n\nUsing this mutation function in a React component looks like:\n\n\\`\\`\\`ts\nconst mutation = useMutation(api.myFunctions.myMutationFunction);\nfunction handleButtonPress() {\n  // fire and forget, the most common way to use mutations\n  mutation({ first: \"Hello!\", second: \"me\" });\n  // OR\n  // use the result once the mutation has completed\n  mutation({ first: \"Hello!\", second: \"me\" }).then((result) => console.log(result));\n}\n\\`\\`\\`\n\nUse the Convex CLI to push your functions to a deployment. See everything\nthe Convex CLI can do by running \\`npx convex -h\\` in your project root\ndirectory. To learn more, launch the docs with \\`npx convex docs\\`.\n`],\n  [\"backend/convex/packages/backend/convex/schema.ts.hbs\", `import { defineSchema, defineTable } from \"convex/server\";\nimport { v } from \"convex/values\";\n\nexport default defineSchema({\n{{#if (includes examples \"todo\")}}\n  todos: defineTable({\n    text: v.string(),\n    completed: v.boolean(),\n  }),\n{{/if}}\n});\n`],\n  [\"backend/convex/packages/backend/convex/tsconfig.json.hbs\", `{\n  /* This TypeScript project config describes the environment that\n   * Convex functions run in and is used to typecheck them.\n   * You can modify it, but some settings are required to use Convex.\n   */\n  \"compilerOptions\": {\n    /* These settings are not required by Convex and can be modified. */\n    \"allowJs\": true,\n    \"strict\": true,\n    \"moduleResolution\": \"Bundler\",\n    \"jsx\": \"react-jsx\",\n    \"skipLibCheck\": true,\n    \"allowSyntheticDefaultImports\": true,\n\n    /* These compiler options are required by Convex */\n    \"target\": \"ESNext\",\n    \"lib\": [\"ES2021\", \"dom\"],\n    \"forceConsistentCasingInFileNames\": true,\n    \"module\": \"ESNext\",\n    \"isolatedModules\": true,\n    \"noEmit\": true\n  },\n  \"include\": [\"./**/*\"],\n  \"exclude\": [\"./_generated\"]\n}\n`],\n  [\"backend/convex/packages/backend/package.json.hbs\", `{\n  \"name\": \"@{{projectName}}/backend\",\n  \"version\": \"1.0.0\",\n  \"scripts\": {\n    \"dev\": \"convex dev\",\n    \"dev:setup\": \"convex dev --configure --until-success\"\n  },\n  \"author\": \"\",\n  \"license\": \"ISC\",\n  \"description\": \"\",\n  \"devDependencies\": {\n    \"@types/node\": \"^24.3.0\"\n  },\n  \"dependencies\": {}\n}\n`],\n  [\"backend/server/base/_gitignore\", `# prod\ndist/\n/build\n/out/\n\n# dev\n.yarn/\n!.yarn/patches\n!.yarn/plugins\n!.yarn/releases\n!.yarn/versions\n.vscode/*\n!.vscode/launch.json\n!.vscode/*.code-snippets\n.idea/workspace.xml\n.idea/usage.statistics.xml\n.idea/shelf\n.wrangler\n.alchemy\n/.next/\n.vercel\nprisma/generated/\n\n\n# deps\nnode_modules/\n/node_modules\n/.pnp\n.pnp.*\n\n# env\n.env*\n.env.production\n!.env.example\n.dev.vars\n\n# logs\nlogs/\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\npnpm-debug.log*\nlerna-debug.log*\n\n# misc\n.DS_Store\n*.pem\n\n# local db\n*.db*\n\n# typescript\n*.tsbuildinfo\nnext-env.d.ts\n`],\n  [\"backend/server/base/package.json.hbs\", `{\n\t\"name\": \"server\",\n\t\"main\": \"src/index.ts\",\n\t\"type\": \"module\",\n\t\"scripts\": {\n\t\t\"build\": \"tsdown\",\n\t\t\"check-types\": \"tsc -b\",\n\t\t\"compile\": \"bun build --compile --minify --sourcemap --bytecode ./src/index.ts --outfile server\"\n\t},\n\t\"dependencies\": {},\n\t{{#if (eq dbSetup 'supabase')}}\n\t\"trustedDependencies\": [\n        \"supabase\"\n    ],\n    {{/if}}\n\t\"devDependencies\": {}\n}\n`],\n  [\"backend/server/base/tsconfig.json.hbs\", `{\n  \"extends\": \"@{{projectName}}/config/tsconfig.base.json\",\n  \"compilerOptions\": {\n    \"composite\": true,\n\t\t\"outDir\": \"dist\",\n\t\t\"paths\": {\n      \"@/*\": [\"./src/*\"]\n    },\n    \"jsx\": \"react-jsx\"{{#if (eq backend \"hono\")}},\n    \"jsxImportSource\": \"hono/jsx\"{{/if}}\n  }\n}\n`],\n  [\"backend/server/base/tsdown.config.ts.hbs\", `import { defineConfig } from 'tsdown';\n\nexport default defineConfig({\n    entry: './src/index.ts',\n    format: 'esm',\n    outDir: './dist',\n    clean: true,\n    noExternal: [/@{{projectName}}\\\\/.*/]\n});\n`],\n  [\"backend/server/elysia/src/index.ts.hbs\", `import { env } from \"@{{projectName}}/env/server\";\n{{#if (eq runtime \"node\")}}\nimport { node } from \"@elysiajs/node\";\n{{/if}}\nimport { Elysia } from \"elysia\";\nimport { cors } from \"@elysiajs/cors\";\n{{#if (includes examples \"ai\")}}\nimport { google } from \"@ai-sdk/google\";\nimport { convertToModelMessages, streamText, type UIMessage, wrapLanguageModel } from \"ai\";\nimport { devToolsMiddleware } from \"@ai-sdk/devtools\";\n{{/if}}\n{{#if (eq api \"trpc\")}}\nimport { createContext } from \"@{{projectName}}/api/context\";\nimport { appRouter } from \"@{{projectName}}/api/routers/index\";\nimport { fetchRequestHandler } from \"@trpc/server/adapters/fetch\";\n{{/if}}\n{{#if (eq api \"orpc\")}}\nimport { OpenAPIHandler } from \"@orpc/openapi/fetch\";\nimport { OpenAPIReferencePlugin } from \"@orpc/openapi/plugins\";\nimport { ZodToJsonSchemaConverter } from \"@orpc/zod/zod4\";\nimport { RPCHandler } from \"@orpc/server/fetch\";\nimport { onError } from \"@orpc/server\";\nimport { appRouter } from \"@{{projectName}}/api/routers/index\";\nimport { createContext } from \"@{{projectName}}/api/context\";\n{{/if}}\n{{#if (eq auth \"better-auth\")}}\nimport { auth } from \"@{{projectName}}/auth\";\n{{/if}}\n\n{{#if (eq api \"orpc\")}}\nconst rpcHandler = new RPCHandler(appRouter, {\n\tinterceptors: [\n\t\tonError((error) => {\n\t\t\tconsole.error(error);\n\t\t}),\n\t],\n});\nconst apiHandler = new OpenAPIHandler(appRouter, {\n\tplugins: [\n\t\tnew OpenAPIReferencePlugin({\n\t\t\tschemaConverters: [new ZodToJsonSchemaConverter()],\n\t\t}),\n\t],\n\tinterceptors: [\n\t\tonError((error) => {\n\t\t\tconsole.error(error);\n\t\t}),\n\t],\n});\n{{/if}}\n\n{{#if (eq runtime \"node\")}}\nnew Elysia({ adapter: node() })\n{{else}}\nnew Elysia()\n{{/if}}\n\t.use(\n\t\tcors({\n\t\t\torigin: env.CORS_ORIGIN,\n\t\t\tmethods: [\"GET\", \"POST\", \"OPTIONS\"],\n{{#if (or (eq auth \"better-auth\") (eq auth \"clerk\"))}}\n\t\t\tallowedHeaders: [\"Content-Type\", \"Authorization\"],\n{{/if}}\n{{#if (eq auth \"better-auth\")}}\n\t\t\tcredentials: true,\n{{/if}}\n\t\t}),\n\t)\n{{#if (eq auth \"better-auth\")}}\n\t.all(\"/api/auth/*\", async (context) => {\n\t\tconst { request, status } = context;\n\t\tif ([\"POST\", \"GET\"].includes(request.method)) {\n\t\t\treturn auth.handler(request);\n\t\t}\n\t\treturn status(405)\n\t})\n{{/if}}\n{{#if (eq api \"orpc\")}}\n\t.all(\n\t\t\"/rpc*\",\n\t\tasync (context) => {\n\t\t\tconst { response } = await rpcHandler.handle(context.request, {\n\t\t\t\tprefix: \"/rpc\",\n\t\t\t\tcontext: await createContext({ context }),\n\t\t\t});\n\t\t\treturn response ?? new Response(\"Not Found\", { status: 404 });\n\t\t},\n\t\t{\n\t\t\tparse: \"none\",\n\t\t}\n\t)\n\t.all(\n\t\t\"/api-reference*\",\n\t\tasync (context) => {\n\t\t\tconst { response } = await apiHandler.handle(context.request, {\n\t\t\t\tprefix: \"/api-reference\",\n\t\t\t\tcontext: await createContext({ context }),\n\t\t\t});\n\t\t\treturn response ?? new Response(\"Not Found\", { status: 404 });\n\t\t},\n\t\t{\n\t\t\tparse: \"none\",\n\t\t}\n\t)\n{{/if}}\n{{#if (eq api \"trpc\")}}\n\t.all(\"/trpc/*\", async (context) => {\n\t\tconst res = await fetchRequestHandler({\n\t\t\tendpoint: \"/trpc\",\n\t\t\trouter: appRouter,\n\t\t\treq: context.request,\n\t\t\tcreateContext: () => createContext({ context }),\n\t\t});\n\t\treturn res;\n\t})\n{{/if}}\n{{#if (includes examples \"ai\")}}\n\t.post(\"/ai\", async (context) => {\n\t\tconst body = (await context.request.json()) as { messages?: UIMessage[] };\n\t\tconst uiMessages = body.messages || [];\n\t\tconst model = wrapLanguageModel({\n\t\t\tmodel: google(\"gemini-2.5-flash\"),\n\t\t\tmiddleware: devToolsMiddleware(),\n\t\t});\n\t\tconst result = streamText({\n\t\t\tmodel,\n\t\t\tmessages: await convertToModelMessages(uiMessages),\n\t\t});\n\n\t\treturn result.toUIMessageStreamResponse();\n\t})\n{{/if}}\n\t.get(\"/\", () => \"OK\")\n\t.listen(3000, () => {\n\t\tconsole.log(\"Server is running on http://localhost:3000\");\n\t});\n`],\n  [\"backend/server/express/src/index.ts.hbs\", `import { env } from \"@{{projectName}}/env/server\";\n{{#if (eq api \"trpc\")}}\nimport { createExpressMiddleware } from \"@trpc/server/adapters/express\";\nimport { createContext } from \"@{{projectName}}/api/context\";\nimport { appRouter } from \"@{{projectName}}/api/routers/index\";\n{{/if}}\n{{#if (eq api \"orpc\")}}\nimport { OpenAPIHandler } from \"@orpc/openapi/node\";\nimport { OpenAPIReferencePlugin } from \"@orpc/openapi/plugins\";\nimport { ZodToJsonSchemaConverter } from \"@orpc/zod/zod4\";\nimport { RPCHandler } from \"@orpc/server/node\";\nimport { onError } from \"@orpc/server\";\nimport { appRouter } from \"@{{projectName}}/api/routers/index\";\n{{#if (or (eq auth \"better-auth\") (eq auth \"clerk\"))}}\nimport { createContext } from \"@{{projectName}}/api/context\";\n{{/if}}\n{{/if}}\nimport cors from \"cors\";\nimport express from \"express\";\n{{#if (includes examples \"ai\")}}\nimport { streamText, type UIMessage, convertToModelMessages, wrapLanguageModel } from \"ai\";\nimport { google } from \"@ai-sdk/google\";\nimport { devToolsMiddleware } from \"@ai-sdk/devtools\";\n{{/if}}\n{{#if (eq auth \"better-auth\")}}\nimport { auth } from \"@{{projectName}}/auth\";\nimport { toNodeHandler } from \"better-auth/node\";\n{{/if}}\n{{#if (eq auth \"clerk\")}}\nimport { clerkMiddleware } from \"@clerk/express\";\n{{/if}}\n\nconst app = express();\n\napp.use(\n\tcors({\n\t\torigin: env.CORS_ORIGIN,\n\t\tmethods: [\"GET\", \"POST\", \"OPTIONS\"],\n{{#if (or (eq auth \"better-auth\") (eq auth \"clerk\"))}}\n\t\tallowedHeaders: [\"Content-Type\", \"Authorization\"],\n{{/if}}\n{{#if (eq auth \"better-auth\")}}\n\t\tcredentials: true,\n{{/if}}\n\t})\n);\n\n{{#if (eq auth \"clerk\")}}\napp.use(clerkMiddleware());\n{{/if}}\n\n{{#if (eq auth \"better-auth\")}}\napp.all(\"/api/auth{/*path}\", toNodeHandler(auth));\n{{/if}}\n\n{{#if (eq api \"trpc\")}}\napp.use(\n\t\"/trpc\",\n\tcreateExpressMiddleware({\n\t\trouter: appRouter,\n\t\tcreateContext,\n\t})\n);\n{{/if}}\n\n{{#if (eq api \"orpc\")}}\nconst rpcHandler = new RPCHandler(appRouter, {\n\tinterceptors: [\n\t\tonError((error) => {\n\t\t\tconsole.error(error);\n\t\t}),\n\t],\n});\nconst apiHandler = new OpenAPIHandler(appRouter, {\n\tplugins: [\n\t\tnew OpenAPIReferencePlugin({\n\t\t\tschemaConverters: [new ZodToJsonSchemaConverter()],\n\t\t}),\n\t],\n\tinterceptors: [\n\t\tonError((error) => {\n\t\t\tconsole.error(error);\n\t\t}),\n\t],\n});\n\napp.use(async (req, res, next) => {\n\tconst rpcResult = await rpcHandler.handle(req, res, {\n\t\tprefix: \"/rpc\",\n{{#if (or (eq auth \"better-auth\") (eq auth \"clerk\"))}}\n\t\tcontext: await createContext({ req }),\n{{else}}\n\t\tcontext: {},\n{{/if}}\n\t});\n\tif (rpcResult.matched) return;\n\n\tconst apiResult = await apiHandler.handle(req, res, {\n\t\tprefix: \"/api-reference\",\n{{#if (or (eq auth \"better-auth\") (eq auth \"clerk\"))}}\n\t\tcontext: await createContext({ req }),\n{{else}}\n\t\tcontext: {},\n{{/if}}\n\t});\n\tif (apiResult.matched) return;\n\n\tnext();\n});\n{{/if}}\n\napp.use(express.json());\n\n{{#if (includes examples \"ai\")}}\napp.post(\"/ai\", async (req, res) => {\n\tconst { messages = [] } = (req.body || {}) as { messages: UIMessage[] };\n\tconst model = wrapLanguageModel({\n\t\tmodel: google(\"gemini-2.5-flash\"),\n\t\tmiddleware: devToolsMiddleware(),\n\t});\n\tconst result = streamText({\n\t\tmodel,\n\t\tmessages: await convertToModelMessages(messages),\n\t});\n\tresult.pipeUIMessageStreamToResponse(res);\n});\n{{/if}}\n\napp.get(\"/\", (_req, res) => {\n\tres.status(200).send(\"OK\");\n});\n\napp.listen(3000, () => {\n\tconsole.log(\"Server is running on http://localhost:3000\");\n});\n`],\n  [\"backend/server/fastify/src/index.ts.hbs\", `import { env } from \"@{{projectName}}/env/server\";\nimport Fastify from \"fastify\";\nimport fastifyCors from \"@fastify/cors\";\n\n{{#if (eq api \"trpc\")}}\nimport { fastifyTRPCPlugin, type FastifyTRPCPluginOptions } from \"@trpc/server/adapters/fastify\";\nimport { createContext } from \"@{{projectName}}/api/context\";\nimport { appRouter, type AppRouter } from \"@{{projectName}}/api/routers/index\";\n{{/if}}\n\n{{#if (eq api \"orpc\")}}\nimport { OpenAPIHandler } from \"@orpc/openapi/fastify\";\nimport { OpenAPIReferencePlugin } from \"@orpc/openapi/plugins\";\nimport { ZodToJsonSchemaConverter } from \"@orpc/zod/zod4\";\nimport { RPCHandler } from \"@orpc/server/fastify\";\nimport { onError } from \"@orpc/server\";\nimport { createContext } from \"@{{projectName}}/api/context\";\nimport { appRouter } from \"@{{projectName}}/api/routers/index\";\n{{/if}}\n\n{{#if (includes examples \"ai\")}}\nimport { streamText, type UIMessage, convertToModelMessages, wrapLanguageModel } from \"ai\";\nimport { google } from \"@ai-sdk/google\";\nimport { devToolsMiddleware } from \"@ai-sdk/devtools\";\n{{/if}}\n\n{{#if (eq auth \"better-auth\")}}\nimport { auth } from \"@{{projectName}}/auth\";\n{{/if}}\n{{#if (eq auth \"clerk\")}}\nimport { clerkPlugin } from \"@clerk/fastify\";\n{{/if}}\n\nconst baseCorsConfig = {\n\torigin: env.CORS_ORIGIN,\n\tmethods: [\"GET\", \"POST\", \"PUT\", \"DELETE\", \"OPTIONS\"],\n\tallowedHeaders: [\n\t\t\"Content-Type\",\n\t\t\"Authorization\",\n\t\t\"X-Requested-With\"\n\t],\n\tcredentials: true,\n\tmaxAge: 86400,\n};\n\n{{#if (eq api \"orpc\")}}\nconst rpcHandler = new RPCHandler(appRouter, {\n\tinterceptors: [\n\t\tonError((error) => {\n\t\t\tconsole.error(error);\n\t\t}),\n\t],\n});\n\nconst apiHandler = new OpenAPIHandler(appRouter, {\n\tplugins: [\n\t\tnew OpenAPIReferencePlugin({\n\t\t\tschemaConverters: [new ZodToJsonSchemaConverter()],\n\t\t}),\n\t],\n\tinterceptors: [\n\t\tonError((error) => {\n\t\t\tconsole.error(error);\n\t\t}),\n\t],\n});\n\nconst fastify = Fastify({\n\tlogger: true,\n});\n{{else}}\nconst fastify = Fastify({\n\tlogger: true,\n});\n{{/if}}\n\nfastify.register(fastifyCors, baseCorsConfig);\n{{#if (eq auth \"clerk\")}}\nfastify.register(clerkPlugin);\n{{/if}}\n\n{{#if (eq api \"orpc\")}}\nfastify.register(async (rpcApp) => {\n\t// Fully utilize oRPC features by letting oRPC parse the request body.\n\trpcApp.addContentTypeParser(\"*\", (_, _payload, done) => {\n\t\tdone(null, undefined);\n\t});\n\n\trpcApp.all(\"/rpc/*\", async (request, reply) => {\n\t\tconst { matched } = await rpcHandler.handle(request, reply, {\n\t\t\tcontext: await createContext({{#if (eq auth \"clerk\")}}request{{else}}request.headers{{/if}}),\n\t\t\tprefix: \"/rpc\",\n\t\t});\n\n\t\tif (!matched) {\n\t\t\treply.status(404).send();\n\t\t}\n\t});\n\n\trpcApp.all(\"/api-reference/*\", async (request, reply) => {\n\t\tconst { matched } = await apiHandler.handle(request, reply, {\n\t\t\tcontext: await createContext({{#if (eq auth \"clerk\")}}request{{else}}request.headers{{/if}}),\n\t\t\tprefix: \"/api-reference\",\n\t\t});\n\n\t\tif (!matched) {\n\t\t\treply.status(404).send();\n\t\t}\n\t});\n});\n{{/if}}\n\n{{#if (eq auth \"better-auth\")}}\nfastify.route({\n\tmethod: [\"GET\", \"POST\"],\n\turl: \"/api/auth/*\",\n\tasync handler(request, reply) {\n\t\ttry {\n\t\t\tconst url = new URL(request.url, \\`http://\\${request.headers.host}\\`);\n\t\t\tconst headers = new Headers();\n\t\t\tObject.entries(request.headers).forEach(([key, value]) => {\n\t\t\t\tif (value) headers.append(key, value.toString());\n\t\t\t});\n\t\t\tconst req = new Request(url.toString(), {\n\t\t\t\tmethod: request.method,\n\t\t\t\theaders,\n\t\t\t\tbody: request.body ? JSON.stringify(request.body) : undefined,\n\t\t\t});\n\t\t\tconst response = await auth.handler(req);\n\t\t\treply.status(response.status);\n\t\t\tresponse.headers.forEach((value, key) => reply.header(key, value));\n\t\t\treply.send(response.body ? await response.text() : null);\n\t\t} catch (error) {\n\t\t\tfastify.log.error({ err: error }, \"Authentication Error:\");\n\t\t\treply.status(500).send({\n\t\t\t\terror: \"Internal authentication error\",\n\t\t\t\tcode: \"AUTH_FAILURE\"\n\t\t\t});\n\t\t}\n\t}\n});\n{{/if}}\n\n{{#if (eq api \"trpc\")}}\nfastify.register(fastifyTRPCPlugin, {\n\tprefix: \"/trpc\",\n\ttrpcOptions: {\n\t\trouter: appRouter,\n\t\tcreateContext,\n\t\tonError({ path, error }) {\n\t\t\tconsole.error(\\`Error in tRPC handler on path '\\${path}':\\`, error);\n\t\t},\n\t} satisfies FastifyTRPCPluginOptions<AppRouter>[\"trpcOptions\"],\n});\n{{/if}}\n\n{{#if (includes examples \"ai\")}}\ninterface AiRequestBody {\n\tid?: string;\n\tmessages: UIMessage[];\n}\n\nfastify.post('/ai', async function (request) {\n\tconst { messages } = request.body as AiRequestBody;\n\tconst model = wrapLanguageModel({\n\t\tmodel: google('gemini-2.5-flash'),\n\t\tmiddleware: devToolsMiddleware(),\n\t});\n\tconst result = streamText({\n\t\tmodel,\n\t\tmessages: await convertToModelMessages(messages),\n\t});\n\n\treturn result.toUIMessageStreamResponse();\n});\n{{/if}}\n\nfastify.get('/', async () => {\n\treturn 'OK';\n});\n\nfastify.listen({ port: 3000 }, (err) => {\n\tif (err) {\n\t\tfastify.log.error(err);\n\t\tprocess.exit(1);\n\t}\n\tconsole.log(\"Server running on port 3000\");\n});\n`],\n  [\"backend/server/hono/src/index.ts.hbs\", `import { env } from \"@{{projectName}}/env/server\";\n{{#if (eq api \"orpc\")}}\nimport { OpenAPIHandler } from \"@orpc/openapi/fetch\";\nimport { OpenAPIReferencePlugin } from \"@orpc/openapi/plugins\";\nimport { ZodToJsonSchemaConverter } from \"@orpc/zod/zod4\";\nimport { RPCHandler } from \"@orpc/server/fetch\";\nimport { onError } from \"@orpc/server\";\nimport { createContext } from \"@{{projectName}}/api/context\";\nimport { appRouter } from \"@{{projectName}}/api/routers/index\";\n{{/if}}\n{{#if (eq api \"trpc\")}}\nimport { trpcServer } from \"@hono/trpc-server\";\nimport { createContext } from \"@{{projectName}}/api/context\";\nimport { appRouter } from \"@{{projectName}}/api/routers/index\";\n{{/if}}\n{{#if (eq auth \"better-auth\")}}\n{{#if (or (eq runtime \"workers\") (eq serverDeploy \"cloudflare\") (and (eq backend \"self\") (eq webDeploy \"cloudflare\")))}}\nimport { createAuth } from \"@{{projectName}}/auth\";\n{{else}}\nimport { auth } from \"@{{projectName}}/auth\";\n{{/if}}\n{{/if}}\nimport { Hono } from \"hono\";\nimport { cors } from \"hono/cors\";\nimport { logger } from \"hono/logger\";\n{{#if (and (includes examples \"ai\") (or (eq runtime \"bun\") (eq runtime \"node\")))}}\nimport { streamText, convertToModelMessages, wrapLanguageModel } from \"ai\";\nimport { google } from \"@ai-sdk/google\";\nimport { devToolsMiddleware } from \"@ai-sdk/devtools\";\n{{/if}}\n{{#if (and (includes examples \"ai\") (eq runtime \"workers\"))}}\nimport { streamText, convertToModelMessages, wrapLanguageModel } from \"ai\";\nimport { createGoogleGenerativeAI } from \"@ai-sdk/google\";\nimport { devToolsMiddleware } from \"@ai-sdk/devtools\";\n{{/if}}\n\nconst app = new Hono();\n\napp.use(logger());\napp.use(\n\t\"/*\",\n\tcors({\n\t\torigin: env.CORS_ORIGIN,\n\t\tallowMethods: [\"GET\", \"POST\", \"OPTIONS\"],\n{{#if (or (eq auth \"better-auth\") (eq auth \"clerk\"))}}\n\t\tallowHeaders: [\"Content-Type\", \"Authorization\"],\n{{/if}}\n{{#if (eq auth \"better-auth\")}}\n\t\tcredentials: true,\n{{/if}}\n\t})\n);\n\n{{#if (eq auth \"better-auth\")}}\napp.on(\n\t[\"POST\", \"GET\"],\n\t\"/api/auth/*\",\n\t(c) =>\n{{#if (or (eq runtime \"workers\") (eq serverDeploy \"cloudflare\") (and (eq backend \"self\") (eq webDeploy \"cloudflare\")))}}\n\t\tcreateAuth().handler(c.req.raw)\n{{else}}\n\t\tauth.handler(c.req.raw)\n{{/if}}\n);\n{{/if}}\n\n{{#if (eq api \"orpc\")}}\nexport const apiHandler = new OpenAPIHandler(appRouter, {\n\tplugins: [\n\t\tnew OpenAPIReferencePlugin({\n\t\t\tschemaConverters: [new ZodToJsonSchemaConverter()],\n\t\t}),\n\t],\n\tinterceptors: [\n\t\tonError((error) => {\n\t\t\tconsole.error(error);\n\t\t}),\n\t],\n});\n\nexport const rpcHandler = new RPCHandler(appRouter, {\n\tinterceptors: [\n\t\tonError((error) => {\n\t\t\tconsole.error(error);\n\t\t}),\n\t],\n});\n\napp.use(\"/*\", async (c, next) => {\n\tconst context = await createContext({ context: c });\n\n\tconst rpcResult = await rpcHandler.handle(c.req.raw, {\n\t\tprefix: \"/rpc\",\n\t\tcontext: context,\n\t});\n\n\tif (rpcResult.matched) {\n\t\treturn c.newResponse(rpcResult.response.body, rpcResult.response);\n\t}\n\n\tconst apiResult = await apiHandler.handle(c.req.raw, {\n\t\tprefix: \"/api-reference\",\n\t\tcontext: context,\n\t});\n\n\tif (apiResult.matched) {\n\t\treturn c.newResponse(apiResult.response.body, apiResult.response);\n\t}\n\n\tawait next();\n});\n{{/if}}\n\n{{#if (eq api \"trpc\")}}\napp.use(\n\t\"/trpc/*\",\n\ttrpcServer({\n\t\trouter: appRouter,\n\t\tcreateContext: (_opts, context) => {\n\t\t\treturn createContext({ context });\n\t\t},\n\t})\n);\n{{/if}}\n\n{{#if (and (includes examples \"ai\") (or (eq runtime \"bun\") (eq runtime \"node\")))}}\napp.post(\"/ai\", async (c) => {\n\tconst body = await c.req.json();\n\tconst uiMessages = body.messages || [];\n\tconst model = wrapLanguageModel({\n\t\tmodel: google(\"gemini-2.5-flash\"),\n\t\tmiddleware: devToolsMiddleware(),\n\t});\n\tconst result = streamText({\n\t\tmodel,\n\t\tmessages: await convertToModelMessages(uiMessages),\n\t});\n\n\treturn result.toUIMessageStreamResponse();\n});\n{{/if}}\n\n{{#if (and (includes examples \"ai\") (eq runtime \"workers\"))}}\napp.post(\"/ai\", async (c) => {\n\tconst body = await c.req.json();\n\tconst uiMessages = body.messages || [];\n\tconst google = createGoogleGenerativeAI({\n\t\tapiKey: env.GOOGLE_GENERATIVE_AI_API_KEY,\n\t});\n\tconst model = wrapLanguageModel({\n\t\tmodel: google(\"gemini-2.5-flash\"),\n\t\tmiddleware: devToolsMiddleware(),\n\t});\n\tconst result = streamText({\n\t\tmodel,\n\t\tmessages: await convertToModelMessages(uiMessages),\n\t});\n\n\treturn result.toUIMessageStreamResponse();\n});\n{{/if}}\n\napp.get(\"/\", (c) => {\n\treturn c.text(\"OK\");\n});\n\n{{#if (eq runtime \"node\")}}\nimport { serve } from \"@hono/node-server\";\n\nserve(\n\t{\n\t\tfetch: app.fetch,\n\t\tport: 3000,\n\t},\n\t(info) => {\n\t\tconsole.log(\\`Server is running on http://localhost:\\${info.port}\\`);\n\t}\n);\n{{else}}\n{{#if (eq runtime \"bun\")}}\nexport default app;\n{{/if}}\n{{#if (eq runtime \"workers\")}}\nexport default app;\n{{/if}}\n{{/if}}\n`],\n  [\"base/_gitignore\", `# Dependencies\nnode_modules\n.pnp\n.pnp.js\n\n# Build outputs\ndist\nbuild\n*.tsbuildinfo\n\n# Environment variables\n.env\n.env*.local\n\n# IDEs and editors\n.vscode/*\n!.vscode/settings.json\n!.vscode/tasks.json\n!.vscode/launch.json\n!.vscode/extensions.json\n.idea\n*.swp\n*.swo\n*~\n.DS_Store\n\n# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\nlerna-debug.log*\n.pnpm-debug.log*\n\n# Turbo\n.turbo\n.nx\n\n# Better-T-Stack\n.alchemy\n\n# Testing\ncoverage\n.nyc_output\n\n# Misc\n*.tgz\n.cache\ntmp\ntemp\n`],\n  [\"base/package.json.hbs\", `{\n  \"name\": \"better-t-stack\",\n  \"private\": true,\n  \"type\": \"module\",\n  \"workspaces\": [\n    \"apps/*\",\n    \"packages/*\"\n  ],\n  \"scripts\": {}\n}\n`],\n  [\"base/tsconfig.json.hbs\", `{\n  \"extends\": \"@{{projectName}}/config/tsconfig.base.json\",\n}\n`],\n  [\"db-setup/docker-compose/mongodb/docker-compose.yml.hbs\", `name: {{projectName}}\n\nservices:\n  mongodb:\n    image: mongo\n    container_name: {{projectName}}-mongodb\n    environment:\n      MONGO_INITDB_ROOT_USERNAME: root\n      MONGO_INITDB_ROOT_PASSWORD: password\n      MONGO_INITDB_DATABASE: {{projectName}}\n    ports:\n      - \"27017:27017\"\n    volumes:\n      - {{projectName}}_mongodb_data:/data/db\n    healthcheck:\n      test: [\"CMD\", \"mongosh\", \"--eval\", \"db.adminCommand('ping')\"]\n      interval: 10s\n      timeout: 5s\n      retries: 5\n    restart: unless-stopped\n\nvolumes:\n  {{projectName}}_mongodb_data:`],\n  [\"db-setup/docker-compose/mysql/docker-compose.yml.hbs\", `name: {{projectName}}\n\nservices:\n  mysql:\n    image: mysql\n    container_name: {{projectName}}-mysql\n    environment:\n      MYSQL_ROOT_PASSWORD: password\n      MYSQL_DATABASE: {{projectName}}\n      MYSQL_USER: user\n      MYSQL_PASSWORD: password\n    ports:\n      - \"3306:3306\"\n    volumes:\n      - {{projectName}}_mysql_data:/var/lib/mysql\n    healthcheck:\n      test: [\"CMD\", \"mysqladmin\", \"ping\", \"-h\", \"localhost\"]\n      interval: 10s\n      timeout: 5s\n      retries: 5\n    restart: unless-stopped\n\nvolumes:\n  {{projectName}}_mysql_data:`],\n  [\"db-setup/docker-compose/postgres/docker-compose.yml.hbs\", `name: {{projectName}}\n\nservices:\n  postgres:\n    image: postgres\n    container_name: {{projectName}}-postgres\n    environment:\n      POSTGRES_DB: {{projectName}}\n      POSTGRES_USER: postgres\n      POSTGRES_PASSWORD: password\n    ports:\n      - \"5432:5432\"\n    volumes:\n      - {{projectName}}_postgres_data:/var/lib/postgresql\n    healthcheck:\n      test: [\"CMD-SHELL\", \"pg_isready -U postgres\"]\n      interval: 10s\n      timeout: 5s\n      retries: 5\n    restart: unless-stopped\n\nvolumes:\n  {{projectName}}_postgres_data:`],\n  [\"db/base/_gitignore\", `# dependencies (bun install)\nnode_modules\n\n# output\nout\ndist\n*.tgz\n/prisma/generated\n\n# code coverage\ncoverage\n*.lcov\n\n# logs\nlogs\n_.log\nreport.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json\n\n# dotenv environment variable files\n.env\n.env.development.local\n.env.test.local\n.env.production.local\n.env.local\n\n# caches\n.eslintcache\n.cache\n*.tsbuildinfo\n\n# IntelliJ based IDEs\n.idea\n\n# Finder (MacOS) folder config\n.DS_Store\n`],\n  [\"db/base/package.json.hbs\", `{\n  \"name\": \"@{{projectName}}/db\",\n  \"type\": \"module\",\n  \"exports\": {\n    \".\": {\n      \"default\": \"./src/index.ts\"\n    },\n    \"./*\": {\n      \"default\": \"./src/*.ts\"\n    }\n  },\n  \"scripts\": {},\n  \"devDependencies\": {}\n}`],\n  [\"db/base/tsconfig.json.hbs\", `{\n  \"extends\": \"@{{projectName}}/config/tsconfig.base.json\",\n  \"compilerOptions\": {\n    \"declaration\": true,\n    \"declarationMap\": true,\n    \"sourceMap\": true,\n    \"outDir\": \"dist\",\n    \"composite\": true\n  }\n}`],\n  [\"db/drizzle/base/src/schema/index.ts.hbs\", `{{#if (eq auth \"better-auth\")}}\nexport * from \"./auth\";\n{{/if}}\n{{#if (includes examples \"todo\")}}\nexport * from \"./todo\";\n{{/if}}\nexport {};`],\n  [\"db/drizzle/mysql/drizzle.config.ts.hbs\", `import { defineConfig } from \"drizzle-kit\";\nimport dotenv from \"dotenv\";\n\ndotenv.config({\n    {{#if (eq backend \"self\")}}\n    path: \"../../apps/web/.env\",\n    {{else}}\n    path: \"../../apps/server/.env\",\n    {{/if}}\n});\n\nexport default defineConfig({\n  schema: \"./src/schema\",\n  out: \"./src/migrations\",\n  dialect: \"mysql\",\n  dbCredentials: {\n    url: process.env.DATABASE_URL || \"\",\n  },\n});\n`],\n  [\"db/drizzle/mysql/src/index.ts.hbs\", `{{#if (or (eq runtime \"bun\") (eq runtime \"node\") (eq runtime \"none\"))}}\n{{#if (and (eq backend \"self\") (eq webDeploy \"cloudflare\") (includes frontend \"svelte\"))}}\nimport type {} from \"@{{projectName}}/env/server\";\n{{else}}\nimport { env } from \"@{{projectName}}/env/server\";\n{{/if}}\nimport * as schema from \"./schema\";\n\n{{#if (eq dbSetup \"planetscale\")}}\nimport { drizzle } from \"drizzle-orm/planetscale-serverless\";\n\nexport function createDb({{#if (and (eq backend \"self\") (eq webDeploy \"cloudflare\") (includes frontend \"svelte\"))}}env: Env{{/if}}) {\n\treturn drizzle({\n\t\tconnection: {\n\t\t\thost: env.DATABASE_HOST,\n\t\t\tusername: env.DATABASE_USERNAME,\n\t\t\tpassword: env.DATABASE_PASSWORD,\n\t\t},\n\t\tschema,\n\t});\n}\n{{else}}\nimport { drizzle } from \"drizzle-orm/mysql2\";\n\nexport function createDb({{#if (and (eq backend \"self\") (eq webDeploy \"cloudflare\") (includes frontend \"svelte\"))}}env: Env{{/if}}) {\n\treturn drizzle({\n\t\tconnection: {\n\t\t\turi: env.DATABASE_URL,\n\t\t},\n\t\tschema,\n\t});\n}\n{{/if}}\n\n{{#if (and (ne serverDeploy \"cloudflare\") (or (ne backend \"self\") (ne webDeploy \"cloudflare\")))}}\nexport const db = createDb();\n{{/if}}\n{{/if}}\n\n{{#if (eq runtime \"workers\")}}\nimport * as schema from \"./schema\";\n\n{{#if (eq dbSetup \"planetscale\")}}\nimport { drizzle } from \"drizzle-orm/planetscale-serverless\";\nimport { env } from \"@{{projectName}}/env/server\";\n\nexport function createDb() {\n\treturn drizzle({\n\t\tconnection: {\n\t\t\thost: env.DATABASE_HOST,\n\t\t\tusername: env.DATABASE_USERNAME,\n\t\t\tpassword: env.DATABASE_PASSWORD,\n\t\t},\n\t\tschema,\n\t});\n}\n{{else}}\nimport { drizzle } from \"drizzle-orm/mysql2\";\nimport { env } from \"@{{projectName}}/env/server\";\n\nexport function createDb() {\n\treturn drizzle({\n\t\tconnection: {\n\t\t\turi: env.DATABASE_URL,\n\t\t},\n\t\tschema,\n\t});\n}\n{{/if}}\n{{/if}}\n`],\n  [\"db/drizzle/postgres/drizzle.config.ts.hbs\", `import { defineConfig } from \"drizzle-kit\";\nimport dotenv from \"dotenv\";\n\ndotenv.config({\n    {{#if (eq backend \"self\")}}\n    path: \"../../apps/web/.env\",\n    {{else}}\n    path: \"../../apps/server/.env\",\n    {{/if}}\n});\n\nexport default defineConfig({\n  schema: \"./src/schema\",\n  out: \"./src/migrations\",\n  dialect: \"postgresql\",\n  dbCredentials: {\n    url: process.env.DATABASE_URL || \"\",\n  },\n});\n`],\n  [\"db/drizzle/postgres/src/index.ts.hbs\", `{{#if (or (eq runtime \"bun\") (eq runtime \"node\") (eq runtime \"none\"))}}\n{{#if (and (eq backend \"self\") (eq webDeploy \"cloudflare\") (includes frontend \"svelte\"))}}\nimport type {} from \"@{{projectName}}/env/server\";\n{{else}}\nimport { env } from \"@{{projectName}}/env/server\";\n{{/if}}\nimport * as schema from \"./schema\";\n\n{{#if (eq dbSetup \"neon\")}}\nimport { neon } from '@neondatabase/serverless';\nimport { drizzle } from 'drizzle-orm/neon-http';\n\nexport function createDb({{#if (and (eq backend \"self\") (eq webDeploy \"cloudflare\") (includes frontend \"svelte\"))}}env: Env{{/if}}) {\n\tconst sql = neon(env.DATABASE_URL);\n\treturn drizzle(sql, { schema });\n}\n{{else}}\nimport { drizzle } from \"drizzle-orm/node-postgres\";\n{{#if (and (eq backend \"self\") (eq webDeploy \"cloudflare\"))}}\nimport { Pool } from \"pg\";\n{{/if}}\n\nexport function createDb({{#if (and (eq backend \"self\") (eq webDeploy \"cloudflare\") (includes frontend \"svelte\"))}}env: Env{{/if}}) {\n{{#if (and (eq backend \"self\") (eq webDeploy \"cloudflare\"))}}\n\tconst pool = new Pool({\n\t\tconnectionString: env.DATABASE_URL,\n\t\tmaxUses: 1,\n\t});\n\n\treturn drizzle({ client: pool, schema });\n{{else}}\n\treturn drizzle(env.DATABASE_URL, { schema });\n{{/if}}\n}\n{{/if}}\n\n{{#if (and (ne serverDeploy \"cloudflare\") (or (ne backend \"self\") (ne webDeploy \"cloudflare\")))}}\nexport const db = createDb();\n{{/if}}\n{{/if}}\n\n{{#if (eq runtime \"workers\")}}\nimport * as schema from \"./schema\";\n\n{{#if (eq dbSetup \"neon\")}}\nimport { neon } from '@neondatabase/serverless';\nimport { drizzle } from 'drizzle-orm/neon-http';\nimport { env } from \"@{{projectName}}/env/server\";\n\nexport function createDb() {\n\tconst sql = neon(env.DATABASE_URL || \"\");\n\treturn drizzle(sql, { schema });\n}\n{{else}}\nimport { drizzle } from \"drizzle-orm/node-postgres\";\nimport { env } from \"@{{projectName}}/env/server\";\nimport { Pool } from \"pg\";\n\nexport function createDb() {\n\tconst pool = new Pool({\n\t\tconnectionString: env.DATABASE_URL || \"\",\n\t\tmaxUses: 1,\n\t});\n\n\treturn drizzle({ client: pool, schema });\n}\n{{/if}}\n{{/if}}\n`],\n  [\"db/drizzle/sqlite/drizzle.config.ts.hbs\", `import { defineConfig } from \"drizzle-kit\";\nimport dotenv from \"dotenv\";\n\ndotenv.config({\n    {{#if (eq backend \"self\")}}\n    path: \"../../apps/web/.env\",\n    {{else}}\n    path: \"../../apps/server/.env\",\n    {{/if}}\n});\n\nexport default defineConfig({\n  schema: \"./src/schema\",\n  out: \"./src/migrations\",\n  {{#if (eq dbSetup \"d1\")}}\n  // DOCS: https://orm.drizzle.team/docs/guides/d1-http-with-drizzle-kit\n  dialect: \"sqlite\",\n  driver: \"d1-http\",\n  {{else}}\n  dialect: \"turso\",\n  dbCredentials: {\n    url: process.env.DATABASE_URL || \"\",\n    {{#if (eq dbSetup \"turso\")}}\n    authToken: process.env.DATABASE_AUTH_TOKEN,\n    {{/if}}\n  },\n  {{/if}}\n});\n`],\n  [\"db/drizzle/sqlite/src/index.ts.hbs\", `{{#if (eq dbSetup \"d1\")}}\nimport * as schema from \"./schema\";\nimport { drizzle } from \"drizzle-orm/d1\";\n{{#if (and (eq backend \"self\") (eq webDeploy \"cloudflare\") (includes frontend \"svelte\"))}}\nimport type {} from \"@{{projectName}}/env/server\";\n{{else}}\nimport { env } from \"@{{projectName}}/env/server\";\n{{/if}}\n\nexport function createDb({{#if (and (eq backend \"self\") (eq webDeploy \"cloudflare\") (includes frontend \"svelte\"))}}env: Env{{/if}}) {\n\treturn drizzle(env.DB, { schema });\n}\n{{else if (or (eq runtime \"bun\") (eq runtime \"node\") (eq runtime \"none\"))}}\n{{#if (and (eq backend \"self\") (eq webDeploy \"cloudflare\") (includes frontend \"svelte\"))}}\nimport type {} from \"@{{projectName}}/env/server\";\n{{else}}\nimport { env } from \"@{{projectName}}/env/server\";\n{{/if}}\nimport * as schema from \"./schema\";\nimport { drizzle } from \"drizzle-orm/libsql\";\nimport { createClient } from \"@libsql/client\";\n\nexport function createDb({{#if (and (eq backend \"self\") (eq webDeploy \"cloudflare\") (includes frontend \"svelte\"))}}env: Env{{/if}}) {\n\tconst client = createClient({\n\t\turl: env.DATABASE_URL,\n{{#if (eq dbSetup \"turso\")}}\n\t\tauthToken: env.DATABASE_AUTH_TOKEN,\n{{/if}}\n\t});\n\n\treturn drizzle({ client, schema });\n}\n\n{{#if (and (ne serverDeploy \"cloudflare\") (or (ne backend \"self\") (ne webDeploy \"cloudflare\")))}}\nexport const db = createDb();\n{{/if}}\n{{else if (eq runtime \"workers\")}}\nimport * as schema from \"./schema\";\nimport { drizzle } from \"drizzle-orm/libsql\";\nimport { env } from \"@{{projectName}}/env/server\";\nimport { createClient } from \"@libsql/client\";\n\nexport function createDb() {\n\tconst client = createClient({\n\t\turl: env.DATABASE_URL || \"\",\n{{#if (eq dbSetup \"turso\")}}\n\t\tauthToken: env.DATABASE_AUTH_TOKEN,\n{{/if}}\n\t});\n\n\treturn drizzle({ client, schema });\n}\n{{/if}}\n`],\n  [\"db/mongoose/mongodb/src/index.ts.hbs\", `import mongoose from \"mongoose\";\nimport { env } from \"@{{projectName}}/env/server\";\n\nawait mongoose.connect(env.DATABASE_URL).catch((error) => {\n\tconsole.log(\"Error connecting to database:\", error);\n});\n\nconst client = mongoose.connection.getClient().db(\"myDB\");\n\nexport { client };\n`],\n  [\"db/prisma/mongodb/prisma.config.ts.hbs\", `import path from \"node:path\";\nimport type { PrismaConfig } from \"prisma\";\nimport dotenv from \"dotenv\";\n\ndotenv.config({\n    {{#if (eq backend \"self\")}}\n    path: \"../../apps/web/.env\",\n    {{else}}\n    path: \"../../apps/server/.env\",\n    {{/if}}\n});\n\nexport default {\n  schema: path.join(\"prisma\", \"schema\"),\n  migrations: {\n    path: path.join(\"prisma\", \"migrations\"),\n  }\n} satisfies PrismaConfig;\n`],\n  [\"db/prisma/mongodb/prisma/schema/schema.prisma.hbs\", `generator client {\n  provider = \"prisma-client\"\n  output   = \"../generated\"\n  moduleFormat = \"esm\"\n  {{#if (eq runtime \"bun\")}}\n  runtime = \"bun\"\n  {{/if}}\n  {{#if (eq runtime \"node\")}}\n  runtime = \"nodejs\"\n  {{/if}}\n  {{#if (or (eq runtime \"workers\") (and (eq backend \"self\") (eq webDeploy \"cloudflare\")))}}\n  runtime = \"workerd\"\n  {{/if}}\n}\n\ndatasource db {\n  provider = \"mongodb\"\n  url      = env(\"DATABASE_URL\")\n}\n`],\n  [\"db/prisma/mongodb/src/index.ts.hbs\", `import { PrismaClient } from \"../prisma/generated/client\";\n\nconst prisma = new PrismaClient();\n\nexport default prisma;\n`],\n  [\"db/prisma/mysql/prisma.config.ts.hbs\", `import path from \"node:path\";\nimport { defineConfig, env } from \"prisma/config\";\nimport dotenv from \"dotenv\";\n\ndotenv.config({\n  {{#if (eq backend \"self\")}}\n  path: \"../../apps/web/.env\",\n  {{else}}\n  path: \"../../apps/server/.env\",\n  {{/if}}\n});\n\nexport default defineConfig({\n  schema: path.join(\"prisma\", \"schema\"),\n  migrations: {\n    path: path.join(\"prisma\", \"migrations\"),\n  },\n  datasource: {\n    url: env(\"DATABASE_URL\"),\n  },\n});`],\n  [\"db/prisma/mysql/prisma/schema/schema.prisma.hbs\", `generator client {\n  provider      = \"prisma-client\"\n  output        = \"../generated\"\n  moduleFormat  = \"esm\"\n  {{#if (eq runtime \"bun\")}}\n  runtime       = \"bun\"\n  {{/if}}\n  {{#if (eq runtime \"node\")}}\n  runtime       = \"nodejs\"\n  {{/if}}\n  {{#if (or (eq runtime \"workers\") (and (eq backend \"self\") (eq webDeploy \"cloudflare\")))}}\n  runtime       = \"workerd\"\n  {{/if}}\n}\n\ndatasource db {\n  provider = \"mysql\"\n  {{#if (eq dbSetup \"planetscale\")}}\n  relationMode = \"prisma\"\n  {{/if}}\n}`],\n  [\"db/prisma/mysql/src/index.ts.hbs\", `{{#if (eq runtime \"workers\")}}\nimport { PrismaClient } from \"../prisma/generated/client\";\nimport { env } from \"@{{projectName}}/env/server\";\n\n{{#if (eq dbSetup \"planetscale\")}}\nimport { PrismaPlanetScale } from \"@prisma/adapter-planetscale\";\n\nexport function createPrismaClient() {\n\tconst adapter = new PrismaPlanetScale({ url: env.DATABASE_URL });\n\treturn new PrismaClient({ adapter });\n}\n{{else}}\nimport { PrismaMariaDb } from \"@prisma/adapter-mariadb\";\n\nexport function createPrismaClient() {\n\tconst databaseUrl: string = env.DATABASE_URL;\n\tconst url: URL = new URL(databaseUrl);\n\tconst connectionConfig = {\n\t\thost: url.hostname,\n\t\tport: parseInt(url.port || \"3306\"),\n\t\tuser: url.username,\n\t\tpassword: url.password,\n\t\tdatabase: url.pathname.slice(1),\n\t};\n\n\tconst adapter = new PrismaMariaDb(connectionConfig);\n\treturn new PrismaClient({ adapter });\n}\n{{/if}}\n{{else}}\nimport { PrismaClient } from \"../prisma/generated/client\";\n{{#if (and (eq backend \"self\") (eq webDeploy \"cloudflare\") (includes frontend \"svelte\"))}}\nimport type {} from \"@{{projectName}}/env/server\";\n{{else}}\nimport { env } from \"@{{projectName}}/env/server\";\n{{/if}}\n\n{{#if (eq dbSetup \"planetscale\")}}\nimport { PrismaPlanetScale } from \"@prisma/adapter-planetscale\";\n\nexport function createPrismaClient({{#if (and (eq backend \"self\") (eq webDeploy \"cloudflare\") (includes frontend \"svelte\"))}}env: Env{{/if}}) {\n\tconst adapter = new PrismaPlanetScale({ url: env.DATABASE_URL });\n\treturn new PrismaClient({ adapter });\n}\n{{else}}\nimport { PrismaMariaDb } from \"@prisma/adapter-mariadb\";\n\nexport function createPrismaClient({{#if (and (eq backend \"self\") (eq webDeploy \"cloudflare\") (includes frontend \"svelte\"))}}env: Env{{/if}}) {\n\tconst databaseUrl: string = env.DATABASE_URL;\n\tconst url: URL = new URL(databaseUrl);\n\tconst connectionConfig = {\n\t\thost: url.hostname,\n\t\tport: parseInt(url.port || \"3306\"),\n\t\tuser: url.username,\n\t\tpassword: url.password,\n\t\tdatabase: url.pathname.slice(1),\n\t};\n\n\tconst adapter = new PrismaMariaDb(connectionConfig);\n\treturn new PrismaClient({ adapter });\n}\n{{/if}}\n\n{{#if (and (ne serverDeploy \"cloudflare\") (or (ne backend \"self\") (ne webDeploy \"cloudflare\")))}}\nconst prisma = createPrismaClient();\nexport default prisma;\n{{/if}}\n{{/if}}\n`],\n  [\"db/prisma/postgres/prisma.config.ts.hbs\", `import path from \"node:path\";\nimport { defineConfig, env } from 'prisma/config'\nimport dotenv from 'dotenv'\n\ndotenv.config({\n    {{#if (eq backend \"self\")}}\n    path: \"../../apps/web/.env\",\n    {{else}}\n    path: \"../../apps/server/.env\",\n    {{/if}}\n})\n\nexport default defineConfig({\n  schema: path.join(\"prisma\", \"schema\"),\n  migrations: {\n    path: path.join(\"prisma\", \"migrations\"),\n    },\n    datasource: {\n        url: env('DATABASE_URL'),\n    },\n})\n`],\n  [\"db/prisma/postgres/prisma/schema/schema.prisma.hbs\", `generator client {\n  provider = \"prisma-client\"\n  output   = \"../generated\"\n  moduleFormat = \"esm\"\n  {{#if (eq runtime \"bun\")}}\n  runtime = \"bun\"\n  {{/if}}\n  {{#if (eq runtime \"node\")}}\n  runtime = \"nodejs\"\n  {{/if}}\n  {{#if (or (eq runtime \"workers\") (and (eq backend \"self\") (eq webDeploy \"cloudflare\")))}}\n  runtime = \"workerd\"\n  {{/if}}\n}\n\ndatasource db {\n  provider = \"postgresql\"\n  {{#if (eq dbSetup \"planetscale\")}}\n  relationMode = \"prisma\"\n  {{/if}}\n}\n`],\n  [\"db/prisma/postgres/src/index.ts.hbs\", `{{#if (eq runtime \"workers\")}}\nimport { PrismaClient } from \"../prisma/generated/client\";\nimport { env } from \"@{{projectName}}/env/server\";\n{{#if (eq dbSetup \"neon\")}}\nimport { PrismaNeon } from \"@prisma/adapter-neon\";\nimport { neonConfig } from \"@neondatabase/serverless\";\n\nneonConfig.poolQueryViaFetch = true;\n\nexport function createPrismaClient() {\n\treturn new PrismaClient({\n\t\tadapter: new PrismaNeon({\n\t\t\tconnectionString: env.DATABASE_URL,\n\t\t}),\n\t});\n}\n\n{{else if (eq dbSetup \"prisma-postgres\")}}\nimport { PrismaPg } from \"@prisma/adapter-pg\";\n\nexport function createPrismaClient() {\n\tconst adapter = new PrismaPg({\n\t\tconnectionString: env.DATABASE_URL,\n\t\tmaxUses: 1,\n\t});\n\n\treturn new PrismaClient({ adapter });\n}\n\n{{else}}\nimport { PrismaPg } from \"@prisma/adapter-pg\";\n\nexport function createPrismaClient() {\n\tconst adapter = new PrismaPg({\n\t\tconnectionString: env.DATABASE_URL,\n\t\tmaxUses: 1,\n\t});\n\treturn new PrismaClient({ adapter });\n}\n\n{{/if}}\n{{else}}\nimport { PrismaClient } from \"../prisma/generated/client\";\n{{#if (and (eq backend \"self\") (eq webDeploy \"cloudflare\") (includes frontend \"svelte\"))}}\nimport type {} from \"@{{projectName}}/env/server\";\n{{else}}\nimport { env } from \"@{{projectName}}/env/server\";\n{{/if}}\n{{#if (eq dbSetup \"neon\")}}\nimport { PrismaNeon } from \"@prisma/adapter-neon\";\n\nexport function createPrismaClient({{#if (and (eq backend \"self\") (eq webDeploy \"cloudflare\") (includes frontend \"svelte\"))}}env: Env{{/if}}) {\n\tconst adapter = new PrismaNeon({\n\t\tconnectionString: env.DATABASE_URL,\n\t});\n\n\treturn new PrismaClient({ adapter });\n}\n\n{{else if (eq dbSetup \"prisma-postgres\")}}\nimport { PrismaPg } from \"@prisma/adapter-pg\";\n\nexport function createPrismaClient({{#if (and (eq backend \"self\") (eq webDeploy \"cloudflare\") (includes frontend \"svelte\"))}}env: Env{{/if}}) {\n\tconst adapter = new PrismaPg({\n\t\tconnectionString: env.DATABASE_URL,\n{{#if (and (eq backend \"self\") (eq webDeploy \"cloudflare\"))}}\n\t\tmaxUses: 1,\n{{/if}}\n\t});\n\n\treturn new PrismaClient({ adapter });\n}\n\n{{else}}\nimport { PrismaPg } from \"@prisma/adapter-pg\";\n\nexport function createPrismaClient({{#if (and (eq backend \"self\") (eq webDeploy \"cloudflare\") (includes frontend \"svelte\"))}}env: Env{{/if}}) {\n\tconst adapter = new PrismaPg({\n\t\tconnectionString: env.DATABASE_URL,\n{{#if (and (eq backend \"self\") (eq webDeploy \"cloudflare\"))}}\n\t\tmaxUses: 1,\n{{/if}}\n\t});\n\treturn new PrismaClient({ adapter });\n}\n\n{{/if}}\n{{#if (and (ne serverDeploy \"cloudflare\") (or (ne backend \"self\") (ne webDeploy \"cloudflare\")))}}\nconst prisma = createPrismaClient();\nexport default prisma;\n{{/if}}\n{{/if}}\n`],\n  [\"db/prisma/sqlite/prisma.config.ts.hbs\", `import path from \"node:path\";\nimport { defineConfig, env } from \"prisma/config\";\nimport dotenv from \"dotenv\";\n\ndotenv.config({\n  {{#if (eq backend \"self\")}}\n  path: \"../../apps/web/.env\",\n  {{else}}\n  path: \"../../apps/server/.env\",\n  {{/if}}\n});\n\nexport default defineConfig({\n  schema: path.join(\"prisma\", \"schema\"),\n  migrations: {\n    path: path.join(\"prisma\", \"migrations\"),\n  },\n  datasource: {\n    {{#if (eq dbSetup \"turso\")}}\n    url: \"file:./dev.db\",\n    {{else}}\n    url: env(\"DATABASE_URL\"),\n    {{/if}}\n  },\n});`],\n  [\"db/prisma/sqlite/prisma/schema/schema.prisma.hbs\", `generator client {\n  provider = \"prisma-client\"\n  output   = \"../generated\"\n  moduleFormat = \"esm\"\n  {{#if (eq runtime \"bun\")}}\n  runtime = \"bun\"\n  {{/if}}\n  {{#if (eq runtime \"node\")}}\n  runtime = \"nodejs\"\n  {{/if}}\n  {{#if (or (eq runtime \"workers\") (and (eq backend \"self\") (eq webDeploy \"cloudflare\")))}}\n  runtime = \"workerd\"\n  {{/if}}\n}\n\ndatasource db {\n  provider = \"sqlite\"\n}\n`],\n  [\"db/prisma/sqlite/src/index.ts.hbs\", `import { PrismaClient } from \"../prisma/generated/client\";\n\n{{#if (eq dbSetup \"d1\")}}\nimport { PrismaD1 } from \"@prisma/adapter-d1\";\n{{#if (and (eq backend \"self\") (eq webDeploy \"cloudflare\") (includes frontend \"svelte\"))}}\nimport type {} from \"@{{projectName}}/env/server\";\n{{else}}\nimport { env } from \"@{{projectName}}/env/server\";\n{{/if}}\n\nexport function createPrismaClient({{#if (and (eq backend \"self\") (eq webDeploy \"cloudflare\") (includes frontend \"svelte\"))}}env: Env{{/if}}) {\n\tconst adapter = new PrismaD1(env.DB);\n\treturn new PrismaClient({ adapter });\n}\n\n{{#if (and (ne runtime \"workers\") (ne serverDeploy \"cloudflare\") (or (ne backend \"self\") (ne webDeploy \"cloudflare\")))}}\nconst prisma = createPrismaClient();\nexport default prisma;\n{{/if}}\n{{else}}\nimport { PrismaLibSql } from \"@prisma/adapter-libsql\";\n{{#if (and (eq backend \"self\") (eq webDeploy \"cloudflare\") (includes frontend \"svelte\"))}}\nimport type {} from \"@{{projectName}}/env/server\";\n{{else}}\nimport { env } from \"@{{projectName}}/env/server\";\n{{/if}}\n\nexport function createPrismaClient({{#if (and (eq backend \"self\") (eq webDeploy \"cloudflare\") (includes frontend \"svelte\"))}}env: Env{{/if}}) {\n\tconst adapter = new PrismaLibSql({\n\t\turl: env.DATABASE_URL,\n{{#if (eq dbSetup \"turso\")}}\n\t\tauthToken: env.DATABASE_AUTH_TOKEN || \"\",\n{{/if}}\n\t});\n\n\treturn new PrismaClient({ adapter });\n}\n\n{{#if (and (ne runtime \"workers\") (ne serverDeploy \"cloudflare\") (or (ne backend \"self\") (ne webDeploy \"cloudflare\")))}}\nconst prisma = createPrismaClient();\nexport default prisma;\n{{/if}}\n{{/if}}\n`],\n  [\"examples/ai/convex/packages/backend/convex/agent.ts.hbs\", `import { Agent } from \"@convex-dev/agent\";\nimport { google } from \"@ai-sdk/google\";\nimport { components } from \"./_generated/api\";\n\nexport const chatAgent = new Agent(components.agent, {\n  name: \"Chat Agent\",\n  languageModel: google(\"gemini-2.5-flash\"),\n  instructions: \"You are a helpful AI assistant. Be concise and friendly in your responses.\",\n});\n`],\n  [\"examples/ai/convex/packages/backend/convex/chat.ts.hbs\", `import {\n  createThread,\n  listUIMessages,\n  saveMessage,\n  syncStreams,\n  vStreamArgs,\n} from \"@convex-dev/agent\";\nimport { paginationOptsValidator } from \"convex/server\";\nimport { v } from \"convex/values\";\n\nimport { components, internal } from \"./_generated/api\";\nimport { internalAction, mutation, query } from \"./_generated/server\";\nimport { chatAgent } from \"./agent\";\n\nexport const createNewThread = mutation({\n  args: {},\n  handler: async (ctx) => {\n    const threadId = await createThread(ctx, components.agent, {});\n    return threadId;\n  },\n});\n\nexport const listMessages = query({\n  args: {\n    threadId: v.string(),\n    paginationOpts: paginationOptsValidator,\n    streamArgs: vStreamArgs,\n  },\n  handler: async (ctx, args) => {\n    const paginated = await listUIMessages(ctx, components.agent, args);\n    const streams = await syncStreams(ctx, components.agent, args);\n    return { ...paginated, streams };\n  },\n});\n\nexport const sendMessage = mutation({\n  args: {\n    threadId: v.string(),\n    prompt: v.string(),\n  },\n  handler: async (ctx, { threadId, prompt }) => {\n    const { messageId } = await saveMessage(ctx, components.agent, {\n      threadId,\n      prompt,\n    });\n    await ctx.scheduler.runAfter(0, internal.chat.generateResponseAsync, {\n      threadId,\n      promptMessageId: messageId,\n    });\n    return messageId;\n  },\n});\n\nexport const generateResponseAsync = internalAction({\n  args: {\n    threadId: v.string(),\n    promptMessageId: v.string(),\n  },\n  handler: async (ctx, { threadId, promptMessageId }) => {\n    await chatAgent.streamText(\n      ctx,\n      { threadId },\n      { promptMessageId },\n      { saveStreamDeltas: true },\n    );\n  },\n});\n`],\n  [\"examples/ai/fullstack/next/src/app/api/ai/route.ts.hbs\", `import { google } from \"@ai-sdk/google\";\nimport { streamText, type UIMessage, convertToModelMessages, wrapLanguageModel } from \"ai\";\nimport { devToolsMiddleware } from \"@ai-sdk/devtools\";\n\nexport const maxDuration = 30;\n\nexport async function POST(req: Request) {\n\tconst { messages }: { messages: UIMessage[] } = await req.json();\n\n\tconst model = wrapLanguageModel({\n\t\tmodel: google(\"gemini-2.5-flash\"),\n\t\tmiddleware: devToolsMiddleware(),\n\t});\n\tconst result = streamText({\n\t\tmodel,\n\t\tmessages: await convertToModelMessages(messages),\n\t});\n\n\treturn result.toUIMessageStreamResponse();\n}\n`],\n  [\"examples/ai/fullstack/nuxt/server/api/ai.post.ts.hbs\", `import { devToolsMiddleware } from \"@ai-sdk/devtools\";\nimport { google } from \"@ai-sdk/google\";\nimport { streamText, convertToModelMessages, wrapLanguageModel } from \"ai\";\n\nexport default defineEventHandler(async (event) => {\n  const body = await readBody(event);\n  const uiMessages = body.messages || [];\n\n  const model = wrapLanguageModel({\n    model: google(\"gemini-2.5-flash\"),\n    middleware: devToolsMiddleware(),\n  });\n\n  const result = streamText({\n    model,\n    messages: await convertToModelMessages(uiMessages),\n  });\n\n  return result.toUIMessageStreamResponse();\n});\n`],\n  [\"examples/ai/fullstack/svelte/src/routes/api/ai/+server.ts.hbs\", `import { devToolsMiddleware } from \"@ai-sdk/devtools\";\nimport { google } from \"@ai-sdk/google\";\nimport { convertToModelMessages, streamText, type UIMessage, wrapLanguageModel } from \"ai\";\nimport type { RequestHandler } from \"@sveltejs/kit\";\n\nexport const POST: RequestHandler = async ({ request }) => {\n\tconst { messages }: { messages: UIMessage[] } = await request.json();\n\n\tconst model = wrapLanguageModel({\n\t\tmodel: google(\"gemini-2.5-flash\"),\n\t\tmiddleware: devToolsMiddleware(),\n\t});\n\tconst result = streamText({\n\t\tmodel,\n\t\tmessages: await convertToModelMessages(messages),\n\t});\n\n\treturn result.toUIMessageStreamResponse();\n};\n`],\n  [\"examples/ai/fullstack/tanstack-start/src/routes/api/ai/$.ts.hbs\", `import { createFileRoute } from \"@tanstack/react-router\";\nimport { google } from \"@ai-sdk/google\";\nimport { streamText, type UIMessage, convertToModelMessages, wrapLanguageModel } from \"ai\";\nimport { devToolsMiddleware } from \"@ai-sdk/devtools\";\n\nexport const Route = createFileRoute(\"/api/ai/$\")({\n  server: {\n    handlers: {\n      POST: async ({ request }) => {\n        try {\n          const { messages }: { messages: UIMessage[] } = await request.json();\n\n          const model = wrapLanguageModel({\n            model: google(\"gemini-2.5-flash\"),\n            middleware: devToolsMiddleware(),\n          });\n          const result = streamText({\n            model,\n            messages: await convertToModelMessages(messages),\n          });\n\n          return result.toUIMessageStreamResponse();\n        } catch (error) {\n          console.error(\"AI API error:\", error);\n          return new Response(\n            JSON.stringify({ error: \"Failed to process AI request\" }),\n            {\n              status: 500,\n              headers: { \"Content-Type\": \"application/json\" },\n            },\n          );\n        }\n      },\n    },\n  },\n});\n`],\n  [\"examples/ai/native/bare/app/(drawer)/ai.tsx.hbs\", `{{#if (eq backend \"convex\")}}\nimport { Ionicons } from \"@expo/vector-icons\";\nimport {\n  useUIMessages,\n  useSmoothText,\n  type UIMessage,\n} from \"@convex-dev/agent/react\";\nimport { api } from \"@{{projectName}}/backend/convex/_generated/api\";\nimport { useMutation } from \"convex/react\";\nimport { useRef, useEffect, useState } from \"react\";\nimport {\n  View,\n  Text,\n  TextInput,\n  TouchableOpacity,\n  ScrollView,\n  KeyboardAvoidingView,\n  Platform,\n  StyleSheet,\n  ActivityIndicator,\n} from \"react-native\";\n\nimport { Container } from \"@/components/container\";\nimport { useColorScheme } from \"@/lib/use-color-scheme\";\nimport { NAV_THEME } from \"@/lib/constants\";\n\nfunction MessageContent({\n  text,\n  isStreaming,\n  textColor,\n}: {\n  text: string;\n  isStreaming: boolean;\n  textColor: string;\n}) {\n  const [visibleText] = useSmoothText(text, {\n    startStreaming: isStreaming,\n  });\n  return <Text style={[styles.messageText, { color: textColor }]}>{visibleText}</Text>;\n}\n\nexport default function AIScreen() {\n  const { colorScheme } = useColorScheme();\n  const theme = colorScheme === \"dark\" ? NAV_THEME.dark : NAV_THEME.light;\n  const [input, setInput] = useState(\"\");\n  const [threadId, setThreadId] = useState<string | null>(null);\n  const [isLoading, setIsLoading] = useState(false);\n  const scrollViewRef = useRef<ScrollView>(null);\n\n  const createThread = useMutation(api.chat.createNewThread);\n  const sendMessage = useMutation(api.chat.sendMessage);\n\n  const { results: messages } = useUIMessages(\n    api.chat.listMessages,\n    threadId ? { threadId } : \"skip\",\n    { initialNumItems: 50, stream: true },\n  );\n\n  const hasStreamingMessage = messages?.some(\n    (m: UIMessage) => m.status === \"streaming\",\n  );\n\n  useEffect(() => {\n    scrollViewRef.current?.scrollToEnd({ animated: true });\n  }, [messages]);\n\n  async function onSubmit() {\n    const value = input.trim();\n    if (!value || isLoading) return;\n\n    setIsLoading(true);\n    setInput(\"\");\n\n    try {\n      let currentThreadId = threadId;\n      if (!currentThreadId) {\n        currentThreadId = await createThread();\n        setThreadId(currentThreadId);\n      }\n\n      await sendMessage({ threadId: currentThreadId, prompt: value });\n    } catch (error) {\n      console.error(\"Failed to send message:\", error);\n    } finally {\n      setIsLoading(false);\n    }\n  }\n\n  return (\n    <Container>\n      <KeyboardAvoidingView\n        style={styles.container}\n        behavior={Platform.OS === \"ios\" ? \"padding\" : \"height\"}\n      >\n        <View style={styles.content}>\n          <View style={styles.header}>\n            <Text style={[styles.headerTitle, { color: theme.text }]}>\n              AI Chat\n            </Text>\n            <Text style={[styles.headerSubtitle, { color: theme.text, opacity: 0.7 }]}>\n              Chat with our AI assistant\n            </Text>\n          </View>\n          <ScrollView\n            ref={scrollViewRef}\n            style={styles.scrollView}\n            showsVerticalScrollIndicator={false}\n          >\n            {!messages || messages.length === 0 ? (\n              <View style={styles.emptyContainer}>\n                <Text style={[styles.emptyText, { color: theme.text, opacity: 0.7 }]}>\n                  Ask me anything to get started!\n                </Text>\n              </View>\n            ) : (\n              <View style={styles.messagesList}>\n                {messages.map((message: UIMessage) => (\n                  <View\n                    key={message.key}\n                    style={[\n                      styles.messageCard,\n                      {\n                        backgroundColor: message.role === \"user\"\n                          ? theme.primary + \"20\"\n                          : theme.card,\n                        borderColor: theme.border,\n                        alignSelf: message.role === \"user\" ? \"flex-end\" : \"flex-start\",\n                        marginLeft: message.role === \"user\" ? 32 : 0,\n                        marginRight: message.role === \"user\" ? 0 : 32,\n                      },\n                    ]}\n                  >\n                    <Text style={[styles.messageRole, { color: theme.text }]}>\n                      {message.role === \"user\" ? \"You\" : \"AI Assistant\"}\n                    </Text>\n                    <MessageContent\n                      text={message.text ?? \"\"}\n                      isStreaming={message.status === \"streaming\"}\n                      textColor={theme.text}\n                    />\n                  </View>\n                ))}\n                {isLoading && !hasStreamingMessage && (\n                  <View\n                    style={[\n                      styles.messageCard,\n                      {\n                        backgroundColor: theme.card,\n                        borderColor: theme.border,\n                        alignSelf: \"flex-start\",\n                        marginRight: 32,\n                      },\n                    ]}\n                  >\n                    <Text style={[styles.messageRole, { color: theme.text }]}>\n                      AI Assistant\n                    </Text>\n                    <View style={styles.loadingContainer}>\n                      <ActivityIndicator size=\"small\" color={theme.primary} />\n                      <Text style={[styles.loadingText, { color: theme.text, opacity: 0.7 }]}>\n                        Thinking...\n                      </Text>\n                    </View>\n                  </View>\n                )}\n              </View>\n            )}\n          </ScrollView>\n          <View style={[styles.inputContainer, { borderTopColor: theme.border }]}>\n            <View style={styles.inputRow}>\n              <TextInput\n                value={input}\n                onChangeText={setInput}\n                placeholder=\"Type your message...\"\n                placeholderTextColor={theme.text}\n                style={[\n                  styles.input,\n                  {\n                    color: theme.text,\n                    borderColor: theme.border,\n                    backgroundColor: theme.background,\n                  },\n                ]}\n                onSubmitEditing={(e) => {\n                  e.preventDefault();\n                  onSubmit();\n                }}\n                editable={!isLoading}\n                autoFocus={true}\n                multiline\n              />\n              <TouchableOpacity\n                onPress={onSubmit}\n                disabled={!input.trim() || isLoading}\n                style={[\n                  styles.sendButton,\n                  {\n                    backgroundColor: input.trim() && !isLoading ? theme.primary : theme.border,\n                    opacity: input.trim() && !isLoading ? 1 : 0.5,\n                  },\n                ]}\n              >\n                <Ionicons\n                  name=\"send\"\n                  size={20}\n                  color=\"#ffffff\"\n                />\n              </TouchableOpacity>\n            </View>\n          </View>\n        </View>\n      </KeyboardAvoidingView>\n    </Container>\n  );\n}\n\nconst styles = StyleSheet.create({\n  container: {\n    flex: 1,\n  },\n  content: {\n    flex: 1,\n    padding: 16,\n  },\n  header: {\n    marginBottom: 16,\n  },\n  headerTitle: {\n    fontSize: 20,\n    fontWeight: \"bold\",\n    marginBottom: 4,\n  },\n  headerSubtitle: {\n    fontSize: 14,\n  },\n  scrollView: {\n    flex: 1,\n    marginBottom: 16,\n  },\n  emptyContainer: {\n    flex: 1,\n    justifyContent: \"center\",\n    alignItems: \"center\",\n  },\n  emptyText: {\n    fontSize: 16,\n    textAlign: \"center\",\n  },\n  messagesList: {\n    gap: 8,\n    paddingBottom: 16,\n  },\n  messageCard: {\n    borderWidth: 1,\n    padding: 12,\n    maxWidth: \"80%\",\n  },\n  messageRole: {\n    fontSize: 12,\n    fontWeight: \"bold\",\n    marginBottom: 4,\n  },\n  messageText: {\n    fontSize: 14,\n    lineHeight: 20,\n  },\n  loadingContainer: {\n    flexDirection: \"row\",\n    alignItems: \"center\",\n    gap: 8,\n  },\n  loadingText: {\n    fontSize: 14,\n  },\n  inputContainer: {\n    borderTopWidth: 1,\n    paddingTop: 12,\n  },\n  inputRow: {\n    flexDirection: \"row\",\n    alignItems: \"flex-end\",\n    gap: 8,\n  },\n  input: {\n    flex: 1,\n    borderWidth: 1,\n    padding: 8,\n    fontSize: 14,\n    minHeight: 36,\n    maxHeight: 100,\n  },\n  sendButton: {\n    padding: 8,\n    justifyContent: \"center\",\n    alignItems: \"center\",\n  },\n});\n{{else}}\nimport { useRef, useEffect, useState } from \"react\";\nimport {\n  View,\n  Text,\n  TextInput,\n  TouchableOpacity,\n  ScrollView,\n  KeyboardAvoidingView,\n  Platform,\n  StyleSheet,\n} from \"react-native\";\nimport { useChat } from \"@ai-sdk/react\";\nimport { DefaultChatTransport } from \"ai\";\nimport { fetch as expoFetch } from \"expo/fetch\";\nimport { Ionicons } from \"@expo/vector-icons\";\nimport { Container } from \"@/components/container\";\nimport { useColorScheme } from \"@/lib/use-color-scheme\";\nimport { NAV_THEME } from \"@/lib/constants\";\nimport { env } from \"@{{projectName}}/env/native\";\n\nconst generateAPIUrl = (relativePath: string) => {\n  const serverUrl = env.EXPO_PUBLIC_SERVER_URL;\n  if (!serverUrl) {\n    throw new Error(\n      \"EXPO_PUBLIC_SERVER_URL environment variable is not defined\"\n    );\n  }\n  const path = relativePath.startsWith(\"/\") ? relativePath : \\`/\\${relativePath}\\`;\n  return serverUrl.concat(path);\n};\n\nexport default function AIScreen() {\n  const { colorScheme } = useColorScheme();\n  const theme = colorScheme === \"dark\" ? NAV_THEME.dark : NAV_THEME.light;\n  const [input, setInput] = useState(\"\");\n  const { messages, error, sendMessage } = useChat({\n    transport: new DefaultChatTransport({\n      fetch: expoFetch as unknown as typeof globalThis.fetch,\n      api: generateAPIUrl(\"/ai\"),\n    }),\n    onError: (error) => console.error(error, \"AI Chat Error\"),\n  });\n  const scrollViewRef = useRef<ScrollView>(null);\n\n  useEffect(() => {\n    scrollViewRef.current?.scrollToEnd({ animated: true });\n  }, [messages]);\n\n  function onSubmit() {\n    const value = input.trim();\n    if (value) {\n      sendMessage({ text: value });\n      setInput(\"\");\n    }\n  }\n\n  if (error) {\n    return (\n      <Container>\n        <View style={styles.errorContainer}>\n          <View style={[styles.errorCard, { backgroundColor: theme.notification + \"20\", borderColor: theme.notification }]}>\n            <Text style={[styles.errorTitle, { color: theme.notification }]}>\n              Error: {error.message}\n            </Text>\n            <Text style={[styles.errorText, { color: theme.text, opacity: 0.7 }]}>\n              Please check your connection and try again.\n            </Text>\n          </View>\n        </View>\n      </Container>\n    );\n  }\n\n  return (\n    <Container>\n      <KeyboardAvoidingView\n        style={styles.container}\n        behavior={Platform.OS === \"ios\" ? \"padding\" : \"height\"}\n      >\n        <View style={styles.content}>\n          <View style={styles.header}>\n            <Text style={[styles.headerTitle, { color: theme.text }]}>\n              AI Chat\n            </Text>\n            <Text style={[styles.headerSubtitle, { color: theme.text, opacity: 0.7 }]}>\n              Chat with our AI assistant\n            </Text>\n          </View>\n          <ScrollView\n            ref={scrollViewRef}\n            style={styles.scrollView}\n            showsVerticalScrollIndicator={false}\n          >\n            {messages.length === 0 ? (\n              <View style={styles.emptyContainer}>\n                <Text style={[styles.emptyText, { color: theme.text, opacity: 0.7 }]}>\n                  Ask me anything to get started!\n                </Text>\n              </View>\n            ) : (\n              <View style={styles.messagesList}>\n                {messages.map((message) => (\n                  <View\n                    key={message.id}\n                    style={[\n                      styles.messageCard,\n                      {\n                        backgroundColor: message.role === \"user\"\n                          ? theme.primary + \"20\"\n                          : theme.card,\n                        borderColor: theme.border,\n                        alignSelf: message.role === \"user\" ? \"flex-end\" : \"flex-start\",\n                        marginLeft: message.role === \"user\" ? 32 : 0,\n                        marginRight: message.role === \"user\" ? 0 : 32,\n                      },\n                    ]}\n                  >\n                    <Text style={[styles.messageRole, { color: theme.text }]}>\n                      {message.role === \"user\" ? \"You\" : \"AI Assistant\"}\n                    </Text>\n                    <View style={styles.messageParts}>\n                      {message.parts.map((part, i) =>\n                        part.type === \"text\" ? (\n                          <Text\n                            key={\\`\\${message.id}-\\${i}\\`}\n                            style={[styles.messageText, { color: theme.text }]}\n                          >\n                            {part.text}\n                          </Text>\n                        ) : (\n                          <Text\n                            key={\\`\\${message.id}-\\${i}\\`}\n                            style={[styles.messageText, { color: theme.text }]}\n                          >\n                            {JSON.stringify(part)}\n                          </Text>\n                        )\n                      )}\n                    </View>\n                  </View>\n                ))}\n              </View>\n            )}\n          </ScrollView>\n          <View style={[styles.inputContainer, { borderTopColor: theme.border }]}>\n            <View style={styles.inputRow}>\n              <TextInput\n                value={input}\n                onChangeText={setInput}\n                placeholder=\"Type your message...\"\n                placeholderTextColor={theme.text}\n                style={[\n                  styles.input,\n                  {\n                    color: theme.text,\n                    borderColor: theme.border,\n                    backgroundColor: theme.background,\n                  },\n                ]}\n                onSubmitEditing={(e) => {\n                  e.preventDefault();\n                  onSubmit();\n                }}\n                autoFocus={true}\n                multiline\n              />\n              <TouchableOpacity\n                onPress={onSubmit}\n                disabled={!input.trim()}\n                style={[\n                  styles.sendButton,\n                  {\n                    backgroundColor: input.trim() ? theme.primary : theme.border,\n                    opacity: input.trim() ? 1 : 0.5,\n                  },\n                ]}\n              >\n                <Ionicons\n                  name=\"send\"\n                  size={20}\n                  color=\"#ffffff\"\n                />\n              </TouchableOpacity>\n            </View>\n          </View>\n        </View>\n      </KeyboardAvoidingView>\n    </Container>\n  );\n}\n\nconst styles = StyleSheet.create({\n  container: {\n    flex: 1,\n  },\n  content: {\n    flex: 1,\n    padding: 16,\n  },\n  header: {\n    marginBottom: 16,\n  },\n  headerTitle: {\n    fontSize: 20,\n    fontWeight: \"bold\",\n    marginBottom: 4,\n  },\n  headerSubtitle: {\n    fontSize: 14,\n  },\n  scrollView: {\n    flex: 1,\n    marginBottom: 16,\n  },\n  emptyContainer: {\n    flex: 1,\n    justifyContent: \"center\",\n    alignItems: \"center\",\n  },\n  emptyText: {\n    fontSize: 16,\n    textAlign: \"center\",\n  },\n  messagesList: {\n    gap: 8,\n    paddingBottom: 16,\n  },\n  messageCard: {\n    borderWidth: 1,\n    padding: 12,\n    maxWidth: \"80%\",\n  },\n  messageRole: {\n    fontSize: 12,\n    fontWeight: \"bold\",\n    marginBottom: 4,\n  },\n  messageParts: {\n    gap: 4,\n  },\n  messageText: {\n    fontSize: 14,\n    lineHeight: 20,\n  },\n  inputContainer: {\n    borderTopWidth: 1,\n    paddingTop: 12,\n  },\n  inputRow: {\n    flexDirection: \"row\",\n    alignItems: \"flex-end\",\n    gap: 8,\n  },\n  input: {\n    flex: 1,\n    borderWidth: 1,\n    padding: 8,\n    fontSize: 14,\n    minHeight: 36,\n    maxHeight: 100,\n  },\n  sendButton: {\n    padding: 8,\n    justifyContent: \"center\",\n    alignItems: \"center\",\n  },\n  errorContainer: {\n    flex: 1,\n    justifyContent: \"center\",\n    alignItems: \"center\",\n    padding: 16,\n  },\n  errorCard: {\n    borderWidth: 1,\n    padding: 16,\n  },\n  errorTitle: {\n    fontSize: 16,\n    fontWeight: \"bold\",\n    marginBottom: 8,\n    textAlign: \"center\",\n  },\n  errorText: {\n    fontSize: 14,\n    textAlign: \"center\",\n  },\n});\n{{/if}}\n`],\n  [\"examples/ai/native/bare/polyfills.js\", `import structuredClone from \"@ungap/structured-clone\";\nimport { Platform } from \"react-native\";\n\nif (Platform.OS !== \"web\") {\n  const setupPolyfills = async () => {\n    const { polyfillGlobal } = await import(\"react-native/Libraries/Utilities/PolyfillFunctions\");\n\n    const { TextEncoderStream, TextDecoderStream } =\n      await import(\"@stardazed/streams-text-encoding\");\n\n    if (!(\"structuredClone\" in global)) {\n      polyfillGlobal(\"structuredClone\", () => structuredClone);\n    }\n\n    polyfillGlobal(\"TextEncoderStream\", () => TextEncoderStream);\n    polyfillGlobal(\"TextDecoderStream\", () => TextDecoderStream);\n  };\n\n  setupPolyfills();\n}\n\nexport {};\n`],\n  [\"examples/ai/native/unistyles/app/(drawer)/ai.tsx.hbs\", `{{#if (eq backend \"convex\")}}\nimport { Ionicons } from \"@expo/vector-icons\";\nimport {\n  useUIMessages,\n  useSmoothText,\n  type UIMessage,\n} from \"@convex-dev/agent/react\";\nimport { api } from \"@{{projectName}}/backend/convex/_generated/api\";\nimport { useMutation } from \"convex/react\";\nimport React, { useRef, useEffect, useState } from \"react\";\nimport {\n  View,\n  Text,\n  TextInput,\n  TouchableOpacity,\n  ScrollView,\n  KeyboardAvoidingView,\n  Platform,\n  ActivityIndicator,\n} from \"react-native\";\nimport { StyleSheet, useUnistyles } from \"react-native-unistyles\";\n\nimport { Container } from \"@/components/container\";\n\nfunction MessageContent({\n  text,\n  isStreaming,\n  style,\n}: {\n  text: string;\n  isStreaming: boolean;\n  style: object;\n}) {\n  const [visibleText] = useSmoothText(text, {\n    startStreaming: isStreaming,\n  });\n  return <Text style={style}>{visibleText}</Text>;\n}\n\nexport default function AIScreen() {\n  const { theme } = useUnistyles();\n  const [input, setInput] = useState(\"\");\n  const [threadId, setThreadId] = useState<string | null>(null);\n  const [isLoading, setIsLoading] = useState(false);\n  const scrollViewRef = useRef<ScrollView>(null);\n\n  const createThread = useMutation(api.chat.createNewThread);\n  const sendMessage = useMutation(api.chat.sendMessage);\n\n  const { results: messages } = useUIMessages(\n    api.chat.listMessages,\n    threadId ? { threadId } : \"skip\",\n    { initialNumItems: 50, stream: true },\n  );\n\n  const hasStreamingMessage = messages?.some(\n    (m: UIMessage) => m.status === \"streaming\",\n  );\n\n  useEffect(() => {\n    scrollViewRef.current?.scrollToEnd({ animated: true });\n  }, [messages]);\n\n  const onSubmit = async () => {\n    const value = input.trim();\n    if (!value || isLoading) return;\n\n    setIsLoading(true);\n    setInput(\"\");\n\n    try {\n      let currentThreadId = threadId;\n      if (!currentThreadId) {\n        currentThreadId = await createThread();\n        setThreadId(currentThreadId);\n      }\n\n      await sendMessage({ threadId: currentThreadId, prompt: value });\n    } catch (error) {\n      console.error(\"Failed to send message:\", error);\n    } finally {\n      setIsLoading(false);\n    }\n  };\n\n  return (\n    <Container>\n      <KeyboardAvoidingView\n        style={styles.container}\n        behavior={Platform.OS === \"ios\" ? \"padding\" : \"height\"}\n      >\n        <View style={styles.content}>\n          <View style={styles.header}>\n            <Text style={styles.headerTitle}>AI Chat</Text>\n            <Text style={styles.headerSubtitle}>\n              Chat with our AI assistant\n            </Text>\n          </View>\n\n          <ScrollView\n            ref={scrollViewRef}\n            style={styles.messagesContainer}\n            showsVerticalScrollIndicator={false}\n          >\n            {!messages || messages.length === 0 ? (\n              <View style={styles.emptyContainer}>\n                <Text style={styles.emptyText}>\n                  Ask me anything to get started!\n                </Text>\n              </View>\n            ) : (\n              <View style={styles.messagesWrapper}>\n                {messages.map((message: UIMessage) => (\n                  <View\n                    key={message.key}\n                    style={[\n                      styles.messageContainer,\n                      message.role === \"user\"\n                        ? styles.userMessage\n                        : styles.assistantMessage,\n                    ]}\n                  >\n                    <Text style={styles.messageRole}>\n                      {message.role === \"user\" ? \"You\" : \"AI Assistant\"}\n                    </Text>\n                    <MessageContent\n                      text={message.text ?? \"\"}\n                      isStreaming={message.status === \"streaming\"}\n                      style={styles.messageContent}\n                    />\n                  </View>\n                ))}\n                {isLoading && !hasStreamingMessage && (\n                  <View style={[styles.messageContainer, styles.assistantMessage]}>\n                    <Text style={styles.messageRole}>AI Assistant</Text>\n                    <View style={styles.loadingContainer}>\n                      <ActivityIndicator size=\"small\" color={theme.colors.primary} />\n                      <Text style={styles.loadingText}>Thinking...</Text>\n                    </View>\n                  </View>\n                )}\n              </View>\n            )}\n          </ScrollView>\n\n          <View style={styles.inputSection}>\n            <View style={styles.inputContainer}>\n              <TextInput\n                value={input}\n                onChangeText={setInput}\n                placeholder=\"Type your message...\"\n                placeholderTextColor={theme.colors.border}\n                style={styles.textInput}\n                onSubmitEditing={(e) => {\n                  e.preventDefault();\n                  onSubmit();\n                }}\n                editable={!isLoading}\n                autoFocus={true}\n              />\n              <TouchableOpacity\n                onPress={onSubmit}\n                disabled={!input.trim() || isLoading}\n                style={[\n                  styles.sendButton,\n                  (!input.trim() || isLoading) && styles.sendButtonDisabled,\n                ]}\n              >\n                <Ionicons\n                  name=\"send\"\n                  size={20}\n                  color={\n                    input.trim() && !isLoading\n                      ? theme.colors.background\n                      : theme.colors.border\n                  }\n                />\n              </TouchableOpacity>\n            </View>\n          </View>\n        </View>\n      </KeyboardAvoidingView>\n    </Container>\n  );\n}\n\nconst styles = StyleSheet.create((theme) => ({\n  container: {\n    flex: 1,\n  },\n  content: {\n    flex: 1,\n    paddingHorizontal: theme.spacing.md,\n    paddingVertical: theme.spacing.lg,\n  },\n  header: {\n    marginBottom: theme.spacing.lg,\n  },\n  headerTitle: {\n    fontSize: 28,\n    fontWeight: \"bold\",\n    color: theme.colors.typography,\n    marginBottom: theme.spacing.sm,\n  },\n  headerSubtitle: {\n    fontSize: 16,\n    color: theme.colors.typography,\n  },\n  messagesContainer: {\n    flex: 1,\n    marginBottom: theme.spacing.md,\n  },\n  emptyContainer: {\n    flex: 1,\n    justifyContent: \"center\",\n    alignItems: \"center\",\n  },\n  emptyText: {\n    textAlign: \"center\",\n    color: theme.colors.typography,\n    fontSize: 18,\n  },\n  messagesWrapper: {\n    gap: theme.spacing.md,\n  },\n  messageContainer: {\n    padding: theme.spacing.md,\n    borderRadius: 8,\n  },\n  userMessage: {\n    backgroundColor: theme.colors.primary + \"20\",\n    marginLeft: theme.spacing.xl,\n    alignSelf: \"flex-end\",\n  },\n  assistantMessage: {\n    backgroundColor: theme.colors.background,\n    marginRight: theme.spacing.xl,\n    borderWidth: 1,\n    borderColor: theme.colors.border,\n  },\n  messageRole: {\n    fontSize: 14,\n    fontWeight: \"600\",\n    marginBottom: theme.spacing.sm,\n    color: theme.colors.typography,\n  },\n  messageContent: {\n    color: theme.colors.typography,\n    lineHeight: 20,\n  },\n  loadingContainer: {\n    flexDirection: \"row\",\n    alignItems: \"center\",\n    gap: theme.spacing.sm,\n  },\n  loadingText: {\n    color: theme.colors.typography,\n    opacity: 0.7,\n  },\n  inputSection: {\n    borderTopWidth: 1,\n    borderTopColor: theme.colors.border,\n    paddingTop: theme.spacing.md,\n  },\n  inputContainer: {\n    flexDirection: \"row\",\n    alignItems: \"flex-end\",\n    gap: theme.spacing.sm,\n  },\n  textInput: {\n    flex: 1,\n    borderWidth: 1,\n    borderColor: theme.colors.border,\n    borderRadius: 8,\n    paddingHorizontal: theme.spacing.md,\n    paddingVertical: theme.spacing.sm,\n    color: theme.colors.typography,\n    backgroundColor: theme.colors.background,\n    fontSize: 16,\n    minHeight: 40,\n    maxHeight: 120,\n  },\n  sendButton: {\n    backgroundColor: theme.colors.primary,\n    padding: theme.spacing.sm,\n    borderRadius: 8,\n    justifyContent: \"center\",\n    alignItems: \"center\",\n  },\n  sendButtonDisabled: {\n    backgroundColor: theme.colors.border,\n  },\n}));\n{{else}}\nimport React, { useRef, useEffect, useState } from \"react\";\nimport {\n  View,\n  Text,\n  TextInput,\n  TouchableOpacity,\n  ScrollView,\n  KeyboardAvoidingView,\n  Platform,\n} from \"react-native\";\nimport { useChat } from \"@ai-sdk/react\";\nimport { DefaultChatTransport } from \"ai\";\nimport { fetch as expoFetch } from \"expo/fetch\";\nimport { Ionicons } from \"@expo/vector-icons\";\nimport { StyleSheet, useUnistyles } from \"react-native-unistyles\";\nimport { Container } from \"@/components/container\";\nimport { env } from \"@{{projectName}}/env/native\";\n\nconst generateAPIUrl = (relativePath: string) => {\n  const serverUrl = env.EXPO_PUBLIC_SERVER_URL;\n  if (!serverUrl) {\n    throw new Error(\n      \"EXPO_PUBLIC_SERVER_URL environment variable is not defined\"\n    );\n  }\n  const path = relativePath.startsWith(\"/\") ? relativePath : \\`/\\${relativePath}\\`;\n  return serverUrl.concat(path);\n};\n\nexport default function AIScreen() {\n  const { theme } = useUnistyles();\n  const [input, setInput] = useState(\"\");\n  const { messages, error, sendMessage } = useChat({\n    transport: new DefaultChatTransport({\n      fetch: expoFetch as unknown as typeof globalThis.fetch,\n      api: generateAPIUrl(\"/ai\"),\n    }),\n    onError: (error) => console.error(error, \"AI Chat Error\"),\n  });\n\n  const scrollViewRef = useRef<ScrollView>(null);\n\n  useEffect(() => {\n    scrollViewRef.current?.scrollToEnd({ animated: true });\n  }, [messages]);\n\n  const onSubmit = () => {\n    const value = input.trim();\n    if (value) {\n      sendMessage({ text: value });\n      setInput(\"\");\n    }\n  };\n\n  if (error) {\n    return (\n      <Container>\n        <View style={styles.errorContainer}>\n          <Text style={styles.errorText}>Error: {error.message}</Text>\n          <Text style={styles.errorSubtext}>\n            Please check your connection and try again.\n          </Text>\n        </View>\n      </Container>\n    );\n  }\n\n  return (\n    <Container>\n      <KeyboardAvoidingView\n        style={styles.container}\n        behavior={Platform.OS === \"ios\" ? \"padding\" : \"height\"}\n      >\n        <View style={styles.content}>\n          <View style={styles.header}>\n            <Text style={styles.headerTitle}>AI Chat</Text>\n            <Text style={styles.headerSubtitle}>\n              Chat with our AI assistant\n            </Text>\n          </View>\n\n          <ScrollView\n            ref={scrollViewRef}\n            style={styles.messagesContainer}\n            showsVerticalScrollIndicator={false}\n          >\n            {messages.length === 0 ? (\n              <View style={styles.emptyContainer}>\n                <Text style={styles.emptyText}>\n                  Ask me anything to get started!\n                </Text>\n              </View>\n            ) : (\n              <View style={styles.messagesWrapper}>\n                {messages.map((message) => (\n                  <View\n                    key={message.id}\n                    style={[\n                      styles.messageContainer,\n                      message.role === \"user\"\n                        ? styles.userMessage\n                        : styles.assistantMessage,\n                    ]}\n                  >\n                    <Text style={styles.messageRole}>\n                      {message.role === \"user\" ? \"You\" : \"AI Assistant\"}\n                    </Text>\n                    <View style={styles.messageContentWrapper}>\n                      {message.parts.map((part, i) => {\n                        if (part.type === \"text\") {\n                          return (\n                            <Text\n                              key={\\`\\${message.id}-\\${i}\\`}\n                              style={styles.messageContent}\n                            >\n                              {part.text}\n                            </Text>\n                          );\n                        }\n                        return (\n                          <Text\n                            key={\\`\\${message.id}-\\${i}\\`}\n                            style={styles.messageContent}\n                          >\n                            {JSON.stringify(part)}\n                          </Text>\n                        );\n                      })}\n                    </View>\n                  </View>\n                ))}\n              </View>\n            )}\n          </ScrollView>\n\n          <View style={styles.inputSection}>\n            <View style={styles.inputContainer}>\n              <TextInput\n                value={input}\n                onChangeText={setInput}\n                placeholder=\"Type your message...\"\n                placeholderTextColor={theme.colors.border}\n                style={styles.textInput}\n                onSubmitEditing={(e) => {\n                  e.preventDefault();\n                  onSubmit();\n                }}\n                autoFocus={true}\n              />\n              <TouchableOpacity\n                onPress={onSubmit}\n                disabled={!input.trim()}\n                style={[\n                  styles.sendButton,\n                  !input.trim() && styles.sendButtonDisabled,\n                ]}\n              >\n                <Ionicons\n                  name=\"send\"\n                  size={20}\n                  color={\n                    input.trim()\n                      ? theme.colors.background\n                      : theme.colors.border\n                  }\n                />\n              </TouchableOpacity>\n            </View>\n          </View>\n        </View>\n      </KeyboardAvoidingView>\n    </Container>\n  );\n}\n\nconst styles = StyleSheet.create((theme) => ({\n  container: {\n    flex: 1,\n  },\n  content: {\n    flex: 1,\n    paddingHorizontal: theme.spacing.md,\n    paddingVertical: theme.spacing.lg,\n  },\n  errorContainer: {\n    flex: 1,\n    justifyContent: \"center\",\n    alignItems: \"center\",\n    paddingHorizontal: theme.spacing.md,\n  },\n  errorText: {\n    color: theme.colors.destructive,\n    textAlign: \"center\",\n    fontSize: 18,\n    marginBottom: theme.spacing.md,\n  },\n  errorSubtext: {\n    color: theme.colors.typography,\n    textAlign: \"center\",\n    fontSize: 16,\n  },\n  header: {\n    marginBottom: theme.spacing.lg,\n  },\n  headerTitle: {\n    fontSize: 28,\n    fontWeight: \"bold\",\n    color: theme.colors.typography,\n    marginBottom: theme.spacing.sm,\n  },\n  headerSubtitle: {\n    fontSize: 16,\n    color: theme.colors.typography,\n  },\n  messagesContainer: {\n    flex: 1,\n    marginBottom: theme.spacing.md,\n  },\n  emptyContainer: {\n    flex: 1,\n    justifyContent: \"center\",\n    alignItems: \"center\",\n  },\n  emptyText: {\n    textAlign: \"center\",\n    color: theme.colors.typography,\n    fontSize: 18,\n  },\n  messagesWrapper: {\n    gap: theme.spacing.md,\n  },\n  messageContainer: {\n    padding: theme.spacing.md,\n    borderRadius: 8,\n  },\n  userMessage: {\n    backgroundColor: theme.colors.primary + \"20\",\n    marginLeft: theme.spacing.xl,\n    alignSelf: \"flex-end\",\n  },\n  assistantMessage: {\n    backgroundColor: theme.colors.background,\n    marginRight: theme.spacing.xl,\n    borderWidth: 1,\n    borderColor: theme.colors.border,\n  },\n  messageRole: {\n    fontSize: 14,\n    fontWeight: \"600\",\n    marginBottom: theme.spacing.sm,\n    color: theme.colors.typography,\n  },\n  messageContentWrapper: {\n    gap: theme.spacing.xs,\n  },\n  messageContent: {\n    color: theme.colors.typography,\n    lineHeight: 20,\n  },\n  inputSection: {\n    borderTopWidth: 1,\n    borderTopColor: theme.colors.border,\n    paddingTop: theme.spacing.md,\n  },\n  inputContainer: {\n    flexDirection: \"row\",\n    alignItems: \"flex-end\",\n    gap: theme.spacing.sm,\n  },\n  textInput: {\n    flex: 1,\n    borderWidth: 1,\n    borderColor: theme.colors.border,\n    borderRadius: 8,\n    paddingHorizontal: theme.spacing.md,\n    paddingVertical: theme.spacing.sm,\n    color: theme.colors.typography,\n    backgroundColor: theme.colors.background,\n    fontSize: 16,\n    minHeight: 40,\n    maxHeight: 120,\n  },\n  sendButton: {\n    backgroundColor: theme.colors.primary,\n    padding: theme.spacing.sm,\n    borderRadius: 8,\n    justifyContent: \"center\",\n    alignItems: \"center\",\n  },\n  sendButtonDisabled: {\n    backgroundColor: theme.colors.border,\n  },\n}));\n{{/if}}\n`],\n  [\"examples/ai/native/unistyles/polyfills.js\", `import structuredClone from \"@ungap/structured-clone\";\nimport { Platform } from \"react-native\";\n\nif (Platform.OS !== \"web\") {\n  const setupPolyfills = async () => {\n    const { polyfillGlobal } = await import(\"react-native/Libraries/Utilities/PolyfillFunctions\");\n\n    const { TextEncoderStream, TextDecoderStream } =\n      await import(\"@stardazed/streams-text-encoding\");\n\n    if (!(\"structuredClone\" in global)) {\n      polyfillGlobal(\"structuredClone\", () => structuredClone);\n    }\n\n    polyfillGlobal(\"TextEncoderStream\", () => TextEncoderStream);\n    polyfillGlobal(\"TextDecoderStream\", () => TextDecoderStream);\n  };\n\n  setupPolyfills();\n}\n\nexport {};\n`],\n  [\"examples/ai/native/uniwind/app/(drawer)/ai.tsx.hbs\", `{{#if (eq backend \"convex\")}}\nimport { Ionicons } from \"@expo/vector-icons\";\nimport {\n  useUIMessages,\n  useSmoothText,\n  type UIMessage,\n} from \"@convex-dev/agent/react\";\nimport { api } from \"@{{projectName}}/backend/convex/_generated/api\";\nimport { useMutation } from \"convex/react\";\nimport { Button, Separator, Spinner, Surface, Input, TextField, useThemeColor } from \"heroui-native\";\nimport { useRef, useEffect, useState } from \"react\";\nimport {\n  View,\n  Text,\n  ScrollView,\n  KeyboardAvoidingView,\n  Platform,\n} from \"react-native\";\n\nimport { Container } from \"@/components/container\";\n\nfunction MessageContent({\n  text,\n  isStreaming,\n}: {\n  text: string;\n  isStreaming: boolean;\n}) {\n  const [visibleText] = useSmoothText(text, {\n    startStreaming: isStreaming,\n  });\n  return <Text className=\"text-foreground text-sm leading-relaxed\">{visibleText}</Text>;\n}\n\nexport default function AIScreen() {\n  const [input, setInput] = useState(\"\");\n  const [threadId, setThreadId] = useState<string | null>(null);\n  const [isLoading, setIsLoading] = useState(false);\n  const scrollViewRef = useRef<ScrollView>(null);\n  const mutedColor = useThemeColor(\"muted\");\n  const foregroundColor = useThemeColor(\"foreground\");\n\n  const createThread = useMutation(api.chat.createNewThread);\n  const sendMessage = useMutation(api.chat.sendMessage);\n\n  const { results: messages } = useUIMessages(\n    api.chat.listMessages,\n    threadId ? { threadId } : \"skip\",\n    { initialNumItems: 50, stream: true },\n  );\n\n  const hasStreamingMessage = messages?.some(\n    (m: UIMessage) => m.status === \"streaming\",\n  );\n\n  useEffect(() => {\n    scrollViewRef.current?.scrollToEnd({ animated: true });\n  }, [messages]);\n\n  const onSubmit = async () => {\n    const value = input.trim();\n    if (!value || isLoading) return;\n\n    setIsLoading(true);\n    setInput(\"\");\n\n    try {\n      let currentThreadId = threadId;\n      if (!currentThreadId) {\n        currentThreadId = await createThread();\n        setThreadId(currentThreadId);\n      }\n\n      await sendMessage({ threadId: currentThreadId, prompt: value });\n    } catch (error) {\n      console.error(\"Failed to send message:\", error);\n    } finally {\n      setIsLoading(false);\n    }\n  };\n\n  return (\n    <Container isScrollable={false}>\n      <KeyboardAvoidingView\n        className=\"flex-1\"\n        behavior={Platform.OS === \"ios\" ? \"padding\" : \"height\"}\n      >\n        <View className=\"flex-1 px-4 py-4\">\n          <View className=\"py-4 mb-4\">\n            <Text className=\"text-2xl font-semibold text-foreground tracking-tight\">AI Chat</Text>\n            <Text className=\"text-muted text-sm mt-1\">Chat with our AI assistant</Text>\n          </View>\n\n          <ScrollView\n            ref={scrollViewRef}\n            className=\"flex-1 mb-4\"\n            showsVerticalScrollIndicator={false}\n            contentContainerStyle=\\\\{{ flexGrow: 1, paddingBottom: 8 }}\n            keyboardShouldPersistTaps=\"handled\"\n          >\n            {!messages || messages.length === 0 ? (\n              <Surface variant=\"secondary\" className=\"flex-1 justify-center items-center py-8 rounded-xl\">\n                <Ionicons name=\"chatbubble-ellipses-outline\" size={32} color={mutedColor} />\n                <Text className=\"text-muted text-sm mt-3\">Ask me anything to get started</Text>\n              </Surface>\n            ) : (\n              <View className=\"gap-3\">\n                {messages.map((message: UIMessage) => (\n                  <Surface\n                    key={message.key}\n                    variant={message.role === \"user\" ? \"tertiary\" : \"secondary\"}\n                    className={\\`p-3 rounded-xl \\${message.role === \"user\" ? \"ml-8\" : \"mr-8\"}\\`}\n                  >\n                    <Text className=\"text-xs font-medium mb-1 text-muted\">\n                      {message.role === \"user\" ? \"You\" : \"AI\"}\n                    </Text>\n                    <MessageContent\n                      text={message.text ?? \"\"}\n                      isStreaming={message.status === \"streaming\"}\n                    />\n                  </Surface>\n                ))}\n                {isLoading && !hasStreamingMessage && (\n                  <Surface variant=\"secondary\" className=\"p-3 mr-10 rounded-lg\">\n                    <Text className=\"text-xs font-medium mb-1 text-muted\">AI</Text>\n                    <View className=\"flex-row items-center gap-2\">\n                      <Spinner size=\"sm\" />\n                      <Text className=\"text-muted text-sm\">Thinking...</Text>\n                    </View>\n                  </Surface>\n                )}\n              </View>\n            )}\n          </ScrollView>\n\n          <Separator className=\"mb-3\" />\n\n          <View className=\"flex-row items-center gap-2\">\n            <View className=\"flex-1\">\n                <TextField>\n                  <Input\n                    value={input}\n                    onChangeText={setInput}\n                    placeholder=\"Type a message...\"\n                    onSubmitEditing={onSubmit}\n                    editable={!isLoading}\n                    returnKeyType=\"send\"\n                  />\n                </TextField>\n              </View>\n            <Button\n              isIconOnly\n              variant={input.trim() && !isLoading ? \"primary\" : \"secondary\"}\n              onPress={onSubmit}\n              isDisabled={!input.trim() || isLoading}\n              size=\"sm\"\n            >\n              <Ionicons\n                name=\"arrow-up\"\n                size={18}\n                color={input.trim() && !isLoading ? foregroundColor : mutedColor}\n              />\n            </Button>\n          </View>\n        </View>\n      </KeyboardAvoidingView>\n    </Container>\n  );\n}\n{{else}}\nimport { useRef, useEffect, useState } from \"react\";\nimport {\n  View,\n  Text,\n  ScrollView,\n  KeyboardAvoidingView,\n  Platform,\n} from \"react-native\";\nimport { useChat } from \"@ai-sdk/react\";\nimport { DefaultChatTransport } from \"ai\";\nimport { fetch as expoFetch } from \"expo/fetch\";\nimport { Ionicons } from \"@expo/vector-icons\";\nimport { Container } from \"@/components/container\";\nimport { Button, Separator, FieldError, Spinner, Surface, Input, TextField, useThemeColor } from \"heroui-native\";\nimport { env } from \"@{{projectName}}/env/native\";\n\nconst generateAPIUrl = (relativePath: string) => {\n  const serverUrl = env.EXPO_PUBLIC_SERVER_URL;\n  if (!serverUrl) {\n    throw new Error(\n      \"EXPO_PUBLIC_SERVER_URL environment variable is not defined\"\n    );\n  }\n  const path = relativePath.startsWith(\"/\") ? relativePath : \\`/\\${relativePath}\\`;\n  return serverUrl.concat(path);\n};\n\nexport default function AIScreen() {\n  const [input, setInput] = useState(\"\");\n  const { messages, error, sendMessage, status } = useChat({\n    transport: new DefaultChatTransport({\n      fetch: expoFetch as unknown as typeof globalThis.fetch,\n      api: generateAPIUrl(\"/ai\"),\n    }),\n    onError: (error) => console.error(error, \"AI Chat Error\"),\n  });\n  const scrollViewRef = useRef<ScrollView>(null);\n  const foregroundColor = useThemeColor(\"foreground\");\n  const mutedColor = useThemeColor(\"muted\");\n  const isBusy = status === \"submitted\" || status === \"streaming\";\n\n  useEffect(() => {\n    scrollViewRef.current?.scrollToEnd({ animated: true });\n  }, [messages]);\n\n  const onSubmit = () => {\n    const value = input.trim();\n    if (value && !isBusy) {\n      sendMessage({ text: value });\n      setInput(\"\");\n    }\n  };\n\n  if (error) {\n    return (\n      <Container isScrollable={false}>\n        <View className=\"flex-1 justify-center items-center px-4\">\n          <Surface variant=\"secondary\" className=\"p-4 rounded-lg\">\n            <FieldError isInvalid>\n              <Text className=\"text-danger text-center font-medium mb-1\">\n                {error.message}\n              </Text>\n              <Text className=\"text-muted text-center text-xs\">\n                Please check your connection and try again.\n              </Text>\n            </FieldError>\n          </Surface>\n        </View>\n      </Container>\n    );\n  }\n\n  return (\n    <Container isScrollable={false}>\n      <KeyboardAvoidingView\n        className=\"flex-1\"\n        behavior={Platform.OS === \"ios\" ? \"padding\" : \"height\"}\n      >\n        <View className=\"flex-1 px-4 py-4\">\n          <View className=\"py-4 mb-4\">\n            <Text className=\"text-2xl font-semibold text-foreground tracking-tight\">AI Chat</Text>\n            <Text className=\"text-muted text-sm mt-1\">Chat with our AI assistant</Text>\n          </View>\n\n          <ScrollView\n            ref={scrollViewRef}\n            className=\"flex-1 mb-4\"\n            showsVerticalScrollIndicator={false}\n            contentContainerStyle=\\\\{{ flexGrow: 1, paddingBottom: 8 }}\n            keyboardShouldPersistTaps=\"handled\"\n          >\n            {messages.length === 0 ? (\n              <Surface variant=\"secondary\" className=\"flex-1 justify-center items-center py-8 rounded-xl\">\n                <Ionicons name=\"chatbubble-ellipses-outline\" size={32} color={mutedColor} />\n                <Text className=\"text-muted text-sm mt-3\">Ask me anything to get started</Text>\n              </Surface>\n            ) : (\n              <View className=\"gap-3\">\n                {messages.map((message) => (\n                  <Surface\n                    key={message.id}\n                    variant={message.role === \"user\" ? \"tertiary\" : \"secondary\"}\n                    className={\\`p-3 rounded-xl \\${message.role === \"user\" ? \"ml-8\" : \"mr-8\"}\\`}\n                  >\n                    <Text className=\"text-xs font-medium mb-1 text-muted\">\n                      {message.role === \"user\" ? \"You\" : \"AI\"}\n                    </Text>\n                    <View className=\"gap-1\">\n                      {message.parts.map((part, i) =>\n                        part.type === \"text\" ? (\n                          <Text\n                            key={\\`\\${message.id}-\\${i}\\`}\n                            className=\"text-foreground text-sm leading-relaxed\"\n                          >\n                            {part.text}\n                          </Text>\n                        ) : (\n                          <Text\n                            key={\\`\\${message.id}-\\${i}\\`}\n                            className=\"text-foreground text-sm leading-relaxed\"\n                          >\n                            {JSON.stringify(part)}\n                          </Text>\n                        )\n                      )}\n                    </View>\n                  </Surface>\n                ))}\n                {isBusy && (\n                  <Surface variant=\"secondary\" className=\"p-3 mr-8 rounded-xl\">\n                    <Text className=\"text-xs font-medium mb-1 text-muted\">AI</Text>\n                    <View className=\"flex-row items-center gap-2\">\n                      <Spinner size=\"sm\" />\n                      <Text className=\"text-muted text-sm\">Thinking...</Text>\n                    </View>\n                  </Surface>\n                )}\n              </View>\n            )}\n          </ScrollView>\n\n          <Separator className=\"mb-3\" />\n\n          <View className=\"flex-row items-center gap-2\">\n            <View className=\"flex-1\">\n              <TextField>\n                <Input\n                  value={input}\n                  onChangeText={setInput}\n                  placeholder=\"Type a message...\"\n                  onSubmitEditing={onSubmit}\n                  returnKeyType=\"send\"\n                  editable={!isBusy}\n                />\n              </TextField>\n            </View>\n            <Button\n              isIconOnly\n              variant={input.trim() && !isBusy ? \"primary\" : \"secondary\"}\n              onPress={onSubmit}\n              isDisabled={!input.trim() || isBusy}\n              size=\"sm\"\n            >\n              <Ionicons\n                name=\"arrow-up\"\n                size={18}\n                color={input.trim() && !isBusy ? foregroundColor : mutedColor}\n              />\n            </Button>\n          </View>\n        </View>\n      </KeyboardAvoidingView>\n    </Container>\n  );\n}\n{{/if}}\n`],\n  [\"examples/ai/native/uniwind/polyfills.js\", `import structuredClone from \"@ungap/structured-clone\";\nimport { Platform } from \"react-native\";\n\nif (Platform.OS !== \"web\") {\n  const setupPolyfills = async () => {\n    const { polyfillGlobal } = await import(\"react-native/Libraries/Utilities/PolyfillFunctions\");\n\n    const { TextEncoderStream, TextDecoderStream } =\n      await import(\"@stardazed/streams-text-encoding\");\n\n    if (!(\"structuredClone\" in global)) {\n      polyfillGlobal(\"structuredClone\", () => structuredClone);\n    }\n\n    polyfillGlobal(\"TextEncoderStream\", () => TextEncoderStream);\n    polyfillGlobal(\"TextDecoderStream\", () => TextDecoderStream);\n  };\n\n  setupPolyfills();\n}\n\nexport {};\n`],\n  [\"examples/ai/web/nuxt/app/pages/ai.vue.hbs\", `<script setup lang=\"ts\">\nimport { Chat } from '@ai-sdk/vue'\nimport type { UIMessage } from 'ai'\nimport { getTextFromMessage } from '@nuxt/ui/utils/ai'\nimport { DefaultChatTransport } from 'ai'\nimport { computed, ref } from 'vue'\n\nconst SUGGESTIONS = [\n  {\n    title: 'Plan a feature',\n    prompt: 'Help me break down a small product feature into implementation steps.'\n  },\n  {\n    title: 'Design the schema',\n    prompt: 'Suggest a database schema for a collaborative notes app.'\n  },\n  {\n    title: 'Add auth flow',\n    prompt: 'What is the cleanest way to add login, signup, and protected routes here?'\n  },\n  {\n    title: 'Deploy checklist',\n    prompt: 'Give me a production deployment checklist for this stack.'\n  }\n] as const\n\nconst messages: UIMessage[] = []\nconst input = ref('')\nconst aiApiUrl = {{#if (eq backend \"self\")}}'/api/ai'{{else}}\\`\\${useRuntimeConfig().public.serverUrl}/ai\\`{{/if}}\n\nconst chat = new Chat({\n  messages,\n  transport: new DefaultChatTransport({\n    api: aiApiUrl,\n  }),\n  onError(error) {\n    console.error('Chat error:', error)\n  }\n})\n\nconst hasMessages = computed(() => chat.messages.length > 0)\nconst isLoading = computed(() => chat.status === 'submitted' || chat.status === 'streaming')\n\nfunction applySuggestion(prompt: string) {\n  input.value = prompt\n}\n\nasync function handleSubmit(e: Event) {\n  e.preventDefault()\n  const userInput = input.value\n  input.value = ''\n\n  if (!userInput.trim()) return\n\n  chat.sendMessage({ text: userInput })\n}\n</script>\n\n<template>\n  <UContainer class=\"flex min-h-[calc(100vh-var(--ui-header-height)-2rem)] max-w-5xl flex-col py-4 sm:py-6\">\n    <div class=\"min-h-0 flex-1\">\n      <div v-if=\"!hasMessages\" class=\"flex h-full items-center\">\n        <div class=\"mx-auto flex w-full max-w-3xl flex-col gap-8\">\n          <div class=\"space-y-3 text-center\">\n            <UBadge label=\"AI Chat\" color=\"primary\" variant=\"subtle\" class=\"rounded-full\" />\n            <div class=\"space-y-2\">\n              <h1 class=\"text-3xl font-semibold tracking-tight text-highlighted sm:text-4xl\">\n                Ask the starter for your next move.\n              </h1>\n              <p class=\"mx-auto max-w-2xl text-sm leading-6 text-muted sm:text-base\">\n                Use the built-in chat to plan features, sketch schemas, or unblock implementation work without leaving the app.\n              </p>\n            </div>\n          </div>\n\n          <div class=\"grid gap-3 sm:grid-cols-2\">\n            <UButton\n              v-for=\"suggestion in SUGGESTIONS\"\n              :key=\"suggestion.title\"\n              color=\"neutral\"\n              variant=\"soft\"\n              class=\"h-auto justify-start rounded-2xl px-4 py-4 text-left\"\n              @click=\"applySuggestion(suggestion.prompt)\"\n            >\n              <div class=\"space-y-1\">\n                <div class=\"text-sm font-medium text-highlighted\">\\\\{{ suggestion.title }}</div>\n                <div class=\"text-sm leading-6 text-muted\">\\\\{{ suggestion.prompt }}</div>\n              </div>\n            </UButton>\n          </div>\n        </div>\n      </div>\n\n      <div v-else class=\"mx-auto flex h-full w-full max-w-3xl min-h-0 flex-col\">\n        <UChatMessages\n          :messages=\"chat.messages\"\n          :status=\"chat.status\"\n          :assistant=\"{\n            variant: 'outline',\n            avatar: {\n              icon: 'i-lucide-bot'\n            }\n          }\"\n          :user=\"{\n            variant: 'soft',\n            avatar: {\n              icon: 'i-lucide-user'\n            }\n          }\"\n          class=\"min-h-0 flex-1 px-1\"\n        >\n          <template #content=\"{ message }\">\n            <div class=\"whitespace-pre-wrap text-sm leading-6\">\\\\{{ getTextFromMessage(message) }}</div>\n          </template>\n        </UChatMessages>\n      </div>\n    </div>\n\n    <div class=\"sticky bottom-0 mt-4 border-t border-default bg-default pt-4\">\n      <div class=\"mx-auto w-full max-w-3xl\">\n        <UChatPrompt\n          v-model=\"input\"\n          icon=\"i-lucide-sparkles\"\n          variant=\"soft\"\n          :rows=\"1\"\n          :maxrows=\"8\"\n          :loading=\"isLoading\"\n          :error=\"chat.error\"\n          :placeholder=\"hasMessages ? 'Keep the conversation going...' : 'Ask about your app, schema, auth, or deployment...'\"\n          @submit=\"handleSubmit\"\n        >\n          <UChatPromptSubmit\n            class=\"ms-auto\"\n            :status=\"chat.status\"\n            @stop=\"() => chat.stop()\"\n            @reload=\"() => chat.regenerate()\"\n          />\n        </UChatPrompt>\n\n        <div class=\"mt-2 flex items-center justify-between gap-3 px-1 text-xs text-muted\">\n          <span>Press Enter to send and Shift+Enter for a new line.</span>\n          <span>\\\\{{ hasMessages ? \\`\\${chat.messages.length} messages\\` : 'Ready when you are.' }}</span>\n        </div>\n      </div>\n    </div>\n  </UContainer>\n</template>\n`],\n  [\"examples/ai/web/react/next/src/app/ai/page.tsx.hbs\", `{{#if (eq backend \"convex\")}}\n\"use client\";\n\nimport { api } from \"@{{projectName}}/backend/convex/_generated/api\";\nimport {\n  useUIMessages,\n  useSmoothText,\n  type UIMessage,\n} from \"@convex-dev/agent/react\";\nimport { useMutation } from \"convex/react\";\nimport { Send, Loader2 } from \"lucide-react\";\n{{#if (eq webDeploy \"cloudflare\")}}\nimport dynamic from \"next/dynamic\";\n\nconst Streamdown = dynamic(\n  () => import(\"streamdown\").then((mod) => ({ default: mod.Streamdown })),\n  {\n    loading: () => (\n      <div className=\"flex h-full items-center justify-center\">\n        <div className=\"text-muted-foreground\">Loading response...</div>\n      </div>\n    ),\n    ssr: false,\n  }\n);\n{{else}}\nimport { Streamdown } from \"streamdown\";\n{{/if}}\nimport { useEffect, useRef, useState, type FormEvent } from \"react\";\n\nimport { Button } from \"@{{projectName}}/ui/components/button\";\nimport { Input } from \"@{{projectName}}/ui/components/input\";\n\nfunction MessageContent({\n  text,\n  isStreaming,\n}: {\n  text: string;\n  isStreaming: boolean;\n}) {\n  const [visibleText] = useSmoothText(text, {\n    startStreaming: isStreaming,\n  });\n  return <Streamdown>{visibleText}</Streamdown>;\n}\n\nexport default function AIPage() {\n  const [input, setInput] = useState(\"\");\n  const [threadId, setThreadId] = useState<string | null>(null);\n  const [isLoading, setIsLoading] = useState(false);\n  const messagesEndRef = useRef<HTMLDivElement>(null);\n\n  const createThread = useMutation(api.chat.createNewThread);\n  const sendMessage = useMutation(api.chat.sendMessage);\n\n  const { results: messages } = useUIMessages(\n    api.chat.listMessages,\n    threadId ? { threadId } : \"skip\",\n    { initialNumItems: 50, stream: true },\n  );\n\n  useEffect(() => {\n    messagesEndRef.current?.scrollIntoView({ behavior: \"smooth\" });\n  }, [messages]);\n\n  const hasStreamingMessage = messages?.some(\n    (m: UIMessage) => m.status === \"streaming\",\n  );\n\n  const handleSubmit = async (e: FormEvent<HTMLFormElement>) => {\n    e.preventDefault();\n    const text = input.trim();\n    if (!text || isLoading) return;\n\n    setIsLoading(true);\n    setInput(\"\");\n\n    try {\n      let currentThreadId = threadId;\n      if (!currentThreadId) {\n        currentThreadId = await createThread();\n        setThreadId(currentThreadId);\n      }\n\n      await sendMessage({ threadId: currentThreadId, prompt: text });\n    } catch (error) {\n      console.error(\"Failed to send message:\", error);\n    } finally {\n      setIsLoading(false);\n    }\n  };\n\n  return (\n    <div className=\"grid grid-rows-[1fr_auto] overflow-hidden w-full mx-auto p-4\">\n      <div className=\"overflow-y-auto space-y-4 pb-4\">\n        {!messages || messages.length === 0 ? (\n          <div className=\"text-center text-muted-foreground mt-8\">\n            Ask me anything to get started!\n          </div>\n        ) : (\n          messages.map((message: UIMessage) => (\n            <div\n              key={message.key}\n              className={\\`p-3 rounded-lg \\${\n                message.role === \"user\"\n                  ? \"bg-primary/10 ml-8\"\n                  : \"bg-secondary/20 mr-8\"\n              }\\`}\n            >\n              <p className=\"text-sm font-semibold mb-1\">\n                {message.role === \"user\" ? \"You\" : \"AI Assistant\"}\n              </p>\n              <MessageContent\n                text={message.text ?? \"\"}\n                isStreaming={message.status === \"streaming\"}\n              />\n            </div>\n          ))\n        )}\n        {isLoading && !hasStreamingMessage && (\n          <div className=\"p-3 rounded-lg bg-secondary/20 mr-8\">\n            <p className=\"text-sm font-semibold mb-1\">AI Assistant</p>\n            <div className=\"flex items-center gap-2 text-muted-foreground\">\n              <Loader2 className=\"h-4 w-4 animate-spin\" />\n              <span>Thinking...</span>\n            </div>\n          </div>\n        )}\n        <div ref={messagesEndRef} />\n      </div>\n\n      <form\n        onSubmit={handleSubmit}\n        className=\"w-full flex items-center space-x-2 pt-2 border-t\"\n      >\n        <Input\n          name=\"prompt\"\n          value={input}\n          onChange={(e) => setInput(e.target.value)}\n          placeholder=\"Type your message...\"\n          className=\"flex-1\"\n          autoComplete=\"off\"\n          autoFocus\n          disabled={isLoading}\n        />\n        <Button type=\"submit\" size=\"icon\" disabled={isLoading || !input.trim()}>\n          {isLoading ? (\n            <Loader2 className=\"h-4 w-4 animate-spin\" />\n          ) : (\n            <Send size={18} />\n          )}\n        </Button>\n      </form>\n    </div>\n  );\n}\n{{else}}\n\"use client\";\n\nimport { useChat } from \"@ai-sdk/react\";\nimport { DefaultChatTransport } from \"ai\";\nimport { Send } from \"lucide-react\";\n{{#if (eq webDeploy \"cloudflare\")}}\nimport dynamic from \"next/dynamic\";\n\nconst Streamdown = dynamic(\n  () => import(\"streamdown\").then((mod) => ({ default: mod.Streamdown })),\n  {\n    loading: () => (\n      <div className=\"flex h-full items-center justify-center\">\n        <div className=\"text-muted-foreground\">Loading response...</div>\n      </div>\n    ),\n    ssr: false,\n  }\n);\n{{else}}\nimport { Streamdown } from \"streamdown\";\n{{/if}}\nimport { useEffect, useRef, useState, type FormEvent } from \"react\";\n\nimport { Button } from \"@{{projectName}}/ui/components/button\";\nimport { Input } from \"@{{projectName}}/ui/components/input\";\nimport { env } from \"@{{projectName}}/env/web\";\n\nexport default function AIPage() {\n  const [input, setInput] = useState(\"\");\n  const { messages, sendMessage, status } = useChat({\n    transport: new DefaultChatTransport({\n      api: {{#if (eq backend \"self\")}}\"/api/ai\"{{else}}\\`\\${env.NEXT_PUBLIC_SERVER_URL}/ai\\`{{/if}},\n    }),\n  });\n\n  const messagesEndRef = useRef<HTMLDivElement>(null);\n\n  useEffect(() => {\n    messagesEndRef.current?.scrollIntoView({ behavior: \"smooth\" });\n  }, [messages]);\n\n  const handleSubmit = (e: FormEvent<HTMLFormElement>) => {\n    e.preventDefault();\n    const text = input.trim();\n    if (!text) return;\n    sendMessage({ text });\n    setInput(\"\");\n  };\n\n  return (\n    <div className=\"grid grid-rows-[1fr_auto] overflow-hidden w-full mx-auto p-4\">\n      <div className=\"overflow-y-auto space-y-4 pb-4\">\n        {messages.length === 0 ? (\n          <div className=\"text-center text-muted-foreground mt-8\">\n            Ask me anything to get started!\n          </div>\n        ) : (\n          messages.map((message) => (\n            <div\n              key={message.id}\n              className={\\`p-3 rounded-lg \\${\n                message.role === \"user\"\n                  ? \"bg-primary/10 ml-8\"\n                  : \"bg-secondary/20 mr-8\"\n              }\\`}\n            >\n              <p className=\"text-sm font-semibold mb-1\">\n                {message.role === \"user\" ? \"You\" : \"AI Assistant\"}\n              </p>\n              {message.parts?.map((part, index) => {\n                if (part.type === \"text\") {\n                  return (\n                    <Streamdown\n                      key={index}\n                      isAnimating={status === \"streaming\" && message.role === \"assistant\"}\n                    >\n                      {part.text}\n                    </Streamdown>\n                  );\n                }\n                return null;\n              })}\n            </div>\n          ))\n        )}\n        <div ref={messagesEndRef} />\n      </div>\n\n      <form\n        onSubmit={handleSubmit}\n        className=\"w-full flex items-center space-x-2 pt-2 border-t\"\n      >\n        <Input\n          name=\"prompt\"\n          value={input}\n          onChange={(e) => setInput(e.target.value)}\n          placeholder=\"Type your message...\"\n          className=\"flex-1\"\n          autoComplete=\"off\"\n          autoFocus\n        />\n        <Button type=\"submit\" size=\"icon\">\n          <Send size={18} />\n        </Button>\n      </form>\n    </div>\n  );\n}\n{{/if}}\n`],\n  [\"examples/ai/web/react/react-router/src/routes/ai.tsx.hbs\", `{{#if (eq backend \"convex\")}}\nimport { api } from \"@{{projectName}}/backend/convex/_generated/api\";\nimport {\n  useUIMessages,\n  useSmoothText,\n  type UIMessage,\n} from \"@convex-dev/agent/react\";\nimport { useMutation } from \"convex/react\";\nimport { Send, Loader2 } from \"lucide-react\";\nimport React, { useRef, useEffect, useState, type FormEvent } from \"react\";\nimport { Streamdown } from \"streamdown\";\n\nimport { Button } from \"@{{projectName}}/ui/components/button\";\nimport { Input } from \"@{{projectName}}/ui/components/input\";\n\nfunction MessageContent({\n  text,\n  isStreaming,\n}: {\n  text: string;\n  isStreaming: boolean;\n}) {\n  const [visibleText] = useSmoothText(text, {\n    startStreaming: isStreaming,\n  });\n  return <Streamdown>{visibleText}</Streamdown>;\n}\n\nconst AI: React.FC = () => {\n  const [input, setInput] = useState(\"\");\n  const [threadId, setThreadId] = useState<string | null>(null);\n  const [isLoading, setIsLoading] = useState(false);\n  const messagesEndRef = useRef<HTMLDivElement>(null);\n\n  const createThread = useMutation(api.chat.createNewThread);\n  const sendMessage = useMutation(api.chat.sendMessage);\n\n  const { results: messages } = useUIMessages(\n    api.chat.listMessages,\n    threadId ? { threadId } : \"skip\",\n    { initialNumItems: 50, stream: true },\n  );\n\n  useEffect(() => {\n    messagesEndRef.current?.scrollIntoView({ behavior: \"smooth\" });\n  }, [messages]);\n\n  const hasStreamingMessage = messages?.some(\n    (m: UIMessage) => m.status === \"streaming\",\n  );\n\n  const handleSubmit = async (e: FormEvent<HTMLFormElement>) => {\n    e.preventDefault();\n    const text = input.trim();\n    if (!text || isLoading) return;\n\n    setIsLoading(true);\n    setInput(\"\");\n\n    try {\n      let currentThreadId = threadId;\n      if (!currentThreadId) {\n        currentThreadId = await createThread();\n        setThreadId(currentThreadId);\n      }\n\n      await sendMessage({ threadId: currentThreadId, prompt: text });\n    } catch (error) {\n      console.error(\"Failed to send message:\", error);\n    } finally {\n      setIsLoading(false);\n    }\n  };\n\n  return (\n    <div className=\"grid grid-rows-[1fr_auto] overflow-hidden w-full mx-auto p-4\">\n      <div className=\"overflow-y-auto space-y-4 pb-4\">\n        {!messages || messages.length === 0 ? (\n          <div className=\"text-center text-muted-foreground mt-8\">\n            Ask me anything to get started!\n          </div>\n        ) : (\n          messages.map((message: UIMessage) => (\n            <div\n              key={message.key}\n              className={\\`p-3 rounded-lg \\${\n                message.role === \"user\"\n                  ? \"bg-primary/10 ml-8\"\n                  : \"bg-secondary/20 mr-8\"\n              }\\`}\n            >\n              <p className=\"text-sm font-semibold mb-1\">\n                {message.role === \"user\" ? \"You\" : \"AI Assistant\"}\n              </p>\n              <MessageContent\n                text={message.text ?? \"\"}\n                isStreaming={message.status === \"streaming\"}\n              />\n            </div>\n          ))\n        )}\n        {isLoading && !hasStreamingMessage && (\n          <div className=\"p-3 rounded-lg bg-secondary/20 mr-8\">\n            <p className=\"text-sm font-semibold mb-1\">AI Assistant</p>\n            <div className=\"flex items-center gap-2 text-muted-foreground\">\n              <Loader2 className=\"h-4 w-4 animate-spin\" />\n              <span>Thinking...</span>\n            </div>\n          </div>\n        )}\n        <div ref={messagesEndRef} />\n      </div>\n\n      <form\n        onSubmit={handleSubmit}\n        className=\"w-full flex items-center space-x-2 pt-2 border-t\"\n      >\n        <Input\n          name=\"prompt\"\n          value={input}\n          onChange={(e) => setInput(e.target.value)}\n          placeholder=\"Type your message...\"\n          className=\"flex-1\"\n          autoComplete=\"off\"\n          autoFocus\n          disabled={isLoading}\n        />\n        <Button type=\"submit\" size=\"icon\" disabled={isLoading || !input.trim()}>\n          {isLoading ? (\n            <Loader2 className=\"h-4 w-4 animate-spin\" />\n          ) : (\n            <Send size={18} />\n          )}\n        </Button>\n      </form>\n    </div>\n  );\n};\n\nexport default AI;\n{{else}}\nimport React, { useRef, useEffect, useState, type FormEvent } from \"react\";\nimport { useChat } from \"@ai-sdk/react\";\nimport { DefaultChatTransport } from \"ai\";\nimport { Send } from \"lucide-react\";\nimport { Streamdown } from \"streamdown\";\nimport { env } from \"@{{projectName}}/env/web\";\n\nimport { Button } from \"@{{projectName}}/ui/components/button\";\nimport { Input } from \"@{{projectName}}/ui/components/input\";\n\nconst AI: React.FC = () => {\n  const [input, setInput] = useState(\"\");\n  const { messages, sendMessage, status } = useChat({\n    transport: new DefaultChatTransport({\n      api: \\`\\${env.VITE_SERVER_URL}/ai\\`,\n    }),\n  });\n\n  const messagesEndRef = useRef<HTMLDivElement>(null);\n\n  useEffect(() => {\n    messagesEndRef.current?.scrollIntoView({ behavior: \"smooth\" });\n  }, [messages]);\n\n  const handleSubmit = (e: FormEvent<HTMLFormElement>) => {\n    e.preventDefault();\n    const text = input.trim();\n    if (!text) return;\n    sendMessage({ text });\n    setInput(\"\");\n  };\n\n  return (\n    <div className=\"grid grid-rows-[1fr_auto] overflow-hidden w-full mx-auto p-4\">\n      <div className=\"overflow-y-auto space-y-4 pb-4\">\n        {messages.length === 0 ? (\n          <div className=\"text-center text-muted-foreground mt-8\">\n            Ask me anything to get started!\n          </div>\n        ) : (\n          messages.map((message) => (\n            <div\n              key={message.id}\n              className={\\`p-3 rounded-lg \\${\n                message.role === \"user\"\n                  ? \"bg-primary/10 ml-8\"\n                  : \"bg-secondary/20 mr-8\"\n              }\\`}\n            >\n              <p className=\"text-sm font-semibold mb-1\">\n                {message.role === \"user\" ? \"You\" : \"AI Assistant\"}\n              </p>\n              {message.parts?.map((part, index) => {\n                if (part.type === \"text\") {\n                  return (\n                    <Streamdown\n                      key={index}\n                      isAnimating={status === \"streaming\" && message.role === \"assistant\"}\n                    >\n                      {part.text}\n                    </Streamdown>\n                  );\n                }\n                return null;\n              })}\n            </div>\n          ))\n        )}\n        <div ref={messagesEndRef} />\n      </div>\n\n      <form\n        onSubmit={handleSubmit}\n        className=\"w-full flex items-center space-x-2 pt-2 border-t\"\n      >\n        <Input\n          name=\"prompt\"\n          value={input}\n          onChange={(e) => setInput(e.target.value)}\n          placeholder=\"Type your message...\"\n          className=\"flex-1\"\n          autoComplete=\"off\"\n          autoFocus\n        />\n        <Button type=\"submit\" size=\"icon\">\n          <Send size={18} />\n        </Button>\n      </form>\n    </div>\n  );\n};\n\nexport default AI;\n{{/if}}\n`],\n  [\"examples/ai/web/react/tanstack-router/src/routes/ai.tsx.hbs\", `{{#if (eq backend \"convex\")}}\nimport { api } from \"@{{projectName}}/backend/convex/_generated/api\";\nimport {\n  useUIMessages,\n  useSmoothText,\n  type UIMessage,\n} from \"@convex-dev/agent/react\";\nimport { createFileRoute } from \"@tanstack/react-router\";\nimport { useMutation } from \"convex/react\";\nimport { Send, Loader2 } from \"lucide-react\";\nimport { useRef, useEffect, useState, type FormEvent } from \"react\";\nimport { Streamdown } from \"streamdown\";\n\nimport { Button } from \"@{{projectName}}/ui/components/button\";\nimport { Input } from \"@{{projectName}}/ui/components/input\";\n\nexport const Route = createFileRoute(\"/ai\")({\n  component: RouteComponent,\n});\n\nfunction MessageContent({\n  text,\n  isStreaming,\n}: {\n  text: string;\n  isStreaming: boolean;\n}) {\n  const [visibleText] = useSmoothText(text, {\n    startStreaming: isStreaming,\n  });\n  return <Streamdown>{visibleText}</Streamdown>;\n}\n\nfunction RouteComponent() {\n  const [input, setInput] = useState(\"\");\n  const [threadId, setThreadId] = useState<string | null>(null);\n  const [isLoading, setIsLoading] = useState(false);\n  const messagesEndRef = useRef<HTMLDivElement>(null);\n\n  const createThread = useMutation(api.chat.createNewThread);\n  const sendMessage = useMutation(api.chat.sendMessage);\n\n  const { results: messages } = useUIMessages(\n    api.chat.listMessages,\n    threadId ? { threadId } : \"skip\",\n    { initialNumItems: 50, stream: true },\n  );\n\n  useEffect(() => {\n    messagesEndRef.current?.scrollIntoView({ behavior: \"smooth\" });\n  }, [messages]);\n\n  const hasStreamingMessage = messages?.some(\n    (m: UIMessage) => m.status === \"streaming\",\n  );\n\n  const handleSubmit = async (e: FormEvent<HTMLFormElement>) => {\n    e.preventDefault();\n    const text = input.trim();\n    if (!text || isLoading) return;\n\n    setIsLoading(true);\n    setInput(\"\");\n\n    try {\n      let currentThreadId = threadId;\n      if (!currentThreadId) {\n        currentThreadId = await createThread();\n        setThreadId(currentThreadId);\n      }\n\n      await sendMessage({ threadId: currentThreadId, prompt: text });\n    } catch (error) {\n      console.error(\"Failed to send message:\", error);\n    } finally {\n      setIsLoading(false);\n    }\n  };\n\n  return (\n    <div className=\"grid grid-rows-[1fr_auto] overflow-hidden w-full mx-auto p-4\">\n      <div className=\"overflow-y-auto space-y-4 pb-4\">\n        {!messages || messages.length === 0 ? (\n          <div className=\"text-center text-muted-foreground mt-8\">\n            Ask me anything to get started!\n          </div>\n        ) : (\n          messages.map((message: UIMessage) => (\n            <div\n              key={message.key}\n              className={\\`p-3 rounded-lg \\${\n                message.role === \"user\"\n                  ? \"bg-primary/10 ml-8\"\n                  : \"bg-secondary/20 mr-8\"\n              }\\`}\n            >\n              <p className=\"text-sm font-semibold mb-1\">\n                {message.role === \"user\" ? \"You\" : \"AI Assistant\"}\n              </p>\n              <MessageContent\n                text={message.text ?? \"\"}\n                isStreaming={message.status === \"streaming\"}\n              />\n            </div>\n          ))\n        )}\n        {isLoading && !hasStreamingMessage && (\n          <div className=\"p-3 rounded-lg bg-secondary/20 mr-8\">\n            <p className=\"text-sm font-semibold mb-1\">AI Assistant</p>\n            <div className=\"flex items-center gap-2 text-muted-foreground\">\n              <Loader2 className=\"h-4 w-4 animate-spin\" />\n              <span>Thinking...</span>\n            </div>\n          </div>\n        )}\n        <div ref={messagesEndRef} />\n      </div>\n\n      <form\n        onSubmit={handleSubmit}\n        className=\"w-full flex items-center space-x-2 pt-2 border-t\"\n      >\n        <Input\n          name=\"prompt\"\n          value={input}\n          onChange={(e) => setInput(e.target.value)}\n          placeholder=\"Type your message...\"\n          className=\"flex-1\"\n          autoComplete=\"off\"\n          autoFocus\n          disabled={isLoading}\n        />\n        <Button type=\"submit\" size=\"icon\" disabled={isLoading || !input.trim()}>\n          {isLoading ? (\n            <Loader2 className=\"h-4 w-4 animate-spin\" />\n          ) : (\n            <Send size={18} />\n          )}\n        </Button>\n      </form>\n    </div>\n  );\n}\n{{else}}\nimport { createFileRoute } from \"@tanstack/react-router\";\nimport { useChat } from \"@ai-sdk/react\";\nimport { DefaultChatTransport } from \"ai\";\nimport { Input } from \"@{{projectName}}/ui/components/input\";\nimport { Button } from \"@{{projectName}}/ui/components/button\";\nimport { Send } from \"lucide-react\";\nimport { useRef, useEffect, useState, type FormEvent } from \"react\";\nimport { Streamdown } from \"streamdown\";\n{{#unless (eq backend \"self\")}}\nimport { env } from \"@{{projectName}}/env/web\";\n{{/unless}}\n\nexport const Route = createFileRoute(\"/ai\")({\n  component: RouteComponent,\n});\n\nfunction RouteComponent() {\n  const [input, setInput] = useState(\"\");\n  const { messages, sendMessage, status } = useChat({\n    transport: new DefaultChatTransport({\n      api: {{#if (eq backend \"self\")}}\"/api/ai\"{{else}}\\`\\${env.VITE_SERVER_URL}/ai\\`{{/if}},\n    }),\n  });\n\n  const messagesEndRef = useRef<HTMLDivElement>(null);\n\n  useEffect(() => {\n    messagesEndRef.current?.scrollIntoView({ behavior: \"smooth\" });\n  }, [messages]);\n\n  const handleSubmit = (e: FormEvent<HTMLFormElement>) => {\n    e.preventDefault();\n    const text = input.trim();\n    if (!text) return;\n    sendMessage({ text });\n    setInput(\"\");\n  };\n\n  return (\n    <div className=\"grid grid-rows-[1fr_auto] overflow-hidden w-full mx-auto p-4\">\n      <div className=\"overflow-y-auto space-y-4 pb-4\">\n        {messages.length === 0 ? (\n          <div className=\"text-center text-muted-foreground mt-8\">\n            Ask me anything to get started!\n          </div>\n        ) : (\n          messages.map((message) => (\n            <div\n              key={message.id}\n              className={\\`p-3 rounded-lg \\${\n                message.role === \"user\"\n                  ? \"bg-primary/10 ml-8\"\n                  : \"bg-secondary/20 mr-8\"\n              }\\`}\n            >\n              <p className=\"text-sm font-semibold mb-1\">\n                {message.role === \"user\" ? \"You\" : \"AI Assistant\"}\n              </p>\n              {message.parts?.map((part, index) => {\n                if (part.type === \"text\") {\n                  return (\n                    <Streamdown\n                      key={index}\n                      isAnimating={status === \"streaming\" && message.role === \"assistant\"}\n                    >\n                      {part.text}\n                    </Streamdown>\n                  );\n                }\n                return null;\n              })}\n            </div>\n          ))\n        )}\n        <div ref={messagesEndRef} />\n      </div>\n\n      <form\n        onSubmit={handleSubmit}\n        className=\"w-full flex items-center space-x-2 pt-2 border-t\"\n      >\n        <Input\n          name=\"prompt\"\n          value={input}\n          onChange={(e) => setInput(e.target.value)}\n          placeholder=\"Type your message...\"\n          className=\"flex-1\"\n          autoComplete=\"off\"\n          autoFocus\n        />\n        <Button type=\"submit\" size=\"icon\">\n          <Send size={18} />\n        </Button>\n      </form>\n    </div>\n  );\n}\n{{/if}}\n`],\n  [\"examples/ai/web/react/tanstack-start/src/routes/ai.tsx.hbs\", `{{#if (eq backend \"convex\")}}\nimport { api } from \"@{{projectName}}/backend/convex/_generated/api\";\nimport {\n  useUIMessages,\n  useSmoothText,\n  type UIMessage,\n} from \"@convex-dev/agent/react\";\nimport { createFileRoute } from \"@tanstack/react-router\";\nimport { useMutation } from \"convex/react\";\nimport { Send, Loader2 } from \"lucide-react\";\nimport { useRef, useEffect, useState, type FormEvent } from \"react\";\nimport { Streamdown } from \"streamdown\";\n\nimport { Button } from \"@{{projectName}}/ui/components/button\";\nimport { Input } from \"@{{projectName}}/ui/components/input\";\n\nexport const Route = createFileRoute(\"/ai\")({\n  component: RouteComponent,\n});\n\nfunction MessageContent({\n  text,\n  isStreaming,\n}: {\n  text: string;\n  isStreaming: boolean;\n}) {\n  const [visibleText] = useSmoothText(text, {\n    startStreaming: isStreaming,\n  });\n  return <Streamdown>{visibleText}</Streamdown>;\n}\n\nfunction RouteComponent() {\n  const [input, setInput] = useState(\"\");\n  const [threadId, setThreadId] = useState<string | null>(null);\n  const [isLoading, setIsLoading] = useState(false);\n  const messagesEndRef = useRef<HTMLDivElement>(null);\n\n  const createThread = useMutation(api.chat.createNewThread);\n  const sendMessage = useMutation(api.chat.sendMessage);\n\n  const { results: messages } = useUIMessages(\n    api.chat.listMessages,\n    threadId ? { threadId } : \"skip\",\n    { initialNumItems: 50, stream: true },\n  );\n\n  useEffect(() => {\n    messagesEndRef.current?.scrollIntoView({ behavior: \"smooth\" });\n  }, [messages]);\n\n  const hasStreamingMessage = messages?.some(\n    (m: UIMessage) => m.status === \"streaming\",\n  );\n\n  const handleSubmit = async (e: FormEvent<HTMLFormElement>) => {\n    e.preventDefault();\n    const text = input.trim();\n    if (!text || isLoading) return;\n\n    setIsLoading(true);\n    setInput(\"\");\n\n    try {\n      let currentThreadId = threadId;\n      if (!currentThreadId) {\n        currentThreadId = await createThread();\n        setThreadId(currentThreadId);\n      }\n\n      await sendMessage({ threadId: currentThreadId, prompt: text });\n    } catch (error) {\n      console.error(\"Failed to send message:\", error);\n    } finally {\n      setIsLoading(false);\n    }\n  };\n\n  return (\n    <div className=\"grid grid-rows-[1fr_auto] overflow-hidden w-full mx-auto p-4\">\n      <div className=\"overflow-y-auto space-y-4 pb-4\">\n        {!messages || messages.length === 0 ? (\n          <div className=\"text-center text-muted-foreground mt-8\">\n            Ask me anything to get started!\n          </div>\n        ) : (\n          messages.map((message: UIMessage) => (\n            <div\n              key={message.key}\n              className={\\`p-3 rounded-lg \\${\n                message.role === \"user\"\n                  ? \"bg-primary/10 ml-8\"\n                  : \"bg-secondary/20 mr-8\"\n              }\\`}\n            >\n              <p className=\"text-sm font-semibold mb-1\">\n                {message.role === \"user\" ? \"You\" : \"AI Assistant\"}\n              </p>\n              <MessageContent\n                text={message.text ?? \"\"}\n                isStreaming={message.status === \"streaming\"}\n              />\n            </div>\n          ))\n        )}\n        {isLoading && !hasStreamingMessage && (\n          <div className=\"p-3 rounded-lg bg-secondary/20 mr-8\">\n            <p className=\"text-sm font-semibold mb-1\">AI Assistant</p>\n            <div className=\"flex items-center gap-2 text-muted-foreground\">\n              <Loader2 className=\"h-4 w-4 animate-spin\" />\n              <span>Thinking...</span>\n            </div>\n          </div>\n        )}\n        <div ref={messagesEndRef} />\n      </div>\n\n      <form\n        onSubmit={handleSubmit}\n        className=\"w-full flex items-center space-x-2 pt-2 border-t\"\n      >\n        <Input\n          name=\"prompt\"\n          value={input}\n          onChange={(e) => setInput(e.target.value)}\n          placeholder=\"Type your message...\"\n          className=\"flex-1\"\n          autoComplete=\"off\"\n          autoFocus\n          disabled={isLoading}\n        />\n        <Button type=\"submit\" size=\"icon\" disabled={isLoading || !input.trim()}>\n          {isLoading ? (\n            <Loader2 className=\"h-4 w-4 animate-spin\" />\n          ) : (\n            <Send size={18} />\n          )}\n        </Button>\n      </form>\n    </div>\n  );\n}\n{{else}}\nimport { createFileRoute } from \"@tanstack/react-router\";\nimport { useChat } from \"@ai-sdk/react\";\nimport { DefaultChatTransport } from \"ai\";\nimport { Send } from \"lucide-react\";\nimport { useRef, useEffect, useState, type FormEvent } from \"react\";\nimport { Streamdown } from \"streamdown\";\n{{#unless (eq backend \"self\")}}\nimport { env } from \"@{{projectName}}/env/web\";\n{{/unless}}\n\nimport { Button } from \"@{{projectName}}/ui/components/button\";\nimport { Input } from \"@{{projectName}}/ui/components/input\";\n\nexport const Route = createFileRoute(\"/ai\")({\n  component: RouteComponent,\n});\n\nfunction RouteComponent() {\n  const [input, setInput] = useState(\"\");\n  const { messages, sendMessage, status } = useChat({\n    transport: new DefaultChatTransport({\n      api: {{#if (eq backend \"self\")}}\"/api/ai\"{{else}}\\`\\${env.VITE_SERVER_URL}/ai\\`{{/if}},\n    }),\n  });\n\n  const messagesEndRef = useRef<HTMLDivElement>(null);\n\n  useEffect(() => {\n    messagesEndRef.current?.scrollIntoView({ behavior: \"smooth\" });\n  }, [messages]);\n\n  const handleSubmit = (e: FormEvent<HTMLFormElement>) => {\n    e.preventDefault();\n    const text = input.trim();\n    if (!text) return;\n    sendMessage({ text });\n    setInput(\"\");\n  };\n\n  return (\n    <div className=\"grid grid-rows-[1fr_auto] overflow-hidden w-full mx-auto p-4\">\n      <div className=\"overflow-y-auto space-y-4 pb-4\">\n        {messages.length === 0 ? (\n          <div className=\"text-center text-muted-foreground mt-8\">\n            Ask me anything to get started!\n          </div>\n        ) : (\n          messages.map((message) => (\n            <div\n              key={message.id}\n              className={\\`p-3 rounded-lg \\${\n                message.role === \"user\"\n                  ? \"bg-primary/10 ml-8\"\n                  : \"bg-secondary/20 mr-8\"\n              }\\`}\n            >\n              <p className=\"text-sm font-semibold mb-1\">\n                {message.role === \"user\" ? \"You\" : \"AI Assistant\"}\n              </p>\n              {message.parts?.map((part, index) => {\n                if (part.type === \"text\") {\n                  return (\n                    <Streamdown\n                      key={index}\n                      isAnimating={status === \"streaming\" && message.role === \"assistant\"}\n                    >\n                      {part.text}\n                    </Streamdown>\n                  );\n                }\n                return null;\n              })}\n            </div>\n          ))\n        )}\n        <div ref={messagesEndRef} />\n      </div>\n\n      <form\n        onSubmit={handleSubmit}\n        className=\"w-full flex items-center space-x-2 pt-2 border-t\"\n      >\n        <Input\n          name=\"prompt\"\n          value={input}\n          onChange={(e) => setInput(e.target.value)}\n          placeholder=\"Type your message...\"\n          className=\"flex-1\"\n          autoComplete=\"off\"\n          autoFocus\n        />\n        <Button type=\"submit\" size=\"icon\">\n          <Send size={18} />\n        </Button>\n      </form>\n    </div>\n  );\n}\n{{/if}}\n`],\n  [\"examples/ai/web/svelte/src/routes/ai/+page.svelte.hbs\", `<script lang=\"ts\">\n\t{{#unless (eq backend \"self\")}}\n\timport { PUBLIC_SERVER_URL } from \"$env/static/public\";\n\t{{/unless}}\n\timport { Chat } from \"@ai-sdk/svelte\";\n\timport { DefaultChatTransport } from \"ai\";\n\n\tlet input = $state(\"\");\n\tconst chat = new Chat({\n\t\ttransport: new DefaultChatTransport({\n\t\t\t{{#if (eq backend \"self\")}}\n\t\t\tapi: \"/api/ai\",\n\t\t\t{{else}}\n\t\t\tapi: \\`\\${PUBLIC_SERVER_URL}/ai\\`,\n\t\t\t{{/if}}\n\t\t}),\n\t});\n\n\tlet messagesEndElement: HTMLDivElement | null = $state(null);\n\n\t$effect(() => {\n\t\tif (chat.messages.length > 0) {\n\t\t\tsetTimeout(() => {\n\t\t\t\tmessagesEndElement?.scrollIntoView({ behavior: \"smooth\" });\n\t\t\t}, 0);\n\t\t}\n\t});\n\n\tfunction handleSubmit(e: Event) {\n\t\te.preventDefault();\n\t\tconst text = input.trim();\n\t\tif (!text) return;\n\t\tchat.sendMessage({ text });\n\t\tinput = \"\";\n\t}\n</script>\n\n<div\n\tclass=\"mx-auto grid h-full w-full max-w-2xl grid-rows-[1fr_auto] overflow-hidden p-4\"\n>\n\t<div class=\"mb-4 space-y-4 overflow-y-auto pb-4\">\n\t\t{#if chat.messages.length === 0}\n\t\t\t<div class=\"mt-8 text-center text-neutral-500\">\n\t\t\t\tAsk me anything to get started!\n\t\t\t</div>\n\t\t{/if}\n\n\t\t{#each chat.messages as message (message.id)}\n\t\t\t<div\n\t\t\t\tclass=\"p-3 rounded-lg w-fit max-w-[85%] text-sm md:text-base\"\n\t\t\t\tclass:ml-auto={message.role === \"user\"}\n\t\t\t\tclass:bg-primary={message.role === \"user\"}\n\t\t\t\tclass:bg-secondary={message.role === \"assistant\"}\n\t\t\t>\n\t\t\t\t<p\n\t\t\t\t\tclass=\"mb-1 text-sm font-semibold\"\n\t\t\t\t\tclass:text-indigo-600={message.role === \"user\"}\n\t\t\t\t\tclass:text-neutral-400={message.role === \"assistant\"}\n\t\t\t\t>\n\t\t\t\t\t{message.role === \"user\" ? \"You\" : \"AI Assistant\"}\n\t\t\t\t</p>\n\t\t\t\t<div class=\"whitespace-pre-wrap break-words\">\n\t\t\t\t\t{#each message.parts as part, partIndex (partIndex)}\n\t\t\t\t\t\t{#if part.type === \"text\"}\n\t\t\t\t\t\t\t{part.text}\n\t\t\t\t\t\t{/if}\n\t\t\t\t\t{/each}\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t{/each}\n\t\t<div bind:this={messagesEndElement}></div>\n\t</div>\n\n\t<form\n\t\tonsubmit={handleSubmit}\n\t\tclass=\"w-full flex items-center space-x-2 pt-2 border-t\"\n\t>\n\t\t<input\n\t\t\tname=\"prompt\"\n\t\t\tbind:value={input}\n\t\t\tplaceholder=\"Type your message...\"\n\t\t\tclass=\"flex-1 rounded border border-neutral-600 bg-neutral-800 px-3 py-2 text-neutral-100 placeholder-neutral-500 focus:border-indigo-500 focus:outline-none focus:ring-1 focus:ring-indigo-500 disabled:opacity-50\"\n\t\t\tautocomplete=\"off\"\n\t\t\tonkeydown={(e) => {\n\t\t\t\tif (e.key === \"Enter\" && !e.shiftKey) {\n\t\t\t\t\te.preventDefault();\n\t\t\t\t\thandleSubmit(e);\n\t\t\t\t}\n\t\t\t}}\n\t\t/>\n\t\t<button\n\t\t\ttype=\"submit\"\n\t\t\tdisabled={!input.trim()}\n\t\t\tclass=\"inline-flex h-10 w-10 items-center justify-center rounded bg-indigo-600 text-white hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 focus:ring-offset-neutral-900 disabled:cursor-not-allowed disabled:opacity-50\"\n\t\t\taria-label=\"Send message\"\n\t\t>\n\t\t\t<svg\n\t\t\t\txmlns=\"http://www.w3.org/2000/svg\"\n\t\t\t\twidth=\"18\"\n\t\t\t\theight=\"18\"\n\t\t\t\tviewBox=\"0 0 24 24\"\n\t\t\t\tfill=\"none\"\n\t\t\t\tstroke=\"currentColor\"\n\t\t\t\tstroke-width=\"2\"\n\t\t\t\tstroke-linecap=\"round\"\n\t\t\t\tstroke-linejoin=\"round\"\n\t\t\t>\n\t\t\t\t<path d=\"m22 2-7 20-4-9-9-4Z\" />\n\t\t\t\t<path d=\"M22 2 11 13\" />\n\t\t\t</svg>\n\t\t</button>\n\t</form>\n</div>\n`],\n  [\"examples/todo/convex/packages/backend/convex/todos.ts.hbs\", `import { query, mutation } from \"./_generated/server\";\nimport { v } from \"convex/values\";\n\nexport const getAll = query({\n    handler: async (ctx) => {\n        return await ctx.db.query(\"todos\").collect();\n    },\n});\n\nexport const create = mutation({\n    args: {\n        text: v.string(),\n    },\n    handler: async (ctx, args) => {\n        const newTodoId = await ctx.db.insert(\"todos\", {\n            text: args.text,\n            completed: false,\n        });\n        return await ctx.db.get(\"todos\", newTodoId);\n    },\n});\n\nexport const toggle = mutation({\n    args: {\n        id: v.id(\"todos\"),\n        completed: v.boolean(),\n    },\n    handler: async (ctx, args) => {\n        await ctx.db.patch(\"todos\", args.id, { completed: args.completed });\n        return { success: true };\n    },\n});\n\nexport const deleteTodo = mutation({\n    args: {\n        id: v.id(\"todos\"),\n    },\n    handler: async (ctx, args) => {\n        await ctx.db.delete(\"todos\", args.id);\n        return { success: true };\n    },\n});`],\n  [\"examples/todo/native/bare/app/(drawer)/todos.tsx.hbs\", `import { useState } from \"react\";\nimport {\n  View,\n  Text,\n  TextInput,\n  ScrollView,\n  ActivityIndicator,\n  Alert,\n  TouchableOpacity,\n  StyleSheet,\n} from \"react-native\";\nimport { Ionicons } from \"@expo/vector-icons\";\n{{#if (eq backend \"convex\")}}\nimport { useMutation, useQuery } from \"convex/react\";\nimport { api } from \"@{{projectName}}/backend/convex/_generated/api\";\nimport type { Id } from \"@{{projectName}}/backend/convex/_generated/dataModel\";\n{{else}}\nimport { useMutation, useQuery } from \"@tanstack/react-query\";\n{{/if}}\nimport { Container } from \"@/components/container\";\nimport { useColorScheme } from \"@/lib/use-color-scheme\";\nimport { NAV_THEME } from \"@/lib/constants\";\n{{#unless (eq backend \"convex\")}}\n  {{#if (eq api \"orpc\")}}\nimport { orpc } from \"@/utils/orpc\";\n  {{/if}}\n  {{#if (eq api \"trpc\")}}\nimport { trpc } from \"@/utils/trpc\";\n  {{/if}}\n{{/unless}}\n\nexport default function TodosScreen() {\n  const { colorScheme } = useColorScheme();\n  const theme = colorScheme === \"dark\" ? NAV_THEME.dark : NAV_THEME.light;\n  const [newTodoText, setNewTodoText] = useState(\"\");\n\n  {{#if (eq backend \"convex\")}}\n  const todos = useQuery(api.todos.getAll);\n  const createTodoMutation = useMutation(api.todos.create);\n  const toggleTodoMutation = useMutation(api.todos.toggle);\n  const deleteTodoMutation = useMutation(api.todos.deleteTodo);\n\n  async function handleAddTodo() {\n    const text = newTodoText.trim();\n    if (!text) return;\n    await createTodoMutation({ text });\n    setNewTodoText(\"\");\n  }\n\n  function handleToggleTodo(id: Id<\"todos\">, currentCompleted: boolean) {\n    toggleTodoMutation({ id, completed: !currentCompleted });\n  }\n\n  function handleDeleteTodo(id: Id<\"todos\">) {\n    Alert.alert(\"Delete Todo\", \"Are you sure you want to delete this todo?\", [\n      { text: \"Cancel\", style: \"cancel\" },\n      {\n        text: \"Delete\",\n        style: \"destructive\",\n        onPress: () => deleteTodoMutation({ id }),\n      },\n    ]);\n  }\n\n  const isLoading = !todos;\n  const completedCount = todos?.filter((t) => t.completed).length || 0;\n  const totalCount = todos?.length || 0;\n  {{else}}\n    {{#if (eq api \"orpc\")}}\n  const todos = useQuery(orpc.todo.getAll.queryOptions());\n  const createMutation = useMutation(\n    orpc.todo.create.mutationOptions({\n      onSuccess: () => {\n        todos.refetch();\n        setNewTodoText(\"\");\n      },\n    })\n  );\n  const toggleMutation = useMutation(\n    orpc.todo.toggle.mutationOptions({\n      onSuccess: () => {\n        todos.refetch();\n      },\n    })\n  );\n  const deleteMutation = useMutation(\n    orpc.todo.delete.mutationOptions({\n      onSuccess: () => {\n        todos.refetch();\n      },\n    })\n  );\n    {{/if}}\n    {{#if (eq api \"trpc\")}}\n  const todos = useQuery(trpc.todo.getAll.queryOptions());\n  const createMutation = useMutation(\n    trpc.todo.create.mutationOptions({\n      onSuccess: () => {\n        todos.refetch();\n        setNewTodoText(\"\");\n      },\n    })\n  );\n  const toggleMutation = useMutation(\n    trpc.todo.toggle.mutationOptions({\n      onSuccess: () => {\n        todos.refetch();\n      },\n    })\n  );\n  const deleteMutation = useMutation(\n    trpc.todo.delete.mutationOptions({\n      onSuccess: () => {\n        todos.refetch();\n      },\n    })\n  );\n    {{/if}}\n\n  function handleAddTodo() {\n    if (newTodoText.trim()) {\n      createMutation.mutate({ text: newTodoText });\n    }\n  }\n\n  function handleToggleTodo(id: number, completed: boolean) {\n    toggleMutation.mutate({ id, completed: !completed });\n  }\n\n  function handleDeleteTodo(id: number) {\n    Alert.alert(\"Delete Todo\", \"Are you sure you want to delete this todo?\", [\n      { text: \"Cancel\", style: \"cancel\" },\n      {\n        text: \"Delete\",\n        style: \"destructive\",\n        onPress: () => deleteMutation.mutate({ id }),\n      },\n    ]);\n  }\n\n  const isLoading = todos?.isLoading;\n  const completedCount = todos?.data?.filter((t) => t.completed).length || 0;\n  const totalCount = todos?.data?.length || 0;\n  {{/if}}\n\n  return (\n    <Container>\n      <ScrollView\n        style={styles.scrollView}\n        contentContainerStyle={styles.contentContainer}\n      >\n        <View style={styles.header}>\n          <View style={styles.headerRow}>\n            <Text style={[styles.title, { color: theme.text }]}>\n              Todo List\n            </Text>\n            {totalCount > 0 && (\n              <View style={[styles.badge, { backgroundColor: theme.primary }]}>\n                <Text style={styles.badgeText}>\n                  {completedCount}/{totalCount}\n                </Text>\n              </View>\n            )}\n          </View>\n        </View>\n        <View\n          style={[\n            styles.inputCard,\n            { backgroundColor: theme.card, borderColor: theme.border },\n          ]}\n        >\n          <View style={styles.inputRow}>\n            <View style={styles.inputContainer}>\n              <TextInput\n                value={newTodoText}\n                onChangeText={setNewTodoText}\n                placeholder=\"Add a new task...\"\n                placeholderTextColor={theme.text}\n                {{#unless (eq backend \"convex\")}}\n                editable={!createMutation.isPending}\n                {{/unless}}\n                onSubmitEditing={handleAddTodo}\n                returnKeyType=\"done\"\n                style={[\n                  styles.input,\n                  {\n                    color: theme.text,\n                    borderColor: theme.border,\n                    backgroundColor: theme.background,\n                  },\n                ]}\n              />\n            </View>\n            <TouchableOpacity\n              onPress={handleAddTodo}\n              {{#if (eq backend \"convex\")}}\n              disabled={!newTodoText.trim()}\n              style={[\n                styles.addButton,\n                {\n                  backgroundColor: !newTodoText.trim()\n                    ? theme.border\n                    : theme.primary,\n                  opacity: !newTodoText.trim() ? 0.5 : 1,\n                },\n              ]}\n            >\n              <Ionicons\n                name=\"add\"\n                size={24}\n                color={newTodoText.trim() ? \"#ffffff\" : theme.text}\n              />\n              {{else}}\n              disabled={createMutation.isPending || !newTodoText.trim()}\n              style={[\n                styles.addButton,\n                {\n                  backgroundColor:\n                    createMutation.isPending || !newTodoText.trim()\n                      ? theme.border\n                      : theme.primary,\n                  opacity:\n                    createMutation.isPending || !newTodoText.trim() ? 0.5 : 1,\n                },\n              ]}\n            >\n              {createMutation.isPending ? (\n                <ActivityIndicator size=\"small\" color=\"#ffffff\" />\n              ) : (\n                <Ionicons name=\"add\" size={24} color=\"#ffffff\" />\n              )}\n              {{/if}}\n            </TouchableOpacity>\n          </View>\n        </View>\n\n        {{#if (eq backend \"convex\")}}\n        {isLoading && (\n          <View style={styles.centerContainer}>\n            <ActivityIndicator size=\"large\" color={theme.primary} />\n            <Text\n              style={[styles.loadingText, { color: theme.text, opacity: 0.7 }]}\n            >\n              Loading todos...\n            </Text>\n          </View>\n        )}\n\n        {todos && todos.length === 0 && !isLoading && (\n          <View\n            style={[\n              styles.emptyCard,\n              { backgroundColor: theme.card, borderColor: theme.border },\n            ]}\n          >\n            <Ionicons\n              name=\"checkbox-outline\"\n              size={64}\n              color={theme.text}\n              style=\\\\{{ opacity: 0.5, marginBottom: 16 }}\n            />\n            <Text style={[styles.emptyTitle, { color: theme.text }]}>\n              No todos yet\n            </Text>\n            <Text\n              style={[styles.emptyText, { color: theme.text, opacity: 0.7 }]}\n            >\n              Add your first task to get started!\n            </Text>\n          </View>\n        )}\n\n        {todos && todos.length > 0 && (\n          <View style={styles.todosList}>\n            {todos.map((todo) => (\n              <View\n                key={todo._id}\n                style={[\n                  styles.todoCard,\n                  { backgroundColor: theme.card, borderColor: theme.border },\n                ]}\n              >\n                <View style={styles.todoRow}>\n                  <TouchableOpacity\n                    onPress={() => handleToggleTodo(todo._id, todo.completed)}\n                    style={[styles.checkbox, { borderColor: theme.border }]}\n                  >\n                    {todo.completed && (\n                      <Ionicons\n                        name=\"checkmark\"\n                        size={16}\n                        color={theme.primary}\n                      />\n                    )}\n                  </TouchableOpacity>\n                  <View style={styles.todoTextContainer}>\n                    <Text\n                      style={[\n                        styles.todoText,\n                        { color: theme.text },\n                        todo.completed && {\n                          textDecorationLine: \"line-through\",\n                          opacity: 0.5,\n                        },\n                      ]}\n                    >\n                      {todo.text}\n                    </Text>\n                  </View>\n                  <TouchableOpacity\n                    onPress={() => handleDeleteTodo(todo._id)}\n                    style={styles.deleteButton}\n                  >\n                    <Ionicons\n                      name=\"trash-outline\"\n                      size={24}\n                      color={theme.notification}\n                    />\n                  </TouchableOpacity>\n                </View>\n              </View>\n            ))}\n          </View>\n        )}\n        {{else}}\n        {isLoading && (\n          <View style={styles.centerContainer}>\n            <ActivityIndicator size=\"large\" color={theme.primary} />\n            <Text\n              style={[styles.loadingText, { color: theme.text, opacity: 0.7 }]}\n            >\n              Loading todos...\n            </Text>\n          </View>\n        )}\n\n        {todos?.data && todos.data.length === 0 && !isLoading && (\n          <View\n            style={[\n              styles.emptyCard,\n              { backgroundColor: theme.card, borderColor: theme.border },\n            ]}\n          >\n            <Ionicons\n              name=\"checkbox-outline\"\n              size={64}\n              color={theme.text}\n              style=\\\\{{ opacity: 0.5, marginBottom: 16 }}\n            />\n            <Text style={[styles.emptyTitle, { color: theme.text }]}>\n              No todos yet\n            </Text>\n            <Text\n              style={[styles.emptyText, { color: theme.text, opacity: 0.7 }]}\n            >\n              Add your first task to get started!\n            </Text>\n          </View>\n        )}\n\n        {todos?.data && todos.data.length > 0 && (\n          <View style={styles.todosList}>\n            {todos.data.map((todo) => (\n              <View\n                key={todo.id}\n                style={[\n                  styles.todoCard,\n                  { backgroundColor: theme.card, borderColor: theme.border },\n                ]}\n              >\n                <View style={styles.todoRow}>\n                  <TouchableOpacity\n                    onPress={() => handleToggleTodo(todo.id, todo.completed)}\n                    style={[styles.checkbox, { borderColor: theme.border }]}\n                  >\n                    {todo.completed && (\n                      <Ionicons\n                        name=\"checkmark\"\n                        size={16}\n                        color={theme.primary}\n                      />\n                    )}\n                  </TouchableOpacity>\n                  <View style={styles.todoTextContainer}>\n                    <Text\n                      style={[\n                        styles.todoText,\n                        { color: theme.text },\n                        todo.completed && {\n                          textDecorationLine: \"line-through\",\n                          opacity: 0.5,\n                        },\n                      ]}\n                    >\n                      {todo.text}\n                    </Text>\n                  </View>\n                  <TouchableOpacity\n                    onPress={() => handleDeleteTodo(todo.id)}\n                    style={styles.deleteButton}\n                  >\n                    <Ionicons\n                      name=\"trash-outline\"\n                      size={24}\n                      color={theme.notification}\n                    />\n                  </TouchableOpacity>\n                </View>\n              </View>\n            ))}\n          </View>\n        )}\n        {{/if}}\n      </ScrollView>\n    </Container>\n  );\n}\n\nconst styles = StyleSheet.create({\n  scrollView: {\n    flex: 1,\n  },\n  contentContainer: {\n    padding: 16,\n  },\n  header: {\n    marginBottom: 16,\n  },\n  headerRow: {\n    flexDirection: \"row\",\n    alignItems: \"center\",\n    justifyContent: \"space-between\",\n  },\n  title: {\n    fontSize: 24,\n    fontWeight: \"bold\",\n  },\n  badge: {\n    paddingHorizontal: 8,\n    paddingVertical: 4,\n  },\n  badgeText: {\n    color: \"#ffffff\",\n    fontSize: 12,\n  },\n  inputCard: {\n    borderWidth: 1,\n    padding: 12,\n    marginBottom: 16,\n  },\n  inputRow: {\n    flexDirection: \"row\",\n    alignItems: \"center\",\n    gap: 8,\n  },\n  inputContainer: {\n    flex: 1,\n  },\n  input: {\n    borderWidth: 1,\n    padding: 12,\n    fontSize: 16,\n  },\n  addButton: {\n    padding: 12,\n    justifyContent: \"center\",\n    alignItems: \"center\",\n  },\n  centerContainer: {\n    alignItems: \"center\",\n    justifyContent: \"center\",\n    paddingVertical: 32,\n  },\n  loadingText: {\n    marginTop: 16,\n    fontSize: 14,\n  },\n  emptyCard: {\n    borderWidth: 1,\n    padding: 32,\n    alignItems: \"center\",\n    justifyContent: \"center\",\n  },\n  emptyTitle: {\n    fontSize: 16,\n    fontWeight: \"bold\",\n    marginBottom: 8,\n  },\n  emptyText: {\n    fontSize: 14,\n    textAlign: \"center\",\n  },\n  todosList: {\n    gap: 8,\n  },\n  todoCard: {\n    borderWidth: 1,\n    padding: 12,\n  },\n  todoRow: {\n    flexDirection: \"row\",\n    alignItems: \"center\",\n    gap: 12,\n  },\n  checkbox: {\n    width: 20,\n    height: 20,\n    borderWidth: 2,\n    justifyContent: \"center\",\n    alignItems: \"center\",\n  },\n  todoTextContainer: {\n    flex: 1,\n  },\n  todoText: {\n    fontSize: 16,\n  },\n  deleteButton: {\n    padding: 8,\n  },\n});`],\n  [\"examples/todo/native/unistyles/app/(drawer)/todos.tsx.hbs\", `import { useState } from \"react\";\nimport {\n  View,\n  Text,\n  TextInput,\n  TouchableOpacity,\n  ScrollView,\n  ActivityIndicator,\n  Alert,\n} from \"react-native\";\nimport { Ionicons } from \"@expo/vector-icons\";\nimport { StyleSheet, useUnistyles } from \"react-native-unistyles\";\n\n{{#if (eq backend \"convex\")}}\nimport { useMutation, useQuery } from \"convex/react\";\nimport { api } from \"@{{projectName}}/backend/convex/_generated/api\";\nimport type { Id } from \"@{{projectName}}/backend/convex/_generated/dataModel\";\n{{else}}\nimport { useMutation, useQuery } from \"@tanstack/react-query\";\n{{/if}}\n\nimport { Container } from \"@/components/container\";\n{{#unless (eq backend \"convex\")}}\n{{#if (eq api \"orpc\")}}\nimport { orpc } from \"@/utils/orpc\";\n{{/if}}\n{{#if (eq api \"trpc\")}}\nimport { trpc } from \"@/utils/trpc\";\n{{/if}}\n{{/unless}}\n\nexport default function TodosScreen() {\n  const [newTodoText, setNewTodoText] = useState(\"\");\n  const { theme } = useUnistyles();\n\n  {{#if (eq backend \"convex\")}}\n  const todos = useQuery(api.todos.getAll);\n  const createTodoMutation = useMutation(api.todos.create);\n  const toggleTodoMutation = useMutation(api.todos.toggle);\n  const deleteTodoMutation = useMutation(api.todos.deleteTodo);\n\n  const handleAddTodo = async () => {\n    const text = newTodoText.trim();\n    if (!text) return;\n    await createTodoMutation({ text });\n    setNewTodoText(\"\");\n  };\n\n  const handleToggleTodo = (id: Id<\"todos\">, currentCompleted: boolean) => {\n    toggleTodoMutation({ id, completed: !currentCompleted });\n  };\n\n  const handleDeleteTodo = (id: Id<\"todos\">) => {\n    Alert.alert(\"Delete Todo\", \"Are you sure you want to delete this todo?\", [\n      { text: \"Cancel\", style: \"cancel\" },\n      {\n        text: \"Delete\",\n        style: \"destructive\",\n        onPress: () => deleteTodoMutation({ id }),\n      },\n    ]);\n  };\n  {{else}}\n    {{#if (eq api \"orpc\")}}\n    const todos = useQuery(orpc.todo.getAll.queryOptions());\n    const createMutation = useMutation(\n      orpc.todo.create.mutationOptions({\n        onSuccess: () => {\n          todos.refetch();\n          setNewTodoText(\"\");\n        },\n      })\n    );\n    const toggleMutation = useMutation(\n      orpc.todo.toggle.mutationOptions({\n        onSuccess: () => { todos.refetch() },\n      })\n    );\n    const deleteMutation = useMutation(\n      orpc.todo.delete.mutationOptions({\n        onSuccess: () => { todos.refetch() },\n      })\n    );\n    {{/if}}\n    {{#if (eq api \"trpc\")}}\n    const todos = useQuery(trpc.todo.getAll.queryOptions());\n    const createMutation = useMutation(\n      trpc.todo.create.mutationOptions({\n        onSuccess: () => {\n          todos.refetch();\n          setNewTodoText(\"\");\n        },\n      })\n    );\n    const toggleMutation = useMutation(\n      trpc.todo.toggle.mutationOptions({\n        onSuccess: () => { todos.refetch() },\n      })\n    );\n    const deleteMutation = useMutation(\n      trpc.todo.delete.mutationOptions({\n        onSuccess: () => { todos.refetch() },\n      })\n    );\n    {{/if}}\n\n  const handleAddTodo = () => {\n    if (newTodoText.trim()) {\n      createMutation.mutate({ text: newTodoText });\n    }\n  };\n\n  const handleToggleTodo = (id: number, completed: boolean) => {\n    toggleMutation.mutate({ id, completed: !completed });\n  };\n\n  const handleDeleteTodo = (id: number) => {\n    Alert.alert(\"Delete Todo\", \"Are you sure you want to delete this todo?\", [\n      { text: \"Cancel\", style: \"cancel\" },\n      {\n        text: \"Delete\",\n        style: \"destructive\",\n        onPress: () => deleteMutation.mutate({ id }),\n      },\n    ]);\n  };\n  {{/if}}\n\n  const isLoading = {{#if (eq backend \"convex\")}}!todos{{else}}todos.isLoading{{/if}};\n  const isCreating = {{#if (eq backend \"convex\")}}false{{else}}createMutation.isPending{{/if}};\n  const primaryButtonTextColor = theme.colors.background;\n\n  return (\n    <Container>\n      <ScrollView style={styles.scrollView}>\n        <View style={styles.headerContainer}>\n          <Text style={styles.headerTitle}>Todo List</Text>\n          <Text style={styles.headerSubtitle}>\n            Manage your tasks efficiently\n          </Text>\n\n          <View style={styles.inputContainer}>\n            <TextInput\n              value={newTodoText}\n              onChangeText={setNewTodoText}\n              placeholder=\"Add a new task...\"\n              placeholderTextColor={theme.colors.border}\n              editable={!isCreating}\n              style={styles.textInput}\n              onSubmitEditing={handleAddTodo}\n              returnKeyType=\"done\"\n            />\n            <TouchableOpacity\n              onPress={handleAddTodo}\n              disabled={isCreating || !newTodoText.trim()}\n              style={[\n                styles.addButton,\n                (isCreating || !newTodoText.trim()) && styles.addButtonDisabled,\n              ]}\n            >\n              {isCreating ? (\n                <ActivityIndicator size=\"small\" color={primaryButtonTextColor} />\n              ) : (\n                <Ionicons\n                  name=\"add\"\n                  size={24}\n                  color={primaryButtonTextColor}\n                />\n              )}\n            </TouchableOpacity>\n          </View>\n        </View>\n\n        {isLoading && (\n          <View style={styles.loadingContainer}>\n            <ActivityIndicator size=\"large\" color={theme.colors.primary} />\n            <Text style={styles.loadingText}>Loading todos...</Text>\n          </View>\n        )}\n\n        {{#if (eq backend \"convex\")}}\n          {todos && todos.length === 0 && !isLoading && (\n            <Text style={styles.emptyText}>No todos yet. Add one!</Text>\n          )}\n          {todos?.map((todo) => (\n            <View key={todo._id} style={styles.todoItem}>\n              <TouchableOpacity\n                onPress={() => handleToggleTodo(todo._id, todo.completed)}\n                style={styles.todoContent}\n              >\n                <Ionicons\n                  name={todo.completed ? \"checkbox\" : \"square-outline\"}\n                  size={24}\n                  color={todo.completed ? theme.colors.primary : theme.colors.typography}\n                  style={styles.checkbox}\n                />\n                <Text\n                  style={[\n                    styles.todoText,\n                    todo.completed && styles.todoTextCompleted,\n                  ]}\n                >\n                  {todo.text}\n                </Text>\n              </TouchableOpacity>\n              <TouchableOpacity onPress={() => handleDeleteTodo(todo._id)}>\n                <Ionicons name=\"trash-outline\" size={24} color={theme.colors.destructive} />\n              </TouchableOpacity>\n            </View>\n          ))}\n        {{else}}\n          {todos.data && todos.data.length === 0 && !isLoading && (\n             <Text style={styles.emptyText}>No todos yet. Add one!</Text>\n          )}\n          {todos.data?.map((todo: { id: number; text: string; completed: boolean }) => (\n            <View key={todo.id} style={styles.todoItem}>\n              <TouchableOpacity\n                onPress={() => handleToggleTodo(todo.id, todo.completed)}\n                style={styles.todoContent}\n              >\n                <Ionicons\n                  name={todo.completed ? \"checkbox\" : \"square-outline\"}\n                  size={24}\n                  color={todo.completed ? theme.colors.primary : theme.colors.typography}\n                  style={styles.checkbox}\n                />\n                <Text\n                  style={[\n                    styles.todoText,\n                    todo.completed && styles.todoTextCompleted,\n                  ]}\n                >\n                  {todo.text}\n                </Text>\n              </TouchableOpacity>\n              <TouchableOpacity onPress={() => handleDeleteTodo(todo.id)}>\n                <Ionicons name=\"trash-outline\" size={24} color={theme.colors.destructive} />\n              </TouchableOpacity>\n            </View>\n          ))}\n        {{/if}}\n      </ScrollView>\n    </Container>\n  );\n}\n\nconst styles = StyleSheet.create((theme) => ({\n  scrollView: {\n    flex: 1,\n  },\n  headerContainer: {\n    paddingHorizontal: theme.spacing.md,\n    paddingVertical: theme.spacing.lg,\n    borderBottomWidth: 1,\n    borderBottomColor: theme.colors.border,\n    backgroundColor: theme.colors.background,\n  },\n  headerTitle: {\n    fontSize: 28,\n    fontWeight: \"bold\",\n    color: theme.colors.typography,\n    marginBottom: theme.spacing.sm,\n  },\n  headerSubtitle: {\n    fontSize: 16,\n    color: theme.colors.typography,\n    marginBottom: theme.spacing.md,\n  },\n  inputContainer: {\n    flexDirection: \"row\",\n    alignItems: \"center\",\n    marginBottom: theme.spacing.md,\n  },\n  textInput: {\n    flex: 1,\n    borderWidth: 1,\n    borderColor: theme.colors.border,\n    borderRadius: 8,\n    paddingHorizontal: theme.spacing.md,\n    paddingVertical: theme.spacing.sm,\n    color: theme.colors.typography,\n    backgroundColor: theme.colors.background,\n    marginRight: theme.spacing.sm,\n    fontSize: 16,\n  },\n  addButton: {\n    backgroundColor: theme.colors.primary,\n    padding: theme.spacing.sm,\n    borderRadius: 8,\n    justifyContent: \"center\",\n    alignItems: \"center\",\n  },\n  addButtonDisabled: {\n    backgroundColor: theme.colors.border,\n  },\n  loadingContainer: {\n    flex: 1,\n    justifyContent: \"center\",\n    alignItems: \"center\",\n    padding: theme.spacing.lg,\n  },\n  loadingText: {\n    marginTop: theme.spacing.sm,\n    fontSize: 16,\n    color: theme.colors.typography,\n  },\n  emptyText: {\n    textAlign: \"center\",\n    marginTop: theme.spacing.xl,\n    fontSize: 16,\n    color: theme.colors.typography,\n  },\n  todoItem: {\n    flexDirection: \"row\",\n    justifyContent: \"space-between\",\n    alignItems: \"center\",\n    paddingVertical: theme.spacing.md,\n    paddingHorizontal: theme.spacing.md,\n    borderBottomWidth: 1,\n    borderBottomColor: theme.colors.border,\n    backgroundColor: theme.colors.background,\n  },\n  todoContent: {\n    flexDirection: \"row\",\n    alignItems: \"center\",\n    flex: 1,\n  },\n  checkbox: {\n    marginRight: theme.spacing.md,\n  },\n  todoText: {\n    fontSize: 16,\n    color: theme.colors.typography,\n    flex: 1,\n  },\n  todoTextCompleted: {\n    textDecorationLine: \"line-through\",\n    color: theme.colors.border,\n  },\n}));\n`],\n  [\"examples/todo/native/uniwind/app/(drawer)/todos.tsx.hbs\", `import { useState } from \"react\";\nimport { View, Text, ScrollView, Alert } from \"react-native\";\nimport { Ionicons } from \"@expo/vector-icons\";\n{{#if (eq backend \"convex\")}}\nimport { useMutation, useQuery } from \"convex/react\";\nimport { api } from \"@{{projectName}}/backend/convex/_generated/api\";\nimport type { Id } from \"@{{projectName}}/backend/convex/_generated/dataModel\";\n{{else}}\nimport { useMutation, useQuery } from \"@tanstack/react-query\";\n{{/if}}\nimport { Container } from \"@/components/container\";\n{{#unless (eq backend \"convex\")}}\n  {{#if (eq api \"orpc\")}}\n    import { orpc } from \"@/utils/orpc\";\n  {{/if}}\n  {{#if (eq api \"trpc\")}}\n    import { trpc } from \"@/utils/trpc\";\n  {{/if}}\n{{/unless}}\nimport { Button, Checkbox, Chip, Spinner, Surface, Input, TextField, useThemeColor } from \"heroui-native\";\n\nexport default function TodosScreen() {\n  const [newTodoText, setNewTodoText] = useState(\"\");\n  {{#if (eq backend \"convex\")}}\n    const todos = useQuery(api.todos.getAll);\n    const createTodoMutation = useMutation(api.todos.create);\n    const toggleTodoMutation = useMutation(api.todos.toggle);\n    const deleteTodoMutation = useMutation(api.todos.deleteTodo);\n  {{else}}\n    {{#if (eq api \"orpc\")}}\n      const todos = useQuery(orpc.todo.getAll.queryOptions());\n      const createMutation = useMutation(orpc.todo.create.mutationOptions({\n        onSuccess: () => {\n          todos.refetch();\n          setNewTodoText(\"\");\n        },\n      }));\n      const toggleMutation = useMutation(orpc.todo.toggle.mutationOptions({\n        onSuccess: () => {\n          todos.refetch();\n        },\n      }));\n      const deleteMutation = useMutation(orpc.todo.delete.mutationOptions({\n        onSuccess: () => {\n          todos.refetch();\n        },\n      }));\n    {{/if}}\n    {{#if (eq api \"trpc\")}}\n      const todos = useQuery(trpc.todo.getAll.queryOptions());\n      const createMutation = useMutation(trpc.todo.create.mutationOptions({\n        onSuccess: () => {\n          todos.refetch();\n          setNewTodoText(\"\");\n        },\n      }));\n      const toggleMutation = useMutation(trpc.todo.toggle.mutationOptions({\n        onSuccess: () => {\n          todos.refetch();\n        },\n      }));\n      const deleteMutation = useMutation(trpc.todo.delete.mutationOptions({\n        onSuccess: () => {\n          todos.refetch();\n        },\n      }));\n    {{/if}}\n  {{/if}}\n\n  const mutedColor = useThemeColor(\"muted\");\n  const dangerColor = useThemeColor(\"danger\");\n  const foregroundColor = useThemeColor(\"foreground\");\n\n  {{#if (eq backend \"convex\")}}\n    const handleAddTodo = async () => {\n      const text = newTodoText.trim();\n      if (!text) return;\n      await createTodoMutation({ text });\n      setNewTodoText(\"\");\n    };\n\n    const handleToggleTodo = (id: Id<\"todos\">, currentCompleted: boolean) => {\n      toggleTodoMutation({ id, completed: !currentCompleted });\n    };\n\n    const handleDeleteTodo = (id: Id<\"todos\">) => {\n      Alert.alert(\"Delete Todo\", \"Are you sure you want to delete this todo?\", [\n        { text: \"Cancel\", style: \"cancel\" },\n        {\n          text: \"Delete\",\n          style: \"destructive\",\n          onPress: () => deleteTodoMutation({ id }),\n        },\n      ]);\n    };\n\n    const isLoading = !todos;\n    const completedCount = todos?.filter((t) => t.completed).length || 0;\n    const totalCount = todos?.length || 0;\n  {{else}}\n    const handleAddTodo = () => {\n      if (newTodoText.trim()) {\n        createMutation.mutate({ text: newTodoText });\n      }\n    };\n\n    const handleToggleTodo = (id: number, completed: boolean) => {\n      toggleMutation.mutate({ id, completed: !completed });\n    };\n\n    const handleDeleteTodo = (id: number) => {\n      Alert.alert(\"Delete Todo\", \"Are you sure you want to delete this todo?\", [\n        { text: \"Cancel\", style: \"cancel\" },\n        {\n          text: \"Delete\",\n          style: \"destructive\",\n          onPress: () => deleteMutation.mutate({ id }),\n        },\n      ]);\n    };\n\n    const isLoading = todos?.isLoading;\n    const completedCount = todos?.data?.filter((t) => t.completed).length || 0;\n    const totalCount = todos?.data?.length || 0;\n  {{/if}}\n\n  return (\n    <Container>\n      <ScrollView className=\"flex-1\" contentContainerClassName=\"p-4\">\n        <View className=\"py-4 mb-4\">\n          <View className=\"flex-row items-center justify-between\">\n            <Text className=\"text-2xl font-semibold text-foreground tracking-tight\">Tasks</Text>\n            {totalCount > 0 && (\n              <Chip variant=\"secondary\" color=\"accent\" size=\"sm\">\n                <Chip.Label>\n                  {completedCount}/{totalCount}\n                </Chip.Label>\n              </Chip>\n            )}\n          </View>\n        </View>\n\n        <Surface variant=\"secondary\" className=\"mb-4 p-3 rounded-lg\">\n          <View className=\"flex-row items-center gap-2\">\n            <View className=\"flex-1\">\n              <TextField>\n                <Input\n                  value={newTodoText}\n                  onChangeText={setNewTodoText}\n                  placeholder=\"Add a new task...\"\n                  {{#unless (eq backend \"convex\")}}\n                    editable={!createMutation.isPending}\n                  {{/unless}}\n                  onSubmitEditing={handleAddTodo}\n                  returnKeyType=\"done\"\n                />\n              </TextField>\n            </View>\n            <Button\n              isIconOnly\n              {{#if (eq backend \"convex\")}}\n                variant={!newTodoText.trim() ? \"secondary\" : \"primary\"}\n                isDisabled={!newTodoText.trim()}\n              {{else}}\n                variant={createMutation.isPending || !newTodoText.trim() ? \"secondary\" : \"primary\"}\n                isDisabled={createMutation.isPending || !newTodoText.trim()}\n              {{/if}}\n              onPress={handleAddTodo}\n              size=\"sm\"\n            >\n              {{#if (eq backend \"convex\")}}\n                <Ionicons\n                  name=\"add\"\n                  size={20}\n                  color={newTodoText.trim() ? foregroundColor : mutedColor}\n                />\n              {{else}}\n                {createMutation.isPending ? (\n                  <Spinner size=\"sm\" color=\"default\" />\n                ) : (\n                  <Ionicons\n                    name=\"add\"\n                    size={20}\n                    color={(createMutation.isPending || !newTodoText.trim()) ? mutedColor : foregroundColor}\n                  />\n                )}\n              {{/if}}\n            </Button>\n          </View>\n        </Surface>\n\n        {{#if (eq backend \"convex\")}}\n          {isLoading && (\n            <View className=\"items-center justify-center py-12\">\n              <Spinner size=\"lg\" />\n              <Text className=\"text-muted text-sm mt-3\">Loading tasks...</Text>\n            </View>\n          )}\n\n          {todos && todos.length === 0 && !isLoading && (\n            <Surface variant=\"secondary\" className=\"items-center justify-center py-10 rounded-lg\">\n              <Ionicons name=\"checkbox-outline\" size={40} color={mutedColor} />\n              <Text className=\"text-foreground font-medium mt-3\">No tasks yet</Text>\n              <Text className=\"text-muted text-xs mt-1\">Add your first task to get started</Text>\n            </Surface>\n          )}\n\n          {todos && todos.length > 0 && (\n            <View className=\"gap-2\">\n              {todos.map((todo) => (\n                <Surface key={todo._id} variant=\"secondary\" className=\"p-3 rounded-lg\">\n                  <View className=\"flex-row items-center gap-3\">\n                    <Checkbox\n                      isSelected={todo.completed}\n                      onSelectedChange={() => handleToggleTodo(todo._id, todo.completed)}\n                    />\n                    <View className=\"flex-1\">\n                      <Text className={\\`text-sm \\${todo.completed ? \"text-muted line-through\" : \"text-foreground\"}\\`}>\n                        {todo.text}\n                      </Text>\n                    </View>\n                    <Button\n                      isIconOnly\n                      variant=\"ghost\"\n                      onPress={() => handleDeleteTodo(todo._id)}\n                      size=\"sm\"\n                    >\n                      <Ionicons name=\"trash-outline\" size={16} color={dangerColor} />\n                    </Button>\n                  </View>\n                </Surface>\n              ))}\n            </View>\n          )}\n        {{else}}\n          {isLoading && (\n            <View className=\"items-center justify-center py-12\">\n              <Spinner size=\"lg\" />\n              <Text className=\"text-muted text-sm mt-3\">Loading tasks...</Text>\n            </View>\n          )}\n\n          {todos?.data && todos.data.length === 0 && !isLoading && (\n            <Surface variant=\"secondary\" className=\"items-center justify-center py-10 rounded-lg\">\n              <Ionicons name=\"checkbox-outline\" size={40} color={mutedColor} />\n              <Text className=\"text-foreground font-medium mt-3\">No tasks yet</Text>\n              <Text className=\"text-muted text-xs mt-1\">Add your first task to get started</Text>\n            </Surface>\n          )}\n\n          {todos?.data && todos.data.length > 0 && (\n            <View className=\"gap-2\">\n              {todos.data.map((todo) => (\n                <Surface key={todo.id} variant=\"secondary\" className=\"p-3 rounded-lg\">\n                  <View className=\"flex-row items-center gap-3\">\n                    <Checkbox\n                      isSelected={todo.completed}\n                      onSelectedChange={() => handleToggleTodo(todo.id, todo.completed)}\n                    />\n                    <View className=\"flex-1\">\n                      <Text className={\\`text-sm \\${todo.completed ? \"text-muted line-through\" : \"text-foreground\"}\\`}>\n                        {todo.text}\n                      </Text>\n                    </View>\n                    <Button\n                      isIconOnly\n                      variant=\"ghost\"\n                      onPress={() => handleDeleteTodo(todo.id)}\n                      size=\"sm\"\n                    >\n                      <Ionicons name=\"trash-outline\" size={16} color={dangerColor} />\n                    </Button>\n                  </View>\n                </Surface>\n              ))}\n            </View>\n          )}\n        {{/if}}\n      </ScrollView>\n    </Container>\n  );\n}`],\n  [\"examples/todo/server/drizzle/base/src/routers/todo.ts.hbs\", `{{#if (eq api \"orpc\")}}\nimport { eq } from \"drizzle-orm\";\nimport z from \"zod\";\n{{#if (or (eq runtime \"workers\") (eq serverDeploy \"cloudflare\") (and (eq backend \"self\") (eq webDeploy \"cloudflare\")))}}\nimport { createDb } from \"@{{projectName}}/db\";\n{{else}}\nimport { db } from \"@{{projectName}}/db\";\n{{/if}}\nimport { todo } from \"@{{projectName}}/db/schema/todo\";\nimport { publicProcedure } from \"../index\";\n\nexport const todoRouter = {\n  getAll: publicProcedure.handler(async ({{#if (and (eq backend \"self\") (eq webDeploy \"cloudflare\") (includes frontend \"svelte\"))}}{ context }{{/if}}) => {\n{{#if (or (eq runtime \"workers\") (eq serverDeploy \"cloudflare\") (and (eq backend \"self\") (eq webDeploy \"cloudflare\")))}}\n    const db = createDb({{#if (and (eq backend \"self\") (eq webDeploy \"cloudflare\") (includes frontend \"svelte\"))}}context.env{{/if}});\n{{/if}}\n    return await db.select().from(todo);\n  }),\n\n  create: publicProcedure\n    .input(z.object({ text: z.string().min(1) }))\n    .handler(async ({ input{{#if (and (eq backend \"self\") (eq webDeploy \"cloudflare\") (includes frontend \"svelte\"))}}, context{{/if}} }) => {\n{{#if (or (eq runtime \"workers\") (eq serverDeploy \"cloudflare\") (and (eq backend \"self\") (eq webDeploy \"cloudflare\")))}}\n      const db = createDb({{#if (and (eq backend \"self\") (eq webDeploy \"cloudflare\") (includes frontend \"svelte\"))}}context.env{{/if}});\n{{/if}}\n      return await db\n        .insert(todo)\n        .values({\n          text: input.text,\n        });\n    }),\n\n  toggle: publicProcedure\n    .input(z.object({ id: z.number(), completed: z.boolean() }))\n    .handler(async ({ input{{#if (and (eq backend \"self\") (eq webDeploy \"cloudflare\") (includes frontend \"svelte\"))}}, context{{/if}} }) => {\n{{#if (or (eq runtime \"workers\") (eq serverDeploy \"cloudflare\") (and (eq backend \"self\") (eq webDeploy \"cloudflare\")))}}\n      const db = createDb({{#if (and (eq backend \"self\") (eq webDeploy \"cloudflare\") (includes frontend \"svelte\"))}}context.env{{/if}});\n{{/if}}\n      return await db\n        .update(todo)\n        .set({ completed: input.completed })\n        .where(eq(todo.id, input.id));\n    }),\n\n  delete: publicProcedure\n    .input(z.object({ id: z.number() }))\n    .handler(async ({ input{{#if (and (eq backend \"self\") (eq webDeploy \"cloudflare\") (includes frontend \"svelte\"))}}, context{{/if}} }) => {\n{{#if (or (eq runtime \"workers\") (eq serverDeploy \"cloudflare\") (and (eq backend \"self\") (eq webDeploy \"cloudflare\")))}}\n      const db = createDb({{#if (and (eq backend \"self\") (eq webDeploy \"cloudflare\") (includes frontend \"svelte\"))}}context.env{{/if}});\n{{/if}}\n      return await db.delete(todo).where(eq(todo.id, input.id));\n    }),\n};\n{{/if}}\n\n{{#if (eq api \"trpc\")}}\nimport z from \"zod\";\nimport { router, publicProcedure } from \"../index\";\nimport { todo } from \"@{{projectName}}/db/schema/todo\";\nimport { eq } from \"drizzle-orm\";\n{{#if (or (eq runtime \"workers\") (eq serverDeploy \"cloudflare\") (and (eq backend \"self\") (eq webDeploy \"cloudflare\")))}}\nimport { createDb } from \"@{{projectName}}/db\";\n{{else}}\nimport { db } from \"@{{projectName}}/db\";\n{{/if}}\n\nexport const todoRouter = router({\n  getAll: publicProcedure.query(async () => {\n{{#if (or (eq runtime \"workers\") (eq serverDeploy \"cloudflare\") (and (eq backend \"self\") (eq webDeploy \"cloudflare\")))}}\n    const db = createDb();\n{{/if}}\n    return await db.select().from(todo);\n  }),\n\n  create: publicProcedure\n    .input(z.object({ text: z.string().min(1) }))\n    .mutation(async ({ input }) => {\n{{#if (or (eq runtime \"workers\") (eq serverDeploy \"cloudflare\") (and (eq backend \"self\") (eq webDeploy \"cloudflare\")))}}\n      const db = createDb();\n{{/if}}\n      return await db.insert(todo).values({\n        text: input.text,\n      });\n    }),\n\n  toggle: publicProcedure\n    .input(z.object({ id: z.number(), completed: z.boolean() }))\n    .mutation(async ({ input }) => {\n{{#if (or (eq runtime \"workers\") (eq serverDeploy \"cloudflare\") (and (eq backend \"self\") (eq webDeploy \"cloudflare\")))}}\n      const db = createDb();\n{{/if}}\n      return await db\n        .update(todo)\n        .set({ completed: input.completed })\n        .where(eq(todo.id, input.id));\n    }),\n\n  delete: publicProcedure\n    .input(z.object({ id: z.number() }))\n    .mutation(async ({ input }) => {\n{{#if (or (eq runtime \"workers\") (eq serverDeploy \"cloudflare\") (and (eq backend \"self\") (eq webDeploy \"cloudflare\")))}}\n      const db = createDb();\n{{/if}}\n      return await db.delete(todo).where(eq(todo.id, input.id));\n    }),\n});\n{{/if}}\n`],\n  [\"examples/todo/server/drizzle/mysql/src/schema/todo.ts\", `import { mysqlTable, varchar, int, boolean } from \"drizzle-orm/mysql-core\";\n\nexport const todo = mysqlTable(\"todo\", {\n  id: int(\"id\").primaryKey().autoincrement(),\n  text: varchar(\"text\", { length: 255 }).notNull(),\n  completed: boolean(\"completed\").default(false).notNull(),\n});\n`],\n  [\"examples/todo/server/drizzle/postgres/src/schema/todo.ts\", `import { pgTable, text, boolean, serial } from \"drizzle-orm/pg-core\";\n\nexport const todo = pgTable(\"todo\", {\n  id: serial(\"id\").primaryKey(),\n  text: text(\"text\").notNull(),\n  completed: boolean(\"completed\").default(false).notNull(),\n});\n`],\n  [\"examples/todo/server/drizzle/sqlite/src/schema/todo.ts\", `import { integer, sqliteTable, text } from \"drizzle-orm/sqlite-core\";\n\nexport const todo = sqliteTable(\"todo\", {\n  id: integer(\"id\").primaryKey({ autoIncrement: true }),\n  text: text(\"text\").notNull(),\n  completed: integer(\"completed\", { mode: \"boolean\" }).default(false).notNull(),\n});\n`],\n  [\"examples/todo/server/mongoose/base/src/routers/todo.ts.hbs\", `{{#if (eq api \"orpc\")}}\nimport z from \"zod\";\nimport { publicProcedure } from \"../index\";\nimport { Todo } from \"@{{projectName}}/db/models/todo.model\";\n\nexport const todoRouter = {\n    getAll: publicProcedure.handler(async () => {\n        return await Todo.find().lean();\n    }),\n\n    create: publicProcedure\n        .input(z.object({ text: z.string().min(1) }))\n        .handler(async ({ input }) => {\n            const newTodo = await Todo.create({ text: input.text });\n            return newTodo.toObject();\n    }),\n\n    toggle: publicProcedure\n        .input(z.object({ id: z.string(), completed: z.boolean() }))\n        .handler(async ({ input }) => {\n            await Todo.updateOne({ id: input.id }, { completed: input.completed });\n            return { success: true };\n    }),\n\n    delete: publicProcedure\n        .input(z.object({ id: z.string() }))\n        .handler(async ({ input }) => {\n            await Todo.deleteOne({ id: input.id });\n            return { success: true };\n    }),\n};\n\n{{/if}}\n\n{{#if (eq api \"trpc\")}}\nimport z from \"zod\";\nimport { router, publicProcedure } from \"../index\";\nimport { Todo } from \"@{{projectName}}/db/models/todo.model\";\n\nexport const todoRouter = router({\n    getAll: publicProcedure.query(async () => {\n        return await Todo.find().lean();\n    }),\n\n    create: publicProcedure\n        .input(z.object({ text: z.string().min(1) }))\n        .mutation(async ({ input }) => {\n            const newTodo = await Todo.create({ text: input.text });\n        return newTodo.toObject();\n    }),\n\n    toggle: publicProcedure\n        .input(z.object({ id: z.string(), completed: z.boolean() }))\n        .mutation(async ({ input }) => {\n            await Todo.updateOne({ id: input.id }, { completed: input.completed });\n            return { success: true };\n    }),\n\n    delete: publicProcedure\n        .input(z.object({ id: z.string() }))\n        .mutation(async ({ input }) => {\n            await Todo.deleteOne({ id: input.id });\n            return { success: true };\n    }),\n});\n{{/if}}\n`],\n  [\"examples/todo/server/mongoose/mongodb/src/models/todo.model.ts.hbs\", `import mongoose from 'mongoose';\n\nconst { Schema, model } = mongoose;\n\nconst todoSchema = new Schema({\n  id: {\n    type: mongoose.Schema.Types.ObjectId,\n    auto: true,\n  },\n  text: {\n    type: String,\n    required: true,\n  },\n  completed: {\n    type: Boolean,\n    default: false,\n  },\n}, {\n  collection: 'todo'\n});\n\nconst Todo = model('Todo', todoSchema);\n\nexport { Todo };\n`],\n  [\"examples/todo/server/prisma/base/src/routers/todo.ts.hbs\", `{{#if (eq api \"orpc\")}}\nimport z from \"zod\";\n{{#if (or (eq runtime \"workers\") (eq serverDeploy \"cloudflare\") (and (eq backend \"self\") (eq webDeploy \"cloudflare\")))}}\nimport { createPrismaClient } from \"@{{projectName}}/db\";\n{{else}}\nimport prisma from \"@{{projectName}}/db\";\n{{/if}}\nimport { publicProcedure } from \"../index\";\n\nexport const todoRouter = {\n  getAll: publicProcedure.handler(async ({{#if (and (eq backend \"self\") (eq webDeploy \"cloudflare\") (includes frontend \"svelte\"))}}{ context }{{/if}}) => {\n{{#if (or (eq runtime \"workers\") (eq serverDeploy \"cloudflare\") (and (eq backend \"self\") (eq webDeploy \"cloudflare\")))}}\n    const prisma = createPrismaClient({{#if (and (eq backend \"self\") (eq webDeploy \"cloudflare\") (includes frontend \"svelte\"))}}context.env{{/if}});\n{{/if}}\n    return await prisma.todo.findMany({\n      orderBy: {\n        id: \"asc\",\n      },\n    });\n  }),\n\n  create: publicProcedure\n    .input(z.object({ text: z.string().min(1) }))\n    .handler(async ({ input{{#if (and (eq backend \"self\") (eq webDeploy \"cloudflare\") (includes frontend \"svelte\"))}}, context{{/if}} }) => {\n{{#if (or (eq runtime \"workers\") (eq serverDeploy \"cloudflare\") (and (eq backend \"self\") (eq webDeploy \"cloudflare\")))}}\n      const prisma = createPrismaClient({{#if (and (eq backend \"self\") (eq webDeploy \"cloudflare\") (includes frontend \"svelte\"))}}context.env{{/if}});\n{{/if}}\n      return await prisma.todo.create({\n        data: {\n          text: input.text,\n        },\n      });\n    }),\n\n  toggle: publicProcedure\n    {{#if (eq database \"mongodb\")}}\n    .input(z.object({ id: z.string(), completed: z.boolean() }))\n    {{else}}\n    .input(z.object({ id: z.number(), completed: z.boolean() }))\n    {{/if}}\n    .handler(async ({ input{{#if (and (eq backend \"self\") (eq webDeploy \"cloudflare\") (includes frontend \"svelte\"))}}, context{{/if}} }) => {\n{{#if (or (eq runtime \"workers\") (eq serverDeploy \"cloudflare\") (and (eq backend \"self\") (eq webDeploy \"cloudflare\")))}}\n      const prisma = createPrismaClient({{#if (and (eq backend \"self\") (eq webDeploy \"cloudflare\") (includes frontend \"svelte\"))}}context.env{{/if}});\n{{/if}}\n      return await prisma.todo.update({\n        where: { id: input.id },\n        data: { completed: input.completed },\n      });\n    }),\n\n  delete: publicProcedure\n    {{#if (eq database \"mongodb\")}}\n    .input(z.object({ id: z.string() }))\n    {{else}}\n    .input(z.object({ id: z.number() }))\n    {{/if}}\n    .handler(async ({ input{{#if (and (eq backend \"self\") (eq webDeploy \"cloudflare\") (includes frontend \"svelte\"))}}, context{{/if}} }) => {\n{{#if (or (eq runtime \"workers\") (eq serverDeploy \"cloudflare\") (and (eq backend \"self\") (eq webDeploy \"cloudflare\")))}}\n      const prisma = createPrismaClient({{#if (and (eq backend \"self\") (eq webDeploy \"cloudflare\") (includes frontend \"svelte\"))}}context.env{{/if}});\n{{/if}}\n      return await prisma.todo.delete({\n        where: { id: input.id },\n      });\n    }),\n};\n{{/if}}\n\n{{#if (eq api \"trpc\")}}\nimport { TRPCError } from \"@trpc/server\";\nimport z from \"zod\";\n{{#if (or (eq runtime \"workers\") (eq serverDeploy \"cloudflare\") (and (eq backend \"self\") (eq webDeploy \"cloudflare\")))}}\nimport { createPrismaClient } from \"@{{projectName}}/db\";\n{{else}}\nimport prisma from \"@{{projectName}}/db\";\n{{/if}}\nimport { publicProcedure, router } from \"../index\";\n\nexport const todoRouter = router({\n  getAll: publicProcedure.query(async () => {\n{{#if (or (eq runtime \"workers\") (eq serverDeploy \"cloudflare\") (and (eq backend \"self\") (eq webDeploy \"cloudflare\")))}}\n    const prisma = createPrismaClient();\n{{/if}}\n    return await prisma.todo.findMany({\n      orderBy: {\n        id: \"asc\"\n      }\n    });\n  }),\n\n  create: publicProcedure\n    .input(z.object({ text: z.string().min(1) }))\n    .mutation(async ({ input }) => {\n{{#if (or (eq runtime \"workers\") (eq serverDeploy \"cloudflare\") (and (eq backend \"self\") (eq webDeploy \"cloudflare\")))}}\n      const prisma = createPrismaClient();\n{{/if}}\n      return await prisma.todo.create({\n        data: {\n          text: input.text,\n        },\n      });\n    }),\n\n  toggle: publicProcedure\n    {{#if (eq database \"mongodb\")}}\n    .input(z.object({ id: z.string(), completed: z.boolean() }))\n    {{else}}\n    .input(z.object({ id: z.number(), completed: z.boolean() }))\n    {{/if}}\n    .mutation(async ({ input }) => {\n{{#if (or (eq runtime \"workers\") (eq serverDeploy \"cloudflare\") (and (eq backend \"self\") (eq webDeploy \"cloudflare\")))}}\n      const prisma = createPrismaClient();\n{{/if}}\n      try {\n        return await prisma.todo.update({\n          where: { id: input.id },\n          data: { completed: input.completed },\n        });\n      } catch (error) {\n        throw new TRPCError({\n          code: \"NOT_FOUND\",\n          message: \"Todo not found\",\n        });\n      }\n    }),\n\n  delete: publicProcedure\n    {{#if (eq database \"mongodb\")}}\n    .input(z.object({ id: z.string() }))\n    {{else}}\n    .input(z.object({ id: z.number() }))\n    {{/if}}\n    .mutation(async ({ input }) => {\n{{#if (or (eq runtime \"workers\") (eq serverDeploy \"cloudflare\") (and (eq backend \"self\") (eq webDeploy \"cloudflare\")))}}\n      const prisma = createPrismaClient();\n{{/if}}\n      try {\n        return await prisma.todo.delete({\n          where: { id: input.id },\n        });\n      } catch (error) {\n        throw new TRPCError({\n          code: \"NOT_FOUND\",\n          message: \"Todo not found\",\n        });\n      }\n    }),\n});\n{{/if}}\n`],\n  [\"examples/todo/server/prisma/mongodb/prisma/schema/todo.prisma.hbs\", `model Todo {\n  id        String  @id @default(auto()) @map(\"_id\") @db.ObjectId\n  text      String\n  completed Boolean @default(false)\n\n  @@map(\"todo\")\n}\n`],\n  [\"examples/todo/server/prisma/mysql/prisma/schema/todo.prisma.hbs\", `model Todo {\n  id        Int     @id @default(autoincrement())\n  text      String\n  completed Boolean @default(false)\n\n  @@map(\"todo\")\n}\n`],\n  [\"examples/todo/server/prisma/postgres/prisma/schema/todo.prisma.hbs\", `model Todo {\n  id        Int     @id @default(autoincrement())\n  text      String\n  completed Boolean @default(false)\n\n  @@map(\"todo\")\n}\n`],\n  [\"examples/todo/server/prisma/sqlite/prisma/schema/todo.prisma.hbs\", `model Todo {\n  id        Int     @id @default(autoincrement())\n  text      String\n  completed Boolean @default(false)\n\n  @@map(\"todo\")\n}\n`],\n  [\"examples/todo/web/astro/src/pages/todos.astro.hbs\", `---\nimport Layout from \"../layouts/Layout.astro\";\n---\n\n<Layout title=\"Todos - {{projectName}}\">\n  <div class=\"p-4 max-w-2xl mx-auto\">\n    <h1 class=\"text-xl mb-4 text-white\">Todos</h1>\n\n    <form id=\"add-form\" class=\"flex gap-2 mb-4\">\n      <input\n        type=\"text\"\n        id=\"new-todo\"\n        placeholder=\"New task...\"\n        class=\"p-2 flex-grow rounded border border-neutral-700 bg-neutral-800 text-white placeholder-neutral-500\"\n      />\n      <button\n        type=\"submit\"\n        id=\"add-btn\"\n        class=\"bg-blue-500 text-white px-4 py-2 rounded disabled:opacity-50 hover:bg-blue-600 transition-colors\"\n      >\n        Add\n      </button>\n    </form>\n\n    <div id=\"loading\" class=\"text-neutral-400\">Loading...</div>\n    <div id=\"empty\" class=\"hidden text-neutral-400\">No todos yet.</div>\n    <ul id=\"todo-list\" class=\"space-y-1 hidden\"></ul>\n    <p id=\"error\" class=\"mt-4 text-red-500 hidden\"></p>\n  </div>\n</Layout>\n\n<script>\n  import { orpc } from \"../lib/orpc\";\n\n  interface Todo {\n    id: number;\n    text: string;\n    completed: boolean;\n  }\n\n  const newTodoInput = document.getElementById(\"new-todo\") as HTMLInputElement;\n  const addBtn = document.getElementById(\"add-btn\") as HTMLButtonElement;\n  const addForm = document.getElementById(\"add-form\") as HTMLFormElement;\n  const loadingEl = document.getElementById(\"loading\")!;\n  const emptyEl = document.getElementById(\"empty\")!;\n  const todoList = document.getElementById(\"todo-list\")!;\n  const errorEl = document.getElementById(\"error\")!;\n\n  let todos: Todo[] = [];\n\n  function showError(message: string) {\n    errorEl.textContent = message;\n    errorEl.classList.remove(\"hidden\");\n    setTimeout(() => errorEl.classList.add(\"hidden\"), 3000);\n  }\n\n  function renderTodos() {\n    loadingEl.classList.add(\"hidden\");\n    \n    if (todos.length === 0) {\n      emptyEl.classList.remove(\"hidden\");\n      todoList.classList.add(\"hidden\");\n      return;\n    }\n\n    emptyEl.classList.add(\"hidden\");\n    todoList.classList.remove(\"hidden\");\n    todoList.innerHTML = todos.map(todo => \\`\n      <li class=\"flex items-center justify-between p-2 rounded bg-neutral-800/50\" data-id=\"\\${todo.id}\">\n        <div class=\"flex items-center gap-2\">\n          <input\n            type=\"checkbox\"\n            id=\"todo-\\${todo.id}\"\n            \\${todo.completed ? \"checked\" : \"\"}\n            class=\"toggle-checkbox cursor-pointer\"\n          />\n          <label for=\"todo-\\${todo.id}\" class=\"\\${todo.completed ? \"line-through text-neutral-500\" : \"text-white\"}\">\n            \\${todo.text}\n          </label>\n        </div>\n        <button\n          class=\"delete-btn text-red-500 px-2 hover:text-red-400 transition-colors\"\n          aria-label=\"Delete todo\"\n        >\n          X\n        </button>\n      </li>\n    \\`).join(\"\");\n\n    // Add event listeners\n    todoList.querySelectorAll(\".toggle-checkbox\").forEach(checkbox => {\n      checkbox.addEventListener(\"change\", async (e) => {\n        const li = (e.target as HTMLElement).closest(\"li\")!;\n        const id = parseInt(li.dataset.id!);\n        const todo = todos.find(t => t.id === id);\n        if (todo) {\n          await toggleTodo(id, todo.completed);\n        }\n      });\n    });\n\n    todoList.querySelectorAll(\".delete-btn\").forEach(btn => {\n      btn.addEventListener(\"click\", async (e) => {\n        const li = (e.target as HTMLElement).closest(\"li\")!;\n        const id = parseInt(li.dataset.id!);\n        await deleteTodo(id);\n      });\n    });\n  }\n\n  async function loadTodos() {\n    try {\n      todos = await orpc.todo.getAll();\n      renderTodos();\n    } catch (e) {\n      loadingEl.classList.add(\"hidden\");\n      showError(\"Failed to load todos\");\n    }\n  }\n\n  async function addTodo(text: string) {\n    addBtn.disabled = true;\n    addBtn.textContent = \"Adding...\";\n    try {\n      await orpc.todo.create({ text });\n      newTodoInput.value = \"\";\n      await loadTodos();\n    } catch (e) {\n      showError(\"Failed to add todo\");\n    } finally {\n      addBtn.disabled = false;\n      addBtn.textContent = \"Add\";\n    }\n  }\n\n  async function toggleTodo(id: number, completed: boolean) {\n    try {\n      await orpc.todo.toggle({ id, completed: !completed });\n      await loadTodos();\n    } catch (e) {\n      showError(\"Failed to update todo\");\n    }\n  }\n\n  async function deleteTodo(id: number) {\n    try {\n      await orpc.todo.delete({ id });\n      await loadTodos();\n    } catch (e) {\n      showError(\"Failed to delete todo\");\n    }\n  }\n\n  addForm.addEventListener(\"submit\", async (e) => {\n    e.preventDefault();\n    const text = newTodoInput.value.trim();\n    if (text) {\n      await addTodo(text);\n    }\n  });\n\n  loadTodos();\n</script>\n`],\n  [\"examples/todo/web/nuxt/app/pages/todos.vue.hbs\", `<script setup lang=\"ts\">\nimport { ref } from 'vue'\n{{#if (eq backend \"convex\")}}\nimport { api } from \"@{{ projectName }}/backend/convex/_generated/api\";\nimport type { Id } from \"@{{ projectName }}/backend/convex/_generated/dataModel\";\nimport { useConvexMutation, useConvexQuery } from \"convex-vue\";\n\nconst { data, error, isPending } = useConvexQuery(api.todos.getAll, {});\n\nconst newTodoText = ref(\"\");\nconst { mutate: createTodo, isPending: isCreatePending } = useConvexMutation(api.todos.create);\n\nconst { mutate: toggleTodo } = useConvexMutation(api.todos.toggle);\nconst { mutate: deleteTodo, error: deleteError } = useConvexMutation(\n  api.todos.deleteTodo,\n);\n\nfunction handleAddTodo() {\n  const text = newTodoText.value.trim();\n  if (!text) return;\n\n  createTodo({ text });\n  newTodoText.value = \"\";\n}\n\nfunction handleToggleTodo(id: Id<\"todos\">, completed: boolean) {\n  toggleTodo({ id, completed: !completed });\n}\n\nfunction handleDeleteTodo(id: Id<\"todos\">) {\n  deleteTodo({ id });\n}\n{{else}}\nimport { useQuery, useMutation, useQueryClient } from '@tanstack/vue-query'\n\nconst { $orpc } = useNuxtApp()\n\nconst newTodoText = ref('')\nconst queryClient = useQueryClient()\n\nconst todos = useQuery($orpc.todo.getAll.queryOptions())\n\nonServerPrefetch(async () => {\n  try {\n    await todos.suspense()\n  } catch {}\n})\n\nconst createMutation = useMutation($orpc.todo.create.mutationOptions({\n  onSuccess: () => {\n    queryClient.invalidateQueries()\n    newTodoText.value = ''\n  }\n}))\n\nconst toggleMutation = useMutation($orpc.todo.toggle.mutationOptions({\n  onSuccess: () => queryClient.invalidateQueries()\n}))\n\nconst deleteMutation = useMutation($orpc.todo.delete.mutationOptions({\n  onSuccess: () => queryClient.invalidateQueries()\n}))\n\nfunction handleAddTodo() {\n  if (newTodoText.value.trim()) {\n    createMutation.mutate({ text: newTodoText.value })\n  }\n}\n\nfunction handleToggleTodo(id: number, completed: boolean) {\n  toggleMutation.mutate({ id, completed: !completed })\n}\n\nfunction handleDeleteTodo(id: number) {\n  deleteMutation.mutate({ id })\n}\n{{/if}}\n</script>\n\n<template>\n  <UContainer class=\"py-8 max-w-md\">\n    <UCard>\n      <template #header>\n        <div>\n          <div class=\"text-xl font-bold\">Todo List</div>\n          <div class=\"text-muted text-sm\">Manage your tasks efficiently</div>\n        </div>\n      </template>\n\n      <form @submit.prevent=\"handleAddTodo\" class=\"mb-6 flex items-center gap-2\">\n        <UInput\n          v-model=\"newTodoText\"\n          placeholder=\"Add a new task...\"\n          autocomplete=\"off\"\n          class=\"flex-1\"\n          {{#if (eq backend \"convex\")}}\n          :disabled=\"isCreatePending\"\n          {{/if}}\n        />\n        <UButton\n          type=\"submit\"\n          {{#if (eq backend \"convex\")}}\n          :loading=\"isCreatePending\"\n          :disabled=\"!newTodoText.trim()\"\n          {{else}}\n          :loading=\"createMutation.isPending.value\"\n          {{/if}}\n        >\n          Add\n        </UButton>\n      </form>\n\n      {{#if (eq backend \"convex\")}}\n      <!-- Loading State -->\n      <div v-if=\"isPending\" class=\"space-y-2\">\n        <USkeleton v-for=\"i in 3\" :key=\"i\" class=\"h-12 w-full\" />\n      </div>\n\n      <!-- Error State -->\n      <UAlert\n        v-else-if=\"error || deleteError\"\n        color=\"error\"\n        icon=\"i-lucide-alert-circle\"\n        title=\"Error\"\n        :description=\"error?.message || deleteError?.message\"\n      />\n\n      <!-- Empty State -->\n      <UEmpty\n        v-else-if=\"data?.length === 0\"\n        icon=\"i-lucide-clipboard-list\"\n        title=\"No todos yet\"\n        description=\"Add your first task above!\"\n      />\n\n      <!-- Todo List -->\n      <ul v-else-if=\"data\" class=\"space-y-2\">\n        <li\n          v-for=\"todo in data\"\n          :key=\"todo._id\"\n          class=\"flex items-center justify-between rounded-md border p-3\"\n        >\n          <div class=\"flex items-center gap-3\">\n            <UCheckbox\n              :model-value=\"todo.completed\"\n              @update:model-value=\"() => handleToggleTodo(todo._id, todo.completed)\"\n              :id=\"\\`todo-\\${todo._id}\\`\"\n            />\n            <label\n              :for=\"\\`todo-\\${todo._id}\\`\"\n              :class=\"{ 'line-through text-muted': todo.completed }\"\n              class=\"cursor-pointer\"\n            >\n              \\\\{{ todo.text }}\n            </label>\n          </div>\n          <UButton\n            color=\"error\"\n            variant=\"ghost\"\n            size=\"sm\"\n            square\n            @click=\"handleDeleteTodo(todo._id)\"\n            aria-label=\"Delete todo\"\n            icon=\"i-lucide-trash-2\"\n          />\n        </li>\n      </ul>\n      {{else}}\n      <!-- Loading State -->\n      <div v-if=\"todos.status.value === 'pending'\" class=\"space-y-2\">\n        <USkeleton v-for=\"i in 3\" :key=\"i\" class=\"h-12 w-full\" />\n      </div>\n\n      <!-- Error State -->\n      <UAlert\n        v-else-if=\"todos.status.value === 'error'\"\n        color=\"error\"\n        icon=\"i-lucide-alert-circle\"\n        title=\"Failed to load todos\"\n        :description=\"todos.error.value?.message\"\n      />\n\n      <!-- Empty State -->\n      <UEmpty\n        v-else-if=\"todos.data.value?.length === 0\"\n        icon=\"i-lucide-clipboard-list\"\n        title=\"No todos yet\"\n        description=\"Add your first task above!\"\n      />\n\n      <!-- Todo List -->\n      <ul v-else class=\"space-y-2\">\n        <li\n          v-for=\"todo in todos.data.value\"\n          :key=\"todo.id\"\n          class=\"flex items-center justify-between rounded-md border p-3\"\n        >\n          <div class=\"flex items-center gap-3\">\n            <UCheckbox\n              :model-value=\"todo.completed\"\n              @update:model-value=\"() => handleToggleTodo(todo.id, todo.completed)\"\n              :id=\"\\`todo-\\${todo.id}\\`\"\n            />\n            <label\n              :for=\"\\`todo-\\${todo.id}\\`\"\n              :class=\"{ 'line-through text-muted': todo.completed }\"\n              class=\"cursor-pointer\"\n            >\n              \\\\{{ todo.text }}\n            </label>\n          </div>\n          <UButton\n            color=\"error\"\n            variant=\"ghost\"\n            size=\"sm\"\n            square\n            @click=\"handleDeleteTodo(todo.id)\"\n            aria-label=\"Delete todo\"\n            icon=\"i-lucide-trash-2\"\n          />\n        </li>\n      </ul>\n      {{/if}}\n    </UCard>\n  </UContainer>\n</template>\n`],\n  [\"examples/todo/web/react/next/src/app/todos/page.tsx.hbs\", `\"use client\"\n\nimport { Button } from \"@{{projectName}}/ui/components/button\";\nimport {\n  Card,\n  CardContent,\n  CardDescription,\n  CardHeader,\n  CardTitle,\n} from \"@{{projectName}}/ui/components/card\";\nimport { Checkbox } from \"@{{projectName}}/ui/components/checkbox\";\nimport { Input } from \"@{{projectName}}/ui/components/input\";\nimport { Loader2, Trash2 } from \"lucide-react\";\nimport { useState, type FormEvent } from \"react\";\n\n{{#if (eq backend \"convex\")}}\nimport { useMutation, useQuery } from \"convex/react\";\nimport { api } from \"@{{projectName}}/backend/convex/_generated/api\";\nimport type { Id } from \"@{{projectName}}/backend/convex/_generated/dataModel\";\n{{else}}\nimport { useMutation, useQuery } from \"@tanstack/react-query\";\n  {{#if (eq api \"orpc\")}}\nimport { orpc } from \"@/utils/orpc\";\n  {{/if}}\n  {{#if (eq api \"trpc\")}}\nimport { trpc } from \"@/utils/trpc\";\n  {{/if}}\n{{/if}}\n\n\nexport default function TodosPage() {\n  const [newTodoText, setNewTodoText] = useState(\"\");\n\n  {{#if (eq backend \"convex\")}}\n  const todos = useQuery(api.todos.getAll);\n  const createTodoMutation = useMutation(api.todos.create);\n  const toggleTodoMutation = useMutation(api.todos.toggle);\n  const deleteTodoMutation = useMutation(api.todos.deleteTodo);\n\n  const handleAddTodo = async (e: FormEvent<HTMLFormElement>) => {\n    e.preventDefault();\n    const text = newTodoText.trim();\n    if (!text) return;\n    await createTodoMutation({ text });\n    setNewTodoText(\"\");\n  };\n\n  const handleToggleTodo = (id: Id<\"todos\">, currentCompleted: boolean) => {\n    toggleTodoMutation({ id, completed: !currentCompleted });\n  };\n\n  const handleDeleteTodo = (id: Id<\"todos\">) => {\n    deleteTodoMutation({ id });\n  };\n  {{else}}\n    {{#if (eq api \"orpc\")}}\n    const todos = useQuery(orpc.todo.getAll.queryOptions());\n    const createMutation = useMutation(\n      orpc.todo.create.mutationOptions({\n        onSuccess: () => {\n          todos.refetch();\n          setNewTodoText(\"\");\n        },\n      }),\n    );\n    const toggleMutation = useMutation(\n      orpc.todo.toggle.mutationOptions({\n        onSuccess: () => { todos.refetch() },\n      }),\n    );\n    const deleteMutation = useMutation(\n      orpc.todo.delete.mutationOptions({\n        onSuccess: () => { todos.refetch() },\n      }),\n    );\n    {{/if}}\n    {{#if (eq api \"trpc\")}}\n    const todos = useQuery(trpc.todo.getAll.queryOptions());\n    const createMutation = useMutation(\n      trpc.todo.create.mutationOptions({\n        onSuccess: () => {\n          todos.refetch();\n          setNewTodoText(\"\");\n        },\n      }),\n    );\n    const toggleMutation = useMutation(\n      trpc.todo.toggle.mutationOptions({\n        onSuccess: () => { todos.refetch() },\n      }),\n    );\n    const deleteMutation = useMutation(\n      trpc.todo.delete.mutationOptions({\n        onSuccess: () => { todos.refetch() },\n      }),\n    );\n    {{/if}}\n\n  const handleAddTodo = (e: FormEvent<HTMLFormElement>) => {\n    e.preventDefault();\n    if (newTodoText.trim()) {\n      createMutation.mutate({ text: newTodoText });\n    }\n  };\n\n  const handleToggleTodo = (id: number, completed: boolean) => {\n    toggleMutation.mutate({ id, completed: !completed });\n  };\n\n  const handleDeleteTodo = (id: number) => {\n    deleteMutation.mutate({ id });\n  };\n  {{/if}}\n\n  return (\n    <div className=\"mx-auto w-full max-w-md py-10\">\n      <Card>\n        <CardHeader>\n          <CardTitle>Todo List</CardTitle>\n          <CardDescription>Manage your tasks efficiently</CardDescription>\n        </CardHeader>\n        <CardContent>\n          <form\n            onSubmit={handleAddTodo}\n            className=\"mb-6 flex items-center space-x-2\"\n          >\n            <Input\n              value={newTodoText}\n              onChange={(e) => setNewTodoText(e.target.value)}\n              placeholder=\"Add a new task...\"\n              {{#if (eq backend \"convex\")}}\n              {{else}}\n              disabled={createMutation.isPending}\n              {{/if}}\n            />\n            <Button\n              type=\"submit\"\n              {{#if (eq backend \"convex\")}}\n              disabled={!newTodoText.trim()}\n              {{else}}\n              disabled={createMutation.isPending || !newTodoText.trim()}\n              {{/if}}\n            >\n              {{#if (eq backend \"convex\")}}\n                Add\n              {{else}}\n                {createMutation.isPending ? (\n                  <Loader2 className=\"h-4 w-4 animate-spin\" />\n                ) : (\n                  \"Add\"\n                )}\n              {{/if}}\n            </Button>\n          </form>\n\n          {{#if (eq backend \"convex\")}}\n            {todos === undefined ? (\n              <div className=\"flex justify-center py-4\">\n                <Loader2 className=\"h-6 w-6 animate-spin\" />\n              </div>\n            ) : todos.length === 0 ? (\n              <p className=\"py-4 text-center\">No todos yet. Add one above!</p>\n            ) : (\n              <ul className=\"space-y-2\">\n                {todos.map((todo) => (\n                  <li\n                    key={todo._id}\n                    className=\"flex items-center justify-between rounded-md border p-2\"\n                  >\n                    <div className=\"flex items-center space-x-2\">\n                      <Checkbox\n                        checked={todo.completed}\n                        onCheckedChange={() =>\n                          handleToggleTodo(todo._id, todo.completed)\n                        }\n                        id={\\`todo-\\${todo._id}\\`}\n                      />\n                      <label\n                        htmlFor={\\`todo-\\${todo._id}\\`}\n                        className={\\`\\${todo.completed ? \"line-through text-muted-foreground\" : \"\"}\\`}\n                      >\n                        {todo.text}\n                      </label>\n                    </div>\n                    <Button\n                      variant=\"ghost\"\n                      size=\"icon\"\n                      onClick={() => handleDeleteTodo(todo._id)}\n                      aria-label=\"Delete todo\"\n                    >\n                      <Trash2 className=\"h-4 w-4\" />\n                    </Button>\n                  </li>\n                ))}\n              </ul>\n            )}\n          {{else}}\n            {todos.isLoading ? (\n              <div className=\"flex justify-center py-4\">\n                <Loader2 className=\"h-6 w-6 animate-spin\" />\n              </div>\n            ) : todos.data?.length === 0 ? (\n              <p className=\"py-4 text-center\">\n                No todos yet. Add one above!\n              </p>\n            ) : (\n              <ul className=\"space-y-2\">\n                {todos.data?.map((todo) => (\n                  <li\n                    key={todo.id}\n                    className=\"flex items-center justify-between rounded-md border p-2\"\n                  >\n                    <div className=\"flex items-center space-x-2\">\n                      <Checkbox\n                        checked={todo.completed}\n                        onCheckedChange={() =>\n                          handleToggleTodo(todo.id, todo.completed)\n                        }\n                        id={\\`todo-\\${todo.id}\\`}\n                      />\n                      <label\n                        htmlFor={\\`todo-\\${todo.id}\\`}\n                        className={\\`\\${todo.completed ? \"line-through text-muted-foreground\" : \"\"}\\`}\n                      >\n                        {todo.text}\n                      </label>\n                    </div>\n                    <Button\n                      variant=\"ghost\"\n                      size=\"icon\"\n                      onClick={() => handleDeleteTodo(todo.id)}\n                      aria-label=\"Delete todo\"\n                    >\n                      <Trash2 className=\"h-4 w-4\" />\n                    </Button>\n                  </li>\n                ))}\n              </ul>\n            )}\n          {{/if}}\n        </CardContent>\n      </Card>\n    </div>\n  );\n}\n`],\n  [\"examples/todo/web/react/react-router/src/routes/todos.tsx.hbs\", `import { Button } from \"@{{projectName}}/ui/components/button\";\nimport {\n  Card,\n  CardContent,\n  CardDescription,\n  CardHeader,\n  CardTitle,\n} from \"@{{projectName}}/ui/components/card\";\nimport { Checkbox } from \"@{{projectName}}/ui/components/checkbox\";\nimport { Input } from \"@{{projectName}}/ui/components/input\";\nimport { Loader2, Trash2 } from \"lucide-react\";\nimport { useState, type FormEvent } from \"react\";\n\n{{#if (eq backend \"convex\")}}\nimport { useMutation, useQuery } from \"convex/react\";\nimport { api } from \"@{{projectName}}/backend/convex/_generated/api\";\nimport type { Id } from \"@{{projectName}}/backend/convex/_generated/dataModel\";\n{{else}}\n  {{#if (eq api \"orpc\")}}\n  import { orpc } from \"@/utils/orpc\";\n  {{/if}}\n  {{#if (eq api \"trpc\")}}\n  import { trpc } from \"@/utils/trpc\";\n  {{/if}}\nimport { useMutation, useQuery } from \"@tanstack/react-query\";\n{{/if}}\n\nexport default function Todos() {\n  const [newTodoText, setNewTodoText] = useState(\"\");\n\n  {{#if (eq backend \"convex\")}}\n  const todos = useQuery(api.todos.getAll);\n  const createTodo = useMutation(api.todos.create);\n  const toggleTodo = useMutation(api.todos.toggle);\n  const deleteTodo = useMutation(api.todos.deleteTodo);\n\n  const handleAddTodo = async (e: FormEvent<HTMLFormElement>) => {\n    e.preventDefault();\n    const text = newTodoText.trim();\n    if (!text) return;\n    await createTodo({ text });\n    setNewTodoText(\"\");\n  };\n\n  const handleToggleTodo = (id: Id<\"todos\">, currentCompleted: boolean) => {\n    toggleTodo({ id, completed: !currentCompleted });\n  };\n\n  const handleDeleteTodo = (id: Id<\"todos\">) => {\n    deleteTodo({ id });\n  };\n  {{else}}\n    {{#if (eq api \"orpc\")}}\n    const todos = useQuery(orpc.todo.getAll.queryOptions());\n    const createMutation = useMutation(\n      orpc.todo.create.mutationOptions({\n        onSuccess: () => {\n          todos.refetch();\n          setNewTodoText(\"\");\n        },\n      })\n    );\n    const toggleMutation = useMutation(\n      orpc.todo.toggle.mutationOptions({\n        onSuccess: () => { todos.refetch() },\n      })\n    );\n    const deleteMutation = useMutation(\n      orpc.todo.delete.mutationOptions({\n        onSuccess: () => { todos.refetch() },\n      })\n    );\n    {{/if}}\n    {{#if (eq api \"trpc\")}}\n    const todos = useQuery(trpc.todo.getAll.queryOptions());\n    const createMutation = useMutation(\n      trpc.todo.create.mutationOptions({\n        onSuccess: () => {\n          todos.refetch();\n          setNewTodoText(\"\");\n        },\n      })\n    );\n    const toggleMutation = useMutation(\n      trpc.todo.toggle.mutationOptions({\n        onSuccess: () => { todos.refetch() },\n      })\n    );\n    const deleteMutation = useMutation(\n      trpc.todo.delete.mutationOptions({\n        onSuccess: () => { todos.refetch() },\n      })\n    );\n    {{/if}}\n\n  const handleAddTodo = (e: FormEvent<HTMLFormElement>) => {\n    e.preventDefault();\n    if (newTodoText.trim()) {\n      createMutation.mutate({ text: newTodoText });\n    }\n  };\n\n  const handleToggleTodo = (id: number, completed: boolean) => {\n    toggleMutation.mutate({ id, completed: !completed });\n  };\n\n  const handleDeleteTodo = (id: number) => {\n    deleteMutation.mutate({ id });\n  };\n  {{/if}}\n\n  return (\n    <div className=\"w-full mx-auto max-w-md py-10\">\n      <Card>\n        <CardHeader>\n          <CardTitle>Todo List</CardTitle>\n          <CardDescription>Manage your tasks efficiently</CardDescription>\n        </CardHeader>\n        <CardContent>\n          <form\n            onSubmit={handleAddTodo}\n            className=\"mb-6 flex items-center space-x-2\"\n          >\n            <Input\n              value={newTodoText}\n              onChange={(e) => setNewTodoText(e.target.value)}\n              placeholder=\"Add a new task...\"\n              {{#if (eq backend \"convex\")}}\n              {{else}}\n              disabled={createMutation.isPending}\n              {{/if}}\n            />\n            <Button\n              type=\"submit\"\n              {{#if (eq backend \"convex\")}}\n              disabled={!newTodoText.trim()}\n              {{else}}\n              disabled={createMutation.isPending || !newTodoText.trim()}\n              {{/if}}\n            >\n              {{#if (eq backend \"convex\")}}\n              Add\n              {{else}}\n                {createMutation.isPending ? (\n                  <Loader2 className=\"h-4 w-4 animate-spin\" />\n                ) : (\n                  \"Add\"\n                )}\n              {{/if}}\n            </Button>\n          </form>\n\n          {{#if (eq backend \"convex\")}}\n            {todos === undefined ? (\n              <div className=\"flex justify-center py-4\">\n                <Loader2 className=\"h-6 w-6 animate-spin\" />\n              </div>\n            ) : todos.length === 0 ? (\n              <p className=\"py-4 text-center\">No todos yet. Add one above!</p>\n            ) : (\n              <ul className=\"space-y-2\">\n                {todos.map((todo) => (\n                  <li\n                    key={todo._id}\n                    className=\"flex items-center justify-between rounded-md border p-2\"\n                  >\n                    <div className=\"flex items-center space-x-2\">\n                      <Checkbox\n                        checked={todo.completed}\n                        onCheckedChange={() =>\n                          handleToggleTodo(todo._id, todo.completed)\n                        }\n                        id={\\`todo-\\${todo._id}\\`}\n                      />\n                      <label\n                        htmlFor={\\`todo-\\${todo._id}\\`}\n                        className={\\`\\${todo.completed ? \"line-through text-muted-foreground\" : \"\"}\\`}\n                      >\n                        {todo.text}\n                      </label>\n                    </div>\n                    <Button\n                      variant=\"ghost\"\n                      size=\"icon\"\n                      onClick={() => handleDeleteTodo(todo._id)}\n                      aria-label=\"Delete todo\"\n                    >\n                      <Trash2 className=\"h-4 w-4\" />\n                    </Button>\n                  </li>\n                ))}\n              </ul>\n            )}\n          {{else}}\n            {todos.isLoading ? (\n              <div className=\"flex justify-center py-4\">\n                <Loader2 className=\"h-6 w-6 animate-spin\" />\n              </div>\n            ) : todos.data?.length === 0 ? (\n              <p className=\"py-4 text-center\">\n                No todos yet. Add one above!\n              </p>\n            ) : (\n              <ul className=\"space-y-2\">\n                {todos.data?.map((todo) => (\n                  <li\n                    key={todo.id}\n                    className=\"flex items-center justify-between rounded-md border p-2\"\n                  >\n                    <div className=\"flex items-center space-x-2\">\n                      <Checkbox\n                        checked={todo.completed}\n                        onCheckedChange={() =>\n                          handleToggleTodo(todo.id, todo.completed)\n                        }\n                        id={\\`todo-\\${todo.id}\\`}\n                      />\n                      <label\n                        htmlFor={\\`todo-\\${todo.id}\\`}\n                        className={\\`\\${todo.completed ? \"line-through\" : \"\"}\\`}\n                      >\n                        {todo.text}\n                      </label>\n                    </div>\n                    <Button\n                      variant=\"ghost\"\n                      size=\"icon\"\n                      onClick={() => handleDeleteTodo(todo.id)}\n                      aria-label=\"Delete todo\"\n                    >\n                      <Trash2 className=\"h-4 w-4\" />\n                    </Button>\n                  </li>\n                ))}\n              </ul>\n            )}\n          {{/if}}\n        </CardContent>\n      </Card>\n    </div>\n  );\n}\n`],\n  [\"examples/todo/web/react/tanstack-router/src/routes/todos.tsx.hbs\", `import { Button } from \"@{{projectName}}/ui/components/button\";\nimport {\n  Card,\n  CardContent,\n  CardDescription,\n  CardHeader,\n  CardTitle,\n} from \"@{{projectName}}/ui/components/card\";\nimport { Checkbox } from \"@{{projectName}}/ui/components/checkbox\";\nimport { Input } from \"@{{projectName}}/ui/components/input\";\nimport { createFileRoute } from \"@tanstack/react-router\";\nimport { Loader2, Trash2 } from \"lucide-react\";\nimport { useState, type FormEvent } from \"react\";\n\n{{#if (eq backend \"convex\")}}\nimport { useMutation, useQuery } from \"convex/react\";\nimport { api } from \"@{{projectName}}/backend/convex/_generated/api\";\nimport type { Id } from \"@{{projectName}}/backend/convex/_generated/dataModel\";\n{{else}}\n  {{#if (eq api \"orpc\")}}\n  import { orpc } from \"@/utils/orpc\";\n  {{/if}}\n  {{#if (eq api \"trpc\")}}\n  import { trpc } from \"@/utils/trpc\";\n  {{/if}}\nimport { useMutation, useQuery } from \"@tanstack/react-query\";\n{{/if}}\n\nexport const Route = createFileRoute(\"/todos\")({\n  component: TodosRoute,\n});\n\nfunction TodosRoute() {\n  const [newTodoText, setNewTodoText] = useState(\"\");\n\n  {{#if (eq backend \"convex\")}}\n  const todos = useQuery(api.todos.getAll);\n  const createTodo = useMutation(api.todos.create);\n  const toggleTodo = useMutation(api.todos.toggle);\n  const deleteTodo = useMutation(api.todos.deleteTodo);\n\n  const handleAddTodo = async (e: FormEvent<HTMLFormElement>) => {\n    e.preventDefault();\n    const text = newTodoText.trim();\n    if (!text) return;\n    await createTodo({ text });\n    setNewTodoText(\"\");\n  };\n\n  const handleToggleTodo = (id: Id<\"todos\">, currentCompleted: boolean) => {\n    toggleTodo({ id, completed: !currentCompleted });\n  };\n\n  const handleDeleteTodo = (id: Id<\"todos\">) => {\n    deleteTodo({ id });\n  };\n  {{else}}\n    {{#if (eq api \"orpc\")}}\n    const todos = useQuery(orpc.todo.getAll.queryOptions());\n    const createMutation = useMutation(\n      orpc.todo.create.mutationOptions({\n        onSuccess: () => {\n          todos.refetch();\n          setNewTodoText(\"\");\n        },\n      }),\n    );\n    const toggleMutation = useMutation(\n      orpc.todo.toggle.mutationOptions({\n        onSuccess: () => { todos.refetch() },\n      }),\n    );\n    const deleteMutation = useMutation(\n      orpc.todo.delete.mutationOptions({\n        onSuccess: () => { todos.refetch() },\n      }),\n    );\n    {{/if}}\n    {{#if (eq api \"trpc\")}}\n    const todos = useQuery(trpc.todo.getAll.queryOptions());\n    const createMutation = useMutation(\n      trpc.todo.create.mutationOptions({\n        onSuccess: () => {\n          todos.refetch();\n          setNewTodoText(\"\");\n        },\n      }),\n    );\n    const toggleMutation = useMutation(\n      trpc.todo.toggle.mutationOptions({\n        onSuccess: () => { todos.refetch() },\n      }),\n    );\n    const deleteMutation = useMutation(\n      trpc.todo.delete.mutationOptions({\n        onSuccess: () => { todos.refetch() },\n      }),\n    );\n    {{/if}}\n\n  const handleAddTodo = (e: FormEvent<HTMLFormElement>) => {\n    e.preventDefault();\n    if (newTodoText.trim()) {\n      createMutation.mutate({ text: newTodoText });\n    }\n  };\n\n  const handleToggleTodo = (id: number, completed: boolean) => {\n    toggleMutation.mutate({ id, completed: !completed });\n  };\n\n  const handleDeleteTodo = (id: number) => {\n    deleteMutation.mutate({ id });\n  };\n  {{/if}}\n\n  return (\n    <div className=\"mx-auto w-full max-w-md py-10\">\n      <Card>\n        <CardHeader>\n          <CardTitle>Todo List</CardTitle>\n          <CardDescription>Manage your tasks efficiently</CardDescription>\n        </CardHeader>\n        <CardContent>\n          <form\n            onSubmit={handleAddTodo}\n            className=\"mb-6 flex items-center space-x-2\"\n          >\n            <Input\n              value={newTodoText}\n              onChange={(e) => setNewTodoText(e.target.value)}\n              placeholder=\"Add a new task...\"\n              {{#if (eq backend \"convex\")}}\n              {{else}}\n              disabled={createMutation.isPending}\n              {{/if}}\n            />\n            <Button\n              type=\"submit\"\n              {{#if (eq backend \"convex\")}}\n              disabled={!newTodoText.trim()}\n              {{else}}\n              disabled={createMutation.isPending || !newTodoText.trim()}\n              {{/if}}\n            >\n              {{#if (eq backend \"convex\")}}\n              Add\n              {{else}}\n                {createMutation.isPending ? (\n                  <Loader2 className=\"h-4 w-4 animate-spin\" />\n                ) : (\n                  \"Add\"\n                )}\n              {{/if}}\n            </Button>\n          </form>\n\n          {{#if (eq backend \"convex\")}}\n            {todos === undefined ? (\n              <div className=\"flex justify-center py-4\">\n                <Loader2 className=\"h-6 w-6 animate-spin\" />\n              </div>\n            ) : todos.length === 0 ? (\n              <p className=\"py-4 text-center\">No todos yet. Add one above!</p>\n            ) : (\n              <ul className=\"space-y-2\">\n                {todos.map((todo) => (\n                  <li\n                    key={todo._id}\n                    className=\"flex items-center justify-between rounded-md border p-2\"\n                  >\n                    <div className=\"flex items-center space-x-2\">\n                      <Checkbox\n                        checked={todo.completed}\n                        onCheckedChange={() =>\n                          handleToggleTodo(todo._id, todo.completed)\n                        }\n                        id={\\`todo-\\${todo._id}\\`}\n                      />\n                      <label\n                        htmlFor={\\`todo-\\${todo._id}\\`}\n                        className={\\`\\${todo.completed ? \"line-through text-muted-foreground\" : \"\"}\\`}\n                      >\n                        {todo.text}\n                      </label>\n                    </div>\n                    <Button\n                      variant=\"ghost\"\n                      size=\"icon\"\n                      onClick={() => handleDeleteTodo(todo._id)}\n                      aria-label=\"Delete todo\"\n                    >\n                      <Trash2 className=\"h-4 w-4\" />\n                    </Button>\n                  </li>\n                ))}\n              </ul>\n            )}\n          {{else}}\n            {todos.isLoading ? (\n              <div className=\"flex justify-center py-4\">\n                <Loader2 className=\"h-6 w-6 animate-spin\" />\n              </div>\n            ) : todos.data?.length === 0 ? (\n              <p className=\"py-4 text-center\">\n                No todos yet. Add one above!\n              </p>\n            ) : (\n              <ul className=\"space-y-2\">\n                {todos.data?.map((todo) => (\n                  <li\n                    key={todo.id}\n                    className=\"flex items-center justify-between rounded-md border p-2\"\n                  >\n                    <div className=\"flex items-center space-x-2\">\n                      <Checkbox\n                        checked={todo.completed}\n                        onCheckedChange={() =>\n                          handleToggleTodo(todo.id, todo.completed)\n                        }\n                        id={\\`todo-\\${todo.id}\\`}\n                      />\n                      <label\n                        htmlFor={\\`todo-\\${todo.id}\\`}\n                        className={\\`\\${todo.completed ? \"line-through\" : \"\"}\\`}\n                      >\n                        {todo.text}\n                      </label>\n                    </div>\n                    <Button\n                      variant=\"ghost\"\n                      size=\"icon\"\n                      onClick={() => handleDeleteTodo(todo.id)}\n                      aria-label=\"Delete todo\"\n                    >\n                      <Trash2 className=\"h-4 w-4\" />\n                    </Button>\n                  </li>\n                ))}\n              </ul>\n            )}\n          {{/if}}\n        </CardContent>\n      </Card>\n    </div>\n  );\n}\n`],\n  [\"examples/todo/web/react/tanstack-start/src/routes/todos.tsx.hbs\", `import { Button } from \"@{{projectName}}/ui/components/button\";\nimport {\n  Card,\n  CardContent,\n  CardDescription,\n  CardHeader,\n  CardTitle,\n} from \"@{{projectName}}/ui/components/card\";\nimport { Checkbox } from \"@{{projectName}}/ui/components/checkbox\";\nimport { Input } from \"@{{projectName}}/ui/components/input\";\nimport { createFileRoute } from \"@tanstack/react-router\";\n{{#if (eq backend \"convex\")}}\nimport { Trash2 } from \"lucide-react\";\n{{else}}\nimport { Loader2, Trash2 } from \"lucide-react\";\n{{/if}}\nimport { useState, type FormEvent } from \"react\";\n\n{{#if (eq backend \"convex\")}}\nimport { useSuspenseQuery } from \"@tanstack/react-query\";\nimport { convexQuery } from \"@convex-dev/react-query\";\nimport { useMutation } from \"convex/react\";\nimport { api } from \"@{{projectName}}/backend/convex/_generated/api\";\nimport type { Id } from \"@{{projectName}}/backend/convex/_generated/dataModel\";\n{{else}}\n{{#if (eq api \"trpc\")}}\nimport { useTRPC } from \"@/utils/trpc\";\n{{/if}}\n{{#if (eq api \"orpc\")}}\nimport { orpc } from \"@/utils/orpc\";\n{{/if}}\nimport { useMutation, useQuery } from \"@tanstack/react-query\";\n{{/if}}\n\nexport const Route = createFileRoute(\"/todos\")({\n  component: TodosRoute,\n});\n\nfunction TodosRoute() {\n  const [newTodoText, setNewTodoText] = useState(\"\");\n\n  {{#if (eq backend \"convex\")}}\n  const todosQuery = useSuspenseQuery(convexQuery(api.todos.getAll, {}));\n  const todos = todosQuery.data;\n\n  const createTodo = useMutation(api.todos.create);\n  const toggleTodo = useMutation(api.todos.toggle);\n  const removeTodo = useMutation(api.todos.deleteTodo);\n\n  const handleAddTodo = async (e: FormEvent<HTMLFormElement>) => {\n    e.preventDefault();\n    const text = newTodoText.trim();\n    if (text) {\n      setNewTodoText(\"\");\n      try {\n        await createTodo({ text });\n      } catch (error) {\n        console.error(\"Failed to add todo:\", error);\n        setNewTodoText(text);\n      }\n    }\n  };\n\n  const handleToggleTodo = async (id: Id<\"todos\">, completed: boolean) => {\n    try {\n      await toggleTodo({ id, completed: !completed });\n    } catch (error) {\n      console.error(\"Failed to toggle todo:\", error);\n    }\n  };\n\n  const handleDeleteTodo = async (id: Id<\"todos\">) => {\n    try {\n      await removeTodo({ id });\n    } catch (error) {\n      console.error(\"Failed to delete todo:\", error);\n    }\n  };\n  {{else}}\n    {{#if (eq api \"trpc\")}}\n  const trpc = useTRPC();\n    {{/if}}\n    {{#if (eq api \"orpc\")}}\n    {{/if}}\n\n    {{#if (eq api \"trpc\")}}\n  const todos = useQuery(trpc.todo.getAll.queryOptions());\n  const createMutation = useMutation(\n    trpc.todo.create.mutationOptions({\n      onSuccess: () => {\n        todos.refetch();\n        setNewTodoText(\"\");\n      },\n    }),\n  );\n  const toggleMutation = useMutation(\n    trpc.todo.toggle.mutationOptions({\n      onSuccess: () => { todos.refetch() },\n    }),\n  );\n  const deleteMutation = useMutation(\n    trpc.todo.delete.mutationOptions({\n      onSuccess: () => { todos.refetch() },\n    }),\n  );\n    {{/if}}\n    {{#if (eq api \"orpc\")}}\n  const todos = useQuery(orpc.todo.getAll.queryOptions());\n  const createMutation = useMutation(\n    orpc.todo.create.mutationOptions({\n      onSuccess: () => {\n        todos.refetch();\n        setNewTodoText(\"\");\n      },\n    }),\n  );\n  const toggleMutation = useMutation(\n    orpc.todo.toggle.mutationOptions({\n      onSuccess: () => { todos.refetch() },\n    }),\n  );\n  const deleteMutation = useMutation(\n    orpc.todo.delete.mutationOptions({\n      onSuccess: () => { todos.refetch() },\n    }),\n  );\n    {{/if}}\n\n  const handleAddTodo = (e: FormEvent<HTMLFormElement>) => {\n    e.preventDefault();\n    if (newTodoText.trim()) {\n      createMutation.mutate({ text: newTodoText });\n    }\n  };\n\n  const handleToggleTodo = (id: number, completed: boolean) => {\n    toggleMutation.mutate({ id, completed: !completed });\n  };\n\n  const handleDeleteTodo = (id: number) => {\n    deleteMutation.mutate({ id });\n  };\n  {{/if}}\n\n  return (\n    <div className=\"mx-auto w-full max-w-md py-10\">\n      <Card>\n        <CardHeader>\n          <CardTitle>Todo List{{#if (eq backend \"convex\")}} (Convex){{/if}}</CardTitle>\n          <CardDescription>Manage your tasks efficiently</CardDescription>\n        </CardHeader>\n        <CardContent>\n          <form\n            onSubmit={handleAddTodo}\n            className=\"mb-6 flex items-center space-x-2\"\n          >\n            <Input\n              value={newTodoText}\n              onChange={(e) => setNewTodoText(e.target.value)}\n              placeholder=\"Add a new task...\"\n              {{#unless (eq backend \"convex\")}}\n              disabled={createMutation.isPending}\n              {{/unless}}\n            />\n            <Button\n              type=\"submit\"\n              {{#unless (eq backend \"convex\")}}\n              disabled={createMutation.isPending || !newTodoText.trim()}\n              {{else}}\n              disabled={!newTodoText.trim()}\n              {{/unless}}\n            >\n              {{#unless (eq backend \"convex\")}}\n              {createMutation.isPending ? (\n                <Loader2 className=\"h-4 w-4 animate-spin\" />\n              ) : (\n                \"Add\"\n              )}\n              {{else}}\n              Add\n              {{/unless}}\n            </Button>\n          </form>\n\n          {{#if (eq backend \"convex\")}}\n          {todos?.length === 0 ? (\n            <p className=\"py-4 text-center\">No todos yet. Add one above!</p>\n          ) : (\n            <ul className=\"space-y-2\">\n              {todos?.map((todo) => (\n                <li\n                  key={todo._id}\n                  className=\"flex items-center justify-between rounded-md border p-2\"\n                >\n                  <div className=\"flex items-center space-x-2\">\n                    <Checkbox\n                      checked={todo.completed}\n                      onCheckedChange={() =>\n                        handleToggleTodo(todo._id, todo.completed)\n                      }\n                      id={\\`todo-\\${todo._id}\\`}\n                    />\n                    <label\n                      htmlFor={\\`todo-\\${todo._id}\\`}\n                      className={\\`\\${\n                        todo.completed\n                          ? \"text-muted-foreground line-through\"\n                          : \"\"\n                      }\\`}\n                    >\n                      {todo.text}\n                    </label>\n                  </div>\n                  <Button\n                    variant=\"ghost\"\n                    size=\"icon\"\n                    onClick={() => handleDeleteTodo(todo._id)}\n                    aria-label=\"Delete todo\"\n                  >\n                    <Trash2 className=\"h-4 w-4\" />\n                  </Button>\n                </li>\n              ))}\n            </ul>\n          )}\n          {{else}}\n          {todos.isLoading ? (\n            <div className=\"flex justify-center py-4\">\n              <Loader2 className=\"h-6 w-6 animate-spin\" />\n            </div>\n          ) : todos.data?.length === 0 ? (\n            <p className=\"py-4 text-center\">No todos yet. Add one above!</p>\n          ) : (\n            <ul className=\"space-y-2\">\n              {todos.data?.map((todo) => (\n                <li\n                  key={todo.id}\n                  className=\"flex items-center justify-between rounded-md border p-2\"\n                >\n                  <div className=\"flex items-center space-x-2\">\n                    <Checkbox\n                      checked={todo.completed}\n                      onCheckedChange={() =>\n                        handleToggleTodo(todo.id, todo.completed)\n                      }\n                      id={\\`todo-\\${todo.id}\\`}\n                    />\n                    <label\n                      htmlFor={\\`todo-\\${todo.id}\\`}\n                      className={\\`\\${todo.completed ? \"line-through\" : \"\"}\\`}\n                    >\n                      {todo.text}\n                    </label>\n                  </div>\n                  <Button\n                    variant=\"ghost\"\n                    size=\"icon\"\n                    onClick={() => handleDeleteTodo(todo.id)}\n                    aria-label=\"Delete todo\"\n                  >\n                    <Trash2 className=\"h-4 w-4\" />\n                  </Button>\n                </li>\n              ))}\n            </ul>\n          )}\n          {{/if}}\n        </CardContent>\n      </Card>\n    </div>\n  );\n}\n`],\n  [\"examples/todo/web/solid/src/routes/todos.tsx.hbs\", `import { createFileRoute } from \"@tanstack/solid-router\";\nimport { Loader2, Trash2 } from \"lucide-solid\";\nimport { createSignal, For, Show } from \"solid-js\";\nimport { orpc } from \"@/utils/orpc\";\nimport { useQuery, useMutation } from \"@tanstack/solid-query\";\n\nexport const Route = createFileRoute(\"/todos\")({\n  component: TodosRoute,\n});\n\nfunction TodosRoute() {\n  const [newTodoText, setNewTodoText] = createSignal(\"\");\n\n  const todos = useQuery(() => orpc.todo.getAll.queryOptions());\n\n  const createMutation = useMutation(() =>\n    orpc.todo.create.mutationOptions({\n      onSuccess: () => {\n        todos.refetch();\n        setNewTodoText(\"\");\n      },\n    }),\n  );\n\n  const toggleMutation = useMutation(() =>\n    orpc.todo.toggle.mutationOptions({\n      onSuccess: () => { todos.refetch() },\n    }),\n  );\n\n  const deleteMutation = useMutation(() =>\n    orpc.todo.delete.mutationOptions({\n      onSuccess: () => { todos.refetch() },\n    }),\n  );\n\n  const handleAddTodo = (e: Event) => {\n    e.preventDefault();\n    if (newTodoText().trim()) {\n      createMutation.mutate({ text: newTodoText() });\n    }\n  };\n\n  const handleToggleTodo = (id: number, completed: boolean) => {\n    toggleMutation.mutate({ id, completed: !completed });\n  };\n\n  const handleDeleteTodo = (id: number) => {\n    deleteMutation.mutate({ id });\n  };\n\n  return (\n    <div class=\"mx-auto w-full max-w-md py-10\">\n      <div class=\"rounded-lg border p-6 shadow-sm\">\n        <div class=\"mb-4\">\n          <h2 class=\"text-xl font-semibold\">Todo List</h2>\n          <p class=\"text-sm\">Manage your tasks efficiently</p>\n        </div>\n        <div>\n          <form\n            onSubmit={handleAddTodo}\n            class=\"mb-6 flex items-center space-x-2\"\n          >\n            <input\n              type=\"text\"\n              value={newTodoText()}\n              onInput={(e) => setNewTodoText(e.currentTarget.value)}\n              placeholder=\"Add a new task...\"\n              disabled={createMutation.isPending}\n              class=\"w-full rounded-md border p-2 text-sm\"\n            />\n            <button\n              type=\"submit\"\n              disabled={createMutation.isPending || !newTodoText().trim()}\n              class=\"rounded-md bg-blue-600 px-4 py-2 text-sm text-white disabled:opacity-50\"\n            >\n              <Show when={createMutation.isPending} fallback=\"Add\">\n                <Loader2 class=\"h-4 w-4 animate-spin\" />\n              </Show>\n            </button>\n          </form>\n\n          <Show when={todos.isLoading}>\n            <div class=\"flex justify-center py-4\">\n              <Loader2 class=\"h-6 w-6 animate-spin\" />\n            </div>\n          </Show>\n\n          <Show when={!todos.isLoading && todos.data?.length === 0}>\n            <p class=\"py-4 text-center\">No todos yet. Add one above!</p>\n          </Show>\n\n          <Show when={!todos.isLoading}>\n            <ul class=\"space-y-2\">\n              <For each={todos.data}>\n                {(todo) => (\n                  <li class=\"flex items-center justify-between rounded-md border p-2\">\n                    <div class=\"flex items-center space-x-2\">\n                      <input\n                        type=\"checkbox\"\n                        checked={todo.completed}\n                        onChange={() =>\n                          handleToggleTodo(todo.id, todo.completed)\n                        }\n                        id={\\`todo-\\${todo.id}\\`}\n                        class=\"h-4 w-4\"\n                      />\n                      <label\n                        for={\\`todo-\\${todo.id}\\`}\n                        class={todo.completed ? \"line-through\" : \"\"}\n                      >\n                        {todo.text}\n                      </label>\n                    </div>\n                    <button\n                      type=\"button\"\n                      onClick={() => handleDeleteTodo(todo.id)}\n                      aria-label=\"Delete todo\"\n                      class=\"ml-2 rounded-md p-1\"\n                    >\n                      <Trash2 class=\"h-4 w-4\" />\n                    </button>\n                  </li>\n                )}\n              </For>\n            </ul>\n          </Show>\n        </div>\n      </div>\n    </div>\n  );\n}\n`],\n  [\"examples/todo/web/svelte/src/routes/todos/+page.svelte.hbs\", `{{#if (eq backend \"convex\")}}\n<script lang=\"ts\">\n\timport { useQuery, useConvexClient } from 'convex-svelte';\n\timport { api } from '@{{projectName}}/backend/convex/_generated/api';\n\timport type { Id } from '@{{projectName}}/backend/convex/_generated/dataModel';\n\n\tlet newTodoText = $state('');\n\tlet isAdding = $state(false);\n\tlet addError = $state<Error | null>(null);\n\tlet togglingId = $state<Id<'todos'> | null>(null);\n\tlet toggleError = $state<Error | null>(null);\n\tlet deletingId = $state<Id<'todos'> | null>(null);\n\tlet deleteError = $state<Error | null>(null);\n\n\tconst client = useConvexClient();\n\n\tconst todosQuery = useQuery(api.todos.getAll, {});\n\n\tasync function handleAddTodo(event: SubmitEvent) {\n\t\tevent.preventDefault();\n\t\tconst text = newTodoText.trim();\n\t\tif (!text || isAdding) return;\n\n\t\tisAdding = true;\n\t\taddError = null;\n\t\ttry {\n\t\t\tawait client.mutation(api.todos.create, { text });\n\t\t\tnewTodoText = '';\n\t\t} catch (err) {\n\t\t\tconsole.error('Failed to add todo:', err);\n\t\t\taddError = err instanceof Error ? err : new Error(String(err));\n\t\t} finally {\n\t\t\tisAdding = false;\n\t\t}\n\t}\n\n\tasync function handleToggleTodo(id: Id<'todos'>, completed: boolean) {\n\t\tif (togglingId === id || deletingId === id) return;\n\n\t\ttogglingId = id;\n\t\ttoggleError = null;\n\t\ttry {\n\t\t\tawait client.mutation(api.todos.toggle, { id, completed: !completed });\n\t\t} catch (err) {\n\t\t\tconsole.error('Failed to toggle todo:', err);\n\t\t\ttoggleError = err instanceof Error ? err : new Error(String(err));\n\t\t} finally {\n\t\t\tif (togglingId === id) {\n\t\t\t\ttogglingId = null;\n\t\t\t}\n\t\t}\n\t}\n\n\tasync function handleDeleteTodo(id: Id<'todos'>) {\n\t\tif (togglingId === id || deletingId === id) return;\n\n\t\tdeletingId = id;\n\t\tdeleteError = null;\n\t\ttry {\n\t\t\tawait client.mutation(api.todos.deleteTodo, { id });\n\t\t} catch (err) {\n\t\t\tconsole.error('Failed to delete todo:', err);\n\t\t\tdeleteError = err instanceof Error ? err : new Error(String(err));\n\t\t} finally {\n\t\t\tif (deletingId === id) {\n\t\t\t\tdeletingId = null;\n\t\t\t}\n\t\t}\n\t}\n\n\tconst canAdd = $derived(!isAdding && newTodoText.trim().length > 0);\n\tconst isLoadingTodos = $derived(todosQuery.isLoading);\n\tconst todos = $derived(todosQuery.data ?? []);\n\tconst hasTodos = $derived(todos.length > 0);\n\n</script>\n\n<div class=\"p-4\">\n\t<h1 class=\"text-xl mb-4\">Todos (Convex)</h1>\n\n\t<form onsubmit={handleAddTodo} class=\"flex gap-2 mb-4\">\n\t\t<input\n\t\t\ttype=\"text\"\n\t\t\tbind:value={newTodoText}\n\t\t\tplaceholder=\"New task...\"\n\t\t\tdisabled={isAdding}\n\t\t\tclass=\"p-1 flex-grow\"\n\t\t/>\n\t\t<button\n\t\t\ttype=\"submit\"\n\t\t\tdisabled={!canAdd}\n\t\t\tclass=\"bg-blue-500 text-white px-3 py-1 rounded disabled:opacity-50\"\n\t\t>\n\t\t\t{#if isAdding}Adding...{:else}Add{/if}\n\t\t</button>\n\t</form>\n\n\t{#if isLoadingTodos}\n\t\t<p>Loading...</p>\n\t{:else if !hasTodos}\n\t\t<p>No todos yet.</p>\n\t{:else}\n\t\t<ul class=\"space-y-1\">\n\t\t\t{#each todos as todo (todo._id)}\n\t\t\t\t{@const isTogglingThis = togglingId === todo._id}\n\t\t\t\t{@const isDeletingThis = deletingId === todo._id}\n\t\t\t\t{@const isDisabled = isTogglingThis || isDeletingThis}\n\t\t\t\t<li\n\t\t\t\t\tclass=\"flex items-center justify-between p-2\"\n\t\t\t\t\tclass:opacity-50={isDisabled}\n\t\t\t\t>\n\t\t\t\t\t<div class=\"flex items-center gap-2\">\n\t\t\t\t\t\t<input\n\t\t\t\t\t\t\ttype=\"checkbox\"\n\t\t\t\t\t\t\tid={\\`todo-\\${todo._id}\\`}\n\t\t\t\t\t\t\tchecked={todo.completed}\n\t\t\t\t\t\t\tonchange={() => handleToggleTodo(todo._id, todo.completed)}\n\t\t\t\t\t\t\tdisabled={isDisabled}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<label\n\t\t\t\t\t\t\tfor={\\`todo-\\${todo._id}\\`}\n\t\t\t\t\t\t\tclass:line-through={todo.completed}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t{todo.text}\n\t\t\t\t\t\t</label>\n\t\t\t\t\t</div>\n\t\t\t\t\t<button\n\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\tonclick={() => handleDeleteTodo(todo._id)}\n\t\t\t\t\t\tdisabled={isDisabled}\n\t\t\t\t\t\taria-label=\"Delete todo\"\n\t\t\t\t\t\tclass=\"text-red-500 px-1 disabled:opacity-50\"\n\t\t\t\t\t>\n\t\t\t\t\t\t{#if isDeletingThis}Deleting...{:else}X{/if}\n\t\t\t\t\t</button>\n\t\t\t\t</li>\n\t\t\t{/each}\n\t\t</ul>\n\t{/if}\n\n\t{#if todosQuery.error}\n\t\t<p class=\"mt-4 text-red-500\">\n\t\t\tError loading: {todosQuery.error?.message ?? 'Unknown error'}\n\t\t</p>\n\t{/if}\n\t{#if addError}\n\t\t<p class=\"mt-4 text-red-500\">\n\t\t\tError adding: {addError.message ?? 'Unknown error'}\n\t\t</p>\n\t{/if}\n\t{#if toggleError}\n\t\t<p class=\"mt-4 text-red-500\">\n\t\t\tError updating: {toggleError.message ?? 'Unknown error'}\n\t\t</p>\n\t{/if}\n\t{#if deleteError}\n\t\t<p class=\"mt-4 text-red-500\">\n\t\t\tError deleting: {deleteError.message ?? 'Unknown error'}\n\t\t</p>\n\t{/if}\n</div>\n{{else}}\n<script lang=\"ts\">\n\t{{#if (eq api \"orpc\")}}\n\timport { orpc } from '$lib/orpc';\n\t{{/if}}\n\timport { createQuery, createMutation } from '@tanstack/svelte-query';\n\n\tlet newTodoText = $state('');\n\n\t{{#if (eq api \"orpc\")}}\n\tconst todosQuery = createQuery(orpc.todo.getAll.queryOptions());\n\n\tconst addMutation = createMutation(\n\t\torpc.todo.create.mutationOptions({\n\t\t\tonSuccess: () => {\n\t\t\t\t$todosQuery.refetch();\n\t\t\t\tnewTodoText = '';\n\t\t\t},\n\t\t\tonError: (error) => {\n\t\t\t\tconsole.error('Failed to create todo:', error?.message ?? error);\n\t\t\t},\n\t\t})\n\t);\n\n\tconst toggleMutation = createMutation(\n\t\torpc.todo.toggle.mutationOptions({\n\t\t\tonSuccess: () => {\n\t\t\t\t$todosQuery.refetch();\n\t\t\t},\n\t\t\tonError: (error) => {\n\t\t\t\tconsole.error('Failed to toggle todo:', error?.message ?? error);\n\t\t\t},\n\t\t})\n\t);\n\n\tconst deleteMutation = createMutation(\n\t\torpc.todo.delete.mutationOptions({\n\t\t\tonSuccess: () => {\n\t\t\t\t$todosQuery.refetch();\n\t\t\t},\n\t\t\tonError: (error) => {\n\t\t\t\tconsole.error('Failed to delete todo:', error?.message ?? error);\n\t\t\t},\n\t\t})\n\t);\n\t{{/if}}\n\n\tfunction handleAddTodo(event: SubmitEvent) {\n\t\tevent.preventDefault();\n\t\tconst text = newTodoText.trim();\n\t\tif (text) {\n\t\t\t$addMutation.mutate({ text });\n\t\t}\n\t}\n\n\tfunction handleToggleTodo(id: number, completed: boolean) {\n\t\t$toggleMutation.mutate({ id, completed: !completed });\n\t}\n\n\tfunction handleDeleteTodo(id: number) {\n\t\t$deleteMutation.mutate({ id });\n\t}\n\n\tconst isAdding = $derived($addMutation.isPending);\n\tconst canAdd = $derived(!isAdding && newTodoText.trim().length > 0);\n\tconst isLoadingTodos = $derived($todosQuery.isLoading);\n\tconst todos = $derived($todosQuery.data ?? []);\n\tconst hasTodos = $derived(todos.length > 0);\n\n</script>\n\n<div class=\"p-4\">\n\t<h1 class=\"text-xl mb-4\">Todos{{#if (eq api \"orpc\")}} (oRPC){{/if}}</h1>\n\n\t<form onsubmit={handleAddTodo} class=\"flex gap-2 mb-4\">\n\t\t<input\n\t\t\ttype=\"text\"\n\t\t\tbind:value={newTodoText}\n\t\t\tplaceholder=\"New task...\"\n\t\t\tdisabled={isAdding}\n\t\t\tclass=\" p-1 flex-grow\"\n\t\t/>\n\t\t<button\n\t\t\ttype=\"submit\"\n\t\t\tdisabled={!canAdd}\n\t\t\tclass=\"bg-blue-500 text-white px-3 py-1 rounded disabled:opacity-50\"\n\t\t>\n\t\t\t{#if isAdding}Adding...{:else}Add{/if}\n\t\t</button>\n\t</form>\n\n\t{#if isLoadingTodos}\n\t\t<p>Loading...</p>\n\t{:else if !hasTodos}\n\t\t<p>No todos yet.</p>\n\t{:else}\n\t\t<ul class=\"space-y-1\">\n\t\t\t{#each todos as todo (todo.id)}\n\t\t\t\t{@const isToggling = $toggleMutation.isPending && $toggleMutation.variables?.id === todo.id}\n\t\t\t\t{@const isDeleting = $deleteMutation.isPending && $deleteMutation.variables?.id === todo.id}\n\t\t\t\t{@const isDisabled = isToggling || isDeleting}\n\t\t\t\t<li\n\t\t\t\t\tclass=\"flex items-center justify-between p-2 \"\n\t\t\t\t\tclass:opacity-50={isDisabled}\n\t\t\t\t>\n\t\t\t\t\t<div class=\"flex items-center gap-2\">\n\t\t\t\t\t\t<input\n\t\t\t\t\t\t\ttype=\"checkbox\"\n\t\t\t\t\t\t\tid={\\`todo-\\${todo.id}\\`}\n\t\t\t\t\t\t\tchecked={todo.completed}\n\t\t\t\t\t\t\tonchange={() => handleToggleTodo(todo.id, todo.completed)}\n\t\t\t\t\t\t\tdisabled={isDisabled}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<label\n\t\t\t\t\t\t\tfor={\\`todo-\\${todo.id}\\`}\n\t\t\t\t\t\t\tclass:line-through={todo.completed}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t{todo.text}\n\t\t\t\t\t\t</label>\n\t\t\t\t\t</div>\n\t\t\t\t\t<button\n\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\tonclick={() => handleDeleteTodo(todo.id)}\n\t\t\t\t\t\tdisabled={isDisabled}\n\t\t\t\t\t\taria-label=\"Delete todo\"\n\t\t\t\t\t\tclass=\"text-red-500 px-1 disabled:opacity-50\"\n\t\t\t\t\t>\n\t\t\t\t\t\t{#if isDeleting}Deleting...{:else}X{/if}\n\t\t\t\t\t</button>\n\t\t\t\t</li>\n\t\t\t{/each}\n\t\t</ul>\n\t{/if}\n\n\t{#if $todosQuery.isError}\n\t\t<p class=\"mt-4 text-red-500\">\n\t\t\tError loading: {$todosQuery.error?.message ?? 'Unknown error'}\n\t\t</p>\n\t{/if}\n\t{#if $addMutation.isError}\n\t\t<p class=\"mt-4 text-red-500\">\n\t\t\tError adding: {$addMutation.error?.message ?? 'Unknown error'}\n\t\t</p>\n\t{/if}\n\t{#if $toggleMutation.isError}\n\t\t<p class=\"mt-4 text-red-500\">\n\t\t\tError updating: {$toggleMutation.error?.message ?? 'Unknown error'}\n\t\t</p>\n\t{/if}\n\t{#if $deleteMutation.isError}\n\t\t<p class=\"mt-4 text-red-500\">\n\t\t\tError deleting: {$deleteMutation.error?.message ?? 'Unknown error'}\n\t\t</p>\n\t{/if}\n</div>\n{{/if}}\n`],\n  [\"extras/_npmrc.hbs\", `node-linker=isolated\n{{#if (includes frontend \"nuxt\")}}\nshamefully-hoist=true\nstrict-peer-dependencies=false\n{{/if}}`],\n  [\"extras/bunfig.toml.hbs\", `[install]\n{{#if (or (includes frontend \"nuxt\"))}}\nlinker = \"hoisted\" # having issues with Nuxt when linker is isolated\n{{else}}\nlinker = \"isolated\"\n{{/if}}`],\n  [\"extras/env.d.ts.hbs\", `{{#if (eq serverDeploy \"cloudflare\")}}\nimport { type server } from \"@{{projectName}}/infra/alchemy.run\";\n{{else}}\nimport { type web as server } from \"@{{projectName}}/infra/alchemy.run\";\n{{/if}}\n\n// This file infers types for the cloudflare:workers environment from your Alchemy Worker.\n// @see https://alchemy.run/concepts/bindings/#type-safe-bindings\n\nexport type CloudflareEnv = typeof server.Env;\n\ndeclare global {\n  type Env = CloudflareEnv;\n}\n\ndeclare module \"cloudflare:workers\" {\n  namespace Cloudflare {\n    export interface Env extends CloudflareEnv {}\n  }\n}\n`],\n  [\"extras/pnpm-workspace.yaml\", `packages:\n  - \"apps/*\"\n  - \"packages/*\"\n`],\n  [\"frontend/astro/_gitignore\", `# build output\ndist/\n\n# generated types\n.astro/\n\n# dependencies\nnode_modules/\n\n# logs\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\npnpm-debug.log*\n\n# environment variables\n.env\n.env.production\n\n# macOS-specific files\n.DS_Store\n\n# jetbrains setting folder\n.idea/\n`],\n  [\"frontend/astro/astro.config.mjs.hbs\", `// @ts-check\nimport tailwindcss from \"@tailwindcss/vite\";\nimport { defineConfig, envField } from \"astro/config\";\nimport node from \"@astrojs/node\";\n\n// https://astro.build/config\nexport default defineConfig({\n  output: \"server\",\n  adapter: node({ mode: \"standalone\" }),\n{{#if (ne backend \"self\")}}\n  env: {\n    schema: {\n      PUBLIC_SERVER_URL: envField.string({\n        access: \"public\",\n        context: \"client\",\n        default: \"http://localhost:3000\",\n      }),\n    },\n  },\n{{/if}}\n  vite: {\n    plugins: [tailwindcss()],\n  },\n});\n`],\n  [\"frontend/astro/package.json.hbs\", `{\n  \"name\": \"web\",\n  \"type\": \"module\",\n  \"private\": true,\n  \"version\": \"0.0.1\",\n  \"scripts\": {\n    \"dev\": \"astro dev\",\n    \"build\": \"astro build\",\n    \"preview\": \"astro preview\",\n    \"astro\": \"astro\"\n  },\n  \"dependencies\": {\n    \"astro\": \"^6.0.1\"\n  },\n  \"devDependencies\": {\n    \"@tailwindcss/vite\": \"^4.1.18\",\n    \"tailwindcss\": \"^4.1.18\"\n  }\n}\n`],\n  [\"frontend/astro/public/favicon.svg\", `<svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 128 128\">\n    <path d=\"M50.4 78.5a75.1 75.1 0 0 0-28.5 6.9l24.2-65.7c.7-2 1.9-3.2 3.4-3.2h29c1.5 0 2.7 1.2 3.4 3.2l24.2 65.7s-11.6-7-28.5-7L67 45.5c-.4-1.7-1.6-2.8-2.9-2.8-1.3 0-2.5 1.1-2.9 2.7L50.4 78.5Zm-1.1 28.2Zm-4.2-20.2c-2 6.6-.6 15.8 4.2 20.2a17.5 17.5 0 0 1 .2-.7 5.5 5.5 0 0 1 5.7-4.5c2.8.1 4.3 1.5 4.7 4.7.2 1.1.2 2.3.2 3.5v.4c0 2.7.7 5.2 2.2 7.4a13 13 0 0 0 5.7 4.9v-.3l-.2-.3c-1.8-5.6-.5-9.5 4.4-12.8l1.5-1a73 73 0 0 0 3.2-2.2 16 16 0 0 0 6.8-11.4c.3-2 .1-4-.6-6l-.8.6-1.6 1a37 37 0 0 1-22.4 2.7c-5-.7-9.7-2-13.2-6.2Z\" />\n    <style>\n        path { fill: #000; }\n        @media (prefers-color-scheme: dark) {\n            path { fill: #FFF; }\n        }\n    </style>\n</svg>\n`],\n  [\"frontend/astro/src/components/Header.astro.hbs\", `---\nconst links = [\n  { to: \"/\", label: \"Home\" },\n  {{#if (eq auth \"better-auth\")}}\n  { to: \"/dashboard\", label: \"Dashboard\" },\n  {{/if}}\n  {{#if (includes examples \"todo\")}}\n  { to: \"/todos\", label: \"Todos\" },\n  {{/if}}\n  {{#if (includes examples \"ai\")}}\n  { to: \"/ai\", label: \"AI Chat\" },\n  {{/if}}\n];\n---\n\n<div>\n  <div class=\"flex flex-row items-center justify-between px-4 py-2 md:px-6\">\n    <nav class=\"flex gap-4 text-lg\">\n      {links.map((link) => (\n        <a href={link.to} class=\"text-white hover:text-neutral-400 transition-colors\">\n          {link.label}\n        </a>\n      ))}\n    </nav>\n    <div class=\"flex items-center gap-2\" id=\"user-menu-container\">\n      {{#if (eq auth \"better-auth\")}}\n      <a href=\"/login\" id=\"login-link\" class=\"rounded px-3 py-1.5 text-sm bg-indigo-600 hover:bg-indigo-700 text-white transition-colors\">\n        Sign In\n      </a>\n      <div id=\"user-menu\" class=\"hidden flex items-center gap-3\">\n        <span class=\"text-sm text-neutral-400 hidden sm:inline\" id=\"user-display\"></span>\n        <button\n          id=\"signout-button\"\n          class=\"rounded px-3 py-1.5 text-sm bg-red-600 hover:bg-red-700 text-white transition-colors\"\n        >\n          Sign Out\n        </button>\n      </div>\n      {{/if}}\n    </div>\n  </div>\n  <hr class=\"border-neutral-800\" />\n</div>\n\n{{#if (eq auth \"better-auth\")}}\n<script>\n  import { authClient } from \"../lib/auth-client\";\n\n  const loginLink = document.getElementById(\"login-link\");\n  const userMenu = document.getElementById(\"user-menu\");\n  const userDisplay = document.getElementById(\"user-display\");\n  const signOutButton = document.getElementById(\"signout-button\");\n\n  async function checkSession() {\n    try {\n      const { data: session } = await authClient.getSession();\n      if (session?.user) {\n        loginLink?.classList.add(\"hidden\");\n        userMenu?.classList.remove(\"hidden\");\n        if (userDisplay) {\n          userDisplay.textContent = session.user.name || session.user.email?.split(\"@\")[0] || \"User\";\n        }\n      }\n    } catch (e) {\n      // Not logged in\n    }\n  }\n\n  signOutButton?.addEventListener(\"click\", async () => {\n    await authClient.signOut({\n      fetchOptions: {\n        onSuccess: () => {\n          window.location.href = \"/\";\n        },\n      },\n    });\n  });\n\n  checkSession();\n</script>\n{{/if}}\n`],\n  [\"frontend/astro/src/layouts/Layout.astro.hbs\", `---\nimport \"../styles/global.css\";\nimport Header from \"../components/Header.astro\";\n\ninterface Props {\n  title?: string;\n}\n\nconst { title = \"{{projectName}}\" } = Astro.props;\n---\n\n<!doctype html>\n<html lang=\"en\" class=\"dark\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <link rel=\"icon\" type=\"image/svg+xml\" href=\"/favicon.svg\" />\n    <meta name=\"generator\" content={Astro.generator} />\n    <title>{title}</title>\n  </head>\n  <body class=\"min-h-screen bg-neutral-950 text-white\">\n    <Header />\n    <slot />\n  </body>\n</html>\n`],\n  [\"frontend/astro/src/pages/index.astro.hbs\", `---\nimport Layout from \"../layouts/Layout.astro\";\n{{#if (eq api \"orpc\")}}\nimport { orpc } from \"../lib/orpc\";\n{{/if}}\n\nconst TITLE_TEXT = \\`\n ██████╗ ███████╗████████╗████████╗███████╗██████╗\n ██╔══██╗██╔════╝╚══██╔══╝╚══██╔══╝██╔════╝██╔══██╗\n ██████╔╝█████╗     ██║      ██║   █████╗  ██████╔╝\n ██╔══██╗██╔══╝     ██║      ██║   ██╔══╝  ██╔══██╗\n ██████╔╝███████╗   ██║      ██║   ███████╗██║  ██║\n ╚═════╝ ╚══════╝   ╚═╝      ╚═╝   ╚══════╝╚═╝  ╚═╝\n\n ████████╗    ███████╗████████╗ █████╗  ██████╗██╗  ██╗\n ╚══██╔══╝    ██╔════╝╚══██╔══╝██╔══██╗██╔════╝██║ ██╔╝\n    ██║       ███████╗   ██║   ███████║██║     █████╔╝\n    ██║       ╚════██║   ██║   ██╔══██║██║     ██╔═██╗\n    ██║       ███████║   ██║   ██║  ██║╚██████╗██║  ██╗\n    ╚═╝       ╚══════╝   ╚═╝   ╚═╝  ╚═╝ ╚═════╝╚═╝  ╚═╝\n \\`;\n---\n\n<Layout title=\"{{projectName}}\">\n  <div class=\"container mx-auto max-w-3xl px-4 py-2\">\n    <pre class=\"overflow-x-auto font-mono text-sm text-white\">{TITLE_TEXT}</pre>\n    <div class=\"grid gap-6\">\n      {{#if (eq api \"orpc\")}}\n      <section class=\"rounded-lg border border-neutral-700 p-4\">\n        <h2 class=\"mb-2 font-medium text-white\">API Status</h2>\n        <div class=\"flex items-center gap-2\" id=\"api-status\">\n          <div class=\"h-2 w-2 rounded-full bg-orange-400\" id=\"status-dot\"></div>\n          <span class=\"text-sm text-neutral-400\" id=\"status-text\">Checking...</span>\n        </div>\n      </section>\n      {{/if}}\n    </div>\n  </div>\n</Layout>\n\n{{#if (eq api \"orpc\")}}\n<script>\n  import { orpc } from \"../lib/orpc\";\n\n  const statusDot = document.getElementById(\"status-dot\")!;\n  const statusText = document.getElementById(\"status-text\")!;\n\n  async function checkHealth() {\n    try {\n      const data = await orpc.healthCheck();\n      statusDot.className = \"h-2 w-2 rounded-full bg-green-500\";\n      statusText.textContent = \"Connected\";\n    } catch (error) {\n      statusDot.className = \"h-2 w-2 rounded-full bg-red-500\";\n      statusText.textContent = \"Disconnected\";\n    }\n  }\n\n  checkHealth();\n</script>\n{{/if}}\n`],\n  [\"frontend/astro/src/styles/global.css\", `@import \"tailwindcss\";\n\n/* Add your theme customizations here. */\n/* See the TailwindCSS v4 docs for more info: https://tailwindcss.com/docs/v4-beta */\n\n@theme {\n  --font-sans: \"Inter\", sans-serif;\n}\n`],\n  [\"frontend/astro/tsconfig.json.hbs\", `{\n  \"extends\": \"astro/tsconfigs/strict\",\n  \"include\": [\".astro/types.d.ts\", \"**/*\"],\n  \"exclude\": [\"dist\"]\n}\n`],\n  [\"frontend/native/bare/_gitignore\", `node_modules/\n.expo/\ndist/\nnpm-debug.*\n*.jks\n*.p8\n*.p12\n*.key\n*.mobileprovision\n*.orig.*\nweb-build/\n\n# macOS\n.DS_Store\n\n# Temporary files created by Metro to check the health of the file watcher\n.metro-health-check*\n\n`],\n  [\"frontend/native/bare/app.json.hbs\", `{\n\t\"expo\": {\n\t\t\"name\": \"{{projectName}}\",\n\t\t\"slug\": \"{{projectName}}\",\n\t\t\"version\": \"1.0.0\",\n\t\t\"orientation\": \"portrait\",\n\t\t\"icon\": \"./assets/images/icon.png\",\n\t\t\"scheme\": \"{{projectName}}\",\n\t\t\"userInterfaceStyle\": \"automatic\",\n\t\t\"ios\": {\n\t\t\t\"supportsTablet\": true\n\t\t},\n\t\t\"android\": {\n\t\t\t\"adaptiveIcon\": {\n\t\t\t\t\"backgroundColor\": \"#E6F4FE\",\n\t\t\t\t\"foregroundImage\": \"./assets/images/android-icon-foreground.png\",\n\t\t\t\t\"backgroundImage\": \"./assets/images/android-icon-background.png\",\n\t\t\t\t\"monochromeImage\": \"./assets/images/android-icon-monochrome.png\"\n\t\t\t},\n\t\t\t\"predictiveBackGestureEnabled\": false,\n\t\t\t\"package\": \"com.anonymous.mybettertapp\"\n\t\t},\n\t\t\"web\": {\n\t\t\t\"output\": \"static\",\n\t\t\t\"favicon\": \"./assets/images/favicon.png\"\n\t\t},\n\t\t\"plugins\": [\n\t\t\t\"expo-router\",\n\t\t\t[\n\t\t\t\t\"expo-splash-screen\",\n\t\t\t\t{\n\t\t\t\t\t\"image\": \"./assets/images/splash-icon.png\",\n\t\t\t\t\t\"imageWidth\": 200,\n\t\t\t\t\t\"resizeMode\": \"contain\",\n\t\t\t\t\t\"backgroundColor\": \"#ffffff\",\n\t\t\t\t\t\"dark\": {\n\t\t\t\t\t\t\"backgroundColor\": \"#000000\"\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t]\n\t\t],\n\t\t\"experiments\": {\n\t\t\t\"typedRoutes\": true,\n\t\t\t\"reactCompiler\": true\n\t\t}\n\t}\n}\n`],\n  [\"frontend/native/bare/app/_layout.tsx.hbs\", `{{#if (includes examples \"ai\")}}\nimport \"@/polyfills\";\n{{/if}}\n{{#if (and (eq auth \"clerk\") (ne api \"none\") (ne backend \"convex\"))}}\nimport { useEffect } from \"react\";\nimport { setClerkAuthTokenGetter } from \"@/utils/clerk-auth\";\n{{/if}}\n{{#if (and (ne backend \"convex\") (eq auth \"clerk\"))}}\nimport { ClerkProvider{{#unless (eq api \"none\")}}, useAuth{{/unless}} } from \"@clerk/expo\";\nimport { tokenCache } from \"@clerk/expo/token-cache\";\nimport { env } from \"@{{projectName}}/env/native\";\n{{/if}}\n\n{{#if (eq backend \"convex\")}}\n  {{#if (eq auth \"better-auth\")}}\n    import { ConvexReactClient } from \"convex/react\";\n    import { ConvexBetterAuthProvider } from \"@convex-dev/better-auth/react\";\n    import { authClient } from \"@/lib/auth-client\";\n    import { env } from \"@{{projectName}}/env/native\";\n  {{else}}\n    import { ConvexProvider, ConvexReactClient } from \"convex/react\";\n    import { env } from \"@{{projectName}}/env/native\";\n  {{/if}}\n  {{#if (eq auth \"clerk\")}}\n    import { ClerkProvider, useAuth } from \"@clerk/expo\";\n    import { ConvexProviderWithClerk } from \"convex/react-clerk\";\n    import { tokenCache } from \"@clerk/expo/token-cache\";\n  {{/if}}\n{{else}}\n  {{#unless (eq api \"none\")}}\n    import { QueryClientProvider } from \"@tanstack/react-query\";\n  {{/unless}}\n{{/if}}\n\nimport { Stack } from \"expo-router\";\nimport {\n  DarkTheme,\n  DefaultTheme,\n  type Theme,\n  ThemeProvider,\n} from \"@react-navigation/native\";\nimport { StatusBar } from \"expo-status-bar\";\nimport { GestureHandlerRootView } from \"react-native-gesture-handler\";\n{{#if (eq api \"trpc\")}}\nimport { queryClient } from \"@/utils/trpc\";\n{{/if}}\n{{#if (eq api \"orpc\")}}\nimport { queryClient } from \"@/utils/orpc\";\n{{/if}}\nimport { NAV_THEME } from \"@/lib/constants\";\nimport { useColorScheme } from \"@/lib/use-color-scheme\";\nimport { StyleSheet } from \"react-native\";\n\nconst LIGHT_THEME: Theme = {\n  ...DefaultTheme,\n  colors: NAV_THEME.light,\n};\nconst DARK_THEME: Theme = {\n  ...DarkTheme,\n  colors: NAV_THEME.dark,\n};\n\nexport const unstable_settings = {\n  initialRouteName: \"(drawer)\",\n};\n\n{{#if (eq backend \"convex\")}}\nconst convex = new ConvexReactClient(env.EXPO_PUBLIC_CONVEX_URL, {\n  {{#if (eq auth \"better-auth\")}}\n  expectAuth: true,\n  {{/if}}\n  unsavedChangesWarning: false,\n});\n{{/if}}\n\nconst styles = StyleSheet.create({\n  container: {\n    flex: 1,\n  },\n});\n\n{{#if (and (eq auth \"clerk\") (ne api \"none\") (ne backend \"convex\"))}}\nfunction ClerkApiAuthBridge() {\n  const { getToken } = useAuth();\n\n  useEffect(() => {\n    setClerkAuthTokenGetter(getToken);\n\n    return () => {\n      setClerkAuthTokenGetter(null);\n    };\n  }, [getToken]);\n\n  return null;\n}\n{{/if}}\n\nexport default function RootLayout() {\n  const { isDarkColorScheme } = useColorScheme();\n\n  return (\n    <>\n      {{#if (eq backend \"convex\")}}\n        {{#if (eq auth \"clerk\")}}\n          <ClerkProvider tokenCache={tokenCache} publishableKey={env.EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY}>\n            <ConvexProviderWithClerk client={convex} useAuth={useAuth}>\n              <ThemeProvider value={isDarkColorScheme ? DARK_THEME : LIGHT_THEME}>\n                <StatusBar style={isDarkColorScheme ? \"light\" : \"dark\"} />\n                <GestureHandlerRootView style={styles.container}>\n                  <Stack>\n                    <Stack.Screen name=\"(drawer)\" options=\\\\{{ headerShown: false }} />\n                    <Stack.Screen name=\"(auth)\" options=\\\\{{ headerShown: false }} />\n                    <Stack.Screen name=\"modal\" options=\\\\{{ title: \"Modal\", presentation: \"modal\" }} />\n                  </Stack>\n                </GestureHandlerRootView>\n              </ThemeProvider>\n            </ConvexProviderWithClerk>\n          </ClerkProvider>\n        {{else if (eq auth \"better-auth\")}}\n          <ConvexBetterAuthProvider client={convex} authClient={authClient}>\n            <ThemeProvider value={isDarkColorScheme ? DARK_THEME : LIGHT_THEME}>\n              <StatusBar style={isDarkColorScheme ? \"light\" : \"dark\"} />\n              <GestureHandlerRootView style={styles.container}>\n                <Stack>\n                  <Stack.Screen name=\"(drawer)\" options=\\\\{{ headerShown: false }} />\n                  <Stack.Screen name=\"modal\" options=\\\\{{ title: \"Modal\", presentation: \"modal\" }} />\n                </Stack>\n              </GestureHandlerRootView>\n            </ThemeProvider>\n          </ConvexBetterAuthProvider>\n        {{else}}\n          <ConvexProvider client={convex}>\n            <ThemeProvider value={isDarkColorScheme ? DARK_THEME : LIGHT_THEME}>\n              <StatusBar style={isDarkColorScheme ? \"light\" : \"dark\"} />\n              <GestureHandlerRootView style={styles.container}>\n                <Stack>\n                  <Stack.Screen name=\"(drawer)\" options=\\\\{{ headerShown: false }} />\n                  <Stack.Screen name=\"modal\" options=\\\\{{ title: \"Modal\", presentation: \"modal\" }} />\n                </Stack>\n              </GestureHandlerRootView>\n            </ThemeProvider>\n          </ConvexProvider>\n        {{/if}}\n      {{else}}\n        {{#if (eq auth \"clerk\")}}\n          <ClerkProvider tokenCache={tokenCache} publishableKey={env.EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY}>\n            {{#unless (eq api \"none\")}}\n              <ClerkApiAuthBridge />\n              <QueryClientProvider client={queryClient}>\n                <ThemeProvider value={isDarkColorScheme ? DARK_THEME : LIGHT_THEME}>\n                  <StatusBar style={isDarkColorScheme ? \"light\" : \"dark\"} />\n                  <GestureHandlerRootView style={styles.container}>\n                    <Stack>\n                      <Stack.Screen name=\"(drawer)\" options=\\\\{{ headerShown: false }} />\n                      <Stack.Screen name=\"(auth)\" options=\\\\{{ headerShown: false }} />\n                      <Stack.Screen name=\"modal\" options=\\\\{{ title: \"Modal\", presentation: \"modal\" }} />\n                    </Stack>\n                  </GestureHandlerRootView>\n                </ThemeProvider>\n              </QueryClientProvider>\n            {{else}}\n              <ThemeProvider value={isDarkColorScheme ? DARK_THEME : LIGHT_THEME}>\n                <StatusBar style={isDarkColorScheme ? \"light\" : \"dark\"} />\n                <GestureHandlerRootView style={styles.container}>\n                  <Stack>\n                    <Stack.Screen name=\"(drawer)\" options=\\\\{{ headerShown: false }} />\n                    <Stack.Screen name=\"(auth)\" options=\\\\{{ headerShown: false }} />\n                    <Stack.Screen name=\"modal\" options=\\\\{{ title: \"Modal\", presentation: \"modal\" }} />\n                  </Stack>\n                </GestureHandlerRootView>\n              </ThemeProvider>\n            {{/unless}}\n          </ClerkProvider>\n        {{else}}\n          {{#unless (eq api \"none\")}}\n            <QueryClientProvider client={queryClient}>\n              <ThemeProvider value={isDarkColorScheme ? DARK_THEME : LIGHT_THEME}>\n                <StatusBar style={isDarkColorScheme ? \"light\" : \"dark\"} />\n                <GestureHandlerRootView style={styles.container}>\n                  <Stack>\n                    <Stack.Screen name=\"(drawer)\" options=\\\\{{ headerShown: false }} />\n                    <Stack.Screen name=\"modal\" options=\\\\{{ title: \"Modal\", presentation: \"modal\" }} />\n                  </Stack>\n                </GestureHandlerRootView>\n              </ThemeProvider>\n            </QueryClientProvider>\n          {{else}}\n            <ThemeProvider value={isDarkColorScheme ? DARK_THEME : LIGHT_THEME}>\n              <StatusBar style={isDarkColorScheme ? \"light\" : \"dark\"} />\n              <GestureHandlerRootView style={styles.container}>\n                <Stack>\n                  <Stack.Screen name=\"(drawer)\" options=\\\\{{ headerShown: false }} />\n                  <Stack.Screen name=\"modal\" options=\\\\{{ title: \"Modal\", presentation: \"modal\" }} />\n                </Stack>\n              </GestureHandlerRootView>\n            </ThemeProvider>\n          {{/unless}}\n        {{/if}}\n      {{/if}}\n    </>\n  );\n}\n`],\n  [\"frontend/native/bare/app/(drawer)/_layout.tsx.hbs\", `import { Ionicons, MaterialIcons } from \"@expo/vector-icons\";\nimport { Link } from \"expo-router\";\nimport { Drawer } from \"expo-router/drawer\";\nimport { useColorScheme } from \"@/lib/use-color-scheme\";\nimport { NAV_THEME } from \"@/lib/constants\";\n\nimport { HeaderButton } from \"@/components/header-button\";\n\nconst DrawerLayout = () => {\n  const { colorScheme } = useColorScheme();\n  const theme = colorScheme === \"dark\" ? NAV_THEME.dark : NAV_THEME.light;\n\n  return (\n    <Drawer\n      screenOptions=\\\\{{\n        headerStyle: {\n          backgroundColor: theme.background,\n        },\n        headerTitleStyle: {\n          color: theme.text,\n        },\n        headerTintColor: theme.text,\n        drawerStyle: {\n          backgroundColor: theme.background,\n        },\n        drawerLabelStyle: {\n          color: theme.text,\n        },\n        drawerInactiveTintColor: theme.text,\n      }}\n    >\n      <Drawer.Screen\n        name=\"index\"\n        options=\\\\{{\n          headerTitle: \"Home\",\n          drawerLabel: \"Home\",\n          drawerIcon: ({ size, color }) => (\n            <Ionicons name=\"home-outline\" size={size} color={color} />\n          ),\n        }}\n      />\n      <Drawer.Screen\n        name=\"(tabs)\"\n        options=\\\\{{\n          headerTitle: \"Tabs\",\n          drawerLabel: \"Tabs\",\n          drawerIcon: ({ size, color }) => (\n            <MaterialIcons name=\"border-bottom\" size={size} color={color} />\n          ),\n          headerRight: () => (\n            <Link href=\"/modal\" asChild>\n              <HeaderButton />\n            </Link>\n          ),\n        }}\n      />\n      {{#if (includes examples \"todo\")}}\n      <Drawer.Screen\n        name=\"todos\"\n        options=\\\\{{\n          headerTitle: \"Todos\",\n          drawerLabel: \"Todos\",\n          drawerIcon: ({ size, color }) => (\n            <Ionicons name=\"checkbox-outline\" size={size} color={color} />\n          ),\n        }}\n      />\n      {{/if}}\n      {{#if (includes examples \"ai\")}}\n      <Drawer.Screen\n        name=\"ai\"\n        options=\\\\{{\n          headerTitle: \"AI\",\n          drawerLabel: \"AI\",\n          drawerIcon: ({ size, color }) => (\n            <Ionicons\n              name=\"chatbubble-ellipses-outline\"\n              size={size}\n              color={color}\n            />\n          ),\n        }}\n      />\n      {{/if}}\n    </Drawer>\n  );\n};\n\nexport default DrawerLayout;\n\n`],\n  [\"frontend/native/bare/app/(drawer)/(tabs)/_layout.tsx.hbs\", `import { TabBarIcon } from \"@/components/tabbar-icon\";\nimport { useColorScheme } from \"@/lib/use-color-scheme\";\nimport { Tabs } from \"expo-router\";\nimport { NAV_THEME } from \"@/lib/constants\";\n\nexport default function TabLayout() {\n  const { isDarkColorScheme } = useColorScheme();\n  const theme = isDarkColorScheme ? NAV_THEME.dark : NAV_THEME.light;\n\n  return (\n    <Tabs\n      screenOptions=\\\\{{\n        headerShown: false,\n        tabBarActiveTintColor: theme.primary,\n        tabBarInactiveTintColor: theme.text,\n        tabBarStyle: {\n          backgroundColor: theme.background,\n          borderTopColor: theme.border,\n        },\n      }}\n    >\n      <Tabs.Screen\n        name=\"index\"\n        options=\\\\{{\n          title: \"Home\",\n          tabBarIcon: ({ color }) => <TabBarIcon name=\"home\" color={color} />,\n        }}\n      />\n      <Tabs.Screen\n        name=\"two\"\n        options=\\\\{{\n          title: \"Explore\",\n          tabBarIcon: ({ color }) => (\n            <TabBarIcon name=\"compass\" color={color} />\n          ),\n        }}\n      />\n    </Tabs>\n  );\n}\n\n`],\n  [\"frontend/native/bare/app/(drawer)/(tabs)/index.tsx.hbs\", `import { Container } from \"@/components/container\";\nimport { ScrollView, Text, View, StyleSheet } from \"react-native\";\nimport { useColorScheme } from \"@/lib/use-color-scheme\";\nimport { NAV_THEME } from \"@/lib/constants\";\n\nexport default function TabOne() {\n  const { colorScheme } = useColorScheme();\n  const theme = colorScheme === \"dark\" ? NAV_THEME.dark : NAV_THEME.light;\n\n  return (\n    <Container>\n      <ScrollView style={styles.scrollView}>\n        <View style={styles.content}>\n          <Text style={[styles.title, { color: theme.text }]}>\n            Tab One\n          </Text>\n          <Text style={[styles.subtitle, { color: theme.text, opacity: 0.7 }]}>\n            Explore the first section of your app\n          </Text>\n        </View>\n      </ScrollView>\n    </Container>\n  );\n}\n\nconst styles = StyleSheet.create({\n  scrollView: {\n    flex: 1,\n    padding: 16,\n  },\n  content: {\n    paddingVertical: 16,\n  },\n  title: {\n    fontSize: 24,\n    fontWeight: \"bold\",\n    marginBottom: 8,\n  },\n  subtitle: {\n    fontSize: 16,\n  },\n});\n\n`],\n  [\"frontend/native/bare/app/(drawer)/(tabs)/two.tsx.hbs\", `import { Container } from \"@/components/container\";\nimport { ScrollView, Text, View, StyleSheet } from \"react-native\";\nimport { useColorScheme } from \"@/lib/use-color-scheme\";\nimport { NAV_THEME } from \"@/lib/constants\";\n\nexport default function TabTwo() {\n  const { colorScheme } = useColorScheme();\n  const theme = colorScheme === \"dark\" ? NAV_THEME.dark : NAV_THEME.light;\n\n  return (\n    <Container>\n      <ScrollView style={styles.scrollView}>\n        <View style={styles.content}>\n          <Text style={[styles.title, { color: theme.text }]}>\n            Tab Two\n          </Text>\n          <Text style={[styles.subtitle, { color: theme.text, opacity: 0.7 }]}>\n            Discover more features and content\n          </Text>\n        </View>\n      </ScrollView>\n    </Container>\n  );\n}\n\nconst styles = StyleSheet.create({\n  scrollView: {\n    flex: 1,\n    padding: 16,\n  },\n  content: {\n    paddingVertical: 16,\n  },\n  title: {\n    fontSize: 24,\n    fontWeight: \"bold\",\n    marginBottom: 8,\n  },\n  subtitle: {\n    fontSize: 16,\n  },\n});\n\n`],\n  [\"frontend/native/bare/app/(drawer)/index.tsx.hbs\", `import { View, Text, ScrollView, TouchableOpacity, StyleSheet } from \"react-native\";\nimport { Container } from \"@/components/container\";\nimport { useColorScheme } from \"@/lib/use-color-scheme\";\nimport { NAV_THEME } from \"@/lib/constants\";\n{{#if (eq api \"orpc\")}}\nimport { useQuery } from \"@tanstack/react-query\";\nimport { orpc } from \"@/utils/orpc\";\n{{/if}}\n{{#if (eq api \"trpc\")}}\nimport { useQuery } from \"@tanstack/react-query\";\nimport { trpc } from \"@/utils/trpc\";\n{{/if}}\n{{#if (and (eq backend \"convex\") (eq auth \"clerk\"))}}\nimport { Link } from \"expo-router\";\nimport { Authenticated, AuthLoading, Unauthenticated, useQuery } from \"convex/react\";\nimport { api } from \"@{{ projectName }}/backend/convex/_generated/api\";\nimport { useUser } from \"@clerk/expo\";\nimport { SignOutButton } from \"@/components/sign-out-button\";\n{{else if (and (ne backend \"convex\") (eq auth \"clerk\"))}}\nimport { Link } from \"expo-router\";\nimport { useAuth, useUser } from \"@clerk/expo\";\nimport { SignOutButton } from \"@/components/sign-out-button\";\n{{else if (and (eq backend \"convex\") (eq auth \"better-auth\"))}}\nimport { useConvexAuth, useQuery } from \"convex/react\";\nimport { api } from \"@{{ projectName }}/backend/convex/_generated/api\";\nimport { authClient } from \"@/lib/auth-client\";\nimport { SignIn } from \"@/components/sign-in\";\nimport { SignUp } from \"@/components/sign-up\";\n{{else if (eq backend \"convex\")}}\nimport { useQuery } from \"convex/react\";\nimport { api } from \"@{{ projectName }}/backend/convex/_generated/api\";\n{{/if}}\n\nexport default function Home() {\nconst { colorScheme } = useColorScheme();\nconst theme = colorScheme === \"dark\" ? NAV_THEME.dark : NAV_THEME.light;\n{{#if (eq api \"orpc\")}}\nconst healthCheck = useQuery(orpc.healthCheck.queryOptions());\n{{/if}}\n{{#if (eq api \"trpc\")}}\nconst healthCheck = useQuery(trpc.healthCheck.queryOptions());\n{{/if}}\n{{#if (and (eq backend \"convex\") (eq auth \"clerk\"))}}\nconst { user } = useUser();\nconst healthCheck = useQuery(api.healthCheck.get);\nconst privateData = useQuery(api.privateData.get);\n{{else if (and (ne backend \"convex\") (eq auth \"clerk\"))}}\nconst { isLoaded, isSignedIn } = useAuth();\nconst { user } = useUser();\n{{else if (and (eq backend \"convex\") (eq auth \"better-auth\"))}}\nconst healthCheck = useQuery(api.healthCheck.get);\nconst { isAuthenticated } = useConvexAuth();\nconst user = useQuery(api.auth.getCurrentUser, isAuthenticated ? {} : \"skip\");\n{{else if (eq backend \"convex\")}}\nconst healthCheck = useQuery(api.healthCheck.get);\n{{/if}}\n\nreturn (\n<Container>\n  <ScrollView style={styles.scrollView}>\n    <View style={styles.content}>\n      <Text style={[styles.title, { color: theme.text }]}>\n        BETTER T STACK\n      </Text>\n\n      {{#unless (and (eq backend \"convex\") (eq auth \"better-auth\"))}}\n      <View style={[styles.card, { backgroundColor: theme.card, borderColor: theme.border }]}>\n        {{#if (eq backend \"convex\")}}\n        <View style={styles.statusRow}>\n          <View style={[styles.statusIndicator, { backgroundColor: healthCheck ? \"#10b981\" : \"#f59e0b\" }]} />\n          <View style={styles.statusContent}>\n            <Text style={[styles.statusTitle, { color: theme.text }]}>\n              Convex\n            </Text>\n            <Text style={[styles.statusText, { color: theme.text, opacity: 0.7 }]}>\n              {healthCheck === undefined\n              ? \"Checking...\"\n              : healthCheck === \"OK\"\n              ? \"Connected to API\"\n              : \"API Disconnected\"}\n            </Text>\n          </View>\n        </View>\n        {{else}}\n        {{#unless (eq api \"none\")}}\n        <View style={styles.statusRow}>\n          <View style={[styles.statusIndicator, { backgroundColor: healthCheck.data ? \"#10b981\" : \"#f59e0b\" }]} />\n          <View style={styles.statusContent}>\n            <Text style={[styles.statusTitle, { color: theme.text }]}>\n              {{#if (eq api \"orpc\")}}ORPC{{else}}TRPC{{/if}}\n            </Text>\n            <Text style={[styles.statusText, { color: theme.text, opacity: 0.7 }]}>\n              {healthCheck.isLoading\n              ? \"Checking connection...\"\n              : healthCheck.data\n              ? \"All systems operational\"\n              : \"Service unavailable\"}\n            </Text>\n          </View>\n        </View>\n        {{/unless}}\n        {{/if}}\n      </View>\n      {{/unless}}\n\n      {{#if (and (eq backend \"convex\") (eq auth \"clerk\"))}}\n      <Authenticated>\n        <Text style=\\\\{{ color: theme.text }}>Hello {user?.emailAddresses[0].emailAddress}</Text>\n        <Text style=\\\\{{ color: theme.text }}>Private Data: {privateData?.message}</Text>\n        <SignOutButton />\n      </Authenticated>\n      <Unauthenticated>\n        <Link href=\"/(auth)/sign-in\">\n        <Text style=\\\\{{ color: theme.primary }}>Sign in</Text>\n        </Link>\n        <Link href=\"/(auth)/sign-up\">\n        <Text style=\\\\{{ color: theme.primary }}>Sign up</Text>\n        </Link>\n      </Unauthenticated>\n      <AuthLoading>\n        <Text style=\\\\{{ color: theme.text }}>Loading...</Text>\n      </AuthLoading>\n      {{/if}}\n\n      {{#if (and (ne backend \"convex\") (eq auth \"clerk\"))}}\n      {!isLoaded ? (\n      <Text style=\\\\{{ color: theme.text }}>Loading...</Text>\n      ) : isSignedIn ? (\n      <View style={[styles.userCard, { backgroundColor: theme.card, borderColor: theme.border }]}>\n        <View style={styles.userHeader}>\n          <Text style={[styles.userText, { color: theme.text }]}>\n            Welcome, <Text style={styles.userName}>{user?.fullName ?? user?.firstName ?? \"there\"}</Text>\n          </Text>\n        </View>\n        <Text style={[styles.userEmail, { color: theme.text, opacity: 0.7 }]}>\n          {user?.emailAddresses[0]?.emailAddress}\n        </Text>\n        <SignOutButton />\n      </View>\n      ) : (\n      <View style={[styles.card, { backgroundColor: theme.card, borderColor: theme.border }]}>\n        <Link href=\"/(auth)/sign-in\">\n          <Text style=\\\\{{ color: theme.primary }}>Sign in</Text>\n        </Link>\n        <Link href=\"/(auth)/sign-up\">\n          <Text style=\\\\{{ color: theme.primary }}>Sign up</Text>\n        </Link>\n      </View>\n      )}\n      {{/if}}\n\n      {{#if (and (eq backend \"convex\") (eq auth \"better-auth\"))}}\n      {user ? (\n      <View style={[styles.userCard, { backgroundColor: theme.card, borderColor: theme.border }]}>\n        <View style={styles.userHeader}>\n          <Text style={[styles.userText, { color: theme.text }]}>\n            Welcome, <Text style={styles.userName}>{user.name}</Text>\n          </Text>\n        </View>\n        <Text style={[styles.userEmail, { color: theme.text, opacity: 0.7 }]}>\n          {user.email}\n        </Text>\n        <TouchableOpacity style={[styles.signOutButton, { backgroundColor: theme.notification }]} onPress={()=> {\n          authClient.signOut();\n          }}\n          >\n          <Text style={styles.signOutText}>Sign Out</Text>\n        </TouchableOpacity>\n      </View>\n      ) : null}\n      <View style={[styles.statusCard, { backgroundColor: theme.card, borderColor: theme.border }]}>\n        <Text style={[styles.statusCardTitle, { color: theme.text }]}>\n          API Status\n        </Text>\n        <View style={styles.statusRow}>\n          <View style={[styles.statusIndicator, { backgroundColor: healthCheck ? \"#10b981\" : \"#ef4444\" }]} />\n          <Text style={[styles.statusText, { color: theme.text, opacity: 0.7 }]}>\n            {healthCheck === undefined\n            ? \"Checking...\"\n            : healthCheck === \"OK\"\n            ? \"Connected to API\"\n            : \"API Disconnected\"}\n          </Text>\n        </View>\n      </View>\n      {!user && (\n      <>\n        <SignIn />\n        <SignUp />\n      </>\n      )}\n      {{/if}}\n    </View>\n  </ScrollView>\n</Container>\n);\n}\n\nconst styles = StyleSheet.create({\nscrollView: {\nflex: 1,\n},\ncontent: {\npadding: 16,\n},\ntitle: {\nfontSize: 24,\nfontWeight: \"bold\",\nmarginBottom: 16,\n},\ncard: {\npadding: 16,\nmarginBottom: 16,\nborderWidth: 1,\n},\nstatusRow: {\nflexDirection: \"row\",\nalignItems: \"center\",\ngap: 8,\n},\nstatusIndicator: {\nheight: 8,\nwidth: 8,\n},\nstatusContent: {\nflex: 1,\n},\nstatusTitle: {\nfontSize: 14,\nfontWeight: \"bold\",\n},\nstatusText: {\nfontSize: 12,\n},\nuserCard: {\nmarginBottom: 16,\npadding: 16,\nborderWidth: 1,\n},\nuserHeader: {\nmarginBottom: 8,\n},\nuserText: {\nfontSize: 16,\n},\nuserName: {\nfontWeight: \"bold\",\n},\nuserEmail: {\nfontSize: 14,\nmarginBottom: 12,\n},\nsignOutButton: {\npadding: 12,\n},\nsignOutText: {\ncolor: \"#ffffff\",\n},\nstatusCard: {\nmarginBottom: 16,\npadding: 16,\nborderWidth: 1,\n},\nstatusCardTitle: {\nmarginBottom: 8,\nfontWeight: \"bold\",\n},\n});\n`],\n  [\"frontend/native/bare/app/+not-found.tsx.hbs\", `import { Container } from \"@/components/container\";\nimport { Link, Stack } from \"expo-router\";\nimport { Text, View, StyleSheet } from \"react-native\";\nimport { useColorScheme } from \"@/lib/use-color-scheme\";\nimport { NAV_THEME } from \"@/lib/constants\";\n\nexport default function NotFoundScreen() {\n  const { colorScheme } = useColorScheme();\n  const theme = colorScheme === \"dark\" ? NAV_THEME.dark : NAV_THEME.light;\n\n  return (\n    <>\n      <Stack.Screen options=\\\\{{ title: \"Oops!\" }} />\n      <Container>\n        <View style={styles.container}>\n          <View style={styles.content}>\n            <Text style={styles.emoji}>🤔</Text>\n            <Text style={[styles.title, { color: theme.text }]}>\n              Page Not Found\n            </Text>\n            <Text style={[styles.subtitle, { color: theme.text, opacity: 0.7 }]}>\n              Sorry, the page you're looking for doesn't exist.\n            </Text>\n            <Link href=\"/\" asChild>\n              <Text style={[styles.link, { color: theme.primary, backgroundColor: \\`\\${theme.primary}1a\\` }]}>\n                Go to Home\n              </Text>\n            </Link>\n          </View>\n        </View>\n      </Container>\n    </>\n  );\n}\n\nconst styles = StyleSheet.create({\n  container: {\n    flex: 1,\n    justifyContent: \"center\",\n    alignItems: \"center\",\n    padding: 16,\n  },\n  content: {\n    alignItems: \"center\",\n  },\n  emoji: {\n    fontSize: 48,\n    marginBottom: 16,\n  },\n  title: {\n    fontSize: 20,\n    fontWeight: \"bold\",\n    marginBottom: 8,\n    textAlign: \"center\",\n  },\n  subtitle: {\n    fontSize: 14,\n    textAlign: \"center\",\n    marginBottom: 24,\n  },\n  link: {\n    padding: 12,\n  },\n});\n\n`],\n  [\"frontend/native/bare/app/modal.tsx.hbs\", `import { Container } from \"@/components/container\";\nimport { Text, View, StyleSheet } from \"react-native\";\nimport { useColorScheme } from \"@/lib/use-color-scheme\";\nimport { NAV_THEME } from \"@/lib/constants\";\n\nexport default function Modal() {\n  const { colorScheme } = useColorScheme();\n  const theme = colorScheme === \"dark\" ? NAV_THEME.dark : NAV_THEME.light;\n\n  return (\n    <Container>\n      <View style={styles.container}>\n        <View style={styles.header}>\n          <Text style={[styles.title, { color: theme.text }]}>Modal</Text>\n        </View>\n      </View>\n    </Container>\n  );\n}\n\nconst styles = StyleSheet.create({\n  container: {\n    flex: 1,\n    padding: 16,\n  },\n  header: {\n    marginBottom: 16,\n  },\n  title: {\n    fontSize: 20,\n    fontWeight: \"bold\",\n  },\n});\n\n`],\n  [\"frontend/native/bare/components/container.tsx.hbs\", `import React from \"react\";\nimport { SafeAreaView } from \"react-native-safe-area-context\";\nimport { useColorScheme } from \"@/lib/use-color-scheme\";\nimport { NAV_THEME } from \"@/lib/constants\";\nimport { StyleSheet } from \"react-native\";\n\nexport function Container({ children }: { children: React.ReactNode }) {\n  const { colorScheme } = useColorScheme();\n  const backgroundColor = colorScheme === \"dark\" \n    ? NAV_THEME.dark.background \n    : NAV_THEME.light.background;\n\n  return (\n    <SafeAreaView style={[styles.container, { backgroundColor }]}>\n      {children}\n    </SafeAreaView>\n  );\n}\n\nconst styles = StyleSheet.create({\n  container: {\n    flex: 1,\n  },\n});\n\n`],\n  [\"frontend/native/bare/components/header-button.tsx.hbs\", `import FontAwesome from \"@expo/vector-icons/FontAwesome\";\nimport { forwardRef } from \"react\";\nimport { Pressable, StyleSheet, View } from \"react-native\";\nimport { useColorScheme } from \"@/lib/use-color-scheme\";\nimport { NAV_THEME } from \"@/lib/constants\";\n\nexport const HeaderButton = forwardRef<\n  View,\n  { onPress?: () => void }\n>(({ onPress }, ref) => {\n  const { colorScheme } = useColorScheme();\n  const theme = colorScheme === \"dark\" ? NAV_THEME.dark : NAV_THEME.light;\n\n  return (\n    <Pressable\n      ref={ref}\n      onPress={onPress}\n      style={({ pressed }) => [\n        styles.button,\n        {\n          backgroundColor: pressed \n            ? theme.background \n            : theme.card,\n        },\n      ]}\n    >\n      {({ pressed }) => (\n        <FontAwesome\n          name=\"info-circle\"\n          size={20}\n          color={theme.text}\n          style=\\\\{{\n            opacity: pressed ? 0.7 : 1,\n          }}\n        />\n      )}\n    </Pressable>\n  );\n});\n\nconst styles = StyleSheet.create({\n  button: {\n    padding: 8,\n    marginRight: 8,\n  },\n});\n\n`],\n  [\"frontend/native/bare/components/tabbar-icon.tsx.hbs\", `import FontAwesome from \"@expo/vector-icons/FontAwesome\";\n\nexport const TabBarIcon = (props: {\n  name: React.ComponentProps<typeof FontAwesome>[\"name\"];\n  color: string;\n}) => {\n  return <FontAwesome size={24} style=\\\\{{ marginBottom: -3 }} {...props} />;\n};\n\n`],\n  [\"frontend/native/bare/lib/constants.ts.hbs\", `export const NAV_THEME = {\n  light: {\n    background: \"hsl(0 0% 100%)\",\n    border: \"hsl(220 13% 91%)\",\n    card: \"hsl(0 0% 100%)\",\n    notification: \"hsl(0 84.2% 60.2%)\",\n    primary: \"hsl(221.2 83.2% 53.3%)\",\n    text: \"hsl(222.2 84% 4.9%)\",\n  },\n  dark: {\n    background: \"hsl(222.2 84% 4.9%)\",\n    border: \"hsl(217.2 32.6% 17.5%)\",\n    card: \"hsl(222.2 84% 4.9%)\",\n    notification: \"hsl(0 72% 51%)\",\n    primary: \"hsl(217.2 91.2% 59.8%)\",\n    text: \"hsl(210 40% 98%)\",\n  },\n};\n\n`],\n  [\"frontend/native/bare/lib/use-color-scheme.ts.hbs\", `import { useColorScheme as useRNColorScheme } from \"react-native\";\n\nexport function useColorScheme() {\n  const systemColorScheme = useRNColorScheme();\n  const colorScheme = systemColorScheme ?? \"light\";\n  \n  return {\n    colorScheme: colorScheme as \"light\" | \"dark\",\n    isDarkColorScheme: colorScheme === \"dark\",\n    setColorScheme: () => {\n      // Color scheme is managed by the system in bare mode\n      console.warn(\"setColorScheme is not available in bare mode. Color scheme is managed by the system.\");\n    },\n    toggleColorScheme: () => {\n      // Color scheme is managed by the system in bare mode\n      console.warn(\"toggleColorScheme is not available in bare mode. Color scheme is managed by the system.\");\n    },\n  };\n}\n\n`],\n  [\"frontend/native/bare/metro.config.js.hbs\", `// Learn more https://docs.expo.io/guides/customizing-metro\nconst { getDefaultConfig } = require(\"expo/metro-config\");\n\nconst config = getDefaultConfig(__dirname);\n\nmodule.exports = config;\n`],\n  [\"frontend/native/bare/package.json.hbs\", `{\n  \"name\": \"native\",\n  \"version\": \"1.0.0\",\n  \"main\": \"expo-router/entry\",\n  \"scripts\": {\n    \"dev\": \"expo start --clear\",\n    \"android\": \"expo run:android\",\n    \"ios\": \"expo run:ios\",\n    \"prebuild\": \"expo prebuild\",\n    \"web\": \"expo start --web\"\n  },\n  \"dependencies\": {\n    \"@expo/vector-icons\": \"^15.1.1\",\n    \"@react-navigation/bottom-tabs\": \"^7.15.9\",\n    \"@react-navigation/drawer\": \"^7.9.4\",\n    \"@react-navigation/native\": \"^7.2.2\",\n    \"@tanstack/react-query\": \"^5.99.2\",\n    {{#if (includes examples \"ai\")}}\n    \"@stardazed/streams-text-encoding\": \"^1.0.2\",\n    \"@ungap/structured-clone\": \"^1.3.0\",\n    {{/if}}\n    \"expo\": \"^55.0.17\",\n    \"expo-constants\": \"~55.0.15\",\n    \"expo-crypto\": \"~55.0.14\",\n    \"expo-font\": \"~55.0.6\",\n    \"expo-linking\": \"~55.0.14\",\n    \"expo-network\": \"~55.0.13\",\n    \"expo-router\": \"~55.0.13\",\n    \"expo-secure-store\": \"~55.0.13\",\n    \"expo-splash-screen\": \"~55.0.19\",\n    \"expo-status-bar\": \"~55.0.5\",\n    \"expo-system-ui\": \"~55.0.16\",\n    \"expo-web-browser\": \"~55.0.14\",\n    \"react\": \"19.2.0\",\n    \"react-dom\": \"19.2.0\",\n    \"react-native\": \"0.83.6\",\n    \"react-native-gesture-handler\": \"~2.30.0\",\n    \"react-native-reanimated\": \"4.2.1\",\n    \"react-native-safe-area-context\": \"~5.6.2\",\n    \"react-native-screens\": \"~4.23.0\",\n    \"react-native-web\": \"~0.21.0\",\n    \"react-native-worklets\": \"0.7.4\"\n  },\n  \"devDependencies\": {\n    \"@babel/core\": \"^7.28.0\",\n    \"@types/react\": \"~19.2.10\",\n    \"typescript\": \"~5.9.2\"\n  },\n  \"private\": true\n}\n`],\n  [\"frontend/native/bare/tsconfig.json.hbs\", `{\n\t\"extends\": \"expo/tsconfig.base\",\n\t\"compilerOptions\": {\n\t\t\"strict\": true,\n\t\t\"paths\": {\n\t\t\t\"@/*\": [\"./*\"]\n\t\t}\n\t},\n\t\"include\": [\"**/*.ts\", \"**/*.tsx\", \".expo/types/**/*.ts\", \"expo-env.d.ts\"]\n}\n\n`],\n  [\"frontend/native/base/assets/images/android-icon-background.png\", `[Binary file]`],\n  [\"frontend/native/base/assets/images/android-icon-foreground.png\", `[Binary file]`],\n  [\"frontend/native/base/assets/images/android-icon-monochrome.png\", `[Binary file]`],\n  [\"frontend/native/base/assets/images/favicon.png\", `[Binary file]`],\n  [\"frontend/native/base/assets/images/icon.png\", `[Binary file]`],\n  [\"frontend/native/base/assets/images/partial-react-logo.png\", `[Binary file]`],\n  [\"frontend/native/base/assets/images/react-logo.png\", `[Binary file]`],\n  [\"frontend/native/base/assets/images/react-logo@2x.png\", `[Binary file]`],\n  [\"frontend/native/base/assets/images/react-logo@3x.png\", `[Binary file]`],\n  [\"frontend/native/base/assets/images/splash-icon.png\", `[Binary file]`],\n  [\"frontend/native/unistyles/_gitignore\", `node_modules/\n.expo/\ndist/\nnpm-debug.*\n*.jks\n*.p8\n*.p12\n*.key\n*.mobileprovision\n*.orig.*\nweb-build/\n# expo router\nexpo-env.d.ts\n\n.env\n\nios\nandroid\n\n# macOS\n.DS_Store\n\n# Temporary files created by Metro to check the health of the file watcher\n.metro-health-check*`],\n  [\"frontend/native/unistyles/app.json.hbs\", `{\n  \"expo\": {\n    \"name\": \"{{projectName}}\",\n    \"slug\": \"{{projectName}}\",\n    \"version\": \"1.0.0\",\n    \"orientation\": \"portrait\",\n    \"icon\": \"./assets/images/icon.png\",\n    \"scheme\": \"{{projectName}}\",\n    \"userInterfaceStyle\": \"automatic\",\n    \"ios\": {\n      \"supportsTablet\": true\n    },\n    \"android\": {\n      \"adaptiveIcon\": {\n        \"backgroundColor\": \"#E6F4FE\",\n        \"foregroundImage\": \"./assets/images/android-icon-foreground.png\",\n        \"backgroundImage\": \"./assets/images/android-icon-background.png\",\n        \"monochromeImage\": \"./assets/images/android-icon-monochrome.png\"\n      },\n      \"predictiveBackGestureEnabled\": false,\n      \"package\": \"com.anonymous.mybettertapp\"\n    },\n    \"web\": {\n      \"output\": \"static\",\n      \"favicon\": \"./assets/images/favicon.png\"\n    },\n    \"plugins\": [\n      \"expo-router\",\n      [\n        \"expo-splash-screen\",\n        {\n          \"image\": \"./assets/images/splash-icon.png\",\n          \"imageWidth\": 200,\n          \"resizeMode\": \"contain\",\n          \"backgroundColor\": \"#ffffff\",\n          \"dark\": {\n            \"backgroundColor\": \"#000000\"\n          }\n        }\n      ]\n    ],\n    \"experiments\": {\n      \"typedRoutes\": true,\n      \"reactCompiler\": true\n    }\n  }\n}\n`],\n  [\"frontend/native/unistyles/app/_layout.tsx.hbs\", `{{#if (includes examples \"ai\")}}\nimport \"@/polyfills\";\n{{/if}}\n{{#if (and (eq auth \"clerk\") (ne api \"none\") (ne backend \"convex\"))}}\nimport { useEffect } from \"react\";\nimport { setClerkAuthTokenGetter } from \"@/utils/clerk-auth\";\n{{/if}}\n{{#if (and (ne backend \"convex\") (eq auth \"clerk\"))}}\nimport { ClerkProvider{{#unless (eq api \"none\")}}, useAuth{{/unless}} } from \"@clerk/expo\";\nimport { tokenCache } from \"@clerk/expo/token-cache\";\nimport { env } from \"@{{projectName}}/env/native\";\n{{/if}}\n{{#if (eq api \"trpc\")}}\nimport { queryClient } from \"@/utils/trpc\";\n{{/if}}\n{{#if (eq api \"orpc\")}}\nimport { queryClient } from \"@/utils/orpc\";\n{{/if}}\n{{#if (eq backend \"convex\")}}\n{{#if (eq auth \"better-auth\")}}\nimport { ConvexReactClient } from \"convex/react\";\nimport { ConvexBetterAuthProvider } from \"@convex-dev/better-auth/react\";\nimport { authClient } from \"@/lib/auth-client\";\nimport { env } from \"@{{projectName}}/env/native\";\n{{else}}\nimport { ConvexProvider, ConvexReactClient } from \"convex/react\";\nimport { env } from \"@{{projectName}}/env/native\";\n{{/if}}\n{{#if (eq auth \"clerk\")}}\nimport { ClerkProvider, useAuth } from \"@clerk/expo\";\nimport { ConvexProviderWithClerk } from \"convex/react-clerk\";\nimport { tokenCache } from \"@clerk/expo/token-cache\";\n{{/if}}\n{{else}}\n  {{#unless (eq api \"none\")}}\nimport { QueryClientProvider } from \"@tanstack/react-query\";\n  {{/unless}}\n{{/if}}\nimport { Stack } from \"expo-router\";\nimport { GestureHandlerRootView } from \"react-native-gesture-handler\";\nimport { useUnistyles } from \"react-native-unistyles\";\nimport { StatusBar } from \"expo-status-bar\";\n\nexport const unstable_settings = {\n  initialRouteName: \"(drawer)\",\n};\n\n{{#if (eq backend \"convex\")}}\nconst convex = new ConvexReactClient(env.EXPO_PUBLIC_CONVEX_URL, {\n  {{#if (eq auth \"better-auth\")}}\n  expectAuth: true,\n  {{/if}}\n  unsavedChangesWarning: false,\n});\n{{/if}}\n\n{{#if (and (eq auth \"clerk\") (ne api \"none\") (ne backend \"convex\"))}}\nfunction ClerkApiAuthBridge() {\n  const { getToken } = useAuth();\n\n  useEffect(() => {\n    setClerkAuthTokenGetter(getToken);\n\n    return () => {\n      setClerkAuthTokenGetter(null);\n    };\n  }, [getToken]);\n\n  return null;\n}\n{{/if}}\n\nexport default function RootLayout() {\n  const { theme } = useUnistyles();\n\n  return (\n    {{#if (eq backend \"convex\")}}\n    {{#if (eq auth \"clerk\")}}\n    <ClerkProvider\n      tokenCache={tokenCache}\n      publishableKey={env.EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY}\n    >\n      <ConvexProviderWithClerk client={convex} useAuth={useAuth}>\n        <GestureHandlerRootView style=\\\\{{ flex: 1 }}>\n          <Stack\n            screenOptions=\\\\{{\n              headerStyle: {\n                backgroundColor: theme.colors.background,\n              },\n              headerTitleStyle: {\n                color: theme.colors.foreground,\n              },\n              headerTintColor: theme.colors.foreground,\n            }}\n          >\n            <Stack.Screen name=\"(drawer)\" options=\\\\{{ headerShown: false }} />\n            <Stack.Screen name=\"(auth)\" options=\\\\{{ headerShown: false }} />\n            <Stack.Screen\n              name=\"modal\"\n              options=\\\\{{ title: \"Modal\", presentation: \"modal\" }}\n            />\n          </Stack>\n        </GestureHandlerRootView>\n      </ConvexProviderWithClerk>\n    </ClerkProvider>\n    {{else if (eq auth \"better-auth\")}}\n    <ConvexBetterAuthProvider client={convex} authClient={authClient}>\n      <GestureHandlerRootView style=\\\\{{ flex: 1 }}>\n        <Stack\n          screenOptions=\\\\{{\n            headerStyle: {\n              backgroundColor: theme.colors.background,\n            },\n            headerTitleStyle: {\n              color: theme.colors.foreground,\n            },\n            headerTintColor: theme.colors.foreground,\n          }}\n        >\n          <Stack.Screen name=\"(drawer)\" options=\\\\{{ headerShown: false }} />\n          <Stack.Screen\n            name=\"modal\"\n            options=\\\\{{ title: \"Modal\", presentation: \"modal\" }}\n          />\n        </Stack>\n      </GestureHandlerRootView>\n    </ConvexBetterAuthProvider>\n    {{else}}\n    <ConvexProvider client={convex}>\n      <GestureHandlerRootView style=\\\\{{ flex: 1 }}>\n        <Stack\n          screenOptions=\\\\{{\n            headerStyle: {\n              backgroundColor: theme.colors.background,\n            },\n            headerTitleStyle: {\n              color: theme.colors.foreground,\n            },\n            headerTintColor: theme.colors.foreground,\n          }}\n        >\n          <Stack.Screen name=\"(drawer)\" options=\\\\{{ headerShown: false }} />\n          <Stack.Screen\n            name=\"modal\"\n            options=\\\\{{ title: \"Modal\", presentation: \"modal\" }}\n          />\n        </Stack>\n      </GestureHandlerRootView>\n    </ConvexProvider>\n    {{/if}}\n    {{else}}\n      {{#if (eq auth \"clerk\")}}\n      <ClerkProvider\n        tokenCache={tokenCache}\n        publishableKey={env.EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY}\n      >\n        {{#unless (eq api \"none\")}}\n        <ClerkApiAuthBridge />\n        <QueryClientProvider client={queryClient}>\n          <GestureHandlerRootView style=\\\\{{ flex: 1 }}>\n            <Stack\n              screenOptions=\\\\{{\n                headerStyle: {\n                  backgroundColor: theme.colors.background,\n                },\n                headerTitleStyle: {\n                  color: theme.colors.foreground,\n                },\n                headerTintColor: theme.colors.foreground,\n              }}\n            >\n              <Stack.Screen name=\"(drawer)\" options=\\\\{{ headerShown: false }} />\n              <Stack.Screen name=\"(auth)\" options=\\\\{{ headerShown: false }} />\n              <Stack.Screen\n                name=\"modal\"\n                options=\\\\{{ title: \"Modal\", presentation: \"modal\" }}\n              />\n            </Stack>\n          </GestureHandlerRootView>\n        </QueryClientProvider>\n        {{else}}\n        <GestureHandlerRootView style=\\\\{{ flex: 1 }}>\n          <Stack\n            screenOptions=\\\\{{\n              headerStyle: {\n                backgroundColor: theme.colors.background,\n              },\n              headerTitleStyle: {\n                color: theme.colors.foreground,\n              },\n              headerTintColor: theme.colors.foreground,\n            }}\n          >\n            <Stack.Screen name=\"(drawer)\" options=\\\\{{ headerShown: false }} />\n            <Stack.Screen name=\"(auth)\" options=\\\\{{ headerShown: false }} />\n            <Stack.Screen\n              name=\"modal\"\n              options=\\\\{{ title: \"Modal\", presentation: \"modal\" }}\n            />\n          </Stack>\n        </GestureHandlerRootView>\n        {{/unless}}\n      </ClerkProvider>\n      {{else}}\n        {{#unless (eq api \"none\")}}\n      <QueryClientProvider client={queryClient}>\n        <GestureHandlerRootView style=\\\\{{ flex: 1 }}>\n          <Stack\n            screenOptions=\\\\{{\n              headerStyle: {\n                backgroundColor: theme.colors.background,\n              },\n              headerTitleStyle: {\n                color: theme.colors.foreground,\n              },\n              headerTintColor: theme.colors.foreground,\n            }}\n          >\n            <Stack.Screen name=\"(drawer)\" options=\\\\{{ headerShown: false }} />\n            <Stack.Screen\n              name=\"modal\"\n              options=\\\\{{ title: \"Modal\", presentation: \"modal\" }}\n            />\n          </Stack>\n        </GestureHandlerRootView>\n      </QueryClientProvider>\n        {{else}}\n        <GestureHandlerRootView style=\\\\{{ flex: 1 }}>\n          <Stack\n            screenOptions=\\\\{{\n              headerStyle: {\n                backgroundColor: theme.colors.background,\n              },\n              headerTitleStyle: {\n                color: theme.colors.foreground,\n              },\n              headerTintColor: theme.colors.foreground,\n            }}\n          >\n            <Stack.Screen name=\"(drawer)\" options=\\\\{{ headerShown: false }} />\n            <Stack.Screen\n              name=\"modal\"\n              options=\\\\{{ title: \"Modal\", presentation: \"modal\" }}\n            />\n          </Stack>\n        </GestureHandlerRootView>\n        {{/unless}}\n      {{/if}}\n    {{/if}}\n  );\n}\n`],\n  [\"frontend/native/unistyles/app/(drawer)/_layout.tsx.hbs\", `import { Ionicons, MaterialIcons } from \"@expo/vector-icons\";\nimport { Link } from \"expo-router\";\nimport { Drawer } from \"expo-router/drawer\";\nimport { useUnistyles } from \"react-native-unistyles\";\n\nimport { HeaderButton } from \"../../components/header-button\";\n\nconst DrawerLayout = () => {\n  const { theme } = useUnistyles();\n\n  return (\n    <Drawer\n      screenOptions=\\\\{{\n        headerStyle: {\n          backgroundColor: theme.colors.background,\n        },\n        headerTitleStyle: {\n          color: theme.colors.foreground,\n        },\n        headerTintColor: theme.colors.foreground,\n        drawerStyle: {\n          backgroundColor: theme.colors.background,\n        },\n        drawerLabelStyle: {\n          color: theme.colors.foreground,\n        },\n        drawerInactiveTintColor: theme.colors.mutedForeground,\n      }}\n    >\n      <Drawer.Screen\n        name=\"index\"\n        options=\\\\{{\n          headerTitle: \"Home\",\n          drawerLabel: \"Home\",\n          drawerIcon: ({ size, color }) => (\n            <Ionicons name=\"home-outline\" size={size} color={color} />\n          ),\n        }}\n      />\n      <Drawer.Screen\n        name=\"(tabs)\"\n        options=\\\\{{\n          headerTitle: \"Tabs\",\n          drawerLabel: \"Tabs\",\n          drawerIcon: ({ size, color }) => (\n            <MaterialIcons name=\"border-bottom\" size={size} color={color} />\n          ),\n          headerRight: () => (\n            <Link href=\"/modal\" asChild>\n              <HeaderButton />\n            </Link>\n          ),\n        }}\n      />\n      {{#if (includes examples \"todo\")}}\n      <Drawer.Screen\n        name=\"todos\"\n        options=\\\\{{\n          headerTitle: \"Todos\",\n          drawerLabel: \"Todos\",\n          drawerIcon: ({ size, color }) => (\n            <Ionicons name=\"checkbox-outline\" size={size} color={color} />\n          ),\n        }}\n      />\n      {{/if}}\n      {{#if (includes examples \"ai\")}}\n      <Drawer.Screen\n        name=\"ai\"\n        options=\\\\{{\n          headerTitle: \"AI\",\n          drawerLabel: \"AI\",\n          drawerIcon: ({ size, color }) => (\n            <Ionicons\n              name=\"chatbubble-ellipses-outline\"\n              size={size}\n              color={color}\n            />\n          ),\n        }}\n      />\n      {{/if}}\n    </Drawer>\n  );\n};\n\nexport default DrawerLayout;\n`],\n  [\"frontend/native/unistyles/app/(drawer)/(tabs)/_layout.tsx.hbs\", `import { Tabs } from \"expo-router\";\nimport { useUnistyles } from \"react-native-unistyles\";\n\nimport { TabBarIcon } from \"@/components/tabbar-icon\";\n\nexport default function TabLayout() {\n  const { theme } = useUnistyles();\n\n  return (\n    <Tabs\n      screenOptions=\\\\{{\n        headerShown: false,\n        tabBarActiveTintColor: theme.colors.primary,\n        tabBarInactiveTintColor: theme.colors.mutedForeground,\n        tabBarStyle: {\n          backgroundColor: theme.colors.background,\n          borderTopColor: theme.colors.border,\n        },\n      }}\n    >\n      <Tabs.Screen\n        name=\"index\"\n        options=\\\\{{\n          title: \"Home\",\n          tabBarIcon: ({ color }) => <TabBarIcon name=\"home\" color={color} />,\n        }}\n      />\n      <Tabs.Screen\n        name=\"two\"\n        options=\\\\{{\n          title: \"Explore\",\n          tabBarIcon: ({ color }) => (\n            <TabBarIcon name=\"compass\" color={color} />\n          ),\n        }}\n      />\n    </Tabs>\n  );\n}\n`],\n  [\"frontend/native/unistyles/app/(drawer)/(tabs)/index.tsx.hbs\", `import { Container } from \"@/components/container\";\nimport { ScrollView, Text, View } from \"react-native\";\nimport { StyleSheet } from \"react-native-unistyles\";\n\nexport default function Home() {\n  return (\n    <Container>\n      <ScrollView contentContainerStyle={styles.container}>\n        <View style={styles.headerSection}>\n          <Text style={styles.title}>Tab One</Text>\n          <Text style={styles.subtitle}>\n            Explore the first section of your app\n          </Text>\n        </View>\n      </ScrollView>\n    </Container>\n  );\n}\n\nconst styles = StyleSheet.create((theme) => ({\n  container: {\n    padding: theme.spacing.lg,\n  },\n  headerSection: {\n    paddingVertical: theme.spacing.xl,\n  },\n  title: {\n    fontSize: theme.fontSize[\"3xl\"],\n    fontWeight: \"bold\",\n    color: theme.colors.foreground,\n    marginBottom: theme.spacing.sm,\n  },\n  subtitle: {\n    fontSize: theme.fontSize.lg,\n    color: theme.colors.mutedForeground,\n  },\n}));\n`],\n  [\"frontend/native/unistyles/app/(drawer)/(tabs)/two.tsx.hbs\", `import { Container } from \"@/components/container\";\nimport { ScrollView, Text, View } from \"react-native\";\nimport { StyleSheet } from \"react-native-unistyles\";\n\nexport default function TabTwo() {\n  return (\n    <Container>\n      <ScrollView contentContainerStyle={styles.container}>\n        <View style={styles.headerSection}>\n          <Text style={styles.title}>Tab Two</Text>\n          <Text style={styles.subtitle}>\n            Discover more features and content\n          </Text>\n        </View>\n      </ScrollView>\n    </Container>\n  );\n}\n\nconst styles = StyleSheet.create((theme) => ({\n  container: {\n    padding: theme.spacing.lg,\n  },\n  headerSection: {\n    paddingVertical: theme.spacing.xl,\n  },\n  title: {\n    fontSize: theme.fontSize[\"3xl\"],\n    fontWeight: \"bold\",\n    color: theme.colors.foreground,\n    marginBottom: theme.spacing.sm,\n  },\n  subtitle: {\n    fontSize: theme.fontSize.lg,\n    color: theme.colors.mutedForeground,\n  },\n}));\n`],\n  [\"frontend/native/unistyles/app/(drawer)/index.tsx.hbs\", `import { ScrollView, Text, View, TouchableOpacity } from \"react-native\";\nimport { StyleSheet } from \"react-native-unistyles\";\nimport { Container } from \"@/components/container\";\n\n{{#if (eq api \"orpc\")}}\nimport { useQuery } from \"@tanstack/react-query\";\nimport { orpc } from \"@/utils/orpc\";\n{{/if}}\n{{#if (eq api \"trpc\")}}\nimport { useQuery } from \"@tanstack/react-query\";\nimport { trpc } from \"@/utils/trpc\";\n{{/if}}\n{{#if (and (eq backend \"convex\") (eq auth \"clerk\"))}}\nimport { Link } from \"expo-router\";\nimport { Authenticated, AuthLoading, Unauthenticated, useQuery } from \"convex/react\";\nimport { api } from \"@{{ projectName }}/backend/convex/_generated/api\";\nimport { useUser } from \"@clerk/expo\";\nimport { SignOutButton } from \"@/components/sign-out-button\";\n{{else if (and (ne backend \"convex\") (eq auth \"clerk\"))}}\nimport { Link } from \"expo-router\";\nimport { useAuth, useUser } from \"@clerk/expo\";\nimport { SignOutButton } from \"@/components/sign-out-button\";\n{{else if (and (eq backend \"convex\") (eq auth \"better-auth\"))}}\nimport { useConvexAuth, useQuery } from \"convex/react\";\nimport { api } from \"@{{ projectName }}/backend/convex/_generated/api\";\nimport { authClient } from \"@/lib/auth-client\";\nimport { SignIn } from \"@/components/sign-in\";\nimport { SignUp } from \"@/components/sign-up\";\n{{else if (eq backend \"convex\")}}\nimport { useQuery } from \"convex/react\";\nimport { api } from \"@{{ projectName }}/backend/convex/_generated/api\";\n{{/if}}\n\nexport default function Home() {\n  {{#if (eq api \"orpc\")}}\n  const healthCheck = useQuery(orpc.healthCheck.queryOptions());\n  {{/if}}\n  {{#if (eq api \"trpc\")}}\n  const healthCheck = useQuery(trpc.healthCheck.queryOptions());\n  {{/if}}\n  {{#if (and (eq backend \"convex\") (eq auth \"clerk\"))}}\n  const { user } = useUser();\n  const healthCheck = useQuery(api.healthCheck.get);\n  const privateData = useQuery(api.privateData.get);\n  {{else if (and (ne backend \"convex\") (eq auth \"clerk\"))}}\n  const { isLoaded, isSignedIn } = useAuth();\n  const { user } = useUser();\n  {{else if (and (eq backend \"convex\") (eq auth \"better-auth\"))}}\n  const healthCheck = useQuery(api.healthCheck.get);\n  const { isAuthenticated } = useConvexAuth();\n  const user = useQuery(api.auth.getCurrentUser, isAuthenticated ? {} : \"skip\");\n  {{else if (eq backend \"convex\")}}\n  const healthCheck = useQuery(api.healthCheck.get);\n  {{/if}}\n\n  return (\n    <Container>\n      <ScrollView\n        contentContainerStyle={styles.container}\n        showsVerticalScrollIndicator={false}\n      >\n        <Text style={styles.heroTitle}>\n          BETTER T STACK\n        </Text>\n\n        {{#unless (and (eq backend \"convex\") (eq auth \"better-auth\"))}}\n        <View style={styles.statusCard}>\n          <View style={styles.statusHeader}>\n            <Text style={styles.statusTitle}>System Status</Text>\n            <View style={styles.statusBadge}>\n              <Text style={styles.statusBadgeText}>LIVE</Text>\n            </View>\n          </View>\n          {{#if (eq backend \"convex\")}}\n            {{#unless (eq auth \"better-auth\")}}\n            <View style={styles.statusRow}>\n              <View\n                style={[\n                  styles.statusDot,\n                  healthCheck === \"OK\"\n                    ? styles.statusDotSuccess\n                    : styles.statusDotWarning,\n                ]}\n              />\n              <View style={styles.statusContent}>\n                <Text style={styles.statusLabel}>Convex</Text>\n                <Text style={styles.statusDescription}>\n                  {healthCheck === undefined\n                    ? \"Checking connection...\"\n                    : healthCheck === \"OK\"\n                    ? \"Connected to API\"\n                    : \"API Disconnected\"}\n                </Text>\n              </View>\n            </View>\n            {{/unless}}\n          {{else}}\n            {{#unless (eq api \"none\")}}\n            <View style={styles.statusRow}>\n              <View\n                style={[\n                  styles.statusDot,\n                  healthCheck.data\n                    ? styles.statusDotSuccess\n                    : styles.statusDotWarning,\n                ]}\n              />\n              <View style={styles.statusContent}>\n                <Text style={styles.statusLabel}>\n                  {{#if (eq api \"orpc\")}}ORPC{{/if}}\n                  {{#if (eq api \"trpc\")}}TRPC{{/if}}\n                </Text>\n                <Text style={styles.statusDescription}>\n                  {healthCheck.isLoading\n                    ? \"Checking connection...\"\n                    : healthCheck.data\n                    ? \"Connected to API\"\n                    : \"API Disconnected\"}\n                </Text>\n              </View>\n            </View>\n            {{/unless}}\n          {{/if}}\n        </View>\n        {{/unless}}\n\n        {{#if (and (eq backend \"convex\") (eq auth \"clerk\"))}}\n        <Authenticated>\n          <Text>\n            Hello {user?.emailAddresses[0].emailAddress}\n          </Text>\n          <Text>\n            Private Data: {privateData?.message}\n          </Text>\n          <SignOutButton />\n        </Authenticated>\n        <Unauthenticated>\n          <Link href=\"/(auth)/sign-in\">\n            <Text>Sign in</Text>\n          </Link>\n          <Link href=\"/(auth)/sign-up\">\n            <Text>Sign up</Text>\n          </Link>\n        </Unauthenticated>\n        <AuthLoading>\n          <Text>Loading...</Text>\n        </AuthLoading>\n        {{/if}}\n\n        {{#if (and (ne backend \"convex\") (eq auth \"clerk\"))}}\n        {!isLoaded ? (\n          <Text style={styles.apiStatusText}>Loading...</Text>\n        ) : isSignedIn ? (\n          <View style={styles.userCard}>\n            <View style={styles.userHeader}>\n              <Text style={styles.userWelcome}>\n                Welcome,{\" \"}\n                <Text style={styles.userName}>{user?.fullName ?? user?.firstName ?? \"there\"}</Text>\n              </Text>\n            </View>\n            <Text style={styles.userEmail}>{user?.emailAddresses[0]?.emailAddress}</Text>\n            <SignOutButton />\n          </View>\n        ) : (\n          <>\n            <Link href=\"/(auth)/sign-in\">\n              <Text style={styles.apiStatusText}>Sign in</Text>\n            </Link>\n            <Link href=\"/(auth)/sign-up\">\n              <Text style={styles.apiStatusText}>Sign up</Text>\n            </Link>\n          </>\n        )}\n        {{/if}}\n\n        {{#if (and (eq backend \"convex\") (eq auth \"better-auth\"))}}\n        {user ? (\n          <View style={styles.userCard}>\n            <View style={styles.userHeader}>\n              <Text style={styles.userWelcome}>\n                Welcome,{\" \"}\n                <Text style={styles.userName}>{user.name}</Text>\n              </Text>\n            </View>\n            <Text style={styles.userEmail}>{user.email}</Text>\n            <TouchableOpacity\n              style={styles.signOutButton}\n              onPress={() => {\n                authClient.signOut();\n              }}\n            >\n              <Text style={styles.signOutText}>Sign Out</Text>\n            </TouchableOpacity>\n          </View>\n        ) : null}\n        <View style={styles.apiStatusCard}>\n          <Text style={styles.apiStatusTitle}>API Status</Text>\n          <View style={styles.apiStatusRow}>\n            <View\n              style={[\n                styles.statusDot,\n                healthCheck === \"OK\"\n                  ? styles.statusDotSuccess\n                  : styles.statusDotWarning,\n              ]}\n            />\n            <Text style={styles.apiStatusText}>\n              {healthCheck === undefined\n                ? \"Checking...\"\n                : healthCheck === \"OK\"\n                ? \"Connected to API\"\n                : \"API Disconnected\"}\n            </Text>\n          </View>\n        </View>\n        {!user && (\n          <>\n            <SignIn />\n            <SignUp />\n          </>\n        )}\n        {{/if}}\n      </ScrollView>\n    </Container>\n  );\n}\n\nconst styles = StyleSheet.create((theme) => ({\n  container: {\n    paddingHorizontal: theme.spacing.md,\n  },\n  heroSection: {\n    paddingVertical: theme.spacing.xl,\n  },\n  heroTitle: {\n    fontSize: theme.fontSize[\"4xl\"],\n    fontWeight: \"bold\",\n    color: theme.colors.foreground,\n    marginBottom: theme.spacing.sm,\n  },\n  heroSubtitle: {\n    fontSize: theme.fontSize.lg,\n    color: theme.colors.mutedForeground,\n    lineHeight: 28,\n  },\n  statusCard: {\n    backgroundColor: theme.colors.card,\n    borderWidth: 1,\n    borderColor: theme.colors.border,\n    borderRadius: theme.borderRadius.xl,\n    padding: theme.spacing.lg,\n    marginBottom: theme.spacing.lg,\n    shadowColor: \"#000\",\n    shadowOffset: { width: 0, height: 1 },\n    shadowOpacity: 0.05,\n    shadowRadius: 3,\n    elevation: 2,\n  },\n  statusHeader: {\n    flexDirection: \"row\",\n    alignItems: \"center\",\n    justifyContent: \"space-between\",\n    marginBottom: theme.spacing.md,\n  },\n  statusTitle: {\n    fontSize: theme.fontSize.lg,\n    fontWeight: \"600\",\n    color: theme.colors.cardForeground,\n  },\n  statusBadge: {\n    backgroundColor: theme.colors.secondary,\n    paddingHorizontal: theme.spacing.sm + 4,\n    paddingVertical: theme.spacing.xs,\n    borderRadius: 9999,\n  },\n  statusBadgeText: {\n    fontSize: theme.fontSize.xs,\n    fontWeight: \"500\",\n    color: theme.colors.secondaryForeground,\n  },\n  statusRow: {\n    flexDirection: \"row\",\n    alignItems: \"center\",\n    gap: theme.spacing.sm + 4,\n  },\n  statusDot: {\n    height: 12,\n    width: 12,\n    borderRadius: 6,\n  },\n  statusDotSuccess: {\n    backgroundColor: theme.colors.success,\n  },\n  statusDotWarning: {\n    backgroundColor: \"#F59E0B\",\n  },\n  statusContent: {\n    flex: 1,\n  },\n  statusLabel: {\n    fontSize: theme.fontSize.sm,\n    fontWeight: \"500\",\n    color: theme.colors.cardForeground,\n  },\n  statusDescription: {\n    fontSize: theme.fontSize.xs,\n    color: theme.colors.mutedForeground,\n  },\n  userCard: {\n    backgroundColor: theme.colors.card,\n    borderWidth: 1,\n    borderColor: theme.colors.border,\n    borderRadius: theme.borderRadius.lg,\n    padding: theme.spacing.md,\n    marginBottom: theme.spacing.md,\n  },\n  userHeader: {\n    flexDirection: \"row\",\n    justifyContent: \"space-between\",\n    alignItems: \"center\",\n    marginBottom: theme.spacing.xs,\n  },\n  userWelcome: {\n    fontSize: theme.fontSize.base,\n    color: theme.colors.foreground,\n  },\n  userName: {\n    fontWeight: \"500\",\n  },\n  userEmail: {\n    fontSize: theme.fontSize.sm,\n    color: theme.colors.mutedForeground,\n    marginBottom: theme.spacing.md,\n  },\n  signOutButton: {\n    backgroundColor: theme.colors.destructive,\n    paddingVertical: theme.spacing.sm,\n    paddingHorizontal: theme.spacing.md,\n    borderRadius: theme.borderRadius.md,\n    alignSelf: \"flex-start\",\n  },\n  signOutText: {\n    color: theme.colors.destructiveForeground,\n    fontWeight: \"500\",\n  },\n  apiStatusCard: {\n    marginBottom: theme.spacing.md,\n    borderRadius: theme.borderRadius.lg,\n    borderWidth: 1,\n    borderColor: theme.colors.border,\n    padding: theme.spacing.md,\n  },\n  apiStatusTitle: {\n    marginBottom: theme.spacing.sm,\n    fontWeight: \"500\",\n    color: theme.colors.foreground,\n  },\n  apiStatusRow: {\n    flexDirection: \"row\",\n    alignItems: \"center\",\n    gap: theme.spacing.xs,\n  },\n  apiStatusText: {\n    color: theme.colors.mutedForeground,\n  },\n}));\n`],\n  [\"frontend/native/unistyles/app/+html.tsx.hbs\", `import { ScrollViewStyleReset } from \"expo-router/html\";\nimport { type PropsWithChildren } from \"react\";\n\nimport \"../unistyles\";\n\n// This file is web-only and used to configure the root HTML for every\n// web page during static rendering.\n// The contents of this function only run in Node.js environments and\n// do not have access to the DOM or browser APIs.\nexport default function Root({ children }: PropsWithChildren) {\n  return (\n    <html lang=\"en\">\n      <head>\n        <meta charSet=\"utf-8\" />\n        <meta httpEquiv=\"X-UA-Compatible\" content=\"IE=edge\" />\n        <meta\n          name=\"viewport\"\n          content=\"width=device-width, initial-scale=1, shrink-to-fit=no\"\n        />\n\n        {/*\n          Disable body scrolling on web. This makes ScrollView components work closer to how they do on native.\n          However, body scrolling is often nice to have for mobile web. If you want to enable it, remove this line.\n        */}\n        <ScrollViewStyleReset />\n\n        {/* Add any additional <head> elements that you want globally available on web... */}\n      </head>\n      <body>{children}</body>\n    </html>\n  );\n}\n`],\n  [\"frontend/native/unistyles/app/+not-found.tsx.hbs\", `import { Link, Stack } from \"expo-router\";\nimport { Text, View } from \"react-native\";\nimport { StyleSheet } from \"react-native-unistyles\";\nimport { Container } from \"@/components/container\";\n\nexport default function NotFoundScreen() {\n  return (\n    <>\n      <Stack.Screen options=\\\\{{ title: \"Oops!\" }} />\n      <Container>\n        <View style={styles.container}>\n          <View style={styles.content}>\n            <Text style={styles.emoji}>🤔</Text>\n            <Text style={styles.title}>Page Not Found</Text>\n            <Text style={styles.description}>\n              Sorry, the page you're looking for doesn't exist.\n            </Text>\n            <Link href=\"/\" style={styles.button}>\n              <Text style={styles.buttonText}>Go to Home</Text>\n            </Link>\n          </View>\n        </View>\n      </Container>\n    </>\n  );\n}\n\nconst styles = StyleSheet.create((theme) => ({\n  container: {\n    flex: 1,\n    justifyContent: \"center\",\n    alignItems: \"center\",\n    padding: theme.spacing.lg,\n  },\n  content: {\n    alignItems: \"center\",\n  },\n  emoji: {\n    fontSize: 64,\n    marginBottom: theme.spacing.md,\n  },\n  title: {\n    fontSize: theme.fontSize[\"2xl\"],\n    fontWeight: \"bold\",\n    color: theme.colors.foreground,\n    marginBottom: theme.spacing.sm,\n    textAlign: \"center\",\n  },\n  description: {\n    color: theme.colors.mutedForeground,\n    textAlign: \"center\",\n    marginBottom: theme.spacing.xl,\n    maxWidth: 280,\n  },\n  button: {\n    backgroundColor: \\`\\${theme.colors.primary}1A\\`, // 10% opacity\n    paddingHorizontal: theme.spacing.lg,\n    paddingVertical: theme.spacing.sm + 4,\n    borderRadius: theme.borderRadius.lg,\n  },\n  buttonText: {\n    color: theme.colors.primary,\n    fontWeight: \"500\",\n  },\n}));\n`],\n  [\"frontend/native/unistyles/app/modal.tsx.hbs\", `import { Container } from \"@/components/container\";\nimport { Text, View } from \"react-native\";\nimport { StyleSheet } from \"react-native-unistyles\";\n\nexport default function Modal() {\n  return (\n    <Container>\n      <View style={styles.container}>\n        <View style={styles.header}>\n          <Text style={styles.title}>Modal</Text>\n        </View>\n      </View>\n    </Container>\n  );\n}\n\nconst styles = StyleSheet.create((theme) => ({\n  container: {\n    flex: 1,\n    padding: theme.spacing.lg,\n  },\n  header: {\n    flexDirection: \"row\",\n    alignItems: \"center\",\n    justifyContent: \"space-between\",\n    marginBottom: theme.spacing.xl,\n  },\n  title: {\n    fontSize: theme.fontSize[\"2xl\"],\n    fontWeight: \"bold\",\n    color: theme.colors.foreground,\n  },\n}));\n`],\n  [\"frontend/native/unistyles/babel.config.js.hbs\", `module.exports = (api) => {\n\tapi.cache(true);\n\tconst plugins = [];\n\n\tplugins.push([\n\t\t\"react-native-unistyles/plugin\",\n\t\t{\n\t\t\troot: \"src\",\n\t\t\tautoProcessRoot: \"app\",\n\t\t\tautoProcessImports: [\"@/components\"],\n\t\t},\n\t]);\n\n\tplugins.push(\"react-native-worklets/plugin\");\n\n\treturn {\n\t\tpresets: [\"babel-preset-expo\"],\n\n\t\tplugins,\n\t};\n};\n`],\n  [\"frontend/native/unistyles/breakpoints.ts.hbs\", `export const breakpoints = {\n  xs: 0,\n  sm: 576,\n  md: 768,\n  lg: 992,\n  xl: 1200,\n  superLarge: 2000,\n  tvLike: 4000,\n} as const;\n`],\n  [\"frontend/native/unistyles/components/container.tsx.hbs\", `import React from \"react\";\nimport { SafeAreaView } from \"react-native-safe-area-context\";\nimport { StyleSheet } from \"react-native-unistyles\";\n\nexport const Container = ({ children }: { children: React.ReactNode }) => {\n  return <SafeAreaView style={styles.container}>{children}</SafeAreaView>;\n};\n\nconst styles = StyleSheet.create((theme, rt) => ({\n  container: {\n    flex: 1,\n    backgroundColor: theme.colors.background,\n    paddingBottom: rt.insets.bottom,\n  },\n}));\n`],\n  [\"frontend/native/unistyles/components/header-button.tsx.hbs\", `import FontAwesome from \"@expo/vector-icons/FontAwesome\";\nimport { forwardRef } from \"react\";\nimport { Pressable } from \"react-native\";\nimport { StyleSheet } from \"react-native-unistyles\";\n\nexport const HeaderButton = forwardRef<\n  typeof Pressable,\n  { onPress?: () => void }\n>(({ onPress }, ref) => {\n  return (\n    <Pressable onPress={onPress} style={styles.button}>\n      {({ pressed }) => (\n        <FontAwesome\n          name=\"info-circle\"\n          size={20}\n          color={styles.icon.color}\n          style=\\\\{{\n            opacity: pressed ? 0.7 : 1,\n          }}\n        />\n      )}\n    </Pressable>\n  );\n});\n\nconst styles = StyleSheet.create((theme) => ({\n  button: {\n    padding: theme.spacing.sm,\n    marginRight: theme.spacing.sm,\n    borderRadius: theme.borderRadius.lg,\n    backgroundColor: \\`\\${theme.colors.secondary}80\\`, // 50% opacity\n  },\n  icon: {\n    color: theme.colors.secondaryForeground,\n  },\n}));\n`],\n  [\"frontend/native/unistyles/components/tabbar-icon.tsx.hbs\", `import FontAwesome from \"@expo/vector-icons/FontAwesome\";\n\nexport const TabBarIcon = (props: {\n  name: React.ComponentProps<typeof FontAwesome>[\"name\"];\n  color: string;\n}) => {\n  return <FontAwesome size={24} style=\\\\{{ marginBottom: -3 }} {...props} />;\n};\n`],\n  [\"frontend/native/unistyles/index.js.hbs\", `import 'expo-router/entry';\nimport './unistyles';\n`],\n  [\"frontend/native/unistyles/metro.config.js.hbs\", `const { getDefaultConfig } = require(\"expo/metro-config\");\n\nconst config = getDefaultConfig(__dirname);\n\nmodule.exports = config;\n`],\n  [\"frontend/native/unistyles/package.json.hbs\", `{\n  \"name\": \"native\",\n  \"version\": \"1.0.0\",\n  \"private\": true,\n  \"main\": \"index.js\",\n  \"scripts\": {\n    \"dev\": \"expo start --clear\",\n    \"android\": \"expo run:android\",\n    \"ios\": \"expo run:ios\",\n    \"web\": \"expo start --web\"\n  },\n  \"dependencies\": {\n    \"@expo/vector-icons\": \"^15.1.1\",\n    \"@react-navigation/bottom-tabs\": \"^7.15.9\",\n    \"@react-navigation/drawer\": \"^7.9.4\",\n    \"@react-navigation/native\": \"^7.2.2\",\n    {{#if (includes examples \"ai\")}}\n    \"@stardazed/streams-text-encoding\": \"^1.0.2\",\n    \"@ungap/structured-clone\": \"^1.3.0\",\n    {{/if}}\n    \"babel-preset-expo\": \"~55.0.18\",\n    \"expo\": \"^55.0.17\",\n    \"expo-constants\": \"~55.0.15\",\n    \"expo-crypto\": \"~55.0.14\",\n    \"expo-dev-client\": \"~55.0.28\",\n    \"expo-font\": \"~55.0.6\",\n    \"expo-linking\": \"~55.0.14\",\n    \"expo-network\": \"~55.0.13\",\n    \"expo-router\": \"~55.0.13\",\n    \"expo-secure-store\": \"~55.0.13\",\n    \"expo-splash-screen\": \"~55.0.19\",\n    \"expo-status-bar\": \"~55.0.5\",\n    \"expo-system-ui\": \"~55.0.16\",\n    \"expo-web-browser\": \"~55.0.14\",\n    \"react\": \"19.2.0\",\n    \"react-dom\": \"19.2.0\",\n    \"react-native\": \"0.83.6\",\n    \"react-native-edge-to-edge\": \"^1.8.1\",\n    \"react-native-gesture-handler\": \"~2.30.0\",\n    \"react-native-nitro-modules\": \"^0.35.4\",\n    \"react-native-reanimated\": \"4.2.1\",\n    \"react-native-safe-area-context\": \"~5.6.2\",\n    \"react-native-screens\": \"~4.23.0\",\n    \"react-native-unistyles\": \"^3.2.3\",\n    \"react-native-web\": \"~0.21.0\",\n    \"react-native-worklets\": \"0.7.4\"\n  },\n  \"devDependencies\": {\n    \"ajv\": \"^8.17.1\",\n    \"@babel/core\": \"^7.28.0\",\n    \"@types/react\": \"~19.2.10\",\n    \"typescript\": \"~5.9.2\"\n  }\n}\n`],\n  [\"frontend/native/unistyles/theme.ts.hbs\", `const sharedColors = {\n  success: \"#22C55E\",\n  destructive: \"#EF4444\",\n  destructiveForeground: \"#FFFFFF\",\n  warning: \"#F59E0B\",\n  info: \"#3B82F6\",\n} as const;\n\nexport const lightTheme = {\n  colors: {\n    ...sharedColors,\n    typography: \"hsl(0 0% 0%)\",\n    background: \"hsl(0 0% 100%)\",\n    foreground: \"hsl(0 0% 0%)\",\n    card: \"hsl(0 0% 98%)\",\n    cardForeground: \"hsl(0 0% 0%)\",\n    primary: \"hsl(0 0% 10%)\",\n    primaryForeground: \"hsl(0 0% 100%)\",\n    secondary: \"hsl(0 0% 95%)\",\n    secondaryForeground: \"hsl(0 0% 0%)\",\n    muted: \"hsl(0 0% 96%)\",\n    mutedForeground: \"hsl(0 0% 45%)\",\n    accent: \"hsl(0 0% 96%)\",\n    accentForeground: \"hsl(0 0% 0%)\",\n    border: \"hsl(0 0% 90%)\",\n    input: \"hsl(0 0% 90%)\",\n    ring: \"hsl(0 0% 20%)\",\n  },\n  spacing: {\n    xs: 4,\n    sm: 8,\n    md: 16,\n    lg: 24,\n    xl: 32,\n    xxl: 48,\n  },\n  borderRadius: {\n    sm: 6,\n    md: 8,\n    lg: 12,\n    xl: 16,\n  },\n  fontSize: {\n    xs: 12,\n    sm: 14,\n    base: 16,\n    lg: 18,\n    xl: 20,\n    \"2xl\": 24,\n    \"3xl\": 30,\n    \"4xl\": 36,\n  },\n} as const;\n\nexport const darkTheme = {\n  colors: {\n    ...sharedColors,\n    typography: \"hsl(0 0% 100%)\",\n    background: \"hsl(0 0% 0%)\",\n    foreground: \"hsl(0 0% 100%)\",\n    card: \"hsl(0 0% 2%)\",\n    cardForeground: \"hsl(0 0% 100%)\",\n    primary: \"hsl(0 0% 90%)\",\n    primaryForeground: \"hsl(0 0% 0%)\",\n    secondary: \"hsl(0 0% 10%)\",\n    secondaryForeground: \"hsl(0 0% 100%)\",\n    muted: \"hsl(0 0% 8%)\",\n    mutedForeground: \"hsl(0 0% 65%)\",\n    accent: \"hsl(0 0% 8%)\",\n    accentForeground: \"hsl(0 0% 100%)\",\n    border: \"hsl(0 0% 15%)\",\n    input: \"hsl(0 0% 15%)\",\n    ring: \"hsl(0 0% 80%)\",\n  },\n  spacing: {\n    xs: 4,\n    sm: 8,\n    md: 16,\n    lg: 24,\n    xl: 32,\n    xxl: 48,\n  },\n  borderRadius: {\n    sm: 6,\n    md: 8,\n    lg: 12,\n    xl: 16,\n  },\n  fontSize: {\n    xs: 12,\n    sm: 14,\n    base: 16,\n    lg: 18,\n    xl: 20,\n    \"2xl\": 24,\n    \"3xl\": 30,\n    \"4xl\": 36,\n  },\n} as const;\n`],\n  [\"frontend/native/unistyles/tsconfig.json.hbs\", `{\n  \"extends\": \"expo/tsconfig.base\",\n  \"compilerOptions\": {\n    \"strict\": true,\n    \"jsx\": \"react-jsx\",\n    \"paths\": {\n      \"@/*\": [\"*\"]\n    }\n  },\n  \"include\": [\"**/*.ts\", \"**/*.tsx\", \".expo/types/**/*.ts\", \"expo-env.d.ts\"]\n}\n`],\n  [\"frontend/native/unistyles/unistyles.ts.hbs\", `import { StyleSheet } from \"react-native-unistyles\";\n\nimport { breakpoints } from \"./breakpoints\";\nimport { darkTheme, lightTheme } from \"./theme\";\n\ntype AppBreakpoints = typeof breakpoints;\n\ntype AppThemes = {\n  light: typeof lightTheme;\n  dark: typeof darkTheme;\n};\n\ndeclare module \"react-native-unistyles\" {\n  export interface UnistylesBreakpoints extends AppBreakpoints {}\n  export interface UnistylesThemes extends AppThemes {}\n}\n\nStyleSheet.configure({\n  breakpoints,\n  themes: {\n    light: lightTheme,\n    dark: darkTheme,\n  },\n  settings: {\n    adaptiveThemes: true,\n  },\n});\n`],\n  [\"frontend/native/uniwind/_gitignore\", `node_modules/\n.expo/\ndist/\nnpm-debug.*\n*.jks\n*.p8\n*.p12\n*.key\n*.mobileprovision\n*.orig.*\nweb-build/\n\n# macOS\n.DS_Store\n\n# Temporary files created by Metro to check the health of the file watcher\n.metro-health-check*\n\n# UniWind generated types\nuniwind-types.d.ts\n\n`],\n  [\"frontend/native/uniwind/app.json.hbs\", `{\n  \"expo\": {\n    \"scheme\": \"{{projectName}}\",\n    \"userInterfaceStyle\": \"automatic\",\n    \"orientation\": \"default\",\n    \"web\": {\n      \"bundler\": \"metro\"\n    },\n    \"name\": \"{{projectName}}\",\n    \"slug\": \"{{projectName}}\",\n    \"plugins\": [\n      \"expo-font\"\n    ],\n    \"experiments\": {\n      \"typedRoutes\": true,\n      \"reactCompiler\": true\n    }\n  }\n}\n`],\n  [\"frontend/native/uniwind/app/_layout.tsx.hbs\", `{{#if (includes examples \"ai\")}}\nimport \"@/polyfills\";\n{{/if}}\n{{#if (and (eq auth \"clerk\") (ne api \"none\") (ne backend \"convex\"))}}\nimport { useEffect } from \"react\";\nimport { setClerkAuthTokenGetter } from \"@/utils/clerk-auth\";\n{{/if}}\n\nimport \"@/global.css\";\n{{#if (and (ne backend \"convex\") (eq auth \"clerk\"))}}\nimport { ClerkProvider{{#unless (eq api \"none\")}}, useAuth{{/unless}} } from \"@clerk/expo\";\nimport { tokenCache } from \"@clerk/expo/token-cache\";\nimport { env } from \"@{{projectName}}/env/native\";\n{{/if}}\n\n{{#if (eq backend \"convex\")}}\n  {{#if (eq auth \"better-auth\")}}\n    import { ConvexReactClient } from \"convex/react\";\n    import { ConvexBetterAuthProvider } from \"@convex-dev/better-auth/react\";\n    import { authClient } from \"@/lib/auth-client\";\n    import { env } from \"@{{projectName}}/env/native\";\n  {{else}}\n    import { ConvexProvider, ConvexReactClient } from \"convex/react\";\n    import { env } from \"@{{projectName}}/env/native\";\n  {{/if}}\n\n  {{#if (eq auth \"clerk\")}}\n    import { ClerkProvider, useAuth } from \"@clerk/expo\";\n    import { ConvexProviderWithClerk } from \"convex/react-clerk\";\n    import { tokenCache } from \"@clerk/expo/token-cache\";\n  {{/if}}\n{{else}}\n  {{#unless (eq api \"none\")}}\n    import { QueryClientProvider } from \"@tanstack/react-query\";\n  {{/unless}}\n{{/if}}\n\nimport { Stack } from \"expo-router\";\nimport { HeroUINativeProvider } from \"heroui-native\";\nimport { GestureHandlerRootView } from \"react-native-gesture-handler\";\nimport { KeyboardProvider } from \"react-native-keyboard-controller\";\nimport { AppThemeProvider } from \"@/contexts/app-theme-context\";\n\n{{#if (eq api \"trpc\")}}\n  import { queryClient } from \"@/utils/trpc\";\n{{/if}}\n{{#if (eq api \"orpc\")}}\n  import { queryClient } from \"@/utils/orpc\";\n{{/if}}\n\nexport const unstable_settings = {\n  initialRouteName: \"(drawer)\",\n};\n\n{{#if (eq backend \"convex\")}}\n  const convex = new ConvexReactClient(env.EXPO_PUBLIC_CONVEX_URL, {\n    {{#if (eq auth \"better-auth\")}}\n    expectAuth: true,\n    {{/if}}\n    unsavedChangesWarning: false,\n  });\n{{/if}}\n\n{{#if (and (eq auth \"clerk\") (ne api \"none\") (ne backend \"convex\"))}}\nfunction ClerkApiAuthBridge() {\n  const { getToken } = useAuth();\n\n  useEffect(() => {\n    setClerkAuthTokenGetter(getToken);\n\n    return () => {\n      setClerkAuthTokenGetter(null);\n    };\n  }, [getToken]);\n\n  return null;\n}\n{{/if}}\n\nfunction StackLayout() {\n  return (\n    <Stack screenOptions=\\\\{{}}>\n      <Stack.Screen name=\"(drawer)\" options=\\\\{{ headerShown: false }} />\n      {{#if (eq auth \"clerk\")}}\n        <Stack.Screen name=\"(auth)\" options=\\\\{{ headerShown: false }} />\n      {{/if}}\n      <Stack.Screen name=\"modal\" options=\\\\{{ title: \"Modal\", presentation: \"modal\" }} />\n    </Stack>\n  );\n}\n\nexport default function Layout() {\n  return (\n    {{#if (eq backend \"convex\")}}\n      {{#if (eq auth \"clerk\")}}\n        <ClerkProvider tokenCache={tokenCache} publishableKey={env.EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY}>\n          <ConvexProviderWithClerk client={convex} useAuth={useAuth}>\n            <GestureHandlerRootView style=\\\\{{ flex: 1 }}>\n              <KeyboardProvider>\n                <AppThemeProvider>\n                  <HeroUINativeProvider>\n                    <StackLayout />\n                  </HeroUINativeProvider>\n                </AppThemeProvider>\n              </KeyboardProvider>\n            </GestureHandlerRootView>\n          </ConvexProviderWithClerk>\n        </ClerkProvider>\n      {{else if (eq auth \"better-auth\")}}\n        <ConvexBetterAuthProvider client={convex} authClient={authClient}>\n          <GestureHandlerRootView style=\\\\{{ flex: 1 }}>\n            <KeyboardProvider>\n              <AppThemeProvider>\n                <HeroUINativeProvider>\n                  <StackLayout />\n                </HeroUINativeProvider>\n              </AppThemeProvider>\n            </KeyboardProvider>\n          </GestureHandlerRootView>\n        </ConvexBetterAuthProvider>\n      {{else}}\n        <ConvexProvider client={convex}>\n          <GestureHandlerRootView style=\\\\{{ flex: 1 }}>\n            <KeyboardProvider>\n              <AppThemeProvider>\n                <HeroUINativeProvider>\n                  <StackLayout />\n                </HeroUINativeProvider>\n              </AppThemeProvider>\n            </KeyboardProvider>\n          </GestureHandlerRootView>\n        </ConvexProvider>\n      {{/if}}\n    {{else}}\n      {{#if (eq auth \"clerk\")}}\n        <ClerkProvider tokenCache={tokenCache} publishableKey={env.EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY}>\n          {{#unless (eq api \"none\")}}\n            <ClerkApiAuthBridge />\n            <QueryClientProvider client={queryClient}>\n              <GestureHandlerRootView style=\\\\{{ flex: 1 }}>\n                <KeyboardProvider>\n                  <AppThemeProvider>\n                    <HeroUINativeProvider>\n                      <StackLayout />\n                    </HeroUINativeProvider>\n                  </AppThemeProvider>\n                </KeyboardProvider>\n              </GestureHandlerRootView>\n            </QueryClientProvider>\n          {{else}}\n            <GestureHandlerRootView style=\\\\{{ flex: 1 }}>\n              <KeyboardProvider>\n                <AppThemeProvider>\n                  <HeroUINativeProvider>\n                    <StackLayout />\n                  </HeroUINativeProvider>\n                </AppThemeProvider>\n              </KeyboardProvider>\n            </GestureHandlerRootView>\n          {{/unless}}\n        </ClerkProvider>\n      {{else}}\n        {{#unless (eq api \"none\")}}\n          <QueryClientProvider client={queryClient}>\n            <GestureHandlerRootView style=\\\\{{ flex: 1 }}>\n              <KeyboardProvider>\n                <AppThemeProvider>\n                  <HeroUINativeProvider>\n                    <StackLayout />\n                  </HeroUINativeProvider>\n                </AppThemeProvider>\n              </KeyboardProvider>\n            </GestureHandlerRootView>\n          </QueryClientProvider>\n        {{else}}\n          <GestureHandlerRootView style=\\\\{{ flex: 1 }}>\n            <KeyboardProvider>\n              <AppThemeProvider>\n                <HeroUINativeProvider>\n                  <StackLayout />\n                </HeroUINativeProvider>\n              </AppThemeProvider>\n            </KeyboardProvider>\n          </GestureHandlerRootView>\n        {{/unless}}\n      {{/if}}\n    {{/if}}\n  );\n}\n`],\n  [\"frontend/native/uniwind/app/(drawer)/_layout.tsx.hbs\", `import React, { useCallback } from \"react\";\nimport { Ionicons, MaterialIcons } from \"@expo/vector-icons\";\nimport { Link } from \"expo-router\";\nimport { Drawer } from \"expo-router/drawer\";\nimport { useThemeColor } from \"heroui-native\";\nimport { Pressable, Text } from \"react-native\";\nimport { ThemeToggle } from \"@/components/theme-toggle\";\n\nfunction DrawerLayout() {\n  const themeColorForeground = useThemeColor(\"foreground\");\n  const themeColorBackground = useThemeColor(\"background\");\n\n  const renderThemeToggle = useCallback(() => <ThemeToggle />, []);\n\n  return (\n    <Drawer\n      screenOptions=\\\\{{\n        headerTintColor: themeColorForeground,\n        headerStyle: { backgroundColor: themeColorBackground },\n        headerTitleStyle: {\n          fontWeight: \"600\",\n          color: themeColorForeground,\n        },\n        headerRight: renderThemeToggle,\n        drawerStyle: { backgroundColor: themeColorBackground },\n      }}\n    >\n      <Drawer.Screen\n        name=\"index\"\n        options=\\\\{{\n          headerTitle: \"Home\",\n          drawerLabel: ({ color, focused }) => (\n            <Text style=\\\\{{ color: focused ? color : themeColorForeground }}>Home</Text>\n          ),\n          drawerIcon: ({ size, color, focused }) => (\n            <Ionicons name=\"home-outline\" size={size} color={focused ? color : themeColorForeground} />\n          ),\n        }}\n      />\n      <Drawer.Screen\n        name=\"(tabs)\"\n        options=\\\\{{\n          headerTitle: \"Tabs\",\n          drawerLabel: ({ color, focused }) => (\n            <Text style=\\\\{{ color: focused ? color : themeColorForeground }}>Tabs</Text>\n          ),\n          drawerIcon: ({ size, color, focused }) => (\n            <MaterialIcons name=\"border-bottom\" size={size} color={focused ? color : themeColorForeground} />\n          ),\n          headerRight: () => (\n            <Link href=\"/modal\" asChild>\n              <Pressable className=\"mr-4\">\n                <Ionicons name=\"add-outline\" size={24} color={themeColorForeground} />\n              </Pressable>\n            </Link>\n          ),\n        }}\n      />\n      {{#if (includes examples \"todo\")}}\n      <Drawer.Screen\n        name=\"todos\"\n        options=\\\\{{\n          headerTitle: \"Todos\",\n          drawerLabel: ({ color, focused }) => (\n            <Text style=\\\\{{ color: focused ? color : themeColorForeground }}>Todos</Text>\n          ),\n          drawerIcon: ({ size, color, focused }) => (\n            <Ionicons name=\"checkbox-outline\" size={size} color={focused ? color : themeColorForeground} />\n          ),\n        }}\n      />\n      {{/if}}\n      {{#if (includes examples \"ai\")}}\n      <Drawer.Screen\n        name=\"ai\"\n        options=\\\\{{\n          headerTitle: \"AI\",\n          drawerLabel: ({ color, focused }) => (\n            <Text style=\\\\{{ color: focused ? color : themeColorForeground }}>AI</Text>\n          ),\n          drawerIcon: ({ size, color, focused }) => (\n            <Ionicons name=\"chatbubble-ellipses-outline\" size={size} color={focused ? color : themeColorForeground} />\n          ),\n        }}\n      />\n      {{/if}}\n    </Drawer>\n  );\n}\n\nexport default DrawerLayout;`],\n  [\"frontend/native/uniwind/app/(drawer)/(tabs)/_layout.tsx.hbs\", `import { Tabs } from \"expo-router\";\nimport { Ionicons } from \"@expo/vector-icons\";\nimport { useThemeColor } from \"heroui-native\";\n\nexport default function TabLayout() {\n\tconst themeColorForeground = useThemeColor(\"foreground\");\n\tconst themeColorBackground = useThemeColor(\"background\");\n\n\treturn (\n\t\t<Tabs\n\t\t\tscreenOptions=\\\\{{\n\t\t\t\theaderShown: false,\n\t\t\t\theaderStyle: {\n\t\t\t\t\tbackgroundColor: themeColorBackground,\n\t\t\t\t},\n\t\t\t\theaderTintColor: themeColorForeground,\n\t\t\t\theaderTitleStyle: {\n\t\t\t\t\tcolor: themeColorForeground,\n\t\t\t\t\tfontWeight: \"600\",\n\t\t\t\t},\n\t\t\t\ttabBarStyle: {\n\t\t\t\t\tbackgroundColor: themeColorBackground,\n\t\t\t\t},\n\t\t\t}}\n\t\t>\n\t\t\t<Tabs.Screen\n\t\t\t\tname=\"index\"\n\t\t\t\toptions=\\\\{{\n\t\t\t\t\ttitle: \"Home\",\n\t\t\t\t\ttabBarIcon: ({ color, size }: { color: string; size: number }) => (\n\t\t\t\t\t\t<Ionicons name=\"home\" size={size} color={color} />\n\t\t\t\t\t),\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t<Tabs.Screen\n\t\t\t\tname=\"two\"\n\t\t\t\toptions=\\\\{{\n\t\t\t\t\ttitle: \"Explore\",\n\t\t\t\t\ttabBarIcon: ({ color, size }: { color: string; size: number }) => (\n\t\t\t\t\t\t<Ionicons name=\"compass\" size={size} color={color} />\n\t\t\t\t\t),\n\t\t\t\t}}\n\t\t\t/>\n\t\t</Tabs>\n\t);\n}\n`],\n  [\"frontend/native/uniwind/app/(drawer)/(tabs)/index.tsx.hbs\", `import { Container } from \"@/components/container\";\nimport { Text, View } from \"react-native\";\nimport { Card } from \"heroui-native\";\n\nexport default function Home() {\n\treturn (\n\t\t<Container className=\"p-6\">\n\t\t\t<View className=\"flex-1 justify-center items-center\">\n\t\t\t\t<Card variant=\"secondary\" className=\"p-8 items-center\">\n\t\t\t\t\t<Card.Title className=\"text-3xl mb-2\">Tab One</Card.Title>\n\t\t\t\t</Card>\n\t\t\t</View>\n\t\t</Container>\n\t);\n}\n`],\n  [\"frontend/native/uniwind/app/(drawer)/(tabs)/two.tsx.hbs\", `import { Container } from \"@/components/container\";\nimport { Text, View } from \"react-native\";\nimport { Card } from \"heroui-native\";\n\nexport default function TabTwo() {\n\treturn (\n\t\t<Container className=\"p-6\">\n\t\t\t<View className=\"flex-1 justify-center items-center\">\n\t\t\t\t<Card variant=\"secondary\" className=\"p-8 items-center\">\n\t\t\t\t\t<Card.Title className=\"text-3xl mb-2\">TabTwo</Card.Title>\n\t\t\t\t</Card>\n\t\t\t</View>\n\t\t</Container>\n\t);\n}\n`],\n  [\"frontend/native/uniwind/app/(drawer)/index.tsx.hbs\", `import { Text, View } from \"react-native\";\nimport { Container } from \"@/components/container\";\n{{#if (eq api \"orpc\")}}\nimport { useQuery } from \"@tanstack/react-query\";\nimport { orpc } from \"@/utils/orpc\";\n{{/if}}\n{{#if (eq api \"trpc\")}}\nimport { useQuery } from \"@tanstack/react-query\";\nimport { trpc } from \"@/utils/trpc\";\n{{/if}}\n{{#if (and (eq backend \"convex\") (eq auth \"clerk\"))}}\nimport { Link } from \"expo-router\";\nimport { Authenticated, AuthLoading, Unauthenticated, useQuery } from \"convex/react\";\nimport { api } from \"@{{projectName}}/backend/convex/_generated/api\";\nimport { useUser } from \"@clerk/expo\";\nimport { SignOutButton } from \"@/components/sign-out-button\";\n{{else if (and (ne backend \"convex\") (eq auth \"clerk\"))}}\nimport { Link } from \"expo-router\";\nimport { useAuth, useUser } from \"@clerk/expo\";\nimport { SignOutButton } from \"@/components/sign-out-button\";\n{{else if (and (eq backend \"convex\") (eq auth \"better-auth\"))}}\nimport { useConvexAuth, useQuery } from \"convex/react\";\nimport { api } from \"@{{projectName}}/backend/convex/_generated/api\";\nimport { authClient } from \"@/lib/auth-client\";\nimport { SignIn } from \"@/components/sign-in\";\nimport { SignUp } from \"@/components/sign-up\";\n{{else if (eq backend \"convex\")}}\nimport { useQuery } from \"convex/react\";\nimport { api } from \"@{{projectName}}/backend/convex/_generated/api\";\n{{/if}}\n{{#unless (or (eq backend \"none\") (and (eq backend \"convex\") (eq auth \"better-auth\")))}}\nimport { Ionicons } from \"@expo/vector-icons\";\n{{/unless}}\nimport { Button, Chip, Separator, Spinner, Surface, useThemeColor } from \"heroui-native\";\n\nexport default function Home() {\n{{#if (eq api \"orpc\")}}\nconst healthCheck = useQuery(orpc.healthCheck.queryOptions());\n{{/if}}\n{{#if (eq api \"trpc\")}}\nconst healthCheck = useQuery(trpc.healthCheck.queryOptions());\n{{/if}}\n{{#if (and (eq backend \"convex\") (eq auth \"clerk\"))}}\nconst { user } = useUser();\nconst healthCheck = useQuery(api.healthCheck.get);\nconst privateData = useQuery(api.privateData.get);\n{{else if (and (ne backend \"convex\") (eq auth \"clerk\"))}}\nconst { isLoaded, isSignedIn } = useAuth();\nconst { user } = useUser();\n{{else if (and (eq backend \"convex\") (eq auth \"better-auth\"))}}\nconst healthCheck = useQuery(api.healthCheck.get);\nconst { isAuthenticated } = useConvexAuth();\nconst user = useQuery(api.auth.getCurrentUser, isAuthenticated ? {} : \"skip\");\n{{else if (eq backend \"convex\")}}\nconst healthCheck = useQuery(api.healthCheck.get);\n{{/if}}\n{{#unless (eq backend \"none\")}}\nconst successColor = useThemeColor(\"success\");\nconst dangerColor = useThemeColor(\"danger\");\n\n{{#if (eq backend \"convex\")}}\nconst isConnected = healthCheck === \"OK\";\nconst isLoading = healthCheck === undefined;\n{{else}}\n{{#unless (eq api \"none\")}}\nconst isConnected = healthCheck?.data === \"OK\";\nconst isLoading = healthCheck?.isLoading;\n{{/unless}}\n{{/if}}\n{{/unless}}\n\nreturn (\n<Container className=\"px-4 pb-4\">\n  <View className=\"py-6 mb-5\">\n    <Text className=\"text-3xl font-semibold text-foreground tracking-tight\">\n      Better T Stack\n    </Text>\n    <Text className=\"text-muted text-sm mt-1\">Full-stack TypeScript starter</Text>\n  </View>\n\n  {{#unless (or (eq backend \"none\") (and (eq backend \"convex\") (eq auth \"better-auth\")))}}\n  <Surface variant=\"secondary\" className=\"p-4 rounded-xl\">\n    <View className=\"flex-row items-center justify-between mb-3\">\n      <Text className=\"text-foreground font-medium\">System Status</Text>\n      <Chip variant=\"secondary\" color={isConnected ? \"success\" : \"danger\" } size=\"sm\">\n        <Chip.Label>\n          {isConnected ? \"LIVE\" : \"OFFLINE\"}\n        </Chip.Label>\n      </Chip>\n    </View>\n\n    <Separator className=\"mb-3\" />\n\n    <Surface variant=\"tertiary\" className=\"p-3 rounded-lg\">\n      <View className=\"flex-row items-center\">\n        <View className={\\`w-2 h-2 rounded-full mr-3 \\${ isConnected ? \"bg-success\" : \"bg-muted\" }\\`} />\n        <View className=\"flex-1\">\n          <Text className=\"text-foreground text-sm font-medium\">\n            {{#if (eq backend \"convex\")}}\n            Convex Backend\n            {{else}}\n            {{#unless (eq api \"none\")}}\n            {{#if (eq api \"orpc\")}}ORPC{{else}}TRPC{{/if}} Backend\n            {{/unless}}\n            {{/if}}\n          </Text>\n          <Text className=\"text-muted text-xs mt-0.5\">\n            {isLoading\n            ? \"Checking connection...\"\n            : isConnected\n            ? \"Connected to API\"\n            : \"API Disconnected\"}\n          </Text>\n        </View>\n        {isLoading && <Spinner size=\"sm\" />}\n        {!isLoading && isConnected && (\n        <Ionicons name=\"checkmark-circle\" size={18} color={successColor} />\n        )}\n        {!isLoading && !isConnected && (\n        <Ionicons name=\"close-circle\" size={18} color={dangerColor} />\n        )}\n      </View>\n    </Surface>\n  </Surface>\n  {{/unless}}\n\n  {{#if (and (eq backend \"convex\") (eq auth \"clerk\"))}}\n  <Authenticated>\n    <Surface variant=\"secondary\" className=\"mt-5 p-4 rounded-xl\">\n      <View className=\"flex-row items-center justify-between\">\n        <View className=\"flex-1\">\n          <Text className=\"text-foreground font-medium\">{user?.emailAddresses[0].emailAddress}</Text>\n          <Text className=\"text-muted text-xs mt-0.5\">Private: {privateData?.message}</Text>\n        </View>\n        <SignOutButton />\n      </View>\n    </Surface>\n  </Authenticated>\n  <Unauthenticated>\n    <View className=\"mt-4 gap-3\">\n      <Link href=\"/(auth)/sign-in\" asChild>\n        <Button variant=\"secondary\"><Button.Label>Sign In</Button.Label></Button>\n      </Link>\n      <Link href=\"/(auth)/sign-up\" asChild>\n        <Button variant=\"ghost\"><Button.Label>Sign Up</Button.Label></Button>\n      </Link>\n    </View>\n  </Unauthenticated>\n  <AuthLoading>\n    <View className=\"mt-4 items-center\">\n      <Spinner size=\"sm\" />\n    </View>\n  </AuthLoading>\n  {{/if}}\n\n  {{#if (and (ne backend \"convex\") (eq auth \"clerk\"))}}\n  {!isLoaded ? (\n  <View className=\"mt-4 items-center\">\n    <Spinner size=\"sm\" />\n  </View>\n  ) : isSignedIn ? (\n  <Surface variant=\"secondary\" className=\"mt-5 p-4 rounded-xl\">\n    <View className=\"flex-row items-center justify-between\">\n      <View className=\"flex-1\">\n        <Text className=\"text-foreground font-medium\">\n          {user?.fullName ?? user?.firstName ?? \"Welcome\"}\n        </Text>\n        <Text className=\"text-muted text-xs mt-0.5\">\n          {user?.emailAddresses[0]?.emailAddress}\n        </Text>\n      </View>\n      <SignOutButton />\n    </View>\n  </Surface>\n  ) : (\n  <View className=\"mt-4 gap-3\">\n    <Link href=\"/(auth)/sign-in\" asChild>\n      <Button variant=\"secondary\"><Button.Label>Sign In</Button.Label></Button>\n    </Link>\n    <Link href=\"/(auth)/sign-up\" asChild>\n      <Button variant=\"ghost\"><Button.Label>Sign Up</Button.Label></Button>\n    </Link>\n  </View>\n  )}\n  {{/if}}\n\n  {{#if (and (eq backend \"convex\") (eq auth \"better-auth\"))}}\n  {user ? (\n  <Surface variant=\"secondary\" className=\"mb-4 p-4 rounded-xl\">\n    <View className=\"flex-row items-center justify-between\">\n      <View className=\"flex-1\">\n        <Text className=\"text-foreground font-medium\">{user.name}</Text>\n        <Text className=\"text-muted text-xs mt-0.5\">{user.email}</Text>\n      </View>\n      <Button\n        variant=\"danger\"\n        size=\"sm\"\n        onPress={() => {\n          authClient.signOut();\n        }}\n      >\n        Sign Out\n      </Button>\n    </View>\n  </Surface>\n  ) : null}\n  <Surface variant=\"secondary\" className=\"p-4 rounded-xl\">\n    <Text className=\"text-foreground font-medium mb-2\">API Status</Text>\n    <View className=\"flex-row items-center gap-2\">\n      <View className={\\`w-2 h-2 rounded-full \\${healthCheck===\"OK\" ? \"bg-success\" : \"bg-danger\" }\\`} />\n      <Text className=\"text-muted text-xs\">\n        {healthCheck === undefined\n        ? \"Checking...\"\n        : healthCheck === \"OK\"\n        ? \"Connected to API\"\n        : \"API Disconnected\"}\n      </Text>\n    </View>\n  </Surface>\n  {!user && (\n  <View className=\"mt-5 gap-4\">\n    <SignIn />\n    <SignUp />\n  </View>\n  )}\n  {{/if}}\n</Container>\n);\n}\n`],\n  [\"frontend/native/uniwind/app/+not-found.tsx.hbs\", `import { Link, Stack } from \"expo-router\";\nimport { Button, Surface } from \"heroui-native\";\nimport { Text, View } from \"react-native\";\n\nimport { Container } from \"@/components/container\";\n\nexport default function NotFoundScreen() {\n\treturn (\n\t\t<>\n\t\t\t<Stack.Screen options=\\\\{{ title: \"Not Found\" }} />\n\t\t\t<Container>\n\t\t\t\t<View className=\"flex-1 justify-center items-center p-4\">\n\t\t\t\t\t<Surface variant=\"secondary\" className=\"items-center p-6 max-w-sm rounded-lg\">\n\t\t\t\t\t\t<Text className=\"text-4xl mb-3\">🤔</Text>\n\t\t\t\t\t\t<Text className=\"text-foreground font-medium text-lg mb-1\">Page Not Found</Text>\n\t\t\t\t\t\t<Text className=\"text-muted text-sm text-center mb-4\">\n\t\t\t\t\t\t\tThe page you're looking for doesn't exist.\n\t\t\t\t\t\t</Text>\n\t\t\t\t\t\t<Link href=\"/\" asChild>\n\t\t\t\t\t\t\t<Button size=\"sm\">Go Home</Button>\n\t\t\t\t\t\t</Link>\n\t\t\t\t\t</Surface>\n\t\t\t\t</View>\n\t\t\t</Container>\n\t\t</>\n\t);\n}\n`],\n  [\"frontend/native/uniwind/app/modal.tsx.hbs\", `import { Ionicons } from \"@expo/vector-icons\";\nimport { router } from \"expo-router\";\nimport { Button, Surface, useThemeColor } from \"heroui-native\";\nimport { Text, View } from \"react-native\";\n\nimport { Container } from \"@/components/container\";\n\nfunction Modal() {\n\tconst accentForegroundColor = useThemeColor(\"accent-foreground\");\n\n\tfunction handleClose() {\n\t\trouter.back();\n\t}\n\n\treturn (\n\t\t<Container>\n\t\t\t<View className=\"flex-1 justify-center items-center p-4\">\n\t\t\t\t<Surface variant=\"secondary\" className=\"p-5 w-full max-w-sm rounded-lg\">\n\t\t\t\t\t<View className=\"items-center\">\n\t\t\t\t\t\t<View className=\"w-12 h-12 bg-accent rounded-lg items-center justify-center mb-3\">\n\t\t\t\t\t\t\t<Ionicons name=\"checkmark\" size={24} color={accentForegroundColor} />\n\t\t\t\t\t\t</View>\n\t\t\t\t\t\t<Text className=\"text-foreground font-medium text-lg mb-1\">Modal Screen</Text>\n\t\t\t\t\t\t<Text className=\"text-muted text-sm text-center mb-4\">\n\t\t\t\t\t\t\tThis is an example modal screen for dialogs and confirmations.\n\t\t\t\t\t\t</Text>\n\t\t\t\t\t</View>\n\t\t\t\t\t<Button onPress={handleClose} className=\"w-full\" size=\"sm\">\n\t\t\t\t\t\t<Button.Label>Close</Button.Label>\n\t\t\t\t\t</Button>\n\t\t\t\t</Surface>\n\t\t\t</View>\n\t\t</Container>\n\t);\n}\n\nexport default Modal;\n`],\n  [\"frontend/native/uniwind/components/container.tsx.hbs\", `import { cn } from \"heroui-native\";\nimport { type PropsWithChildren } from \"react\";\nimport { ScrollView, View, type ScrollViewProps, type ViewProps } from \"react-native\";\nimport Animated, { type AnimatedProps } from \"react-native-reanimated\";\nimport { useSafeAreaInsets } from \"react-native-safe-area-context\";\n\nconst AnimatedView = Animated.createAnimatedComponent(View);\n\ntype Props = AnimatedProps<ViewProps> & {\n  className?: string;\n  isScrollable?: boolean;\n  scrollViewProps?: Omit<ScrollViewProps, \"contentContainerStyle\">;\n};\n\nexport function Container({\n  children,\n  className,\n  isScrollable = true,\n  scrollViewProps,\n  ...props\n}: PropsWithChildren<Props>) {\n  const insets = useSafeAreaInsets();\n\n  return (\n    <AnimatedView\n      className={cn(\"flex-1 bg-background\", className)}\n      style=\\\\{{\n        paddingBottom: insets.bottom,\n      }}\n      {...props}\n    >\n      {isScrollable ? (\n        <ScrollView\n          contentContainerStyle=\\\\{{ flexGrow: 1 }}\n          keyboardShouldPersistTaps=\"handled\"\n          contentInsetAdjustmentBehavior=\"automatic\"\n          {...scrollViewProps}\n        >\n          {children}\n        </ScrollView>\n      ) : (\n        <View className=\"flex-1\">{children}</View>\n      )}\n    </AnimatedView>\n  );\n}\n`],\n  [\"frontend/native/uniwind/components/theme-toggle.tsx.hbs\", `import { Ionicons } from '@expo/vector-icons';\nimport * as Haptics from 'expo-haptics';\nimport { Platform, Pressable } from 'react-native';\nimport Animated, { FadeOut, ZoomIn } from 'react-native-reanimated';\nimport { withUniwind } from 'uniwind';\nimport { useAppTheme } from '@/contexts/app-theme-context';\n\nconst StyledIonicons = withUniwind(Ionicons);\n\nexport function ThemeToggle() {\n\tconst { toggleTheme, isLight } = useAppTheme();\n\n\treturn (\n\t\t<Pressable\n\t\t\tonPress={() => {\n\t\t\t\tif (Platform.OS === 'ios') {\n\t\t\t\t\tHaptics.impactAsync(Haptics.ImpactFeedbackStyle.Light);\n\t\t\t\t}\n\t\t\t\ttoggleTheme();\n\t\t\t}}\n\t\t\tclassName=\"px-2.5\"\n\t\t>\n\t\t\t{isLight ? (\n\t\t\t\t<Animated.View key=\"moon\" entering={ZoomIn} exiting={FadeOut}>\n\t\t\t\t\t<StyledIonicons name=\"moon\" size={20} className=\"text-foreground\" />\n\t\t\t\t</Animated.View>\n\t\t\t) : (\n\t\t\t\t<Animated.View key=\"sun\" entering={ZoomIn} exiting={FadeOut}>\n\t\t\t\t\t<StyledIonicons name=\"sunny\" size={20} className=\"text-foreground\" />\n\t\t\t\t</Animated.View>\n\t\t\t)}\n\t\t</Pressable>\n\t);\n}\n\n`],\n  [\"frontend/native/uniwind/contexts/app-theme-context.tsx.hbs\", `import React, { createContext, useCallback, useContext, useMemo } from 'react';\nimport { Uniwind, useUniwind } from 'uniwind';\n\ntype ThemeName = 'light' | 'dark';\n\ntype AppThemeContextType = {\n    currentTheme: string;\n    isLight: boolean;\n    isDark: boolean;\n    setTheme: (theme: ThemeName) => void;\n    toggleTheme: () => void;\n}\n\nconst AppThemeContext = createContext<AppThemeContextType | undefined>(\n    undefined\n);\n\nexport const AppThemeProvider = ({ children }: { children: React.ReactNode }) => {\n    const { theme } = useUniwind();\n\n    const isLight = useMemo(() => {\n        return theme === 'light';\n    }, [theme]);\n\n    const isDark = useMemo(() => {\n        return theme === 'dark';\n    }, [theme]);\n\n    const setTheme = useCallback((newTheme: ThemeName) => {\n        Uniwind.setTheme(newTheme);\n    }, []);\n\n    const toggleTheme = useCallback(() => {\n        Uniwind.setTheme(theme === 'light' ? 'dark' : 'light');\n    }, [theme]);\n\n    const value = useMemo(\n        () => ({\n            currentTheme: theme,\n            isLight,\n            isDark,\n            setTheme,\n            toggleTheme,\n        }),\n        [theme, isLight, isDark, setTheme, toggleTheme]\n    );\n\n    return (\n        <AppThemeContext.Provider value={value}>\n            {children}\n        </AppThemeContext.Provider>\n    );\n};\n\nexport function useAppTheme() {\n    const context = useContext(AppThemeContext);\n    if (!context) {\n        throw new Error('useAppTheme must be used within AppThemeProvider');\n    }\n    return context;\n}\n\n`],\n  [\"frontend/native/uniwind/global.css\", `@import \"tailwindcss\";\n@import \"uniwind\";\n@import \"heroui-native/styles\";\n\n@source './node_modules/heroui-native/lib';\n`],\n  [\"frontend/native/uniwind/metro.config.js.hbs\", `const { getDefaultConfig } = require(\"expo/metro-config\");\nconst { withUniwindConfig } = require(\"uniwind/metro\");\nconst { wrapWithReanimatedMetroConfig } = require(\"react-native-reanimated/metro-config\");\n\n/** @type {import('expo/metro-config').MetroConfig} */\nconst config = getDefaultConfig(__dirname);\n\nconst uniwindConfig = withUniwindConfig(wrapWithReanimatedMetroConfig(config), {\n  cssEntryFile: \"./global.css\",\n  dtsFile: \"./uniwind-types.d.ts\",\n});\n\nmodule.exports = uniwindConfig;\n`],\n  [\"frontend/native/uniwind/package.json.hbs\", `{\n  \"name\": \"native\",\n  \"version\": \"1.0.0\",\n  \"private\": true,\n  \"main\": \"expo-router/entry\",\n  \"scripts\": {\n    \"start\": \"expo start\",\n    \"dev\": \"expo start --clear\",\n    \"android\": \"expo run:android\",\n    \"ios\": \"expo run:ios\",\n    \"prebuild\": \"expo prebuild\",\n    \"web\": \"expo start --web\"\n  },\n  \"dependencies\": {\n    \"@expo/metro-runtime\": \"~55.0.10\",\n    \"@expo/vector-icons\": \"^15.1.1\",\n    \"@gorhom/bottom-sheet\": \"^5.2.10\",\n    \"@react-navigation/drawer\": \"^7.9.4\",\n    \"@react-navigation/elements\": \"^2.9.14\",\n    {{#if (includes examples \"ai\")}}\n    \"@stardazed/streams-text-encoding\": \"^1.0.2\",\n    \"@ungap/structured-clone\": \"^1.3.0\",\n    {{/if}}\n    \"expo\": \"^55.0.17\",\n    \"expo-constants\": \"~55.0.15\",\n    \"expo-font\": \"~55.0.6\",\n    \"expo-haptics\": \"~55.0.14\",\n    \"expo-linking\": \"~55.0.14\",\n    \"expo-network\": \"~55.0.13\",\n    \"expo-router\": \"~55.0.13\",\n    \"expo-secure-store\": \"~55.0.13\",\n    \"expo-status-bar\": \"~55.0.5\",\n    \"expo-web-browser\": \"~55.0.14\",\n    \"heroui-native\": \"^1.0.2\",\n    \"react\": \"19.2.0\",\n    \"react-dom\": \"19.2.0\",\n    \"react-native\": \"0.83.6\",\n    \"react-native-gesture-handler\": \"~2.30.0\",\n    \"react-native-keyboard-controller\": \"1.20.7\",\n    \"react-native-reanimated\": \"4.2.1\",\n    \"react-native-safe-area-context\": \"~5.6.2\",\n    \"react-native-screens\": \"~4.23.0\",\n    \"react-native-svg\": \"15.15.3\",\n    \"react-native-web\": \"~0.21.0\",\n    \"react-native-worklets\": \"0.7.4\",\n    \"tailwind-merge\": \"^3.5.0\",\n    \"tailwind-variants\": \"^3.2.2\",\n    \"tailwindcss\": \"^4.2.4\",\n    \"uniwind\": \"^1.6.3\"\n  },\n  \"devDependencies\": {\n    \"@types/node\": \"^24.10.0\",\n    \"@types/react\": \"~19.2.10\",\n    \"typescript\": \"~5.9.2\"\n  }\n}\n`],\n  [\"frontend/native/uniwind/tsconfig.json.hbs\", `{\n  \"extends\": \"expo/tsconfig.base\",\n  \"compilerOptions\": {\n    \"strict\": true,\n    \"paths\": {\n      \"@/*\": [\"./*\"]\n    }\n  },\n  \"include\": [\n    \"**/*.ts\",\n    \"**/*.tsx\",\n    \".expo/types/**/*.ts\",\n    \"expo-env.d.ts\"\n  ]\n}`],\n  [\"frontend/native/uniwind/uniwind-env.d.ts\", `/// <reference types=\"uniwind/types\" />\n`],\n  [\"frontend/nuxt/_gitignore\", `# Nuxt dev/build outputs\n.output\n.data\n.nuxt\n.nitro\n.cache\ndist\n.wrangler\n.alchemy\n\n# Node dependencies\nnode_modules\n\n# Logs\nlogs\n*.log\n\n# Misc\n.DS_Store\n.fleet\n.idea\n\n# Local env files\n.env\n.env.*\n!.env.example\n\n`],\n  [\"frontend/nuxt/app/app.config.ts.hbs\", `export default defineAppConfig({\n  ui: {\n    colors: {\n      primary: 'emerald',\n      neutral: 'neutral',\n    },\n  }\n})\n`],\n  [\"frontend/nuxt/app/app.vue.hbs\", `<script setup lang=\"ts\">\n{{#if (eq api \"orpc\")}}\nimport { VueQueryDevtools } from '@tanstack/vue-query-devtools'\n{{/if}}\n</script>\n\n<template>\n    <NuxtAnnouncer />\n    <NuxtRouteAnnouncer />\n    <NuxtLoadingIndicator />\n    <UApp>\n        <NuxtLayout>\n            <NuxtPage />\n        </NuxtLayout>\n    </UApp>\n    {{#if (eq api \"orpc\")}}\n    <VueQueryDevtools />\n    {{/if}}\n</template>\n`],\n  [\"frontend/nuxt/app/assets/css/main.css\", `@import \"tailwindcss\";\n@import \"@nuxt/ui\";\n`],\n  [\"frontend/nuxt/app/components/Header.vue.hbs\", `<script setup lang=\"ts\">\nimport type { NavigationMenuItem } from '@nuxt/ui'\n{{#if (eq auth \"better-auth\")}}\nimport UserMenu from './UserMenu.vue'\n{{/if}}\n\nconst route = useRoute()\n\nconst items = computed<NavigationMenuItem[]>(() => [\n    { label: \"Home\", to: \"/\", active: route.path === \"/\" },\n    {{#if (or (eq auth \"better-auth\") (eq auth \"clerk\"))}}\n    { label: \"Dashboard\", to: \"/dashboard\", active: route.path.startsWith(\"/dashboard\") },\n    {{/if}}\n    {{#if (includes examples \"todo\")}}\n    { label: \"Todos\", to: \"/todos\", active: route.path.startsWith(\"/todos\") },\n    {{/if}}\n    {{#if (includes examples \"ai\")}}\n    { label: \"AI Chat\", to: \"/ai\", active: route.path.startsWith(\"/ai\") },\n    {{/if}}\n])\n</script>\n\n<template>\n  <UHeader>\n    <template #left>\n      <UNavigationMenu :items=\"items\" />\n    </template>\n\n    <template #right>\n      <UColorModeButton />\n      {{#if (eq auth \"better-auth\")}}\n      <UserMenu />\n      {{/if}}\n    </template>\n\n    <template #body>\n      <UNavigationMenu :items=\"items\" orientation=\"vertical\" class=\"-mx-2.5\" />\n    </template>\n  </UHeader>\n</template>\n`],\n  [\"frontend/nuxt/app/layouts/default.vue.hbs\", `<script setup></script>\n\n<template>\n  <div>\n    <Header />\n    <UMain>\n      <slot />\n    </UMain>\n  </div>\n</template>\n`],\n  [\"frontend/nuxt/app/pages/index.vue.hbs\", `<script setup lang=\"ts\">\n{{#if (eq backend \"convex\")}}\nimport { api } from \"@{{ projectName }}/backend/convex/_generated/api\";\nimport { useConvexQuery } from \"convex-vue\";\n{{else}}\n  {{#unless (eq api \"none\")}}\nconst { $orpc } = useNuxtApp()\nimport { useQuery } from '@tanstack/vue-query'\n  {{/unless}}\n{{/if}}\n\nconst TITLE_TEXT = \\`\n ██████╗ ███████╗████████╗████████╗███████╗██████╗\n ██╔══██╗██╔════╝╚══██╔══╝╚══██╔══╝██╔════╝██╔══██╗\n ██████╔╝█████╗     ██║      ██║   █████╗  ██████╔╝\n ██╔══██╗██╔══╝     ██║      ██║   ██╔══╝  ██╔══██╗\n ██████╔╝███████╗   ██║      ██║   ███████╗██║  ██║\n ╚═════╝ ╚══════╝   ╚═╝      ╚═╝   ╚══════╝╚═╝  ╚═╝\n\n ████████╗    ███████╗████████╗ █████╗  ██████╗██╗  ██╗\n ╚══██╔══╝    ██╔════╝╚══██╔══╝██╔══██╗██╔════╝██║ ██╔╝\n    ██║       ███████╗   ██║   ███████║██║     █████╔╝\n    ██║       ╚════██║   ██║   ██╔══██║██║     ██╔═██╗\n    ██║       ███████║   ██║   ██║  ██║╚██████╗██║  ██╗\n    ╚═╝       ╚══════╝   ╚═╝   ╚═╝  ╚═╝ ╚═════╝╚═╝  ╚═╝\n \\`;\n\n{{#if (eq backend \"convex\")}}\nconst healthCheck = useConvexQuery(api.healthCheck.get, {});\n{{else}}\n  {{#unless (eq api \"none\")}}\nconst healthCheck = useQuery($orpc.healthCheck.queryOptions())\n\nonServerPrefetch(async () => {\n  try {\n    await healthCheck.suspense()\n  } catch {}\n})\n  {{/unless}}\n{{/if}}\n</script>\n\n<template>\n  <UContainer class=\"py-8\">\n    <pre class=\"overflow-x-auto font-mono text-sm whitespace-pre-wrap\">\\\\{{ TITLE_TEXT }}</pre>\n\n    <div class=\"grid gap-6 mt-6\">\n      <UCard>\n        <template #header>\n          <div class=\"font-medium\">API Status</div>\n        </template>\n\n        {{#if (eq backend \"convex\")}}\n        <div class=\"flex items-center gap-2\">\n          <UIcon\n            :name=\"healthCheck === undefined ? 'i-lucide-loader-2' : healthCheck.data.value === 'OK' ? 'i-lucide-check-circle' : 'i-lucide-x-circle'\"\n            :class=\"[\n              healthCheck === undefined ? 'animate-spin text-muted' : '',\n              healthCheck?.data.value === 'OK' ? 'text-success' : 'text-error'\n            ]\"\n          />\n          <span class=\"text-sm\">\n            \\\\{{\n              healthCheck === undefined\n                ? \"Checking...\"\n                : healthCheck.data.value === \"OK\"\n                  ? \"Connected\"\n                  : \"Error\"\n            }}\n          </span>\n        </div>\n        {{else}}\n        {{#unless (eq api \"none\")}}\n        <div class=\"flex items-center gap-2\">\n          <UIcon\n            :name=\"healthCheck.isLoading.value ? 'i-lucide-loader-2' : healthCheck.isSuccess.value ? 'i-lucide-check-circle' : 'i-lucide-x-circle'\"\n            :class=\"[\n              healthCheck.isLoading.value ? 'animate-spin text-muted' : '',\n              healthCheck.isSuccess.value ? 'text-success' : '',\n              healthCheck.isError.value ? 'text-error' : ''\n            ]\"\n          />\n          <span class=\"text-sm\">\n            <template v-if=\"healthCheck.isLoading.value\">\n              Checking...\n            </template>\n            <template v-else-if=\"healthCheck.isSuccess.value\">\n              Connected (\\\\{{ healthCheck.data.value }})\n            </template>\n            <template v-else-if=\"healthCheck.isError.value\">\n              Error: \\\\{{ healthCheck.error.value?.message || 'Failed to connect' }}\n            </template>\n            <template v-else>\n              Idle\n            </template>\n          </span>\n        </div>\n        {{/unless}}\n        {{/if}}\n      </UCard>\n    </div>\n  </UContainer>\n</template>\n`],\n  [\"frontend/nuxt/nuxt.config.ts.hbs\", `import \"@{{projectName}}/env/web\";\n\n// https://nuxt.com/docs/api/configuration/nuxt-config\nexport default defineNuxtConfig({\n  compatibilityDate: 'latest',\n  devtools: { enabled: true },\n  experimental: {\n    payloadExtraction: 'client',\n  },\n  modules: [\n    '@nuxt/ui'\n    {{#if (eq backend \"convex\")}},\n    'convex-nuxt'\n    {{/if}}\n  ],\n  css: ['~/assets/css/main.css'],\n  devServer: {\n    port: 3001\n  },\n  {{#if (eq backend \"convex\")}}\n  convex: {\n    url: process.env.NUXT_PUBLIC_CONVEX_URL,\n  },\n  {{else if (and (ne backend \"self\") (ne backend \"none\"))}}\n  runtimeConfig: {\n    public: {\n      serverUrl: process.env.NUXT_PUBLIC_SERVER_URL ?? \"\",\n    }\n  },\n  {{/if}}\n})\n`],\n  [\"frontend/nuxt/package.json.hbs\", `{\n  \"name\": \"web\",\n  \"private\": true,\n  \"type\": \"module\",\n  \"scripts\": {\n    \"build\": \"nuxt build\",\n    \"dev\": \"nuxt dev\",\n    \"generate\": \"nuxt generate\",\n    \"preview\": \"nuxt preview\",\n    \"postinstall\": \"nuxt prepare\"\n  },\n  \"dependencies\": {\n    \"@nuxt/ui\": \"^4.5.1\",\n    \"nuxt\": \"^4.4.4\"\n  },\n  \"devDependencies\": {\n    \"tailwindcss\": \"^4.2.1\",\n    \"@iconify-json/lucide\": \"^1.2.96\"\n  }\n}\n`],\n  [\"frontend/nuxt/public/favicon.ico\", `[Binary file]`],\n  [\"frontend/nuxt/public/robots.txt\", `User-Agent: *\nDisallow:\n`],\n  [\"frontend/nuxt/server/tsconfig.json\", `{\n  \"extends\": \"../.nuxt/tsconfig.server.json\"\n}\n`],\n  [\"frontend/nuxt/tsconfig.json.hbs\", `{\n  // https://nuxt.com/docs/guide/concepts/typescript\n  \"files\": [],\n  \"references\": [\n    {\n      \"path\": \"./.nuxt/tsconfig.app.json\"\n    },\n    {\n      \"path\": \"./.nuxt/tsconfig.server.json\"\n    },\n    {\n      \"path\": \"./.nuxt/tsconfig.shared.json\"\n    },\n    {\n      \"path\": \"./.nuxt/tsconfig.node.json\"\n    }\n  ]\n}\n`],\n  [\"frontend/react/next/next-env.d.ts.hbs\", `/// <reference types=\"next\" />\n/// <reference types=\"next/image-types/global\" />\n\n// NOTE: This file should not be edited\n// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.\n`],\n  [\"frontend/react/next/next.config.ts.hbs\", `import \"@{{projectName}}/env/web\";\n{{#if (eq webDeploy \"cloudflare\")}}\nimport { initOpenNextCloudflareForDev } from \"@opennextjs/cloudflare\";\n{{/if}}\nimport type { NextConfig } from \"next\";\n\nconst nextConfig: NextConfig = {\n\ttypedRoutes: true,\n\treactCompiler: true,\n\t{{#if (includes examples \"ai\")}}\n\ttranspilePackages: [\"shiki\"],\n\t{{/if}}\n\t{{#if (and (eq backend \"self\") (eq dbSetup \"turso\"))}}\n\tserverExternalPackages: [\"libsql\", \"@libsql/client\"],\n\t{{/if}}\n};\n\nexport default nextConfig;\n\n{{#if (eq webDeploy \"cloudflare\")}}\ninitOpenNextCloudflareForDev();\n{{/if}}\n`],\n  [\"frontend/react/next/package.json.hbs\", `{\n  \"name\": \"web\",\n  \"version\": \"0.1.0\",\n  \"private\": true,\n  \"scripts\": {\n    \"dev\": \"next dev --port 3001\",\n    \"build\": \"next build\",\n    \"start\": \"next start\"\n  },\n  \"dependencies\": {\n    \"@{{projectName}}/ui\": \"{{#if (eq packageManager \"npm\")}}*{{else}}workspace:*{{/if}}\",\n    \"lucide-react\": \"^0.546.0\",\n    \"next\": \"^16.2.0\",\n    \"next-themes\": \"^0.4.6\",\n    \"react\": \"^19.2.3\",\n    \"react-dom\": \"^19.2.3\",\n    \"sonner\": \"^2.0.5\",\n    \"babel-plugin-react-compiler\": \"^1.0.0\"\n  },\n  \"devDependencies\": {\n    \"@tailwindcss/postcss\": \"^4.1.18\",\n    \"@types/node\": \"^20\",\n    \"@types/react\": \"^19.2.10\",\n    \"@types/react-dom\": \"^19.2.3\",\n    \"tailwindcss\": \"^4.1.18\"\n  }\n}\n`],\n  [\"frontend/react/next/postcss.config.mjs.hbs\", `const config = {\n  plugins: [\"@tailwindcss/postcss\"],\n};\n\nexport default config;\n`],\n  [\"frontend/react/next/src/app/favicon.ico\", `[Binary file]`],\n  [\"frontend/react/next/src/app/layout.tsx.hbs\", `import type { Metadata } from \"next\";\nimport { Geist, Geist_Mono } from \"next/font/google\";\nimport \"../index.css\";\n{{#if (eq auth \"clerk\")}}import { ClerkProvider } from \"@clerk/nextjs\";\n{{/if}}{{#if (and (eq backend \"convex\") (eq auth \"better-auth\"))}}\nimport { getToken } from \"@/lib/auth-server\";\n{{/if}}\nimport Providers from \"@/components/providers\";\nimport Header from \"@/components/header\";\n\nconst geistSans = Geist({\n  variable: \"--font-geist-sans\",\n  subsets: [\"latin\"],\n});\n\nconst geistMono = Geist_Mono({\n  variable: \"--font-geist-mono\",\n  subsets: [\"latin\"],\n});\n\nexport const metadata: Metadata = {\n  title: \"{{projectName}}\",\n  description: \"{{projectName}}\",\n};\n\n{{#if (and (eq backend \"convex\") (eq auth \"better-auth\"))}}\nexport default async function RootLayout({\n  children,\n}: Readonly<{\n  children: React.ReactNode;\n}>) {\n  const token = await getToken();\n  return (\n    <html lang=\"en\" suppressHydrationWarning>\n      <body\n        className={\\`\\${geistSans.variable} \\${geistMono.variable} antialiased\\`}\n      >\n        <Providers initialToken={token}>\n          <div className=\"grid grid-rows-[auto_1fr] h-svh\">\n            <Header />\n            {children}\n          </div>\n        </Providers>\n      </body>\n    </html>\n  );\n}\n{{else}}\nexport default function RootLayout({\n  children,\n}: Readonly<{\n  children: React.ReactNode;\n}>) {\n\treturn (\n\t\t<html lang=\"en\" suppressHydrationWarning>\n\t\t\t<body\n\t\t\t\tclassName={\\`\\${geistSans.variable} \\${geistMono.variable} antialiased\\`}\n\t\t\t>\n\t\t\t\t{{#if (eq auth \"clerk\")}}<ClerkProvider>\n\t\t\t\t\t<Providers>\n\t\t\t\t\t\t<div className=\"grid grid-rows-[auto_1fr] h-svh\">\n\t\t\t\t\t\t\t<Header />\n\t\t\t\t\t\t\t{children}\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</Providers>\n\t\t\t\t</ClerkProvider>{{else}}<Providers>\n\t\t\t\t\t<div className=\"grid grid-rows-[auto_1fr] h-svh\">\n\t\t\t\t\t\t<Header />\n\t\t\t\t\t\t{children}\n\t\t\t\t\t</div>\n\t\t\t\t</Providers>{{/if}}\n\t\t\t</body>\n\t\t</html>\n\t);\n}\n{{/if}}\n`],\n  [\"frontend/react/next/src/app/page.tsx.hbs\", `\"use client\"\n{{#if (eq backend \"convex\")}}\nimport { useQuery } from \"convex/react\";\nimport { api } from \"@{{projectName}}/backend/convex/_generated/api\";\n{{else if (or (eq api \"orpc\") (eq api \"trpc\"))}}\nimport { useQuery } from \"@tanstack/react-query\";\n  {{#if (eq api \"orpc\")}}\nimport { orpc } from \"@/utils/orpc\";\n  {{/if}}\n  {{#if (eq api \"trpc\")}}\nimport { trpc } from \"@/utils/trpc\";\n  {{/if}}\n{{/if}}\n\nconst TITLE_TEXT = \\`\n ██████╗ ███████╗████████╗████████╗███████╗██████╗\n ██╔══██╗██╔════╝╚══██╔══╝╚══██╔══╝██╔════╝██╔══██╗\n ██████╔╝█████╗     ██║      ██║   █████╗  ██████╔╝\n ██╔══██╗██╔══╝     ██║      ██║   ██╔══╝  ██╔══██╗\n ██████╔╝███████╗   ██║      ██║   ███████╗██║  ██║\n ╚═════╝ ╚══════╝   ╚═╝      ╚═╝   ╚══════╝╚═╝  ╚═╝\n\n ████████╗    ███████╗████████╗ █████╗  ██████╗██╗  ██╗\n ╚══██╔══╝    ██╔════╝╚══██╔══╝██╔══██╗██╔════╝██║ ██╔╝\n    ██║       ███████╗   ██║   ███████║██║     █████╔╝\n    ██║       ╚════██║   ██║   ██╔══██║██║     ██╔═██╗\n    ██║       ███████║   ██║   ██║  ██║╚██████╗██║  ██╗\n    ╚═╝       ╚══════╝   ╚═╝   ╚═╝  ╚═╝ ╚═════╝╚═╝  ╚═╝\n \\`;\n\nexport default function Home() {\n  {{#if (eq backend \"convex\")}}\n  const healthCheck = useQuery(api.healthCheck.get);\n  {{else if (eq api \"orpc\")}}\n  const healthCheck = useQuery(orpc.healthCheck.queryOptions());\n  {{else if (eq api \"trpc\")}}\n  const healthCheck = useQuery(trpc.healthCheck.queryOptions());\n  {{/if}}\n\n  return (\n    <div className=\"container mx-auto max-w-3xl px-4 py-2\">\n      <pre className=\"overflow-x-auto font-mono text-sm\">{TITLE_TEXT}</pre>\n      <div className=\"grid gap-6\">\n        <section className=\"rounded-lg border p-4\">\n          <h2 className=\"mb-2 font-medium\">API Status</h2>\n          {{#if (eq backend \"convex\")}}\n          <div className=\"flex items-center gap-2\">\n            <div\n              className={\\`h-2 w-2 rounded-full \\${healthCheck === \"OK\" ? \"bg-green-500\" : healthCheck === undefined ? \"bg-orange-400\" : \"bg-red-500\"}\\`}\n            />\n            <span className=\"text-sm text-muted-foreground\">\n              {healthCheck === undefined\n                ? \"Checking...\"\n                : healthCheck === \"OK\"\n                  ? \"Connected\"\n                  : \"Error\"}\n            </span>\n          </div>\n          {{else}}\n            {{#unless (eq api \"none\")}}\n            <div className=\"flex items-center gap-2\">\n              <div\n                className={\\`h-2 w-2 rounded-full \\${healthCheck.data ? \"bg-green-500\" : \"bg-red-500\"}\\`}\n              />\n              <span className=\"text-sm text-muted-foreground\">\n                {healthCheck.isLoading\n                  ? \"Checking...\"\n                  : healthCheck.data\n                    ? \"Connected\"\n                    : \"Disconnected\"}\n              </span>\n            </div>\n            {{/unless}}\n          {{/if}}\n        </section>\n      </div>\n    </div>\n  );\n}\n`],\n  [\"frontend/react/next/src/components/mode-toggle.tsx.hbs\", `\"use client\"\n\nimport * as React from \"react\"\nimport { Moon, Sun } from \"lucide-react\"\nimport { useTheme } from \"next-themes\"\nimport { Button } from \"@{{projectName}}/ui/components/button\"\nimport {\n  DropdownMenu,\n  DropdownMenuContent,\n  DropdownMenuItem,\n  DropdownMenuTrigger,\n} from \"@{{projectName}}/ui/components/dropdown-menu\"\n\nexport function ModeToggle() {\n  const { setTheme } = useTheme()\n\n  return (\n    <DropdownMenu>\n      <DropdownMenuTrigger render={<Button variant=\"outline\" size=\"icon\" />}>\n        <Sun className=\"h-[1.2rem] w-[1.2rem] rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0\" />\n        <Moon className=\"absolute h-[1.2rem] w-[1.2rem] rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100\" />\n        <span className=\"sr-only\">Toggle theme</span>\n      </DropdownMenuTrigger>\n      <DropdownMenuContent align=\"end\">\n        <DropdownMenuItem onClick={() => setTheme(\"light\")}>\n          Light\n        </DropdownMenuItem>\n        <DropdownMenuItem onClick={() => setTheme(\"dark\")}>\n          Dark\n        </DropdownMenuItem>\n        <DropdownMenuItem onClick={() => setTheme(\"system\")}>\n          System\n        </DropdownMenuItem>\n      </DropdownMenuContent>\n    </DropdownMenu>\n  )\n}\n`],\n  [\"frontend/react/next/src/components/providers.tsx.hbs\", `\"use client\";\n\n{{#if (and (eq auth \"clerk\") (ne api \"none\"))}}\nimport { useEffect } from \"react\";\nimport { setClerkAuthTokenGetter } from \"@/utils/clerk-auth\";\n{{/if}}\n{{#if (and (eq auth \"clerk\") (or (eq backend \"convex\") (ne api \"none\")))}}\nimport { useAuth } from \"@clerk/nextjs\";\n{{/if}}\n{{#if (eq backend \"convex\")}}\n{{#if (eq auth \"clerk\")}}\nimport { ConvexReactClient } from \"convex/react\";\nimport { ConvexProviderWithClerk } from \"convex/react-clerk\";\nimport { env } from \"@{{projectName}}/env/web\";\n{{else if (eq auth \"better-auth\")}}\nimport { ConvexReactClient } from \"convex/react\";\nimport { ConvexBetterAuthProvider } from \"@convex-dev/better-auth/react\";\nimport { authClient } from \"@/lib/auth-client\";\nimport { env } from \"@{{projectName}}/env/web\";\n{{else}}\nimport { ConvexProvider, ConvexReactClient } from \"convex/react\";\nimport { env } from \"@{{projectName}}/env/web\";\n{{/if}}\n{{else}}\n{{#unless (eq api \"none\")}}\nimport { QueryClientProvider } from \"@tanstack/react-query\";\nimport { ReactQueryDevtools } from \"@tanstack/react-query-devtools\";\n{{#if (eq api \"orpc\")}}\nimport { queryClient } from \"@/utils/orpc\";\n{{/if}}\n{{#if (eq api \"trpc\")}}\nimport { queryClient } from \"@/utils/trpc\";\n{{/if}}\n{{/unless}}\n{{/if}}\nimport { ThemeProvider } from \"./theme-provider\";\nimport { Toaster } from \"@{{projectName}}/ui/components/sonner\";\n\n{{#if (eq backend \"convex\")}}\nconst convex = new ConvexReactClient(env.NEXT_PUBLIC_CONVEX_URL);\n{{/if}}\n\n{{#if (and (eq auth \"clerk\") (ne backend \"convex\") (ne api \"none\"))}}\nfunction ClerkApiAuthBridge() {\n  const { getToken } = useAuth();\n\n  useEffect(() => {\n    setClerkAuthTokenGetter(getToken);\n\n    return () => {\n      setClerkAuthTokenGetter(null);\n    };\n  }, [getToken]);\n\n  return null;\n}\n{{/if}}\n\nexport default function Providers({\n  children,\n{{#if (and (eq backend \"convex\") (eq auth \"better-auth\"))}}\n  initialToken,\n{{/if}}\n}: {\n  children: React.ReactNode;\n{{#if (and (eq backend \"convex\") (eq auth \"better-auth\"))}}\n  initialToken?: string | null;\n{{/if}}\n}) {\n  return (\n    <ThemeProvider\n      attribute=\"class\"\n      defaultTheme=\"system\"\n      enableSystem\n      disableTransitionOnChange\n    >\n      {{#if (eq backend \"convex\")}}\n      {{#if (eq auth \"clerk\")}}\n      <ConvexProviderWithClerk client={convex} useAuth={useAuth}>\n        {children}\n      </ConvexProviderWithClerk>\n      {{else if (eq auth \"better-auth\")}}\n      <ConvexBetterAuthProvider\n        client={convex}\n        authClient={authClient}\n        initialToken={initialToken}\n      >\n        {children}\n      </ConvexBetterAuthProvider>\n      {{else}}\n      <ConvexProvider client={convex}>{children}</ConvexProvider>\n      {{/if}}\n      {{else}}\n      {{#unless (eq api \"none\")}}\n      <QueryClientProvider client={queryClient}>\n        {{#if (eq auth \"clerk\")}}\n        <ClerkApiAuthBridge />\n        {{/if}}\n        {{#if (eq api \"orpc\")}}\n        {children}\n        {{/if}}\n        {{#if (eq api \"trpc\")}}\n        {children}\n        {{/if}}\n        <ReactQueryDevtools />\n      </QueryClientProvider>\n      {{else}}\n      {children}\n      {{/unless}}\n      {{/if}}\n      <Toaster richColors />\n    </ThemeProvider>\n  );\n}\n`],\n  [\"frontend/react/next/src/components/theme-provider.tsx.hbs\", `\"use client\"\n\nimport * as React from \"react\"\nimport { ThemeProvider as NextThemesProvider } from \"next-themes\"\n\nexport function ThemeProvider({\n  children,\n  ...props\n}: React.ComponentProps<typeof NextThemesProvider>) {\n  return <NextThemesProvider {...props}>{children}</NextThemesProvider>\n}\n`],\n  [\"frontend/react/next/tsconfig.json.hbs\", `{\n  \"compilerOptions\": {\n    \"target\": \"ES2017\",\n    \"lib\": [\"dom\", \"dom.iterable\", \"esnext\"],\n    \"allowJs\": true,\n    \"skipLibCheck\": true,\n    \"strict\": true,\n    \"noEmit\": true,\n    \"esModuleInterop\": true,\n    \"module\": \"esnext\",\n    \"moduleResolution\": \"bundler\",\n    \"resolveJsonModule\": true,\n    \"isolatedModules\": true,\n    \"verbatimModuleSyntax\": true,\n    \"jsx\": \"react-jsx\",\n    \"incremental\": true,\n    \"plugins\": [\n      {\n        \"name\": \"next\"\n      }\n    ],\n    \"paths\": {\n      \"@/*\": [\"./src/*\"],\n      \"@{{projectName}}/ui/*\": [\"../../packages/ui/src/*\"]\n    }{{#if (or (eq serverDeploy \"cloudflare\") (eq webDeploy \"cloudflare\"))}},\n    \"types\": [\n      \"@cloudflare/workers-types\"\n    ]{{/if}}\n  },\n  \"include\": [\n    {{#if (eq serverDeploy \"cloudflare\")}}\n    \"../server/env.d.ts\",\n    {{/if}}\n    \"next-env.d.ts\",\n    \"**/*.ts\",\n    \"**/*.tsx\",\n    \".next/types/**/*.ts\",\n    \".next/dev/types/**/*.ts\"\n  ],\n  \"exclude\": [\n    \"./node_modules\"\n  ]\n}\n`],\n  [\"frontend/react/react-router/package.json.hbs\", `{\n  \"name\": \"web\",\n  \"private\": true,\n  \"type\": \"module\",\n  \"scripts\": {\n    \"build\": \"react-router build\",\n    \"dev\": \"react-router dev\",\n    \"start\": \"react-router-serve ./build/server/index.js\",\n    \"typecheck\": \"react-router typegen && tsc\"\n  },\n  \"dependencies\": {\n    \"@{{projectName}}/ui\": \"{{#if (eq packageManager \"npm\")}}*{{else}}workspace:*{{/if}}\",\n    \"@react-router/fs-routes\": \"^7.14.1\",\n    \"@react-router/node\": \"^7.14.1\",\n    \"@react-router/serve\": \"^7.14.1\",\n    \"isbot\": \"^5.1.39\",\n    \"lucide-react\": \"^1.8.0\",\n    \"next-themes\": \"^0.4.6\",\n    \"react\": \"^19.2.5\",\n    \"react-dom\": \"^19.2.5\",\n    \"react-router\": \"^7.14.1\",\n    \"sonner\": \"^2.0.7\"\n  },\n  \"devDependencies\": {\n    \"@react-router/dev\": \"^7.14.1\",\n    \"@tailwindcss/vite\": \"^4.2.2\",\n    \"@types/node\": \"^20\",\n    \"@types/react\": \"^19.2.14\",\n    \"@types/react-dom\": \"^19.2.3\",\n    \"react-router-devtools\": \"^1.1.0\",\n    \"tailwindcss\": \"^4.2.2\",\n    \"vite\": \"^8.0.8\",\n    \"vite-tsconfig-paths\": \"^6.1.1\"\n  }\n}\n`],\n  [\"frontend/react/react-router/public/favicon.ico\", `[Binary file]`],\n  [\"frontend/react/react-router/react-router.config.ts\", `import type { Config } from \"@react-router/dev/config\";\n\nexport default {\n  ssr: false,\n  appDirectory: \"src\",\n  future: {\n    v8_middleware: true,\n  },\n} satisfies Config;\n`],\n  [\"frontend/react/react-router/src/components/mode-toggle.tsx.hbs\", `import { Moon, Sun } from \"lucide-react\";\n\nimport { Button } from \"@{{projectName}}/ui/components/button\";\nimport {\n  DropdownMenu,\n  DropdownMenuContent,\n  DropdownMenuItem,\n  DropdownMenuTrigger,\n} from \"@{{projectName}}/ui/components/dropdown-menu\";\nimport { useTheme } from \"@/components/theme-provider\";\n\nexport function ModeToggle() {\n  const { setTheme } = useTheme();\n\n  return (\n    <DropdownMenu>\n      <DropdownMenuTrigger render={<Button variant=\"outline\" size=\"icon\" />}>\n        <Sun className=\"h-[1.2rem] w-[1.2rem] scale-100 rotate-0 transition-all dark:scale-0 dark:-rotate-90\" />\n        <Moon className=\"absolute h-[1.2rem] w-[1.2rem] scale-0 rotate-90 transition-all dark:scale-100 dark:rotate-0\" />\n        <span className=\"sr-only\">Toggle theme</span>\n      </DropdownMenuTrigger>\n      <DropdownMenuContent align=\"end\">\n        <DropdownMenuItem onClick={() => setTheme(\"light\")}>Light</DropdownMenuItem>\n        <DropdownMenuItem onClick={() => setTheme(\"dark\")}>Dark</DropdownMenuItem>\n        <DropdownMenuItem onClick={() => setTheme(\"system\")}>System</DropdownMenuItem>\n      </DropdownMenuContent>\n    </DropdownMenu>\n  );\n}\n`],\n  [\"frontend/react/react-router/src/components/theme-provider.tsx.hbs\", `import * as React from \"react\";\nimport { ThemeProvider as NextThemesProvider } from \"next-themes\";\n\nexport function ThemeProvider({\n  children,\n  ...props\n}: React.ComponentProps<typeof NextThemesProvider>) {\n  return <NextThemesProvider {...props}>{children}</NextThemesProvider>;\n}\n\nexport { useTheme } from \"next-themes\";\n`],\n  [\"frontend/react/react-router/src/root.tsx.hbs\", `import {\n  isRouteErrorResponse,\n  Links,\n  Meta,\n  Outlet,\n  Scripts,\n  ScrollRestoration,\n} from \"react-router\";\nimport type { Route } from \"./+types/root\";\nimport \"./index.css\";\nimport Header from \"./components/header\";\nimport { ThemeProvider } from \"./components/theme-provider\";\nimport { Toaster } from \"@{{projectName}}/ui/components/sonner\";\n{{#if (eq auth \"clerk\")}}\nimport { ClerkProvider{{#if (or (eq backend \"convex\") (ne api \"none\"))}}, useAuth{{/if}} } from \"@clerk/react-router\";\nimport { clerkMiddleware, rootAuthLoader } from \"@clerk/react-router/server\";\n{{/if}}\n{{#if (and (eq auth \"clerk\") (ne backend \"convex\") (ne api \"none\"))}}\nimport { useEffect } from \"react\";\nimport { setClerkAuthTokenGetter } from \"@/utils/clerk-auth\";\n{{/if}}\n\n{{#if (eq backend \"convex\")}}\nimport { ConvexReactClient } from \"convex/react\";\nimport { env } from \"@{{projectName}}/env/web\";\n  {{#if (eq auth \"clerk\")}}\nimport { ConvexProviderWithClerk } from \"convex/react-clerk\";\n  {{else if (eq auth \"better-auth\")}}\nimport { ConvexBetterAuthProvider } from \"@convex-dev/better-auth/react\";\nimport { authClient } from \"@/lib/auth-client\";\n  {{else}}\nimport { ConvexProvider } from \"convex/react\";\n  {{/if}}\n{{else}}\n  {{#unless (eq api \"none\")}}\nimport { QueryClientProvider } from \"@tanstack/react-query\";\nimport { ReactQueryDevtools } from \"@tanstack/react-query-devtools\";\n    {{#if (eq api \"orpc\")}}\nimport { queryClient } from \"./utils/orpc\";\n    {{/if}}\n    {{#if (eq api \"trpc\")}}\nimport { queryClient } from \"./utils/trpc\";\n    {{/if}}\n  {{/unless}}\n{{/if}}\n\n{{#if (eq auth \"clerk\")}}\nexport const middleware: Route.MiddlewareFunction[] = [clerkMiddleware()];\n\nexport const loader = (args: Route.LoaderArgs) => rootAuthLoader(args);\n{{/if}}\n\n{{#if (and (eq auth \"clerk\") (ne backend \"convex\") (ne api \"none\"))}}\nfunction ClerkApiAuthBridge() {\n  const { getToken } = useAuth();\n\n  useEffect(() => {\n    setClerkAuthTokenGetter(getToken);\n\n    return () => {\n      setClerkAuthTokenGetter(null);\n    };\n  }, [getToken]);\n\n  return null;\n}\n{{/if}}\n\nexport const links: Route.LinksFunction = () => [\n  { rel: \"preconnect\", href: \"https://fonts.googleapis.com\" },\n  { rel: \"preconnect\", href: \"https://fonts.gstatic.com\", crossOrigin: \"anonymous\" },\n  {\n    rel: \"stylesheet\",\n    href:\n      \"https://fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&display=swap\",\n  },\n];\n\nexport function Layout({ children }: { children: React.ReactNode }) {\n  return (\n    <html lang=\"en\">\n      <head>\n        <meta charSet=\"utf-8\" />\n        <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n        <Meta />\n        <Links />\n      </head>\n      <body>\n        {children}\n        <ScrollRestoration />\n        <Scripts />\n      </body>\n    </html>\n  );\n}\n\n{{#if (eq backend \"convex\")}}\n{{#if (eq auth \"clerk\")}}\nexport default function App({ loaderData }: Route.ComponentProps) {\n{{else if (eq auth \"better-auth\")}}\nexport default function App() {\n{{else}}\nexport default function App() {\n{{/if}}\n  {{#if (eq auth \"better-auth\")}}\n  const convex = new ConvexReactClient(env.VITE_CONVEX_URL, {\n    expectAuth: true,\n  });\n  {{else}}\n  const convex = new ConvexReactClient(env.VITE_CONVEX_URL);\n  {{/if}}\n  {{#if (eq auth \"clerk\")}}\n  return (\n    <ClerkProvider loaderData={loaderData}>\n      <ConvexProviderWithClerk client={convex} useAuth={useAuth}>\n        <ThemeProvider\n          attribute=\"class\"\n          defaultTheme=\"dark\"\n          disableTransitionOnChange\n          storageKey=\"vite-ui-theme\"\n        >\n          <div className=\"grid grid-rows-[auto_1fr] h-svh\">\n            <Header />\n            <Outlet />\n          </div>\n          <Toaster richColors />\n        </ThemeProvider>\n      </ConvexProviderWithClerk>\n    </ClerkProvider>\n  );\n  {{else if (eq auth \"better-auth\")}}\n  return (\n    <ConvexBetterAuthProvider client={convex} authClient={authClient}>\n      <ThemeProvider\n        attribute=\"class\"\n        defaultTheme=\"dark\"\n        disableTransitionOnChange\n        storageKey=\"vite-ui-theme\"\n      >\n        <div className=\"grid grid-rows-[auto_1fr] h-svh\">\n          <Header />\n          <Outlet />\n        </div>\n        <Toaster richColors />\n      </ThemeProvider>\n    </ConvexBetterAuthProvider>\n  );\n  {{else}}\n  return (\n    <ConvexProvider client={convex}>\n      <ThemeProvider\n        attribute=\"class\"\n        defaultTheme=\"dark\"\n        disableTransitionOnChange\n        storageKey=\"vite-ui-theme\"\n      >\n        <div className=\"grid grid-rows-[auto_1fr] h-svh\">\n          <Header />\n          <Outlet />\n        </div>\n        <Toaster richColors />\n      </ThemeProvider>\n    </ConvexProvider>\n  );\n  {{/if}}\n}\n{{else if (eq auth \"clerk\")}}\nexport default function App({ loaderData }: Route.ComponentProps) {\n  return (\n    <ClerkProvider loaderData={loaderData}>\n      {{#unless (eq api \"none\")}}\n      <ClerkApiAuthBridge />\n      {{/unless}}\n      {{#if (eq api \"orpc\")}}\n      <QueryClientProvider client={queryClient}>\n        <ThemeProvider\n          attribute=\"class\"\n          defaultTheme=\"dark\"\n          disableTransitionOnChange\n          storageKey=\"vite-ui-theme\"\n        >\n          <div className=\"grid grid-rows-[auto_1fr] h-svh\">\n            <Header />\n            <Outlet />\n          </div>\n          <Toaster richColors />\n        </ThemeProvider>\n        <ReactQueryDevtools position=\"bottom\" buttonPosition=\"bottom-right\" />\n      </QueryClientProvider>\n      {{else if (eq api \"trpc\")}}\n      <QueryClientProvider client={queryClient}>\n        <ThemeProvider\n          attribute=\"class\"\n          defaultTheme=\"dark\"\n          disableTransitionOnChange\n          storageKey=\"vite-ui-theme\"\n        >\n          <div className=\"grid grid-rows-[auto_1fr] h-svh\">\n            <Header />\n            <Outlet />\n          </div>\n          <Toaster richColors />\n        </ThemeProvider>\n        <ReactQueryDevtools position=\"bottom\" buttonPosition=\"bottom-right\" />\n      </QueryClientProvider>\n      {{else}}\n      <ThemeProvider\n        attribute=\"class\"\n        defaultTheme=\"dark\"\n        disableTransitionOnChange\n        storageKey=\"vite-ui-theme\"\n      >\n        <div className=\"grid grid-rows-[auto_1fr] h-svh\">\n          <Header />\n          <Outlet />\n        </div>\n        <Toaster richColors />\n      </ThemeProvider>\n      {{/if}}\n    </ClerkProvider>\n  );\n}\n{{else if (eq api \"orpc\")}}\nexport default function App() {\n  return (\n    <QueryClientProvider client={queryClient}>\n      <ThemeProvider\n        attribute=\"class\"\n        defaultTheme=\"dark\"\n        disableTransitionOnChange\n        storageKey=\"vite-ui-theme\"\n      >\n        <div className=\"grid grid-rows-[auto_1fr] h-svh\">\n          <Header />\n          <Outlet />\n        </div>\n        <Toaster richColors />\n      </ThemeProvider>\n      <ReactQueryDevtools position=\"bottom\" buttonPosition=\"bottom-right\" />\n    </QueryClientProvider>\n  );\n}\n{{else if (eq api \"trpc\")}}\nexport default function App() {\n  return (\n    <QueryClientProvider client={queryClient}>\n      <ThemeProvider\n        attribute=\"class\"\n        defaultTheme=\"dark\"\n        disableTransitionOnChange\n        storageKey=\"vite-ui-theme\"\n      >\n        <div className=\"grid grid-rows-[auto_1fr] h-svh\">\n          <Header />\n          <Outlet />\n        </div>\n        <Toaster richColors />\n      </ThemeProvider>\n      <ReactQueryDevtools position=\"bottom\" buttonPosition=\"bottom-right\" />\n    </QueryClientProvider>\n  );\n}\n{{else}}\nexport default function App() {\n  return (\n    <ThemeProvider\n      attribute=\"class\"\n      defaultTheme=\"dark\"\n      disableTransitionOnChange\n      storageKey=\"vite-ui-theme\"\n    >\n      <div className=\"grid grid-rows-[auto_1fr] h-svh\">\n        <Header />\n        <Outlet />\n      </div>\n      <Toaster richColors />\n    </ThemeProvider>\n  );\n}\n{{/if}}\n\nexport function ErrorBoundary({ error }: Route.ErrorBoundaryProps) {\n  let message = \"Oops!\";\n  let details = \"An unexpected error occurred.\";\n  let stack: string | undefined;\n  if (isRouteErrorResponse(error)) {\n    message = error.status === 404 ? \"404\" : \"Error\";\n    details =\n      error.status === 404\n        ? \"The requested page could not be found.\"\n        : error.statusText || details;\n  } else if (import.meta.env.DEV && error && error instanceof Error) {\n    details = error.message;\n    stack = error.stack;\n  }\n  return (\n    <main className=\"pt-16 p-4 container mx-auto\">\n      <h1>{message}</h1>\n      <p>{details}</p>\n      {stack && (\n        <pre className=\"w-full p-4 overflow-x-auto\">\n          <code>{stack}</code>\n        </pre>\n      )}\n    </main>\n  );\n}\n`],\n  [\"frontend/react/react-router/src/routes.ts\", `import { type RouteConfig } from \"@react-router/dev/routes\";\nimport { flatRoutes } from \"@react-router/fs-routes\";\n\nexport default flatRoutes() satisfies RouteConfig;\n`],\n  [\"frontend/react/react-router/src/routes/_index.tsx.hbs\", `import type { Route } from \"./+types/_index\";\n{{#if (eq backend \"convex\")}}\nimport { useQuery } from \"convex/react\";\nimport { api } from \"@{{projectName}}/backend/convex/_generated/api\";\n{{else if (or (eq api \"orpc\") (eq api \"trpc\"))}}\nimport { useQuery } from \"@tanstack/react-query\";\n  {{#if (eq api \"orpc\")}}\n  import { orpc } from \"@/utils/orpc\";\n  {{/if}}\n  {{#if (eq api \"trpc\")}}\n  import { trpc } from \"@/utils/trpc\";\n  {{/if}}\n{{/if}}\n\nconst TITLE_TEXT = \\`\n ██████╗ ███████╗████████╗████████╗███████╗██████╗\n ██╔══██╗██╔════╝╚══██╔══╝╚══██╔══╝██╔════╝██╔══██╗\n ██████╔╝█████╗     ██║      ██║   █████╗  ██████╔╝\n ██╔══██╗██╔══╝     ██║      ██║   ██╔══╝  ██╔══██╗\n ██████╔╝███████╗   ██║      ██║   ███████╗██║  ██║\n ╚═════╝ ╚══════╝   ╚═╝      ╚═╝   ╚══════╝╚═╝  ╚═╝\n\n ████████╗    ███████╗████████╗ █████╗  ██████╗██╗  ██╗\n ╚══██╔══╝    ██╔════╝╚══██╔══╝██╔══██╗██╔════╝██║ ██╔╝\n    ██║       ███████╗   ██║   ███████║██║     █████╔╝\n    ██║       ╚════██║   ██║   ██╔══██║██║     ██╔═██╗\n    ██║       ███████║   ██║   ██║  ██║╚██████╗██║  ██╗\n    ╚═╝       ╚══════╝   ╚═╝   ╚═╝  ╚═╝ ╚═════╝╚═╝  ╚═╝\n \\`;\n\nexport function meta({}: Route.MetaArgs) {\n  return [{ title: \"{{projectName}}\" }, { name: \"description\", content: \"{{projectName}} is a web application\" }];\n}\n\nexport default function Home() {\n  {{#if (eq backend \"convex\")}}\n  const healthCheck = useQuery(api.healthCheck.get);\n  {{else if (eq api \"orpc\")}}\n  const healthCheck = useQuery(orpc.healthCheck.queryOptions());\n  {{else if (eq api \"trpc\")}}\n  const healthCheck = useQuery(trpc.healthCheck.queryOptions());\n  {{/if}}\n\n  return (\n    <div className=\"container mx-auto max-w-3xl px-4 py-2\">\n      <pre className=\"overflow-x-auto font-mono text-sm\">{TITLE_TEXT}</pre>\n      <div className=\"grid gap-6\">\n        <section className=\"rounded-lg border p-4\">\n          <h2 className=\"mb-2 font-medium\">API Status</h2>\n          {{#if (eq backend \"convex\")}}\n          <div className=\"flex items-center gap-2\">\n            <div\n              className={\\`h-2 w-2 rounded-full \\${healthCheck === \"OK\" ? \"bg-green-500\" : healthCheck === undefined ? \"bg-orange-400\" : \"bg-red-500\"}\\`}\n            />\n            <span className=\"text-sm text-muted-foreground\">\n              {healthCheck === undefined\n                ? \"Checking...\"\n                : healthCheck === \"OK\"\n                  ? \"Connected\"\n                  : \"Error\"}\n            </span>\n          </div>\n          {{else}}\n            {{#unless (eq api \"none\")}}\n            <div className=\"flex items-center gap-2\">\n              <div\n                className={\\`h-2 w-2 rounded-full \\${\n                  healthCheck.data ? \"bg-green-500\" : \"bg-red-500\"\n                }\\`}\n              />\n              <span className=\"text-sm text-muted-foreground\">\n                {healthCheck.isLoading\n                  ? \"Checking...\"\n                  : healthCheck.data\n                  ? \"Connected\"\n                  : \"Disconnected\"}\n              </span>\n            </div>\n            {{/unless}}\n          {{/if}}\n        </section>\n      </div>\n    </div>\n  );\n}\n`],\n  [\"frontend/react/react-router/tsconfig.json.hbs\", `{\n  \"include\": [\n    \"**/*\",\n    \"**/.server/**/*\",\n    \"**/.client/**/*\",\n    \".react-router/types/**/*\"\n  ],\n  \"compilerOptions\": {\n    \"lib\": [\"DOM\", \"DOM.Iterable\", \"ES2022\"],\n    \"types\": [\"node\", \"vite/client\"],\n    \"target\": \"ES2022\",\n    \"module\": \"ES2022\",\n    \"moduleResolution\": \"bundler\",\n    \"jsx\": \"react-jsx\",\n    \"rootDirs\": [\".\", \"./.react-router/types\"],\n    \"paths\": {\n      \"@/*\": [\"./src/*\"],\n      \"@{{projectName}}/ui/*\": [\"../../packages/ui/src/*\"]\n    },\n    \"esModuleInterop\": true,\n    \"verbatimModuleSyntax\": true,\n    \"noEmit\": true,\n    \"resolveJsonModule\": true,\n    \"skipLibCheck\": true,\n    \"strict\": true\n  }\n}\n`],\n  [\"frontend/react/react-router/vite.config.ts.hbs\", `import { reactRouter } from \"@react-router/dev/vite\";\nimport tailwindcss from \"@tailwindcss/vite\";\nimport { defineConfig } from \"vite\";\nimport tsconfigPaths from \"vite-tsconfig-paths\";\n\nexport default defineConfig({\n  plugins: [\n    tailwindcss(),\n    reactRouter(),\n    tsconfigPaths(),\n  ],\n});`],\n  [\"frontend/react/tanstack-router/index.html.hbs\", `<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <title>{{projectName}}</title>\n  </head>\n\n  <body>\n    <div id=\"app\"></div>\n    <script type=\"module\" src=\"/src/main.tsx\"></script>\n  </body>\n</html>\n`],\n  [\"frontend/react/tanstack-router/package.json.hbs\", `{\n\t\"name\": \"web\",\n\t\"version\": \"0.0.0\",\n\t\"private\": true,\n\t\"type\": \"module\",\n\t\"scripts\": {\n\t\t\"dev\": \"vite dev\",\n\t\t\"build\": \"vite build\",\n\t\t\"serve\": \"vite preview\",\n\t\t\"start\": \"vite\",\n\t\t\"check-types\": \"vite build && tsc --noEmit\"\n\t},\n\t\"dependencies\": {\n        \"@hookform/resolvers\": \"^5.2.2\",\n        \"@{{projectName}}/ui\": \"{{#if (eq packageManager \"npm\")}}*{{else}}workspace:*{{/if}}\",\n\t\t\"@tailwindcss/vite\": \"^4.2.2\",\n\t\t\"@tanstack/react-router\": \"^1.168.22\",\n\t\t\"lucide-react\": \"^1.8.0\",\n        \"next-themes\": \"^0.4.6\",\n\t\t\"react\": \"^19.2.5\",\n\t\t\"react-dom\": \"^19.2.5\",\n        \"sonner\": \"^2.0.7\"\n\t},\n\t\"devDependencies\": {\n\t\t\"@tanstack/react-router-devtools\": \"^1.166.13\",\n\t\t\"@tanstack/router-plugin\": \"^1.167.22\",\n\t\t\"@types/node\": \"^22.13.14\",\n\t\t\"@types/react\": \"^19.2.14\",\n\t\t\"@types/react-dom\": \"^19.2.3\",\n\t\t\"@vitejs/plugin-react\": \"^6.0.1\",\n\t\t\"postcss\": \"^8.5.10\",\n\t\t\"tailwindcss\": \"^4.2.2\",\n\t\t\"vite\": \"^8.0.8\"\n\t}\n}\n`],\n  [\"frontend/react/tanstack-router/src/components/mode-toggle.tsx.hbs\", `import { Moon, Sun } from \"lucide-react\";\n\nimport { Button } from \"@{{projectName}}/ui/components/button\";\nimport {\n  DropdownMenu,\n  DropdownMenuContent,\n  DropdownMenuItem,\n  DropdownMenuTrigger,\n} from \"@{{projectName}}/ui/components/dropdown-menu\";\nimport { useTheme } from \"@/components/theme-provider\";\n\nexport function ModeToggle() {\n  const { setTheme } = useTheme();\n\n  return (\n    <DropdownMenu>\n      <DropdownMenuTrigger render={<Button variant=\"outline\" size=\"icon\" />}>\n        <Sun className=\"h-[1.2rem] w-[1.2rem] scale-100 rotate-0 transition-all dark:scale-0 dark:-rotate-90\" />\n        <Moon className=\"absolute h-[1.2rem] w-[1.2rem] scale-0 rotate-90 transition-all dark:scale-100 dark:rotate-0\" />\n        <span className=\"sr-only\">Toggle theme</span>\n      </DropdownMenuTrigger>\n      <DropdownMenuContent align=\"end\">\n        <DropdownMenuItem onClick={() => setTheme(\"light\")}>Light</DropdownMenuItem>\n        <DropdownMenuItem onClick={() => setTheme(\"dark\")}>Dark</DropdownMenuItem>\n        <DropdownMenuItem onClick={() => setTheme(\"system\")}>System</DropdownMenuItem>\n      </DropdownMenuContent>\n    </DropdownMenu>\n  );\n}\n`],\n  [\"frontend/react/tanstack-router/src/components/theme-provider.tsx.hbs\", `import * as React from \"react\";\nimport { ThemeProvider as NextThemesProvider } from \"next-themes\";\n\nexport function ThemeProvider({\n  children,\n  ...props\n}: React.ComponentProps<typeof NextThemesProvider>) {\n  return <NextThemesProvider {...props}>{children}</NextThemesProvider>;\n}\n\nexport { useTheme } from \"next-themes\";\n`],\n  [\"frontend/react/tanstack-router/src/main.tsx.hbs\", `import { RouterProvider, createRouter } from \"@tanstack/react-router\";\nimport ReactDOM from \"react-dom/client\";\n{{#if (and (eq auth \"clerk\") (ne backend \"convex\") (ne api \"none\"))}}\nimport { useEffect } from \"react\";\nimport { setClerkAuthTokenGetter } from \"@/utils/clerk-auth\";\n{{/if}}\nimport Loader from \"./components/loader\";\nimport { routeTree } from \"./routeTree.gen\";\n\n{{#if (eq api \"orpc\")}}\n  import { QueryClientProvider } from \"@tanstack/react-query\";\n  import { orpc, queryClient } from \"./utils/orpc\";\n{{/if}}\n{{#if (eq api \"trpc\")}}\n  import { QueryClientProvider } from \"@tanstack/react-query\";\n  import { queryClient, trpc } from \"./utils/trpc\";\n{{/if}}\n{{#if (or (eq backend \"convex\") (eq auth \"clerk\"))}}\n  import { env } from \"@{{projectName}}/env/web\";\n{{/if}}\n{{#if (eq auth \"clerk\")}}\n  import { ClerkProvider{{#if (or (eq backend \"convex\") (ne api \"none\"))}}, useAuth{{/if}} } from \"@clerk/react\";\n{{/if}}\n{{#if (eq backend \"convex\")}}\n  import { ConvexReactClient } from \"convex/react\";\n  {{#if (eq auth \"clerk\")}}\n  import { ConvexProviderWithClerk } from \"convex/react-clerk\";\n  {{else if (eq auth \"better-auth\")}}\n  import { ConvexBetterAuthProvider } from \"@convex-dev/better-auth/react\";\n  import { authClient } from \"@/lib/auth-client\";\n  {{else}}\n  import { ConvexProvider } from \"convex/react\";\n  {{/if}}\n  {{#if (eq auth \"better-auth\")}}\n  const convex = new ConvexReactClient(env.VITE_CONVEX_URL, {\n    expectAuth: true,\n  });\n  {{else}}\n  const convex = new ConvexReactClient(env.VITE_CONVEX_URL);\n  {{/if}}\n{{/if}}\n\n{{#if (and (eq auth \"clerk\") (ne backend \"convex\") (ne api \"none\"))}}\nfunction ClerkApiAuthBridge() {\n  const { getToken } = useAuth();\n\n  useEffect(() => {\n    setClerkAuthTokenGetter(getToken);\n\n    return () => {\n      setClerkAuthTokenGetter(null);\n    };\n  }, [getToken]);\n\n  return null;\n}\n{{/if}}\n\nconst router = createRouter({\n  routeTree,\n  defaultPreload: \"intent\",\n  scrollRestoration: true,\n  defaultPendingComponent: () => <Loader />,\n  {{#if (eq api \"orpc\")}}\n  context: { orpc, queryClient },\n  Wrap: function WrapComponent({ children }: { children: React.ReactNode }) {\n    return (\n      {{#if (eq auth \"clerk\")}}\n      <ClerkProvider publishableKey={env.VITE_CLERK_PUBLISHABLE_KEY}>\n        <ClerkApiAuthBridge />\n        <QueryClientProvider client={queryClient}>\n          {children}\n        </QueryClientProvider>\n      </ClerkProvider>\n      {{else}}\n      <QueryClientProvider client={queryClient}>\n        {children}\n      </QueryClientProvider>\n      {{/if}}\n    );\n  },\n  {{else if (eq api \"trpc\")}}\n  context: { trpc, queryClient },\n  Wrap: function WrapComponent({ children }: { children: React.ReactNode }) {\n    return (\n      {{#if (eq auth \"clerk\")}}\n      <ClerkProvider publishableKey={env.VITE_CLERK_PUBLISHABLE_KEY}>\n        <ClerkApiAuthBridge />\n        <QueryClientProvider client={queryClient}>\n          {children}\n        </QueryClientProvider>\n      </ClerkProvider>\n      {{else}}\n      <QueryClientProvider client={queryClient}>\n        {children}\n      </QueryClientProvider>\n      {{/if}}\n    );\n  },\n  {{else if (eq backend \"convex\")}}\n  context: {},\n  Wrap: function WrapComponent({ children }: { children: React.ReactNode }) {\n    {{#if (eq auth \"clerk\")}}\n    return (\n      <ClerkProvider\n        publishableKey={env.VITE_CLERK_PUBLISHABLE_KEY}\n      >\n        <ConvexProviderWithClerk client={convex} useAuth={useAuth}>\n          {children}\n        </ConvexProviderWithClerk>\n      </ClerkProvider>\n    );\n    {{else if (eq auth \"better-auth\")}}\n    return <ConvexBetterAuthProvider client={convex} authClient={authClient}>{children}</ConvexBetterAuthProvider>;\n    {{else}}\n    return <ConvexProvider client={convex}>{children}</ConvexProvider>;\n    {{/if}}\n  },\n  {{else if (eq auth \"clerk\")}}\n  context: {},\n  Wrap: function WrapComponent({ children }: { children: React.ReactNode }) {\n    return <ClerkProvider publishableKey={env.VITE_CLERK_PUBLISHABLE_KEY}>{children}</ClerkProvider>;\n  },\n  {{else}}\n  context: {},\n  {{/if}}\n});\n\ndeclare module \"@tanstack/react-router\" {\n  interface Register {\n    router: typeof router;\n  }\n}\n\nconst rootElement = document.getElementById(\"app\");\n\nif (!rootElement) {\n  throw new Error(\"Root element not found\");\n}\n\nif (!rootElement.innerHTML) {\n  const root = ReactDOM.createRoot(rootElement);\n  root.render(<RouterProvider router={router} />);\n}\n`],\n  [\"frontend/react/tanstack-router/src/routes/__root.tsx.hbs\", `import Header from \"@/components/header\";\nimport { ThemeProvider } from \"@/components/theme-provider\";\nimport { Toaster } from \"@{{projectName}}/ui/components/sonner\";\n{{#if (eq api \"orpc\")}}\nimport { link, orpc } from \"@/utils/orpc\";\nimport type { QueryClient } from \"@tanstack/react-query\";\nimport { ReactQueryDevtools } from \"@tanstack/react-query-devtools\";\nimport { useState } from \"react\";\nimport { createTanstackQueryUtils } from \"@orpc/tanstack-query\";\nimport type { AppRouterClient } from \"@{{projectName}}/api/routers/index\";\nimport { createORPCClient } from \"@orpc/client\";\n{{/if}}\n{{#if (eq api \"trpc\")}}\nimport type { trpc } from \"@/utils/trpc\";\nimport type { QueryClient } from \"@tanstack/react-query\";\nimport { ReactQueryDevtools } from \"@tanstack/react-query-devtools\";\n{{/if}}\nimport {\n  HeadContent,\n  Outlet,\n  createRootRouteWithContext,\n} from \"@tanstack/react-router\";\nimport { TanStackRouterDevtools } from \"@tanstack/react-router-devtools\";\nimport \"../index.css\";\n\n{{#if (eq api \"orpc\")}}\nexport interface RouterAppContext {\n  orpc: typeof orpc;\n  queryClient: QueryClient;\n}\n{{else if (eq api \"trpc\")}}\nexport interface RouterAppContext {\n  trpc: typeof trpc;\n  queryClient: QueryClient;\n}\n{{else}}\nexport interface RouterAppContext {}\n{{/if}}\n\nexport const Route = createRootRouteWithContext<RouterAppContext>()({\n  component: RootComponent,\n  head: () => ({\n    meta: [\n      {\n        title: \"{{projectName}}\",\n      },\n      {\n        name: \"description\",\n        content: \"{{projectName}} is a web application\",\n      },\n    ],\n    links: [\n      {\n        rel: \"icon\",\n        href: \"/favicon.ico\",\n      },\n    ],\n  }),\n});\n\nfunction RootComponent() {\n  {{#if (eq api \"orpc\")}}\n  const [client] = useState<AppRouterClient>(() => createORPCClient(link));\n  const [orpcUtils] = useState(() => createTanstackQueryUtils(client));\n  {{/if}}\n\n  return (\n    <>\n      <HeadContent />\n      {{#if (eq api \"orpc\")}}\n        <ThemeProvider\n          attribute=\"class\"\n          defaultTheme=\"dark\"\n          disableTransitionOnChange\n          storageKey=\"vite-ui-theme\"\n        >\n          <div className=\"grid grid-rows-[auto_1fr] h-svh\">\n            <Header />\n            <Outlet />\n          </div>\n          <Toaster richColors />\n        </ThemeProvider>\n      {{else}}\n      <ThemeProvider\n        attribute=\"class\"\n        defaultTheme=\"dark\"\n        disableTransitionOnChange\n        storageKey=\"vite-ui-theme\"\n      >\n        <div className=\"grid grid-rows-[auto_1fr] h-svh\">\n          <Header />\n          <Outlet />\n        </div>\n        <Toaster richColors />\n      </ThemeProvider>\n      {{/if}}\n      <TanStackRouterDevtools position=\"bottom-left\" />\n      {{#if (or (eq api \"orpc\") (eq api \"trpc\"))}}\n      <ReactQueryDevtools position=\"bottom\" buttonPosition=\"bottom-right\" />\n      {{/if}}\n    </>\n  );\n}\n`],\n  [\"frontend/react/tanstack-router/src/routes/index.tsx.hbs\", `import { createFileRoute } from \"@tanstack/react-router\";\n{{#if (eq api \"orpc\")}}\nimport { orpc } from \"@/utils/orpc\";\nimport { useQuery } from \"@tanstack/react-query\";\n{{/if}}\n{{#if (eq api \"trpc\")}}\nimport { trpc } from \"@/utils/trpc\";\nimport { useQuery } from \"@tanstack/react-query\";\n{{/if}}\n{{#if (eq backend \"convex\")}}\nimport { useQuery } from \"convex/react\";\nimport { api } from \"@{{ projectName }}/backend/convex/_generated/api\";\n{{/if}}\n\nexport const Route = createFileRoute(\"/\")({\n  component: HomeComponent,\n});\n\nconst TITLE_TEXT = \\`\n ██████╗ ███████╗████████╗████████╗███████╗██████╗\n ██╔══██╗██╔════╝╚══██╔══╝╚══██╔══╝██╔════╝██╔══██╗\n ██████╔╝█████╗     ██║      ██║   █████╗  ██████╔╝\n ██╔══██╗██╔══╝     ██║      ██║   ██╔══╝  ██╔══██╗\n ██████╔╝███████╗   ██║      ██║   ███████╗██║  ██║\n ╚═════╝ ╚══════╝   ╚═╝      ╚═╝   ╚══════╝╚═╝  ╚═╝\n\n ████████╗    ███████╗████████╗ █████╗  ██████╗██╗  ██╗\n ╚══██╔══╝    ██╔════╝╚══██╔══╝██╔══██╗██╔════╝██║ ██╔╝\n    ██║       ███████╗   ██║   ███████║██║     █████╔╝\n    ██║       ╚════██║   ██║   ██╔══██║██║     ██╔═██╗\n    ██║       ███████║   ██║   ██║  ██║╚██████╗██║  ██╗\n    ╚═╝       ╚══════╝   ╚═╝   ╚═╝  ╚═╝ ╚═════╝╚═╝  ╚═╝\n \\`;\n\nfunction HomeComponent() {\n  {{#if (eq api \"orpc\")}}\n  const healthCheck = useQuery(orpc.healthCheck.queryOptions());\n  {{/if}}\n  {{#if (eq api \"trpc\")}}\n  const healthCheck = useQuery(trpc.healthCheck.queryOptions());\n  {{/if}}\n  {{#if (eq backend \"convex\")}}\n  const healthCheck = useQuery(api.healthCheck.get);\n  {{/if}}\n\n  return (\n    <div className=\"container mx-auto max-w-3xl px-4 py-2\">\n      <pre className=\"overflow-x-auto font-mono text-sm\">{TITLE_TEXT}</pre>\n      <div className=\"grid gap-6\">\n        <section className=\"rounded-lg border p-4\">\n          <h2 className=\"mb-2 font-medium\">API Status</h2>\n          {{#if (eq backend \"convex\")}}\n          <div className=\"flex items-center gap-2\">\n            <div\n              className={\\`h-2 w-2 rounded-full \\${healthCheck === \"OK\" ? \"bg-green-500\" : healthCheck === undefined ? \"bg-orange-400\" : \"bg-red-500\"}\\`}\n            />\n            <span className=\"text-sm text-muted-foreground\">\n              {healthCheck === undefined\n                ? \"Checking...\"\n                : healthCheck === \"OK\"\n                  ? \"Connected\"\n                  : \"Error\"}\n            </span>\n          </div>\n          {{else}}\n            {{#unless (eq api \"none\")}}\n            <div className=\"flex items-center gap-2\">\n              <div\n                className={\\`h-2 w-2 rounded-full \\${healthCheck.data ? \"bg-green-500\" : \"bg-red-500\"}\\`}\n              />\n              <span className=\"text-sm text-muted-foreground\">\n                {healthCheck.isLoading\n                  ? \"Checking...\"\n                  : healthCheck.data\n                    ? \"Connected\"\n                    : \"Disconnected\"}\n              </span>\n            </div>\n            {{/unless}}\n          {{/if}}\n        </section>\n      </div>\n    </div>\n  );\n}\n`],\n  [\"frontend/react/tanstack-router/tsconfig.json.hbs\", `{\n  \"compilerOptions\": {\n    \"strict\": true,\n    \"esModuleInterop\": true,\n    \"jsx\": \"react-jsx\",\n    \"target\": \"ESNext\",\n    \"module\": \"ESNext\",\n    \"moduleResolution\": \"Bundler\",\n    \"skipLibCheck\": true,\n    \"types\": [\"vite/client\"],\n    \"rootDirs\": [\".\"],\n    \"paths\": {\n      \"@/*\": [\"./src/*\"],\n      \"@{{projectName}}/ui/*\": [\"../../packages/ui/src/*\"]\n    }\n  }\n}\n`],\n  [\"frontend/react/tanstack-router/vite.config.ts.hbs\", `import tailwindcss from \"@tailwindcss/vite\";\nimport { tanstackRouter } from \"@tanstack/router-plugin/vite\";\nimport react from \"@vitejs/plugin-react\";\nimport { defineConfig } from \"vite\";\n\nexport default defineConfig({\n  server: {\n    port: 3001,\n  },\n  resolve: {\n    tsconfigPaths: true,\n  },\n  plugins: [\n    tailwindcss(),\n    tanstackRouter({\n      target: \"react\",\n      autoCodeSplitting: true,\n    }),\n    react(),\n  ],\n});\n`],\n  [\"frontend/react/tanstack-start/package.json.hbs\", `{\n  \"name\": \"web\",\n  \"private\": true,\n  \"type\": \"module\",\n  \"scripts\": {\n    \"build\": \"vite build\",\n    \"serve\": \"vite preview\",\n    \"dev\": \"vite dev\"\n  },\n  \"dependencies\": {\n    \"@{{projectName}}/ui\": \"{{#if (eq packageManager \"npm\")}}*{{else}}workspace:*{{/if}}\",\n    \"@tailwindcss/vite\": \"^4.2.2\",\n    \"@tanstack/react-query\": \"^5.99.0\",\n    \"@tanstack/react-router\": \"^1.168.22\",\n    \"@tanstack/react-start\": \"^1.167.41\",\n    \"lucide-react\": \"^1.8.0\",\n    \"next-themes\": \"^0.4.6\",\n    \"react\": \"^19.2.5\",\n    \"react-dom\": \"^19.2.5\",\n    \"sonner\": \"^2.0.7\",\n    \"tailwindcss\": \"^4.2.2\"\n  },\n  \"devDependencies\": {\n    \"@tanstack/react-router-devtools\": \"^1.166.13\",\n    \"@testing-library/dom\": \"^10.4.1\",\n    \"@testing-library/react\": \"^16.3.2\",\n    \"@types/react\": \"^19.2.14\",\n    \"@types/react-dom\": \"^19.2.3\",\n    \"@vitejs/plugin-react\": \"^6.0.1\",\n    \"jsdom\": \"^29.0.2\",\n    \"vite\": \"^8.0.8\",\n    \"web-vitals\": \"^5.2.0\"\n  }\n}\n`],\n  [\"frontend/react/tanstack-start/public/robots.txt\", `# https://www.robotstxt.org/robotstxt.html\nUser-agent: *\nDisallow:\n`],\n  [\"frontend/react/tanstack-start/src/router.tsx.hbs\", `{{#if (eq backend \"convex\")}}\nimport { createRouter as createTanStackRouter } from \"@tanstack/react-router\";\nimport { QueryClient } from \"@tanstack/react-query\";\nimport { setupRouterSsrQueryIntegration } from \"@tanstack/react-router-ssr-query\";\nimport { ConvexQueryClient } from \"@convex-dev/react-query\";\nimport { routeTree } from \"./routeTree.gen\";\nimport Loader from \"./components/loader\";\nimport \"./index.css\";\nimport { env } from \"@{{projectName}}/env/web\";\n{{else}}\nimport { createRouter as createTanStackRouter } from \"@tanstack/react-router\";\nimport Loader from \"./components/loader\";\nimport \"./index.css\";\nimport { routeTree } from \"./routeTree.gen\";\n{{#if (eq api \"trpc\")}}\nimport { QueryCache, QueryClient } from \"@tanstack/react-query\";\nimport { setupRouterSsrQueryIntegration } from \"@tanstack/react-router-ssr-query\";\nimport { createTRPCClient, httpBatchLink } from \"@trpc/client\";\nimport { createTRPCOptionsProxy } from \"@trpc/tanstack-react-query\";\nimport { toast } from \"sonner\";\nimport type { AppRouter } from \"@{{projectName}}/api/routers/index\";\nimport { TRPCProvider } from \"./utils/trpc\";\n{{#unless (eq backend \"self\")}}\nimport { env } from \"@{{projectName}}/env/web\";\n{{/unless}}\n{{#if (eq auth \"clerk\")}}\nimport { getClerkAuthToken } from \"@/utils/clerk-auth\";\n{{/if}}\n{{else if (eq api \"orpc\")}}\nimport { setupRouterSsrQueryIntegration } from \"@tanstack/react-router-ssr-query\";\nimport { orpc, queryClient } from \"./utils/orpc\";\n{{/if}}\n{{/if}}\n\n{{#if (eq backend \"convex\")}}\nexport function getRouter() {\n\tconst convexUrl = env.VITE_CONVEX_URL;\n\tif (!convexUrl) {\n\t\tthrow new Error(\"VITE_CONVEX_URL is not set\");\n\t}\n\n\tconst convexQueryClient = new ConvexQueryClient(convexUrl);\n\n\tconst queryClient: QueryClient = new QueryClient({\n\t\tdefaultOptions: {\n\t\t\tqueries: {\n\t\t\t\tqueryKeyHashFn: convexQueryClient.hashFn(),\n\t\t\t\tqueryFn: convexQueryClient.queryFn(),\n\t\t\t},\n\t\t},\n\t});\n\tconvexQueryClient.connect(queryClient);\n\n\tconst router = createTanStackRouter({\n\t\trouteTree,\n\t\tdefaultPreload: \"intent\",\n\t\tdefaultPendingComponent: () => <Loader />,\n\t\tdefaultNotFoundComponent: () => <div>Not Found</div>,\n\t\tcontext: { queryClient, convexQueryClient },\n\t});\n\n\tsetupRouterSsrQueryIntegration({\n\t\trouter,\n\t\tqueryClient,\n\t});\n\n\treturn router;\n}\n{{else}}\n{{#if (eq api \"trpc\")}}\nexport const queryClient = new QueryClient({\n\tqueryCache: new QueryCache({\n\t\tonError: (error, query) => {\n\t\t\ttoast.error(error.message, {\n\t\t\t\taction: {\n\t\t\t\t\tlabel: \"retry\",\n\t\t\t\t\tonClick: query.invalidate,\n\t\t\t\t},\n\t\t\t});\n\t\t},\n\t}),\n\tdefaultOptions: { queries: { staleTime: 60 * 1000 } },\n});\n\nconst trpcClient = createTRPCClient<AppRouter>({\n\tlinks: [\n\t\thttpBatchLink({\n\t\t\turl: {{#if (eq backend \"self\")}}\"/api/trpc\"{{else}}\\`\\${env.VITE_SERVER_URL}/trpc\\`{{/if}},\n{{#if (eq auth \"clerk\")}}\n\t\t\theaders: async () => {\n\t\t\t\tconst token = await getClerkAuthToken();\n\t\t\t\treturn token ? { Authorization: \\`Bearer \\${token}\\` } : {};\n\t\t\t},\n{{/if}}\n{{#if (eq auth \"better-auth\")}}\n\t\t\tfetch(url, options) {\n\t\t\t\treturn fetch(url, {\n\t\t\t\t\t...options,\n\t\t\t\t\tcredentials: \"include\",\n\t\t\t\t});\n\t\t\t},\n{{/if}}\n\t\t}),\n\t],\n});\n\nconst trpc = createTRPCOptionsProxy({\n\tclient: trpcClient,\n\tqueryClient: queryClient,\n});\n{{else if (eq api \"orpc\")}}\n{{/if}}\n\nexport const getRouter = () => {\n\tconst router = createTanStackRouter({\n\t\trouteTree,\n\t\tscrollRestoration: true,\n\t\tdefaultPreloadStaleTime: 0,\n{{#if (eq api \"trpc\")}}\n\t\tcontext: { trpc, queryClient },\n{{else if (eq api \"orpc\")}}\n\t\tcontext: { orpc, queryClient },\n{{else}}\n\t\tcontext: {},\n{{/if}}\n\t\tdefaultPendingComponent: () => <Loader />,\n\t\tdefaultNotFoundComponent: () => <div>Not Found</div>,\n{{#if (eq api \"trpc\")}}\n\t\tWrap: ({ children }) => (\n\t\t\t<TRPCProvider trpcClient={trpcClient} queryClient={queryClient}>\n\t\t\t\t{children}\n\t\t\t</TRPCProvider>\n\t\t),\n{{/if}}\n\t});\n{{#if (or (eq api \"trpc\") (eq api \"orpc\"))}}\n\n\tsetupRouterSsrQueryIntegration({\n\t\trouter,\n\t\tqueryClient,\n\t});\n{{/if}}\n\n\treturn router;\n};\n{{/if}}\n\ndeclare module \"@tanstack/react-router\" {\n\tinterface Register {\n\t\trouter: ReturnType<typeof getRouter>;\n\t}\n}\n`],\n  [\"frontend/react/tanstack-start/src/routes/__root.tsx.hbs\", `import { Toaster } from \"@{{projectName}}/ui/components/sonner\";\n{{#unless (eq backend \"convex\")}} {{#unless (eq api \"none\")}}\nimport { ReactQueryDevtools } from \"@tanstack/react-query-devtools\";\n{{/unless}} {{/unless}}\nimport {\n  HeadContent,\n  Outlet,\n  Scripts,\n  createRootRouteWithContext,\n{{#if (and (eq backend \"convex\") (or (eq auth \"clerk\") (eq auth \"better-auth\")))}}\n  useRouteContext,\n{{/if}}\n} from \"@tanstack/react-router\";\nimport { TanStackRouterDevtools } from \"@tanstack/react-router-devtools\";\nimport Header from \"../components/header\";\nimport appCss from \"../index.css?url\";\n{{#if (eq backend \"convex\")}}\nimport type { QueryClient } from \"@tanstack/react-query\";\nimport type { ConvexQueryClient } from \"@convex-dev/react-query\";\n{{else}}\n{{#if (or (eq api \"trpc\") (eq api \"orpc\"))}}\nimport type { QueryClient } from \"@tanstack/react-query\";\n{{/if}}\n{{/if}}\n\n{{#if (eq auth \"clerk\")}}\nimport { ClerkProvider{{#if (or (eq backend \"convex\") (ne api \"none\"))}}, useAuth{{/if}} } from \"@clerk/tanstack-react-start\";\n{{/if}}\n{{#if (and (eq auth \"clerk\") (ne backend \"convex\") (ne api \"none\"))}}\nimport { useEffect } from \"react\";\nimport { setClerkAuthTokenGetter } from \"@/utils/clerk-auth\";\n{{/if}}\n{{#if (and (eq backend \"convex\") (eq auth \"clerk\"))}}\nimport { auth } from \"@clerk/tanstack-react-start/server\";\nimport { createServerFn } from \"@tanstack/react-start\";\nimport { ConvexProviderWithClerk } from \"convex/react-clerk\";\n\nconst fetchClerkAuth = createServerFn({ method: \"GET\" }).handler(async () => {\n  const clerkAuth = await auth();\n  const token = await clerkAuth.getToken({ template: \"convex\" });\n  return { userId: clerkAuth.userId, token };\n});\n{{else if (and (eq backend \"convex\") (eq auth \"better-auth\"))}}\nimport { createServerFn } from \"@tanstack/react-start\";\nimport { ConvexBetterAuthProvider } from \"@convex-dev/better-auth/react\";\nimport { authClient } from \"@/lib/auth-client\";\nimport { getToken } from \"@/lib/auth-server\";\n\nconst getAuth = createServerFn({ method: \"GET\" }).handler(async () => {\n  return await getToken();\n});\n{{else if (eq backend \"convex\")}}\nimport { ConvexProvider } from \"convex/react\";\n{{/if}}\n\n{{#if (and (eq auth \"clerk\") (ne backend \"convex\") (ne api \"none\"))}}\nfunction ClerkApiAuthBridge() {\n  const { getToken } = useAuth();\n\n  useEffect(() => {\n    setClerkAuthTokenGetter(getToken);\n\n    return () => {\n      setClerkAuthTokenGetter(null);\n    };\n  }, [getToken]);\n\n  return null;\n}\n{{/if}}\n\n{{#if (eq backend \"convex\")}}\nexport interface RouterAppContext {\n  queryClient: QueryClient;\n  convexQueryClient: ConvexQueryClient;\n}\n{{else}}\n  {{#if (eq api \"trpc\")}}\nimport type { TRPCOptionsProxy } from \"@trpc/tanstack-react-query\";\nimport type { AppRouter } from \"@{{projectName}}/api/routers/index\";\nexport interface RouterAppContext {\n  trpc: TRPCOptionsProxy<AppRouter>;\n  queryClient: QueryClient;\n}\n  {{else if (eq api \"orpc\")}}\nimport type { orpc } from \"@/utils/orpc\";\nexport interface RouterAppContext {\n  orpc: typeof orpc;\n  queryClient: QueryClient;\n}\n  {{else}}\nexport interface RouterAppContext {\n}\n  {{/if}}\n{{/if}}\n\nexport const Route = createRootRouteWithContext<RouterAppContext>()({\n  head: () => ({\n    meta: [\n      {\n        charSet: \"utf-8\",\n      },\n      {\n        name: \"viewport\",\n        content: \"width=device-width, initial-scale=1\",\n      },\n      {\n        title: \"My App\",\n      },\n    ],\n    links: [\n      {\n        rel: \"stylesheet\",\n        href: appCss,\n      },\n    ],\n  }),\n\n  component: RootDocument,\n  {{#if (and (eq backend \"convex\") (eq auth \"clerk\"))}}\n  beforeLoad: async (ctx) => {\n    const { userId, token } = await fetchClerkAuth();\n    if (token) {\n      ctx.context.convexQueryClient.serverHttpClient?.setAuth(token);\n    }\n    return { userId, token };\n  },\n  {{else if (and (eq backend \"convex\") (eq auth \"better-auth\"))}}\n  beforeLoad: async (ctx) => {\n    const token = await getAuth();\n    if (token) {\n      ctx.context.convexQueryClient.serverHttpClient?.setAuth(token);\n    }\n    return {\n      isAuthenticated: !!token,\n      token,\n    };\n  },\n  {{/if}}\n});\n\nfunction RootDocument() {\n  {{#if (and (eq backend \"convex\") (eq auth \"clerk\"))}}\n  const context = useRouteContext({ from: Route.id });\n  return (\n    <ClerkProvider>\n      <ConvexProviderWithClerk client={context.convexQueryClient.convexClient} useAuth={useAuth}>\n        <html lang=\"en\" className=\"dark\">\n          <head>\n            <HeadContent />\n          </head>\n          <body>\n            <div className=\"grid h-svh grid-rows-[auto_1fr]\">\n              <Header />\n              <Outlet />\n            </div>\n            <Toaster richColors />\n            <TanStackRouterDevtools position=\"bottom-left\" />\n            <Scripts />\n          </body>\n        </html>\n      </ConvexProviderWithClerk>\n    </ClerkProvider>\n  );\n  {{else if (and (eq backend \"convex\") (eq auth \"better-auth\"))}}\n  const context = useRouteContext({ from: Route.id });\n  return (\n    <ConvexBetterAuthProvider\n      client={context.convexQueryClient.convexClient}\n      authClient={authClient}\n      initialToken={context.token}\n    >\n      <html lang=\"en\" className=\"dark\">\n        <head>\n          <HeadContent />\n        </head>\n        <body>\n          <div className=\"grid h-svh grid-rows-[auto_1fr]\">\n            <Header />\n            <Outlet />\n          </div>\n          <Toaster richColors />\n          <TanStackRouterDevtools position=\"bottom-left\" />\n          <Scripts />\n        </body>\n      </html>\n    </ConvexBetterAuthProvider>\n  );\n  {{else if (eq auth \"clerk\")}}\n  return (\n    <ClerkProvider>\n      {{#unless (eq api \"none\")}}\n      <ClerkApiAuthBridge />\n      {{/unless}}\n      <html lang=\"en\" className=\"dark\">\n        <head>\n          <HeadContent />\n        </head>\n        <body>\n          <div className=\"grid h-svh grid-rows-[auto_1fr]\">\n            <Header />\n            <Outlet />\n          </div>\n          <Toaster richColors />\n          <TanStackRouterDevtools position=\"bottom-left\" />\n          {{#unless (eq api \"none\")}}\n          <ReactQueryDevtools position=\"bottom\" buttonPosition=\"bottom-right\" />\n          {{/unless}}\n          <Scripts />\n        </body>\n      </html>\n    </ClerkProvider>\n  );\n  {{else if (eq backend \"convex\")}}\n  const { convexQueryClient } = Route.useRouteContext();\n  return (\n    <ConvexProvider client={convexQueryClient.convexClient}>\n      <html lang=\"en\" className=\"dark\">\n        <head>\n          <HeadContent />\n        </head>\n        <body>\n          <div className=\"grid h-svh grid-rows-[auto_1fr]\">\n            <Header />\n            <Outlet />\n          </div>\n          <Toaster richColors />\n          <TanStackRouterDevtools position=\"bottom-left\" />\n          <Scripts />\n        </body>\n      </html>\n    </ConvexProvider>\n  );\n  {{else}}\n  return (\n    <html lang=\"en\" className=\"dark\">\n      <head>\n        <HeadContent />\n      </head>\n      <body>\n        <div className=\"grid h-svh grid-rows-[auto_1fr]\">\n          <Header />\n          <Outlet />\n        </div>\n        <Toaster richColors />\n        <TanStackRouterDevtools position=\"bottom-left\" />\n        {{#unless (eq api \"none\")}}\n        <ReactQueryDevtools position=\"bottom\" buttonPosition=\"bottom-right\" />\n        {{/unless}}\n        <Scripts />\n      </body>\n    </html>\n  );\n  {{/if}}\n}\n`],\n  [\"frontend/react/tanstack-start/src/routes/index.tsx.hbs\", `import { createFileRoute } from \"@tanstack/react-router\";\n{{#if (eq backend \"convex\")}}\nimport { convexQuery } from \"@convex-dev/react-query\";\nimport { useQuery } from \"@tanstack/react-query\";\nimport { api } from \"@{{projectName}}/backend/convex/_generated/api\";\n{{else if (or (eq api \"trpc\") (eq api \"orpc\"))}}\nimport { useQuery } from \"@tanstack/react-query\";\n  {{#if (eq api \"trpc\")}}\nimport { useTRPC } from \"@/utils/trpc\";\n  {{/if}}\n  {{#if (eq api \"orpc\")}}\nimport { orpc } from \"@/utils/orpc\";\n  {{/if}}\n{{/if}}\n\nexport const Route = createFileRoute(\"/\")({\n  component: HomeComponent,\n});\n\nconst TITLE_TEXT = \\`\n ██████╗ ███████╗████████╗████████╗███████╗██████╗\n ██╔══██╗██╔════╝╚══██╔══╝╚══██╔══╝██╔════╝██╔══██╗\n ██████╔╝█████╗     ██║      ██║   █████╗  ██████╔╝\n ██╔══██╗██╔══╝     ██║      ██║   ██╔══╝  ██╔══██╗\n ██████╔╝███████╗   ██║      ██║   ███████╗██║  ██║\n ╚═════╝ ╚══════╝   ╚═╝      ╚═╝   ╚══════╝╚═╝  ╚═╝\n\n ████████╗    ███████╗████████╗ █████╗  ██████╗██╗  ██╗\n ╚══██╔══╝    ██╔════╝╚══██╔══╝██╔══██╗██╔════╝██║ ██╔╝\n    ██║       ███████╗   ██║   ███████║██║     █████╔╝\n    ██║       ╚════██║   ██║   ██╔══██║██║     ██╔═██╗\n    ██║       ███████║   ██║   ██║  ██║╚██████╗██║  ██╗\n    ╚═╝       ╚══════╝   ╚═╝   ╚═╝  ╚═╝ ╚═════╝╚═╝  ╚═╝\n \\`;\n\nfunction HomeComponent() {\n  {{#if (eq backend \"convex\")}}\n  const healthCheck = useQuery(convexQuery(api.healthCheck.get, {}));\n  {{else if (eq api \"trpc\")}}\n  const trpc = useTRPC();\n  const healthCheck = useQuery(trpc.healthCheck.queryOptions());\n  {{else if (eq api \"orpc\")}}\n  const healthCheck = useQuery(orpc.healthCheck.queryOptions());\n  {{/if}}\n\n  return (\n    <div className=\"container mx-auto max-w-3xl px-4 py-2\">\n      <pre className=\"overflow-x-auto font-mono text-sm\">{TITLE_TEXT}</pre>\n      <div className=\"grid gap-6\">\n        <section className=\"rounded-lg border p-4\">\n          <h2 className=\"mb-2 font-medium\">API Status</h2>\n          {{#if (eq backend \"convex\")}}\n          <div className=\"flex items-center gap-2\">\n            <div\n              className={\\`h-2 w-2 rounded-full \\${healthCheck.data === \"OK\" ? \"bg-green-500\" : healthCheck.isLoading ? \"bg-orange-400\" : \"bg-red-500\"}\\`}\n            />\n            <span className=\"text-muted-foreground text-sm\">\n              {healthCheck.isLoading\n                ? \"Checking...\"\n                : healthCheck.data === \"OK\"\n                  ? \"Connected\"\n                  : \"Error\"}\n            </span>\n          </div>\n          {{else}}\n            {{#unless (eq api \"none\")}}\n            <div className=\"flex items-center gap-2\">\n              <div\n                className={\\`h-2 w-2 rounded-full \\${healthCheck.data ? \"bg-green-500\" : \"bg-red-500\"}\\`}\n              />\n              <span className=\"text-muted-foreground text-sm\">\n                {healthCheck.isLoading\n                  ? \"Checking...\"\n                  : healthCheck.data\n                    ? \"Connected\"\n                    : \"Disconnected\"}\n              </span>\n            </div>\n            {{/unless}}\n          {{/if}}\n        </section>\n      </div>\n    </div>\n  );\n}\n`],\n  [\"frontend/react/tanstack-start/tsconfig.json.hbs\", `{\n  \"include\": [\"**/*.ts\", \"**/*.tsx\"],\n  \"compilerOptions\": {\n    \"target\": \"ES2022\",\n    \"jsx\": \"react-jsx\",\n    \"module\": \"ESNext\",\n    \"lib\": [\"ES2022\", \"DOM\", \"DOM.Iterable\"],\n    \"types\": [\"vite/client\"],\n\n    /* Bundler mode */\n    \"moduleResolution\": \"bundler\",\n    \"allowImportingTsExtensions\": true,\n    \"noEmit\": true,\n\n    /* Linting */\n    \"skipLibCheck\": true,\n    \"strict\": true,\n    \"noUnusedLocals\": true,\n    \"noUnusedParameters\": true,\n    \"noFallthroughCasesInSwitch\": true,\n    \"noUncheckedSideEffectImports\": true,\n    \"paths\": {\n      \"@/*\": [\"./src/*\"],\n      \"@{{projectName}}/ui/*\": [\"../../packages/ui/src/*\"]\n    }\n  }\n}\n`],\n  [\"frontend/react/tanstack-start/vite.config.ts.hbs\", `import { defineConfig } from \"vite\";\nimport { tanstackStart } from \"@tanstack/react-start/plugin/vite\";\nimport tailwindcss from \"@tailwindcss/vite\";\nimport viteReact from \"@vitejs/plugin-react\";\n\nexport default defineConfig({\n  server: {\n    port: 3001,\n  },\n  resolve: {\n    tsconfigPaths: true,\n  },\n  plugins: [\n    tailwindcss(),\n    tanstackStart(),\n    viteReact(),\n  ],\n{{#if (and (eq backend \"convex\") (eq auth \"better-auth\"))}}\n  ssr: {\n    noExternal: [\"@convex-dev/better-auth\"],\n  },\n{{/if}}\n});\n`],\n  [\"frontend/react/web-base/_gitignore\", `# Dependencies\n/node_modules\n/.pnp\n.pnp.*\n.yarn/*\n!.yarn/patches\n!.yarn/plugins\n!.yarn/releases\n!.yarn/versions\n\n# Testing\n/coverage\n\n# Build outputs\n/.next/\n/out/\n/build/\n/dist/\n.vinxi\n.output\n.react-router/\n.tanstack/\n.nitro/\n\n# Deployment\n.vercel\n.netlify\n.wrangler\n.alchemy\n\n# Environment & local files\n.env*\n!.env.example\n.DS_Store\n*.pem\n*.local\n\n# Logs\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\n.pnpm-debug.log*\n*.log*\n\n# TypeScript\n*.tsbuildinfo\nnext-env.d.ts\n\n# IDE\n.vscode/*\n!.vscode/extensions.json\n.idea\n\n# Other\ndev-dist\n\n.wrangler\n.dev.vars*\n\n.open-next\n`],\n  [\"frontend/react/web-base/components.json.hbs\", `{\n  \"$schema\": \"https://ui.shadcn.com/schema.json\",\n  \"style\": \"base-lyra\",\n  \"rsc\": {{#if (includes frontend \"next\")}}true{{else}}false{{/if}},\n  \"tsx\": true,\n  \"tailwind\": {\n    \"config\": \"\",\n    \"css\": \"../../packages/ui/src/styles/globals.css\",\n    \"baseColor\": \"neutral\",\n    \"cssVariables\": true,\n    \"prefix\": \"\"\n  },\n  \"iconLibrary\": \"lucide\",\n  \"aliases\": {\n    \"components\": \"@/components\",\n    \"utils\": \"@{{projectName}}/ui/lib/utils\",\n    \"ui\": \"@{{projectName}}/ui/components\",\n    \"lib\": \"@/lib\",\n    \"hooks\": \"@/hooks\"\n  },\n  \"menuColor\": \"default\",\n  \"menuAccent\": \"subtle\",\n  \"registries\": {}\n}\n`],\n  [\"frontend/react/web-base/src/components/header.tsx.hbs\", `{{#if (includes frontend \"next\")}}\n\"use client\";\nimport Link from \"next/link\";\n{{else if (includes frontend \"react-router\")}}\nimport { NavLink } from \"react-router\";\n{{else if (or (includes frontend \"tanstack-router\") (includes frontend \"tanstack-start\"))}}\nimport { Link } from \"@tanstack/react-router\";\n{{/if}}\n{{#unless (includes frontend \"tanstack-start\")}}\nimport { ModeToggle } from \"./mode-toggle\";\n{{/unless}}\n{{#if (and (eq auth \"better-auth\") (ne backend \"convex\"))}}\nimport UserMenu from \"./user-menu\";\n{{/if}}\n\nexport default function Header() {\n  const links = [\n    { to: \"/\", label: \"Home\" },\n    {{#if (or (eq auth \"better-auth\") (eq auth \"clerk\"))}}\n      { to: \"/dashboard\", label: \"Dashboard\" },\n    {{/if}}\n    {{#if (includes examples \"todo\")}}\n    { to: \"/todos\", label: \"Todos\" },\n    {{/if}}\n    {{#if (includes examples \"ai\")}}\n    { to: \"/ai\", label: \"AI Chat\" },\n    {{/if}}\n  ] as const;\n\n  return (\n    <div>\n      <div className=\"flex flex-row items-center justify-between px-2 py-1\">\n        <nav className=\"flex gap-4 text-lg\">\n          {links.map(({ to, label }) => {\n            {{#if (includes frontend \"next\")}}\n            return (\n              <Link key={to} href={to}>\n                {label}\n              </Link>\n            );\n            {{else if (includes frontend \"react-router\")}}\n            return (\n              <NavLink\n                key={to}\n                to={to}\n                className={({ isActive }) => isActive ? \"font-bold\" : \"\"}\n                end\n              >\n                {label}\n              </NavLink>\n            );\n            {{else if (or (includes frontend \"tanstack-router\") (includes frontend \"tanstack-start\"))}}\n            return (\n              <Link\n                key={to}\n                to={to}\n              >\n                {label}\n              </Link>\n            );\n            {{else}}\n            return null;\n            {{/if}}\n          })}\n        </nav>\n        <div className=\"flex items-center gap-2\">\n          {{#unless (includes frontend \"tanstack-start\")}}\n          <ModeToggle />\n          {{/unless}}\n          {{#if (and (eq auth \"better-auth\") (ne backend \"convex\"))}}\n          <UserMenu />\n          {{/if}}\n        </div>\n      </div>\n      <hr />\n    </div>\n  );\n}\n`],\n  [\"frontend/react/web-base/src/components/loader.tsx.hbs\", `import { Loader2 } from \"lucide-react\";\n\nexport default function Loader() {\n  return (\n    <div className=\"flex h-full items-center justify-center pt-8\">\n      <Loader2 className=\"animate-spin\" />\n    </div>\n  );\n}\n`],\n  [\"frontend/react/web-base/src/index.css.hbs\", `@import '@{{projectName}}/ui/globals.css';\n{{#if (includes examples \"ai\")}}\n@source \"../node_modules/streamdown/dist/*.js\";\n{{/if}}\n`],\n  [\"frontend/solid/_gitignore\", `node_modules\n.DS_Store\ndist\ndist-ssr\n*.local\n.env\n.env.*\n\n.wrangler\n.alchemy\n.dev.vars*`],\n  [\"frontend/solid/index.html\", `<!doctype html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <link rel=\"icon\" href=\"/favicon.ico\" />\n    <meta name=\"theme-color\" content=\"#000000\" />\n  </head>\n  <body>\n    <div id=\"app\"></div>\n    <script type=\"module\" src=\"/src/main.tsx\"></script>\n  </body>\n</html>\n`],\n  [\"frontend/solid/package.json.hbs\", `{\n  \"name\": \"web\",\n  \"private\": true,\n  \"type\": \"module\",\n  \"scripts\": {\n    \"dev\": \"vite dev\",\n    \"build\": \"vite build\",\n    \"serve\": \"vite preview\",\n    \"test\": \"vitest run\"\n  },\n  \"dependencies\": {\n    \"@tailwindcss/vite\": \"^4.2.2\",\n    \"@tanstack/router-plugin\": \"^1.167.22\",\n    \"@tanstack/solid-router\": \"^1.168.20\",\n    \"lucide-solid\": \"^1.8.0\",\n    \"solid-js\": \"^1.9.12\",\n    \"tailwindcss\": \"^4.2.2\"\n  },\n  \"devDependencies\": {\n    \"vite\": \"^8.0.8\",\n    \"vite-plugin-solid\": \"^2.11.12\"\n  }\n}\n`],\n  [\"frontend/solid/public/robots.txt\", `# https://www.robotstxt.org/robotstxt.html\nUser-agent: *\nDisallow:\n`],\n  [\"frontend/solid/src/components/header.tsx.hbs\", `import { Link } from \"@tanstack/solid-router\";\n{{#if (eq auth \"better-auth\")}}\nimport UserMenu from \"./user-menu\";\n{{/if}}\nimport { For } from \"solid-js\";\n\nexport default function Header() {\n  const links = [\n    { to: \"/\", label: \"Home\" },\n    {{#if (eq auth \"better-auth\")}}\n    { to: \"/dashboard\", label: \"Dashboard\" },\n    {{/if}}\n    {{#if (includes examples \"todo\")}}\n    { to: \"/todos\", label: \"Todos\" },\n    {{/if}}\n    {{#if (includes examples \"ai\")}}\n    { to: \"/ai\", label: \"AI Chat\" },\n    {{/if}}\n  ];\n\n  return (\n    <div>\n      <div class=\"flex flex-row items-center justify-between px-2 py-1\">\n        <nav class=\"flex gap-4 text-lg\">\n          <For each={links}>\n            {(link) => <Link to={link.to}>{link.label}</Link>}\n          </For>\n        </nav>\n        <div class=\"flex items-center gap-2\">\n          {{#if (eq auth \"better-auth\")}}\n          <UserMenu />\n          {{/if}}\n        </div>\n      </div>\n      <hr />\n    </div>\n  );\n}\n`],\n  [\"frontend/solid/src/components/loader.tsx\", `import { Loader2 } from \"lucide-solid\";\n\nexport default function Loader() {\n  return (\n    <div class=\"flex h-full items-center justify-center pt-8\">\n      <Loader2 class=\"animate-spin\" />\n    </div>\n  );\n}\n`],\n  [\"frontend/solid/src/main.tsx.hbs\", `import { RouterProvider, createRouter } from \"@tanstack/solid-router\";\nimport { render } from \"solid-js/web\";\nimport { routeTree } from \"./routeTree.gen\";\nimport \"./styles.css\";\n{{#if (eq api \"orpc\")}}\nimport { QueryClientProvider } from \"@tanstack/solid-query\";\nimport { orpc, queryClient } from \"./utils/orpc\";\n{{/if}}\n\nconst router = createRouter({\n  routeTree,\n  defaultPreload: \"intent\",\n  scrollRestoration: true,\n  defaultPreloadStaleTime: 0,\n  {{#if (eq api \"orpc\")}}\n  context: { orpc, queryClient },\n  {{/if}}\n});\n\ndeclare module \"@tanstack/solid-router\" {\n  interface Register {\n    router: typeof router;\n  }\n}\n\nfunction App() {\n  return (\n    {{#if (eq api \"orpc\")}}\n    <QueryClientProvider client={queryClient}>\n    {{/if}}\n      <RouterProvider router={router} />\n    {{#if (eq api \"orpc\")}}\n    </QueryClientProvider>\n    {{/if}}\n  );\n}\n\nconst rootElement = document.getElementById(\"app\");\nif (rootElement) {\n  render(() => <App />, rootElement);\n}\n`],\n  [\"frontend/solid/src/routes/__root.tsx.hbs\", `import Header from \"@/components/header\";\nimport { Outlet, createRootRouteWithContext } from \"@tanstack/solid-router\";\nimport { TanStackRouterDevtools } from \"@tanstack/solid-router-devtools\";\n{{#if (eq api \"orpc\")}}\nimport { SolidQueryDevtools } from \"@tanstack/solid-query-devtools\";\nimport type { QueryClient } from \"@tanstack/solid-query\";\nimport type { orpc } from \"../utils/orpc\";\n\nexport interface RouterContext {\n  orpc: typeof orpc;\n  queryClient: QueryClient;\n}\n{{else}}\nexport interface RouterContext {}\n{{/if}}\n\nexport const Route = createRootRouteWithContext<RouterContext>()({\n  component: RootComponent,\n});\n\nfunction RootComponent() {\n  return (\n    <>\n      <div class=\"grid grid-rows-[auto_1fr] h-svh\">\n        <Header />\n        <Outlet />\n      </div>\n      {{#if (eq api \"orpc\")}}\n      <SolidQueryDevtools />\n      {{/if}}\n      <TanStackRouterDevtools />\n    </>\n  );\n}\n`],\n  [\"frontend/solid/src/routes/index.tsx.hbs\", `import { createFileRoute } from \"@tanstack/solid-router\";\n{{#if (eq api \"orpc\")}}\nimport { useQuery } from \"@tanstack/solid-query\";\nimport { orpc } from \"../utils/orpc\";\nimport { Match, Switch } from \"solid-js\";\n{{else}}\n{{/if}}\n\nexport const Route = createFileRoute(\"/\")({\n  component: App,\n});\n\nconst TITLE_TEXT = \\`\n ██████╗ ███████╗████████╗████████╗███████╗██████╗\n ██╔══██╗██╔════╝╚══██╔══╝╚══██╔══╝██╔════╝██╔══██╗\n ██████╔╝█████╗     ██║      ██║   █████╗  ██████╔╝\n ██╔══██╗██╔══╝     ██║      ██║   ██╔══╝  ██╔══██╗\n ██████╔╝███████╗   ██║      ██║   ███████╗██║  ██║\n ╚═════╝ ╚══════╝   ╚═╝      ╚═╝   ╚══════╝╚═╝  ╚═╝\n\n ████████╗    ███████╗████████╗ █████╗  ██████╗██╗  ██╗\n ╚══██╔══╝    ██╔════╝╚══██╔══╝██╔══██╗██╔════╝██║ ██╔╝\n    ██║       ███████╗   ██║   ███████║██║     █████╔╝\n    ██║       ╚════██║   ██║   ██╔══██║██║     ██╔═██╗\n    ██║       ███████║   ██║   ██║  ██║╚██████╗██║  ██╗\n    ╚═╝       ╚══════╝   ╚═╝   ╚═╝  ╚═╝ ╚═════╝╚═╝  ╚═╝\n \\`;\n\nfunction App() {\n  {{#if (eq api \"orpc\")}}\n  const healthCheck = useQuery(() => orpc.healthCheck.queryOptions());\n  {{/if}}\n\n  return (\n    <div class=\"container mx-auto max-w-3xl px-4 py-2\">\n      <pre class=\"overflow-x-auto font-mono text-sm\">{TITLE_TEXT}</pre>\n      <div class=\"grid gap-6\">\n        {{#if (eq api \"orpc\")}}\n        <section class=\"rounded-lg border p-4\">\n          <h2 class=\"mb-2 font-medium\">API Status</h2>\n          <Switch>\n            <Match when={healthCheck.isPending}>\n              <div class=\"flex items-center gap-2\">\n                <div class=\"h-2 w-2 rounded-full bg-gray-500 animate-pulse\" />{\" \"}\n                <span class=\"text-sm text-muted-foreground\">Checking...</span>\n              </div>\n            </Match>\n            <Match when={healthCheck.isError}>\n              <div class=\"flex items-center gap-2\">\n                <div class=\"h-2 w-2 rounded-full bg-red-500\" />\n                <span class=\"text-sm text-muted-foreground\">Disconnected</span>\n              </div>\n            </Match>\n            <Match when={healthCheck.isSuccess}>\n              <div class=\"flex items-center gap-2\">\n                <div\n                  class={\\`h-2 w-2 rounded-full \\${healthCheck.data ? \"bg-green-500\" : \"bg-red-500\"}\\`}\n                />\n                <span class=\"text-sm text-muted-foreground\">\n                  {healthCheck.data\n                    ? \"Connected\"\n                    : \"Disconnected\"}\n                </span>\n              </div>\n            </Match>\n          </Switch>\n        </section>\n        {{/if}}\n      </div>\n    </div>\n  );\n}\n`],\n  [\"frontend/solid/src/styles.css\", `@import \"tailwindcss\";\n\nbody {\n  @apply bg-neutral-950 text-neutral-100;\n}\n`],\n  [\"frontend/solid/tsconfig.json.hbs\", `{\n  \"include\": [\"**/*.ts\", \"**/*.tsx\"],\n  \"compilerOptions\": {\n    \"target\": \"ES2022\",\n    \"jsx\": \"preserve\",\n    \"jsxImportSource\": \"solid-js\",\n    \"module\": \"ESNext\",\n    \"lib\": [\"ES2022\", \"DOM\", \"DOM.Iterable\"],\n    \"types\": [\"vite/client\"],\n\n    \"moduleResolution\": \"bundler\",\n    \"allowImportingTsExtensions\": true,\n    \"verbatimModuleSyntax\": true,\n    \"noEmit\": true,\n\n    \"skipLibCheck\": true,\n    \"strict\": true,\n    \"noUnusedLocals\": true,\n    \"noUnusedParameters\": true,\n    \"noFallthroughCasesInSwitch\": true,\n    \"noUncheckedSideEffectImports\": true,\n\n    \"rootDirs\": [\".\"],\n    \"paths\": {\n      \"@/*\": [\"./src/*\"]\n    }\n  }\n}\n`],\n  [\"frontend/solid/vite.config.ts.hbs\", `import { defineConfig } from \"vite\";\nimport { tanstackRouter } from \"@tanstack/router-plugin/vite\";\nimport solidPlugin from \"vite-plugin-solid\";\nimport tailwindcss from \"@tailwindcss/vite\";\nimport path from \"node:path\";\n\nexport default defineConfig({\n  plugins: [\n    tanstackRouter({ target: \"solid\", autoCodeSplitting: true }),\n    solidPlugin(),\n    tailwindcss(),\n  ],\n  resolve: {\n    alias: {\n      \"@\": path.resolve(__dirname, \"./src\"),\n    },\n  },\n  server: {\n    port: 3001,\n  },\n});`],\n  [\"frontend/svelte/_gitignore\", `node_modules\n\n# Output\n.output\n.vercel\n.netlify\n.wrangler\n.alchemy\n/.svelte-kit\n/build\n\n# OS\n.DS_Store\nThumbs.db\n\n# Env\n.env\n.env.*\n!.env.example\n!.env.test\n\n# Vite\nvite.config.js.timestamp-*\nvite.config.ts.timestamp-*\n`],\n  [\"frontend/svelte/_npmrc\", `engine-strict=true\n`],\n  [\"frontend/svelte/package.json.hbs\", `{\n\t\"name\": \"web\",\n\t\"private\": true,\n\t\"version\": \"0.0.1\",\n\t\"type\": \"module\",\n\t\"scripts\": {\n\t\t\"dev\": \"vite dev\",\n\t\t\"build\": \"vite build\",\n\t\t\"preview\": \"vite preview\",\n\t\t\"prepare\": \"svelte-kit sync || echo ''\",\n\t\t\"check\": \"svelte-kit sync && svelte-check --tsconfig ./tsconfig.json\",\n\t\t\"check:watch\": \"svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch\"\n\t},\n\t\"devDependencies\": {\n\t\t\"@sveltejs/adapter-auto\": \"^7.0.1\",\n\t\t\"@sveltejs/kit\": \"^2.58.0\",\n\t\t\"@sveltejs/vite-plugin-svelte\": \"^7.0.0\",\n\t\t\"@tailwindcss/vite\": \"^4.2.4\",\n\t\t\"svelte\": \"^5.55.5\",\n\t\t\"svelte-check\": \"^4.4.6\",\n\t\t\"tailwindcss\": \"^4.2.4\",\n\t\t\"vite\": \"^8.0.10\"\n\t},\n\t\"dependencies\": {}\n}\n`],\n  [\"frontend/svelte/src/app.css\", `@import \"tailwindcss\";\n\nbody {\n  @apply bg-neutral-950 text-neutral-100;\n}\n`],\n  [\"frontend/svelte/src/app.d.ts.hbs\", `{{#if (eq webDeploy \"cloudflare\")}}\n/// <reference path=\"../../../packages/env/env.d.ts\" />\n{{/if}}\n{{#if (and (eq backend \"self\") (eq api \"orpc\"))}}\nimport type { AppRouterClient } from \"@{{projectName}}/api/routers/index\";\n\n{{/if}}\n// See https://svelte.dev/docs/kit/types#app.d.ts\n// for information about these interfaces\ndeclare global {\n{{#if (and (eq backend \"self\") (eq api \"orpc\"))}}\n\tvar $client: AppRouterClient | undefined;\n\n{{/if}}\n\tnamespace App {\n\t\t// interface Error {}\n\t\t// interface Locals {}\n\t\t// interface PageData {}\n\t\t// interface PageState {}\n{{#if (eq webDeploy \"cloudflare\")}}\n\t\tinterface Platform {\n\t\t\tenv: Env;\n\t\t\tctx: ExecutionContext;\n\t\t\tcaches: CacheStorage;\n\t\t\tcf: IncomingRequestCfProperties;\n\t\t}\n{{else}}\n\t\t// interface Platform {}\n{{/if}}\n\t}\n}\n\nexport {};\n`],\n  [\"frontend/svelte/src/app.html\", `<!doctype html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"utf-8\" />\n    <link rel=\"icon\" href=\"%sveltekit.assets%/favicon.png\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n    %sveltekit.head%\n  </head>\n  <body data-sveltekit-preload-data=\"hover\">\n    <div style=\"display: contents\">%sveltekit.body%</div>\n  </body>\n</html>\n`],\n  [\"frontend/svelte/src/components/Header.svelte.hbs\", `<script lang=\"ts\">\n\n    {{#if (eq auth \"better-auth\")}}\n\timport UserMenu from './UserMenu.svelte';\n    {{/if}}\n\n</script>\n\n<div>\n\t<div class=\"flex flex-row items-center justify-between px-4 py-2 md:px-6\">\n\t\t<nav class=\"flex gap-4 text-lg\">\n\t\t\t<a href=\"/\" class=\"hover:text-neutral-400 transition-colors\">Home</a>\n\t\t    {{#if (eq auth \"better-auth\")}}\n\t\t\t<a href=\"/dashboard\" class=\"hover:text-neutral-400 transition-colors\">Dashboard</a>\n\t\t    {{/if}}\n\t\t    {{#if (includes examples \"todo\")}}\n\t\t\t<a href=\"/todos\" class=\"hover:text-neutral-400 transition-colors\">Todos</a>\n\t\t    {{/if}}\n\t\t    {{#if (includes examples \"ai\")}}\n\t\t\t<a href=\"/ai\" class=\"hover:text-neutral-400 transition-colors\">AI Chat</a>\n\t\t    {{/if}}\n\t\t</nav>\n\t\t<div class=\"flex items-center gap-2\">\n\t\t    {{#if (eq auth \"better-auth\")}}\n            <UserMenu />\n             {{/if}}\n\t\t</div>\n\t</div>\n\t<hr class=\"border-neutral-800\" />\n</div>\n`],\n  [\"frontend/svelte/src/lib/index.ts\", `// place files you want to import through the \\`$lib\\` alias in this folder.\nexport {};\n`],\n  [\"frontend/svelte/src/routes/+layout.svelte.hbs\", `{{#if (eq backend \"convex\")}}\n<script lang=\"ts\">\n\timport '../app.css';\n    import Header from '../components/Header.svelte';\n    import { PUBLIC_CONVEX_URL } from '$env/static/public';\n\timport { setupConvex } from 'convex-svelte';\n\n\tconst { children } = $props();\n\tsetupConvex(PUBLIC_CONVEX_URL);\n</script>\n\n<div class=\"grid h-svh grid-rows-[auto_1fr]\">\n\t<Header />\n\t<main class=\"overflow-y-auto\">\n\t\t{@render children()}\n\t</main>\n</div>\n{{else}}\n  {{#if (eq api \"orpc\")}}\n<script lang=\"ts\">\n    import { QueryClientProvider } from '@tanstack/svelte-query';\n    import { SvelteQueryDevtools } from '@tanstack/svelte-query-devtools'\n\timport '../app.css';\n    import { queryClient } from '$lib/orpc';\n    import Header from '../components/Header.svelte';\n\n\tconst { children } = $props();\n</script>\n\n<QueryClientProvider client={queryClient}>\n    <div class=\"grid h-svh grid-rows-[auto_1fr]\">\n\t\t<Header />\n\t\t<main class=\"overflow-y-auto\">\n\t\t\t{@render children()}\n\t\t</main>\n    </div>\n    <SvelteQueryDevtools />\n</QueryClientProvider>\n  {{else}}\n<script lang=\"ts\">\n\timport '../app.css';\n    import Header from '../components/Header.svelte';\n\n\tconst { children } = $props();\n</script>\n\n<div class=\"grid h-svh grid-rows-[auto_1fr]\">\n\t<Header />\n\t<main class=\"overflow-y-auto\">\n\t\t{@render children()}\n\t</main>\n</div>\n  {{/if}}\n{{/if}}\n`],\n  [\"frontend/svelte/src/routes/+page.svelte.hbs\", `{{#if (eq backend \"convex\")}}\n<script lang=\"ts\">\nimport { useQuery } from 'convex-svelte';\nimport { api } from \"@{{projectName}}/backend/convex/_generated/api\";\n\nconst healthCheck = useQuery(api.healthCheck.get, {});\n\nconst TITLE_TEXT = \\`\n   ██████╗ ███████╗████████╗████████╗███████╗██████╗\n   ██╔══██╗██╔════╝╚══██╔══╝╚══██╔══╝██╔════╝██╔══██╗\n   ██████╔╝█████╗     ██║      ██║   █████╗  ██████╔╝\n   ██╔══██╗██╔══╝     ██║      ██║   ██╔══╝  ██╔══██╗\n   ██████╔╝███████╗   ██║      ██║   ███████╗██║  ██║\n   ╚═════╝ ╚══════╝   ╚═╝      ╚═╝   ╚══════╝╚═╝  ╚═╝\n\n   ████████╗    ███████╗████████╗ █████╗  ██████╗██╗  ██╗\n   ╚══██╔══╝    ██╔════╝╚══██╔══╝██╔══██╗██╔════╝██║ ██╔╝\n      ██║       ███████╗   ██║   ███████║██║     █████╔╝\n      ██║       ╚════██║   ██║   ██╔══██║██║     ██╔═██╗\n      ██║       ███████║   ██║   ██║  ██║╚██████╗██║  ██╗\n      ╚═╝       ╚══════╝   ╚═╝   ╚═╝  ╚═╝ ╚═════╝╚═╝  ╚═╝\n   \\`;\n</script>\n\n<div class=\"container mx-auto max-w-3xl px-4 py-2\">\n\t<pre class=\"overflow-x-auto font-mono text-sm\">{TITLE_TEXT}</pre>\n\t<div class=\"grid gap-6\">\n\t\t<section class=\"rounded-lg border p-4\">\n\t\t\t<h2 class=\"mb-2 font-medium\">API Status</h2>\n\t\t\t<div class=\"flex items-center gap-2\">\n\t\t\t\t<div\n\t\t\t\t\tclass={\\`h-2 w-2 rounded-full \\${healthCheck.data ? \"bg-green-500\" : \"bg-red-500\"}\\`}\n\t\t\t\t></div>\n\t\t\t\t<span class=\"text-muted-foreground text-sm\">\n\t\t\t\t\t{healthCheck.isLoading\n\t\t\t\t\t\t? \"Checking...\"\n\t\t\t\t\t\t: healthCheck.data\n\t\t\t\t\t\t\t? \"Connected\"\n\t\t\t\t\t\t\t: \"Disconnected\"}\n\t\t\t\t</span>\n\t\t\t</div>\n\t\t</section>\n\t</div>\n</div>\n{{else}}\n<script lang=\"ts\">\n{{#if (eq api \"orpc\")}}\nimport { orpc } from \"$lib/orpc\";\nimport { createQuery } from \"@tanstack/svelte-query\";\nconst healthCheck = createQuery(orpc.healthCheck.queryOptions());\n{{/if}}\n\nconst TITLE_TEXT = \\`\n   ██████╗ ███████╗████████╗████████╗███████╗██████╗\n   ██╔══██╗██╔════╝╚══██╔══╝╚══██╔══╝██╔════╝██╔══██╗\n   ██████╔╝█████╗     ██║      ██║   █████╗  ██████╔╝\n   ██╔══██╗██╔══╝     ██║      ██║   ██╔══╝  ██╔══██╗\n   ██████╔╝███████╗   ██║      ██║   ███████╗██║  ██║\n   ╚═════╝ ╚══════╝   ╚═╝      ╚═╝   ╚══════╝╚═╝  ╚═╝\n\n   ████████╗    ███████╗████████╗ █████╗  ██████╗██╗  ██╗\n   ╚══██╔══╝    ██╔════╝╚══██╔══╝██╔══██╗██╔════╝██║ ██╔╝\n      ██║       ███████╗   ██║   ███████║██║     █████╔╝\n      ██║       ╚════██║   ██║   ██╔══██║██║     ██╔═██╗\n      ██║       ███████║   ██║   ██║  ██║╚██████╗██║  ██╗\n      ╚═╝       ╚══════╝   ╚═╝   ╚═╝  ╚═╝ ╚═════╝╚═╝  ╚═╝\n   \\`;\n</script>\n\n<div class=\"container mx-auto max-w-3xl px-4 py-2\">\n\t<pre class=\"overflow-x-auto font-mono text-sm\">{TITLE_TEXT}</pre>\n\t<div class=\"grid gap-6\">\n\t    {{#if (eq api \"orpc\")}}\n\t\t<section class=\"rounded-lg border p-4\">\n\t\t\t<h2 class=\"mb-2 font-medium\">API Status</h2>\n\t\t\t<div class=\"flex items-center gap-2\">\n\t\t\t\t<div\n\t\t\t\t\tclass={\\`h-2 w-2 rounded-full \\${$healthCheck.data ? \"bg-green-500\" : \"bg-red-500\"}\\`}\n\t\t\t\t></div>\n\t\t\t\t<span class=\"text-muted-foreground text-sm\">\n\t\t\t\t\t{$healthCheck.isLoading\n\t\t\t\t\t\t? \"Checking...\"\n\t\t\t\t\t\t: $healthCheck.data\n\t\t\t\t\t\t\t? \"Connected\"\n\t\t\t\t\t\t\t: \"Disconnected\"}\n\t\t\t\t</span>\n\t\t\t</div>\n\t\t</section>\n\t    {{/if}}\n\t</div>\n</div>\n{{/if}}\n`],\n  [\"frontend/svelte/static/favicon.png\", `[Binary file]`],\n  [\"frontend/svelte/svelte.config.js.hbs\", `{{#if (eq webDeploy \"cloudflare\")}}\nimport alchemy from 'alchemy/cloudflare/sveltekit';\n{{else}}\nimport adapter from '@sveltejs/adapter-auto';\n{{/if}}\nimport { vitePreprocess } from '@sveltejs/vite-plugin-svelte';\n\n/** @type {import('@sveltejs/kit').Config} */\nconst config = {\n\t// Consult https://svelte.dev/docs/kit/integrations\n\t// for more information about preprocessors\n\tpreprocess: vitePreprocess(),\n\n\tkit: {\n{{#if (eq webDeploy \"cloudflare\")}}\n\t\t// Alchemy's adapter wraps SvelteKit's Cloudflare adapter for local platform.env and Worker builds.\n\t\tadapter: alchemy()\n{{else}}\n\t\t// adapter-auto only supports some environments, see https://svelte.dev/docs/kit/adapter-auto for a list.\n\t\t// If your environment is not supported, or you settled on a specific environment, switch out the adapter.\n\t\t// See https://svelte.dev/docs/kit/adapters for more information about adapters.\n\t\tadapter: adapter()\n{{/if}}\n\t}\n};\n\nexport default config;\n`],\n  [\"frontend/svelte/tsconfig.json.hbs\", `{\n\t\"extends\": \"./.svelte-kit/tsconfig.json\",\n\t\"compilerOptions\": {\n\t\t\"allowJs\": true,\n\t\t\"checkJs\": true,\n\t\t\"esModuleInterop\": true,\n\t\t\"forceConsistentCasingInFileNames\": true,\n\t\t\"resolveJsonModule\": true,\n\t\t\"skipLibCheck\": true,\n\t\t\"sourceMap\": true,\n\t\t\"strict\": true,\n\t\t\"moduleResolution\": \"bundler\"{{#if (eq webDeploy \"cloudflare\")}},\n\t\t\t\"types\": [\"@cloudflare/workers-types\"]{{/if}}\n\t}\n\t// Path aliases are handled by https://svelte.dev/docs/kit/configuration#alias\n\t// except $lib which is handled by https://svelte.dev/docs/kit/configuration#files\n\t//\n\t// If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes\n\t// from the referenced tsconfig.json - TypeScript does not merge them in\n}\n`],\n  [\"frontend/svelte/vite.config.ts.hbs\", `import tailwindcss from \"@tailwindcss/vite\";\nimport { sveltekit } from \"@sveltejs/kit/vite\";\nimport { defineConfig } from \"vite\";\n\nexport default defineConfig({\n  plugins: [tailwindcss(), sveltekit()],\n});\n`],\n  [\"packages/config/package.json.hbs\", `{\n  \"name\": \"@{{projectName}}/config\",\n  \"version\": \"0.0.0\",\n  \"private\": true\n}\n`],\n  [\"packages/config/tsconfig.base.json.hbs\", `{\n  \"$schema\": \"https://json.schemastore.org/tsconfig\",\n  \"compilerOptions\": {\n    \"target\": \"ESNext\",\n    \"module\": \"ESNext\",\n    \"moduleResolution\": \"bundler\",\n    \"lib\": [\"ESNext\"],\n    \"verbatimModuleSyntax\": true,\n    \"strict\": true,\n    \"skipLibCheck\": true,\n    \"resolveJsonModule\": true,\n    \"allowSyntheticDefaultImports\": true,\n    \"esModuleInterop\": true,\n    \"forceConsistentCasingInFileNames\": true,\n    \"isolatedModules\": true,\n    \"noUncheckedIndexedAccess\": true,\n    \"noUnusedLocals\": true,\n    \"noUnusedParameters\": true,\n    \"noFallthroughCasesInSwitch\": true,\n    \"types\": [\n      {{#if (eq runtime \"node\")}}\n        \"node\"\n      {{else if (eq runtime \"bun\")}}\n        \"bun\"\n      {{else if (eq runtime \"workers\")}}\n        \"node\"\n      {{else}}\n        \"node\"\n      {{/if}}{{#if (or (eq serverDeploy \"cloudflare\") (eq webDeploy \"cloudflare\"))}},\n      \"@cloudflare/workers-types\"{{/if}}\n    ]\n  }\n}`],\n  [\"packages/env/package.json.hbs\", `{\n\t\"name\": \"@{{projectName}}/env\",\n\t\"version\": \"0.0.0\",\n\t\"private\": true,\n\t\"type\": \"module\",\n\t\"exports\": {}\n}`],\n  [\"packages/env/src/cloudflare-local.ts.hbs\", `import { config } from \"dotenv\";\nimport { fileURLToPath } from \"node:url\";\n\nconfig({ path: fileURLToPath(new URL(\"../../../.env\", import.meta.url)) });\nconfig();\n\nconst runtimeEnv = typeof process === \"undefined\" ? {} : process.env;\n\nexport const env = new Proxy({} as Env, {\n\tget(_target, prop) {\n\t\tif (typeof prop !== \"string\") {\n\t\t\treturn undefined;\n\t\t}\n\n\t\treturn runtimeEnv[prop];\n\t},\n});\n`],\n  [\"packages/env/src/native.ts.hbs\", `import { createEnv } from \"@t3-oss/env-core\";\nimport { z } from \"zod\";\n\nexport const env = createEnv({\n\tclientPrefix: \"EXPO_PUBLIC_\",\n\tclient: {\n{{#if (eq backend \"convex\")}}\n\t\tEXPO_PUBLIC_CONVEX_URL: z.url(),\n{{#if (eq auth \"better-auth\")}}\n\t\tEXPO_PUBLIC_CONVEX_SITE_URL: z.url(),\n{{/if}}\n{{else}}\n\t\tEXPO_PUBLIC_SERVER_URL: z.url(),\n{{/if}}\n{{#if (eq auth \"clerk\")}}\n\t\tEXPO_PUBLIC_CLERK_PUBLISHABLE_KEY: z.string().min(1),\n{{/if}}\n\t},\n\truntimeEnv: process.env,\n\temptyStringAsUndefined: true,\n});\n`],\n  [\"packages/env/src/server.ts.hbs\", `{{#if (and (eq serverDeploy \"cloudflare\") (or (ne backend \"self\") (ne webDeploy \"cloudflare\")))}}\n/// <reference types=\"@cloudflare/workers-types\" />\n/// <reference path=\"../env.d.ts\" />\n// For Cloudflare Workers, env is accessed via cloudflare:workers module\n// Types are defined in env.d.ts based on your alchemy.run.ts bindings\nexport { env } from \"cloudflare:workers\";\n{{else if (and (eq backend \"self\") (eq webDeploy \"cloudflare\") (includes frontend \"next\"))}}\n/// <reference path=\"../env.d.ts\" />\nimport { getCloudflareContext } from \"@opennextjs/cloudflare\";\n\nfunction getNodeEnvValue(key: string) {\n\tif (key === \"DB\") {\n\t\treturn undefined;\n\t}\n\n\treturn process.env[key];\n}\n\nfunction getCloudflareEnvSync() {\n\ttry {\n\t\treturn getCloudflareContext().env as Env;\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\ntype EnvValue = Env[keyof Env];\n\nfunction createEnvProxy(getValue: (key: keyof Env & string) => EnvValue | undefined) {\n\treturn new Proxy({} as Env, {\n\t\tget(_target, prop) {\n\t\t\tif (typeof prop !== \"string\") {\n\t\t\t\treturn undefined;\n\t\t\t}\n\n\t\t\treturn getValue(prop as keyof Env & string);\n\t\t},\n\t});\n}\n\nfunction resolveEnvValue(key: keyof Env & string): EnvValue | undefined {\n\tconst nodeValue = getNodeEnvValue(key);\n\tif (nodeValue !== undefined) {\n\t\treturn nodeValue as EnvValue;\n\t}\n\n\treturn getCloudflareEnvSync()?.[key as keyof Env];\n}\n\n// Next.js local dev runs in Node.js, where env vars are exposed on process.env.\n// In the Cloudflare runtime, fall back to OpenNext's Cloudflare context bindings.\n// For static routes (ISR/SSG), use getEnvAsync() so OpenNext can resolve bindings\n// with the async Cloudflare context API.\nexport async function getEnvAsync() {\n\tconst cloudflareEnv = (await getCloudflareContext({ async: true })).env as Env;\n\n\treturn createEnvProxy((key) => {\n\t\tconst nodeValue = getNodeEnvValue(key);\n\t\tif (nodeValue !== undefined) {\n\t\t\treturn nodeValue;\n\t\t}\n\n\t\treturn cloudflareEnv[key as keyof Env];\n\t});\n}\n\nexport const env = createEnvProxy(resolveEnvValue);\n{{else if (and (eq backend \"self\") (eq webDeploy \"cloudflare\") (includes frontend \"svelte\"))}}\n/// <reference path=\"../env.d.ts\" />\nimport { config } from \"dotenv\";\nimport { fileURLToPath } from \"node:url\";\n\nconfig({ path: fileURLToPath(new URL(\"../../../.env\", import.meta.url)) });\nconfig();\n\nconst runtimeEnv = typeof process === \"undefined\" ? {} : process.env;\n\nexport const env = new Proxy({} as Env, {\n\tget(_target, prop) {\n\t\tif (typeof prop !== \"string\") {\n\t\t\treturn undefined;\n\t\t}\n\n\t\treturn runtimeEnv[prop];\n\t},\n});\n{{else if (and (eq backend \"self\") (eq webDeploy \"cloudflare\"))}}\n/// <reference types=\"@cloudflare/workers-types\" />\n/// <reference path=\"../env.d.ts\" />\n// For Cloudflare Workers, env is accessed via cloudflare:workers module\n// Types are defined in env.d.ts based on your alchemy.run.ts bindings\nexport { env } from \"cloudflare:workers\";\n{{else}}\nimport \"dotenv/config\";\nimport { createEnv } from \"@t3-oss/env-core\";\nimport { z } from \"zod\";\n\nexport const env = createEnv({\n\tserver: {\n{{#if (ne database \"none\")}}\n{{#if (eq dbSetup \"planetscale\")}}\n\t\tDATABASE_HOST: z.string().min(1),\n\t\tDATABASE_USERNAME: z.string().min(1),\n\t\tDATABASE_PASSWORD: z.string().min(1),\n{{else}}\n\t\tDATABASE_URL: z.string().min(1),\n{{#if (eq dbSetup \"turso\")}}\n\t\tDATABASE_AUTH_TOKEN: z.string().min(1),\n{{/if}}\n{{/if}}\n{{/if}}\n{{#if (eq auth \"better-auth\")}}\n\t\tBETTER_AUTH_SECRET: z.string().min(32),\n\t\tBETTER_AUTH_URL: z.url(),\n{{/if}}\n{{#if (eq auth \"clerk\")}}\n\t\tCLERK_SECRET_KEY: z.string().min(1),\n{{#if (or (eq backend \"express\") (eq backend \"fastify\") (and (ne api \"none\") (or (eq backend \"self\") (eq backend \"hono\") (eq backend \"elysia\"))))}}\n\t\tCLERK_PUBLISHABLE_KEY: z.string().min(1),\n{{/if}}\n{{/if}}\n{{#if (eq payments \"polar\")}}\n\t\tPOLAR_ACCESS_TOKEN: z.string().min(1),\n\t\tPOLAR_SUCCESS_URL: z.url(),\n{{/if}}\n\t\tCORS_ORIGIN: z.url(),\n\t\tNODE_ENV: z.enum([\"development\", \"production\", \"test\"]).default(\"development\"),\n\t},\n\truntimeEnv: process.env,\n\temptyStringAsUndefined: true,\n});\n{{/if}}\n`],\n  [\"packages/env/src/web.ts.hbs\", `{{#if (includes frontend \"next\")}}\nimport { createEnv } from \"@t3-oss/env-nextjs\";\n{{else if (includes frontend \"nuxt\")}}\nimport { createEnv } from \"@t3-oss/env-nuxt\";\n{{else if (or (includes frontend \"svelte\") (includes frontend \"astro\"))}}\nimport { createEnv } from \"@t3-oss/env-core\";\n{{else}}\nimport { createEnv } from \"@t3-oss/env-core\";\n{{/if}}\nimport { z } from \"zod\";\n\n{{#if (includes frontend \"nuxt\")}}\n/**\n * Nuxt env validation - validates at build time when imported in nuxt.config.ts\n * For runtime access in components/plugins, use useRuntimeConfig() instead:\n *   const config = useRuntimeConfig()\n *   config.public.serverUrl (NUXT_PUBLIC_SERVER_URL maps to serverUrl)\n */\n{{/if}}\nexport const env = createEnv({\n{{#if (eq backend \"convex\")}}\n{{#if (includes frontend \"next\")}}\n\tclient: {\n\t\tNEXT_PUBLIC_CONVEX_URL: z.url(),\n{{#if (eq auth \"better-auth\")}}\n\t\tNEXT_PUBLIC_CONVEX_SITE_URL: z.url(),\n{{/if}}\n{{#if (eq auth \"clerk\")}}\n\t\tNEXT_PUBLIC_CLERK_PUBLISHABLE_KEY: z.string().min(1),\n{{/if}}\n\t},\n\truntimeEnv: {\n\t\tNEXT_PUBLIC_CONVEX_URL: process.env.NEXT_PUBLIC_CONVEX_URL,\n{{#if (eq auth \"better-auth\")}}\n\t\tNEXT_PUBLIC_CONVEX_SITE_URL: process.env.NEXT_PUBLIC_CONVEX_SITE_URL,\n{{/if}}\n{{#if (eq auth \"clerk\")}}\n\t\tNEXT_PUBLIC_CLERK_PUBLISHABLE_KEY: process.env.NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY,\n{{/if}}\n\t},\n{{else if (includes frontend \"nuxt\")}}\n\tclient: {\n\t\tNUXT_PUBLIC_CONVEX_URL: z.url(),\n\t},\n{{else if (or (includes frontend \"svelte\") (includes frontend \"astro\"))}}\n\tclientPrefix: \"PUBLIC_\",\n\tclient: {\n\t\tPUBLIC_CONVEX_URL: z.url(),\n\t},\n\truntimeEnv: (import.meta as any).env,\n{{else}}\n\tclientPrefix: \"VITE_\",\n\tclient: {\n\t\tVITE_CONVEX_URL: z.url(),\n{{#if (eq auth \"better-auth\")}}\n\t\tVITE_CONVEX_SITE_URL: z.url(),\n{{/if}}\n{{#if (eq auth \"clerk\")}}\n\t\tVITE_CLERK_PUBLISHABLE_KEY: z.string().min(1),\n{{/if}}\n\t},\n\truntimeEnv: (import.meta as any).env,\n{{/if}}\n{{else if (eq backend \"self\")}}\n{{#if (includes frontend \"next\")}}\n\tclient: {\n{{#if (eq auth \"clerk\")}}\n\t\tNEXT_PUBLIC_CLERK_PUBLISHABLE_KEY: z.string().min(1),\n{{/if}}\n\t},\n\truntimeEnv: {\n{{#if (eq auth \"clerk\")}}\n\t\tNEXT_PUBLIC_CLERK_PUBLISHABLE_KEY: process.env.NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY,\n{{/if}}\n\t},\n{{else if (includes frontend \"nuxt\")}}\n\tclient: {},\n{{else}}\n\tclientPrefix: \"VITE_\",\n\tclient: {\n{{#if (eq auth \"clerk\")}}\n\t\tVITE_CLERK_PUBLISHABLE_KEY: z.string().min(1),\n{{/if}}\n\t},\n\truntimeEnv: (import.meta as any).env,\n{{/if}}\n{{else if (ne backend \"none\")}}\n{{#if (includes frontend \"next\")}}\n\tclient: {\n\t\tNEXT_PUBLIC_SERVER_URL: z.url(),\n{{#if (eq auth \"clerk\")}}\n\t\tNEXT_PUBLIC_CLERK_PUBLISHABLE_KEY: z.string().min(1),\n{{/if}}\n\t},\n\truntimeEnv: {\n\t\tNEXT_PUBLIC_SERVER_URL: process.env.NEXT_PUBLIC_SERVER_URL,\n{{#if (eq auth \"clerk\")}}\n\t\tNEXT_PUBLIC_CLERK_PUBLISHABLE_KEY: process.env.NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY,\n{{/if}}\n\t},\n{{else if (includes frontend \"nuxt\")}}\n\tclient: {\n\t\tNUXT_PUBLIC_SERVER_URL: z.url(),\n\t},\n{{else if (or (includes frontend \"svelte\") (includes frontend \"astro\"))}}\n\tclientPrefix: \"PUBLIC_\",\n\tclient: {\n\t\tPUBLIC_SERVER_URL: z.url(),\n\t},\n\truntimeEnv: (import.meta as any).env,\n{{else}}\n\tclientPrefix: \"VITE_\",\n\tclient: {\n\t\tVITE_SERVER_URL: z.url(),\n{{#if (eq auth \"clerk\")}}\n\t\tVITE_CLERK_PUBLISHABLE_KEY: z.string().min(1),\n{{/if}}\n\t},\n\truntimeEnv: (import.meta as any).env,\n{{/if}}\n{{/if}}\n\temptyStringAsUndefined: true,\n});\n`],\n  [\"packages/env/tsconfig.json.hbs\", `{\n  \"extends\": \"@{{projectName}}/config/tsconfig.base.json\",\n}\n`],\n  [\"packages/infra/alchemy.run.ts.hbs\", `import alchemy from \"alchemy\";\n{{#if (eq webDeploy \"cloudflare\")}}\n{{#if (includes frontend \"next\")}}\nimport { Nextjs } from \"alchemy/cloudflare\";\n{{else if (includes frontend \"nuxt\")}}\nimport { Nuxt } from \"alchemy/cloudflare\";\n{{else if (includes frontend \"svelte\")}}\nimport { SvelteKit } from \"alchemy/cloudflare\";\n{{else if (includes frontend \"tanstack-start\")}}\nimport { TanStackStart } from \"alchemy/cloudflare\";\n{{else if (includes frontend \"tanstack-router\")}}\nimport { Vite } from \"alchemy/cloudflare\";\n{{else if (includes frontend \"react-router\")}}\nimport { ReactRouter } from \"alchemy/cloudflare\";\n{{else if (includes frontend \"solid\")}}\nimport { Vite } from \"alchemy/cloudflare\";\n{{else if (includes frontend \"astro\")}}\nimport { Astro } from \"alchemy/cloudflare\";\n{{/if}}\n{{/if}}\n{{#if (eq serverDeploy \"cloudflare\")}}\nimport { Worker } from \"alchemy/cloudflare\";\n{{/if}}\n{{#if (and (or (eq serverDeploy \"cloudflare\") (and (eq webDeploy \"cloudflare\") (eq backend \"self\"))) (eq dbSetup \"d1\"))}}\nimport { D1Database } from \"alchemy/cloudflare\";\n{{/if}}\nimport { config } from \"dotenv\";\n\n{{#if (and (eq webDeploy \"cloudflare\") (eq serverDeploy \"cloudflare\"))}}\nconfig({ path: \"./.env\" });\nconfig({ path: \"../../apps/web/.env\" });\nconfig({ path: \"../../apps/server/.env\" });\n{{else if (eq webDeploy \"cloudflare\")}}\nconfig({ path: \"./.env\" });\nconfig({ path: \"../../apps/web/.env\" });\n{{else if (eq serverDeploy \"cloudflare\")}}\nconfig({ path: \"./.env\" });\nconfig({ path: \"../../apps/server/.env\" });\n{{/if}}\n\nconst app = await alchemy(\"{{projectName}}\");\n\n{{#if (and (or (eq serverDeploy \"cloudflare\") (and (eq webDeploy \"cloudflare\") (eq backend \"self\"))) (eq dbSetup \"d1\"))}}\nconst db = await D1Database(\"database\", {\n\t{{#if (eq orm \"prisma\")}}\n\tmigrationsDir: \"../../packages/db/prisma/migrations\",\n\t{{else if (eq orm \"drizzle\")}}\n\tmigrationsDir: \"../../packages/db/src/migrations\",\n\t{{/if}}\n});\n{{/if}}\n\n{{#if (eq webDeploy \"cloudflare\")}}\n{{#if (includes frontend \"next\")}}\nexport const web = await Nextjs(\"web\", {\n  cwd: \"../../apps/web\",\n  bindings: {\n    {{#if (eq backend \"convex\")}}\n    NEXT_PUBLIC_CONVEX_URL: alchemy.env.NEXT_PUBLIC_CONVEX_URL!,\n    {{#if (eq auth \"better-auth\")}}\n    NEXT_PUBLIC_CONVEX_SITE_URL: alchemy.env.NEXT_PUBLIC_CONVEX_SITE_URL!,\n    {{/if}}\n    {{else if (ne backend \"self\")}}\n    NEXT_PUBLIC_SERVER_URL: alchemy.env.NEXT_PUBLIC_SERVER_URL!,\n    {{/if}}\n    {{#if (eq dbSetup \"d1\")}}\n    DB: db,\n    {{else if (ne database \"none\")}}\n    DATABASE_URL: alchemy.secret.env.DATABASE_URL!,\n    {{/if}}\n    {{#if (ne backend \"convex\")}}\n    CORS_ORIGIN: alchemy.env.CORS_ORIGIN!,\n    {{#if (eq auth \"better-auth\")}}\n    BETTER_AUTH_SECRET: alchemy.secret.env.BETTER_AUTH_SECRET!,\n    BETTER_AUTH_URL: alchemy.env.BETTER_AUTH_URL!,\n    {{/if}}\n    {{/if}}\n    {{#if (eq auth \"clerk\")}}\n    CLERK_SECRET_KEY: alchemy.secret.env.CLERK_SECRET_KEY!,\n    {{#if (and (ne api \"none\") (or (eq backend \"self\") (eq backend \"hono\") (eq backend \"elysia\")))}}\n    CLERK_PUBLISHABLE_KEY: alchemy.env.CLERK_PUBLISHABLE_KEY!,\n    {{/if}}\n    {{/if}}\n    {{#if (and (includes examples \"ai\") (ne backend \"convex\"))}}\n    GOOGLE_GENERATIVE_AI_API_KEY: alchemy.secret.env.GOOGLE_GENERATIVE_AI_API_KEY!,\n    {{/if}}\n    {{#if (eq payments \"polar\")}}\n    POLAR_ACCESS_TOKEN: alchemy.secret.env.POLAR_ACCESS_TOKEN!,\n    POLAR_SUCCESS_URL: alchemy.env.POLAR_SUCCESS_URL!,\n    {{/if}}\n    {{#if (eq dbSetup \"turso\")}}\n    DATABASE_AUTH_TOKEN: alchemy.secret.env.DATABASE_AUTH_TOKEN!,\n    {{/if}}\n    {{#if (eq database \"mysql\")}}\n    {{#if (eq orm \"drizzle\")}}\n    DATABASE_HOST: alchemy.env.DATABASE_HOST!,\n    DATABASE_USERNAME: alchemy.env.DATABASE_USERNAME!,\n    DATABASE_PASSWORD: alchemy.secret.env.DATABASE_PASSWORD!,\n    {{/if}}\n    {{/if}}\n  },\n  dev: {\n    env: {\n      PORT: \"3001\",\n    },\n  },\n});\n{{else if (includes frontend \"nuxt\")}}\nexport const web = await Nuxt(\"web\", {\n  cwd: \"../../apps/web\",\n  bindings: {\n    {{#if (eq backend \"convex\")}}\n    NUXT_PUBLIC_CONVEX_URL: alchemy.env.NUXT_PUBLIC_CONVEX_URL!,\n    {{#if (eq auth \"better-auth\")}}\n    NUXT_PUBLIC_CONVEX_SITE_URL: alchemy.env.NUXT_PUBLIC_CONVEX_SITE_URL!,\n    {{/if}}\n    {{else if (ne backend \"self\")}}\n    NUXT_PUBLIC_SERVER_URL: alchemy.env.NUXT_PUBLIC_SERVER_URL!,\n    {{/if}}\n    {{#if (eq backend \"self\")}}\n    {{#if (eq dbSetup \"d1\")}}\n    DB: db,\n    {{else if (ne database \"none\")}}\n    DATABASE_URL: alchemy.secret.env.DATABASE_URL!,\n    {{/if}}\n    {{#if (ne backend \"convex\")}}\n    CORS_ORIGIN: alchemy.env.CORS_ORIGIN!,\n    {{#if (eq auth \"better-auth\")}}\n    BETTER_AUTH_SECRET: alchemy.secret.env.BETTER_AUTH_SECRET!,\n    BETTER_AUTH_URL: alchemy.env.BETTER_AUTH_URL!,\n    {{/if}}\n    {{/if}}\n    {{#if (eq auth \"clerk\")}}\n    CLERK_SECRET_KEY: alchemy.secret.env.CLERK_SECRET_KEY!,\n    {{#if (and (ne api \"none\") (or (eq backend \"self\") (eq backend \"hono\") (eq backend \"elysia\")))}}\n    CLERK_PUBLISHABLE_KEY: alchemy.env.CLERK_PUBLISHABLE_KEY!,\n    {{/if}}\n    {{/if}}\n    {{#if (and (includes examples \"ai\") (ne backend \"convex\"))}}\n    GOOGLE_GENERATIVE_AI_API_KEY: alchemy.secret.env.GOOGLE_GENERATIVE_AI_API_KEY!,\n    {{/if}}\n    {{#if (eq payments \"polar\")}}\n    POLAR_ACCESS_TOKEN: alchemy.secret.env.POLAR_ACCESS_TOKEN!,\n    POLAR_SUCCESS_URL: alchemy.env.POLAR_SUCCESS_URL!,\n    {{/if}}\n    {{#if (eq dbSetup \"turso\")}}\n    DATABASE_AUTH_TOKEN: alchemy.secret.env.DATABASE_AUTH_TOKEN!,\n    {{/if}}\n    {{#if (eq database \"mysql\")}}\n    {{#if (eq orm \"drizzle\")}}\n    DATABASE_HOST: alchemy.env.DATABASE_HOST!,\n    DATABASE_USERNAME: alchemy.env.DATABASE_USERNAME!,\n    DATABASE_PASSWORD: alchemy.secret.env.DATABASE_PASSWORD!,\n    {{/if}}\n    {{/if}}\n    {{/if}}\n  }\n});\n{{else if (includes frontend \"svelte\")}}\nexport const web = await SvelteKit(\"web\", {\n  cwd: \"../../apps/web\",\n  bindings: {\n    {{#if (eq backend \"convex\")}}\n    PUBLIC_CONVEX_URL: alchemy.env.PUBLIC_CONVEX_URL!,\n    {{#if (eq auth \"better-auth\")}}\n    PUBLIC_CONVEX_SITE_URL: alchemy.env.PUBLIC_CONVEX_SITE_URL!,\n    {{/if}}\n    {{else if (ne backend \"self\")}}\n    PUBLIC_SERVER_URL: alchemy.env.PUBLIC_SERVER_URL!,\n    {{/if}}\n    {{#if (eq backend \"self\")}}\n    {{#if (eq dbSetup \"d1\")}}\n    DB: db,\n    {{else if (ne database \"none\")}}\n    DATABASE_URL: alchemy.secret.env.DATABASE_URL!,\n    {{/if}}\n    CORS_ORIGIN: alchemy.env.CORS_ORIGIN!,\n    {{#if (eq auth \"better-auth\")}}\n    BETTER_AUTH_SECRET: alchemy.secret.env.BETTER_AUTH_SECRET!,\n    BETTER_AUTH_URL: alchemy.env.BETTER_AUTH_URL!,\n    {{/if}}\n    {{#if (and (includes examples \"ai\") (ne backend \"convex\"))}}\n    GOOGLE_GENERATIVE_AI_API_KEY: alchemy.secret.env.GOOGLE_GENERATIVE_AI_API_KEY!,\n    {{/if}}\n    {{#if (eq payments \"polar\")}}\n    POLAR_ACCESS_TOKEN: alchemy.secret.env.POLAR_ACCESS_TOKEN!,\n    POLAR_SUCCESS_URL: alchemy.env.POLAR_SUCCESS_URL!,\n    {{/if}}\n    {{#if (eq dbSetup \"turso\")}}\n    DATABASE_AUTH_TOKEN: alchemy.secret.env.DATABASE_AUTH_TOKEN!,\n    {{/if}}\n    {{#if (eq database \"mysql\")}}\n    {{#if (eq orm \"drizzle\")}}\n    DATABASE_HOST: alchemy.env.DATABASE_HOST!,\n    DATABASE_USERNAME: alchemy.env.DATABASE_USERNAME!,\n    DATABASE_PASSWORD: alchemy.secret.env.DATABASE_PASSWORD!,\n    {{/if}}\n    {{/if}}\n    {{/if}}\n  },\n  dev: {\n    domain: \"localhost:5173\",\n  },\n});\n{{else if (includes frontend \"tanstack-start\")}}\nexport const web = await TanStackStart(\"web\", {\n  cwd: \"../../apps/web\",\n  bindings: {\n    {{#if (eq backend \"convex\")}}\n    VITE_CONVEX_URL: alchemy.env.VITE_CONVEX_URL!,\n    {{#if (eq auth \"better-auth\")}}\n    VITE_CONVEX_SITE_URL: alchemy.env.VITE_CONVEX_SITE_URL!,\n    {{/if}}\n    {{else if (ne backend \"self\")}}\n    VITE_SERVER_URL: alchemy.env.VITE_SERVER_URL!,\n    {{/if}}\n    {{#if (eq dbSetup \"d1\")}}\n    DB: db,\n    {{else if (ne database \"none\")}}\n    DATABASE_URL: alchemy.secret.env.DATABASE_URL!,\n    {{/if}}\n    {{#if (ne backend \"convex\")}}\n    CORS_ORIGIN: alchemy.env.CORS_ORIGIN!,\n    {{#if (eq auth \"better-auth\")}}\n    BETTER_AUTH_SECRET: alchemy.secret.env.BETTER_AUTH_SECRET!,\n    BETTER_AUTH_URL: alchemy.env.BETTER_AUTH_URL!,\n    {{/if}}\n    {{/if}}\n    {{#if (eq auth \"clerk\")}}\n    CLERK_SECRET_KEY: alchemy.secret.env.CLERK_SECRET_KEY!,\n    {{#if (and (ne api \"none\") (or (eq backend \"self\") (eq backend \"hono\") (eq backend \"elysia\")))}}\n    CLERK_PUBLISHABLE_KEY: alchemy.env.CLERK_PUBLISHABLE_KEY!,\n    {{/if}}\n    {{/if}}\n    {{#if (and (includes examples \"ai\") (ne backend \"convex\"))}}\n    GOOGLE_GENERATIVE_AI_API_KEY: alchemy.secret.env.GOOGLE_GENERATIVE_AI_API_KEY!,\n    {{/if}}\n    {{#if (eq payments \"polar\")}}\n    POLAR_ACCESS_TOKEN: alchemy.secret.env.POLAR_ACCESS_TOKEN!,\n    POLAR_SUCCESS_URL: alchemy.env.POLAR_SUCCESS_URL!,\n    {{/if}}\n    {{#if (eq dbSetup \"turso\")}}\n    DATABASE_AUTH_TOKEN: alchemy.secret.env.DATABASE_AUTH_TOKEN!,\n    {{/if}}\n    {{#if (eq database \"mysql\")}}\n    {{#if (eq orm \"drizzle\")}}\n    DATABASE_HOST: alchemy.env.DATABASE_HOST!,\n    DATABASE_USERNAME: alchemy.env.DATABASE_USERNAME!,\n    DATABASE_PASSWORD: alchemy.secret.env.DATABASE_PASSWORD!,\n    {{/if}}\n    {{/if}}\n  }\n});\n{{else if (includes frontend \"tanstack-router\")}}\nexport const web = await Vite(\"web\", {\n  cwd: \"../../apps/web\",\n  assets: \"dist\",\n  bindings: {\n    {{#if (eq backend \"convex\")}}\n    VITE_CONVEX_URL: alchemy.env.VITE_CONVEX_URL!,\n    {{#if (eq auth \"better-auth\")}}\n    VITE_CONVEX_SITE_URL: alchemy.env.VITE_CONVEX_SITE_URL!,\n    {{/if}}\n    {{else if (ne backend \"self\")}}\n    VITE_SERVER_URL: alchemy.env.VITE_SERVER_URL!,\n    {{/if}}\n  }\n});\n{{else if (includes frontend \"react-router\")}}\nexport const web = await ReactRouter(\"web\", {\n  cwd: \"../../apps/web\",\n  bindings: {\n    {{#if (eq backend \"convex\")}}\n    VITE_CONVEX_URL: alchemy.env.VITE_CONVEX_URL!,\n    {{#if (eq auth \"better-auth\")}}\n    VITE_CONVEX_SITE_URL: alchemy.env.VITE_CONVEX_SITE_URL!,\n    {{/if}}\n    {{else if (ne backend \"self\")}}\n    VITE_SERVER_URL: alchemy.env.VITE_SERVER_URL!,\n    {{/if}}\n  }\n});\n{{else if (includes frontend \"solid\")}}\nexport const web = await Vite(\"web\", {\n  cwd: \"../../apps/web\",\n  assets: \"dist\",\n  bindings: {\n    {{#if (eq backend \"convex\")}}\n    VITE_CONVEX_URL: alchemy.env.VITE_CONVEX_URL!,\n    {{#if (eq auth \"better-auth\")}}\n    VITE_CONVEX_SITE_URL: alchemy.env.VITE_CONVEX_SITE_URL!,\n    {{/if}}\n    {{else if (ne backend \"self\")}}\n    VITE_SERVER_URL: alchemy.env.VITE_SERVER_URL!,\n    {{/if}}\n  }\n});\n{{else if (includes frontend \"astro\")}}\nexport const web = await Astro(\"web\", {\n  cwd: \"../../apps/web\",\n  entrypoint: \"dist/server/entry.mjs\",\n  assets: \"dist/client\",\n  {{#if (eq backend \"self\")}}\n  compatibility: \"node\",\n  {{/if}}\n  bindings: {\n    {{#if (ne backend \"self\")}}\n    PUBLIC_SERVER_URL: alchemy.env.PUBLIC_SERVER_URL!,\n    {{/if}}\n    {{#if (eq backend \"self\")}}\n    {{#if (eq dbSetup \"d1\")}}\n    DB: db,\n    {{else if (ne database \"none\")}}\n    DATABASE_URL: alchemy.secret.env.DATABASE_URL!,\n    {{/if}}\n    CORS_ORIGIN: alchemy.env.CORS_ORIGIN!,\n    {{#if (eq auth \"better-auth\")}}\n    BETTER_AUTH_SECRET: alchemy.secret.env.BETTER_AUTH_SECRET!,\n    BETTER_AUTH_URL: alchemy.env.BETTER_AUTH_URL!,\n    {{/if}}\n    {{#if (eq payments \"polar\")}}\n    POLAR_ACCESS_TOKEN: alchemy.secret.env.POLAR_ACCESS_TOKEN!,\n    POLAR_SUCCESS_URL: alchemy.env.POLAR_SUCCESS_URL!,\n    {{/if}}\n    {{#if (eq dbSetup \"turso\")}}\n    DATABASE_AUTH_TOKEN: alchemy.secret.env.DATABASE_AUTH_TOKEN!,\n    {{/if}}\n    {{#if (eq database \"mysql\")}}\n    {{#if (eq orm \"drizzle\")}}\n    DATABASE_HOST: alchemy.env.DATABASE_HOST!,\n    DATABASE_USERNAME: alchemy.env.DATABASE_USERNAME!,\n    DATABASE_PASSWORD: alchemy.secret.env.DATABASE_PASSWORD!,\n    {{/if}}\n    {{/if}}\n    {{/if}}\n  }\n});\n{{/if}}\n{{/if}}\n\n{{#if (eq serverDeploy \"cloudflare\")}}\nexport const server = await Worker(\"server\", {\n  cwd: \"../../apps/server\",\n  entrypoint: \"src/index.ts\",\n  compatibility: \"node\",\n  bindings: {\n    {{#if (eq dbSetup \"d1\")}}\n    DB: db,\n    {{else if (ne database \"none\")}}\n    DATABASE_URL: alchemy.secret.env.DATABASE_URL!,\n    {{/if}}\n    CORS_ORIGIN: alchemy.env.CORS_ORIGIN!,\n    {{#if (eq auth \"better-auth\")}}\n    BETTER_AUTH_SECRET: alchemy.secret.env.BETTER_AUTH_SECRET!,\n    BETTER_AUTH_URL: alchemy.env.BETTER_AUTH_URL!,\n    {{/if}}\n    {{#if (eq auth \"clerk\")}}\n    CLERK_SECRET_KEY: alchemy.secret.env.CLERK_SECRET_KEY!,\n    {{#if (and (ne api \"none\") (or (eq backend \"self\") (eq backend \"hono\") (eq backend \"elysia\")))}}\n    CLERK_PUBLISHABLE_KEY: alchemy.env.CLERK_PUBLISHABLE_KEY!,\n    {{/if}}\n    {{/if}}\n    {{#if (includes examples \"ai\")}}\n    GOOGLE_GENERATIVE_AI_API_KEY: alchemy.secret.env.GOOGLE_GENERATIVE_AI_API_KEY!,\n    {{/if}}\n    {{#if (eq payments \"polar\")}}\n    POLAR_ACCESS_TOKEN: alchemy.secret.env.POLAR_ACCESS_TOKEN!,\n    POLAR_SUCCESS_URL: alchemy.env.POLAR_SUCCESS_URL!,\n    {{/if}}\n    {{#if (eq dbSetup \"turso\")}}\n    DATABASE_AUTH_TOKEN: alchemy.secret.env.DATABASE_AUTH_TOKEN!,\n    {{/if}}\n    {{#if (eq database \"mysql\")}}\n    {{#if (eq orm \"drizzle\")}}\n    DATABASE_HOST: alchemy.env.DATABASE_HOST!,\n    DATABASE_USERNAME: alchemy.env.DATABASE_USERNAME!,\n    DATABASE_PASSWORD: alchemy.secret.env.DATABASE_PASSWORD!,\n    {{/if}}\n    {{/if}}\n  },\n  dev: {\n\t\tport: 3000,\n\t},\n});\n{{/if}}\n\n{{#if (and (eq webDeploy \"cloudflare\") (eq serverDeploy \"cloudflare\"))}}\nconsole.log(\\`Web    -> \\${web.url}\\`);\nconsole.log(\\`Server -> \\${server.url}\\`);\n{{else if (eq webDeploy \"cloudflare\")}}\nconsole.log(\\`Web    -> \\${web.url}\\`);\n{{else if (eq serverDeploy \"cloudflare\")}}\nconsole.log(\\`Server -> \\${server.url}\\`);\n{{/if}}\n\nawait app.finalize();\n`],\n  [\"packages/infra/package.json.hbs\", `{\n  \"name\": \"@{{projectName}}/infra\",\n  \"private\": true,\n  \"type\": \"module\",\n  \"scripts\": {\n    \"dev\": \"alchemy dev\",\n    \"deploy\": \"alchemy deploy\",\n    \"destroy\": \"alchemy destroy\"\n  }\n}\n`],\n  [\"packages/ui/components.json.hbs\", `{\n  \"$schema\": \"https://ui.shadcn.com/schema.json\",\n  \"style\": \"base-lyra\",\n  \"rsc\": {{#if (includes frontend \"next\")}}true{{else}}false{{/if}},\n  \"tsx\": true,\n  \"tailwind\": {\n    \"config\": \"\",\n    \"css\": \"src/styles/globals.css\",\n    \"baseColor\": \"neutral\",\n    \"cssVariables\": true,\n    \"prefix\": \"\"\n  },\n  \"iconLibrary\": \"lucide\",\n  \"aliases\": {\n    \"components\": \"@{{projectName}}/ui/components\",\n    \"utils\": \"@{{projectName}}/ui/lib/utils\",\n    \"hooks\": \"@{{projectName}}/ui/hooks\",\n    \"lib\": \"@{{projectName}}/ui/lib\",\n    \"ui\": \"@{{projectName}}/ui/components\"\n  },\n  \"menuColor\": \"default\",\n  \"menuAccent\": \"subtle\",\n  \"registries\": {}\n}\n`],\n  [\"packages/ui/package.json.hbs\", `{\n  \"name\": \"@{{projectName}}/ui\",\n  \"version\": \"0.0.0\",\n  \"private\": true,\n  \"type\": \"module\",\n  \"exports\": {\n    \"./globals.css\": \"./src/styles/globals.css\",\n    \"./lib/*\": \"./src/lib/*.ts\",\n    \"./components/*\": \"./src/components/*.tsx\",\n    \"./hooks/*\": \"./src/hooks/*.ts\",\n    \"./postcss.config\": \"./postcss.config.mjs\"\n  },\n  \"dependencies\": {\n    \"@base-ui/react\": \"^1.0.0\",\n    \"shadcn\": \"^3.6.2\",\n    \"class-variance-authority\": \"^0.7.1\",\n    \"clsx\": \"^2.1.1\",\n    \"lucide-react\": \"^0.546.0\",\n    \"next-themes\": \"^0.4.6\",\n    \"react\": \"^19.2.3\",\n    \"react-dom\": \"^19.2.3\",\n    \"sonner\": \"^2.0.5\",\n    \"tailwind-merge\": \"^3.3.1\",\n    \"tw-animate-css\": \"^1.3.4\"\n  },\n  \"devDependencies\": {\n    \"@types/react\": \"^19.2.10\",\n    \"@types/react-dom\": \"^19.2.3\",\n    \"tailwindcss\": \"^4.1.18\"\n  },\n  \"scripts\": {\n    \"check-types\": \"tsc --noEmit\"\n  }\n}\n`],\n  [\"packages/ui/postcss.config.mjs.hbs\", `export default {\n  plugins: {\n    \"@tailwindcss/postcss\": {},\n  },\n};\n`],\n  [\"packages/ui/src/components/button.tsx.hbs\", `import { Button as ButtonPrimitive } from \"@base-ui/react/button\"\nimport { cva, type VariantProps } from \"class-variance-authority\"\n\nimport { cn } from \"@{{projectName}}/ui/lib/utils\"\n\nconst buttonVariants = cva(\n  \"group/button inline-flex shrink-0 items-center justify-center rounded-none border border-transparent bg-clip-padding text-xs font-medium whitespace-nowrap transition-all outline-none select-none focus-visible:border-ring focus-visible:ring-1 focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-1 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4\",\n  {\n    variants: {\n      variant: {\n        default: \"bg-primary text-primary-foreground [a]:hover:bg-primary/80\",\n        outline:\n          \"border-border bg-background hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50\",\n        secondary:\n          \"bg-secondary text-secondary-foreground hover:bg-secondary/80 aria-expanded:bg-secondary aria-expanded:text-secondary-foreground\",\n        ghost:\n          \"hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:hover:bg-muted/50\",\n        destructive:\n          \"bg-destructive/10 text-destructive hover:bg-destructive/20 focus-visible:border-destructive/40 focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:hover:bg-destructive/30 dark:focus-visible:ring-destructive/40\",\n        link: \"text-primary underline-offset-4 hover:underline\",\n      },\n      size: {\n        default:\n          \"h-8 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2\",\n        xs: \"h-6 gap-1 rounded-none px-2 text-xs has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3\",\n        sm: \"h-7 gap-1 rounded-none px-2.5 has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3.5\",\n        lg: \"h-9 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-3 has-data-[icon=inline-start]:pl-3\",\n        icon: \"size-8\",\n        \"icon-xs\": \"size-6 rounded-none [&_svg:not([class*='size-'])]:size-3\",\n        \"icon-sm\": \"size-7 rounded-none\",\n        \"icon-lg\": \"size-9\",\n      },\n    },\n    defaultVariants: {\n      variant: \"default\",\n      size: \"default\",\n    },\n  }\n)\n\nfunction Button({\n  className,\n  variant = \"default\",\n  size = \"default\",\n  ...props\n}: ButtonPrimitive.Props & VariantProps<typeof buttonVariants>) {\n  return (\n    <ButtonPrimitive\n      data-slot=\"button\"\n      className={cn(buttonVariants({ variant, size, className }))}\n      {...props}\n    />\n  )\n}\n\nexport { Button, buttonVariants }\n`],\n  [\"packages/ui/src/components/card.tsx.hbs\", `import * as React from \"react\"\n\nimport { cn } from \"@{{projectName}}/ui/lib/utils\"\n\nfunction Card({\n  className,\n  size = \"default\",\n  ...props\n}: React.ComponentProps<\"div\"> & { size?: \"default\" | \"sm\" }) {\n  return (\n    <div\n      data-slot=\"card\"\n      data-size={size}\n      className={cn(\n        \"group/card flex flex-col gap-4 overflow-hidden rounded-none bg-card py-4 text-xs/relaxed text-card-foreground ring-1 ring-foreground/10 has-data-[slot=card-footer]:pb-0 has-[>img:first-child]:pt-0 data-[size=sm]:gap-2 data-[size=sm]:py-3 data-[size=sm]:has-data-[slot=card-footer]:pb-0 *:[img:first-child]:rounded-none *:[img:last-child]:rounded-none\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction CardHeader({ className, ...props }: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"card-header\"\n      className={cn(\n        \"group/card-header @container/card-header grid auto-rows-min items-start gap-1 rounded-none px-4 group-data-[size=sm]/card:px-3 has-data-[slot=card-action]:grid-cols-[1fr_auto] has-data-[slot=card-description]:grid-rows-[auto_auto] [.border-b]:pb-4 group-data-[size=sm]/card:[.border-b]:pb-3\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction CardTitle({ className, ...props }: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"card-title\"\n      className={cn(\n        \"text-sm font-medium group-data-[size=sm]/card:text-sm\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction CardDescription({ className, ...props }: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"card-description\"\n      className={cn(\"text-xs/relaxed text-muted-foreground\", className)}\n      {...props}\n    />\n  )\n}\n\nfunction CardAction({ className, ...props }: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"card-action\"\n      className={cn(\n        \"col-start-2 row-span-2 row-start-1 self-start justify-self-end\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction CardContent({ className, ...props }: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"card-content\"\n      className={cn(\"px-4 group-data-[size=sm]/card:px-3\", className)}\n      {...props}\n    />\n  )\n}\n\nfunction CardFooter({ className, ...props }: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"card-footer\"\n      className={cn(\n        \"flex items-center rounded-none border-t p-4 group-data-[size=sm]/card:p-3\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nexport {\n  Card,\n  CardHeader,\n  CardFooter,\n  CardTitle,\n  CardAction,\n  CardDescription,\n  CardContent,\n}\n`],\n  [\"packages/ui/src/components/checkbox.tsx.hbs\", `\"use client\"\n\nimport { Checkbox as CheckboxPrimitive } from \"@base-ui/react/checkbox\"\n\nimport { cn } from \"@{{projectName}}/ui/lib/utils\"\nimport { CheckIcon } from \"lucide-react\"\n\nfunction Checkbox({ className, ...props }: CheckboxPrimitive.Root.Props) {\n  return (\n    <CheckboxPrimitive.Root\n      data-slot=\"checkbox\"\n      className={cn(\n        \"peer relative flex size-4 shrink-0 items-center justify-center rounded-none border border-input transition-colors outline-none group-has-disabled/field:opacity-50 after:absolute after:-inset-x-3 after:-inset-y-2 focus-visible:border-ring focus-visible:ring-1 focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-1 aria-invalid:ring-destructive/20 aria-invalid:aria-checked:border-primary dark:bg-input/30 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 data-checked:border-primary data-checked:bg-primary data-checked:text-primary-foreground dark:data-checked:bg-primary\",\n        className\n      )}\n      {...props}\n    >\n      <CheckboxPrimitive.Indicator\n        data-slot=\"checkbox-indicator\"\n        className=\"grid place-content-center text-current transition-none [&>svg]:size-3.5\"\n      >\n        <CheckIcon />\n      </CheckboxPrimitive.Indicator>\n    </CheckboxPrimitive.Root>\n  )\n}\n\nexport { Checkbox }\n`],\n  [\"packages/ui/src/components/dropdown-menu.tsx.hbs\", `\"use client\"\n\nimport * as React from \"react\"\nimport { Menu as MenuPrimitive } from \"@base-ui/react/menu\"\n\nimport { cn } from \"@{{projectName}}/ui/lib/utils\"\nimport { ChevronRightIcon, CheckIcon } from \"lucide-react\"\n\nfunction DropdownMenu({ ...props }: MenuPrimitive.Root.Props) {\n  return <MenuPrimitive.Root data-slot=\"dropdown-menu\" {...props} />\n}\n\nfunction DropdownMenuPortal({ ...props }: MenuPrimitive.Portal.Props) {\n  return <MenuPrimitive.Portal data-slot=\"dropdown-menu-portal\" {...props} />\n}\n\nfunction DropdownMenuTrigger({ ...props }: MenuPrimitive.Trigger.Props) {\n  return <MenuPrimitive.Trigger data-slot=\"dropdown-menu-trigger\" {...props} />\n}\n\nfunction DropdownMenuContent({\n  align = \"start\",\n  alignOffset = 0,\n  side = \"bottom\",\n  sideOffset = 4,\n  className,\n  ...props\n}: MenuPrimitive.Popup.Props &\n  Pick<\n    MenuPrimitive.Positioner.Props,\n    \"align\" | \"alignOffset\" | \"side\" | \"sideOffset\"\n  >) {\n  return (\n    <MenuPrimitive.Portal>\n      <MenuPrimitive.Positioner\n        className=\"isolate z-50 outline-none\"\n        align={align}\n        alignOffset={alignOffset}\n        side={side}\n        sideOffset={sideOffset}\n      >\n        <MenuPrimitive.Popup\n          data-slot=\"dropdown-menu-content\"\n          className={cn(\n            \"z-50 max-h-(--available-height) w-(--anchor-width) min-w-32 origin-(--transform-origin) overflow-x-hidden overflow-y-auto rounded-none bg-popover text-popover-foreground shadow-md ring-1 ring-foreground/10 duration-100 outline-none data-[side=bottom]:slide-in-from-top-2 data-[side=inline-end]:slide-in-from-left-2 data-[side=inline-start]:slide-in-from-right-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:overflow-hidden data-closed:fade-out-0 data-closed:zoom-out-95\",\n            className\n          )}\n          {...props}\n        />\n      </MenuPrimitive.Positioner>\n    </MenuPrimitive.Portal>\n  )\n}\n\nfunction DropdownMenuGroup({ ...props }: MenuPrimitive.Group.Props) {\n  return <MenuPrimitive.Group data-slot=\"dropdown-menu-group\" {...props} />\n}\n\nfunction DropdownMenuLabel({\n  className,\n  inset,\n  ...props\n}: MenuPrimitive.GroupLabel.Props & {\n  inset?: boolean\n}) {\n  return (\n    <MenuPrimitive.GroupLabel\n      data-slot=\"dropdown-menu-label\"\n      data-inset={inset}\n      className={cn(\n        \"px-2 py-2 text-xs text-muted-foreground data-inset:pl-7\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction DropdownMenuItem({\n  className,\n  inset,\n  variant = \"default\",\n  ...props\n}: MenuPrimitive.Item.Props & {\n  inset?: boolean\n  variant?: \"default\" | \"destructive\"\n}) {\n  return (\n    <MenuPrimitive.Item\n      data-slot=\"dropdown-menu-item\"\n      data-inset={inset}\n      data-variant={variant}\n      className={cn(\n        \"group/dropdown-menu-item relative flex cursor-default items-center gap-2 rounded-none px-2 py-2 text-xs outline-hidden select-none focus:bg-accent focus:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground data-inset:pl-7 data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 data-[variant=destructive]:focus:text-destructive dark:data-[variant=destructive]:focus:bg-destructive/20 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 data-[variant=destructive]:*:[svg]:text-destructive\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction DropdownMenuSub({ ...props }: MenuPrimitive.SubmenuRoot.Props) {\n  return <MenuPrimitive.SubmenuRoot data-slot=\"dropdown-menu-sub\" {...props} />\n}\n\nfunction DropdownMenuSubTrigger({\n  className,\n  inset,\n  children,\n  ...props\n}: MenuPrimitive.SubmenuTrigger.Props & {\n  inset?: boolean\n}) {\n  return (\n    <MenuPrimitive.SubmenuTrigger\n      data-slot=\"dropdown-menu-sub-trigger\"\n      data-inset={inset}\n      className={cn(\n        \"flex cursor-default items-center gap-2 rounded-none px-2 py-2 text-xs outline-hidden select-none focus:bg-accent focus:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground data-inset:pl-7 data-popup-open:bg-accent data-popup-open:text-accent-foreground data-open:bg-accent data-open:text-accent-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4\",\n        className\n      )}\n      {...props}\n    >\n      {children}\n      <ChevronRightIcon className=\"ml-auto\" />\n    </MenuPrimitive.SubmenuTrigger>\n  )\n}\n\nfunction DropdownMenuSubContent({\n  align = \"start\",\n  alignOffset = -3,\n  side = \"right\",\n  sideOffset = 0,\n  className,\n  ...props\n}: React.ComponentProps<typeof DropdownMenuContent>) {\n  return (\n    <DropdownMenuContent\n      data-slot=\"dropdown-menu-sub-content\"\n      className={cn(\n        \"w-auto min-w-[96px] rounded-none bg-popover text-popover-foreground shadow-lg ring-1 ring-foreground/10 duration-100 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95\",\n        className\n      )}\n      align={align}\n      alignOffset={alignOffset}\n      side={side}\n      sideOffset={sideOffset}\n      {...props}\n    />\n  )\n}\n\nfunction DropdownMenuCheckboxItem({\n  className,\n  children,\n  checked,\n  inset,\n  ...props\n}: MenuPrimitive.CheckboxItem.Props & {\n  inset?: boolean\n}) {\n  return (\n    <MenuPrimitive.CheckboxItem\n      data-slot=\"dropdown-menu-checkbox-item\"\n      data-inset={inset}\n      className={cn(\n        \"relative flex cursor-default items-center gap-2 rounded-none py-2 pr-8 pl-2 text-xs outline-hidden select-none focus:bg-accent focus:text-accent-foreground focus:**:text-accent-foreground data-inset:pl-7 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4\",\n        className\n      )}\n      checked={checked}\n      {...props}\n    >\n      <span\n        className=\"pointer-events-none absolute right-2 flex items-center justify-center\"\n        data-slot=\"dropdown-menu-checkbox-item-indicator\"\n      >\n        <MenuPrimitive.CheckboxItemIndicator>\n          <CheckIcon />\n        </MenuPrimitive.CheckboxItemIndicator>\n      </span>\n      {children}\n    </MenuPrimitive.CheckboxItem>\n  )\n}\n\nfunction DropdownMenuRadioGroup({ ...props }: MenuPrimitive.RadioGroup.Props) {\n  return (\n    <MenuPrimitive.RadioGroup\n      data-slot=\"dropdown-menu-radio-group\"\n      {...props}\n    />\n  )\n}\n\nfunction DropdownMenuRadioItem({\n  className,\n  children,\n  inset,\n  ...props\n}: MenuPrimitive.RadioItem.Props & {\n  inset?: boolean\n}) {\n  return (\n    <MenuPrimitive.RadioItem\n      data-slot=\"dropdown-menu-radio-item\"\n      data-inset={inset}\n      className={cn(\n        \"relative flex cursor-default items-center gap-2 rounded-none py-2 pr-8 pl-2 text-xs outline-hidden select-none focus:bg-accent focus:text-accent-foreground focus:**:text-accent-foreground data-inset:pl-7 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4\",\n        className\n      )}\n      {...props}\n    >\n      <span\n        className=\"pointer-events-none absolute right-2 flex items-center justify-center\"\n        data-slot=\"dropdown-menu-radio-item-indicator\"\n      >\n        <MenuPrimitive.RadioItemIndicator>\n          <CheckIcon />\n        </MenuPrimitive.RadioItemIndicator>\n      </span>\n      {children}\n    </MenuPrimitive.RadioItem>\n  )\n}\n\nfunction DropdownMenuSeparator({\n  className,\n  ...props\n}: MenuPrimitive.Separator.Props) {\n  return (\n    <MenuPrimitive.Separator\n      data-slot=\"dropdown-menu-separator\"\n      className={cn(\"-mx-1 h-px bg-border\", className)}\n      {...props}\n    />\n  )\n}\n\nfunction DropdownMenuShortcut({\n  className,\n  ...props\n}: React.ComponentProps<\"span\">) {\n  return (\n    <span\n      data-slot=\"dropdown-menu-shortcut\"\n      className={cn(\n        \"ml-auto text-xs tracking-widest text-muted-foreground group-focus/dropdown-menu-item:text-accent-foreground\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nexport {\n  DropdownMenu,\n  DropdownMenuPortal,\n  DropdownMenuTrigger,\n  DropdownMenuContent,\n  DropdownMenuGroup,\n  DropdownMenuLabel,\n  DropdownMenuItem,\n  DropdownMenuCheckboxItem,\n  DropdownMenuRadioGroup,\n  DropdownMenuRadioItem,\n  DropdownMenuSeparator,\n  DropdownMenuShortcut,\n  DropdownMenuSub,\n  DropdownMenuSubTrigger,\n  DropdownMenuSubContent,\n}\n`],\n  [\"packages/ui/src/components/input.tsx.hbs\", `import * as React from \"react\"\nimport { Input as InputPrimitive } from \"@base-ui/react/input\"\n\nimport { cn } from \"@{{projectName}}/ui/lib/utils\"\n\nfunction Input({ className, type, ...props }: React.ComponentProps<\"input\">) {\n  return (\n    <InputPrimitive\n      type={type}\n      data-slot=\"input\"\n      className={cn(\n        \"h-8 w-full min-w-0 rounded-none border border-input bg-transparent px-2.5 py-1 text-xs transition-colors outline-none file:inline-flex file:h-6 file:border-0 file:bg-transparent file:text-xs file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-1 focus-visible:ring-ring/50 disabled:pointer-events-none disabled:cursor-not-allowed disabled:bg-input/50 disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-1 aria-invalid:ring-destructive/20 md:text-xs dark:bg-input/30 dark:disabled:bg-input/80 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nexport { Input }\n`],\n  [\"packages/ui/src/components/label.tsx.hbs\", `import * as React from \"react\"\n\nimport { cn } from \"@{{projectName}}/ui/lib/utils\"\n\nfunction Label({ className, ...props }: React.ComponentProps<\"label\">) {\n  return (\n    <label\n      data-slot=\"label\"\n      className={cn(\n        \"flex items-center gap-2 text-xs leading-none select-none group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50 peer-disabled:cursor-not-allowed peer-disabled:opacity-50\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nexport { Label }\n`],\n  [\"packages/ui/src/components/skeleton.tsx.hbs\", `import { cn } from \"@{{projectName}}/ui/lib/utils\"\n\nfunction Skeleton({ className, ...props }: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"skeleton\"\n      className={cn(\"animate-pulse rounded-none bg-muted\", className)}\n      {...props}\n    />\n  )\n}\n\nexport { Skeleton }\n`],\n  [\"packages/ui/src/components/sonner.tsx.hbs\", `\"use client\"\n\nimport { useTheme } from \"next-themes\"\nimport { Toaster as Sonner, type ToasterProps } from \"sonner\"\nimport { CircleCheckIcon, InfoIcon, TriangleAlertIcon, OctagonXIcon, Loader2Icon } from \"lucide-react\"\n\nconst Toaster = ({ ...props }: ToasterProps) => {\n  const { theme = \"system\" } = useTheme()\n\n  return (\n    <Sonner\n      theme={theme as ToasterProps[\"theme\"]}\n      className=\"toaster group\"\n      icons=\\\\{{\n        success: (\n          <CircleCheckIcon className=\"size-4\" />\n        ),\n        info: (\n          <InfoIcon className=\"size-4\" />\n        ),\n        warning: (\n          <TriangleAlertIcon className=\"size-4\" />\n        ),\n        error: (\n          <OctagonXIcon className=\"size-4\" />\n        ),\n        loading: (\n          <Loader2Icon className=\"size-4 animate-spin\" />\n        ),\n      }}\n      style={\n        {\n          \"--normal-bg\": \"var(--popover)\",\n          \"--normal-text\": \"var(--popover-foreground)\",\n          \"--normal-border\": \"var(--border)\",\n          \"--border-radius\": \"var(--radius)\",\n        } as React.CSSProperties\n      }\n      toastOptions=\\\\{{\n        classNames: {\n          toast: \"cn-toast\",\n        },\n      }}\n      {...props}\n    />\n  )\n}\n\nexport { Toaster }\n`],\n  [\"packages/ui/src/hooks/.gitkeep\", ``],\n  [\"packages/ui/src/lib/utils.ts.hbs\", `import { clsx, type ClassValue } from \"clsx\";\nimport { twMerge } from \"tailwind-merge\";\n\nexport function cn(...inputs: ClassValue[]) {\n  return twMerge(clsx(inputs));\n}\n`],\n  [\"packages/ui/src/styles/globals.css.hbs\", `@import 'tailwindcss';\n@import 'tw-animate-css';\n@import 'shadcn/tailwind.css';\n@source \"../../../apps/**/*.{ts,tsx}\";\n@source \"../**/*.{ts,tsx}\";\n\n@custom-variant dark (&:is(.dark *));\n\n:root {\n  --background: oklch(1 0 0);\n  --foreground: oklch(0.145 0 0);\n  --card: oklch(1 0 0);\n  --card-foreground: oklch(0.145 0 0);\n  --popover: oklch(1 0 0);\n  --popover-foreground: oklch(0.145 0 0);\n  --primary: oklch(0.205 0 0);\n  --primary-foreground: oklch(0.985 0 0);\n  --secondary: oklch(0.97 0 0);\n  --secondary-foreground: oklch(0.205 0 0);\n  --muted: oklch(0.97 0 0);\n  --muted-foreground: oklch(0.556 0 0);\n  --accent: oklch(0.97 0 0);\n  --accent-foreground: oklch(0.205 0 0);\n  --destructive: oklch(0.58 0.22 27);\n  --border: oklch(0.922 0 0);\n  --input: oklch(0.922 0 0);\n  --ring: oklch(0.708 0 0);\n  --chart-1: oklch(0.809 0.105 251.813);\n  --chart-2: oklch(0.623 0.214 259.815);\n  --chart-3: oklch(0.546 0.245 262.881);\n  --chart-4: oklch(0.488 0.243 264.376);\n  --chart-5: oklch(0.424 0.199 265.638);\n  --radius: 0.625rem;\n  --sidebar: oklch(0.985 0 0);\n  --sidebar-foreground: oklch(0.145 0 0);\n  --sidebar-primary: oklch(0.205 0 0);\n  --sidebar-primary-foreground: oklch(0.985 0 0);\n  --sidebar-accent: oklch(0.97 0 0);\n  --sidebar-accent-foreground: oklch(0.205 0 0);\n  --sidebar-border: oklch(0.922 0 0);\n  --sidebar-ring: oklch(0.708 0 0);\n}\n\n.dark {\n  --background: oklch(0.145 0 0);\n  --foreground: oklch(0.985 0 0);\n  --card: oklch(0.205 0 0);\n  --card-foreground: oklch(0.985 0 0);\n  --popover: oklch(0.205 0 0);\n  --popover-foreground: oklch(0.985 0 0);\n  --primary: oklch(0.87 0 0);\n  --primary-foreground: oklch(0.205 0 0);\n  --secondary: oklch(0.269 0 0);\n  --secondary-foreground: oklch(0.985 0 0);\n  --muted: oklch(0.269 0 0);\n  --muted-foreground: oklch(0.708 0 0);\n  --accent: oklch(0.371 0 0);\n  --accent-foreground: oklch(0.985 0 0);\n  --destructive: oklch(0.704 0.191 22.216);\n  --border: oklch(1 0 0 / 10%);\n  --input: oklch(1 0 0 / 15%);\n  --ring: oklch(0.556 0 0);\n  --chart-1: oklch(0.809 0.105 251.813);\n  --chart-2: oklch(0.623 0.214 259.815);\n  --chart-3: oklch(0.546 0.245 262.881);\n  --chart-4: oklch(0.488 0.243 264.376);\n  --chart-5: oklch(0.424 0.199 265.638);\n  --sidebar: oklch(0.205 0 0);\n  --sidebar-foreground: oklch(0.985 0 0);\n  --sidebar-primary: oklch(0.488 0.243 264.376);\n  --sidebar-primary-foreground: oklch(0.985 0 0);\n  --sidebar-accent: oklch(0.269 0 0);\n  --sidebar-accent-foreground: oklch(0.985 0 0);\n  --sidebar-border: oklch(1 0 0 / 10%);\n  --sidebar-ring: oklch(0.556 0 0);\n}\n\n@theme inline {\n  --font-sans: 'Inter Variable', sans-serif;\n  --color-sidebar-ring: var(--sidebar-ring);\n  --color-sidebar-border: var(--sidebar-border);\n  --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);\n  --color-sidebar-accent: var(--sidebar-accent);\n  --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);\n  --color-sidebar-primary: var(--sidebar-primary);\n  --color-sidebar-foreground: var(--sidebar-foreground);\n  --color-sidebar: var(--sidebar);\n  --color-chart-5: var(--chart-5);\n  --color-chart-4: var(--chart-4);\n  --color-chart-3: var(--chart-3);\n  --color-chart-2: var(--chart-2);\n  --color-chart-1: var(--chart-1);\n  --color-ring: var(--ring);\n  --color-input: var(--input);\n  --color-border: var(--border);\n  --color-destructive: var(--destructive);\n  --color-accent-foreground: var(--accent-foreground);\n  --color-accent: var(--accent);\n  --color-muted-foreground: var(--muted-foreground);\n  --color-muted: var(--muted);\n  --color-secondary-foreground: var(--secondary-foreground);\n  --color-secondary: var(--secondary);\n  --color-primary-foreground: var(--primary-foreground);\n  --color-primary: var(--primary);\n  --color-popover-foreground: var(--popover-foreground);\n  --color-popover: var(--popover);\n  --color-card-foreground: var(--card-foreground);\n  --color-card: var(--card);\n  --color-foreground: var(--foreground);\n  --color-background: var(--background);\n  --radius-sm: calc(var(--radius) - 4px);\n  --radius-md: calc(var(--radius) - 2px);\n  --radius-lg: var(--radius);\n  --radius-xl: calc(var(--radius) + 4px);\n  --radius-2xl: calc(var(--radius) + 8px);\n  --radius-3xl: calc(var(--radius) + 12px);\n  --radius-4xl: calc(var(--radius) + 16px);\n}\n\n@layer base {\n  * {\n    @apply border-border outline-ring/50;\n  }\n  body {\n    @apply font-sans bg-background text-foreground;\n  }\n  html {\n    @apply font-sans;\n  }\n}\n`],\n  [\"packages/ui/tsconfig.json.hbs\", `{\n  \"extends\": \"@{{projectName}}/config/tsconfig.base.json\",\n  \"compilerOptions\": {\n    \"jsx\": \"react-jsx\",\n    \"lib\": [\"ESNext\", \"DOM\", \"DOM.Iterable\"],\n    \"paths\": {\n      \"@{{projectName}}/ui/*\": [\"./src/*\"]\n    }\n  },\n  \"include\": [\"src/**/*.ts\", \"src/**/*.tsx\"],\n  \"exclude\": [\"node_modules\"]\n}\n`],\n  [\"payments/polar/server/base/src/lib/payments.ts.hbs\", `import { Polar } from \"@polar-sh/sdk\";\n{{#if (and (eq backend \"self\") (eq webDeploy \"cloudflare\") (includes frontend \"svelte\"))}}\nimport type {} from \"@{{projectName}}/env/server\";\n{{else}}\nimport { env } from \"@{{projectName}}/env/server\";\n{{/if}}\n\n{{#if (and (eq backend \"self\") (eq webDeploy \"cloudflare\") (includes frontend \"svelte\"))}}\nexport function createPolarClient({{#if (and (eq backend \"self\") (eq webDeploy \"cloudflare\") (includes frontend \"svelte\"))}}env: Env{{/if}}) {\n\treturn new Polar({\n\t\taccessToken: env.POLAR_ACCESS_TOKEN,\n\t\tserver: \"sandbox\",\n\t});\n}\n{{else}}\nexport const polarClient = new Polar({\n\taccessToken: env.POLAR_ACCESS_TOKEN,\n\tserver: \"sandbox\",\n});\n{{/if}}\n`],\n  [\"payments/polar/web/nuxt/app/pages/success.vue.hbs\", `<script setup lang=\"ts\">\nconst route = useRoute()\nconst checkout_id = route.query.checkout_id as string\n</script>\n\n<template>\n  <div class=\"container mx-auto px-4 py-8\">\n    <h1 class=\"text-2xl font-bold mb-4\">Payment Successful!</h1>\n    <p v-if=\"checkout_id\">Checkout ID: \\\\{{ checkout_id }}</p>\n  </div>\n</template>\n`],\n  [\"payments/polar/web/react/next/src/app/success/page.tsx.hbs\", `export default async function SuccessPage({\n    searchParams,\n}: {\n    searchParams: Promise<{ checkout_id: string }>\n}) {\n    const params = await searchParams;\n    const checkout_id = params.checkout_id;\n\n    return (\n        <div className=\"px-4 py-8\">\n            <h1>Payment Successful!</h1>\n            {checkout_id && <p>Checkout ID: {checkout_id}</p>}\n        </div>\n    );\n}\n`],\n  [\"payments/polar/web/react/react-router/src/routes/success.tsx.hbs\", `import { useSearchParams } from \"react-router\";\n\nexport default function SuccessPage() {\n    const [searchParams] = useSearchParams();\n    const checkout_id = searchParams.get(\"checkout_id\");\n\n    return (\n        <div className=\"container mx-auto px-4 py-8\">\n            <h1>Payment Successful!</h1>\n            {checkout_id && <p>Checkout ID: {checkout_id}</p>}\n        </div>\n    );\n}\n`],\n  [\"payments/polar/web/react/tanstack-router/src/routes/success.tsx.hbs\", `import { createFileRoute, useSearch } from \"@tanstack/react-router\";\n\nexport const Route = createFileRoute(\"/success\")({\n\tcomponent: SuccessPage,\n\tvalidateSearch: (search) => ({\n\t\tcheckout_id: search.checkout_id as string,\n\t}),\n});\n\nfunction SuccessPage() {\n\tconst { checkout_id } = useSearch({ from: \"/success\" });\n\n\treturn (\n\t\t<div className=\"container mx-auto px-4 py-8\">\n\t\t\t<h1>Payment Successful!</h1>\n\t\t\t{checkout_id && <p>Checkout ID: {checkout_id}</p>}\n\t\t</div>\n\t);\n}\n`],\n  [\"payments/polar/web/react/tanstack-start/src/functions/get-payment.ts.hbs\", `import { authClient } from \"@/lib/auth-client\";\nimport { authMiddleware } from \"@/middleware/auth\";\nimport { createServerFn } from \"@tanstack/react-start\";\nimport { getRequestHeaders } from \"@tanstack/react-start/server\";\n\nexport const getPayment = createServerFn({ method: \"GET\" })\n    .middleware([authMiddleware])\n    .handler(async () => {\n        const { data: customerState } = await authClient.customer.state({\n            fetchOptions: {\n                headers: getRequestHeaders()\n            }\n        });\n        return customerState;\n    });\n`],\n  [\"payments/polar/web/react/tanstack-start/src/routes/success.tsx.hbs\", `import { createFileRoute, useSearch } from \"@tanstack/react-router\";\n\nexport const Route = createFileRoute(\"/success\")({\n\tcomponent: SuccessPage,\n\tvalidateSearch: (search) => ({\n\t\tcheckout_id: search.checkout_id as string,\n\t}),\n});\n\nfunction SuccessPage() {\n\tconst { checkout_id } = useSearch({ from: \"/success\" });\n\n\treturn (\n\t\t<div className=\"container mx-auto px-4 py-8\">\n\t\t\t<h1>Payment Successful!</h1>\n\t\t\t{checkout_id && <p>Checkout ID: {checkout_id}</p>}\n\t\t</div>\n\t);\n}\n`],\n  [\"payments/polar/web/solid/src/routes/success.tsx.hbs\", `import { createFileRoute } from \"@tanstack/solid-router\";\nimport { Show } from \"solid-js\";\n\nexport const Route = createFileRoute(\"/success\")({\n\tcomponent: SuccessPage,\n\tvalidateSearch: (search) => ({\n\t\tcheckout_id: search.checkout_id as string,\n\t}),\n});\n\nfunction SuccessPage() {\n\tconst searchParams = Route.useSearch();\n\tconst checkout_id = searchParams().checkout_id;\n\n\treturn (\n\t\t<div class=\"container mx-auto px-4 py-8\">\n\t\t\t<h1>Payment Successful!</h1>\n\t\t\t<Show when={checkout_id}>\n\t\t\t\t<p>Checkout ID: {checkout_id}</p>\n\t\t\t</Show>\n\t\t</div>\n\t);\n}\n`],\n  [\"payments/polar/web/svelte/src/routes/success/+page.svelte.hbs\", `<script lang=\"ts\">\n\timport { page } from '$app/state';\n\t\n\tconst checkout_id = $derived(page.url.searchParams.get('checkout_id'));\n</script>\n\n<div class=\"container mx-auto px-4 py-8\">\n\t<h1>Payment Successful!</h1>\n\t{#if checkout_id}\n\t\t<p>Checkout ID: {checkout_id}</p>\n\t{/if}\n</div>\n`]\n]);\n\nexport const TEMPLATE_COUNT = 467;\n"
  },
  {
    "path": "packages/template-generator/src/types.ts",
    "content": "import type { ProjectConfig } from \"@better-t-stack/types\";\nimport { TaggedError } from \"better-result\";\n\nexport interface VirtualFile {\n  type: \"file\";\n  path: string;\n  name: string;\n  content: string;\n  extension: string;\n  sourcePath?: string; // Original template path for binary files\n}\n\nexport interface VirtualDirectory {\n  type: \"directory\";\n  path: string;\n  name: string;\n  children: VirtualNode[];\n}\n\nexport type VirtualNode = VirtualFile | VirtualDirectory;\n\nexport interface VirtualFileTree {\n  root: VirtualDirectory;\n  fileCount: number;\n  directoryCount: number;\n  config: ProjectConfig;\n}\n\nexport interface GeneratorOptions {\n  config: ProjectConfig;\n  templateBasePath?: string;\n  templates?: Map<string, string>;\n  /** CLI version string for bts.jsonc */\n  version?: string;\n}\n\n/**\n * Error class for template generation failures\n */\nexport class GeneratorError extends TaggedError(\"GeneratorError\")<{\n  message: string;\n  phase?: string;\n  cause?: unknown;\n}>() {}\n"
  },
  {
    "path": "packages/template-generator/src/utils/add-deps.ts",
    "content": "/**\n * Add dependencies to a package.json in the virtual filesystem\n */\n\nimport type { VirtualFileSystem } from \"../core/virtual-fs\";\n\ntype PackageJson = {\n  name?: string;\n  dependencies?: Record<string, string>;\n  devDependencies?: Record<string, string>;\n  [key: string]: unknown;\n};\n\nexport const dependencyVersionMap = {\n  typescript: \"^6\",\n\n  \"better-auth\": \"1.6.9\",\n  \"@better-auth/expo\": \"1.6.9\",\n\n  \"@clerk/backend\": \"^3.2.1\",\n  \"@clerk/express\": \"^2.0.5\",\n  \"@clerk/fastify\": \"^3.1.3\",\n  \"@clerk/nextjs\": \"^7.0.5\",\n  \"@clerk/react\": \"^6.1.1\",\n  \"@clerk/react-router\": \"^3.0.5\",\n  \"@clerk/tanstack-react-start\": \"^1.1.3\",\n  \"@clerk/expo\": \"^3.1.3\",\n\n  \"drizzle-orm\": \"^0.45.1\",\n  \"drizzle-kit\": \"^0.31.8\",\n  \"@planetscale/database\": \"^1.19.0\",\n\n  \"@libsql/client\": \"0.15.15\",\n  libsql: \"0.5.22\",\n\n  \"@neondatabase/serverless\": \"^1.0.2\",\n  pg: \"^8.17.1\",\n  \"@types/pg\": \"^8.16.0\",\n  \"@types/ws\": \"^8.18.1\",\n  ws: \"^8.18.3\",\n\n  mysql2: \"^3.14.0\",\n\n  \"@prisma/client\": \"^7.7.0\",\n  prisma: \"^7.7.0\",\n  \"@prisma/adapter-d1\": \"^7.7.0\",\n  \"@prisma/adapter-neon\": \"^7.7.0\",\n  \"@prisma/adapter-mariadb\": \"^7.7.0\",\n  \"@prisma/adapter-libsql\": \"^7.7.0\",\n  \"@prisma/adapter-better-sqlite3\": \"^7.7.0\",\n  \"@prisma/adapter-pg\": \"^7.7.0\",\n  \"@prisma/adapter-planetscale\": \"^7.7.0\",\n\n  mongoose: \"^8.14.0\",\n\n  \"vite-plugin-pwa\": \"^1.2.0\",\n  \"@vite-pwa/assets-generator\": \"^1.0.2\",\n\n  \"@tauri-apps/cli\": \"^2.4.0\",\n\n  \"@biomejs/biome\": \"^2.2.0\",\n\n  oxlint: \"^1.61.0\",\n  oxfmt: \"^0.46.0\",\n\n  husky: \"^9.1.7\",\n  lefthook: \"^2.0.13\",\n  \"lint-staged\": \"^16.1.2\",\n\n  tsx: \"^4.19.2\",\n  \"@types/node\": \"^22.13.14\",\n\n  \"@types/bun\": \"^1.3.4\",\n\n  \"@elysiajs/node\": \"^1.4.5\",\n\n  \"@elysiajs/cors\": \"^1.4.1\",\n  \"@elysiajs/trpc\": \"^1.1.0\",\n  elysia: \"^1.4.28\",\n  // Peer dep of elysia; Bun isolated linker won't install peers, so Node/tsx fails without it.\n  \"@sinclair/typebox\": \"^0.34.49\",\n\n  \"@hono/node-server\": \"^1.14.4\",\n  \"@hono/trpc-server\": \"^0.4.0\",\n  hono: \"^4.8.2\",\n\n  cors: \"^2.8.5\",\n  express: \"^5.1.0\",\n  \"@types/express\": \"^5.0.1\",\n  \"@types/cors\": \"^2.8.17\",\n\n  fastify: \"^5.3.3\",\n  \"@fastify/cors\": \"^11.0.1\",\n\n  turbo: \"^2.8.12\",\n  nx: \"^21.5.2\",\n\n  ai: \"^6.0.3\",\n  \"@ai-sdk/google\": \"^3.0.1\",\n  \"@ai-sdk/vue\": \"^3.0.3\",\n  \"@ai-sdk/svelte\": \"^4.0.3\",\n  \"@ai-sdk/react\": \"^3.0.3\",\n  \"@ai-sdk/devtools\": \"^0.0.2\",\n  streamdown: \"^1.6.10\",\n  shiki: \"^3.20.0\",\n\n  \"@orpc/server\": \"^1.13.14\",\n  \"@orpc/client\": \"^1.13.14\",\n  \"@orpc/openapi\": \"^1.13.14\",\n  \"@orpc/zod\": \"^1.13.14\",\n  \"@orpc/tanstack-query\": \"^1.13.14\",\n\n  \"@trpc/tanstack-react-query\": \"^11.16.0\",\n  \"@trpc/server\": \"^11.16.0\",\n  \"@trpc/client\": \"^11.16.0\",\n\n  next: \"^16.2.0\",\n  nitro: \"^3.0.260429-beta\",\n\n  convex: \"^1.33.1\",\n  \"@convex-dev/react-query\": \"^0.1.0\",\n  \"@convex-dev/agent\": \"^0.3.2\",\n  \"convex-svelte\": \"^0.0.12\",\n  \"convex-nuxt\": \"0.1.5\",\n  \"convex-vue\": \"^0.1.5\",\n  \"@convex-dev/better-auth\": \"^0.12.1\",\n\n  \"@tanstack/svelte-query\": \"^5.85.3\",\n  \"@tanstack/svelte-query-devtools\": \"^5.85.3\",\n\n  \"@tanstack/vue-query-devtools\": \"^6.1.5\",\n  \"@tanstack/vue-query\": \"^5.92.9\",\n\n  \"@tanstack/react-query-devtools\": \"^5.91.1\",\n  \"@tanstack/react-query\": \"^5.90.12\",\n  \"@tanstack/react-form\": \"^1.28.0\",\n  \"@tanstack/react-router-ssr-query\": \"^1.166.11\",\n  \"@tanstack/solid-form\": \"^1.28.0\",\n  \"@tanstack/svelte-form\": \"^1.28.0\",\n\n  \"@tanstack/solid-query\": \"^5.99.1\",\n  \"@tanstack/solid-query-devtools\": \"^5.99.1\",\n  \"@tanstack/solid-router-devtools\": \"^1.166.13\",\n\n  wrangler: \"^4.77.0\",\n  \"@cloudflare/vite-plugin\": \"^1.17.1\",\n  \"@opennextjs/cloudflare\": \"^1.17.3\",\n  \"nitro-cloudflare-dev\": \"^0.2.2\",\n  \"@sveltejs/adapter-cloudflare\": \"^7.2.8\",\n  \"@cloudflare/workers-types\": \"^4.20251213.0\",\n  \"@astrojs/cloudflare\": \"^13.0.1\",\n  \"@astrojs/node\": \"^10.0.0-beta.9\",\n\n  alchemy: \"^0.91.2\",\n\n  dotenv: \"^17.2.2\",\n  tsdown: \"^0.21.9\",\n  zod: \"^4.1.13\",\n  \"@t3-oss/env-core\": \"^0.13.1\",\n  \"@t3-oss/env-nextjs\": \"^0.13.1\",\n  \"@t3-oss/env-nuxt\": \"^0.13.1\",\n\n  \"@polar-sh/better-auth\": \"^1.8.3\",\n  \"@polar-sh/sdk\": \"^0.42.2\",\n\n  evlog: \"^2.14.1\",\n} as const;\n\nexport type AvailableDependencies = keyof typeof dependencyVersionMap;\n\nexport type AddDepsOptions = {\n  vfs: VirtualFileSystem;\n  packagePath: string;\n  dependencies?: AvailableDependencies[];\n  devDependencies?: AvailableDependencies[];\n  customDependencies?: Record<string, string>;\n  customDevDependencies?: Record<string, string>;\n};\n\n/**\n * Add dependencies to a package.json file in the VFS\n */\nexport function addPackageDependency(options: AddDepsOptions): void {\n  const {\n    vfs,\n    packagePath,\n    dependencies = [],\n    devDependencies = [],\n    customDependencies = {},\n    customDevDependencies = {},\n  } = options;\n\n  const pkgJson = vfs.readJson<PackageJson>(packagePath);\n  if (!pkgJson) return;\n\n  // Initialize if not present\n  pkgJson.dependencies = pkgJson.dependencies || {};\n  pkgJson.devDependencies = pkgJson.devDependencies || {};\n\n  // Add regular dependencies\n  for (const dep of dependencies) {\n    if (!pkgJson.dependencies[dep]) {\n      const version = dependencyVersionMap[dep as AvailableDependencies];\n      if (!version) {\n        throw new Error(\n          `Missing version for dependency: ${dep}. Add it to dependencyVersionMap in add-deps.ts`,\n        );\n      }\n      pkgJson.dependencies[dep] = version;\n    }\n  }\n\n  // Add dev dependencies\n  for (const dep of devDependencies) {\n    if (!pkgJson.devDependencies[dep]) {\n      const version = dependencyVersionMap[dep as AvailableDependencies];\n      if (!version) {\n        throw new Error(\n          `Missing version for devDependency: ${dep}. Add it to dependencyVersionMap in add-deps.ts`,\n        );\n      }\n      pkgJson.devDependencies[dep] = version;\n    }\n  }\n\n  // Add custom dependencies (with specific versions)\n  for (const [dep, version] of Object.entries(customDependencies)) {\n    pkgJson.dependencies[dep] = version;\n  }\n\n  // Add custom dev dependencies (with specific versions)\n  for (const [dep, version] of Object.entries(customDevDependencies)) {\n    pkgJson.devDependencies[dep] = version;\n  }\n\n  vfs.writeJson(packagePath, pkgJson);\n}\n"
  },
  {
    "path": "packages/template-generator/src/utils/db-scripts.ts",
    "content": "import type { ProjectConfig } from \"@better-t-stack/types\";\n\nexport type DbScriptSupport = {\n  hasDbScripts: boolean;\n  hasDbPush: boolean;\n  hasDbGenerate: boolean;\n  hasDbMigrate: boolean;\n  hasDbStudio: boolean;\n  isD1Alchemy: boolean;\n};\n\nexport function getDbScriptSupport(config: ProjectConfig): DbScriptSupport {\n  const isD1Alchemy =\n    config.dbSetup === \"d1\" &&\n    (config.serverDeploy === \"cloudflare\" ||\n      (config.backend === \"self\" && config.webDeploy === \"cloudflare\"));\n  const hasDbScripts =\n    config.backend !== \"convex\" &&\n    config.backend !== \"none\" &&\n    config.database !== \"none\" &&\n    config.orm !== \"none\" &&\n    config.orm !== \"mongoose\";\n\n  if (!hasDbScripts) {\n    return {\n      hasDbScripts: false,\n      hasDbPush: false,\n      hasDbGenerate: false,\n      hasDbMigrate: false,\n      hasDbStudio: false,\n      isD1Alchemy,\n    };\n  }\n\n  const hasDbMigrate = config.orm === \"prisma\" || (config.orm === \"drizzle\" && !isD1Alchemy);\n  const hasDbStudio = !isD1Alchemy;\n\n  return {\n    hasDbScripts: true,\n    hasDbPush: true,\n    hasDbGenerate: true,\n    hasDbMigrate,\n    hasDbStudio,\n    isD1Alchemy,\n  };\n}\n"
  },
  {
    "path": "packages/template-generator/src/utils/reproducible-command.ts",
    "content": "import type { ProjectConfig } from \"@better-t-stack/types\";\n\nfunction normalizeMultiValues(values: string[] | undefined): string[] {\n  if (!values || values.length === 0) return [];\n  const filtered = values.filter((value) => value !== \"none\");\n  return Array.from(new Set(filtered));\n}\n\nfunction formatMultiFlag(flag: string, values: string[]): string {\n  if (values.length === 0) {\n    return `${flag} none`;\n  }\n  return `${flag} ${values.join(\" \")}`;\n}\n\nfunction getBaseCommand(packageManager: ProjectConfig[\"packageManager\"]): string {\n  if (packageManager === \"bun\") {\n    return \"bun create better-t-stack@latest\";\n  }\n\n  if (packageManager === \"pnpm\") {\n    return \"pnpm create better-t-stack@latest\";\n  }\n\n  return \"npx create-better-t-stack@latest\";\n}\n\nexport function generateReproducibleCommand(config: ProjectConfig): string {\n  const baseCommand = getBaseCommand(config.packageManager);\n\n  const flags: string[] = [];\n  const frontend = normalizeMultiValues(config.frontend);\n  const addons = normalizeMultiValues(config.addons);\n  const examples = normalizeMultiValues(config.examples);\n\n  flags.push(formatMultiFlag(\"--frontend\", frontend));\n\n  flags.push(`--backend ${config.backend}`);\n  flags.push(`--runtime ${config.runtime}`);\n  flags.push(`--database ${config.database}`);\n  flags.push(`--orm ${config.orm}`);\n  flags.push(`--api ${config.api}`);\n  flags.push(`--auth ${config.auth}`);\n  flags.push(`--payments ${config.payments}`);\n\n  flags.push(formatMultiFlag(\"--addons\", addons));\n  flags.push(formatMultiFlag(\"--examples\", examples));\n\n  flags.push(`--db-setup ${config.dbSetup}`);\n  if (config.dbSetupOptions?.mode === \"manual\") {\n    flags.push(\"--manual-db\");\n  }\n  flags.push(`--web-deploy ${config.webDeploy}`);\n  flags.push(`--server-deploy ${config.serverDeploy}`);\n  flags.push(config.git ? \"--git\" : \"--no-git\");\n  flags.push(`--package-manager ${config.packageManager}`);\n  flags.push(config.install ? \"--install\" : \"--no-install\");\n\n  const projectPathArg = config.relativePath ? ` ${config.relativePath}` : \"\";\n\n  return `${baseCommand}${projectPathArg} ${flags.join(\" \")}`;\n}\n"
  },
  {
    "path": "packages/template-generator/templates/addons/biome/biome.json.hbs",
    "content": "{\n    \"$schema\": \"./node_modules/@biomejs/biome/configuration_schema.json\",\n\t\"vcs\": {\n\t\t\"enabled\": false,\n\t\t\"clientKind\": \"git\",\n\t\t\"useIgnoreFile\": false\n\t},\n\t\"files\": {\n\t\t\"ignoreUnknown\": false,\n\t\t\"includes\": [\n\t\t\t\"**\",\n\t\t\t\"!**/.next\",\n\t\t\t\"!**/dist\",\n\t\t\t\"!**/.turbo\",\n\t\t\t\"!**/.nx\",\n\t\t\t\"!**/dev-dist\",\n\t\t\t\"!**/.zed\",\n\t\t\t\"!**/.vscode\",\n\t\t\t\"!**/routeTree.gen.ts\",\n\t\t\t\"!**/src-tauri\",\n\t\t\t\"!**/.nuxt\",\n\t\t\t\"!bts.jsonc\",\n\t\t\t\"!**/.expo\",\n\t\t\t\"!**/.wrangler\",\n\t\t\t\"!**/.alchemy\",\n\t\t\t\"!**/.svelte-kit\",\n\t\t\t\"!**/wrangler.jsonc\",\n\t\t\t\"!**/.source\",\n\t\t\t\"!**/convex/_generated\"\n\t\t]\n\t},\n\t\"formatter\": {\n\t\t\"enabled\": true,\n\t\t\"indentStyle\": \"tab\"\n\t},\n\t\"assist\": { \"actions\": { \"source\": { \"organizeImports\": \"on\" } } },\n\t\"linter\": {\n\t\t\"enabled\": true,\n\t\t\"rules\": {\n\t\t\t\"recommended\": true,\n\t\t\t\"correctness\": {\n\t\t\t\t\"useExhaustiveDependencies\": \"info\"\n\t\t\t},\n\t\t\t\"nursery\": {\n\t\t\t\t\"useSortedClasses\": {\n\t\t\t\t\t\"level\": \"warn\",\n\t\t\t\t\t\"fix\": \"safe\",\n\t\t\t\t\t\"options\": {\n\t\t\t\t\t\t\"functions\": [\"clsx\", \"cva\", \"cn\"]\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t},\n\t\t\t\"style\": {\n\t\t\t\t\"noParameterAssign\": \"error\",\n\t\t\t\t\"useAsConstAssertion\": \"error\",\n\t\t\t\t\"useDefaultParameterLast\": \"error\",\n\t\t\t\t\"useEnumInitializers\": \"error\",\n\t\t\t\t\"useSelfClosingElements\": \"error\",\n\t\t\t\t\"useSingleVarDeclarator\": \"error\",\n\t\t\t\t\"noUnusedTemplateLiteral\": \"error\",\n\t\t\t\t\"useNumberNamespace\": \"error\",\n\t\t\t\t\"noInferrableTypes\": \"error\",\n\t\t\t\t\"noUselessElse\": \"error\"\n\t\t\t}\n\t\t}\n\t},\n\t\"javascript\": {\n\t\t\"formatter\": {\n\t\t\t\"quoteStyle\": \"double\"\n\t\t}\n\t},\n\t\"css\": {\n\t\t\"parser\": {\n\t\t\t\"tailwindDirectives\": true\n\t\t}\n\t}\n\t{{#if (or (includes frontend \"svelte\") (includes frontend \"nuxt\"))}}\n\t,\n\t\"overrides\": [\n\t\t{\n\t\t\t\"includes\": [\"**/*.svelte\", \"**/*.vue\"],\n\t\t\t\"linter\": {\n\t\t\t\t\"rules\": {\n\t\t\t\t\t\"style\": {\n\t\t\t\t\t\t\"useConst\": \"off\",\n\t\t\t\t\t\t\"useImportType\": \"off\"\n\t\t\t\t\t},\n\t\t\t\t\t\"correctness\": {\n\t\t\t\t\t\t\"noUnusedVariables\": \"off\",\n\t\t\t\t\t\t\"noUnusedImports\": \"off\"\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t]\n\t{{/if}}\n}\n"
  },
  {
    "path": "packages/template-generator/templates/addons/electrobun/apps/desktop/.gitignore",
    "content": "artifacts\n"
  },
  {
    "path": "packages/template-generator/templates/addons/electrobun/apps/desktop/electrobun.config.ts.hbs",
    "content": "import type { ElectrobunConfig } from \"electrobun\";\n\nconst webBuildDir =\n  \"{{#if (includes frontend \"react-router\")}}../web/build/client{{else if (includes frontend \"tanstack-start\")}}../web/dist/client{{else if (includes frontend \"next\")}}../web/out{{else if (includes frontend \"nuxt\")}}../web/.output/public{{else if (includes frontend \"svelte\")}}../web/build{{else}}../web/dist{{/if}}\";\n\nexport default {\n  app: {\n    name: \"{{projectName}}\",\n    identifier: \"dev.bettertstack.{{projectName}}.desktop\",\n    version: \"0.0.1\",\n  },\n  runtime: {\n    exitOnLastWindowClosed: true,\n  },\n  build: {\n    bun: {\n      entrypoint: \"src/bun/index.ts\",\n    },\n    copy: {\n      [webBuildDir]: \"views/mainview\",\n    },\n    watchIgnore: [`${webBuildDir}/**`],\n    mac: {\n      bundleCEF: true,\n      defaultRenderer: \"cef\",\n    },\n    linux: {\n      bundleCEF: true,\n      defaultRenderer: \"cef\",\n    },\n    win: {\n      bundleCEF: true,\n      defaultRenderer: \"cef\",\n    },\n  },\n} satisfies ElectrobunConfig;\n"
  },
  {
    "path": "packages/template-generator/templates/addons/electrobun/apps/desktop/package.json.hbs",
    "content": "{\n  \"name\": \"desktop\",\n  \"private\": true,\n  \"type\": \"module\",\n  \"scripts\": {},\n  \"dependencies\": {\n    \"electrobun\": \"^1.15.1\"\n  },\n  \"devDependencies\": {\n    \"@types/bun\": \"^1.3.4\",\n    \"concurrently\": \"^9.1.0\",\n    \"typescript\": \"^6\"\n  }\n}\n"
  },
  {
    "path": "packages/template-generator/templates/addons/electrobun/apps/desktop/src/bun/index.ts.hbs",
    "content": "import { BrowserWindow, Updater } from \"electrobun/bun\";\n\nconst DEV_SERVER_PORT = {{#if (or (includes frontend \"react-router\") (includes frontend \"svelte\"))}}5173{{else if (includes frontend \"astro\")}}4321{{else}}3001{{/if}};\nconst DEV_SERVER_URL = `http://localhost:${DEV_SERVER_PORT}`;\n\n// Check if the web dev server is running for HMR\nasync function getMainViewUrl(): Promise<string> {\n  const channel = await Updater.localInfo.channel();\n  if (channel === \"dev\") {\n    try {\n      await fetch(DEV_SERVER_URL, { method: \"HEAD\" });\n      console.log(`HMR enabled: Using web dev server at ${DEV_SERVER_URL}`);\n      return DEV_SERVER_URL;\n    } catch {\n      console.log(\n        'Web dev server not running. Run \"{{packageManager}} run dev:hmr\" for HMR support.',\n      );\n    }\n  }\n\n  return \"views://mainview/index.html\";\n}\n\nconst url = await getMainViewUrl();\n\nnew BrowserWindow({\n  title: \"{{projectName}}\",\n  url,\n  frame: {\n    width: 1280,\n    height: 820,\n    x: 120,\n    y: 120,\n  },\n});\n\nconsole.log(\"Electrobun desktop shell started.\");\n"
  },
  {
    "path": "packages/template-generator/templates/addons/electrobun/apps/desktop/tsconfig.json.hbs",
    "content": "{\n  \"extends\": \"../../packages/config/tsconfig.base.json\",\n  \"compilerOptions\": {\n    \"lib\": [\"ESNext\", \"DOM\"],\n    \"target\": \"ESNext\",\n    \"module\": \"ESNext\",\n    \"moduleResolution\": \"bundler\",\n    \"noEmit\": true,\n    \"paths\": {\n      \"@/*\": [\"./src/*\"]\n    }\n  },\n  \"include\": [\"src/**/*.ts\", \"electrobun.config.ts\"]\n}\n"
  },
  {
    "path": "packages/template-generator/templates/addons/husky/.husky/pre-commit",
    "content": "lint-staged\n"
  },
  {
    "path": "packages/template-generator/templates/addons/lefthook/lefthook.yml.hbs",
    "content": "# Lefthook configuration\n# https://github.com/evilmartians/lefthook\n\npre-commit:\n  parallel: true\n  jobs:\n{{#if (includes addons \"biome\")}}\n    - name: biome\n      glob: \"*.{js,ts,cjs,mjs,d.cts,d.mts,jsx,tsx,json,jsonc}\"\n      run: {{packageManager}} biome check --write --no-errors-on-unmatched --files-ignore-unknown=true {staged_files}\n      stage_fixed: true\n{{else if (includes addons \"oxlint\")}}\n    - name: oxlint\n      run: {{packageManager}} oxlint --fix {staged_files}\n      stage_fixed: true\n    - name: oxfmt\n      run: {{packageManager}} oxfmt --write {staged_files}\n      stage_fixed: true\n{{else}}\n    # Add your pre-commit commands here\n    # Example:\n    # - name: lint\n    #   run: {{packageManagerRunCmd}} lint\n{{/if}}\n"
  },
  {
    "path": "packages/template-generator/templates/addons/pwa/apps/web/next/public/favicon/site.webmanifest.hbs",
    "content": "{\n\t\"name\": \"{{projectName}}\",\n\t\"short_name\": \"{{projectName}}\",\n\t\"icons\": [\n\t\t{\n\t\t\t\"src\": \"/web-app-manifest-192x192.png\",\n\t\t\t\"sizes\": \"192x192\",\n\t\t\t\"type\": \"image/png\",\n\t\t\t\"purpose\": \"maskable\"\n\t\t},\n\t\t{\n\t\t\t\"src\": \"/web-app-manifest-512x512.png\",\n\t\t\t\"sizes\": \"512x512\",\n\t\t\t\"type\": \"image/png\",\n\t\t\t\"purpose\": \"maskable\"\n\t\t}\n\t],\n\t\"theme_color\": \"#ffffff\",\n\t\"background_color\": \"#ffffff\",\n\t\"display\": \"standalone\"\n}\n"
  },
  {
    "path": "packages/template-generator/templates/addons/pwa/apps/web/next/src/app/manifest.ts.hbs",
    "content": "import type { MetadataRoute } from \"next\";\n\nexport default function manifest(): MetadataRoute.Manifest {\n\treturn {\n\t\tname: \"{{projectName}}\",\n\t\tshort_name: \"{{projectName}}\",\n\t\tdescription:\n\t\t\t\"my pwa app\",\n\t\tstart_url: \"/new\",\n\t\tdisplay: \"standalone\",\n\t\tbackground_color: \"#ffffff\",\n\t\ttheme_color: \"#000000\",\n\t\ticons: [\n\t\t\t{\n\t\t\t\tsrc: \"/favicon/web-app-manifest-192x192.png\",\n\t\t\t\tsizes: \"192x192\",\n\t\t\t\ttype: \"image/png\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tsrc: \"/favicon/web-app-manifest-512x512.png\",\n\t\t\t\tsizes: \"512x512\",\n\t\t\t\ttype: \"image/png\",\n\t\t\t},\n\t\t],\n\t};\n}\n"
  },
  {
    "path": "packages/template-generator/templates/addons/pwa/apps/web/vite/pwa-assets.config.ts.hbs",
    "content": "import {\n  defineConfig,\n  minimal2023Preset as preset,\n} from \"@vite-pwa/assets-generator/config\";\n\nexport default defineConfig({\n  headLinkOptions: {\n    preset: \"2023\",\n  },\n  preset,\n  images: [\"public/logo.png\"],\n});\n"
  },
  {
    "path": "packages/template-generator/templates/api/orpc/fullstack/astro/src/pages/rpc/[...rest].ts.hbs",
    "content": "import type { APIRoute } from \"astro\";\nimport { OpenAPIHandler } from \"@orpc/openapi/fetch\";\nimport { OpenAPIReferencePlugin } from \"@orpc/openapi/plugins\";\nimport { ZodToJsonSchemaConverter } from \"@orpc/zod/zod4\";\nimport { RPCHandler } from \"@orpc/server/fetch\";\nimport { onError } from \"@orpc/server\";\nimport { appRouter } from \"@{{projectName}}/api/routers/index\";\nimport { createContext } from \"@{{projectName}}/api/context\";\n\nconst handler = new RPCHandler(appRouter, {\n  interceptors: [\n    onError((error) => {\n      console.error(error);\n    }),\n  ],\n});\n\nconst apiHandler = new OpenAPIHandler(appRouter, {\n  plugins: [\n    new OpenAPIReferencePlugin({\n      schemaConverters: [new ZodToJsonSchemaConverter()],\n    }),\n  ],\n  interceptors: [\n    onError((error) => {\n      console.error(error);\n    }),\n  ],\n});\n\nexport const prerender = false;\n\nexport const ALL: APIRoute = async ({ request }) => {\n  const context = await createContext({ headers: request.headers });\n\n  const rpcResult = await handler.handle(request, {\n    prefix: \"/rpc\",\n    context,\n  });\n  if (rpcResult.response) return rpcResult.response;\n\n  const apiResult = await apiHandler.handle(request, {\n    prefix: \"/rpc/api-reference\",\n    context,\n  });\n  if (apiResult.response) return apiResult.response;\n\n  return new Response(\"Not found\", { status: 404 });\n};\n"
  },
  {
    "path": "packages/template-generator/templates/api/orpc/fullstack/next/src/app/api/rpc/[[...rest]]/route.ts.hbs",
    "content": "import { createContext } from \"@{{projectName}}/api/context\";\nimport { appRouter } from \"@{{projectName}}/api/routers/index\";\nimport { OpenAPIHandler } from \"@orpc/openapi/fetch\";\nimport { OpenAPIReferencePlugin } from \"@orpc/openapi/plugins\";\nimport { ZodToJsonSchemaConverter } from \"@orpc/zod/zod4\";\nimport { RPCHandler } from \"@orpc/server/fetch\";\nimport { onError } from \"@orpc/server\";\nimport { NextRequest } from \"next/server\";\n\nconst rpcHandler = new RPCHandler(appRouter, {\n\tinterceptors: [\n\t\tonError((error) => {\n\t\t\tconsole.error(error);\n\t\t}),\n\t],\n});\nconst apiHandler = new OpenAPIHandler(appRouter, {\n\tplugins: [\n\t\tnew OpenAPIReferencePlugin({\n\t\t\tschemaConverters: [new ZodToJsonSchemaConverter()],\n\t\t}),\n\t],\n\tinterceptors: [\n\t\tonError((error) => {\n\t\t\tconsole.error(error);\n\t\t}),\n\t],\n});\n\nasync function handleRequest(req: NextRequest) {\n\tconst rpcResult = await rpcHandler.handle(req, {\n\t\tprefix: \"/api/rpc\",\n\t\tcontext: await createContext(req),\n\t});\n\tif (rpcResult.response) return rpcResult.response;\n\n\tconst apiResult = await apiHandler.handle(req, {\n\t\tprefix: \"/api/rpc/api-reference\",\n\t\tcontext: await createContext(req),\n\t});\n\tif (apiResult.response) return apiResult.response;\n\n\treturn new Response(\"Not found\", { status: 404 });\n}\n\nexport const GET = handleRequest;\nexport const POST = handleRequest;\nexport const PUT = handleRequest;\nexport const PATCH = handleRequest;\nexport const DELETE = handleRequest;"
  },
  {
    "path": "packages/template-generator/templates/api/orpc/fullstack/nuxt/app/plugins/orpc.client.ts.hbs",
    "content": "import type { AppRouterClient } from \"@{{projectName}}/api/routers/index\";\nimport { createORPCClient } from \"@orpc/client\";\nimport { RPCLink } from \"@orpc/client/fetch\";\nimport { createTanstackQueryUtils } from \"@orpc/tanstack-query\";\n\nexport default defineNuxtPlugin(() => {\n  const rpcLink = new RPCLink({\n    url: `${window.location.origin}/rpc`,\n    {{#if (eq auth \"better-auth\")}}\n    fetch(url, options) {\n        return fetch(url, {\n        ...options,\n        credentials: \"include\",\n        });\n    },\n    {{/if}}\n  });\n\n  const client: AppRouterClient = createORPCClient(rpcLink);\n  const orpcUtils = createTanstackQueryUtils(client);\n\n  return {\n    provide: {\n      orpc: orpcUtils,\n    },\n  };\n});\n"
  },
  {
    "path": "packages/template-generator/templates/api/orpc/fullstack/nuxt/app/plugins/orpc.server.ts.hbs",
    "content": "import { createRouterClient } from \"@orpc/server\";\nimport { createTanstackQueryUtils } from \"@orpc/tanstack-query\";\nimport { appRouter } from \"@{{projectName}}/api/routers/index\";\nimport { createContext } from \"@{{projectName}}/api/context\";\n\nexport default defineNuxtPlugin(async () => {\n  const event = useRequestEvent();\n\n  const context = await createContext({\n    headers: event?.headers ?? new Headers(),\n  });\n\n  const client = createRouterClient(appRouter, {\n    context,\n  });\n\n  const orpc = createTanstackQueryUtils(client);\n\n  return {\n    provide: {\n      orpc,\n    },\n  };\n});\n"
  },
  {
    "path": "packages/template-generator/templates/api/orpc/fullstack/nuxt/server/routes/rpc/[...].ts.hbs",
    "content": "import { RPCHandler } from \"@orpc/server/fetch\";\nimport { OpenAPIHandler } from \"@orpc/openapi/fetch\";\nimport { OpenAPIReferencePlugin } from \"@orpc/openapi/plugins\";\nimport { onError } from \"@orpc/server\";\nimport { BatchHandlerPlugin } from \"@orpc/server/plugins\";\nimport { ZodToJsonSchemaConverter } from \"@orpc/zod/zod4\";\nimport { appRouter } from \"@{{projectName}}/api/routers/index\";\nimport { createContext } from \"@{{projectName}}/api/context\";\n\nconst rpcHandler = new RPCHandler(appRouter, {\n  interceptors: [\n    onError((error) => {\n      console.error(error);\n    }),\n  ],\n  plugins: [new BatchHandlerPlugin()],\n});\n\nconst apiHandler = new OpenAPIHandler(appRouter, {\n  plugins: [\n    new OpenAPIReferencePlugin({\n      schemaConverters: [new ZodToJsonSchemaConverter()],\n    }),\n  ],\n  interceptors: [\n    onError((error) => {\n      console.error(error);\n    }),\n  ],\n});\n\nexport default defineEventHandler(async (event) => {\n  const request = toWebRequest(event);\n  const context = await createContext({ headers: request.headers });\n\n  const rpcResult = await rpcHandler.handle(request, {\n    prefix: \"/rpc\",\n    context,\n  });\n  if (rpcResult.response) return rpcResult.response;\n\n  const apiResult = await apiHandler.handle(request, {\n    prefix: \"/rpc/api-reference\",\n    context,\n  });\n  if (apiResult.response) return apiResult.response;\n\n  setResponseStatus(event, 404, \"Not Found\");\n  return \"Not found\";\n});\n"
  },
  {
    "path": "packages/template-generator/templates/api/orpc/fullstack/nuxt/server/routes/rpc/index.ts.hbs",
    "content": "export { default } from \"./[...]\";\n"
  },
  {
    "path": "packages/template-generator/templates/api/orpc/fullstack/svelte/src/lib/orpc.server.ts.hbs",
    "content": "import { getRequestEvent } from \"$app/server\";\nimport { createContext } from \"@{{projectName}}/api/context\";\nimport { appRouter, type AppRouterClient } from \"@{{projectName}}/api/routers/index\";\n{{#if (eq webDeploy \"cloudflare\")}}\nimport { env as localEnv } from \"@{{projectName}}/env/server\";\n{{/if}}\nimport { createRouterClient } from \"@orpc/server\";\n\nif (typeof window !== \"undefined\") {\n\tthrow new Error(\"This file should only be imported on the server.\");\n}\n\nconst serverClient: AppRouterClient = createRouterClient(appRouter, {\n\tcontext: async () => {\n\t\tconst event = getRequestEvent();\n{{#if (eq webDeploy \"cloudflare\")}}\n\t\tconst env = event.platform?.env ?? localEnv;\n\n{{/if}}\n\t\treturn createContext({\n\t\t\theaders: event.request.headers,\n{{#if (eq webDeploy \"cloudflare\")}}\n\t\t\tenv,\n{{/if}}\n\t\t});\n\t},\n});\n\n// oRPC's SvelteKit SSR setup loads this from hooks.server.ts so $lib/orpc can\n// reuse the in-process server client during SSR and fall back to HTTP in the browser.\nglobalThis.$client = serverClient;\n"
  },
  {
    "path": "packages/template-generator/templates/api/orpc/fullstack/svelte/src/routes/rpc/[...rest]/+server.ts.hbs",
    "content": "import { createContext } from \"@{{projectName}}/api/context\";\nimport { appRouter } from \"@{{projectName}}/api/routers/index\";\n{{#if (eq webDeploy \"cloudflare\")}}\nimport { env as localEnv } from \"@{{projectName}}/env/server\";\n{{/if}}\nimport { OpenAPIHandler } from \"@orpc/openapi/fetch\";\nimport { OpenAPIReferencePlugin } from \"@orpc/openapi/plugins\";\nimport { ZodToJsonSchemaConverter } from \"@orpc/zod/zod4\";\nimport { onError } from \"@orpc/server\";\nimport { RPCHandler } from \"@orpc/server/fetch\";\nimport type { RequestHandler } from \"@sveltejs/kit\";\n\nconst rpcHandler = new RPCHandler(appRouter, {\n\tinterceptors: [\n\t\tonError((error) => {\n\t\t\tconsole.error(error);\n\t\t}),\n\t],\n});\n\nconst apiHandler = new OpenAPIHandler(appRouter, {\n\tplugins: [\n\t\tnew OpenAPIReferencePlugin({\n\t\t\tschemaConverters: [new ZodToJsonSchemaConverter()],\n\t\t}),\n\t],\n\tinterceptors: [\n\t\tonError((error) => {\n\t\t\tconsole.error(error);\n\t\t}),\n\t],\n});\n\nconst handle: RequestHandler = async ({ request{{#if (eq webDeploy \"cloudflare\")}}, platform{{/if}} }) => {\n{{#if (eq webDeploy \"cloudflare\")}}\n\tconst env = platform?.env ?? localEnv;\n\n{{/if}}\n\tconst context = await createContext({\n\t\theaders: request.headers,\n{{#if (eq webDeploy \"cloudflare\")}}\n\t\tenv,\n{{/if}}\n\t});\n\n\tconst rpcResult = await rpcHandler.handle(request, {\n\t\tprefix: \"/rpc\",\n\t\tcontext,\n\t});\n\tif (rpcResult.response) return rpcResult.response;\n\n\tconst apiResult = await apiHandler.handle(request, {\n\t\tprefix: \"/rpc/api-reference\",\n\t\tcontext,\n\t});\n\tif (apiResult.response) return apiResult.response;\n\n\treturn new Response(\"Not found\", { status: 404 });\n};\n\nexport const HEAD = handle;\nexport const GET = handle;\nexport const POST = handle;\nexport const PUT = handle;\nexport const PATCH = handle;\nexport const DELETE = handle;\n"
  },
  {
    "path": "packages/template-generator/templates/api/orpc/fullstack/tanstack-start/src/routes/api/rpc/$.ts.hbs",
    "content": "import { createContext } from \"@{{projectName}}/api/context\";\nimport { appRouter } from \"@{{projectName}}/api/routers/index\";\nimport { OpenAPIHandler } from \"@orpc/openapi/fetch\";\nimport { OpenAPIReferencePlugin } from \"@orpc/openapi/plugins\";\nimport { ZodToJsonSchemaConverter } from \"@orpc/zod/zod4\";\nimport { RPCHandler } from \"@orpc/server/fetch\";\nimport { onError } from \"@orpc/server\";\nimport { createFileRoute } from \"@tanstack/react-router\";\n\nconst rpcHandler = new RPCHandler(appRouter, {\n\tinterceptors: [\n\t\tonError((error) => {\n\t\t\tconsole.error(error);\n\t\t}),\n\t],\n});\n\nconst apiHandler = new OpenAPIHandler(appRouter, {\n\tplugins: [\n\t\tnew OpenAPIReferencePlugin({\n\t\t\tschemaConverters: [new ZodToJsonSchemaConverter()],\n\t\t}),\n\t],\n\tinterceptors: [\n\t\tonError((error) => {\n\t\t\tconsole.error(error);\n\t\t}),\n\t],\n});\n\nasync function handle({ request }: { request: Request }) {\n\tconst rpcResult = await rpcHandler.handle(request, {\n\t\tprefix: \"/api/rpc\",\n\t\tcontext: await createContext({ req: request }),\n\t});\n\tif (rpcResult.response) return rpcResult.response;\n\n\tconst apiResult = await apiHandler.handle(request, {\n\t\tprefix: \"/api/rpc/api-reference\",\n\t\tcontext: await createContext({ req: request }),\n\t});\n\tif (apiResult.response) return apiResult.response;\n\n\treturn new Response(\"Not found\", { status: 404 });\n}\n\nexport const Route = createFileRoute('/api/rpc/$')({\n  server: {\n    handlers: {\n      HEAD: handle,\n      GET: handle,\n      POST: handle,\n      PUT: handle,\n      PATCH: handle,\n      DELETE: handle,\n    },\n  },\n})"
  },
  {
    "path": "packages/template-generator/templates/api/orpc/native/utils/orpc.ts.hbs",
    "content": "import { createORPCClient } from \"@orpc/client\";\nimport { RPCLink } from \"@orpc/client/fetch\";\nimport { createTanstackQueryUtils } from \"@orpc/tanstack-query\";\nimport { QueryCache, QueryClient } from \"@tanstack/react-query\";\nimport type { AppRouterClient } from \"@{{projectName}}/api/routers/index\";\nimport { env } from \"@{{projectName}}/env/native\";\n{{#if (eq auth \"better-auth\")}}\nimport { authClient } from \"@/lib/auth-client\";\nimport { Platform } from \"react-native\";\n{{else if (eq auth \"clerk\")}}\nimport { getClerkAuthToken } from \"@/utils/clerk-auth\";\n{{/if}}\n\nexport const queryClient = new QueryClient({\n\tqueryCache: new QueryCache({\n\t\tonError: (error) => {\n\t\t\tconsole.log(error)\n\t\t},\n\t}),\n});\n\nexport const link = new RPCLink({\n{{#if (eq backend \"self\")}}\n{{#if (or (includes frontend \"next\") (includes frontend \"tanstack-start\"))}}\n\turl: `${env.EXPO_PUBLIC_SERVER_URL}/api/rpc`,\n{{else}}\n\turl: `${env.EXPO_PUBLIC_SERVER_URL}/rpc`,\n{{/if}}\n{{else}}\n\turl: `${env.EXPO_PUBLIC_SERVER_URL}/rpc`,\n{{/if}}\n{{#if (eq auth \"better-auth\")}}\n\tfetch:\n\t\tfunction (url, options) {\n\t\t\treturn fetch(url, {\n\t\t\t\t...options,\n\t\t\t\t// Better Auth Expo forwards the session cookie manually on native.\n\t\t\t\tcredentials: Platform.OS === \"web\" ? \"include\" : \"omit\",\n\t\t\t});\n\t\t},\n\theaders() {\n\t\tif (Platform.OS === \"web\") {\n\t\t\treturn {};\n\t\t}\n\t\tconst headers = new Map<string, string>();\n\t\tconst cookies = authClient.getCookie();\n\t\tif (cookies) {\n\t\t\theaders.set(\"Cookie\", cookies);\n\t\t}\n\t\treturn Object.fromEntries(headers);\n\t},\n{{else if (eq auth \"clerk\")}}\n\theaders: async () => {\n\t\tconst token = await getClerkAuthToken();\n\t\treturn token ? { Authorization: `Bearer ${token}` } : {};\n\t},\n{{/if}}\n});\n\nexport const client: AppRouterClient = createORPCClient(link);\n\nexport const orpc = createTanstackQueryUtils(client);\n"
  },
  {
    "path": "packages/template-generator/templates/api/orpc/server/_gitignore",
    "content": "# dependencies (bun install)\nnode_modules\n\n# output\nout\ndist\n*.tgz\n\n# code coverage\ncoverage\n*.lcov\n\n# logs\nlogs\n_.log\nreport.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json\n\n# dotenv environment variable files\n.env\n.env.development.local\n.env.test.local\n.env.production.local\n.env.local\n\n# caches\n.eslintcache\n.cache\n*.tsbuildinfo\n\n# IntelliJ based IDEs\n.idea\n\n# Finder (MacOS) folder config\n.DS_Store\n"
  },
  {
    "path": "packages/template-generator/templates/api/orpc/server/package.json.hbs",
    "content": "{\n  \"name\": \"@{{projectName}}/api\",\n  \"exports\": {\n    \".\": {\n      \"default\": \"./src/index.ts\"\n    },\n    \"./*\": {\n      \"default\": \"./src/*.ts\"\n    }\n  },\n  \"type\": \"module\",\n  \"scripts\": {},\n  \"devDependencies\": {},\n  \"dependencies\": {}\n}"
  },
  {
    "path": "packages/template-generator/templates/api/orpc/server/src/context.ts.hbs",
    "content": "{{#if (eq auth \"clerk\")}}\ntype ClerkContextAuth = {\n\tuserId: string | null;\n};\n\ntype ClerkRequestContext = {\n\tauth: ClerkContextAuth | null;\n\tsession: null;\n};\n\nfunction toClerkContextAuth(auth: { userId: string | null } | null): ClerkContextAuth | null {\n\treturn auth ? { userId: auth.userId } : null;\n}\n{{/if}}\n\n{{#if (and (eq auth \"clerk\") (or (eq backend 'self') (eq backend 'hono') (eq backend 'elysia')))}}\n{{#if (and (eq backend \"self\") (eq webDeploy \"cloudflare\") (includes frontend \"svelte\"))}}\n{{else}}\nimport { createClerkClient } from \"@clerk/backend\";\nimport { env } from \"@{{projectName}}/env/server\";\n\nconst clerkClient = createClerkClient({\n\tsecretKey: env.CLERK_SECRET_KEY,\n\tpublishableKey: env.CLERK_PUBLISHABLE_KEY,\n});\n\nasync function authenticateClerkRequest(request: Request): Promise<ClerkContextAuth | null> {\n\tconst requestState = await clerkClient.authenticateRequest(request, {\n\t\tauthorizedParties: [env.CORS_ORIGIN],\n\t});\n\treturn toClerkContextAuth(requestState.toAuth());\n}\n{{/if}}\n{{/if}}\n\n{{#if (and (eq backend 'self') (includes frontend \"next\"))}}\nimport type { NextRequest } from \"next/server\";\n{{#if (eq auth \"better-auth\")}}\n{{#if (or (eq runtime \"workers\") (eq serverDeploy \"cloudflare\") (and (eq backend \"self\") (eq webDeploy \"cloudflare\")))}}\nimport { createAuth } from \"@{{projectName}}/auth\";\n{{else}}\nimport { auth } from \"@{{projectName}}/auth\";\n{{/if}}\n{{/if}}\n\nexport async function createContext(req: NextRequest){{#if (eq auth \"clerk\")}}: Promise<ClerkRequestContext>{{/if}} {\n{{#if (eq auth \"better-auth\")}}\n\tconst session = await {{#if (or (eq runtime \"workers\") (eq serverDeploy \"cloudflare\") (and (eq backend \"self\") (eq webDeploy \"cloudflare\")))}}createAuth(){{else}}auth{{/if}}.api.getSession({\n\t\theaders: req.headers,\n\t});\n\treturn {\n\t\tauth: null,\n\t\tsession,\n\t};\n{{else if (eq auth \"clerk\")}}\n\tconst clerkAuth = await authenticateClerkRequest(req);\n\treturn {\n\t\tauth: clerkAuth,\n\t\tsession: null,\n\t};\n{{else}}\n\treturn {\n\t\tauth: null,\n\t\tsession: null,\n\t};\n{{/if}}\n}\n\n{{else if (and (eq backend 'self') (includes frontend \"tanstack-start\"))}}\n{{#if (eq auth \"better-auth\")}}\n{{#if (or (eq runtime \"workers\") (eq serverDeploy \"cloudflare\") (and (eq backend \"self\") (eq webDeploy \"cloudflare\")))}}\nimport { createAuth } from \"@{{projectName}}/auth\";\n{{else}}\nimport { auth } from \"@{{projectName}}/auth\";\n{{/if}}\n{{/if}}\n\nexport async function createContext({ req }: { req: Request }){{#if (eq auth \"clerk\")}}: Promise<ClerkRequestContext>{{/if}} {\n{{#if (eq auth \"better-auth\")}}\n\tconst session = await {{#if (or (eq runtime \"workers\") (eq serverDeploy \"cloudflare\") (and (eq backend \"self\") (eq webDeploy \"cloudflare\")))}}createAuth(){{else}}auth{{/if}}.api.getSession({\n\t\theaders: req.headers,\n\t});\n\treturn {\n\t\tauth: null,\n\t\tsession,\n\t};\n{{else if (eq auth \"clerk\")}}\n\tconst clerkAuth = await authenticateClerkRequest(req);\n\treturn {\n\t\tauth: clerkAuth,\n\t\tsession: null,\n\t};\n{{else}}\n\treturn {\n\t\tauth: null,\n\t\tsession: null,\n\t};\n{{/if}}\n}\n\n{{else if (and (eq backend 'self') (includes frontend \"nuxt\"))}}\n{{#if (eq auth \"better-auth\")}}\n{{#if (or (eq runtime \"workers\") (eq serverDeploy \"cloudflare\") (and (eq backend \"self\") (eq webDeploy \"cloudflare\")))}}\nimport { createAuth } from \"@{{projectName}}/auth\";\n{{else}}\nimport { auth } from \"@{{projectName}}/auth\";\n{{/if}}\n{{/if}}\n\nexport type CreateContextOptions = {\n\theaders: Headers;\n};\n\nexport async function createContext({ headers }: CreateContextOptions) {\n{{#if (eq auth \"better-auth\")}}\n\tconst session = await {{#if (or (eq runtime \"workers\") (eq serverDeploy \"cloudflare\") (and (eq backend \"self\") (eq webDeploy \"cloudflare\")))}}createAuth(){{else}}auth{{/if}}.api.getSession({ headers });\n\treturn {\n\t\tauth: null,\n\t\tsession,\n\t};\n{{else}}\n\treturn {\n\t\tauth: null,\n\t\tsession: null,\n\t};\n{{/if}}\n}\n\n{{else if (and (eq backend 'self') (includes frontend \"svelte\"))}}\n{{#if (eq auth \"better-auth\")}}\n{{#if (or (eq runtime \"workers\") (eq serverDeploy \"cloudflare\") (and (eq backend \"self\") (eq webDeploy \"cloudflare\")))}}\nimport { createAuth } from \"@{{projectName}}/auth\";\n{{else}}\nimport { auth } from \"@{{projectName}}/auth\";\n{{/if}}\n{{/if}}\n{{#if (eq webDeploy \"cloudflare\")}}\nimport type {} from \"@{{projectName}}/env/server\";\n{{/if}}\n\nexport type CreateContextOptions = {\n\theaders: Headers;\n{{#if (eq webDeploy \"cloudflare\")}}\n\tenv: Env;\n{{/if}}\n};\n\nexport async function createContext({ headers{{#if (eq webDeploy \"cloudflare\")}}, env{{/if}} }: CreateContextOptions) {\n{{#if (eq auth \"better-auth\")}}\n\tconst session = await {{#if (or (eq runtime \"workers\") (eq serverDeploy \"cloudflare\") (and (eq backend \"self\") (eq webDeploy \"cloudflare\")))}}createAuth({{#if (eq webDeploy \"cloudflare\")}}env{{/if}}){{else}}auth{{/if}}.api.getSession({ headers });\n\treturn {\n\t\tauth: null,\n\t\tsession,\n{{#if (eq webDeploy \"cloudflare\")}}\n\t\tenv,\n{{/if}}\n\t};\n{{else}}\n\treturn {\n\t\tauth: null,\n\t\tsession: null,\n{{#if (eq webDeploy \"cloudflare\")}}\n\t\tenv,\n{{/if}}\n\t};\n{{/if}}\n}\n\n{{else if (and (eq backend 'self') (includes frontend \"astro\"))}}\n{{#if (eq auth \"better-auth\")}}\n{{#if (or (eq runtime \"workers\") (eq serverDeploy \"cloudflare\") (and (eq backend \"self\") (eq webDeploy \"cloudflare\")))}}\nimport { createAuth } from \"@{{projectName}}/auth\";\n{{else}}\nimport { auth } from \"@{{projectName}}/auth\";\n{{/if}}\n{{/if}}\n\nexport type CreateContextOptions = {\n\theaders: Headers;\n};\n\nexport async function createContext({ headers }: CreateContextOptions) {\n{{#if (eq auth \"better-auth\")}}\n\tconst session = await {{#if (or (eq runtime \"workers\") (eq serverDeploy \"cloudflare\") (and (eq backend \"self\") (eq webDeploy \"cloudflare\")))}}createAuth(){{else}}auth{{/if}}.api.getSession({ headers });\n\treturn {\n\t\tauth: null,\n\t\tsession,\n\t};\n{{else}}\n\treturn {\n\t\tauth: null,\n\t\tsession: null,\n\t};\n{{/if}}\n}\n\n{{else if (eq backend 'hono')}}\nimport type { Context as HonoContext } from \"hono\";\n{{#if (eq auth \"better-auth\")}}\n{{#if (or (eq runtime \"workers\") (eq serverDeploy \"cloudflare\") (and (eq backend \"self\") (eq webDeploy \"cloudflare\")))}}\nimport { createAuth } from \"@{{projectName}}/auth\";\n{{else}}\nimport { auth } from \"@{{projectName}}/auth\";\n{{/if}}\n{{/if}}\n\nexport type CreateContextOptions = {\n\tcontext: HonoContext;\n};\n\nexport async function createContext({ context }: CreateContextOptions){{#if (eq auth \"clerk\")}}: Promise<ClerkRequestContext>{{/if}} {\n{{#if (eq auth \"better-auth\")}}\n\tconst session = await {{#if (or (eq runtime \"workers\") (eq serverDeploy \"cloudflare\") (and (eq backend \"self\") (eq webDeploy \"cloudflare\")))}}createAuth(){{else}}auth{{/if}}.api.getSession({\n\t\theaders: context.req.raw.headers,\n\t});\n\treturn {\n\t\tauth: null,\n\t\tsession,\n\t};\n{{else if (eq auth \"clerk\")}}\n\tconst clerkAuth = await authenticateClerkRequest(context.req.raw);\n\treturn {\n\t\tauth: clerkAuth,\n\t\tsession: null,\n\t};\n{{else}}\n\treturn {\n\t\tauth: null,\n\t\tsession: null,\n\t};\n{{/if}}\n}\n\n{{else if (eq backend 'elysia')}}\nimport type { Context as ElysiaContext } from \"elysia\";\n{{#if (eq auth \"better-auth\")}}\nimport { auth } from \"@{{projectName}}/auth\";\n{{/if}}\n\nexport type CreateContextOptions = {\n\tcontext: ElysiaContext;\n};\n\nexport async function createContext({ context }: CreateContextOptions){{#if (eq auth \"clerk\")}}: Promise<ClerkRequestContext>{{/if}} {\n{{#if (eq auth \"better-auth\")}}\n\tconst session = await auth.api.getSession({\n\t\theaders: context.request.headers,\n\t});\n\treturn {\n\t\tauth: null,\n\t\tsession,\n\t};\n{{else if (eq auth \"clerk\")}}\n\tconst clerkAuth = await authenticateClerkRequest(context.request);\n\treturn {\n\t\tauth: clerkAuth,\n\t\tsession: null,\n\t};\n{{else}}\n\treturn {\n\t\tauth: null,\n\t\tsession: null,\n\t};\n{{/if}}\n}\n\n{{else if (eq backend 'express')}}\nimport type { Request } from \"express\";\n{{#if (eq auth \"better-auth\")}}\nimport { fromNodeHeaders } from \"better-auth/node\";\nimport { auth } from \"@{{projectName}}/auth\";\n{{else if (eq auth \"clerk\")}}\nimport { getAuth } from \"@clerk/express\";\n{{/if}}\n\ninterface CreateContextOptions {\n\treq: Request;\n}\n\nexport async function createContext(opts: CreateContextOptions){{#if (eq auth \"clerk\")}}: Promise<ClerkRequestContext>{{/if}} {\n{{#if (eq auth \"better-auth\")}}\n\tconst session = await auth.api.getSession({\n\t\theaders: fromNodeHeaders(opts.req.headers),\n\t});\n\treturn {\n\t\tauth: null,\n\t\tsession,\n\t};\n{{else if (eq auth \"clerk\")}}\n\tconst clerkAuth = toClerkContextAuth(getAuth(opts.req));\n\treturn {\n\t\tauth: clerkAuth,\n\t\tsession: null,\n\t};\n{{else}}\n\treturn {\n\t\tauth: null,\n\t\tsession: null,\n\t};\n{{/if}}\n}\n\n{{else if (eq backend 'fastify')}}\n{{#if (eq auth \"better-auth\")}}\nimport type { IncomingHttpHeaders } from \"node:http\";\nimport { fromNodeHeaders } from \"better-auth/node\";\nimport { auth } from \"@{{projectName}}/auth\";\n{{else if (eq auth \"clerk\")}}\nimport { getAuth } from \"@clerk/fastify\";\n{{else}}\nimport type { IncomingHttpHeaders } from \"node:http\";\n{{/if}}\n\nexport async function createContext(req: {{#if (eq auth \"clerk\")}}Parameters<typeof getAuth>[0]{{else}}IncomingHttpHeaders{{/if}}){{#if (eq auth \"clerk\")}}: Promise<ClerkRequestContext>{{/if}} {\n{{#if (eq auth \"better-auth\")}}\n\tconst session = await auth.api.getSession({\n\t\theaders: fromNodeHeaders(req),\n\t});\n\treturn {\n\t\tauth: null,\n\t\tsession,\n\t};\n{{else if (eq auth \"clerk\")}}\n\tconst clerkAuth = toClerkContextAuth(getAuth(req));\n\treturn {\n\t\tauth: clerkAuth,\n\t\tsession: null,\n\t};\n{{else}}\n\treturn {\n\t\tauth: null,\n\t\tsession: null,\n\t};\n{{/if}}\n}\n\n{{else}}\nexport async function createContext() {\n\treturn {\n\t\tauth: null,\n\t\tsession: null,\n\t};\n}\n{{/if}}\n\nexport type Context = Awaited<ReturnType<typeof createContext>>;\n"
  },
  {
    "path": "packages/template-generator/templates/api/orpc/server/src/index.ts.hbs",
    "content": "import { ORPCError, os } from \"@orpc/server\";\nimport type { Context } from \"./context\";\n\nexport const o = os.$context<Context>();\n\nexport const publicProcedure = o;\n\n{{#if (or (eq auth \"better-auth\") (eq auth \"clerk\"))}}\nconst requireAuth = o.middleware(async ({ context, next }) => {\n  {{#if (eq auth \"better-auth\")}}\n  if (!context.session?.user) {\n    throw new ORPCError(\"UNAUTHORIZED\");\n  }\n  return next({\n    context: {\n      session: context.session,\n    },\n  });\n  {{else}}\n  if (!context.auth?.userId) {\n    throw new ORPCError(\"UNAUTHORIZED\");\n  }\n  return next({\n    context: {\n      auth: context.auth,\n    },\n  });\n  {{/if}}\n});\n\nexport const protectedProcedure = publicProcedure.use(requireAuth);\n{{/if}}\n"
  },
  {
    "path": "packages/template-generator/templates/api/orpc/server/src/routers/index.ts.hbs",
    "content": "{{#if (eq api \"orpc\")}}\nimport { {{#if (or (eq auth \"better-auth\") (eq auth \"clerk\"))}}protectedProcedure, {{/if}}publicProcedure } from \"../index\";\nimport type { RouterClient } from \"@orpc/server\";\n{{#if (includes examples \"todo\")}}\nimport { todoRouter } from \"./todo\";\n{{/if}}\n\nexport const appRouter = {\n  healthCheck: publicProcedure.handler(() => {\n    return \"OK\";\n  }),\n  {{#if (or (eq auth \"better-auth\") (eq auth \"clerk\"))}}\n  privateData: protectedProcedure.handler(({ context }) => {\n    return {\n      message: \"This is private\",\n      {{#if (eq auth \"better-auth\")}}\n      user: context.session?.user,\n      {{else}}\n      userId: context.auth?.userId,\n      {{/if}}\n    };\n  }),\n  {{/if}}\n  {{#if (includes examples \"todo\")}}\n  todo: todoRouter,\n  {{/if}}\n};\nexport type AppRouter = typeof appRouter;\nexport type AppRouterClient = RouterClient<typeof appRouter>;\n{{else if (eq api \"trpc\")}}\nimport {\n  {{#if (eq auth \"better-auth\")}}protectedProcedure, {{/if}}publicProcedure,\n  router,\n} from \"../index\";\n{{#if (includes examples \"todo\")}}\nimport { todoRouter } from \"./todo\";\n{{/if}}\n\nexport const appRouter = router({\n  healthCheck: publicProcedure.query(() => {\n    return \"OK\";\n  }),\n  {{#if (eq auth \"better-auth\")}}\n  privateData: protectedProcedure.query(({ ctx }) => {\n    return {\n      message: \"This is private\",\n      user: ctx.session.user,\n    };\n  }),\n  {{/if}}\n  {{#if (includes examples \"todo\")}}\n  todo: todoRouter,\n  {{/if}}\n});\nexport type AppRouter = typeof appRouter;\n{{else}}\nexport const appRouter = {};\nexport type AppRouter = typeof appRouter;\n{{/if}}\n"
  },
  {
    "path": "packages/template-generator/templates/api/orpc/server/tsconfig.json.hbs",
    "content": "{\n  \"extends\": \"@{{projectName}}/config/tsconfig.base.json\",\n  \"compilerOptions\": {\n    \"declaration\": true,\n    \"declarationMap\": true,\n    \"sourceMap\": true,\n    \"outDir\": \"dist\",\n    \"composite\": true\n  }\n}"
  },
  {
    "path": "packages/template-generator/templates/api/orpc/web/astro/src/lib/orpc.ts.hbs",
    "content": "import type { AppRouterClient } from \"@{{projectName}}/api/routers/index\";\n\nimport { createORPCClient } from \"@orpc/client\";\nimport { RPCLink } from \"@orpc/client/fetch\";\n\n{{#if (eq backend \"self\")}}\nexport const link = new RPCLink({\n  url: `${window.location.origin}/rpc`,\n});\n{{else}}\nimport { PUBLIC_SERVER_URL } from \"astro:env/client\";\n\nexport const link = new RPCLink({\n  url: `${PUBLIC_SERVER_URL}/rpc`,\n{{#if (eq auth \"better-auth\")}}\n  fetch(url, options) {\n    return fetch(url, {\n      ...options,\n      credentials: \"include\",\n    });\n  },\n{{/if}}\n});\n{{/if}}\n\nexport const orpc: AppRouterClient = createORPCClient(link);\n"
  },
  {
    "path": "packages/template-generator/templates/api/orpc/web/nuxt/app/plugins/orpc.ts.hbs",
    "content": "import { defineNuxtPlugin } from '#app'\nimport type { AppRouterClient } from \"@{{projectName}}/api/routers/index\";\nimport { createORPCClient } from '@orpc/client'\nimport { RPCLink } from '@orpc/client/fetch'\nimport { createTanstackQueryUtils } from \"@orpc/tanstack-query\";\n\nexport default defineNuxtPlugin(() => {\n  const config = useRuntimeConfig();\n  const rpcUrl = `${config.public.serverUrl}/rpc`;\n\n  const rpcLink = new RPCLink({\n    url: rpcUrl,\n    {{#if (eq auth \"better-auth\")}}\n    fetch(url, options) {\n        return fetch(url, {\n        ...options,\n        credentials: \"include\",\n        });\n    },\n    {{/if}}\n  })\n\n\n  const client: AppRouterClient = createORPCClient(rpcLink)\n  const orpcUtils = createTanstackQueryUtils(client)\n\n  return {\n    provide: {\n      orpc: orpcUtils\n    }\n  }\n})\n"
  },
  {
    "path": "packages/template-generator/templates/api/orpc/web/nuxt/app/plugins/vue-query.ts.hbs",
    "content": "import type {\n  DehydratedState,\n  VueQueryPluginOptions,\n} from '@tanstack/vue-query'\nimport {\n  dehydrate,\n  hydrate,\n  QueryCache,\n  QueryClient,\n  VueQueryPlugin,\n} from '@tanstack/vue-query'\n\nexport default defineNuxtPlugin((nuxt) => {\n  const vueQueryState = useState<DehydratedState | null>('vue-query')\n\n  const toast = useToast()\n\n  const queryClient = new QueryClient({\n    defaultOptions: {\n      queries: {\n        staleTime: 5_000,\n      },\n    },\n    queryCache: new QueryCache({\n      onError: (error) => {\n        console.log(error)\n        toast.add({\n          title: 'Error',\n          description: error?.message || 'An unexpected error occurred.',\n        })\n      },\n    }),\n  })\n  const options: VueQueryPluginOptions = { queryClient }\n\n  nuxt.vueApp.use(VueQueryPlugin, options)\n\n  if (import.meta.server) {\n    nuxt.hooks.hook('app:rendered', () => {\n      vueQueryState.value = dehydrate(queryClient)\n    })\n  }\n\n  if (import.meta.client) {\n    nuxt.hooks.hook('app:created', () => {\n      hydrate(queryClient, vueQueryState.value)\n    })\n  }\n})\n"
  },
  {
    "path": "packages/template-generator/templates/api/orpc/web/react/base/src/utils/orpc.ts.hbs",
    "content": "import { createORPCClient } from \"@orpc/client\";\nimport { RPCLink } from \"@orpc/client/fetch\";\nimport { createTanstackQueryUtils } from \"@orpc/tanstack-query\";\nimport { QueryCache, QueryClient } from \"@tanstack/react-query\";\nimport { toast } from \"sonner\";\n{{#if (and (includes frontend \"tanstack-start\") (eq backend \"self\"))}}\nimport { createRouterClient } from \"@orpc/server\";\nimport type { RouterClient } from \"@orpc/server\";\nimport { createIsomorphicFn } from \"@tanstack/react-start\";\nimport { getRequest } from \"@tanstack/react-start/server\";\nimport { appRouter } from \"@{{projectName}}/api/routers/index\";\nimport { createContext } from \"@{{projectName}}/api/context\";\n{{else if (includes frontend \"tanstack-start\")}}\nimport type { RouterClient } from \"@orpc/server\";\nimport type { AppRouter } from \"@{{projectName}}/api/routers/index\";\nimport { env } from \"@{{projectName}}/env/web\";\n{{#if (eq auth \"clerk\")}}\nimport { getClerkAuthToken } from \"@/utils/clerk-auth\";\n{{/if}}\n{{else}}\nimport type { AppRouterClient } from \"@{{projectName}}/api/routers/index\";\n{{#unless (eq backend \"self\")}}\nimport { env } from \"@{{projectName}}/env/web\";\n{{/unless}}\n{{#if (eq auth \"clerk\")}}\nimport { getClerkAuthToken } from \"@/utils/clerk-auth\";\n{{/if}}\n{{/if}}\n\nexport const queryClient = new QueryClient({\n\tqueryCache: new QueryCache({\n\t\tonError: (error, query) => {\n\t\t\ttoast.error(`Error: ${error.message}`, {\n\t\t\t\taction: {\n\t\t\t\t\tlabel: \"retry\",\n\t\t\t\t\tonClick: query.invalidate,\n\t\t\t\t},\n\t\t\t});\n\t\t},\n\t}),\n});\n\n{{#if (and (includes frontend \"tanstack-start\") (eq backend \"self\"))}}\nconst getORPCClient = createIsomorphicFn()\n\t.server(() =>\n\t\tcreateRouterClient(appRouter, {\n\t\t\tcontext: async () => {\n\t\t\t\treturn createContext({ req: getRequest() });\n\t\t\t},\n\t\t}),\n\t)\n\t.client((): RouterClient<typeof appRouter> => {\n\t\t\tconst link = new RPCLink({\n\t\t\turl: `${window.location.origin}/api/rpc`,\n{{#if (eq auth \"better-auth\")}}\n\t\t\tfetch(url, options) {\n\t\t\t\treturn fetch(url, {\n\t\t\t\t\t...options,\n\t\t\t\t\tcredentials: \"include\",\n\t\t\t\t});\n\t\t\t},\n{{/if}}\n\t\t});\n\n\t\treturn createORPCClient(link);\n\t});\n\nexport const client: RouterClient<typeof appRouter> = getORPCClient();\n{{else if (includes frontend \"tanstack-start\")}}\nconst link = new RPCLink({\n\turl: `${env.VITE_SERVER_URL}/rpc`,\n{{#if (eq auth \"clerk\")}}\n\theaders: async () => {\n\t\tconst token = await getClerkAuthToken();\n\t\treturn token ? { Authorization: `Bearer ${token}` } : {};\n\t},\n{{/if}}\n{{#if (eq auth \"better-auth\")}}\n\tfetch(url, options) {\n\t\treturn fetch(url, {\n\t\t\t...options,\n\t\t\tcredentials: \"include\",\n\t\t});\n\t},\n{{/if}}\n});\n\nconst getORPCClient = () => {\n\treturn createORPCClient(link) as RouterClient<AppRouter>;\n};\n\nexport const client: RouterClient<AppRouter> = getORPCClient();\n{{else}}\nexport const link = new RPCLink({\n{{#if (and (eq backend \"self\") (includes frontend \"next\"))}}\n\turl: `${typeof window !== \"undefined\" ? window.location.origin : \"http://localhost:3001\"}/api/rpc`,\n{{else if (includes frontend \"next\")}}\n\turl: `${env.NEXT_PUBLIC_SERVER_URL}/rpc`,\n{{else}}\n\turl: `${env.VITE_SERVER_URL}/rpc`,\n{{/if}}\n{{#if (eq auth \"clerk\")}}\n\theaders: async () => {\n\t\t{{#if (includes frontend \"next\")}}\n\t\tif (typeof window !== \"undefined\") {\n\t\t\tconst token = await getClerkAuthToken();\n\t\t\treturn token ? { Authorization: `Bearer ${token}` } : {};\n\t\t}\n\n\t\tconst { auth } = await import(\"@clerk/nextjs/server\");\n\t\tconst clerkAuth = await auth();\n\t\tconst token = await clerkAuth.getToken();\n\n\t\treturn token ? { Authorization: `Bearer ${token}` } : {};\n\t\t{{else}}\n\t\tconst token = await getClerkAuthToken();\n\t\treturn token ? { Authorization: `Bearer ${token}` } : {};\n\t\t{{/if}}\n\t},\n{{/if}}\n{{#if (eq auth \"better-auth\")}}\n\tfetch(url, options) {\n\t\treturn fetch(url, {\n\t\t\t...options,\n\t\t\tcredentials: \"include\",\n\t\t});\n\t},\n{{#if (includes frontend \"next\")}}\n\theaders: async () => {\n\t\tif (typeof window !== \"undefined\") {\n\t\t\treturn {}\n\t\t}\n\n\t\tconst { headers } = await import(\"next/headers\")\n\t\treturn Object.fromEntries(await headers())\n\t},\n{{/if}}\n{{/if}}\n});\n\nexport const client: AppRouterClient = createORPCClient(link)\n{{/if}}\n\nexport const orpc = createTanstackQueryUtils(client)\n"
  },
  {
    "path": "packages/template-generator/templates/api/orpc/web/solid/src/utils/orpc.ts.hbs",
    "content": "import { createORPCClient } from \"@orpc/client\";\nimport { RPCLink } from \"@orpc/client/fetch\";\nimport { createTanstackQueryUtils } from \"@orpc/tanstack-query\";\nimport { QueryCache, QueryClient } from \"@tanstack/solid-query\";\nimport type { AppRouterClient } from \"@{{projectName}}/api/routers/index\";\nimport { env } from \"@{{projectName}}/env/web\";\n\nexport const queryClient = new QueryClient({\n\tqueryCache: new QueryCache({\n\t\tonError: (error) => {\n\t\t\tconsole.error(`Error: ${error.message}`);\n\t\t},\n\t}),\n});\n\nexport const link = new RPCLink({\n\turl: `${env.VITE_SERVER_URL}/rpc`,\n{{#if (eq auth \"better-auth\")}}\n\tfetch(url, options) {\n\t\treturn fetch(url, {\n\t\t\t...options,\n\t\t\tcredentials: \"include\",\n\t\t});\n\t},\n{{/if}}\n});\n\nexport const client: AppRouterClient = createORPCClient(link);\n\nexport const orpc = createTanstackQueryUtils(client);\n"
  },
  {
    "path": "packages/template-generator/templates/api/orpc/web/svelte/src/lib/orpc.ts.hbs",
    "content": "{{#unless (eq backend \"self\")}}\nimport { PUBLIC_SERVER_URL } from \"$env/static/public\";\n{{/unless}}\nimport { createORPCClient } from \"@orpc/client\";\nimport { RPCLink } from \"@orpc/client/fetch\";\nimport { createTanstackQueryUtils } from \"@orpc/tanstack-query\";\nimport { QueryCache, QueryClient } from \"@tanstack/svelte-query\";\nimport type { AppRouterClient } from \"@{{projectName}}/api/routers/index\";\n\nexport const queryClient = new QueryClient({\n\tqueryCache: new QueryCache({\n\t\tonError: (error) => {\n\t\t\tconsole.error(`Error: ${error.message}`);\n\t\t},\n\t}),\n});\n\nexport const link = new RPCLink({\n\t{{#if (eq backend \"self\")}}\n\turl: () => {\n\t\tif (typeof window === \"undefined\") {\n\t\t\tthrow new Error(\"This link is not allowed on the server side.\");\n\t\t}\n\n\t\treturn `${window.location.origin}/rpc`;\n\t},\n\t{{else}}\n\turl: `${PUBLIC_SERVER_URL}/rpc`,\n\t{{/if}}\n\t{{#if (eq auth \"better-auth\")}}\n\tfetch(url, options) {\n\t\treturn fetch(url, {\n\t\t\t...options,\n\t\t\tcredentials: \"include\",\n\t\t});\n\t},\n\t{{/if}}\n});\n\n{{#if (eq backend \"self\")}}\nexport const client: AppRouterClient = globalThis.$client ?? createORPCClient(link);\n{{else}}\nexport const client: AppRouterClient = createORPCClient(link);\n{{/if}}\n\nexport const orpc = createTanstackQueryUtils(client);\n"
  },
  {
    "path": "packages/template-generator/templates/api/trpc/fullstack/next/src/app/api/trpc/[trpc]/route.ts.hbs",
    "content": "import { fetchRequestHandler } from \"@trpc/server/adapters/fetch\";\nimport { appRouter } from \"@{{projectName}}/api/routers/index\";\nimport { createContext } from \"@{{projectName}}/api/context\";\nimport { NextRequest } from \"next/server\";\n\nfunction handler(req: NextRequest) {\n\treturn fetchRequestHandler({\n\t\tendpoint: \"/api/trpc\",\n\t\treq,\n\t\trouter: appRouter,\n\t\tcreateContext: () => createContext(req),\n\t});\n}\nexport { handler as GET, handler as POST };\n"
  },
  {
    "path": "packages/template-generator/templates/api/trpc/fullstack/tanstack-start/src/routes/api/trpc/$.ts.hbs",
    "content": "import { fetchRequestHandler } from '@trpc/server/adapters/fetch'\nimport { appRouter } from '@{{projectName}}/api/routers/index'\nimport { createContext } from '@{{projectName}}/api/context'\nimport { createFileRoute } from '@tanstack/react-router'\n\nfunction handler({ request }: { request: Request }) {\n  return fetchRequestHandler({\n    req: request,\n    router: appRouter,\n    createContext,\n    endpoint: '/api/trpc',\n  })\n}\n\nexport const Route = createFileRoute('/api/trpc/$')({\n  server: {\n    handlers: {\n      GET: handler,\n      POST: handler,\n    },\n  },\n})\n"
  },
  {
    "path": "packages/template-generator/templates/api/trpc/native/utils/trpc.ts.hbs",
    "content": "{{#if (eq auth \"better-auth\")}}\nimport { authClient } from \"@/lib/auth-client\";\nimport { Platform } from \"react-native\";\n{{else if (eq auth \"clerk\")}}\nimport { getClerkAuthToken } from \"@/utils/clerk-auth\";\n{{/if}}\nimport { QueryClient } from \"@tanstack/react-query\";\nimport { createTRPCClient, httpBatchLink } from \"@trpc/client\";\nimport { createTRPCOptionsProxy } from \"@trpc/tanstack-react-query\";\nimport type { AppRouter } from \"@{{projectName}}/api/routers/index\";\nimport { env } from \"@{{projectName}}/env/native\";\n\nexport const queryClient = new QueryClient();\n\nconst trpcClient = createTRPCClient<AppRouter>({\n\tlinks: [\n\t\thttpBatchLink({\n{{#if (eq backend \"self\")}}\n\t\t\turl: `${env.EXPO_PUBLIC_SERVER_URL}/api/trpc`,\n{{else}}\n\t\t\turl: `${env.EXPO_PUBLIC_SERVER_URL}/trpc`,\n{{/if}}\n{{#if (eq auth \"better-auth\")}}\n\t\t\tfetch:\n\t\t\t\tfunction (url, options) {\n\t\t\t\t\treturn fetch(url, {\n\t\t\t\t\t\t...options,\n\t\t\t\t\t\t// Better Auth Expo forwards the session cookie manually on native.\n\t\t\t\t\t\tcredentials: Platform.OS === \"web\" ? \"include\" : \"omit\",\n\t\t\t\t\t});\n\t\t\t\t},\n\t\t\theaders() {\n\t\t\t\tif (Platform.OS === \"web\") {\n\t\t\t\t\treturn {};\n\t\t\t\t}\n\t\t\t\tconst headers = new Map<string, string>();\n\t\t\t\tconst cookies = authClient.getCookie();\n\t\t\t\tif (cookies) {\n\t\t\t\t\theaders.set(\"Cookie\", cookies);\n\t\t\t\t}\n\t\t\t\treturn Object.fromEntries(headers);\n\t\t\t},\n{{else if (eq auth \"clerk\")}}\n\t\t\theaders: async function () {\n\t\t\t\tconst token = await getClerkAuthToken();\n\t\t\t\treturn token ? { Authorization: `Bearer ${token}` } : {};\n\t\t\t},\n{{/if}}\n\t\t}),\n\t],\n});\n\nexport const trpc = createTRPCOptionsProxy<AppRouter>({\n\tclient: trpcClient,\n\tqueryClient,\n});\n"
  },
  {
    "path": "packages/template-generator/templates/api/trpc/server/_gitignore",
    "content": "# dependencies (bun install)\nnode_modules\n\n# output\nout\ndist\n*.tgz\n\n# code coverage\ncoverage\n*.lcov\n\n# logs\nlogs\n_.log\nreport.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json\n\n# dotenv environment variable files\n.env\n.env.development.local\n.env.test.local\n.env.production.local\n.env.local\n\n# caches\n.eslintcache\n.cache\n*.tsbuildinfo\n\n# IntelliJ based IDEs\n.idea\n\n# Finder (MacOS) folder config\n.DS_Store\n"
  },
  {
    "path": "packages/template-generator/templates/api/trpc/server/package.json.hbs",
    "content": "{\n  \"name\": \"@{{projectName}}/api\",\n  \"exports\": {\n    \".\": {\n      \"default\": \"./src/index.ts\"\n    },\n    \"./*\": {\n      \"default\": \"./src/*.ts\"\n    }\n  },\n  \"type\": \"module\",\n  \"scripts\": {},\n  \"devDependencies\": {}\n}"
  },
  {
    "path": "packages/template-generator/templates/api/trpc/server/src/context.ts.hbs",
    "content": "{{#if (eq auth \"clerk\")}}\ntype ClerkContextAuth = {\n\tuserId: string | null;\n};\n\ntype ClerkRequestContext = {\n\tauth: ClerkContextAuth | null;\n\tsession: null;\n};\n\nfunction toClerkContextAuth(auth: { userId: string | null } | null): ClerkContextAuth | null {\n\treturn auth ? { userId: auth.userId } : null;\n}\n{{/if}}\n\n{{#if (and (eq auth \"clerk\") (or (eq backend 'self') (eq backend 'hono') (eq backend 'elysia')))}}\nimport { createClerkClient } from \"@clerk/backend\";\nimport { env } from \"@{{projectName}}/env/server\";\n\nconst clerkClient = createClerkClient({\n\tsecretKey: env.CLERK_SECRET_KEY,\n\tpublishableKey: env.CLERK_PUBLISHABLE_KEY,\n});\n\nasync function authenticateClerkRequest(request: Request): Promise<ClerkContextAuth | null> {\n\tconst requestState = await clerkClient.authenticateRequest(request, {\n\t\tauthorizedParties: [env.CORS_ORIGIN],\n\t});\n\treturn toClerkContextAuth(requestState.toAuth());\n}\n{{/if}}\n\n{{#if (and (eq backend 'self') (includes frontend \"next\"))}}\nimport type { NextRequest } from \"next/server\";\n{{#if (eq auth \"better-auth\")}}\n{{#if (or (eq runtime \"workers\") (eq serverDeploy \"cloudflare\") (and (eq backend \"self\") (eq webDeploy \"cloudflare\")))}}\nimport { createAuth } from \"@{{projectName}}/auth\";\n{{else}}\nimport { auth } from \"@{{projectName}}/auth\";\n{{/if}}\n{{/if}}\n\nexport async function createContext(req: NextRequest){{#if (eq auth \"clerk\")}}: Promise<ClerkRequestContext>{{/if}} {\n{{#if (eq auth \"better-auth\")}}\n\tconst session = await {{#if (or (eq runtime \"workers\") (eq serverDeploy \"cloudflare\") (and (eq backend \"self\") (eq webDeploy \"cloudflare\")))}}createAuth(){{else}}auth{{/if}}.api.getSession({\n\t\theaders: req.headers,\n\t});\n\treturn {\n\t\tauth: null,\n\t\tsession,\n\t};\n{{else if (eq auth \"clerk\")}}\n\tconst clerkAuth = await authenticateClerkRequest(req);\n\treturn {\n\t\tauth: clerkAuth,\n\t\tsession: null,\n\t};\n{{else}}\n\treturn {\n\t\tauth: null,\n\t\tsession: null,\n\t};\n{{/if}}\n}\n\n{{else if (and (eq backend 'self') (includes frontend \"tanstack-start\"))}}\n{{#if (eq auth \"better-auth\")}}\n{{#if (or (eq runtime \"workers\") (eq serverDeploy \"cloudflare\") (and (eq backend \"self\") (eq webDeploy \"cloudflare\")))}}\nimport { createAuth } from \"@{{projectName}}/auth\";\n{{else}}\nimport { auth } from \"@{{projectName}}/auth\";\n{{/if}}\n{{/if}}\n\nexport async function createContext({ req }: { req: Request }){{#if (eq auth \"clerk\")}}: Promise<ClerkRequestContext>{{/if}} {\n{{#if (eq auth \"better-auth\")}}\n\tconst session = await {{#if (or (eq runtime \"workers\") (eq serverDeploy \"cloudflare\") (and (eq backend \"self\") (eq webDeploy \"cloudflare\")))}}createAuth(){{else}}auth{{/if}}.api.getSession({\n\t\theaders: req.headers,\n\t});\n\treturn {\n\t\tauth: null,\n\t\tsession,\n\t};\n{{else if (eq auth \"clerk\")}}\n\tconst clerkAuth = await authenticateClerkRequest(req);\n\treturn {\n\t\tauth: clerkAuth,\n\t\tsession: null,\n\t};\n{{else}}\n\treturn {\n\t\tauth: null,\n\t\tsession: null,\n\t};\n{{/if}}\n}\n\n{{else if (eq backend 'hono')}}\nimport type { Context as HonoContext } from \"hono\";\n{{#if (eq auth \"better-auth\")}}\n{{#if (or (eq runtime \"workers\") (eq serverDeploy \"cloudflare\") (and (eq backend \"self\") (eq webDeploy \"cloudflare\")))}}\nimport { createAuth } from \"@{{projectName}}/auth\";\n{{else}}\nimport { auth } from \"@{{projectName}}/auth\";\n{{/if}}\n{{/if}}\n\nexport type CreateContextOptions = {\n\tcontext: HonoContext;\n};\n\nexport async function createContext({ context }: CreateContextOptions){{#if (eq auth \"clerk\")}}: Promise<ClerkRequestContext>{{/if}} {\n{{#if (eq auth \"better-auth\")}}\n\tconst session = await {{#if (or (eq runtime \"workers\") (eq serverDeploy \"cloudflare\") (and (eq backend \"self\") (eq webDeploy \"cloudflare\")))}}createAuth(){{else}}auth{{/if}}.api.getSession({\n\t\theaders: context.req.raw.headers,\n\t});\n\treturn {\n\t\tauth: null,\n\t\tsession,\n\t};\n{{else if (eq auth \"clerk\")}}\n\tconst clerkAuth = await authenticateClerkRequest(context.req.raw);\n\treturn {\n\t\tauth: clerkAuth,\n\t\tsession: null,\n\t};\n{{else}}\n\treturn {\n\t\tauth: null,\n\t\tsession: null,\n\t};\n{{/if}}\n}\n\n{{else if (eq backend 'elysia')}}\nimport type { Context as ElysiaContext } from \"elysia\";\n{{#if (eq auth \"better-auth\")}}\nimport { auth } from \"@{{projectName}}/auth\";\n{{/if}}\n\nexport type CreateContextOptions = {\n\tcontext: ElysiaContext;\n};\n\nexport async function createContext({ context }: CreateContextOptions){{#if (eq auth \"clerk\")}}: Promise<ClerkRequestContext>{{/if}} {\n{{#if (eq auth \"better-auth\")}}\n\tconst session = await auth.api.getSession({\n\t\theaders: context.request.headers,\n\t});\n\treturn {\n\t\tauth: null,\n\t\tsession,\n\t};\n{{else if (eq auth \"clerk\")}}\n\tconst clerkAuth = await authenticateClerkRequest(context.request);\n\treturn {\n\t\tauth: clerkAuth,\n\t\tsession: null,\n\t};\n{{else}}\n\treturn {\n\t\tauth: null,\n\t\tsession: null,\n\t};\n{{/if}}\n}\n\n{{else if (eq backend 'express')}}\nimport type { CreateExpressContextOptions } from \"@trpc/server/adapters/express\";\n{{#if (eq auth \"better-auth\")}}\nimport { fromNodeHeaders } from \"better-auth/node\";\nimport { auth } from \"@{{projectName}}/auth\";\n{{else if (eq auth \"clerk\")}}\nimport { getAuth } from \"@clerk/express\";\n{{/if}}\n\nexport async function createContext(opts: CreateExpressContextOptions){{#if (eq auth \"clerk\")}}: Promise<ClerkRequestContext>{{/if}} {\n{{#if (eq auth \"better-auth\")}}\n\tconst session = await auth.api.getSession({\n\t\theaders: fromNodeHeaders(opts.req.headers),\n\t});\n\treturn {\n\t\tauth: null,\n\t\tsession,\n\t};\n{{else if (eq auth \"clerk\")}}\n\tconst clerkAuth = toClerkContextAuth(getAuth(opts.req));\n\treturn {\n\t\tauth: clerkAuth,\n\t\tsession: null,\n\t};\n{{else}}\n\treturn {\n\t\tauth: null,\n\t\tsession: null,\n\t};\n{{/if}}\n}\n\n{{else if (eq backend 'fastify')}}\nimport type { CreateFastifyContextOptions } from \"@trpc/server/adapters/fastify\";\n{{#if (eq auth \"better-auth\")}}\nimport { fromNodeHeaders } from \"better-auth/node\";\nimport { auth } from \"@{{projectName}}/auth\";\n{{else if (eq auth \"clerk\")}}\nimport { getAuth } from \"@clerk/fastify\";\n{{/if}}\n\nexport async function createContext({ req }: CreateFastifyContextOptions){{#if (eq auth \"clerk\")}}: Promise<ClerkRequestContext>{{/if}} {\n{{#if (eq auth \"better-auth\")}}\n\tconst session = await auth.api.getSession({\n\t\theaders: fromNodeHeaders(req.headers),\n\t});\n\treturn {\n\t\tauth: null,\n\t\tsession,\n\t};\n{{else if (eq auth \"clerk\")}}\n\tconst clerkAuth = toClerkContextAuth(getAuth(req));\n\treturn {\n\t\tauth: clerkAuth,\n\t\tsession: null,\n\t};\n{{else}}\n\treturn {\n\t\tauth: null,\n\t\tsession: null,\n\t};\n{{/if}}\n}\n\n{{else}}\nexport async function createContext() {\n\treturn {\n\t\tauth: null,\n\t\tsession: null,\n\t};\n}\n{{/if}}\n\nexport type Context = Awaited<ReturnType<typeof createContext>>;\n"
  },
  {
    "path": "packages/template-generator/templates/api/trpc/server/src/index.ts.hbs",
    "content": "import { initTRPC, TRPCError } from \"@trpc/server\";\nimport type { Context } from \"./context\";\n\nexport const t = initTRPC.context<Context>().create();\n\nexport const router = t.router;\n\nexport const publicProcedure = t.procedure;\n\n{{#if (or (eq auth \"better-auth\") (eq auth \"clerk\"))}}\nexport const protectedProcedure = t.procedure.use(({ ctx, next }) => {\n  {{#if (eq auth \"better-auth\")}}\n  if (!ctx.session) {\n    throw new TRPCError({\n      code: \"UNAUTHORIZED\",\n      message: \"Authentication required\",\n      cause: \"No session\",\n    });\n  }\n  {{else}}\n  if (!ctx.auth?.userId) {\n    throw new TRPCError({\n      code: \"UNAUTHORIZED\",\n      message: \"Authentication required\",\n      cause: \"No Clerk userId\",\n    });\n  }\n  {{/if}}\n  return next({\n    ctx: {\n      ...ctx,\n      {{#if (eq auth \"better-auth\")}}\n      session: ctx.session,\n      {{else}}\n      auth: ctx.auth,\n      {{/if}}\n    },\n  });\n});\n{{/if}}\n"
  },
  {
    "path": "packages/template-generator/templates/api/trpc/server/src/routers/index.ts.hbs",
    "content": "{{#if (eq api \"orpc\")}}\nimport { {{#if (eq auth \"better-auth\")}}protectedProcedure, {{/if}}publicProcedure } from \"../index\";\nimport type { RouterClient } from \"@orpc/server\";\n{{#if (includes examples \"todo\")}}\nimport { todoRouter } from \"./todo\";\n{{/if}}\n\nexport const appRouter = {\n  healthCheck: publicProcedure.handler(() => {\n    return \"OK\";\n  }),\n  {{#if (eq auth \"better-auth\")}}\n  privateData: protectedProcedure.handler(({ context }) => {\n    return {\n      message: \"This is private\",\n      user: context.session?.user,\n    };\n  }),\n  {{/if}}\n  {{#if (includes examples \"todo\")}}\n  todo: todoRouter,\n  {{/if}}\n};\nexport type AppRouter = typeof appRouter;\nexport type AppRouterClient = RouterClient<typeof appRouter>;\n{{else if (eq api \"trpc\")}}\nimport {\n  {{#if (or (eq auth \"better-auth\") (eq auth \"clerk\"))}}protectedProcedure, {{/if}}publicProcedure,\n  router,\n} from \"../index\";\n{{#if (includes examples \"todo\")}}\nimport { todoRouter } from \"./todo\";\n{{/if}}\n\nexport const appRouter = router({\n  healthCheck: publicProcedure.query(() => {\n    return \"OK\";\n  }),\n  {{#if (or (eq auth \"better-auth\") (eq auth \"clerk\"))}}\n  privateData: protectedProcedure.query(({ ctx }) => {\n    return {\n      message: \"This is private\",\n      {{#if (eq auth \"better-auth\")}}\n      user: ctx.session.user,\n      {{else}}\n      userId: ctx.auth.userId,\n      {{/if}}\n    };\n  }),\n  {{/if}}\n  {{#if (includes examples \"todo\")}}\n  todo: todoRouter,\n  {{/if}}\n});\nexport type AppRouter = typeof appRouter;\n{{else}}\nexport const appRouter = {};\nexport type AppRouter = typeof appRouter;\n{{/if}}\n"
  },
  {
    "path": "packages/template-generator/templates/api/trpc/server/tsconfig.json.hbs",
    "content": "{\n  \"extends\": \"@{{projectName}}/config/tsconfig.base.json\",\n  \"compilerOptions\": {\n    \"declaration\": true,\n    \"declarationMap\": true,\n    \"sourceMap\": true,\n    \"outDir\": \"dist\",\n    \"composite\": true\n  }\n}"
  },
  {
    "path": "packages/template-generator/templates/api/trpc/web/react/base/src/utils/trpc.ts.hbs",
    "content": "{{#if (includes frontend 'next')}}\nimport { QueryCache, QueryClient } from '@tanstack/react-query';\nimport { createTRPCClient, httpBatchLink } from '@trpc/client';\nimport { createTRPCOptionsProxy } from '@trpc/tanstack-react-query';\nimport type { AppRouter } from \"@{{projectName}}/api/routers/index\";\nimport { toast } from 'sonner';\n{{#unless (eq backend \"self\")}}\nimport { env } from \"@{{projectName}}/env/web\";\n{{/unless}}\n{{#if (eq auth \"clerk\")}}\nimport { getClerkAuthToken } from \"@/utils/clerk-auth\";\n{{/if}}\n\nexport const queryClient = new QueryClient({\n\tqueryCache: new QueryCache({\n\t\tonError: (error, query) => {\n\t\t\ttoast.error(error.message, {\n\t\t\t\taction: {\n\t\t\t\t\tlabel: \"retry\",\n\t\t\t\t\tonClick: query.invalidate,\n\t\t\t\t},\n\t\t\t});\n\t\t},\n\t}),\n});\n\nconst trpcClient = createTRPCClient<AppRouter>({\n\tlinks: [\n\t\thttpBatchLink({\n{{#if (eq backend \"self\")}}\n\t\t\turl: \"/api/trpc\",\n{{else}}\n\t\t\turl: `${env.NEXT_PUBLIC_SERVER_URL}/trpc`,\n{{/if}}\n{{#if (eq auth \"clerk\")}}\n\t\t\theaders: async () => {\n\t\t\t\tif (typeof window !== \"undefined\") {\n\t\t\t\t\tconst token = await getClerkAuthToken();\n\t\t\t\t\treturn token ? { Authorization: `Bearer ${token}` } : {};\n\t\t\t\t}\n\n\t\t\t\tconst { auth } = await import(\"@clerk/nextjs/server\");\n\t\t\t\tconst clerkAuth = await auth();\n\t\t\t\tconst token = await clerkAuth.getToken();\n\n\t\t\t\treturn token ? { Authorization: `Bearer ${token}` } : {};\n\t\t\t},\n{{/if}}\n{{#if (eq auth \"better-auth\")}}\n\t\t\tfetch(url, options) {\n\t\t\t\treturn fetch(url, {\n\t\t\t\t\t...options,\n\t\t\t\t\tcredentials: \"include\",\n\t\t\t\t});\n\t\t\t},\n{{/if}}\n\t\t}),\n\t],\n})\n\nexport const trpc = createTRPCOptionsProxy<AppRouter>({\n\tclient: trpcClient,\n\tqueryClient,\n});\n\n{{else if (includes frontend 'tanstack-start')}}\nimport { createTRPCContext } from \"@trpc/tanstack-react-query\";\nimport type { AppRouter } from \"@{{projectName}}/api/routers/index\";\n\nexport const { TRPCProvider, useTRPC, useTRPCClient } =\n\tcreateTRPCContext<AppRouter>();\n\n{{else}}\nimport type { AppRouter } from \"@{{projectName}}/api/routers/index\";\nimport { QueryCache, QueryClient } from \"@tanstack/react-query\";\nimport { createTRPCClient, httpBatchLink } from \"@trpc/client\";\nimport { createTRPCOptionsProxy } from \"@trpc/tanstack-react-query\";\nimport { toast } from \"sonner\";\nimport { env } from \"@{{projectName}}/env/web\";\n{{#if (eq auth \"clerk\")}}\nimport { getClerkAuthToken } from \"@/utils/clerk-auth\";\n{{/if}}\n\nexport const queryClient = new QueryClient({\n\tqueryCache: new QueryCache({\n\t\tonError: (error, query) => {\n\t\t\ttoast.error(error.message, {\n\t\t\t\taction: {\n\t\t\t\t\tlabel: \"retry\",\n\t\t\t\t\tonClick: query.invalidate,\n\t\t\t\t},\n\t\t\t});\n\t\t},\n\t}),\n});\n\nexport const trpcClient = createTRPCClient<AppRouter>({\n\tlinks: [\n\t\thttpBatchLink({\n\t\t\turl: `${env.VITE_SERVER_URL}/trpc`,\n{{#if (eq auth \"clerk\")}}\n\t\t\theaders: async () => {\n\t\t\t\tconst token = await getClerkAuthToken();\n\t\t\t\treturn token ? { Authorization: `Bearer ${token}` } : {};\n\t\t\t},\n{{/if}}\n{{#if (eq auth \"better-auth\")}}\n\t\t\tfetch(url, options) {\n\t\t\t\treturn fetch(url, {\n\t\t\t\t\t...options,\n\t\t\t\t\tcredentials: \"include\",\n\t\t\t\t});\n\t\t\t},\n{{/if}}\n\t\t}),\n\t],\n});\n\nexport const trpc = createTRPCOptionsProxy<AppRouter>({\n\tclient: trpcClient,\n\tqueryClient,\n});\n{{/if}}\n"
  },
  {
    "path": "packages/template-generator/templates/auth/better-auth/convex/backend/convex/auth.config.ts.hbs",
    "content": "import { getAuthConfigProvider } from \"@convex-dev/better-auth/auth-config\";\nimport type { AuthConfig } from \"convex/server\";\n\nexport default {\n  providers: [getAuthConfigProvider()],\n} satisfies AuthConfig;\n"
  },
  {
    "path": "packages/template-generator/templates/auth/better-auth/convex/backend/convex/auth.ts.hbs",
    "content": "import { createClient, type GenericCtx } from \"@convex-dev/better-auth\";\n{{#if (or (includes frontend \"native-bare\") (includes frontend \"native-uniwind\") (includes frontend \"native-unistyles\") (includes frontend \"tanstack-router\") (includes frontend \"react-router\") (includes frontend \"nuxt\") (includes frontend \"svelte\") (includes frontend \"solid\"))}}\nimport { convex, crossDomain } from \"@convex-dev/better-auth/plugins\";\n{{else}}\nimport { convex } from \"@convex-dev/better-auth/plugins\";\n{{/if}}\n{{#if (or (includes frontend \"native-bare\") (includes frontend \"native-uniwind\") (includes frontend \"native-unistyles\"))}}\nimport { expo } from \"@better-auth/expo\";\n{{/if}}\nimport { components } from \"./_generated/api\";\nimport type { DataModel } from \"./_generated/dataModel\";\nimport { query } from \"./_generated/server\";\nimport { betterAuth } from \"better-auth/minimal\";\nimport authConfig from \"./auth.config\";\n\n{{#if (or (includes frontend \"tanstack-start\") (includes frontend \"next\") (includes frontend \"tanstack-router\") (includes frontend \"react-router\") (includes frontend \"nuxt\") (includes frontend \"svelte\") (includes frontend \"solid\") (includes frontend \"native-bare\") (includes frontend \"native-uniwind\") (includes frontend \"native-unistyles\"))}}\nconst siteUrl = process.env.SITE_URL{{#if (or (includes frontend \"tanstack-start\") (includes frontend \"next\") (includes frontend \"tanstack-router\") (includes frontend \"react-router\") (includes frontend \"nuxt\") (includes frontend \"svelte\") (includes frontend \"solid\"))}}!{{else}} || \"http://localhost:8081\"{{/if}};\n{{/if}}\n{{#if (or (includes frontend \"native-bare\") (includes frontend \"native-uniwind\") (includes frontend \"native-unistyles\"))}}\nconst nativeAppUrl = process.env.NATIVE_APP_URL || \"{{projectName}}://\";\n{{/if}}\n\nexport const authComponent = createClient<DataModel>(components.betterAuth);\n\nfunction createAuth(ctx: GenericCtx<DataModel>) {\n  return betterAuth({\n    {{#if (or (includes frontend \"tanstack-start\") (includes frontend \"next\"))}}\n    baseURL: siteUrl,\n    {{/if}}\n    {{#if (or (includes frontend \"native-bare\") (includes frontend \"native-uniwind\") (includes frontend \"native-unistyles\"))}}\n    trustedOrigins: [siteUrl, nativeAppUrl, ...(process.env.NODE_ENV === \"development\" ? [\"exp://\", \"exp://**\", \"exp://192.168.*.*:*/**\"] : [])],\n    {{else if (or (includes frontend \"tanstack-router\") (includes frontend \"react-router\") (includes frontend \"nuxt\") (includes frontend \"svelte\") (includes frontend \"solid\"))}}\n    trustedOrigins: [siteUrl],\n    {{else if (or (includes frontend \"tanstack-start\") (includes frontend \"next\"))}}\n    trustedOrigins: [siteUrl],\n    {{/if}}\n    database: authComponent.adapter(ctx),\n    emailAndPassword: {\n      enabled: true,\n      requireEmailVerification: false,\n    },\n    plugins: [\n      {{#if (or (includes frontend \"native-bare\") (includes frontend \"native-uniwind\") (includes frontend \"native-unistyles\"))}}\n      expo(),\n      {{/if}}\n      {{#if (or (includes frontend \"native-bare\") (includes frontend \"native-uniwind\") (includes frontend \"native-unistyles\") (includes frontend \"tanstack-router\") (includes frontend \"react-router\") (includes frontend \"nuxt\") (includes frontend \"svelte\") (includes frontend \"solid\"))}}\n      crossDomain({ siteUrl }),\n      {{/if}}\n      convex({\n        authConfig,\n        jwksRotateOnTokenGenerationError: true,\n      }),\n    ],\n  });\n}\n\nexport { createAuth };\n\nexport const getCurrentUser = query({\n  args: {},\n  handler: async (ctx) => {\n    return await authComponent.safeGetAuthUser(ctx);\n  },\n});\n"
  },
  {
    "path": "packages/template-generator/templates/auth/better-auth/convex/backend/convex/http.ts.hbs",
    "content": "import { httpRouter } from \"convex/server\";\nimport { authComponent, createAuth } from \"./auth\";\n\nconst http = httpRouter();\n\n{{#if (or (includes frontend \"native-bare\") (includes frontend \"native-uniwind\") (includes frontend \"native-unistyles\") (includes frontend \"tanstack-router\") (includes frontend \"react-router\") (includes frontend \"nuxt\") (includes frontend \"svelte\") (includes frontend \"solid\"))}}\n{{#if (or (includes frontend \"tanstack-router\") (includes frontend \"react-router\") (includes frontend \"nuxt\") (includes frontend \"svelte\") (includes frontend \"solid\"))}}\nauthComponent.registerRoutesLazy(http, createAuth, {\n  cors: true,\n  trustedOrigins: [process.env.SITE_URL!],\n});\n{{else}}\nauthComponent.registerRoutes(http, createAuth, { cors: true });\n{{/if}}\n{{else}}\nauthComponent.registerRoutes(http, createAuth);\n{{/if}}\n\nexport default http;\n"
  },
  {
    "path": "packages/template-generator/templates/auth/better-auth/convex/backend/convex/privateData.ts.hbs",
    "content": "import { query } from \"./_generated/server\";\nimport { authComponent } from \"./auth\";\n\nexport const get = query({\n  args: {},\n  handler: async (ctx) => {\n    const authUser = await authComponent.safeGetAuthUser(ctx);\n    if (!authUser) {\n      return {\n        message: \"Not authenticated\",\n      };\n    }\n    return {\n      message: \"This is private\",\n    };\n  },\n});\n"
  },
  {
    "path": "packages/template-generator/templates/auth/better-auth/convex/native/bare/components/sign-in.tsx.hbs",
    "content": "import { authClient } from \"@/lib/auth-client\";\nimport { useForm } from \"@tanstack/react-form\";\nimport { useState } from \"react\";\nimport {\n  ActivityIndicator,\n  StyleSheet,\n  Text,\n  TextInput,\n  TouchableOpacity,\n  View,\n} from \"react-native\";\nimport { NAV_THEME } from \"@/lib/constants\";\nimport { useColorScheme } from \"@/lib/use-color-scheme\";\nimport z from \"zod\";\n\nconst signInSchema = z.object({\n  email: z\n    .string()\n    .trim()\n    .min(1, \"Email is required\")\n    .email(\"Enter a valid email address\"),\n  password: z\n    .string()\n    .min(1, \"Password is required\")\n    .min(8, \"Use at least 8 characters\"),\n});\n\nfunction getErrorMessage(error: unknown): string | null {\n  if (!error) return null;\n\n  if (typeof error === \"string\") {\n    return error;\n  }\n\n  if (Array.isArray(error)) {\n    for (const issue of error) {\n      const message = getErrorMessage(issue);\n      if (message) {\n        return message;\n      }\n    }\n    return null;\n  }\n\n  if (typeof error === \"object\" && error !== null) {\n    const maybeError = error as { message?: unknown };\n    if (typeof maybeError.message === \"string\") {\n      return maybeError.message;\n    }\n  }\n\n  return null;\n}\n\nfunction SignIn() {\n  const { colorScheme } = useColorScheme();\n  const theme = colorScheme === \"dark\" ? NAV_THEME.dark : NAV_THEME.light;\n  const [error, setError] = useState<string | null>(null);\n\n  const form = useForm({\n    defaultValues: {\n      email: \"\",\n      password: \"\",\n    },\n    validators: {\n      onSubmit: signInSchema,\n    },\n    onSubmit: async ({ value, formApi }) => {\n      await authClient.signIn.email(\n        {\n          email: value.email.trim(),\n          password: value.password,\n        },\n        {\n          onError(error) {\n            setError(error.error?.message || \"Failed to sign in\");\n          },\n          onSuccess() {\n            setError(null);\n            formApi.reset();\n          },\n        },\n      );\n    },\n  });\n\n  return (\n    <View style={[styles.card, { backgroundColor: theme.card, borderColor: theme.border }]}>\n      <Text style={[styles.title, { color: theme.text }]}>Sign In</Text>\n\n      <form.Subscribe\n        selector={(state) => ({\n          isSubmitting: state.isSubmitting,\n          validationError: getErrorMessage(state.errorMap.onSubmit),\n        })}\n      >\n        {({ isSubmitting, validationError }) => {\n          const formError = error ?? validationError;\n\n          return (\n            <>\n              {formError ? (\n                <View style={[styles.errorContainer, { backgroundColor: theme.notification + \"20\" }]}>\n                  <Text style={[styles.errorText, { color: theme.notification }]}>{formError}</Text>\n                </View>\n              ) : null}\n\n              <form.Field name=\"email\">\n                {(field) => (\n                  <TextInput\n                    style={[\n                      styles.input,\n                      {\n                        color: theme.text,\n                        borderColor: theme.border,\n                        backgroundColor: theme.background,\n                      },\n                    ]}\n                    placeholder=\"Email\"\n                    placeholderTextColor={theme.text}\n                    value={field.state.value}\n                    onBlur={field.handleBlur}\n                    onChangeText={(value) => {\n                      field.handleChange(value);\n                      if (error) {\n                        setError(null);\n                      }\n                    }}\n                    keyboardType=\"email-address\"\n                    autoCapitalize=\"none\"\n                  />\n                )}\n              </form.Field>\n\n              <form.Field name=\"password\">\n                {(field) => (\n                  <TextInput\n                    style={[\n                      styles.input,\n                      {\n                        color: theme.text,\n                        borderColor: theme.border,\n                        backgroundColor: theme.background,\n                      },\n                    ]}\n                    placeholder=\"Password\"\n                    placeholderTextColor={theme.text}\n                    value={field.state.value}\n                    onBlur={field.handleBlur}\n                    onChangeText={(value) => {\n                      field.handleChange(value);\n                      if (error) {\n                        setError(null);\n                      }\n                    }}\n                    secureTextEntry\n                    onSubmitEditing={form.handleSubmit}\n                  />\n                )}\n              </form.Field>\n\n              <TouchableOpacity\n                onPress={form.handleSubmit}\n                disabled={isSubmitting}\n                style={[\n                  styles.button,\n                  {\n                    backgroundColor: theme.primary,\n                    opacity: isSubmitting ? 0.5 : 1,\n                  },\n                ]}\n              >\n                {isSubmitting ? (\n                  <ActivityIndicator size=\"small\" color=\"#ffffff\" />\n                ) : (\n                  <Text style={styles.buttonText}>Sign In</Text>\n                )}\n              </TouchableOpacity>\n            </>\n          );\n        }}\n      </form.Subscribe>\n    </View>\n  );\n}\n\nconst styles = StyleSheet.create({\n  card: {\n    marginTop: 16,\n    padding: 16,\n    borderWidth: 1,\n  },\n  title: {\n    fontSize: 18,\n    fontWeight: \"bold\",\n    marginBottom: 12,\n  },\n  errorContainer: {\n    marginBottom: 12,\n    padding: 8,\n  },\n  errorText: {\n    fontSize: 14,\n  },\n  input: {\n    borderWidth: 1,\n    padding: 12,\n    fontSize: 16,\n    marginBottom: 12,\n  },\n  button: {\n    padding: 12,\n    alignItems: \"center\",\n    justifyContent: \"center\",\n  },\n  buttonText: {\n    color: \"#ffffff\",\n    fontSize: 16,\n  },\n});\n\nexport { SignIn };\n"
  },
  {
    "path": "packages/template-generator/templates/auth/better-auth/convex/native/bare/components/sign-up.tsx.hbs",
    "content": "import { authClient } from \"@/lib/auth-client\";\nimport { useForm } from \"@tanstack/react-form\";\nimport { useState } from \"react\";\nimport {\n  ActivityIndicator,\n  StyleSheet,\n  Text,\n  TextInput,\n  TouchableOpacity,\n  View,\n} from \"react-native\";\nimport { NAV_THEME } from \"@/lib/constants\";\nimport { useColorScheme } from \"@/lib/use-color-scheme\";\nimport z from \"zod\";\n\nconst signUpSchema = z.object({\n  name: z\n    .string()\n    .trim()\n    .min(1, \"Name is required\")\n    .min(2, \"Name must be at least 2 characters\"),\n  email: z\n    .string()\n    .trim()\n    .min(1, \"Email is required\")\n    .email(\"Enter a valid email address\"),\n  password: z\n    .string()\n    .min(1, \"Password is required\")\n    .min(8, \"Use at least 8 characters\"),\n});\n\nfunction getErrorMessage(error: unknown): string | null {\n  if (!error) return null;\n\n  if (typeof error === \"string\") {\n    return error;\n  }\n\n  if (Array.isArray(error)) {\n    for (const issue of error) {\n      const message = getErrorMessage(issue);\n      if (message) {\n        return message;\n      }\n    }\n    return null;\n  }\n\n  if (typeof error === \"object\" && error !== null) {\n    const maybeError = error as { message?: unknown };\n    if (typeof maybeError.message === \"string\") {\n      return maybeError.message;\n    }\n  }\n\n  return null;\n}\n\nfunction SignUp() {\n  const { colorScheme } = useColorScheme();\n  const theme = colorScheme === \"dark\" ? NAV_THEME.dark : NAV_THEME.light;\n  const [error, setError] = useState<string | null>(null);\n\n  const form = useForm({\n    defaultValues: {\n      name: \"\",\n      email: \"\",\n      password: \"\",\n    },\n    validators: {\n      onSubmit: signUpSchema,\n    },\n    onSubmit: async ({ value, formApi }) => {\n      await authClient.signUp.email(\n        {\n          name: value.name.trim(),\n          email: value.email.trim(),\n          password: value.password,\n        },\n        {\n          onError(error) {\n            setError(error.error?.message || \"Failed to sign up\");\n          },\n          onSuccess() {\n            setError(null);\n            formApi.reset();\n          },\n        },\n      );\n    },\n  });\n\n  return (\n    <View style={[styles.card, { backgroundColor: theme.card, borderColor: theme.border }]}>\n      <Text style={[styles.title, { color: theme.text }]}>Create Account</Text>\n\n      <form.Subscribe\n        selector={(state) => ({\n          isSubmitting: state.isSubmitting,\n          validationError: getErrorMessage(state.errorMap.onSubmit),\n        })}\n      >\n        {({ isSubmitting, validationError }) => {\n          const formError = error ?? validationError;\n\n          return (\n            <>\n              {formError ? (\n                <View style={[styles.errorContainer, { backgroundColor: theme.notification + \"20\" }]}>\n                  <Text style={[styles.errorText, { color: theme.notification }]}>{formError}</Text>\n                </View>\n              ) : null}\n\n              <form.Field name=\"name\">\n                {(field) => (\n                  <TextInput\n                    style={[\n                      styles.input,\n                      {\n                        color: theme.text,\n                        borderColor: theme.border,\n                        backgroundColor: theme.background,\n                      },\n                    ]}\n                    placeholder=\"Name\"\n                    placeholderTextColor={theme.text}\n                    value={field.state.value}\n                    onBlur={field.handleBlur}\n                    onChangeText={(value) => {\n                      field.handleChange(value);\n                      if (error) {\n                        setError(null);\n                      }\n                    }}\n                  />\n                )}\n              </form.Field>\n\n              <form.Field name=\"email\">\n                {(field) => (\n                  <TextInput\n                    style={[\n                      styles.input,\n                      {\n                        color: theme.text,\n                        borderColor: theme.border,\n                        backgroundColor: theme.background,\n                      },\n                    ]}\n                    placeholder=\"Email\"\n                    placeholderTextColor={theme.text}\n                    value={field.state.value}\n                    onBlur={field.handleBlur}\n                    onChangeText={(value) => {\n                      field.handleChange(value);\n                      if (error) {\n                        setError(null);\n                      }\n                    }}\n                    keyboardType=\"email-address\"\n                    autoCapitalize=\"none\"\n                  />\n                )}\n              </form.Field>\n\n              <form.Field name=\"password\">\n                {(field) => (\n                  <TextInput\n                    style={[\n                      styles.input,\n                      {\n                        color: theme.text,\n                        borderColor: theme.border,\n                        backgroundColor: theme.background,\n                      },\n                    ]}\n                    placeholder=\"Password\"\n                    placeholderTextColor={theme.text}\n                    value={field.state.value}\n                    onBlur={field.handleBlur}\n                    onChangeText={(value) => {\n                      field.handleChange(value);\n                      if (error) {\n                        setError(null);\n                      }\n                    }}\n                    secureTextEntry\n                    onSubmitEditing={form.handleSubmit}\n                  />\n                )}\n              </form.Field>\n\n              <TouchableOpacity\n                onPress={form.handleSubmit}\n                disabled={isSubmitting}\n                style={[\n                  styles.button,\n                  {\n                    backgroundColor: theme.primary,\n                    opacity: isSubmitting ? 0.5 : 1,\n                  },\n                ]}\n              >\n                {isSubmitting ? (\n                  <ActivityIndicator size=\"small\" color=\"#ffffff\" />\n                ) : (\n                  <Text style={styles.buttonText}>Sign Up</Text>\n                )}\n              </TouchableOpacity>\n            </>\n          );\n        }}\n      </form.Subscribe>\n    </View>\n  );\n}\n\nconst styles = StyleSheet.create({\n  card: {\n    marginTop: 16,\n    padding: 16,\n    borderWidth: 1,\n  },\n  title: {\n    fontSize: 18,\n    fontWeight: \"bold\",\n    marginBottom: 12,\n  },\n  errorContainer: {\n    marginBottom: 12,\n    padding: 8,\n  },\n  errorText: {\n    fontSize: 14,\n  },\n  input: {\n    borderWidth: 1,\n    padding: 12,\n    fontSize: 16,\n    marginBottom: 12,\n  },\n  button: {\n    padding: 12,\n    alignItems: \"center\",\n    justifyContent: \"center\",\n  },\n  buttonText: {\n    color: \"#ffffff\",\n    fontSize: 16,\n  },\n});\n\nexport { SignUp };\n"
  },
  {
    "path": "packages/template-generator/templates/auth/better-auth/convex/native/base/lib/auth-client.ts.hbs",
    "content": "import { createAuthClient } from \"better-auth/react\";\nimport { convexClient, crossDomainClient } from \"@convex-dev/better-auth/client/plugins\";\nimport { expoClient } from \"@better-auth/expo/client\";\nimport Constants from \"expo-constants\";\nimport * as SecureStore from \"expo-secure-store\";\nimport { Platform } from \"react-native\";\nimport { env } from \"@{{projectName}}/env/native\";\n\nexport const authClient = createAuthClient({\n\tbaseURL: env.EXPO_PUBLIC_CONVEX_SITE_URL,\n\tplugins: [\n\t\tconvexClient(),\n\t\tPlatform.OS === \"web\"\n\t\t\t? crossDomainClient()\n\t\t\t: expoClient({\n\t\t\t\tscheme: Constants.expoConfig?.scheme as string,\n\t\t\t\tstoragePrefix: Constants.expoConfig?.scheme as string,\n\t\t\t\tstorage: SecureStore,\n\t\t\t}),\n\t],\n});\n"
  },
  {
    "path": "packages/template-generator/templates/auth/better-auth/convex/native/unistyles/components/sign-in.tsx.hbs",
    "content": "import { authClient } from \"@/lib/auth-client\";\nimport { useForm } from \"@tanstack/react-form\";\nimport { useState } from \"react\";\nimport {\n  ActivityIndicator,\n  Text,\n  TextInput,\n  TouchableOpacity,\n  View,\n} from \"react-native\";\nimport { StyleSheet } from \"react-native-unistyles\";\nimport z from \"zod\";\n\nconst signInSchema = z.object({\n  email: z\n    .string()\n    .trim()\n    .min(1, \"Email is required\")\n    .email(\"Enter a valid email address\"),\n  password: z\n    .string()\n    .min(1, \"Password is required\")\n    .min(8, \"Use at least 8 characters\"),\n});\n\nfunction getErrorMessage(error: unknown): string | null {\n  if (!error) return null;\n\n  if (typeof error === \"string\") {\n    return error;\n  }\n\n  if (Array.isArray(error)) {\n    for (const issue of error) {\n      const message = getErrorMessage(issue);\n      if (message) {\n        return message;\n      }\n    }\n    return null;\n  }\n\n  if (typeof error === \"object\" && error !== null) {\n    const maybeError = error as { message?: unknown };\n    if (typeof maybeError.message === \"string\") {\n      return maybeError.message;\n    }\n  }\n\n  return null;\n}\n\nexport function SignIn() {\n  const [error, setError] = useState<string | null>(null);\n\n  const form = useForm({\n    defaultValues: {\n      email: \"\",\n      password: \"\",\n    },\n    validators: {\n      onSubmit: signInSchema,\n    },\n    onSubmit: async ({ value, formApi }) => {\n      await authClient.signIn.email(\n        {\n          email: value.email.trim(),\n          password: value.password,\n        },\n        {\n          onError(error) {\n            setError(error.error?.message || \"Failed to sign in\");\n          },\n          onSuccess() {\n            setError(null);\n            formApi.reset();\n          },\n        },\n      );\n    },\n  });\n\n  return (\n    <View style={styles.container}>\n      <Text style={styles.title}>Sign In</Text>\n\n      <form.Subscribe\n        selector={(state) => ({\n          isSubmitting: state.isSubmitting,\n          validationError: getErrorMessage(state.errorMap.onSubmit),\n        })}\n      >\n        {({ isSubmitting, validationError }) => {\n          const formError = error ?? validationError;\n\n          return (\n            <>\n              {formError ? (\n                <View style={styles.errorContainer}>\n                  <Text style={styles.errorText}>{formError}</Text>\n                </View>\n              ) : null}\n\n              <form.Field name=\"email\">\n                {(field) => (\n                  <TextInput\n                    style={styles.input}\n                    placeholder=\"Email\"\n                    value={field.state.value}\n                    onBlur={field.handleBlur}\n                    onChangeText={(value) => {\n                      field.handleChange(value);\n                      if (error) {\n                        setError(null);\n                      }\n                    }}\n                    keyboardType=\"email-address\"\n                    autoCapitalize=\"none\"\n                  />\n                )}\n              </form.Field>\n\n              <form.Field name=\"password\">\n                {(field) => (\n                  <TextInput\n                    style={styles.input}\n                    placeholder=\"Password\"\n                    value={field.state.value}\n                    onBlur={field.handleBlur}\n                    onChangeText={(value) => {\n                      field.handleChange(value);\n                      if (error) {\n                        setError(null);\n                      }\n                    }}\n                    secureTextEntry\n                    onSubmitEditing={form.handleSubmit}\n                  />\n                )}\n              </form.Field>\n\n              <TouchableOpacity\n                onPress={form.handleSubmit}\n                disabled={isSubmitting}\n                style={styles.button}\n              >\n                {isSubmitting ? (\n                  <ActivityIndicator size=\"small\" color=\"#fff\" />\n                ) : (\n                  <Text style={styles.buttonText}>Sign In</Text>\n                )}\n              </TouchableOpacity>\n            </>\n          );\n        }}\n      </form.Subscribe>\n    </View>\n  );\n}\n\nconst styles = StyleSheet.create((theme) => ({\n  container: {\n    marginTop: 24,\n    padding: 16,\n    borderRadius: 8,\n    borderWidth: 1,\n    borderColor: theme.colors.border,\n  },\n  title: {\n    fontSize: 18,\n    fontWeight: \"600\",\n    color: theme.colors.typography,\n    marginBottom: 16,\n  },\n  errorContainer: {\n    marginBottom: 16,\n    padding: 12,\n    borderRadius: 6,\n  },\n  errorText: {\n    color: theme.colors.destructive,\n    fontSize: 14,\n  },\n  input: {\n    marginBottom: 12,\n    padding: 16,\n    borderRadius: 6,\n    color: theme.colors.typography,\n    borderWidth: 1,\n    borderColor: theme.colors.border,\n  },\n  button: {\n    backgroundColor: theme.colors.primary,\n    padding: 16,\n    borderRadius: 6,\n    flexDirection: \"row\",\n    justifyContent: \"center\",\n    alignItems: \"center\",\n  },\n  buttonText: {\n    fontWeight: \"500\",\n  },\n}));\n"
  },
  {
    "path": "packages/template-generator/templates/auth/better-auth/convex/native/unistyles/components/sign-up.tsx.hbs",
    "content": "import { authClient } from \"@/lib/auth-client\";\nimport { useForm } from \"@tanstack/react-form\";\nimport { useState } from \"react\";\nimport {\n  ActivityIndicator,\n  Text,\n  TextInput,\n  TouchableOpacity,\n  View,\n} from \"react-native\";\nimport { StyleSheet } from \"react-native-unistyles\";\nimport z from \"zod\";\n\nconst signUpSchema = z.object({\n  name: z\n    .string()\n    .trim()\n    .min(1, \"Name is required\")\n    .min(2, \"Name must be at least 2 characters\"),\n  email: z\n    .string()\n    .trim()\n    .min(1, \"Email is required\")\n    .email(\"Enter a valid email address\"),\n  password: z\n    .string()\n    .min(1, \"Password is required\")\n    .min(8, \"Use at least 8 characters\"),\n});\n\nfunction getErrorMessage(error: unknown): string | null {\n  if (!error) return null;\n\n  if (typeof error === \"string\") {\n    return error;\n  }\n\n  if (Array.isArray(error)) {\n    for (const issue of error) {\n      const message = getErrorMessage(issue);\n      if (message) {\n        return message;\n      }\n    }\n    return null;\n  }\n\n  if (typeof error === \"object\" && error !== null) {\n    const maybeError = error as { message?: unknown };\n    if (typeof maybeError.message === \"string\") {\n      return maybeError.message;\n    }\n  }\n\n  return null;\n}\n\nexport function SignUp() {\n  const [error, setError] = useState<string | null>(null);\n\n  const form = useForm({\n    defaultValues: {\n      name: \"\",\n      email: \"\",\n      password: \"\",\n    },\n    validators: {\n      onSubmit: signUpSchema,\n    },\n    onSubmit: async ({ value, formApi }) => {\n      await authClient.signUp.email(\n        {\n          name: value.name.trim(),\n          email: value.email.trim(),\n          password: value.password,\n        },\n        {\n          onError(error) {\n            setError(error.error?.message || \"Failed to sign up\");\n          },\n          onSuccess() {\n            setError(null);\n            formApi.reset();\n          },\n        },\n      );\n    },\n  });\n\n  return (\n    <View style={styles.container}>\n      <Text style={styles.title}>Create Account</Text>\n\n      <form.Subscribe\n        selector={(state) => ({\n          isSubmitting: state.isSubmitting,\n          validationError: getErrorMessage(state.errorMap.onSubmit),\n        })}\n      >\n        {({ isSubmitting, validationError }) => {\n          const formError = error ?? validationError;\n\n          return (\n            <>\n              {formError ? (\n                <View style={styles.errorContainer}>\n                  <Text style={styles.errorText}>{formError}</Text>\n                </View>\n              ) : null}\n\n              <form.Field name=\"name\">\n                {(field) => (\n                  <TextInput\n                    style={styles.input}\n                    placeholder=\"Name\"\n                    value={field.state.value}\n                    onBlur={field.handleBlur}\n                    onChangeText={(value) => {\n                      field.handleChange(value);\n                      if (error) {\n                        setError(null);\n                      }\n                    }}\n                  />\n                )}\n              </form.Field>\n\n              <form.Field name=\"email\">\n                {(field) => (\n                  <TextInput\n                    style={styles.input}\n                    placeholder=\"Email\"\n                    value={field.state.value}\n                    onBlur={field.handleBlur}\n                    onChangeText={(value) => {\n                      field.handleChange(value);\n                      if (error) {\n                        setError(null);\n                      }\n                    }}\n                    keyboardType=\"email-address\"\n                    autoCapitalize=\"none\"\n                  />\n                )}\n              </form.Field>\n\n              <form.Field name=\"password\">\n                {(field) => (\n                  <TextInput\n                    style={styles.inputLast}\n                    placeholder=\"Password\"\n                    value={field.state.value}\n                    onBlur={field.handleBlur}\n                    onChangeText={(value) => {\n                      field.handleChange(value);\n                      if (error) {\n                        setError(null);\n                      }\n                    }}\n                    secureTextEntry\n                    onSubmitEditing={form.handleSubmit}\n                  />\n                )}\n              </form.Field>\n\n              <TouchableOpacity\n                onPress={form.handleSubmit}\n                disabled={isSubmitting}\n                style={styles.button}\n              >\n                {isSubmitting ? (\n                  <ActivityIndicator size=\"small\" color=\"#fff\" />\n                ) : (\n                  <Text style={styles.buttonText}>Sign Up</Text>\n                )}\n              </TouchableOpacity>\n            </>\n          );\n        }}\n      </form.Subscribe>\n    </View>\n  );\n}\n\nconst styles = StyleSheet.create((theme) => ({\n  container: {\n    marginTop: 24,\n    padding: 16,\n    borderRadius: 8,\n    borderWidth: 1,\n    borderColor: theme.colors.border,\n  },\n  title: {\n    fontSize: 18,\n    fontWeight: \"600\",\n    color: theme.colors.typography,\n    marginBottom: 16,\n  },\n  errorContainer: {\n    marginBottom: 16,\n    padding: 12,\n    borderRadius: 6,\n  },\n  errorText: {\n    color: theme.colors.destructive,\n    fontSize: 14,\n  },\n  input: {\n    marginBottom: 12,\n    padding: 16,\n    borderRadius: 6,\n    color: theme.colors.typography,\n    borderWidth: 1,\n    borderColor: theme.colors.border,\n  },\n  inputLast: {\n    marginBottom: 16,\n    padding: 16,\n    borderRadius: 6,\n    color: theme.colors.typography,\n    borderWidth: 1,\n    borderColor: theme.colors.border,\n  },\n  button: {\n    backgroundColor: theme.colors.primary,\n    padding: 16,\n    borderRadius: 6,\n    flexDirection: \"row\",\n    justifyContent: \"center\",\n    alignItems: \"center\",\n  },\n  buttonText: {\n    fontWeight: \"500\",\n  },\n}));\n"
  },
  {
    "path": "packages/template-generator/templates/auth/better-auth/convex/native/uniwind/components/sign-in.tsx.hbs",
    "content": "import { authClient } from \"@/lib/auth-client\";\nimport { useForm } from \"@tanstack/react-form\";\nimport { useRef } from \"react\";\nimport { Text, TextInput, View } from \"react-native\";\nimport { Button, FieldError, Input, Label, Spinner, Surface, TextField, useToast } from \"heroui-native\";\nimport z from \"zod\";\n\nconst signInSchema = z.object({\n  email: z\n    .string()\n    .trim()\n    .min(1, \"Email is required\")\n    .email(\"Enter a valid email address\"),\n  password: z\n    .string()\n    .min(1, \"Password is required\")\n    .min(8, \"Use at least 8 characters\"),\n});\n\nfunction getErrorMessage(error: unknown): string | null {\n  if (!error) return null;\n\n  if (typeof error === \"string\") {\n    return error;\n  }\n\n  if (Array.isArray(error)) {\n    for (const issue of error) {\n      const message = getErrorMessage(issue);\n      if (message) {\n        return message;\n      }\n    }\n    return null;\n  }\n\n  if (typeof error === \"object\" && error !== null) {\n    const maybeError = error as { message?: unknown };\n    if (typeof maybeError.message === \"string\") {\n      return maybeError.message;\n    }\n  }\n\n  return null;\n}\n\nexport function SignIn() {\n  const passwordInputRef = useRef<TextInput>(null);\n  const { toast } = useToast();\n\n  const form = useForm({\n    defaultValues: {\n      email: \"\",\n      password: \"\",\n    },\n    validators: {\n      onSubmit: signInSchema,\n    },\n    onSubmit: async ({ value, formApi }) => {\n      await authClient.signIn.email(\n        {\n          email: value.email.trim(),\n          password: value.password,\n        },\n        {\n          onError(error) {\n            toast.show({\n              variant: \"danger\",\n              label: error.error?.message || \"Failed to sign in\",\n            });\n          },\n          onSuccess() {\n            formApi.reset();\n            toast.show({\n              variant: \"success\",\n              label: \"Signed in successfully\",\n            });\n          },\n        },\n      );\n    },\n  });\n\n  return (\n    <Surface variant=\"secondary\" className=\"p-4 rounded-lg\">\n      <Text className=\"text-foreground font-medium mb-4\">Sign In</Text>\n\n      <form.Subscribe\n        selector={(state) => ({\n          isSubmitting: state.isSubmitting,\n          validationError: getErrorMessage(state.errorMap.onSubmit),\n        })}\n      >\n        {({ isSubmitting, validationError }) => {\n          const formError = validationError;\n\n          return (\n            <>\n              <FieldError isInvalid={!!formError} className=\"mb-3\">\n                {formError}\n              </FieldError>\n\n              <View className=\"gap-3\">\n                <form.Field name=\"email\">\n                  {(field) => (\n                    <TextField>\n                      <Label>Email</Label>\n                      <Input\n                        value={field.state.value}\n                        onBlur={field.handleBlur}\n                        onChangeText={field.handleChange}\n                        placeholder=\"email@example.com\"\n                        keyboardType=\"email-address\"\n                        autoCapitalize=\"none\"\n                        autoComplete=\"email\"\n                        textContentType=\"emailAddress\"\n                        returnKeyType=\"next\"\n                        blurOnSubmit={false}\n                        onSubmitEditing={() => {\n                          passwordInputRef.current?.focus();\n                        }}\n                      />\n                    </TextField>\n                  )}\n                </form.Field>\n\n                <form.Field name=\"password\">\n                  {(field) => (\n                    <TextField>\n                      <Label>Password</Label>\n                      <Input\n                        ref={passwordInputRef}\n                        value={field.state.value}\n                        onBlur={field.handleBlur}\n                        onChangeText={field.handleChange}\n                        placeholder=\"••••••••\"\n                        secureTextEntry\n                        autoComplete=\"password\"\n                        textContentType=\"password\"\n                        returnKeyType=\"go\"\n                        onSubmitEditing={form.handleSubmit}\n                      />\n                    </TextField>\n                  )}\n                </form.Field>\n\n                <Button onPress={form.handleSubmit} isDisabled={isSubmitting} className=\"mt-1\">\n                  {isSubmitting ? <Spinner size=\"sm\" color=\"default\" /> : <Button.Label>Sign In</Button.Label>}\n                </Button>\n              </View>\n            </>\n          );\n        }}\n      </form.Subscribe>\n    </Surface>\n  );\n}\n"
  },
  {
    "path": "packages/template-generator/templates/auth/better-auth/convex/native/uniwind/components/sign-up.tsx.hbs",
    "content": "import { authClient } from \"@/lib/auth-client\";\nimport { useForm } from \"@tanstack/react-form\";\nimport { useRef } from \"react\";\nimport { Text, TextInput, View } from \"react-native\";\nimport { Button, FieldError, Input, Label, Spinner, Surface, TextField, useToast } from \"heroui-native\";\nimport z from \"zod\";\n\nconst signUpSchema = z.object({\n  name: z\n    .string()\n    .trim()\n    .min(1, \"Name is required\")\n    .min(2, \"Name must be at least 2 characters\"),\n  email: z\n    .string()\n    .trim()\n    .min(1, \"Email is required\")\n    .email(\"Enter a valid email address\"),\n  password: z\n    .string()\n    .min(1, \"Password is required\")\n    .min(8, \"Use at least 8 characters\"),\n});\n\nfunction getErrorMessage(error: unknown): string | null {\n  if (!error) return null;\n\n  if (typeof error === \"string\") {\n    return error;\n  }\n\n  if (Array.isArray(error)) {\n    for (const issue of error) {\n      const message = getErrorMessage(issue);\n      if (message) {\n        return message;\n      }\n    }\n    return null;\n  }\n\n  if (typeof error === \"object\" && error !== null) {\n    const maybeError = error as { message?: unknown };\n    if (typeof maybeError.message === \"string\") {\n      return maybeError.message;\n    }\n  }\n\n  return null;\n}\n\nexport function SignUp() {\n  const emailInputRef = useRef<TextInput>(null);\n  const passwordInputRef = useRef<TextInput>(null);\n  const { toast } = useToast();\n\n  const form = useForm({\n    defaultValues: {\n      name: \"\",\n      email: \"\",\n      password: \"\",\n    },\n    validators: {\n      onSubmit: signUpSchema,\n    },\n    onSubmit: async ({ value, formApi }) => {\n      await authClient.signUp.email(\n        {\n          name: value.name.trim(),\n          email: value.email.trim(),\n          password: value.password,\n        },\n        {\n          onError(error) {\n            toast.show({\n              variant: \"danger\",\n              label: error.error?.message || \"Failed to sign up\",\n            });\n          },\n          onSuccess() {\n            formApi.reset();\n            toast.show({\n              variant: \"success\",\n              label: \"Account created successfully\",\n            });\n          },\n        },\n      );\n    },\n  });\n\n  return (\n    <Surface variant=\"secondary\" className=\"p-4 rounded-lg\">\n      <Text className=\"text-foreground font-medium mb-4\">Create Account</Text>\n\n      <form.Subscribe\n        selector={(state) => ({\n          isSubmitting: state.isSubmitting,\n          validationError: getErrorMessage(state.errorMap.onSubmit),\n        })}\n      >\n        {({ isSubmitting, validationError }) => {\n          const formError = validationError;\n\n          return (\n            <>\n              <FieldError isInvalid={!!formError} className=\"mb-3\">\n                {formError}\n              </FieldError>\n\n              <View className=\"gap-3\">\n                <form.Field name=\"name\">\n                  {(field) => (\n                    <TextField>\n                      <Label>Name</Label>\n                      <Input\n                        value={field.state.value}\n                        onBlur={field.handleBlur}\n                        onChangeText={field.handleChange}\n                        placeholder=\"John Doe\"\n                        autoComplete=\"name\"\n                        textContentType=\"name\"\n                        returnKeyType=\"next\"\n                        blurOnSubmit={false}\n                        onSubmitEditing={() => {\n                          emailInputRef.current?.focus();\n                        }}\n                      />\n                    </TextField>\n                  )}\n                </form.Field>\n\n                <form.Field name=\"email\">\n                  {(field) => (\n                    <TextField>\n                      <Label>Email</Label>\n                      <Input\n                        ref={emailInputRef}\n                        value={field.state.value}\n                        onBlur={field.handleBlur}\n                        onChangeText={field.handleChange}\n                        placeholder=\"email@example.com\"\n                        keyboardType=\"email-address\"\n                        autoCapitalize=\"none\"\n                        autoComplete=\"email\"\n                        textContentType=\"emailAddress\"\n                        returnKeyType=\"next\"\n                        blurOnSubmit={false}\n                        onSubmitEditing={() => {\n                          passwordInputRef.current?.focus();\n                        }}\n                      />\n                    </TextField>\n                  )}\n                </form.Field>\n\n                <form.Field name=\"password\">\n                  {(field) => (\n                    <TextField>\n                      <Label>Password</Label>\n                      <Input\n                        ref={passwordInputRef}\n                        value={field.state.value}\n                        onBlur={field.handleBlur}\n                        onChangeText={field.handleChange}\n                        placeholder=\"••••••••\"\n                        secureTextEntry\n                        autoComplete=\"new-password\"\n                        textContentType=\"newPassword\"\n                        returnKeyType=\"go\"\n                        onSubmitEditing={form.handleSubmit}\n                      />\n                    </TextField>\n                  )}\n                </form.Field>\n\n                <Button onPress={form.handleSubmit} isDisabled={isSubmitting} className=\"mt-1\">\n                  {isSubmitting ? (\n                    <Spinner size=\"sm\" color=\"default\" />\n                  ) : (\n                    <Button.Label>Create Account</Button.Label>\n                  )}\n                </Button>\n              </View>\n            </>\n          );\n        }}\n      </form.Subscribe>\n    </Surface>\n  );\n}\n"
  },
  {
    "path": "packages/template-generator/templates/auth/better-auth/convex/web/react/next/src/app/api/auth/[...all]/route.ts.hbs",
    "content": "import { handler } from \"@/lib/auth-server\";\n\nexport const { GET, POST } = handler;\n"
  },
  {
    "path": "packages/template-generator/templates/auth/better-auth/convex/web/react/next/src/app/dashboard/page.tsx.hbs",
    "content": "\"use client\"\n\nimport SignInForm from \"@/components/sign-in-form\";\nimport SignUpForm from \"@/components/sign-up-form\";\nimport UserMenu from \"@/components/user-menu\";\nimport { api } from \"@{{projectName}}/backend/convex/_generated/api\";\nimport {\n    Authenticated,\n    AuthLoading,\n    Unauthenticated,\n    useQuery,\n} from \"convex/react\";\nimport { useState } from \"react\";\n\nexport default function DashboardPage() {\n    const [showSignIn, setShowSignIn] = useState(false);\n    const privateData = useQuery(api.privateData.get);\n\n    return (\n        <>\n            <Authenticated>\n                <div>\n                    <h1>Dashboard</h1>\n                    <p>privateData: {privateData?.message}</p>\n                    <UserMenu />\n                </div>\n            </Authenticated>\n            <Unauthenticated>\n                {showSignIn ? (\n                    <SignInForm onSwitchToSignUp={() => setShowSignIn(false)} />\n                ) : (\n                    <SignUpForm onSwitchToSignIn={() => setShowSignIn(true)} />\n                )}\n            </Unauthenticated>\n            <AuthLoading>\n                <div>Loading...</div>\n            </AuthLoading>\n        </>\n    );\n}\n"
  },
  {
    "path": "packages/template-generator/templates/auth/better-auth/convex/web/react/next/src/components/sign-in-form.tsx.hbs",
    "content": "import { authClient } from \"@/lib/auth-client\";\nimport { useForm } from \"@tanstack/react-form\";\nimport { toast } from \"sonner\";\nimport z from \"zod\";\nimport { Button } from \"@{{projectName}}/ui/components/button\";\nimport { Input } from \"@{{projectName}}/ui/components/input\";\nimport { Label } from \"@{{projectName}}/ui/components/label\";\nimport { useRouter } from \"next/navigation\";\n\nexport default function SignInForm({\n\tonSwitchToSignUp,\n}: {\n\tonSwitchToSignUp: () => void;\n}) {\n\tconst router = useRouter();\n\n\tconst form = useForm({\n\t\tdefaultValues: {\n\t\t\temail: \"\",\n\t\t\tpassword: \"\",\n\t\t},\n\t\tonSubmit: async ({ value }) => {\n\t\t\tawait authClient.signIn.email(\n\t\t\t\t{\n\t\t\t\t\temail: value.email,\n\t\t\t\t\tpassword: value.password,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tonSuccess: () => {\n\t\t\t\t\t\trouter.push(\"/dashboard\");\n\t\t\t\t\t\ttoast.success(\"Sign in successful\");\n\t\t\t\t\t},\n\t\t\t\t\tonError: (error) => {\n\t\t\t\t\t\ttoast.error(error.error.message || error.error.statusText);\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t);\n\t\t},\n\t\tvalidators: {\n\t\t\tonSubmit: z.object({\n\t\t\t\temail: z.email(\"Invalid email address\"),\n\t\t\t\tpassword: z.string().min(8, \"Password must be at least 8 characters\"),\n\t\t\t}),\n\t\t},\n\t});\n\n\treturn (\n\t\t<div className=\"mx-auto w-full mt-10 max-w-md p-6\">\n\t\t\t<h1 className=\"mb-6 text-center text-3xl font-bold\">Welcome Back</h1>\n\n\t\t\t<form\n\t\t\t\tonSubmit={(e) => {\n\t\t\t\t\te.preventDefault();\n\t\t\t\t\te.stopPropagation();\n\t\t\t\t\tform.handleSubmit();\n\t\t\t\t}}\n\t\t\t\tclassName=\"space-y-4\"\n\t\t\t>\n\t\t\t\t<div>\n\t\t\t\t\t<form.Field name=\"email\">\n\t\t\t\t\t\t{(field) => (\n\t\t\t\t\t\t\t<div className=\"space-y-2\">\n\t\t\t\t\t\t\t\t<Label htmlFor={field.name}>Email</Label>\n\t\t\t\t\t\t\t\t<Input\n\t\t\t\t\t\t\t\t\tid={field.name}\n\t\t\t\t\t\t\t\t\tname={field.name}\n\t\t\t\t\t\t\t\t\ttype=\"email\"\n\t\t\t\t\t\t\t\t\tvalue={field.state.value}\n\t\t\t\t\t\t\t\t\tonBlur={field.handleBlur}\n\t\t\t\t\t\t\t\t\tonChange={(e) => field.handleChange(e.target.value)}\n\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t{field.state.meta.errors.map((error) => (\n\t\t\t\t\t\t\t\t\t<p key={error?.message} className=\"text-red-500\">\n\t\t\t\t\t\t\t\t\t\t{error?.message}\n\t\t\t\t\t\t\t\t\t</p>\n\t\t\t\t\t\t\t\t))}\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t)}\n\t\t\t\t\t</form.Field>\n\t\t\t\t</div>\n\n\t\t\t\t<div>\n\t\t\t\t\t<form.Field name=\"password\">\n\t\t\t\t\t\t{(field) => (\n\t\t\t\t\t\t\t<div className=\"space-y-2\">\n\t\t\t\t\t\t\t\t<Label htmlFor={field.name}>Password</Label>\n\t\t\t\t\t\t\t\t<Input\n\t\t\t\t\t\t\t\t\tid={field.name}\n\t\t\t\t\t\t\t\t\tname={field.name}\n\t\t\t\t\t\t\t\t\ttype=\"password\"\n\t\t\t\t\t\t\t\t\tvalue={field.state.value}\n\t\t\t\t\t\t\t\t\tonBlur={field.handleBlur}\n\t\t\t\t\t\t\t\t\tonChange={(e) => field.handleChange(e.target.value)}\n\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t{field.state.meta.errors.map((error) => (\n\t\t\t\t\t\t\t\t\t<p key={error?.message} className=\"text-red-500\">\n\t\t\t\t\t\t\t\t\t\t{error?.message}\n\t\t\t\t\t\t\t\t\t</p>\n\t\t\t\t\t\t\t\t))}\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t)}\n\t\t\t\t\t</form.Field>\n\t\t\t\t</div>\n\n\t\t\t\t<form.Subscribe selector={(state) => ({ canSubmit: state.canSubmit, isSubmitting: state.isSubmitting })}>\n          {({ canSubmit, isSubmitting }) => (\n\t\t\t\t\t\t<Button\n\t\t\t\t\t\t\ttype=\"submit\"\n\t\t\t\t\t\t\tclassName=\"w-full\"\n\t\t\t\t\t\t\tdisabled={!canSubmit || isSubmitting}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t{isSubmitting ? \"Submitting...\" : \"Sign In\"}\n\t\t\t\t\t\t</Button>\n\t\t\t\t\t)}\n\t\t\t\t</form.Subscribe>\n\t\t\t</form>\n\n\t\t\t<div className=\"mt-4 text-center\">\n\t\t\t\t<Button\n\t\t\t\t\tvariant=\"link\"\n\t\t\t\t\tonClick={onSwitchToSignUp}\n\t\t\t\t\tclassName=\"text-indigo-600 hover:text-indigo-800\"\n\t\t\t\t>\n\t\t\t\t\tNeed an account? Sign Up\n\t\t\t\t</Button>\n\t\t\t</div>\n\t\t</div>\n\t);\n}\n"
  },
  {
    "path": "packages/template-generator/templates/auth/better-auth/convex/web/react/next/src/components/sign-up-form.tsx.hbs",
    "content": "import { authClient } from \"@/lib/auth-client\";\nimport { useForm } from \"@tanstack/react-form\";\nimport { toast } from \"sonner\";\nimport z from \"zod\";\nimport { Button } from \"@{{projectName}}/ui/components/button\";\nimport { Input } from \"@{{projectName}}/ui/components/input\";\nimport { Label } from \"@{{projectName}}/ui/components/label\";\nimport { useRouter } from \"next/navigation\";\n\nexport default function SignUpForm({\n\tonSwitchToSignIn,\n}: {\n\tonSwitchToSignIn: () => void;\n}) {\n\tconst router = useRouter();\n\n\tconst form = useForm({\n\t\tdefaultValues: {\n\t\t\temail: \"\",\n\t\t\tpassword: \"\",\n\t\t\tname: \"\",\n\t\t},\n\t\tonSubmit: async ({ value }) => {\n\t\t\tawait authClient.signUp.email(\n\t\t\t\t{\n\t\t\t\t\temail: value.email,\n\t\t\t\t\tpassword: value.password,\n\t\t\t\t\tname: value.name,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tonSuccess: () => {\n\t\t\t\t\t\trouter.push(\"/dashboard\");\n\t\t\t\t\t\ttoast.success(\"Sign up successful\");\n\t\t\t\t\t},\n\t\t\t\t\tonError: (error) => {\n\t\t\t\t\t\ttoast.error(error.error.message || error.error.statusText);\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t);\n\t\t},\n\t\tvalidators: {\n\t\t\tonSubmit: z.object({\n\t\t\t\tname: z.string().min(2, \"Name must be at least 2 characters\"),\n\t\t\t\temail: z.email(\"Invalid email address\"),\n\t\t\t\tpassword: z.string().min(8, \"Password must be at least 8 characters\"),\n\t\t\t}),\n\t\t},\n\t});\n\n\treturn (\n\t\t<div className=\"mx-auto w-full mt-10 max-w-md p-6\">\n\t\t\t<h1 className=\"mb-6 text-center text-3xl font-bold\">Create Account</h1>\n\n\t\t\t<form\n\t\t\t\tonSubmit={(e) => {\n\t\t\t\t\te.preventDefault();\n\t\t\t\t\te.stopPropagation();\n\t\t\t\t\tform.handleSubmit();\n\t\t\t\t}}\n\t\t\t\tclassName=\"space-y-4\"\n\t\t\t>\n\t\t\t\t<div>\n\t\t\t\t\t<form.Field name=\"name\">\n\t\t\t\t\t\t{(field) => (\n\t\t\t\t\t\t\t<div className=\"space-y-2\">\n\t\t\t\t\t\t\t\t<Label htmlFor={field.name}>Name</Label>\n\t\t\t\t\t\t\t\t<Input\n\t\t\t\t\t\t\t\t\tid={field.name}\n\t\t\t\t\t\t\t\t\tname={field.name}\n\t\t\t\t\t\t\t\t\tvalue={field.state.value}\n\t\t\t\t\t\t\t\t\tonBlur={field.handleBlur}\n\t\t\t\t\t\t\t\t\tonChange={(e) => field.handleChange(e.target.value)}\n\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t{field.state.meta.errors.map((error) => (\n\t\t\t\t\t\t\t\t\t<p key={error?.message} className=\"text-red-500\">\n\t\t\t\t\t\t\t\t\t\t{error?.message}\n\t\t\t\t\t\t\t\t\t</p>\n\t\t\t\t\t\t\t\t))}\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t)}\n\t\t\t\t\t</form.Field>\n\t\t\t\t</div>\n\n\t\t\t\t<div>\n\t\t\t\t\t<form.Field name=\"email\">\n\t\t\t\t\t\t{(field) => (\n\t\t\t\t\t\t\t<div className=\"space-y-2\">\n\t\t\t\t\t\t\t\t<Label htmlFor={field.name}>Email</Label>\n\t\t\t\t\t\t\t\t<Input\n\t\t\t\t\t\t\t\t\tid={field.name}\n\t\t\t\t\t\t\t\t\tname={field.name}\n\t\t\t\t\t\t\t\t\ttype=\"email\"\n\t\t\t\t\t\t\t\t\tvalue={field.state.value}\n\t\t\t\t\t\t\t\t\tonBlur={field.handleBlur}\n\t\t\t\t\t\t\t\t\tonChange={(e) => field.handleChange(e.target.value)}\n\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t{field.state.meta.errors.map((error) => (\n\t\t\t\t\t\t\t\t\t<p key={error?.message} className=\"text-red-500\">\n\t\t\t\t\t\t\t\t\t\t{error?.message}\n\t\t\t\t\t\t\t\t\t</p>\n\t\t\t\t\t\t\t\t))}\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t)}\n\t\t\t\t\t</form.Field>\n\t\t\t\t</div>\n\n\t\t\t\t<div>\n\t\t\t\t\t<form.Field name=\"password\">\n\t\t\t\t\t\t{(field) => (\n\t\t\t\t\t\t\t<div className=\"space-y-2\">\n\t\t\t\t\t\t\t\t<Label htmlFor={field.name}>Password</Label>\n\t\t\t\t\t\t\t\t<Input\n\t\t\t\t\t\t\t\t\tid={field.name}\n\t\t\t\t\t\t\t\t\tname={field.name}\n\t\t\t\t\t\t\t\t\ttype=\"password\"\n\t\t\t\t\t\t\t\t\tvalue={field.state.value}\n\t\t\t\t\t\t\t\t\tonBlur={field.handleBlur}\n\t\t\t\t\t\t\t\t\tonChange={(e) => field.handleChange(e.target.value)}\n\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t{field.state.meta.errors.map((error) => (\n\t\t\t\t\t\t\t\t\t<p key={error?.message} className=\"text-red-500\">\n\t\t\t\t\t\t\t\t\t\t{error?.message}\n\t\t\t\t\t\t\t\t\t</p>\n\t\t\t\t\t\t\t\t))}\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t)}\n\t\t\t\t\t</form.Field>\n\t\t\t\t</div>\n\n\t\t\t\t<form.Subscribe selector={(state) => ({ canSubmit: state.canSubmit, isSubmitting: state.isSubmitting })}>\n          {({ canSubmit, isSubmitting }) => (\n\t\t\t\t\t\t<Button\n\t\t\t\t\t\t\ttype=\"submit\"\n\t\t\t\t\t\t\tclassName=\"w-full\"\n\t\t\t\t\t\t\tdisabled={!canSubmit || isSubmitting}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t{isSubmitting ? \"Submitting...\" : \"Sign Up\"}\n\t\t\t\t\t\t</Button>\n\t\t\t\t\t)}\n\t\t\t\t</form.Subscribe>\n\t\t\t</form>\n\n\t\t\t<div className=\"mt-4 text-center\">\n\t\t\t\t<Button\n\t\t\t\t\tvariant=\"link\"\n\t\t\t\t\tonClick={onSwitchToSignIn}\n\t\t\t\t\tclassName=\"text-indigo-600 hover:text-indigo-800\"\n\t\t\t\t>\n\t\t\t\t\tAlready have an account? Sign In\n\t\t\t\t</Button>\n\t\t\t</div>\n\t\t</div>\n\t);\n}\n"
  },
  {
    "path": "packages/template-generator/templates/auth/better-auth/convex/web/react/next/src/components/user-menu.tsx.hbs",
    "content": "import {\n\tDropdownMenu,\n\tDropdownMenuContent,\n\tDropdownMenuGroup,\n\tDropdownMenuItem,\n\tDropdownMenuLabel,\n\tDropdownMenuSeparator,\n\tDropdownMenuTrigger,\n} from \"@{{projectName}}/ui/components/dropdown-menu\";\nimport { authClient } from \"@/lib/auth-client\";\nimport { Button } from \"@{{projectName}}/ui/components/button\";\nimport { useRouter } from \"next/navigation\";\nimport { useQuery } from \"convex/react\";\nimport { api } from \"@{{projectName}}/backend/convex/_generated/api\";\n\nexport default function UserMenu() {\n\tconst router = useRouter();\n\tconst user = useQuery(api.auth.getCurrentUser)\n\n\treturn (\n\t\t<DropdownMenu>\n\t\t\t<DropdownMenuTrigger render={<Button variant=\"outline\" />}>\n\t\t\t\t{user?.name}\n\t\t\t</DropdownMenuTrigger>\n\t\t\t<DropdownMenuContent className=\"bg-card\">\n\t\t\t\t<DropdownMenuGroup>\n\t\t\t\t\t<DropdownMenuLabel>My Account</DropdownMenuLabel>\n\t\t\t\t\t<DropdownMenuSeparator />\n\t\t\t\t\t<DropdownMenuItem>{user?.email}</DropdownMenuItem>\n\t\t\t\t\t<DropdownMenuItem\n\t\t\t\t\t\tvariant=\"destructive\"\n\t\t\t\t\t\tonClick={() => {\n\t\t\t\t\t\t\tauthClient.signOut({\n\t\t\t\t\t\t\t\tfetchOptions: {\n\t\t\t\t\t\t\t\t\tonSuccess: () => {\n\t\t\t\t\t\t\t\t\t\trouter.push(\"/dashboard\");\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}}\n\t\t\t\t\t>\n\t\t\t\t\t\tSign Out\n\t\t\t\t\t</DropdownMenuItem>\n\t\t\t\t</DropdownMenuGroup>\n\t\t\t</DropdownMenuContent>\n\t\t</DropdownMenu>\n\t);\n}\n"
  },
  {
    "path": "packages/template-generator/templates/auth/better-auth/convex/web/react/next/src/lib/auth-client.ts.hbs",
    "content": "import { createAuthClient } from \"better-auth/react\";\nimport { convexClient } from \"@convex-dev/better-auth/client/plugins\";\n\nexport const authClient = createAuthClient({\n  plugins: [convexClient()],\n});\n"
  },
  {
    "path": "packages/template-generator/templates/auth/better-auth/convex/web/react/next/src/lib/auth-server.ts.hbs",
    "content": "import { convexBetterAuthNextJs } from \"@convex-dev/better-auth/nextjs\";\nimport { env } from \"@{{projectName}}/env/web\";\n\nexport const {\n\thandler,\n\tpreloadAuthQuery,\n\tisAuthenticated,\n\tgetToken,\n\tfetchAuthQuery,\n\tfetchAuthMutation,\n\tfetchAuthAction,\n} = convexBetterAuthNextJs({\n\tconvexUrl: env.NEXT_PUBLIC_CONVEX_URL,\n\tconvexSiteUrl: env.NEXT_PUBLIC_CONVEX_SITE_URL,\n});\n"
  },
  {
    "path": "packages/template-generator/templates/auth/better-auth/convex/web/react/react-router/src/components/sign-in-form.tsx.hbs",
    "content": "import { authClient } from \"@/lib/auth-client\";\nimport { useForm } from \"@tanstack/react-form\";\nimport { useNavigate } from \"react-router\";\nimport { toast } from \"sonner\";\nimport z from \"zod\";\nimport { Button } from \"@{{projectName}}/ui/components/button\";\nimport { Input } from \"@{{projectName}}/ui/components/input\";\nimport { Label } from \"@{{projectName}}/ui/components/label\";\n\nexport default function SignInForm({\n  onSwitchToSignUp,\n}: {\n  onSwitchToSignUp: () => void;\n}) {\n  const navigate = useNavigate();\n\n  const form = useForm({\n    defaultValues: {\n      email: \"\",\n      password: \"\",\n    },\n    onSubmit: async ({ value }) => {\n      await authClient.signIn.email(\n        {\n          email: value.email,\n          password: value.password,\n        },\n        {\n          onSuccess: () => {\n            navigate(\"/dashboard\");\n            toast.success(\"Sign in successful\");\n          },\n          onError: (error) => {\n            toast.error(error.error.message || error.error.statusText);\n          },\n        },\n      );\n    },\n    validators: {\n      onSubmit: z.object({\n        email: z.email(\"Invalid email address\"),\n        password: z.string().min(8, \"Password must be at least 8 characters\"),\n      }),\n    },\n  });\n\n  return (\n    <div className=\"mx-auto mt-10 w-full max-w-md p-6\">\n      <h1 className=\"mb-6 text-center text-3xl font-bold\">Welcome Back</h1>\n\n      <form\n        onSubmit={(e) => {\n          e.preventDefault();\n          e.stopPropagation();\n          form.handleSubmit();\n        }}\n        className=\"space-y-4\"\n      >\n        <div>\n          <form.Field name=\"email\">\n            {(field) => (\n              <div className=\"space-y-2\">\n                <Label htmlFor={field.name}>Email</Label>\n                <Input\n                  id={field.name}\n                  name={field.name}\n                  type=\"email\"\n                  value={field.state.value}\n                  onBlur={field.handleBlur}\n                  onChange={(e) => field.handleChange(e.target.value)}\n                />\n                {field.state.meta.errors.map((error, index) => (\n                  <p key={`${field.name}-error-${index}`} className=\"text-red-500\">\n                    {error?.message}\n                  </p>\n                ))}\n              </div>\n            )}\n          </form.Field>\n        </div>\n\n        <div>\n          <form.Field name=\"password\">\n            {(field) => (\n              <div className=\"space-y-2\">\n                <Label htmlFor={field.name}>Password</Label>\n                <Input\n                  id={field.name}\n                  name={field.name}\n                  type=\"password\"\n                  value={field.state.value}\n                  onBlur={field.handleBlur}\n                  onChange={(e) => field.handleChange(e.target.value)}\n                />\n                {field.state.meta.errors.map((error, index) => (\n                  <p key={`${field.name}-error-${index}`} className=\"text-red-500\">\n                    {error?.message}\n                  </p>\n                ))}\n              </div>\n            )}\n          </form.Field>\n        </div>\n\n        <form.Subscribe\n          selector={(state) => ({\n            canSubmit: state.canSubmit,\n            isSubmitting: state.isSubmitting,\n          })}\n        >\n          {({ canSubmit, isSubmitting }) => (\n            <Button type=\"submit\" className=\"w-full\" disabled={!canSubmit || isSubmitting}>\n              {isSubmitting ? \"Submitting...\" : \"Sign In\"}\n            </Button>\n          )}\n        </form.Subscribe>\n      </form>\n\n      <div className=\"mt-4 text-center\">\n        <Button\n          variant=\"link\"\n          onClick={onSwitchToSignUp}\n          className=\"text-indigo-600 hover:text-indigo-800\"\n        >\n          Need an account? Sign Up\n        </Button>\n      </div>\n    </div>\n  );\n}\n"
  },
  {
    "path": "packages/template-generator/templates/auth/better-auth/convex/web/react/react-router/src/components/sign-up-form.tsx.hbs",
    "content": "import { authClient } from \"@/lib/auth-client\";\nimport { useForm } from \"@tanstack/react-form\";\nimport { useNavigate } from \"react-router\";\nimport { toast } from \"sonner\";\nimport z from \"zod\";\nimport { Button } from \"@{{projectName}}/ui/components/button\";\nimport { Input } from \"@{{projectName}}/ui/components/input\";\nimport { Label } from \"@{{projectName}}/ui/components/label\";\n\nexport default function SignUpForm({\n  onSwitchToSignIn,\n}: {\n  onSwitchToSignIn: () => void;\n}) {\n  const navigate = useNavigate();\n\n  const form = useForm({\n    defaultValues: {\n      email: \"\",\n      password: \"\",\n      name: \"\",\n    },\n    onSubmit: async ({ value }) => {\n      await authClient.signUp.email(\n        {\n          email: value.email,\n          password: value.password,\n          name: value.name,\n        },\n        {\n          onSuccess: () => {\n            navigate(\"/dashboard\");\n            toast.success(\"Sign up successful\");\n          },\n          onError: (error) => {\n            toast.error(error.error.message || error.error.statusText);\n          },\n        },\n      );\n    },\n    validators: {\n      onSubmit: z.object({\n        name: z.string().min(2, \"Name must be at least 2 characters\"),\n        email: z.email(\"Invalid email address\"),\n        password: z.string().min(8, \"Password must be at least 8 characters\"),\n      }),\n    },\n  });\n\n  return (\n    <div className=\"mx-auto mt-10 w-full max-w-md p-6\">\n      <h1 className=\"mb-6 text-center text-3xl font-bold\">Create Account</h1>\n\n      <form\n        onSubmit={(e) => {\n          e.preventDefault();\n          e.stopPropagation();\n          form.handleSubmit();\n        }}\n        className=\"space-y-4\"\n      >\n        <div>\n          <form.Field name=\"name\">\n            {(field) => (\n              <div className=\"space-y-2\">\n                <Label htmlFor={field.name}>Name</Label>\n                <Input\n                  id={field.name}\n                  name={field.name}\n                  value={field.state.value}\n                  onBlur={field.handleBlur}\n                  onChange={(e) => field.handleChange(e.target.value)}\n                />\n                {field.state.meta.errors.map((error, index) => (\n                  <p key={`${field.name}-error-${index}`} className=\"text-red-500\">\n                    {error?.message}\n                  </p>\n                ))}\n              </div>\n            )}\n          </form.Field>\n        </div>\n\n        <div>\n          <form.Field name=\"email\">\n            {(field) => (\n              <div className=\"space-y-2\">\n                <Label htmlFor={field.name}>Email</Label>\n                <Input\n                  id={field.name}\n                  name={field.name}\n                  type=\"email\"\n                  value={field.state.value}\n                  onBlur={field.handleBlur}\n                  onChange={(e) => field.handleChange(e.target.value)}\n                />\n                {field.state.meta.errors.map((error, index) => (\n                  <p key={`${field.name}-error-${index}`} className=\"text-red-500\">\n                    {error?.message}\n                  </p>\n                ))}\n              </div>\n            )}\n          </form.Field>\n        </div>\n\n        <div>\n          <form.Field name=\"password\">\n            {(field) => (\n              <div className=\"space-y-2\">\n                <Label htmlFor={field.name}>Password</Label>\n                <Input\n                  id={field.name}\n                  name={field.name}\n                  type=\"password\"\n                  value={field.state.value}\n                  onBlur={field.handleBlur}\n                  onChange={(e) => field.handleChange(e.target.value)}\n                />\n                {field.state.meta.errors.map((error, index) => (\n                  <p key={`${field.name}-error-${index}`} className=\"text-red-500\">\n                    {error?.message}\n                  </p>\n                ))}\n              </div>\n            )}\n          </form.Field>\n        </div>\n\n        <form.Subscribe\n          selector={(state) => ({\n            canSubmit: state.canSubmit,\n            isSubmitting: state.isSubmitting,\n          })}\n        >\n          {({ canSubmit, isSubmitting }) => (\n            <Button type=\"submit\" className=\"w-full\" disabled={!canSubmit || isSubmitting}>\n              {isSubmitting ? \"Submitting...\" : \"Sign Up\"}\n            </Button>\n          )}\n        </form.Subscribe>\n      </form>\n\n      <div className=\"mt-4 text-center\">\n        <Button\n          variant=\"link\"\n          onClick={onSwitchToSignIn}\n          className=\"text-indigo-600 hover:text-indigo-800\"\n        >\n          Already have an account? Sign In\n        </Button>\n      </div>\n    </div>\n  );\n}\n"
  },
  {
    "path": "packages/template-generator/templates/auth/better-auth/convex/web/react/react-router/src/components/user-menu.tsx.hbs",
    "content": "import { useNavigate } from \"react-router\";\n\nimport {\n  DropdownMenu,\n  DropdownMenuContent,\n  DropdownMenuGroup,\n  DropdownMenuItem,\n  DropdownMenuLabel,\n  DropdownMenuSeparator,\n  DropdownMenuTrigger,\n} from \"@{{projectName}}/ui/components/dropdown-menu\";\nimport { authClient } from \"@/lib/auth-client\";\nimport { useQuery } from \"convex/react\";\nimport { api } from \"@{{projectName}}/backend/convex/_generated/api\";\n\nimport { Button } from \"@{{projectName}}/ui/components/button\";\n\nexport default function UserMenu() {\n  const navigate = useNavigate();\n  const user = useQuery(api.auth.getCurrentUser);\n\n  return (\n    <DropdownMenu>\n      <DropdownMenuTrigger render={<Button variant=\"outline\" />}>\n        {user?.name}\n      </DropdownMenuTrigger>\n      <DropdownMenuContent className=\"bg-card\">\n        <DropdownMenuGroup>\n          <DropdownMenuLabel>My Account</DropdownMenuLabel>\n          <DropdownMenuSeparator />\n          <DropdownMenuItem>{user?.email}</DropdownMenuItem>\n          <DropdownMenuItem\n            variant=\"destructive\"\n            onClick={() => {\n              authClient.signOut({\n                fetchOptions: {\n                  onSuccess: () => {\n                    navigate(\"/dashboard\");\n                  },\n                },\n              });\n            }}\n          >\n            Sign Out\n          </DropdownMenuItem>\n        </DropdownMenuGroup>\n      </DropdownMenuContent>\n    </DropdownMenu>\n  );\n}\n"
  },
  {
    "path": "packages/template-generator/templates/auth/better-auth/convex/web/react/react-router/src/lib/auth-client.ts.hbs",
    "content": "import { createAuthClient } from \"better-auth/react\";\nimport {\n  convexClient,\n  crossDomainClient,\n} from \"@convex-dev/better-auth/client/plugins\";\nimport { env } from \"@{{projectName}}/env/web\";\n\nexport const authClient = createAuthClient({\n  baseURL: env.VITE_CONVEX_SITE_URL,\n  plugins: [crossDomainClient(), convexClient()],\n});\n"
  },
  {
    "path": "packages/template-generator/templates/auth/better-auth/convex/web/react/react-router/src/routes/dashboard.tsx.hbs",
    "content": "import SignInForm from \"@/components/sign-in-form\";\nimport SignUpForm from \"@/components/sign-up-form\";\nimport UserMenu from \"@/components/user-menu\";\nimport { api } from \"@{{projectName}}/backend/convex/_generated/api\";\nimport {\n  Authenticated,\n  AuthLoading,\n  Unauthenticated,\n  useQuery,\n} from \"convex/react\";\nimport { useState } from \"react\";\n\nfunction PrivateDashboardContent() {\n  const privateData = useQuery(api.privateData.get);\n\n  return (\n    <div>\n      <h1>Dashboard</h1>\n      <p>privateData: {privateData?.message}</p>\n      <UserMenu />\n    </div>\n  );\n}\n\nexport default function Dashboard() {\n  const [showSignIn, setShowSignIn] = useState(false);\n\n  return (\n    <>\n      <Authenticated>\n        <PrivateDashboardContent />\n      </Authenticated>\n      <Unauthenticated>\n        {showSignIn ? (\n          <SignInForm onSwitchToSignUp={() => setShowSignIn(false)} />\n        ) : (\n          <SignUpForm onSwitchToSignIn={() => setShowSignIn(true)} />\n        )}\n      </Unauthenticated>\n      <AuthLoading>\n        <div>Loading...</div>\n      </AuthLoading>\n    </>\n  );\n}\n"
  },
  {
    "path": "packages/template-generator/templates/auth/better-auth/convex/web/react/tanstack-router/src/components/sign-in-form.tsx.hbs",
    "content": "import { authClient } from \"@/lib/auth-client\";\nimport { useForm } from \"@tanstack/react-form\";\nimport { useNavigate } from \"@tanstack/react-router\";\nimport { toast } from \"sonner\";\nimport z from \"zod\";\nimport { Button } from \"@{{projectName}}/ui/components/button\";\nimport { Input } from \"@{{projectName}}/ui/components/input\";\nimport { Label } from \"@{{projectName}}/ui/components/label\";\n\nexport default function SignInForm({\n    onSwitchToSignUp,\n}: {\n    onSwitchToSignUp: () => void;\n}) {\n    const navigate = useNavigate({\n        from: \"/\",\n    });\n\n    const form = useForm({\n        defaultValues: {\n            email: \"\",\n            password: \"\",\n        },\n        onSubmit: async ({ value }) => {\n            await authClient.signIn.email(\n                {\n                    email: value.email,\n                    password: value.password,\n                },\n                {\n                    onSuccess: () => {\n                        navigate({\n                            to: \"/dashboard\",\n                        });\n                        toast.success(\"Sign in successful\");\n                    },\n                    onError: (error) => {\n                        toast.error(error.error.message || error.error.statusText);\n                    },\n                },\n            );\n        },\n        validators: {\n            onSubmit: z.object({\n                email: z.email(\"Invalid email address\"),\n                password: z.string().min(8, \"Password must be at least 8 characters\"),\n            }),\n        },\n    });\n\n    return (\n        <div className=\"mx-auto w-full mt-10 max-w-md p-6\">\n            <h1 className=\"mb-6 text-center text-3xl font-bold\">Welcome Back</h1>\n\n            <form\n                onSubmit={(e) => {\n                    e.preventDefault();\n                    e.stopPropagation();\n                    form.handleSubmit();\n                }}\n                className=\"space-y-4\"\n            >\n                <div>\n                    <form.Field name=\"email\">\n                        {(field) => (\n                            <div className=\"space-y-2\">\n                                <Label htmlFor={field.name}>Email</Label>\n                                <Input\n                                    id={field.name}\n                                    name={field.name}\n                                    type=\"email\"\n                                    value={field.state.value}\n                                    onBlur={field.handleBlur}\n                                    onChange={(e) => field.handleChange(e.target.value)}\n                                />\n                                {field.state.meta.errors.map((error, index) => (\n                                    <p key={`${field.name}-error-${index}`} className=\"text-red-500\">\n                                        {error?.message}\n                                    </p>\n                                ))}\n                            </div>\n                        )}\n                    </form.Field>\n                </div>\n\n                <div>\n                    <form.Field name=\"password\">\n                        {(field) => (\n                            <div className=\"space-y-2\">\n                                <Label htmlFor={field.name}>Password</Label>\n                                <Input\n                                    id={field.name}\n                                    name={field.name}\n                                    type=\"password\"\n                                    value={field.state.value}\n                                    onBlur={field.handleBlur}\n                                    onChange={(e) => field.handleChange(e.target.value)}\n                                />\n                                {field.state.meta.errors.map((error, index) => (\n                                    <p key={`${field.name}-error-${index}`} className=\"text-red-500\">\n                                        {error?.message}\n                                    </p>\n                                ))}\n                            </div>\n                        )}\n                    </form.Field>\n                </div>\n\n                <form.Subscribe selector={(state) => ({ canSubmit: state.canSubmit, isSubmitting: state.isSubmitting })}>\n          {({ canSubmit, isSubmitting }) => (\n                        <Button\n                            type=\"submit\"\n                            className=\"w-full\"\n                            disabled={!canSubmit || isSubmitting}\n                        >\n                            {isSubmitting ? \"Submitting...\" : \"Sign In\"}\n                        </Button>\n                    )}\n                </form.Subscribe>\n            </form>\n\n            <div className=\"mt-4 text-center\">\n                <Button\n                    variant=\"link\"\n                    onClick={onSwitchToSignUp}\n                    className=\"text-indigo-600 hover:text-indigo-800\"\n                >\n                    Need an account? Sign Up\n                </Button>\n            </div>\n        </div>\n    );\n}\n"
  },
  {
    "path": "packages/template-generator/templates/auth/better-auth/convex/web/react/tanstack-router/src/components/sign-up-form.tsx.hbs",
    "content": "import { authClient } from \"@/lib/auth-client\";\nimport { useForm } from \"@tanstack/react-form\";\nimport { useNavigate } from \"@tanstack/react-router\";\nimport { toast } from \"sonner\";\nimport z from \"zod\";\nimport { Button } from \"@{{projectName}}/ui/components/button\";\nimport { Input } from \"@{{projectName}}/ui/components/input\";\nimport { Label } from \"@{{projectName}}/ui/components/label\";\n\nexport default function SignUpForm({\n    onSwitchToSignIn,\n}: {\n    onSwitchToSignIn: () => void;\n}) {\n    const navigate = useNavigate({\n        from: \"/\",\n    });\n\n    const form = useForm({\n        defaultValues: {\n            email: \"\",\n            password: \"\",\n            name: \"\",\n        },\n        onSubmit: async ({ value }) => {\n            await authClient.signUp.email(\n                {\n                    email: value.email,\n                    password: value.password,\n                    name: value.name,\n                },\n                {\n                    onSuccess: () => {\n                        navigate({\n                            to: \"/dashboard\",\n                        });\n                        toast.success(\"Sign up successful\");\n                    },\n                    onError: (error) => {\n                        toast.error(error.error.message || error.error.statusText);\n                    },\n                },\n            );\n        },\n        validators: {\n            onSubmit: z.object({\n                name: z.string().min(2, \"Name must be at least 2 characters\"),\n                email: z.email(\"Invalid email address\"),\n                password: z.string().min(8, \"Password must be at least 8 characters\"),\n            }),\n        },\n    });\n\n    return (\n        <div className=\"mx-auto w-full mt-10 max-w-md p-6\">\n            <h1 className=\"mb-6 text-center text-3xl font-bold\">Create Account</h1>\n\n            <form\n                onSubmit={(e) => {\n                    e.preventDefault();\n                    e.stopPropagation();\n                    form.handleSubmit();\n                }}\n                className=\"space-y-4\"\n            >\n                <div>\n                    <form.Field name=\"name\">\n                        {(field) => (\n                            <div className=\"space-y-2\">\n                                <Label htmlFor={field.name}>Name</Label>\n                                <Input\n                                    id={field.name}\n                                    name={field.name}\n                                    value={field.state.value}\n                                    onBlur={field.handleBlur}\n                                    onChange={(e) => field.handleChange(e.target.value)}\n                                />\n                                {field.state.meta.errors.map((error, index) => (\n                                    <p key={`${field.name}-error-${index}`} className=\"text-red-500\">\n                                        {error?.message}\n                                    </p>\n                                ))}\n                            </div>\n                        )}\n                    </form.Field>\n                </div>\n\n                <div>\n                    <form.Field name=\"email\">\n                        {(field) => (\n                            <div className=\"space-y-2\">\n                                <Label htmlFor={field.name}>Email</Label>\n                                <Input\n                                    id={field.name}\n                                    name={field.name}\n                                    type=\"email\"\n                                    value={field.state.value}\n                                    onBlur={field.handleBlur}\n                                    onChange={(e) => field.handleChange(e.target.value)}\n                                />\n                                {field.state.meta.errors.map((error, index) => (\n                                    <p key={`${field.name}-error-${index}`} className=\"text-red-500\">\n                                        {error?.message}\n                                    </p>\n                                ))}\n                            </div>\n                        )}\n                    </form.Field>\n                </div>\n\n                <div>\n                    <form.Field name=\"password\">\n                        {(field) => (\n                            <div className=\"space-y-2\">\n                                <Label htmlFor={field.name}>Password</Label>\n                                <Input\n                                    id={field.name}\n                                    name={field.name}\n                                    type=\"password\"\n                                    value={field.state.value}\n                                    onBlur={field.handleBlur}\n                                    onChange={(e) => field.handleChange(e.target.value)}\n                                />\n                                {field.state.meta.errors.map((error, index) => (\n                                    <p key={`${field.name}-error-${index}`} className=\"text-red-500\">\n                                        {error?.message}\n                                    </p>\n                                ))}\n                            </div>\n                        )}\n                    </form.Field>\n                </div>\n\n                <form.Subscribe selector={(state) => ({ canSubmit: state.canSubmit, isSubmitting: state.isSubmitting })}>\n          {({ canSubmit, isSubmitting }) => (\n                        <Button\n                            type=\"submit\"\n                            className=\"w-full\"\n                            disabled={!canSubmit || isSubmitting}\n                        >\n                            {isSubmitting ? \"Submitting...\" : \"Sign Up\"}\n                        </Button>\n                    )}\n                </form.Subscribe>\n            </form>\n\n            <div className=\"mt-4 text-center\">\n                <Button\n                    variant=\"link\"\n                    onClick={onSwitchToSignIn}\n                    className=\"text-indigo-600 hover:text-indigo-800\"\n                >\n                    Already have an account? Sign In\n                </Button>\n            </div>\n        </div>\n    );\n}\n"
  },
  {
    "path": "packages/template-generator/templates/auth/better-auth/convex/web/react/tanstack-router/src/components/user-menu.tsx.hbs",
    "content": "import { useNavigate } from \"@tanstack/react-router\";\n\nimport {\n    DropdownMenu,\n    DropdownMenuContent,\n    DropdownMenuGroup,\n    DropdownMenuItem,\n    DropdownMenuLabel,\n    DropdownMenuSeparator,\n    DropdownMenuTrigger,\n} from \"@{{projectName}}/ui/components/dropdown-menu\";\nimport { authClient } from \"@/lib/auth-client\";\nimport { useQuery } from \"convex/react\";\nimport { api } from \"@{{projectName}}/backend/convex/_generated/api\";\n\nimport { Button } from \"@{{projectName}}/ui/components/button\";\n\nexport default function UserMenu() {\n    const navigate = useNavigate();\n    const user = useQuery(api.auth.getCurrentUser)\n\n    return (\n        <DropdownMenu>\n            <DropdownMenuTrigger render={<Button variant=\"outline\" />}>\n                {user?.name}\n            </DropdownMenuTrigger>\n            <DropdownMenuContent className=\"bg-card\">\n                <DropdownMenuGroup>\n                    <DropdownMenuLabel>My Account</DropdownMenuLabel>\n                    <DropdownMenuSeparator />\n                    <DropdownMenuItem>{user?.email}</DropdownMenuItem>\n                    <DropdownMenuItem\n                        variant=\"destructive\"\n                        onClick={() => {\n                            authClient.signOut({\n                                fetchOptions: {\n                                    onSuccess: () => {\n                                        navigate({\n                                            to: \"/dashboard\",\n                                        });\n                                    },\n                                },\n                            });\n                        }}\n                    >\n                        Sign Out\n                    </DropdownMenuItem>\n                </DropdownMenuGroup>\n            </DropdownMenuContent>\n        </DropdownMenu>\n    );\n}\n"
  },
  {
    "path": "packages/template-generator/templates/auth/better-auth/convex/web/react/tanstack-router/src/lib/auth-client.ts.hbs",
    "content": "import { createAuthClient } from \"better-auth/react\";\nimport {\n\tconvexClient,\n\tcrossDomainClient,\n} from \"@convex-dev/better-auth/client/plugins\";\nimport { env } from \"@{{projectName}}/env/web\";\n\nexport const authClient = createAuthClient({\n\tbaseURL: env.VITE_CONVEX_SITE_URL,\n\tplugins: [crossDomainClient(), convexClient()],\n});\n"
  },
  {
    "path": "packages/template-generator/templates/auth/better-auth/convex/web/react/tanstack-router/src/routes/dashboard.tsx.hbs",
    "content": "import SignInForm from \"@/components/sign-in-form\";\nimport SignUpForm from \"@/components/sign-up-form\";\nimport UserMenu from \"@/components/user-menu\";\nimport { api } from \"@{{projectName}}/backend/convex/_generated/api\";\nimport { createFileRoute } from \"@tanstack/react-router\";\nimport {\n  Authenticated,\n  AuthLoading,\n  Unauthenticated,\n  useQuery,\n} from \"convex/react\";\nimport { useState } from \"react\";\n\nexport const Route = createFileRoute(\"/dashboard\")({\n  component: RouteComponent,\n});\n\nfunction PrivateDashboardContent() {\n  const privateData = useQuery(api.privateData.get);\n\n  return (\n    <div>\n      <h1>Dashboard</h1>\n      <p>privateData: {privateData?.message}</p>\n      <UserMenu />\n    </div>\n  );\n}\n\nfunction RouteComponent() {\n  const [showSignIn, setShowSignIn] = useState(false);\n\n  return (\n    <>\n      <Authenticated>\n        <PrivateDashboardContent />\n      </Authenticated>\n      <Unauthenticated>\n        {showSignIn ? (\n          <SignInForm onSwitchToSignUp={() => setShowSignIn(false)} />\n        ) : (\n          <SignUpForm onSwitchToSignIn={() => setShowSignIn(true)} />\n        )}\n      </Unauthenticated>\n      <AuthLoading>\n        <div>Loading...</div>\n      </AuthLoading>\n    </>\n  );\n}\n"
  },
  {
    "path": "packages/template-generator/templates/auth/better-auth/convex/web/react/tanstack-start/src/components/sign-in-form.tsx.hbs",
    "content": "import { authClient } from \"@/lib/auth-client\";\nimport { useForm } from \"@tanstack/react-form\";\nimport { useNavigate } from \"@tanstack/react-router\";\nimport { toast } from \"sonner\";\nimport z from \"zod\";\nimport { Button } from \"@{{projectName}}/ui/components/button\";\nimport { Input } from \"@{{projectName}}/ui/components/input\";\nimport { Label } from \"@{{projectName}}/ui/components/label\";\n\nexport default function SignInForm({\n    onSwitchToSignUp,\n}: {\n    onSwitchToSignUp: () => void;\n}) {\n    const navigate = useNavigate({\n        from: \"/\",\n    });\n\n    const form = useForm({\n        defaultValues: {\n            email: \"\",\n            password: \"\",\n        },\n        onSubmit: async ({ value }) => {\n            await authClient.signIn.email(\n                {\n                    email: value.email,\n                    password: value.password,\n                },\n                {\n                    onSuccess: () => {\n                        navigate({\n                            to: \"/dashboard\",\n                        });\n                        toast.success(\"Sign in successful\");\n                    },\n                    onError: (error) => {\n                        toast.error(error.error.message || error.error.statusText);\n                    },\n                },\n            );\n        },\n        validators: {\n            onSubmit: z.object({\n                email: z.email(\"Invalid email address\"),\n                password: z.string().min(8, \"Password must be at least 8 characters\"),\n            }),\n        },\n    });\n\n    return (\n        <div className=\"mx-auto w-full mt-10 max-w-md p-6\">\n            <h1 className=\"mb-6 text-center text-3xl font-bold\">Welcome Back</h1>\n\n            <form\n                onSubmit={(e) => {\n                    e.preventDefault();\n                    e.stopPropagation();\n                    form.handleSubmit();\n                }}\n                className=\"space-y-4\"\n            >\n                <div>\n                    <form.Field name=\"email\">\n                        {(field) => (\n                            <div className=\"space-y-2\">\n                                <Label htmlFor={field.name}>Email</Label>\n                                <Input\n                                    id={field.name}\n                                    name={field.name}\n                                    type=\"email\"\n                                    value={field.state.value}\n                                    onBlur={field.handleBlur}\n                                    onChange={(e) => field.handleChange(e.target.value)}\n                                />\n                                {field.state.meta.errors.map((error) => (\n                                    <p key={error?.message} className=\"text-red-500\">\n                                        {error?.message}\n                                    </p>\n                                ))}\n                            </div>\n                        )}\n                    </form.Field>\n                </div>\n\n                <div>\n                    <form.Field name=\"password\">\n                        {(field) => (\n                            <div className=\"space-y-2\">\n                                <Label htmlFor={field.name}>Password</Label>\n                                <Input\n                                    id={field.name}\n                                    name={field.name}\n                                    type=\"password\"\n                                    value={field.state.value}\n                                    onBlur={field.handleBlur}\n                                    onChange={(e) => field.handleChange(e.target.value)}\n                                />\n                                {field.state.meta.errors.map((error) => (\n                                    <p key={error?.message} className=\"text-red-500\">\n                                        {error?.message}\n                                    </p>\n                                ))}\n                            </div>\n                        )}\n                    </form.Field>\n                </div>\n\n                <form.Subscribe selector={(state) => ({ canSubmit: state.canSubmit, isSubmitting: state.isSubmitting })}>\n          {({ canSubmit, isSubmitting }) => (\n                        <Button\n                            type=\"submit\"\n                            className=\"w-full\"\n                            disabled={!canSubmit || isSubmitting}\n                        >\n                            {isSubmitting ? \"Submitting...\" : \"Sign In\"}\n                        </Button>\n                    )}\n                </form.Subscribe>\n            </form>\n\n            <div className=\"mt-4 text-center\">\n                <Button\n                    variant=\"link\"\n                    onClick={onSwitchToSignUp}\n                    className=\"text-indigo-600 hover:text-indigo-800\"\n                >\n                    Need an account? Sign Up\n                </Button>\n            </div>\n        </div>\n    );\n}\n"
  },
  {
    "path": "packages/template-generator/templates/auth/better-auth/convex/web/react/tanstack-start/src/components/sign-up-form.tsx.hbs",
    "content": "import { authClient } from \"@/lib/auth-client\";\nimport { useForm } from \"@tanstack/react-form\";\nimport { useNavigate } from \"@tanstack/react-router\";\nimport { toast } from \"sonner\";\nimport z from \"zod\";\nimport { Button } from \"@{{projectName}}/ui/components/button\";\nimport { Input } from \"@{{projectName}}/ui/components/input\";\nimport { Label } from \"@{{projectName}}/ui/components/label\";\n\nexport default function SignUpForm({\n    onSwitchToSignIn,\n}: {\n    onSwitchToSignIn: () => void;\n}) {\n    const navigate = useNavigate({\n        from: \"/\",\n    });\n\n    const form = useForm({\n        defaultValues: {\n            email: \"\",\n            password: \"\",\n            name: \"\",\n        },\n        onSubmit: async ({ value }) => {\n            await authClient.signUp.email(\n                {\n                    email: value.email,\n                    password: value.password,\n                    name: value.name,\n                },\n                {\n                    onSuccess: () => {\n                        navigate({\n                            to: \"/dashboard\",\n                        });\n                        toast.success(\"Sign up successful\");\n                    },\n                    onError: (error) => {\n                        toast.error(error.error.message || error.error.statusText);\n                    },\n                },\n            );\n        },\n        validators: {\n            onSubmit: z.object({\n                name: z.string().min(2, \"Name must be at least 2 characters\"),\n                email: z.email(\"Invalid email address\"),\n                password: z.string().min(8, \"Password must be at least 8 characters\"),\n            }),\n        },\n    });\n\n    return (\n        <div className=\"mx-auto w-full mt-10 max-w-md p-6\">\n            <h1 className=\"mb-6 text-center text-3xl font-bold\">Create Account</h1>\n\n            <form\n                onSubmit={(e) => {\n                    e.preventDefault();\n                    e.stopPropagation();\n                    form.handleSubmit();\n                }}\n                className=\"space-y-4\"\n            >\n                <div>\n                    <form.Field name=\"name\">\n                        {(field) => (\n                            <div className=\"space-y-2\">\n                                <Label htmlFor={field.name}>Name</Label>\n                                <Input\n                                    id={field.name}\n                                    name={field.name}\n                                    value={field.state.value}\n                                    onBlur={field.handleBlur}\n                                    onChange={(e) => field.handleChange(e.target.value)}\n                                />\n                                {field.state.meta.errors.map((error) => (\n                                    <p key={error?.message} className=\"text-red-500\">\n                                        {error?.message}\n                                    </p>\n                                ))}\n                            </div>\n                        )}\n                    </form.Field>\n                </div>\n\n                <div>\n                    <form.Field name=\"email\">\n                        {(field) => (\n                            <div className=\"space-y-2\">\n                                <Label htmlFor={field.name}>Email</Label>\n                                <Input\n                                    id={field.name}\n                                    name={field.name}\n                                    type=\"email\"\n                                    value={field.state.value}\n                                    onBlur={field.handleBlur}\n                                    onChange={(e) => field.handleChange(e.target.value)}\n                                />\n                                {field.state.meta.errors.map((error) => (\n                                    <p key={error?.message} className=\"text-red-500\">\n                                        {error?.message}\n                                    </p>\n                                ))}\n                            </div>\n                        )}\n                    </form.Field>\n                </div>\n\n                <div>\n                    <form.Field name=\"password\">\n                        {(field) => (\n                            <div className=\"space-y-2\">\n                                <Label htmlFor={field.name}>Password</Label>\n                                <Input\n                                    id={field.name}\n                                    name={field.name}\n                                    type=\"password\"\n                                    value={field.state.value}\n                                    onBlur={field.handleBlur}\n                                    onChange={(e) => field.handleChange(e.target.value)}\n                                />\n                                {field.state.meta.errors.map((error) => (\n                                    <p key={error?.message} className=\"text-red-500\">\n                                        {error?.message}\n                                    </p>\n                                ))}\n                            </div>\n                        )}\n                    </form.Field>\n                </div>\n\n                <form.Subscribe selector={(state) => ({ canSubmit: state.canSubmit, isSubmitting: state.isSubmitting })}>\n          {({ canSubmit, isSubmitting }) => (\n                        <Button\n                            type=\"submit\"\n                            className=\"w-full\"\n                            disabled={!canSubmit || isSubmitting}\n                        >\n                            {isSubmitting ? \"Submitting...\" : \"Sign Up\"}\n                        </Button>\n                    )}\n                </form.Subscribe>\n            </form>\n\n            <div className=\"mt-4 text-center\">\n                <Button\n                    variant=\"link\"\n                    onClick={onSwitchToSignIn}\n                    className=\"text-indigo-600 hover:text-indigo-800\"\n                >\n                    Already have an account? Sign In\n                </Button>\n            </div>\n        </div>\n    );\n}\n"
  },
  {
    "path": "packages/template-generator/templates/auth/better-auth/convex/web/react/tanstack-start/src/components/user-menu.tsx.hbs",
    "content": "import {\n    DropdownMenu,\n    DropdownMenuContent,\n    DropdownMenuGroup,\n    DropdownMenuItem,\n    DropdownMenuLabel,\n    DropdownMenuSeparator,\n    DropdownMenuTrigger,\n} from \"@{{projectName}}/ui/components/dropdown-menu\";\nimport { authClient } from \"@/lib/auth-client\";\nimport { useQuery } from \"convex/react\";\nimport { api } from \"@{{projectName}}/backend/convex/_generated/api\";\n\nimport { Button } from \"@{{projectName}}/ui/components/button\";\n\nexport default function UserMenu() {\n    const user = useQuery(api.auth.getCurrentUser)\n\n    return (\n        <DropdownMenu>\n            <DropdownMenuTrigger render={<Button variant=\"outline\" />}>\n                {user?.name}\n            </DropdownMenuTrigger>\n            <DropdownMenuContent className=\"bg-card\">\n                <DropdownMenuGroup>\n                    <DropdownMenuLabel>My Account</DropdownMenuLabel>\n                    <DropdownMenuSeparator />\n                    <DropdownMenuItem>{user?.email}</DropdownMenuItem>\n                    <DropdownMenuItem\n                        variant=\"destructive\"\n                        onClick={() => {\n                            authClient.signOut({\n                                fetchOptions: {\n                                    onSuccess: () => {\n                                        location.reload();\n                                    },\n                                },\n                            });\n                        }}\n                    >\n                        Sign Out\n                    </DropdownMenuItem>\n                </DropdownMenuGroup>\n            </DropdownMenuContent>\n        </DropdownMenu>\n    );\n}\n"
  },
  {
    "path": "packages/template-generator/templates/auth/better-auth/convex/web/react/tanstack-start/src/lib/auth-client.ts.hbs",
    "content": "import { createAuthClient } from \"better-auth/react\";\nimport { convexClient } from \"@convex-dev/better-auth/client/plugins\";\n\nexport const authClient = createAuthClient({\n  plugins: [convexClient()],\n});"
  },
  {
    "path": "packages/template-generator/templates/auth/better-auth/convex/web/react/tanstack-start/src/lib/auth-server.ts.hbs",
    "content": "import { convexBetterAuthReactStart } from \"@convex-dev/better-auth/react-start\";\nimport { env } from \"@{{projectName}}/env/web\";\n\nexport const {\n\thandler,\n\tgetToken,\n\tfetchAuthQuery,\n\tfetchAuthMutation,\n\tfetchAuthAction,\n} = convexBetterAuthReactStart({\n\tconvexUrl: env.VITE_CONVEX_URL,\n\tconvexSiteUrl: env.VITE_CONVEX_SITE_URL,\n});\n"
  },
  {
    "path": "packages/template-generator/templates/auth/better-auth/convex/web/react/tanstack-start/src/routes/api/auth/$.ts.hbs",
    "content": "import { createFileRoute } from \"@tanstack/react-router\";\nimport { handler } from \"@/lib/auth-server\";\n\nexport const Route = createFileRoute(\"/api/auth/$\")({\n  server: {\n    handlers: {\n      GET: ({ request }) => handler(request),\n      POST: ({ request }) => handler(request),\n    },\n  },\n});\n"
  },
  {
    "path": "packages/template-generator/templates/auth/better-auth/convex/web/react/tanstack-start/src/routes/dashboard.tsx.hbs",
    "content": "import SignInForm from \"@/components/sign-in-form\";\nimport SignUpForm from \"@/components/sign-up-form\";\nimport UserMenu from \"@/components/user-menu\";\nimport { api } from \"@{{projectName}}/backend/convex/_generated/api\";\nimport { createFileRoute } from \"@tanstack/react-router\";\nimport {\n  Authenticated,\n  AuthLoading,\n  Unauthenticated,\n  useQuery,\n} from \"convex/react\";\nimport { useState } from \"react\";\n\nexport const Route = createFileRoute(\"/dashboard\")({\n  component: RouteComponent,\n});\n\nfunction RouteComponent() {\n  const [showSignIn, setShowSignIn] = useState(false);\n  const privateData = useQuery(api.privateData.get);\n\n  return (\n    <>\n      <Authenticated>\n        <div>\n          <h1>Dashboard</h1>\n          <p>privateData: {privateData?.message}</p>\n          <UserMenu />\n        </div>\n      </Authenticated>\n      <Unauthenticated>\n        {showSignIn ? (\n          <SignInForm onSwitchToSignUp={() => setShowSignIn(false)} />\n        ) : (\n          <SignUpForm onSwitchToSignIn={() => setShowSignIn(true)} />\n        )}\n      </Unauthenticated>\n      <AuthLoading>\n        <div>Loading...</div>\n      </AuthLoading>\n    </>\n  );\n}\n"
  },
  {
    "path": "packages/template-generator/templates/auth/better-auth/fullstack/astro/src/env.d.ts.hbs",
    "content": "/// <reference path=\"../.astro/types.d.ts\" />\n\ndeclare namespace App {\n  interface Locals {\n    user: import(\"better-auth\").User | null;\n    session: import(\"better-auth\").Session | null;\n  }\n}\n"
  },
  {
    "path": "packages/template-generator/templates/auth/better-auth/fullstack/astro/src/middleware.ts.hbs",
    "content": "{{#if (or (eq runtime \"workers\") (eq serverDeploy \"cloudflare\") (and (eq backend \"self\") (eq webDeploy \"cloudflare\")))}}\nimport { createAuth } from \"@{{projectName}}/auth\";\n{{else}}\nimport { auth } from \"@{{projectName}}/auth\";\n{{/if}}\nimport { defineMiddleware } from \"astro:middleware\";\n\nexport const onRequest = defineMiddleware(async (context, next) => {\n{{#if (or (eq runtime \"workers\") (eq serverDeploy \"cloudflare\") (and (eq backend \"self\") (eq webDeploy \"cloudflare\")))}}\n  const auth = createAuth();\n{{/if}}\n  const isAuthed = await auth.api.getSession({\n    headers: context.request.headers,\n  });\n\n  if (isAuthed) {\n    context.locals.user = isAuthed.user;\n    context.locals.session = isAuthed.session;\n  } else {\n    context.locals.user = null;\n    context.locals.session = null;\n  }\n\n  return next();\n});\n"
  },
  {
    "path": "packages/template-generator/templates/auth/better-auth/fullstack/astro/src/pages/api/auth/[...all].ts.hbs",
    "content": "{{#if (or (eq runtime \"workers\") (eq serverDeploy \"cloudflare\") (and (eq backend \"self\") (eq webDeploy \"cloudflare\")))}}\nimport { createAuth } from \"@{{projectName}}/auth\";\n{{else}}\nimport { auth } from \"@{{projectName}}/auth\";\n{{/if}}\nimport type { APIRoute } from \"astro\";\n\nexport const ALL: APIRoute = async (ctx) => {\n{{#if (or (eq runtime \"workers\") (eq serverDeploy \"cloudflare\") (and (eq backend \"self\") (eq webDeploy \"cloudflare\")))}}\n  const auth = createAuth();\n{{/if}}\n  return auth.handler(ctx.request);\n};\n"
  },
  {
    "path": "packages/template-generator/templates/auth/better-auth/fullstack/next/src/app/api/auth/[...all]/route.ts.hbs",
    "content": "{{#if (or (eq runtime \"workers\") (eq serverDeploy \"cloudflare\") (and (eq backend \"self\") (eq webDeploy \"cloudflare\")))}}\nimport { createAuth } from \"@{{projectName}}/auth\";\n{{else}}\nimport { auth } from \"@{{projectName}}/auth\";\n{{/if}}\nimport { toNextJsHandler } from \"better-auth/next-js\";\n\n{{#if (or (eq runtime \"workers\") (eq serverDeploy \"cloudflare\") (and (eq backend \"self\") (eq webDeploy \"cloudflare\")))}}\nexport async function GET(request: Request) {\n\treturn toNextJsHandler(createAuth()).GET(request);\n}\n\nexport async function POST(request: Request) {\n\treturn toNextJsHandler(createAuth()).POST(request);\n}\n{{else}}\nexport const { GET, POST } = toNextJsHandler(auth);\n{{/if}}\n"
  },
  {
    "path": "packages/template-generator/templates/auth/better-auth/fullstack/nuxt/server/api/auth/[...all].ts.hbs",
    "content": "{{#if (or (eq runtime \"workers\") (eq serverDeploy \"cloudflare\") (and (eq backend \"self\") (eq webDeploy \"cloudflare\")))}}\nimport { createAuth } from \"@{{projectName}}/auth\";\n{{else}}\nimport { auth } from \"@{{projectName}}/auth\";\n{{/if}}\n\nexport default defineEventHandler((event) => {\n{{#if (or (eq runtime \"workers\") (eq serverDeploy \"cloudflare\") (and (eq backend \"self\") (eq webDeploy \"cloudflare\")))}}\n  const auth = createAuth();\n{{/if}}\n  return auth.handler(toWebRequest(event));\n});\n"
  },
  {
    "path": "packages/template-generator/templates/auth/better-auth/fullstack/svelte/src/hooks.server.ts.hbs",
    "content": "{{#if (eq api \"orpc\")}}\nimport \"./lib/orpc.server\";\n{{/if}}\nimport { building } from \"$app/environment\";\n{{#if (or (eq runtime \"workers\") (eq serverDeploy \"cloudflare\") (and (eq backend \"self\") (eq webDeploy \"cloudflare\")))}}\nimport { createAuth } from \"@{{projectName}}/auth\";\n{{#if (and (eq backend \"self\") (eq webDeploy \"cloudflare\"))}}\nimport { env as localEnv } from \"@{{projectName}}/env/server\";\n{{/if}}\n{{else}}\nimport { auth } from \"@{{projectName}}/auth\";\n{{/if}}\nimport { svelteKitHandler } from \"better-auth/svelte-kit\";\nimport type { Handle } from \"@sveltejs/kit\";\n\nexport const handle: Handle = async ({ event, resolve }) => {\n{{#if (or (eq runtime \"workers\") (eq serverDeploy \"cloudflare\") (and (eq backend \"self\") (eq webDeploy \"cloudflare\")))}}\n{{#if (and (eq backend \"self\") (eq webDeploy \"cloudflare\"))}}\n\tif (building) {\n\t\treturn resolve(event);\n\t}\n\n\tconst authEnv = event.platform?.env ?? localEnv;\n\tconst authInstance = createAuth(authEnv);\n{{else}}\n\tconst authInstance = createAuth();\n{{/if}}\n{{else}}\n\tconst authInstance = auth;\n{{/if}}\n\n\treturn svelteKitHandler({\n\t\tevent,\n\t\tresolve,\n\t\tauth: authInstance,\n\t\tbuilding,\n\t});\n};\n"
  },
  {
    "path": "packages/template-generator/templates/auth/better-auth/fullstack/tanstack-start/src/routes/api/auth/$.ts.hbs",
    "content": "{{#if (or (eq runtime \"workers\") (eq serverDeploy \"cloudflare\") (and (eq backend \"self\") (eq webDeploy \"cloudflare\")))}}\nimport { createAuth } from '@{{projectName}}/auth'\n{{else}}\nimport { auth } from '@{{projectName}}/auth'\n{{/if}}\nimport { createFileRoute } from '@tanstack/react-router'\n\nexport const Route = createFileRoute('/api/auth/$')({\n  server: {\n    handlers: {\n      GET: ({ request }) => {\n        {{#if (or (eq runtime \"workers\") (eq serverDeploy \"cloudflare\") (and (eq backend \"self\") (eq webDeploy \"cloudflare\")))}}\n        const auth = createAuth()\n        {{/if}}\n        return auth.handler(request)\n      },\n      POST: ({ request }) => {\n        {{#if (or (eq runtime \"workers\") (eq serverDeploy \"cloudflare\") (and (eq backend \"self\") (eq webDeploy \"cloudflare\")))}}\n        const auth = createAuth()\n        {{/if}}\n        return auth.handler(request)\n      },\n    },\n  },\n})\n"
  },
  {
    "path": "packages/template-generator/templates/auth/better-auth/native/bare/app/(drawer)/index.tsx.hbs",
    "content": "import { View, Text, ScrollView, TouchableOpacity, StyleSheet } from \"react-native\";\nimport { Container } from \"@/components/container\";\nimport { useColorScheme } from \"@/lib/use-color-scheme\";\nimport { NAV_THEME } from \"@/lib/constants\";\nimport { authClient } from \"@/lib/auth-client\";\nimport { SignIn } from \"@/components/sign-in\";\nimport { SignUp } from \"@/components/sign-up\";\n{{#if (eq api \"orpc\")}}\nimport { useQuery } from \"@tanstack/react-query\";\nimport { queryClient, orpc } from \"@/utils/orpc\";\n{{/if}}\n{{#if (eq api \"trpc\")}}\nimport { useQuery } from \"@tanstack/react-query\";\nimport { queryClient, trpc } from \"@/utils/trpc\";\n{{/if}}\n\nexport default function Home() {\nconst { colorScheme } = useColorScheme();\nconst theme = colorScheme === \"dark\" ? NAV_THEME.dark : NAV_THEME.light;\n{{#if (eq api \"orpc\")}}\nconst healthCheck = useQuery(orpc.healthCheck.queryOptions());\nconst privateData = useQuery(orpc.privateData.queryOptions());\nconst isConnected = healthCheck?.data === \"OK\";\nconst isLoading = healthCheck?.isLoading;\n{{/if}}\n{{#if (eq api \"trpc\")}}\nconst healthCheck = useQuery(trpc.healthCheck.queryOptions());\nconst privateData = useQuery(trpc.privateData.queryOptions());\nconst isConnected = healthCheck?.data === \"OK\";\nconst isLoading = healthCheck?.isLoading;\n{{/if}}\nconst { data: session } = authClient.useSession();\n\nreturn (\n<Container>\n  <ScrollView style={styles.scrollView}>\n    <View style={styles.content}>\n      <Text style={[styles.title, { color: theme.text }]}>\n        BETTER T STACK\n      </Text>\n\n      {session?.user ? (\n      <View style={[styles.userCard, { backgroundColor: theme.card, borderColor: theme.border }]}>\n        <View style={styles.userHeader}>\n          <Text style={[styles.userText, { color: theme.text }]}>\n            Welcome, <Text style={styles.userName}>{session.user.name}</Text>\n          </Text>\n        </View>\n        <Text style={[styles.userEmail, { color: theme.text, opacity: 0.7 }]}>\n          {session.user.email}\n        </Text>\n        <TouchableOpacity style={[styles.signOutButton, { backgroundColor: theme.notification }]} onPress={()=> {\n          authClient.signOut();\n          {{#if (eq api \"orpc\")}}\n          queryClient.invalidateQueries();\n          {{/if}}\n          {{#if (eq api \"trpc\")}}\n          queryClient.invalidateQueries();\n          {{/if}}\n          }}\n          >\n          <Text style={styles.signOutText}>Sign Out</Text>\n        </TouchableOpacity>\n      </View>\n      ) : null}\n\n      {{#unless (eq api \"none\")}}\n      <View style={[styles.statusCard, { backgroundColor: theme.card, borderColor: theme.border }]}>\n        <Text style={[styles.cardTitle, { color: theme.text }]}>\n          System Status\n        </Text>\n        <View style={styles.statusRow}>\n          <View style={[styles.statusIndicator, { backgroundColor: isConnected ? \"#10b981\" : \"#ef4444\" }]} />\n          <View style={styles.statusContent}>\n            <Text style={[styles.statusTitle, { color: theme.text }]}>\n              {{#if (eq api \"orpc\")}}ORPC{{else}}TRPC{{/if}} Backend\n            </Text>\n            <Text style={[styles.statusText, { color: theme.text, opacity: 0.7 }]}>\n              {isLoading\n              ? \"Checking connection...\"\n              : isConnected\n              ? \"Connected to API\"\n              : \"API Disconnected\"}\n            </Text>\n          </View>\n        </View>\n      </View>\n\n      <View style={[styles.privateDataCard, { backgroundColor: theme.card, borderColor: theme.border }]}>\n        <Text style={[styles.cardTitle, { color: theme.text }]}>\n          Private Data\n        </Text>\n        {privateData && (\n        <Text style={[styles.privateDataText, { color: theme.text, opacity: 0.7 }]}>\n          {privateData.data?.message}\n        </Text>\n        )}\n      </View>\n      {{/unless}}\n\n      {!session?.user && (\n      <>\n        <SignIn />\n        <SignUp />\n      </>\n      )}\n    </View>\n  </ScrollView>\n</Container>\n);\n}\n\nconst styles = StyleSheet.create({\nscrollView: {\nflex: 1,\n},\ncontent: {\npadding: 16,\n},\ntitle: {\nfontSize: 24,\nfontWeight: \"bold\",\nmarginBottom: 16,\n},\nuserCard: {\nmarginBottom: 16,\npadding: 16,\nborderWidth: 1,\n},\nuserHeader: {\nmarginBottom: 8,\n},\nuserText: {\nfontSize: 16,\n},\nuserName: {\nfontWeight: \"bold\",\n},\nuserEmail: {\nfontSize: 14,\nmarginBottom: 12,\n},\nsignOutButton: {\npadding: 12,\n},\nsignOutText: {\ncolor: \"#ffffff\",\n},\nstatusCard: {\nmarginBottom: 16,\npadding: 16,\nborderWidth: 1,\n},\ncardTitle: {\nfontSize: 16,\nfontWeight: \"bold\",\nmarginBottom: 12,\n},\nstatusRow: {\nflexDirection: \"row\",\nalignItems: \"center\",\ngap: 8,\n},\nstatusIndicator: {\nheight: 8,\nwidth: 8,\n},\nstatusContent: {\nflex: 1,\n},\nstatusTitle: {\nfontSize: 14,\nfontWeight: \"bold\",\n},\nstatusText: {\nfontSize: 12,\n},\nprivateDataCard: {\nmarginBottom: 16,\npadding: 16,\nborderWidth: 1,\n},\nprivateDataText: {\nfontSize: 14,\n},\n});"
  },
  {
    "path": "packages/template-generator/templates/auth/better-auth/native/bare/components/sign-in.tsx.hbs",
    "content": "import { authClient } from \"@/lib/auth-client\";\n{{#if (eq api \"trpc\")}}\nimport { queryClient } from \"@/utils/trpc\";\n{{/if}}\n{{#if (eq api \"orpc\")}}\nimport { queryClient } from \"@/utils/orpc\";\n{{/if}}\nimport { useForm } from \"@tanstack/react-form\";\nimport { useState } from \"react\";\nimport {\n  ActivityIndicator,\n  StyleSheet,\n  Text,\n  TextInput,\n  TouchableOpacity,\n  View,\n} from \"react-native\";\nimport { NAV_THEME } from \"@/lib/constants\";\nimport { useColorScheme } from \"@/lib/use-color-scheme\";\nimport z from \"zod\";\n\nconst signInSchema = z.object({\n  email: z\n    .string()\n    .trim()\n    .min(1, \"Email is required\")\n    .email(\"Enter a valid email address\"),\n  password: z\n    .string()\n    .min(1, \"Password is required\")\n    .min(8, \"Use at least 8 characters\"),\n});\n\nfunction getErrorMessage(error: unknown): string | null {\n  if (!error) return null;\n\n  if (typeof error === \"string\") {\n    return error;\n  }\n\n  if (Array.isArray(error)) {\n    for (const issue of error) {\n      const message = getErrorMessage(issue);\n      if (message) {\n        return message;\n      }\n    }\n    return null;\n  }\n\n  if (typeof error === \"object\" && error !== null) {\n    const maybeError = error as { message?: unknown };\n    if (typeof maybeError.message === \"string\") {\n      return maybeError.message;\n    }\n  }\n\n  return null;\n}\n\nfunction SignIn() {\n  const { colorScheme } = useColorScheme();\n  const theme = colorScheme === \"dark\" ? NAV_THEME.dark : NAV_THEME.light;\n  const [error, setError] = useState<string | null>(null);\n\n  const form = useForm({\n    defaultValues: {\n      email: \"\",\n      password: \"\",\n    },\n    validators: {\n      onSubmit: signInSchema,\n    },\n    onSubmit: async ({ value, formApi }) => {\n      await authClient.signIn.email(\n        {\n          email: value.email.trim(),\n          password: value.password,\n        },\n        {\n          onError(error) {\n            setError(error.error?.message || \"Failed to sign in\");\n          },\n          onSuccess() {\n            setError(null);\n            formApi.reset();\n            {{#if (eq api \"orpc\")}}\n            queryClient.refetchQueries();\n            {{/if}}\n            {{#if (eq api \"trpc\")}}\n            queryClient.refetchQueries();\n            {{/if}}\n          },\n        },\n      );\n    },\n  });\n\n  return (\n    <View style={[styles.card, { backgroundColor: theme.card, borderColor: theme.border }]}>\n      <Text style={[styles.title, { color: theme.text }]}>Sign In</Text>\n\n      <form.Subscribe\n        selector={(state) => ({\n          isSubmitting: state.isSubmitting,\n          validationError: getErrorMessage(state.errorMap.onSubmit),\n        })}\n      >\n        {({ isSubmitting, validationError }) => {\n          const formError = error ?? validationError;\n\n          return (\n            <>\n              {formError ? (\n                <View style={[styles.errorContainer, { backgroundColor: theme.notification + \"20\" }]}>\n                  <Text style={[styles.errorText, { color: theme.notification }]}>{formError}</Text>\n                </View>\n              ) : null}\n\n              <form.Field name=\"email\">\n                {(field) => (\n                  <TextInput\n                    style={[\n                      styles.input,\n                      {\n                        color: theme.text,\n                        borderColor: theme.border,\n                        backgroundColor: theme.background,\n                      },\n                    ]}\n                    placeholder=\"Email\"\n                    placeholderTextColor={theme.text}\n                    value={field.state.value}\n                    onBlur={field.handleBlur}\n                    onChangeText={(value) => {\n                      field.handleChange(value);\n                      if (error) {\n                        setError(null);\n                      }\n                    }}\n                    keyboardType=\"email-address\"\n                    autoCapitalize=\"none\"\n                  />\n                )}\n              </form.Field>\n\n              <form.Field name=\"password\">\n                {(field) => (\n                  <TextInput\n                    style={[\n                      styles.input,\n                      {\n                        color: theme.text,\n                        borderColor: theme.border,\n                        backgroundColor: theme.background,\n                      },\n                    ]}\n                    placeholder=\"Password\"\n                    placeholderTextColor={theme.text}\n                    value={field.state.value}\n                    onBlur={field.handleBlur}\n                    onChangeText={(value) => {\n                      field.handleChange(value);\n                      if (error) {\n                        setError(null);\n                      }\n                    }}\n                    secureTextEntry\n                    onSubmitEditing={form.handleSubmit}\n                  />\n                )}\n              </form.Field>\n\n              <TouchableOpacity\n                onPress={form.handleSubmit}\n                disabled={isSubmitting}\n                style={[\n                  styles.button,\n                  {\n                    backgroundColor: theme.primary,\n                    opacity: isSubmitting ? 0.5 : 1,\n                  },\n                ]}\n              >\n                {isSubmitting ? (\n                  <ActivityIndicator size=\"small\" color=\"#ffffff\" />\n                ) : (\n                  <Text style={styles.buttonText}>Sign In</Text>\n                )}\n              </TouchableOpacity>\n            </>\n          );\n        }}\n      </form.Subscribe>\n    </View>\n  );\n}\n\nconst styles = StyleSheet.create({\n  card: {\n    marginTop: 16,\n    padding: 16,\n    borderWidth: 1,\n  },\n  title: {\n    fontSize: 18,\n    fontWeight: \"bold\",\n    marginBottom: 12,\n  },\n  errorContainer: {\n    marginBottom: 12,\n    padding: 8,\n  },\n  errorText: {\n    fontSize: 14,\n  },\n  input: {\n    borderWidth: 1,\n    padding: 12,\n    fontSize: 16,\n    marginBottom: 12,\n  },\n  button: {\n    padding: 12,\n    alignItems: \"center\",\n    justifyContent: \"center\",\n  },\n  buttonText: {\n    color: \"#ffffff\",\n    fontSize: 16,\n  },\n});\n\nexport { SignIn };\n"
  },
  {
    "path": "packages/template-generator/templates/auth/better-auth/native/bare/components/sign-up.tsx.hbs",
    "content": "import { authClient } from \"@/lib/auth-client\";\n{{#if (eq api \"trpc\")}}\nimport { queryClient } from \"@/utils/trpc\";\n{{/if}}\n{{#if (eq api \"orpc\")}}\nimport { queryClient } from \"@/utils/orpc\";\n{{/if}}\nimport { useForm } from \"@tanstack/react-form\";\nimport { useState } from \"react\";\nimport {\n  ActivityIndicator,\n  StyleSheet,\n  Text,\n  TextInput,\n  TouchableOpacity,\n  View,\n} from \"react-native\";\nimport { NAV_THEME } from \"@/lib/constants\";\nimport { useColorScheme } from \"@/lib/use-color-scheme\";\nimport z from \"zod\";\n\nconst signUpSchema = z.object({\n  name: z\n    .string()\n    .trim()\n    .min(1, \"Name is required\")\n    .min(2, \"Name must be at least 2 characters\"),\n  email: z\n    .string()\n    .trim()\n    .min(1, \"Email is required\")\n    .email(\"Enter a valid email address\"),\n  password: z\n    .string()\n    .min(1, \"Password is required\")\n    .min(8, \"Use at least 8 characters\"),\n});\n\nfunction getErrorMessage(error: unknown): string | null {\n  if (!error) return null;\n\n  if (typeof error === \"string\") {\n    return error;\n  }\n\n  if (Array.isArray(error)) {\n    for (const issue of error) {\n      const message = getErrorMessage(issue);\n      if (message) {\n        return message;\n      }\n    }\n    return null;\n  }\n\n  if (typeof error === \"object\" && error !== null) {\n    const maybeError = error as { message?: unknown };\n    if (typeof maybeError.message === \"string\") {\n      return maybeError.message;\n    }\n  }\n\n  return null;\n}\n\nfunction SignUp() {\n  const { colorScheme } = useColorScheme();\n  const theme = colorScheme === \"dark\" ? NAV_THEME.dark : NAV_THEME.light;\n  const [error, setError] = useState<string | null>(null);\n\n  const form = useForm({\n    defaultValues: {\n      name: \"\",\n      email: \"\",\n      password: \"\",\n    },\n    validators: {\n      onSubmit: signUpSchema,\n    },\n    onSubmit: async ({ value, formApi }) => {\n      await authClient.signUp.email(\n        {\n          name: value.name.trim(),\n          email: value.email.trim(),\n          password: value.password,\n        },\n        {\n          onError(error) {\n            setError(error.error?.message || \"Failed to sign up\");\n          },\n          onSuccess() {\n            setError(null);\n            formApi.reset();\n            {{#if (eq api \"orpc\")}}\n            queryClient.refetchQueries();\n            {{/if}}\n            {{#if (eq api \"trpc\")}}\n            queryClient.refetchQueries();\n            {{/if}}\n          },\n        },\n      );\n    },\n  });\n\n  return (\n    <View style={[styles.card, { backgroundColor: theme.card, borderColor: theme.border }]}>\n      <Text style={[styles.title, { color: theme.text }]}>Create Account</Text>\n\n      <form.Subscribe\n        selector={(state) => ({\n          isSubmitting: state.isSubmitting,\n          validationError: getErrorMessage(state.errorMap.onSubmit),\n        })}\n      >\n        {({ isSubmitting, validationError }) => {\n          const formError = error ?? validationError;\n\n          return (\n            <>\n              {formError ? (\n                <View style={[styles.errorContainer, { backgroundColor: theme.notification + \"20\" }]}>\n                  <Text style={[styles.errorText, { color: theme.notification }]}>{formError}</Text>\n                </View>\n              ) : null}\n\n              <form.Field name=\"name\">\n                {(field) => (\n                  <TextInput\n                    style={[\n                      styles.input,\n                      {\n                        color: theme.text,\n                        borderColor: theme.border,\n                        backgroundColor: theme.background,\n                      },\n                    ]}\n                    placeholder=\"Name\"\n                    placeholderTextColor={theme.text}\n                    value={field.state.value}\n                    onBlur={field.handleBlur}\n                    onChangeText={(value) => {\n                      field.handleChange(value);\n                      if (error) {\n                        setError(null);\n                      }\n                    }}\n                  />\n                )}\n              </form.Field>\n\n              <form.Field name=\"email\">\n                {(field) => (\n                  <TextInput\n                    style={[\n                      styles.input,\n                      {\n                        color: theme.text,\n                        borderColor: theme.border,\n                        backgroundColor: theme.background,\n                      },\n                    ]}\n                    placeholder=\"Email\"\n                    placeholderTextColor={theme.text}\n                    value={field.state.value}\n                    onBlur={field.handleBlur}\n                    onChangeText={(value) => {\n                      field.handleChange(value);\n                      if (error) {\n                        setError(null);\n                      }\n                    }}\n                    keyboardType=\"email-address\"\n                    autoCapitalize=\"none\"\n                  />\n                )}\n              </form.Field>\n\n              <form.Field name=\"password\">\n                {(field) => (\n                  <TextInput\n                    style={[\n                      styles.input,\n                      {\n                        color: theme.text,\n                        borderColor: theme.border,\n                        backgroundColor: theme.background,\n                      },\n                    ]}\n                    placeholder=\"Password\"\n                    placeholderTextColor={theme.text}\n                    value={field.state.value}\n                    onBlur={field.handleBlur}\n                    onChangeText={(value) => {\n                      field.handleChange(value);\n                      if (error) {\n                        setError(null);\n                      }\n                    }}\n                    secureTextEntry\n                    onSubmitEditing={form.handleSubmit}\n                  />\n                )}\n              </form.Field>\n\n              <TouchableOpacity\n                onPress={form.handleSubmit}\n                disabled={isSubmitting}\n                style={[\n                  styles.button,\n                  {\n                    backgroundColor: theme.primary,\n                    opacity: isSubmitting ? 0.5 : 1,\n                  },\n                ]}\n              >\n                {isSubmitting ? (\n                  <ActivityIndicator size=\"small\" color=\"#ffffff\" />\n                ) : (\n                  <Text style={styles.buttonText}>Sign Up</Text>\n                )}\n              </TouchableOpacity>\n            </>\n          );\n        }}\n      </form.Subscribe>\n    </View>\n  );\n}\n\nconst styles = StyleSheet.create({\n  card: {\n    marginTop: 16,\n    padding: 16,\n    borderWidth: 1,\n  },\n  title: {\n    fontSize: 18,\n    fontWeight: \"bold\",\n    marginBottom: 12,\n  },\n  errorContainer: {\n    marginBottom: 12,\n    padding: 8,\n  },\n  errorText: {\n    fontSize: 14,\n  },\n  input: {\n    borderWidth: 1,\n    padding: 12,\n    fontSize: 16,\n    marginBottom: 12,\n  },\n  button: {\n    padding: 12,\n    alignItems: \"center\",\n    justifyContent: \"center\",\n  },\n  buttonText: {\n    color: \"#ffffff\",\n    fontSize: 16,\n  },\n});\n\nexport { SignUp };\n"
  },
  {
    "path": "packages/template-generator/templates/auth/better-auth/native/base/lib/auth-client.ts.hbs",
    "content": "import { expoClient } from \"@better-auth/expo/client\";\nimport { createAuthClient } from \"better-auth/react\";\nimport * as SecureStore from \"expo-secure-store\";\nimport Constants from \"expo-constants\";\nimport { env } from \"@{{projectName}}/env/native\";\n\nexport const authClient = createAuthClient({\n\tbaseURL: env.EXPO_PUBLIC_SERVER_URL,\n\tplugins: [\n\t\texpoClient({\n\t\t\tscheme: Constants.expoConfig?.scheme as string,\n\t\t\tstoragePrefix: Constants.expoConfig?.scheme as string,\n\t\t\tstorage: SecureStore,\n\t\t}),\n\t],\n});\n"
  },
  {
    "path": "packages/template-generator/templates/auth/better-auth/native/unistyles/app/(drawer)/index.tsx.hbs",
    "content": "import { authClient } from \"@/lib/auth-client\";\nimport { ScrollView, Text, TouchableOpacity, View } from \"react-native\";\nimport { StyleSheet } from \"react-native-unistyles\";\n\nimport { Container } from \"@/components/container\";\nimport { SignIn } from \"@/components/sign-in\";\nimport { SignUp } from \"@/components/sign-up\";\n{{#if (eq api \"orpc\")}}\nimport { useQuery } from \"@tanstack/react-query\";\nimport { queryClient, orpc } from \"@/utils/orpc\";\n{{/if}}\n{{#if (eq api \"trpc\")}}\nimport { useQuery } from \"@tanstack/react-query\";\nimport { queryClient, trpc } from \"@/utils/trpc\";\n{{/if}}\n\nexport default function Home() {\n    {{#if (eq api \"orpc\")}}\n    const healthCheck = useQuery(orpc.healthCheck.queryOptions());\n    const privateData = useQuery(orpc.privateData.queryOptions());\n    {{/if}}\n    {{#if (eq api \"trpc\")}}\n    const healthCheck = useQuery(trpc.healthCheck.queryOptions());\n    const privateData = useQuery(trpc.privateData.queryOptions());\n    {{/if}}\n  const { data: session } = authClient.useSession();\n\n  return (\n    <Container>\n      <ScrollView>\n        <View style={styles.pageContainer}>\n          <Text style={styles.headerTitle}>BETTER T STACK</Text>\n          {session?.user ? (\n            <View style={styles.sessionInfoCard}>\n              <View style={styles.sessionUserRow}>\n                <Text style={styles.welcomeText}>\n                  Welcome,{\" \"}\n                  <Text style={styles.userNameText}>{session.user.name}</Text>\n                </Text>\n              </View>\n              <Text style={styles.emailText}>{session.user.email}</Text>\n\n              <TouchableOpacity\n                style={styles.signOutButton}\n                onPress={() => {\n                  authClient.signOut();\n                  {{#if (eq api \"orpc\")}}\n                  queryClient.invalidateQueries();\n                  {{/if}}\n                  {{#if (eq api \"trpc\")}}\n                  queryClient.invalidateQueries();\n                  {{/if}}\n                }}\n              >\n                <Text style={styles.signOutButtonText}>Sign Out</Text>\n              </TouchableOpacity>\n            </View>\n          ) : null}\n          {{#unless (eq api \"none\")}}\n          <View style={styles.apiStatusCard}>\n            <Text style={styles.cardTitle}>API Status</Text>\n            <View style={styles.apiStatusRow}>\n              <View\n                style={[\n                  styles.statusIndicatorDot,\n                  healthCheck.data\n                    ? styles.statusIndicatorGreen\n                    : styles.statusIndicatorRed,\n                ]}\n              />\n              <Text style={styles.mutedText}>\n                {healthCheck.isLoading\n                  ? \"Checking...\"\n                  : healthCheck.data\n                    ? \"Connected to API\"\n                    : \"API Disconnected\"}\n              </Text>\n            </View>\n          </View>\n          <View style={styles.privateDataCard}>\n            <Text style={styles.cardTitle}>Private Data</Text>\n            {privateData && (\n              <View>\n                <Text style={styles.mutedText}>\n                  {privateData.data?.message}\n                </Text>\n              </View>\n            )}\n          </View>\n          {{/unless}}\n          {!session?.user && (\n            <>\n              <SignIn />\n              <SignUp />\n            </>\n          )}\n        </View>\n      </ScrollView>\n    </Container>\n  );\n}\n\nconst styles = StyleSheet.create((theme) => ({\n  pageContainer: {\n    paddingHorizontal: 8,\n  },\n  headerTitle: {\n    color: theme?.colors?.typography,\n    fontSize: 30,\n    fontWeight: \"bold\",\n    marginBottom: 16,\n  },\n  sessionInfoCard: {\n    marginBottom: 24,\n    padding: 16,\n    borderRadius: 8,\n    borderWidth: 1,\n    borderColor: theme?.colors?.border,\n  },\n  sessionUserRow: {\n    flexDirection: \"row\",\n    justifyContent: \"space-between\",\n    alignItems: \"center\",\n    marginBottom: 8,\n  },\n  welcomeText: {\n    color: theme?.colors?.typography,\n    fontSize: 16,\n  },\n  userNameText: {\n    fontWeight: \"500\",\n    color: theme?.colors?.typography,\n  },\n  emailText: {\n    color: theme?.colors?.typography,\n    fontSize: 14,\n    marginBottom: 16,\n  },\n  signOutButton: {\n    backgroundColor: theme?.colors?.destructive,\n    paddingVertical: 8,\n    paddingHorizontal: 16,\n    borderRadius: 6,\n    alignSelf: \"flex-start\",\n  },\n  signOutButtonText: {\n    fontWeight: \"500\",\n  },\n  apiStatusCard: {\n    marginBottom: 24,\n    borderRadius: 8,\n    borderWidth: 1,\n    borderColor: theme?.colors?.border,\n    padding: 16,\n  },\n  cardTitle: {\n    marginBottom: 12,\n    fontWeight: \"500\",\n    color: theme?.colors?.typography,\n  },\n  apiStatusRow: {\n    flexDirection: \"row\",\n    alignItems: \"center\",\n    gap: 8,\n  },\n  statusIndicatorDot: {\n    height: 12,\n    width: 12,\n    borderRadius: 9999,\n  },\n  statusIndicatorGreen: {\n    backgroundColor: theme.colors.success,\n  },\n  statusIndicatorRed: {\n    backgroundColor: theme.colors.destructive,\n  },\n  mutedText: {\n    color: theme?.colors?.typography,\n  },\n  privateDataCard: {\n    marginBottom: 24,\n    borderRadius: 8,\n    borderWidth: 1,\n    borderColor: theme?.colors?.border,\n    padding: 16,\n  },\n}));\n"
  },
  {
    "path": "packages/template-generator/templates/auth/better-auth/native/unistyles/components/sign-in.tsx.hbs",
    "content": "import { authClient } from \"@/lib/auth-client\";\n{{#if (eq api \"trpc\")}}\nimport { queryClient } from \"@/utils/trpc\";\n{{/if}}\n{{#if (eq api \"orpc\")}}\nimport { queryClient } from \"@/utils/orpc\";\n{{/if}}\nimport { useForm } from \"@tanstack/react-form\";\nimport { useState } from \"react\";\nimport {\n  ActivityIndicator,\n  Text,\n  TextInput,\n  TouchableOpacity,\n  View,\n} from \"react-native\";\nimport { StyleSheet } from \"react-native-unistyles\";\nimport z from \"zod\";\n\nconst signInSchema = z.object({\n  email: z\n    .string()\n    .trim()\n    .min(1, \"Email is required\")\n    .email(\"Enter a valid email address\"),\n  password: z\n    .string()\n    .min(1, \"Password is required\")\n    .min(8, \"Use at least 8 characters\"),\n});\n\nfunction getErrorMessage(error: unknown): string | null {\n  if (!error) return null;\n\n  if (typeof error === \"string\") {\n    return error;\n  }\n\n  if (Array.isArray(error)) {\n    for (const issue of error) {\n      const message = getErrorMessage(issue);\n      if (message) {\n        return message;\n      }\n    }\n    return null;\n  }\n\n  if (typeof error === \"object\" && error !== null) {\n    const maybeError = error as { message?: unknown };\n    if (typeof maybeError.message === \"string\") {\n      return maybeError.message;\n    }\n  }\n\n  return null;\n}\n\nexport function SignIn() {\n  const [error, setError] = useState<string | null>(null);\n\n  const form = useForm({\n    defaultValues: {\n      email: \"\",\n      password: \"\",\n    },\n    validators: {\n      onSubmit: signInSchema,\n    },\n    onSubmit: async ({ value, formApi }) => {\n      await authClient.signIn.email(\n        {\n          email: value.email.trim(),\n          password: value.password,\n        },\n        {\n          onError(error) {\n            setError(error.error?.message || \"Failed to sign in\");\n          },\n          onSuccess() {\n            setError(null);\n            formApi.reset();\n            {{#if (eq api \"orpc\")}}\n            queryClient.refetchQueries();\n            {{/if}}\n            {{#if (eq api \"trpc\")}}\n            queryClient.refetchQueries();\n            {{/if}}\n          },\n        },\n      );\n    },\n  });\n\n  return (\n    <View style={styles.container}>\n      <Text style={styles.title}>Sign In</Text>\n\n      <form.Subscribe\n        selector={(state) => ({\n          isSubmitting: state.isSubmitting,\n          validationError: getErrorMessage(state.errorMap.onSubmit),\n        })}\n      >\n        {({ isSubmitting, validationError }) => {\n          const formError = error ?? validationError;\n\n          return (\n            <>\n              {formError ? (\n                <View style={styles.errorContainer}>\n                  <Text style={styles.errorText}>{formError}</Text>\n                </View>\n              ) : null}\n\n              <form.Field name=\"email\">\n                {(field) => (\n                  <TextInput\n                    style={styles.input}\n                    placeholder=\"Email\"\n                    value={field.state.value}\n                    onBlur={field.handleBlur}\n                    onChangeText={(value) => {\n                      field.handleChange(value);\n                      if (error) {\n                        setError(null);\n                      }\n                    }}\n                    keyboardType=\"email-address\"\n                    autoCapitalize=\"none\"\n                  />\n                )}\n              </form.Field>\n\n              <form.Field name=\"password\">\n                {(field) => (\n                  <TextInput\n                    style={styles.input}\n                    placeholder=\"Password\"\n                    value={field.state.value}\n                    onBlur={field.handleBlur}\n                    onChangeText={(value) => {\n                      field.handleChange(value);\n                      if (error) {\n                        setError(null);\n                      }\n                    }}\n                    secureTextEntry\n                    onSubmitEditing={form.handleSubmit}\n                  />\n                )}\n              </form.Field>\n\n              <TouchableOpacity\n                onPress={form.handleSubmit}\n                disabled={isSubmitting}\n                style={styles.button}\n              >\n                {isSubmitting ? (\n                  <ActivityIndicator size=\"small\" color=\"#fff\" />\n                ) : (\n                  <Text style={styles.buttonText}>Sign In</Text>\n                )}\n              </TouchableOpacity>\n            </>\n          );\n        }}\n      </form.Subscribe>\n    </View>\n  );\n}\n\nconst styles = StyleSheet.create((theme) => ({\n  container: {\n    marginTop: 24,\n    padding: 16,\n    borderRadius: 8,\n    borderWidth: 1,\n    borderColor: theme.colors.border,\n  },\n  title: {\n    fontSize: 18,\n    fontWeight: \"600\",\n    color: theme.colors.typography,\n    marginBottom: 16,\n  },\n  errorContainer: {\n    marginBottom: 16,\n    padding: 12,\n    borderRadius: 6,\n  },\n  errorText: {\n    color: theme.colors.destructive,\n    fontSize: 14,\n  },\n  input: {\n    marginBottom: 12,\n    padding: 16,\n    borderRadius: 6,\n    color: theme.colors.typography,\n    borderWidth: 1,\n    borderColor: theme.colors.border,\n  },\n  button: {\n    backgroundColor: theme.colors.primary,\n    padding: 16,\n    borderRadius: 6,\n    flexDirection: \"row\",\n    justifyContent: \"center\",\n    alignItems: \"center\",\n  },\n  buttonText: {\n    fontWeight: \"500\",\n  },\n}));\n"
  },
  {
    "path": "packages/template-generator/templates/auth/better-auth/native/unistyles/components/sign-up.tsx.hbs",
    "content": "import { authClient } from \"@/lib/auth-client\";\n{{#if (eq api \"trpc\")}}\nimport { queryClient } from \"@/utils/trpc\";\n{{/if}}\n{{#if (eq api \"orpc\")}}\nimport { queryClient } from \"@/utils/orpc\";\n{{/if}}\nimport { useForm } from \"@tanstack/react-form\";\nimport { useState } from \"react\";\nimport {\n  ActivityIndicator,\n  Text,\n  TextInput,\n  TouchableOpacity,\n  View,\n} from \"react-native\";\nimport { StyleSheet } from \"react-native-unistyles\";\nimport z from \"zod\";\n\nconst signUpSchema = z.object({\n  name: z\n    .string()\n    .trim()\n    .min(1, \"Name is required\")\n    .min(2, \"Name must be at least 2 characters\"),\n  email: z\n    .string()\n    .trim()\n    .min(1, \"Email is required\")\n    .email(\"Enter a valid email address\"),\n  password: z\n    .string()\n    .min(1, \"Password is required\")\n    .min(8, \"Use at least 8 characters\"),\n});\n\nfunction getErrorMessage(error: unknown): string | null {\n  if (!error) return null;\n\n  if (typeof error === \"string\") {\n    return error;\n  }\n\n  if (Array.isArray(error)) {\n    for (const issue of error) {\n      const message = getErrorMessage(issue);\n      if (message) {\n        return message;\n      }\n    }\n    return null;\n  }\n\n  if (typeof error === \"object\" && error !== null) {\n    const maybeError = error as { message?: unknown };\n    if (typeof maybeError.message === \"string\") {\n      return maybeError.message;\n    }\n  }\n\n  return null;\n}\n\nexport function SignUp() {\n  const [error, setError] = useState<string | null>(null);\n\n  const form = useForm({\n    defaultValues: {\n      name: \"\",\n      email: \"\",\n      password: \"\",\n    },\n    validators: {\n      onSubmit: signUpSchema,\n    },\n    onSubmit: async ({ value, formApi }) => {\n      await authClient.signUp.email(\n        {\n          name: value.name.trim(),\n          email: value.email.trim(),\n          password: value.password,\n        },\n        {\n          onError(error) {\n            setError(error.error?.message || \"Failed to sign up\");\n          },\n          onSuccess() {\n            setError(null);\n            formApi.reset();\n            {{#if (eq api \"orpc\")}}\n            queryClient.refetchQueries();\n            {{/if}}\n            {{#if (eq api \"trpc\")}}\n            queryClient.refetchQueries();\n            {{/if}}\n          },\n        },\n      );\n    },\n  });\n\n  return (\n    <View style={styles.container}>\n      <Text style={styles.title}>Create Account</Text>\n\n      <form.Subscribe\n        selector={(state) => ({\n          isSubmitting: state.isSubmitting,\n          validationError: getErrorMessage(state.errorMap.onSubmit),\n        })}\n      >\n        {({ isSubmitting, validationError }) => {\n          const formError = error ?? validationError;\n\n          return (\n            <>\n              {formError ? (\n                <View style={styles.errorContainer}>\n                  <Text style={styles.errorText}>{formError}</Text>\n                </View>\n              ) : null}\n\n              <form.Field name=\"name\">\n                {(field) => (\n                  <TextInput\n                    style={styles.input}\n                    placeholder=\"Name\"\n                    value={field.state.value}\n                    onBlur={field.handleBlur}\n                    onChangeText={(value) => {\n                      field.handleChange(value);\n                      if (error) {\n                        setError(null);\n                      }\n                    }}\n                  />\n                )}\n              </form.Field>\n\n              <form.Field name=\"email\">\n                {(field) => (\n                  <TextInput\n                    style={styles.input}\n                    placeholder=\"Email\"\n                    value={field.state.value}\n                    onBlur={field.handleBlur}\n                    onChangeText={(value) => {\n                      field.handleChange(value);\n                      if (error) {\n                        setError(null);\n                      }\n                    }}\n                    keyboardType=\"email-address\"\n                    autoCapitalize=\"none\"\n                  />\n                )}\n              </form.Field>\n\n              <form.Field name=\"password\">\n                {(field) => (\n                  <TextInput\n                    style={styles.inputLast}\n                    placeholder=\"Password\"\n                    value={field.state.value}\n                    onBlur={field.handleBlur}\n                    onChangeText={(value) => {\n                      field.handleChange(value);\n                      if (error) {\n                        setError(null);\n                      }\n                    }}\n                    secureTextEntry\n                    onSubmitEditing={form.handleSubmit}\n                  />\n                )}\n              </form.Field>\n\n              <TouchableOpacity\n                onPress={form.handleSubmit}\n                disabled={isSubmitting}\n                style={styles.button}\n              >\n                {isSubmitting ? (\n                  <ActivityIndicator size=\"small\" color=\"#fff\" />\n                ) : (\n                  <Text style={styles.buttonText}>Sign Up</Text>\n                )}\n              </TouchableOpacity>\n            </>\n          );\n        }}\n      </form.Subscribe>\n    </View>\n  );\n}\n\nconst styles = StyleSheet.create((theme) => ({\n  container: {\n    marginTop: 24,\n    padding: 16,\n    borderRadius: 8,\n    borderWidth: 1,\n    borderColor: theme.colors.border,\n  },\n  title: {\n    fontSize: 18,\n    fontWeight: \"600\",\n    color: theme.colors.typography,\n    marginBottom: 16,\n  },\n  errorContainer: {\n    marginBottom: 16,\n    padding: 12,\n    borderRadius: 6,\n  },\n  errorText: {\n    color: theme.colors.destructive,\n    fontSize: 14,\n  },\n  input: {\n    marginBottom: 12,\n    padding: 16,\n    borderRadius: 6,\n    color: theme.colors.typography,\n    borderWidth: 1,\n    borderColor: theme.colors.border,\n  },\n  inputLast: {\n    marginBottom: 16,\n    padding: 16,\n    borderRadius: 6,\n    color: theme.colors.typography,\n    borderWidth: 1,\n    borderColor: theme.colors.border,\n  },\n  button: {\n    backgroundColor: theme.colors.primary,\n    padding: 16,\n    borderRadius: 6,\n    flexDirection: \"row\",\n    justifyContent: \"center\",\n    alignItems: \"center\",\n  },\n  buttonText: {\n    fontWeight: \"500\",\n  },\n}));\n"
  },
  {
    "path": "packages/template-generator/templates/auth/better-auth/native/uniwind/app/(drawer)/index.tsx.hbs",
    "content": "import { Text, View, Pressable } from \"react-native\";\nimport { Container } from \"@/components/container\";\nimport { authClient } from \"@/lib/auth-client\";\nimport { Ionicons } from \"@expo/vector-icons\";\nimport { Card, Chip, useThemeColor } from \"heroui-native\";\nimport { SignIn } from \"@/components/sign-in\";\nimport { SignUp } from \"@/components/sign-up\";\n{{#if (eq api \"orpc\")}}\nimport { useQuery } from \"@tanstack/react-query\";\nimport { queryClient, orpc } from \"@/utils/orpc\";\n{{/if}}\n{{#if (eq api \"trpc\")}}\nimport { useQuery } from \"@tanstack/react-query\";\nimport { queryClient, trpc } from \"@/utils/trpc\";\n{{/if}}\n\nexport default function Home() {\n{{#if (eq api \"orpc\")}}\nconst healthCheck = useQuery(orpc.healthCheck.queryOptions());\nconst privateData = useQuery(orpc.privateData.queryOptions());\nconst isConnected = healthCheck?.data === \"OK\";\nconst isLoading = healthCheck?.isLoading;\n{{/if}}\n{{#if (eq api \"trpc\")}}\nconst healthCheck = useQuery(trpc.healthCheck.queryOptions());\nconst privateData = useQuery(trpc.privateData.queryOptions());\nconst isConnected = healthCheck?.data === \"OK\";\nconst isLoading = healthCheck?.isLoading;\n{{/if}}\nconst { data: session } = authClient.useSession();\n\nconst mutedColor = useThemeColor(\"muted\");\nconst successColor = useThemeColor(\"success\");\nconst dangerColor = useThemeColor(\"danger\");\nconst foregroundColor = useThemeColor(\"foreground\");\n\nreturn (\n<Container className=\"p-6\">\n  <View className=\"py-4 mb-6\">\n    <Text className=\"text-4xl font-bold text-foreground mb-2\">\n      BETTER T STACK\n    </Text>\n  </View>\n\n  {session?.user ? (\n  <Card variant=\"secondary\" className=\"mb-6 p-4\">\n    <Text className=\"text-foreground text-base mb-2\">\n      Welcome, <Text className=\"font-medium\">{session.user.name}</Text>\n    </Text>\n    <Text className=\"text-muted text-sm mb-4\">\n      {session.user.email}\n    </Text>\n    <Pressable className=\"bg-danger py-3 px-4 rounded-lg self-start active:opacity-70\" onPress={()=> {\n      authClient.signOut();\n      {{#if (eq api \"orpc\")}}\n      queryClient.invalidateQueries();\n      {{/if}}\n      {{#if (eq api \"trpc\")}}\n      queryClient.invalidateQueries();\n      {{/if}}\n      }}\n      >\n      <Text className=\"text-foreground font-medium\">Sign Out</Text>\n    </Pressable>\n  </Card>\n  ) : null}\n\n  {{#unless (eq api \"none\")}}\n  <Card variant=\"secondary\" className=\"p-6\">\n    <View className=\"flex-row items-center justify-between mb-4\">\n      <Card.Title>System Status</Card.Title>\n      <Chip variant=\"secondary\" color={isConnected ? \"success\" : \"danger\" } size=\"sm\">\n        <Chip.Label>{isConnected ? \"LIVE\" : \"OFFLINE\"}</Chip.Label>\n      </Chip>\n    </View>\n\n    <Card className=\"p-4\">\n      <View className=\"flex-row items-center\">\n        <View className={`w-3 h-3 rounded-full mr-3 ${isConnected ? \"bg-success\" : \"bg-muted\" }`} />\n        <View className=\"flex-1\">\n          <Text className=\"text-foreground font-medium mb-1\">\n            {{#if (eq api \"orpc\")}}ORPC{{else}}TRPC{{/if}} Backend\n          </Text>\n          <Card.Description>\n            {isLoading\n            ? \"Checking connection...\"\n            : isConnected\n            ? \"Connected to API\"\n            : \"API Disconnected\"}\n          </Card.Description>\n        </View>\n        {isLoading && (\n        <Ionicons name=\"hourglass-outline\" size={20} color={mutedColor} />\n        )}\n        {!isLoading && isConnected && (\n        <Ionicons name=\"checkmark-circle\" size={20} color={successColor} />\n        )}\n        {!isLoading && !isConnected && (\n        <Ionicons name=\"close-circle\" size={20} color={dangerColor} />\n        )}\n      </View>\n    </Card>\n  </Card>\n\n  <Card variant=\"secondary\" className=\"mt-6 p-4\">\n    <Card.Title className=\"mb-3\">Private Data</Card.Title>\n    {privateData && (\n    <Card.Description>\n      {privateData.data?.message}\n    </Card.Description>\n    )}\n  </Card>\n  {{/unless}}\n\n  {!session?.user && (\n  <>\n    <SignIn />\n    <SignUp />\n  </>\n  )}\n</Container>\n);\n}"
  },
  {
    "path": "packages/template-generator/templates/auth/better-auth/native/uniwind/components/sign-in.tsx.hbs",
    "content": "import { authClient } from \"@/lib/auth-client\";\n{{#if (eq api \"trpc\")}}\nimport { queryClient } from \"@/utils/trpc\";\n{{/if}}\n{{#if (eq api \"orpc\")}}\nimport { queryClient } from \"@/utils/orpc\";\n{{/if}}\nimport { useForm } from \"@tanstack/react-form\";\nimport { useRef } from \"react\";\nimport { Text, TextInput, View } from \"react-native\";\nimport { Button, FieldError, Input, Label, Spinner, Surface, TextField, useToast } from \"heroui-native\";\nimport z from \"zod\";\n\nconst signInSchema = z.object({\n  email: z\n    .string()\n    .trim()\n    .min(1, \"Email is required\")\n    .email(\"Enter a valid email address\"),\n  password: z\n    .string()\n    .min(1, \"Password is required\")\n    .min(8, \"Use at least 8 characters\"),\n});\n\nfunction getErrorMessage(error: unknown): string | null {\n  if (!error) return null;\n\n  if (typeof error === \"string\") {\n    return error;\n  }\n\n  if (Array.isArray(error)) {\n    for (const issue of error) {\n      const message = getErrorMessage(issue);\n      if (message) {\n        return message;\n      }\n    }\n    return null;\n  }\n\n  if (typeof error === \"object\" && error !== null) {\n    const maybeError = error as { message?: unknown };\n    if (typeof maybeError.message === \"string\") {\n      return maybeError.message;\n    }\n  }\n\n  return null;\n}\n\nfunction SignIn() {\n  const passwordInputRef = useRef<TextInput>(null);\n  const { toast } = useToast();\n\n  const form = useForm({\n    defaultValues: {\n      email: \"\",\n      password: \"\",\n    },\n    validators: {\n      onSubmit: signInSchema,\n    },\n    onSubmit: async ({ value, formApi }) => {\n      await authClient.signIn.email(\n        {\n          email: value.email.trim(),\n          password: value.password,\n        },\n        {\n          onError(error) {\n            toast.show({\n              variant: \"danger\",\n              label: error.error?.message || \"Failed to sign in\",\n            });\n          },\n          onSuccess() {\n            formApi.reset();\n            toast.show({\n              variant: \"success\",\n              label: \"Signed in successfully\",\n            });\n            {{#if (eq api \"orpc\")}}\n            queryClient.refetchQueries();\n            {{/if}}\n            {{#if (eq api \"trpc\")}}\n            queryClient.refetchQueries();\n            {{/if}}\n          },\n        },\n      );\n    },\n  });\n\n  return (\n    <Surface variant=\"secondary\" className=\"p-4 rounded-lg\">\n      <Text className=\"text-foreground font-medium mb-4\">Sign In</Text>\n\n      <form.Subscribe\n        selector={(state) => ({\n          isSubmitting: state.isSubmitting,\n          validationError: getErrorMessage(state.errorMap.onSubmit),\n        })}\n      >\n        {({ isSubmitting, validationError }) => {\n          const formError = validationError;\n\n          return (\n            <>\n              <FieldError isInvalid={!!formError} className=\"mb-3\">\n                {formError}\n              </FieldError>\n\n              <View className=\"gap-3\">\n                <form.Field name=\"email\">\n                  {(field) => (\n                    <TextField>\n                      <Label>Email</Label>\n                      <Input\n                        value={field.state.value}\n                        onBlur={field.handleBlur}\n                        onChangeText={field.handleChange}\n                        placeholder=\"email@example.com\"\n                        keyboardType=\"email-address\"\n                        autoCapitalize=\"none\"\n                        autoComplete=\"email\"\n                        textContentType=\"emailAddress\"\n                        returnKeyType=\"next\"\n                        blurOnSubmit={false}\n                        onSubmitEditing={() => {\n                          passwordInputRef.current?.focus();\n                        }}\n                      />\n                    </TextField>\n                  )}\n                </form.Field>\n\n                <form.Field name=\"password\">\n                  {(field) => (\n                    <TextField>\n                      <Label>Password</Label>\n                      <Input\n                        ref={passwordInputRef}\n                        value={field.state.value}\n                        onBlur={field.handleBlur}\n                        onChangeText={field.handleChange}\n                        placeholder=\"••••••••\"\n                        secureTextEntry\n                        autoComplete=\"password\"\n                        textContentType=\"password\"\n                        returnKeyType=\"go\"\n                        onSubmitEditing={form.handleSubmit}\n                      />\n                    </TextField>\n                  )}\n                </form.Field>\n\n                <Button onPress={form.handleSubmit} isDisabled={isSubmitting} className=\"mt-1\">\n                  {isSubmitting ? <Spinner size=\"sm\" color=\"default\" /> : <Button.Label>Sign In</Button.Label>}\n                </Button>\n              </View>\n            </>\n          );\n        }}\n      </form.Subscribe>\n    </Surface>\n  );\n}\n\nexport { SignIn };\n"
  },
  {
    "path": "packages/template-generator/templates/auth/better-auth/native/uniwind/components/sign-up.tsx.hbs",
    "content": "import { authClient } from \"@/lib/auth-client\";\n{{#if (eq api \"trpc\")}}\nimport { queryClient } from \"@/utils/trpc\";\n{{/if}}\n{{#if (eq api \"orpc\")}}\nimport { queryClient } from \"@/utils/orpc\";\n{{/if}}\nimport { useForm } from \"@tanstack/react-form\";\nimport { useRef } from \"react\";\nimport { Text, TextInput, View } from \"react-native\";\nimport { Button, FieldError, Input, Label, Spinner, Surface, TextField, useToast } from \"heroui-native\";\nimport z from \"zod\";\n\nconst signUpSchema = z.object({\n  name: z\n    .string()\n    .trim()\n    .min(1, \"Name is required\")\n    .min(2, \"Name must be at least 2 characters\"),\n  email: z\n    .string()\n    .trim()\n    .min(1, \"Email is required\")\n    .email(\"Enter a valid email address\"),\n  password: z\n    .string()\n    .min(1, \"Password is required\")\n    .min(8, \"Use at least 8 characters\"),\n});\n\nfunction getErrorMessage(error: unknown): string | null {\n  if (!error) return null;\n\n  if (typeof error === \"string\") {\n    return error;\n  }\n\n  if (Array.isArray(error)) {\n    for (const issue of error) {\n      const message = getErrorMessage(issue);\n      if (message) {\n        return message;\n      }\n    }\n    return null;\n  }\n\n  if (typeof error === \"object\" && error !== null) {\n    const maybeError = error as { message?: unknown };\n    if (typeof maybeError.message === \"string\") {\n      return maybeError.message;\n    }\n  }\n\n  return null;\n}\n\nexport function SignUp() {\n  const emailInputRef = useRef<TextInput>(null);\n  const passwordInputRef = useRef<TextInput>(null);\n  const { toast } = useToast();\n\n  const form = useForm({\n    defaultValues: {\n      name: \"\",\n      email: \"\",\n      password: \"\",\n    },\n    validators: {\n      onSubmit: signUpSchema,\n    },\n    onSubmit: async ({ value, formApi }) => {\n      await authClient.signUp.email(\n        {\n          name: value.name.trim(),\n          email: value.email.trim(),\n          password: value.password,\n        },\n        {\n          onError(error) {\n            toast.show({\n              variant: \"danger\",\n              label: error.error?.message || \"Failed to sign up\",\n            });\n          },\n          onSuccess() {\n            formApi.reset();\n            toast.show({\n              variant: \"success\",\n              label: \"Account created successfully\",\n            });\n            {{#if (eq api \"orpc\")}}\n            queryClient.refetchQueries();\n            {{/if}}\n            {{#if (eq api \"trpc\")}}\n            queryClient.refetchQueries();\n            {{/if}}\n          },\n        },\n      );\n    },\n  });\n\n  return (\n    <Surface variant=\"secondary\" className=\"p-4 rounded-lg\">\n      <Text className=\"text-foreground font-medium mb-4\">Create Account</Text>\n\n      <form.Subscribe\n        selector={(state) => ({\n          isSubmitting: state.isSubmitting,\n          validationError: getErrorMessage(state.errorMap.onSubmit),\n        })}\n      >\n        {({ isSubmitting, validationError }) => {\n          const formError = validationError;\n\n          return (\n            <>\n              <FieldError isInvalid={!!formError} className=\"mb-3\">\n                {formError}\n              </FieldError>\n\n              <View className=\"gap-3\">\n                <form.Field name=\"name\">\n                  {(field) => (\n                    <TextField>\n                      <Label>Name</Label>\n                      <Input\n                        value={field.state.value}\n                        onBlur={field.handleBlur}\n                        onChangeText={field.handleChange}\n                        placeholder=\"John Doe\"\n                        autoComplete=\"name\"\n                        textContentType=\"name\"\n                        returnKeyType=\"next\"\n                        blurOnSubmit={false}\n                        onSubmitEditing={() => {\n                          emailInputRef.current?.focus();\n                        }}\n                      />\n                    </TextField>\n                  )}\n                </form.Field>\n\n                <form.Field name=\"email\">\n                  {(field) => (\n                    <TextField>\n                      <Label>Email</Label>\n                      <Input\n                        ref={emailInputRef}\n                        value={field.state.value}\n                        onBlur={field.handleBlur}\n                        onChangeText={field.handleChange}\n                        placeholder=\"email@example.com\"\n                        keyboardType=\"email-address\"\n                        autoCapitalize=\"none\"\n                        autoComplete=\"email\"\n                        textContentType=\"emailAddress\"\n                        returnKeyType=\"next\"\n                        blurOnSubmit={false}\n                        onSubmitEditing={() => {\n                          passwordInputRef.current?.focus();\n                        }}\n                      />\n                    </TextField>\n                  )}\n                </form.Field>\n\n                <form.Field name=\"password\">\n                  {(field) => (\n                    <TextField>\n                      <Label>Password</Label>\n                      <Input\n                        ref={passwordInputRef}\n                        value={field.state.value}\n                        onBlur={field.handleBlur}\n                        onChangeText={field.handleChange}\n                        placeholder=\"••••••••\"\n                        secureTextEntry\n                        autoComplete=\"new-password\"\n                        textContentType=\"newPassword\"\n                        returnKeyType=\"go\"\n                        onSubmitEditing={form.handleSubmit}\n                      />\n                    </TextField>\n                  )}\n                </form.Field>\n\n                <Button onPress={form.handleSubmit} isDisabled={isSubmitting} className=\"mt-1\">\n                  {isSubmitting ? (\n                    <Spinner size=\"sm\" color=\"default\" />\n                  ) : (\n                    <Button.Label>Create Account</Button.Label>\n                  )}\n                </Button>\n              </View>\n            </>\n          );\n        }}\n      </form.Subscribe>\n    </Surface>\n  );\n}\n"
  },
  {
    "path": "packages/template-generator/templates/auth/better-auth/server/base/_gitignore",
    "content": "# dependencies (bun install)\nnode_modules\n\n# output\nout\ndist\n*.tgz\n\n# code coverage\ncoverage\n*.lcov\n\n# logs\nlogs\n_.log\nreport.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json\n\n# dotenv environment variable files\n.env\n.env.development.local\n.env.test.local\n.env.production.local\n.env.local\n\n# caches\n.eslintcache\n.cache\n*.tsbuildinfo\n\n# IntelliJ based IDEs\n.idea\n\n# Finder (MacOS) folder config\n.DS_Store\n"
  },
  {
    "path": "packages/template-generator/templates/auth/better-auth/server/base/package.json.hbs",
    "content": "{\n  \"name\": \"@{{projectName}}/auth\",\n  \"exports\": {\n    \".\": {\n      \"default\": \"./src/index.ts\"\n    },\n    \"./*\": {\n      \"default\": \"./src/*.ts\"\n    }\n  },\n  \"type\": \"module\",\n  \"scripts\": {},\n  \"devDependencies\": {}\n}"
  },
  {
    "path": "packages/template-generator/templates/auth/better-auth/server/base/src/index.ts.hbs",
    "content": "{{#if (eq orm \"prisma\")}}\nimport { betterAuth } from \"better-auth\";\nimport { prismaAdapter } from \"better-auth/adapters/prisma\";\n{{#if (and (eq backend \"self\") (eq webDeploy \"cloudflare\") (includes frontend \"svelte\"))}}\nimport type {} from \"@{{projectName}}/env/server\";\n{{else}}\nimport { env } from \"@{{projectName}}/env/server\";\n{{/if}}\n{{#if (eq payments \"polar\")}}\nimport { polar, checkout, portal } from \"@polar-sh/better-auth\";\n{{#if (and (eq backend \"self\") (eq webDeploy \"cloudflare\") (includes frontend \"svelte\"))}}\nimport { createPolarClient } from \"./lib/payments\";\n{{else}}\nimport { polarClient } from \"./lib/payments\";\n{{/if}}\n{{/if}}\nimport { createPrismaClient } from \"@{{projectName}}/db\";\n\nexport function createAuth({{#if (and (eq backend \"self\") (eq webDeploy \"cloudflare\") (includes frontend \"svelte\"))}}env: Env{{/if}}) {\n\tconst prisma = createPrismaClient({{#if (and (eq backend \"self\") (eq webDeploy \"cloudflare\") (includes frontend \"svelte\"))}}env{{/if}});\n\n\treturn betterAuth({\n\t\tdatabase: prismaAdapter(prisma, {\n{{#if (eq database \"postgres\")}}provider: \"postgresql\",{{/if}}\n{{#if (eq database \"sqlite\")}}provider: \"sqlite\",{{/if}}\n{{#if (eq database \"mysql\")}}provider: \"mysql\",{{/if}}\n{{#if (eq database \"mongodb\")}}provider: \"mongodb\",{{/if}}\n\t\t}),\n\n\t\ttrustedOrigins: [\n\t\t\tenv.CORS_ORIGIN,\n{{#if (or (includes frontend \"native-bare\") (includes frontend \"native-uniwind\") (includes frontend \"native-unistyles\"))}}\n\t\t\t\"{{projectName}}://\",\n\t\t\t...(env.NODE_ENV === \"development\"\n\t\t\t\t? [\n\t\t\t\t\t\"exp://\",\n\t\t\t\t\t\"exp://**\",\n\t\t\t\t\t\"exp://192.168.*.*:*/**\",\n\t\t\t\t\t\"http://localhost:8081\",\n\t\t\t\t]\n\t\t\t\t: []),\n{{/if}}\n\t\t],\n\t\temailAndPassword: {\n\t\t\tenabled: true,\n\t\t},\n\t\tsecret: env.BETTER_AUTH_SECRET,\n\t\tbaseURL: env.BETTER_AUTH_URL,\n{{#if (ne backend \"self\")}}\n\t\tadvanced: {\n\t\t\tdefaultCookieAttributes: {\n\t\t\t\tsameSite: \"none\",\n\t\t\t\tsecure: true,\n\t\t\t\thttpOnly: true,\n\t\t\t},\n\t\t},\n{{/if}}\n\t\tplugins: [\n{{#if (eq payments \"polar\")}}\n\t\t\tpolar({\n\t\t\t\tclient: {{#if (and (eq backend \"self\") (eq webDeploy \"cloudflare\") (includes frontend \"svelte\"))}}createPolarClient(env){{else}}polarClient{{/if}},\n\t\t\t\tcreateCustomerOnSignUp: true,\n\t\t\t\tenableCustomerPortal: true,\n\t\t\t\tuse: [\n\t\t\t\t\tcheckout({\n\t\t\t\t\t\tproducts: [\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tproductId: \"your-product-id\",\n\t\t\t\t\t\t\t\tslug: \"pro\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t],\n\t\t\t\t\t\tsuccessUrl: env.POLAR_SUCCESS_URL,\n\t\t\t\t\t\tauthenticatedUsersOnly: true,\n\t\t\t\t\t}),\n\t\t\t\t\tportal(),\n\t\t\t\t],\n\t\t\t}),\n{{/if}}\n\t\t],\n\t});\n}\n\n{{#if (and (ne runtime \"workers\") (ne serverDeploy \"cloudflare\") (or (ne backend \"self\") (ne webDeploy \"cloudflare\")))}}\nexport const auth = createAuth();\n{{/if}}\n{{/if}}\n\n{{#if (eq orm \"drizzle\")}}\n{{#if (or (eq runtime \"bun\") (eq runtime \"node\") (eq runtime \"none\"))}}\nimport { betterAuth } from \"better-auth\";\nimport { drizzleAdapter } from \"better-auth/adapters/drizzle\";\n{{#if (and (eq backend \"self\") (eq webDeploy \"cloudflare\") (includes frontend \"svelte\"))}}\nimport type {} from \"@{{projectName}}/env/server\";\n{{else}}\nimport { env } from \"@{{projectName}}/env/server\";\n{{/if}}\n{{#if (eq payments \"polar\")}}\nimport { polar, checkout, portal } from \"@polar-sh/better-auth\";\n{{#if (and (eq backend \"self\") (eq webDeploy \"cloudflare\") (includes frontend \"svelte\"))}}\nimport { createPolarClient } from \"./lib/payments\";\n{{else}}\nimport { polarClient } from \"./lib/payments\";\n{{/if}}\n{{/if}}\nimport { createDb } from \"@{{projectName}}/db\";\nimport * as schema from \"@{{projectName}}/db/schema/auth\";\n\n\nexport function createAuth({{#if (and (eq backend \"self\") (eq webDeploy \"cloudflare\") (includes frontend \"svelte\"))}}env: Env{{/if}}) {\n\tconst db = createDb({{#if (and (eq backend \"self\") (eq webDeploy \"cloudflare\") (includes frontend \"svelte\"))}}env{{/if}});\n\n\treturn betterAuth({\n\t\tdatabase: drizzleAdapter(db, {\n{{#if (eq database \"postgres\")}}provider: \"pg\",{{/if}}\n{{#if (eq database \"sqlite\")}}provider: \"sqlite\",{{/if}}\n{{#if (eq database \"mysql\")}}provider: \"mysql\",{{/if}}\n\t\t\tschema: schema,\n\t\t}),\n\t\ttrustedOrigins: [\n\t\t\tenv.CORS_ORIGIN,\n{{#if (or (includes frontend \"native-bare\") (includes frontend \"native-uniwind\") (includes frontend \"native-unistyles\"))}}\n\t\t\t\"{{projectName}}://\",\n\t\t\t...(env.NODE_ENV === \"development\"\n\t\t\t\t? [\n\t\t\t\t\t\"exp://\",\n\t\t\t\t\t\"exp://**\",\n\t\t\t\t\t\"exp://192.168.*.*:*/**\",\n\t\t\t\t\t\"http://localhost:8081\",\n\t\t\t\t]\n\t\t\t\t: []),\n{{/if}}\n\t\t],\n\t\temailAndPassword: {\n\t\t\tenabled: true,\n\t\t},\n\t\tsecret: env.BETTER_AUTH_SECRET,\n\t\tbaseURL: env.BETTER_AUTH_URL,\n{{#if (ne backend \"self\")}}\n\t\tadvanced: {\n\t\t\tdefaultCookieAttributes: {\n\t\t\t\tsameSite: \"none\",\n\t\t\t\tsecure: true,\n\t\t\t\thttpOnly: true,\n\t\t\t},\n\t\t},\n{{/if}}\n\t\tplugins: [\n{{#if (eq payments \"polar\")}}\n\t\t\tpolar({\n\t\t\t\tclient: {{#if (and (eq backend \"self\") (eq webDeploy \"cloudflare\") (includes frontend \"svelte\"))}}createPolarClient(env){{else}}polarClient{{/if}},\n\t\t\t\tcreateCustomerOnSignUp: true,\n\t\t\t\tenableCustomerPortal: true,\n\t\t\t\tuse: [\n\t\t\t\t\tcheckout({\n\t\t\t\t\t\tproducts: [\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tproductId: \"your-product-id\",\n\t\t\t\t\t\t\t\tslug: \"pro\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t],\n\t\t\t\t\t\tsuccessUrl: env.POLAR_SUCCESS_URL,\n\t\t\t\t\t\tauthenticatedUsersOnly: true,\n\t\t\t\t\t}),\n\t\t\t\t\tportal(),\n\t\t\t\t],\n\t\t\t}),\n{{/if}}\n\t\t],\n\t});\n}\n\n{{#if (and (ne runtime \"workers\") (ne serverDeploy \"cloudflare\") (or (ne backend \"self\") (ne webDeploy \"cloudflare\")))}}\nexport const auth = createAuth();\n{{/if}}\n{{/if}}\n\n{{#if (eq runtime \"workers\")}}\nimport { betterAuth } from \"better-auth\";\nimport { drizzleAdapter } from \"better-auth/adapters/drizzle\";\nimport { env } from \"@{{projectName}}/env/server\";\n{{#if (eq payments \"polar\")}}\nimport { polar, checkout, portal } from \"@polar-sh/better-auth\";\nimport { polarClient } from \"./lib/payments\";\n{{/if}}\nimport { createDb } from \"@{{projectName}}/db\";\nimport * as schema from \"@{{projectName}}/db/schema/auth\";\n\n\nexport function createAuth() {\n\tconst db = createDb();\n\n\treturn betterAuth({\n\t\tdatabase: drizzleAdapter(db, {\n{{#if (eq database \"postgres\")}}provider: \"pg\",{{/if}}\n{{#if (eq database \"sqlite\")}}provider: \"sqlite\",{{/if}}\n{{#if (eq database \"mysql\")}}provider: \"mysql\",{{/if}}\n\t\t\tschema: schema,\n\t\t}),\n\t\ttrustedOrigins: [\n\t\t\tenv.CORS_ORIGIN,\n{{#if (or (includes frontend \"native-bare\") (includes frontend \"native-uniwind\") (includes frontend \"native-unistyles\"))}}\n\t\t\t\"{{projectName}}://\",\n\t\t\t...(env.NODE_ENV === \"development\"\n\t\t\t\t? [\n\t\t\t\t\t\"exp://\",\n\t\t\t\t\t\"exp://**\",\n\t\t\t\t\t\"exp://192.168.*.*:*/**\",\n\t\t\t\t\t\"http://localhost:8081\",\n\t\t\t\t]\n\t\t\t\t: []),\n{{/if}}\n\t\t],\n\t\temailAndPassword: {\n\t\t\tenabled: true,\n\t\t},\n\t\t// uncomment cookieCache setting when ready to deploy to Cloudflare using *.workers.dev domains\n\t\t// session: {\n\t\t//   cookieCache: {\n\t\t//     enabled: true,\n\t\t//     maxAge: 60,\n\t\t//   },\n\t\t// },\n\t\tsecret: env.BETTER_AUTH_SECRET,\n\t\tbaseURL: env.BETTER_AUTH_URL,\n\t\tadvanced: {\n\t\t\tdefaultCookieAttributes: {\n\t\t\t\tsameSite: \"none\",\n\t\t\t\tsecure: true,\n\t\t\t\thttpOnly: true,\n\t\t\t},\n\t\t\t// uncomment crossSubDomainCookies setting when ready to deploy and replace <your-workers-subdomain> with your actual workers subdomain\n\t\t\t// https://developers.cloudflare.com/workers/wrangler/configuration/#workersdev\n\t\t\t// crossSubDomainCookies: {\n\t\t\t//   enabled: true,\n\t\t\t//   domain: \"<your-workers-subdomain>\",\n\t\t\t// },\n\t\t},\n{{#if (eq payments \"polar\")}}\n\t\tplugins: [\n\t\t\tpolar({\n\t\t\t\tclient: polarClient,\n\t\t\t\tcreateCustomerOnSignUp: true,\n\t\t\t\tenableCustomerPortal: true,\n\t\t\t\tuse: [\n\t\t\t\t\tcheckout({\n\t\t\t\t\t\tproducts: [\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tproductId: \"your-product-id\",\n\t\t\t\t\t\t\t\tslug: \"pro\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t],\n\t\t\t\t\t\tsuccessUrl: env.POLAR_SUCCESS_URL,\n\t\t\t\t\t\tauthenticatedUsersOnly: true,\n\t\t\t\t\t}),\n\t\t\t\t\tportal(),\n\t\t\t\t],\n\t\t\t}),\n\t\t],\n{{/if}}\n\t});\n}\n{{/if}}\n{{/if}}\n\n{{#if (eq orm \"mongoose\")}}\nimport { betterAuth } from \"better-auth\";\nimport { mongodbAdapter } from \"better-auth/adapters/mongodb\";\n{{#if (and (eq backend \"self\") (eq webDeploy \"cloudflare\") (includes frontend \"svelte\"))}}\nimport type {} from \"@{{projectName}}/env/server\";\n{{else}}\nimport { env } from \"@{{projectName}}/env/server\";\n{{/if}}\n{{#if (eq payments \"polar\")}}\nimport { polar, checkout, portal } from \"@polar-sh/better-auth\";\n{{#if (and (eq backend \"self\") (eq webDeploy \"cloudflare\") (includes frontend \"svelte\"))}}\nimport { createPolarClient } from \"./lib/payments\";\n{{else}}\nimport { polarClient } from \"./lib/payments\";\n{{/if}}\n{{/if}}\nimport { client } from \"@{{projectName}}/db\";\n\nexport function createAuth({{#if (and (eq backend \"self\") (eq webDeploy \"cloudflare\") (includes frontend \"svelte\"))}}env: Env{{/if}}) {\n\treturn betterAuth({\n\t\tdatabase: mongodbAdapter(client),\n\t\ttrustedOrigins: [\n\t\t\tenv.CORS_ORIGIN,\n{{#if (or (includes frontend \"native-bare\") (includes frontend \"native-uniwind\") (includes frontend \"native-unistyles\"))}}\n\t\t\t\"{{projectName}}://\",\n\t\t\t...(env.NODE_ENV === \"development\"\n\t\t\t\t? [\n\t\t\t\t\t\"exp://\",\n\t\t\t\t\t\"exp://**\",\n\t\t\t\t\t\"exp://192.168.*.*:*/**\",\n\t\t\t\t\t\"http://localhost:8081\",\n\t\t\t\t]\n\t\t\t\t: []),\n{{/if}}\n\t\t],\n\t\temailAndPassword: {\n\t\t\tenabled: true,\n\t\t},\n\t\tsecret: env.BETTER_AUTH_SECRET,\n\t\tbaseURL: env.BETTER_AUTH_URL,\n{{#if (ne backend \"self\")}}\n\t\tadvanced: {\n\t\t\tdefaultCookieAttributes: {\n\t\t\t\tsameSite: \"none\",\n\t\t\t\tsecure: true,\n\t\t\t\thttpOnly: true,\n\t\t\t},\n\t\t},\n{{/if}}\n{{#if (eq payments \"polar\")}}\n\t\tplugins: [\n\t\t\tpolar({\n\t\t\t\tclient: {{#if (and (eq backend \"self\") (eq webDeploy \"cloudflare\") (includes frontend \"svelte\"))}}createPolarClient(env){{else}}polarClient{{/if}},\n\t\t\t\tcreateCustomerOnSignUp: true,\n\t\t\t\tenableCustomerPortal: true,\n\t\t\t\tuse: [\n\t\t\t\t\tcheckout({\n\t\t\t\t\t\tproducts: [\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tproductId: \"your-product-id\",\n\t\t\t\t\t\t\t\tslug: \"pro\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t],\n\t\t\t\t\t\tsuccessUrl: env.POLAR_SUCCESS_URL,\n\t\t\t\t\t\tauthenticatedUsersOnly: true,\n\t\t\t\t\t}),\n\t\t\t\t\tportal(),\n\t\t\t\t],\n\t\t\t}),\n\t\t],\n{{/if}}\n\t});\n}\n\n{{#if (and (ne runtime \"workers\") (ne serverDeploy \"cloudflare\") (or (ne backend \"self\") (ne webDeploy \"cloudflare\")))}}\nexport const auth = createAuth();\n{{/if}}\n{{/if}}\n\n{{#if (eq orm \"none\")}}\nimport { betterAuth } from \"better-auth\";\n{{#if (and (eq backend \"self\") (eq webDeploy \"cloudflare\") (includes frontend \"svelte\"))}}\nimport type {} from \"@{{projectName}}/env/server\";\n{{else}}\nimport { env } from \"@{{projectName}}/env/server\";\n{{/if}}\n{{#if (eq payments \"polar\")}}\nimport { polar, checkout, portal } from \"@polar-sh/better-auth\";\n{{#if (and (eq backend \"self\") (eq webDeploy \"cloudflare\") (includes frontend \"svelte\"))}}\nimport { createPolarClient } from \"./lib/payments\";\n{{else}}\nimport { polarClient } from \"./lib/payments\";\n{{/if}}\n{{/if}}\n\n\nexport function createAuth({{#if (and (eq backend \"self\") (eq webDeploy \"cloudflare\") (includes frontend \"svelte\"))}}env: Env{{/if}}) {\n\treturn betterAuth({\n\t\tdatabase: \"\", // Invalid configuration\n\t\ttrustedOrigins: [\n\t\t\tenv.CORS_ORIGIN,\n{{#if (or (includes frontend \"native-bare\") (includes frontend \"native-uniwind\") (includes frontend \"native-unistyles\"))}}\n\t\t\t\"{{projectName}}://\",\n\t\t\t...(env.NODE_ENV === \"development\"\n\t\t\t\t? [\n\t\t\t\t\t\"exp://\",\n\t\t\t\t\t\"exp://**\",\n\t\t\t\t\t\"exp://192.168.*.*:*/**\",\n\t\t\t\t\t\"http://localhost:8081\",\n\t\t\t\t]\n\t\t\t\t: []),\n{{/if}}\n\t\t],\n\t\temailAndPassword: {\n\t\t\tenabled: true,\n\t\t},\n\t\tsecret: env.BETTER_AUTH_SECRET,\n\t\tbaseURL: env.BETTER_AUTH_URL,\n{{#if (ne backend \"self\")}}\n\t\tadvanced: {\n\t\t\tdefaultCookieAttributes: {\n\t\t\t\tsameSite: \"none\",\n\t\t\t\tsecure: true,\n\t\t\t\thttpOnly: true,\n\t\t\t},\n\t\t},\n{{/if}}\n{{#if (eq payments \"polar\")}}\n\t\tplugins: [\n\t\t\tpolar({\n\t\t\t\tclient: {{#if (and (eq backend \"self\") (eq webDeploy \"cloudflare\") (includes frontend \"svelte\"))}}createPolarClient(env){{else}}polarClient{{/if}},\n\t\t\t\tcreateCustomerOnSignUp: true,\n\t\t\t\tenableCustomerPortal: true,\n\t\t\t\tuse: [\n\t\t\t\t\tcheckout({\n\t\t\t\t\t\tproducts: [\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tproductId: \"your-product-id\",\n\t\t\t\t\t\t\t\tslug: \"pro\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t],\n\t\t\t\t\t\tsuccessUrl: env.POLAR_SUCCESS_URL,\n\t\t\t\t\t\tauthenticatedUsersOnly: true,\n\t\t\t\t\t}),\n\t\t\t\t\tportal(),\n\t\t\t\t],\n\t\t\t}),\n\t\t],\n{{/if}}\n\t});\n}\n\n{{#if (and (ne runtime \"workers\") (ne serverDeploy \"cloudflare\") (or (ne backend \"self\") (ne webDeploy \"cloudflare\")))}}\nexport const auth = createAuth();\n{{/if}}\n{{/if}}\n"
  },
  {
    "path": "packages/template-generator/templates/auth/better-auth/server/base/tsconfig.json.hbs",
    "content": "{\n  \"extends\": \"@{{projectName}}/config/tsconfig.base.json\",\n  \"compilerOptions\": {\n    \"declaration\": true,\n    \"declarationMap\": true,\n    \"sourceMap\": true,\n    \"outDir\": \"dist\",\n    \"composite\": true\n  }\n}"
  },
  {
    "path": "packages/template-generator/templates/auth/better-auth/server/db/drizzle/mysql/src/schema/auth.ts.hbs",
    "content": "import { relations } from \"drizzle-orm\";\nimport {\n  mysqlTable,\n  varchar,\n  text,\n  timestamp,\n  boolean,\n  index,\n} from \"drizzle-orm/mysql-core\";\n\nexport const user = mysqlTable(\"user\", {\n  id: varchar(\"id\", { length: 36 }).primaryKey(),\n  name: varchar(\"name\", { length: 255 }).notNull(),\n  email: varchar(\"email\", { length: 255 }).notNull().unique(),\n  emailVerified: boolean(\"email_verified\").default(false).notNull(),\n  image: text(\"image\"),\n  createdAt: timestamp(\"created_at\", { fsp: 3 }).defaultNow().notNull(),\n  updatedAt: timestamp(\"updated_at\", { fsp: 3 })\n    .defaultNow()\n    .$onUpdate(() => /* @__PURE__ */ new Date())\n    .notNull(),\n});\n\nexport const session = mysqlTable(\n  \"session\",\n  {\n    id: varchar(\"id\", { length: 36 }).primaryKey(),\n    expiresAt: timestamp(\"expires_at\", { fsp: 3 }).notNull(),\n    token: varchar(\"token\", { length: 255 }).notNull().unique(),\n    createdAt: timestamp(\"created_at\", { fsp: 3 }).defaultNow().notNull(),\n    updatedAt: timestamp(\"updated_at\", { fsp: 3 })\n      .$onUpdate(() => /* @__PURE__ */ new Date())\n      .notNull(),\n    ipAddress: text(\"ip_address\"),\n    userAgent: text(\"user_agent\"),\n    userId: varchar(\"user_id\", { length: 36 })\n      .notNull()\n      .references(() => user.id, { onDelete: \"cascade\" }),\n  },\n  (table) => [index(\"session_userId_idx\").on(table.userId)],\n);\n\nexport const account = mysqlTable(\n  \"account\",\n  {\n    id: varchar(\"id\", { length: 36 }).primaryKey(),\n    accountId: text(\"account_id\").notNull(),\n    providerId: text(\"provider_id\").notNull(),\n    userId: varchar(\"user_id\", { length: 36 })\n      .notNull()\n      .references(() => user.id, { onDelete: \"cascade\" }),\n    accessToken: text(\"access_token\"),\n    refreshToken: text(\"refresh_token\"),\n    idToken: text(\"id_token\"),\n    accessTokenExpiresAt: timestamp(\"access_token_expires_at\", { fsp: 3 }),\n    refreshTokenExpiresAt: timestamp(\"refresh_token_expires_at\", { fsp: 3 }),\n    scope: text(\"scope\"),\n    password: text(\"password\"),\n    createdAt: timestamp(\"created_at\", { fsp: 3 }).defaultNow().notNull(),\n    updatedAt: timestamp(\"updated_at\", { fsp: 3 })\n      .$onUpdate(() => /* @__PURE__ */ new Date())\n      .notNull(),\n  },\n  (table) => [index(\"account_userId_idx\").on(table.userId)],\n);\n\nexport const verification = mysqlTable(\n  \"verification\",\n  {\n    id: varchar(\"id\", { length: 36 }).primaryKey(),\n    identifier: varchar(\"identifier\", { length: 255 }).notNull(),\n    value: text(\"value\").notNull(),\n    expiresAt: timestamp(\"expires_at\", { fsp: 3 }).notNull(),\n    createdAt: timestamp(\"created_at\", { fsp: 3 }).defaultNow().notNull(),\n    updatedAt: timestamp(\"updated_at\", { fsp: 3 })\n      .defaultNow()\n      .$onUpdate(() => /* @__PURE__ */ new Date())\n      .notNull(),\n  },\n  (table) => [index(\"verification_identifier_idx\").on(table.identifier)],\n);\n\nexport const userRelations = relations(user, ({ many }) => ({\n  sessions: many(session),\n  accounts: many(account),\n}));\n\nexport const sessionRelations = relations(session, ({ one }) => ({\n  user: one(user, {\n    fields: [session.userId],\n    references: [user.id],\n  }),\n}));\n\nexport const accountRelations = relations(account, ({ one }) => ({\n  user: one(user, {\n    fields: [account.userId],\n    references: [user.id],\n  }),\n}));\n"
  },
  {
    "path": "packages/template-generator/templates/auth/better-auth/server/db/drizzle/postgres/src/schema/auth.ts.hbs",
    "content": "import { relations } from \"drizzle-orm\";\nimport { pgTable, text, timestamp, boolean, index } from \"drizzle-orm/pg-core\";\n\nexport const user = pgTable(\"user\", {\n  id: text(\"id\").primaryKey(),\n  name: text(\"name\").notNull(),\n  email: text(\"email\").notNull().unique(),\n  emailVerified: boolean(\"email_verified\").default(false).notNull(),\n  image: text(\"image\"),\n  createdAt: timestamp(\"created_at\").defaultNow().notNull(),\n  updatedAt: timestamp(\"updated_at\")\n    .defaultNow()\n    .$onUpdate(() => /* @__PURE__ */ new Date())\n    .notNull(),\n});\n\nexport const session = pgTable(\n  \"session\",\n  {\n    id: text(\"id\").primaryKey(),\n    expiresAt: timestamp(\"expires_at\").notNull(),\n    token: text(\"token\").notNull().unique(),\n    createdAt: timestamp(\"created_at\").defaultNow().notNull(),\n    updatedAt: timestamp(\"updated_at\")\n      .$onUpdate(() => /* @__PURE__ */ new Date())\n      .notNull(),\n    ipAddress: text(\"ip_address\"),\n    userAgent: text(\"user_agent\"),\n    userId: text(\"user_id\")\n      .notNull()\n      .references(() => user.id, { onDelete: \"cascade\" }),\n  },\n  (table) => [index(\"session_userId_idx\").on(table.userId)],\n);\n\nexport const account = pgTable(\n  \"account\",\n  {\n    id: text(\"id\").primaryKey(),\n    accountId: text(\"account_id\").notNull(),\n    providerId: text(\"provider_id\").notNull(),\n    userId: text(\"user_id\")\n      .notNull()\n      .references(() => user.id, { onDelete: \"cascade\" }),\n    accessToken: text(\"access_token\"),\n    refreshToken: text(\"refresh_token\"),\n    idToken: text(\"id_token\"),\n    accessTokenExpiresAt: timestamp(\"access_token_expires_at\"),\n    refreshTokenExpiresAt: timestamp(\"refresh_token_expires_at\"),\n    scope: text(\"scope\"),\n    password: text(\"password\"),\n    createdAt: timestamp(\"created_at\").defaultNow().notNull(),\n    updatedAt: timestamp(\"updated_at\")\n      .$onUpdate(() => /* @__PURE__ */ new Date())\n      .notNull(),\n  },\n  (table) => [index(\"account_userId_idx\").on(table.userId)],\n);\n\nexport const verification = pgTable(\n  \"verification\",\n  {\n    id: text(\"id\").primaryKey(),\n    identifier: text(\"identifier\").notNull(),\n    value: text(\"value\").notNull(),\n    expiresAt: timestamp(\"expires_at\").notNull(),\n    createdAt: timestamp(\"created_at\").defaultNow().notNull(),\n    updatedAt: timestamp(\"updated_at\")\n      .defaultNow()\n      .$onUpdate(() => /* @__PURE__ */ new Date())\n      .notNull(),\n  },\n  (table) => [index(\"verification_identifier_idx\").on(table.identifier)],\n);\n\nexport const userRelations = relations(user, ({ many }) => ({\n  sessions: many(session),\n  accounts: many(account),\n}));\n\nexport const sessionRelations = relations(session, ({ one }) => ({\n  user: one(user, {\n    fields: [session.userId],\n    references: [user.id],\n  }),\n}));\n\nexport const accountRelations = relations(account, ({ one }) => ({\n  user: one(user, {\n    fields: [account.userId],\n    references: [user.id],\n  }),\n}));\n"
  },
  {
    "path": "packages/template-generator/templates/auth/better-auth/server/db/drizzle/sqlite/src/schema/auth.ts.hbs",
    "content": "import { relations, sql } from \"drizzle-orm\";\nimport { sqliteTable, text, integer, index } from \"drizzle-orm/sqlite-core\";\n\nexport const user = sqliteTable(\"user\", {\n  id: text(\"id\").primaryKey(),\n  name: text(\"name\").notNull(),\n  email: text(\"email\").notNull().unique(),\n  emailVerified: integer(\"email_verified\", { mode: \"boolean\" })\n    .default(false)\n    .notNull(),\n  image: text(\"image\"),\n  createdAt: integer(\"created_at\", { mode: \"timestamp_ms\" })\n    .default(sql`(cast(unixepoch('subsecond') * 1000 as integer))`)\n    .notNull(),\n  updatedAt: integer(\"updated_at\", { mode: \"timestamp_ms\" })\n    .default(sql`(cast(unixepoch('subsecond') * 1000 as integer))`)\n    .$onUpdate(() => /* @__PURE__ */ new Date())\n    .notNull(),\n});\n\nexport const session = sqliteTable(\n  \"session\",\n  {\n    id: text(\"id\").primaryKey(),\n    expiresAt: integer(\"expires_at\", { mode: \"timestamp_ms\" }).notNull(),\n    token: text(\"token\").notNull().unique(),\n    createdAt: integer(\"created_at\", { mode: \"timestamp_ms\" })\n      .default(sql`(cast(unixepoch('subsecond') * 1000 as integer))`)\n      .notNull(),\n    updatedAt: integer(\"updated_at\", { mode: \"timestamp_ms\" })\n      .$onUpdate(() => /* @__PURE__ */ new Date())\n      .notNull(),\n    ipAddress: text(\"ip_address\"),\n    userAgent: text(\"user_agent\"),\n    userId: text(\"user_id\")\n      .notNull()\n      .references(() => user.id, { onDelete: \"cascade\" }),\n  },\n  (table) => [index(\"session_userId_idx\").on(table.userId)],\n);\n\nexport const account = sqliteTable(\n  \"account\",\n  {\n    id: text(\"id\").primaryKey(),\n    accountId: text(\"account_id\").notNull(),\n    providerId: text(\"provider_id\").notNull(),\n    userId: text(\"user_id\")\n      .notNull()\n      .references(() => user.id, { onDelete: \"cascade\" }),\n    accessToken: text(\"access_token\"),\n    refreshToken: text(\"refresh_token\"),\n    idToken: text(\"id_token\"),\n    accessTokenExpiresAt: integer(\"access_token_expires_at\", {\n      mode: \"timestamp_ms\",\n    }),\n    refreshTokenExpiresAt: integer(\"refresh_token_expires_at\", {\n      mode: \"timestamp_ms\",\n    }),\n    scope: text(\"scope\"),\n    password: text(\"password\"),\n    createdAt: integer(\"created_at\", { mode: \"timestamp_ms\" })\n      .default(sql`(cast(unixepoch('subsecond') * 1000 as integer))`)\n      .notNull(),\n    updatedAt: integer(\"updated_at\", { mode: \"timestamp_ms\" })\n      .$onUpdate(() => /* @__PURE__ */ new Date())\n      .notNull(),\n  },\n  (table) => [index(\"account_userId_idx\").on(table.userId)],\n);\n\nexport const verification = sqliteTable(\n  \"verification\",\n  {\n    id: text(\"id\").primaryKey(),\n    identifier: text(\"identifier\").notNull(),\n    value: text(\"value\").notNull(),\n    expiresAt: integer(\"expires_at\", { mode: \"timestamp_ms\" }).notNull(),\n    createdAt: integer(\"created_at\", { mode: \"timestamp_ms\" })\n      .default(sql`(cast(unixepoch('subsecond') * 1000 as integer))`)\n      .notNull(),\n    updatedAt: integer(\"updated_at\", { mode: \"timestamp_ms\" })\n      .default(sql`(cast(unixepoch('subsecond') * 1000 as integer))`)\n      .$onUpdate(() => /* @__PURE__ */ new Date())\n      .notNull(),\n  },\n  (table) => [index(\"verification_identifier_idx\").on(table.identifier)],\n);\n\nexport const userRelations = relations(user, ({ many }) => ({\n  sessions: many(session),\n  accounts: many(account),\n}));\n\nexport const sessionRelations = relations(session, ({ one }) => ({\n  user: one(user, {\n    fields: [session.userId],\n    references: [user.id],\n  }),\n}));\n\nexport const accountRelations = relations(account, ({ one }) => ({\n  user: one(user, {\n    fields: [account.userId],\n    references: [user.id],\n  }),\n}));\n"
  },
  {
    "path": "packages/template-generator/templates/auth/better-auth/server/db/mongoose/mongodb/src/models/auth.model.ts.hbs",
    "content": "import mongoose from 'mongoose';\n\nconst { Schema, model } = mongoose;\n\nconst userSchema = new Schema(\n    {\n        _id: { type: String },\n        name: { type: String, required: true },\n        email: { type: String, required: true, unique: true },\n        emailVerified: { type: Boolean, required: true },\n        image: { type: String },\n        createdAt: { type: Date, required: true },\n        updatedAt: { type: Date, required: true },\n    },\n    { collection: 'user' }\n);\n\nconst sessionSchema = new Schema(\n    {\n        _id: { type: String },\n        expiresAt: { type: Date, required: true },\n        token: { type: String, required: true, unique: true },\n        createdAt: { type: Date, required: true },\n        updatedAt: { type: Date, required: true },\n        ipAddress: { type: String },\n        userAgent: { type: String },\n        userId: { type: String, ref: 'User', required: true },\n    },\n    { collection: 'session' }\n);\n\nconst accountSchema = new Schema(\n    {\n        _id: { type: String },\n        accountId: { type: String, required: true },\n        providerId: { type: String, required: true },\n        userId: { type: String, ref: 'User', required: true },\n        accessToken: { type: String },\n        refreshToken: { type: String },\n        idToken: { type: String },\n        accessTokenExpiresAt: { type: Date },\n        refreshTokenExpiresAt: { type: Date },\n        scope: { type: String },\n        password: { type: String },\n        createdAt: { type: Date, required: true },\n        updatedAt: { type: Date, required: true },\n    },\n    { collection: 'account' }\n);\n\nconst verificationSchema = new Schema(\n    {\n        _id: { type: String },\n        identifier: { type: String, required: true },\n        value: { type: String, required: true },\n        expiresAt: { type: Date, required: true },\n        createdAt: { type: Date },\n        updatedAt: { type: Date },\n    },\n    { collection: 'verification' }\n);\n\nconst User = model('User', userSchema);\nconst Session = model('Session', sessionSchema);\nconst Account = model('Account', accountSchema);\nconst Verification = model('Verification', verificationSchema);\n\nexport { User, Session, Account, Verification };\n"
  },
  {
    "path": "packages/template-generator/templates/auth/better-auth/server/db/prisma/mongodb/prisma/schema/auth.prisma.hbs",
    "content": "model User {\n  id            String    @id @map(\"_id\")\n  name          String\n  email         String\n  emailVerified Boolean   @default(false)\n  image         String?\n  createdAt     DateTime  @default(now())\n  updatedAt     DateTime  @updatedAt\n  sessions      Session[]\n  accounts      Account[]\n\n  @@unique([email])\n  @@map(\"user\")\n}\n\nmodel Session {\n  id        String   @id @map(\"_id\")\n  expiresAt DateTime\n  token     String\n  createdAt DateTime @default(now())\n  updatedAt DateTime @updatedAt\n  ipAddress String?\n  userAgent String?\n  userId    String\n  user      User     @relation(fields: [userId], references: [id], onDelete: Cascade)\n\n  @@unique([token])\n  @@index([userId])\n  @@map(\"session\")\n}\n\nmodel Account {\n  id                    String    @id @map(\"_id\")\n  accountId             String\n  providerId            String\n  userId                String\n  user                  User      @relation(fields: [userId], references: [id], onDelete: Cascade)\n  accessToken           String?\n  refreshToken          String?\n  idToken               String?\n  accessTokenExpiresAt  DateTime?\n  refreshTokenExpiresAt DateTime?\n  scope                 String?\n  password              String?\n  createdAt             DateTime  @default(now())\n  updatedAt             DateTime  @updatedAt\n\n  @@index([userId])\n  @@map(\"account\")\n}\n\nmodel Verification {\n  id         String   @id @map(\"_id\")\n  identifier String\n  value      String\n  expiresAt  DateTime\n  createdAt  DateTime @default(now())\n  updatedAt  DateTime @updatedAt\n\n  @@index([identifier])\n  @@map(\"verification\")\n}\n"
  },
  {
    "path": "packages/template-generator/templates/auth/better-auth/server/db/prisma/mysql/prisma/schema/auth.prisma.hbs",
    "content": "model User {\n  id            String    @id\n  name          String    @db.Text\n  email         String\n  emailVerified Boolean   @default(false)\n  image         String?   @db.Text\n  createdAt     DateTime  @default(now())\n  updatedAt     DateTime  @updatedAt\n  sessions      Session[]\n  accounts      Account[]\n\n  @@unique([email])\n  @@map(\"user\")\n}\n\nmodel Session {\n  id        String   @id\n  expiresAt DateTime\n  token     String\n  createdAt DateTime @default(now())\n  updatedAt DateTime @updatedAt\n  ipAddress String?  @db.Text\n  userAgent String?  @db.Text\n  userId    String\n  user      User     @relation(fields: [userId], references: [id], onDelete: Cascade)\n\n  @@unique([token])\n  @@index([userId(length: 191)])\n  @@map(\"session\")\n}\n\nmodel Account {\n  id                    String    @id\n  accountId             String    @db.Text\n  providerId            String    @db.Text\n  userId                String\n  user                  User      @relation(fields: [userId], references: [id], onDelete: Cascade)\n  accessToken           String?   @db.Text\n  refreshToken          String?   @db.Text\n  idToken               String?   @db.Text\n  accessTokenExpiresAt  DateTime?\n  refreshTokenExpiresAt DateTime?\n  scope                 String?   @db.Text\n  password              String?   @db.Text\n  createdAt             DateTime  @default(now())\n  updatedAt             DateTime  @updatedAt\n\n  @@index([userId(length: 191)])\n  @@map(\"account\")\n}\n\nmodel Verification {\n  id         String   @id\n  identifier String   @db.Text\n  value      String   @db.Text\n  expiresAt  DateTime\n  createdAt  DateTime @default(now())\n  updatedAt  DateTime @updatedAt\n\n  @@index([identifier(length: 191)])\n  @@map(\"verification\")\n}\n"
  },
  {
    "path": "packages/template-generator/templates/auth/better-auth/server/db/prisma/postgres/prisma/schema/auth.prisma.hbs",
    "content": "model User {\n  id            String    @id\n  name          String\n  email         String\n  emailVerified Boolean   @default(false)\n  image         String?\n  createdAt     DateTime  @default(now())\n  updatedAt     DateTime  @updatedAt\n  sessions      Session[]\n  accounts      Account[]\n\n  @@unique([email])\n  @@map(\"user\")\n}\n\nmodel Session {\n  id        String   @id\n  expiresAt DateTime\n  token     String\n  createdAt DateTime @default(now())\n  updatedAt DateTime @updatedAt\n  ipAddress String?\n  userAgent String?\n  userId    String\n  user      User     @relation(fields: [userId], references: [id], onDelete: Cascade)\n\n  @@unique([token])\n  @@index([userId])\n  @@map(\"session\")\n}\n\nmodel Account {\n  id                    String    @id\n  accountId             String\n  providerId            String\n  userId                String\n  user                  User      @relation(fields: [userId], references: [id], onDelete: Cascade)\n  accessToken           String?\n  refreshToken          String?\n  idToken               String?\n  accessTokenExpiresAt  DateTime?\n  refreshTokenExpiresAt DateTime?\n  scope                 String?\n  password              String?\n  createdAt             DateTime  @default(now())\n  updatedAt             DateTime  @updatedAt\n\n  @@index([userId])\n  @@map(\"account\")\n}\n\nmodel Verification {\n  id         String   @id\n  identifier String\n  value      String\n  expiresAt  DateTime\n  createdAt  DateTime @default(now())\n  updatedAt  DateTime @updatedAt\n\n  @@index([identifier])\n  @@map(\"verification\")\n}\n"
  },
  {
    "path": "packages/template-generator/templates/auth/better-auth/server/db/prisma/sqlite/prisma/schema/auth.prisma.hbs",
    "content": "model User {\n  id            String    @id\n  name          String\n  email         String\n  emailVerified Boolean   @default(false)\n  image         String?\n  createdAt     DateTime  @default(now())\n  updatedAt     DateTime  @updatedAt\n  sessions      Session[]\n  accounts      Account[]\n\n  @@unique([email])\n  @@map(\"user\")\n}\n\nmodel Session {\n  id        String   @id\n  expiresAt DateTime\n  token     String\n  createdAt DateTime @default(now())\n  updatedAt DateTime @updatedAt\n  ipAddress String?\n  userAgent String?\n  userId    String\n  user      User     @relation(fields: [userId], references: [id], onDelete: Cascade)\n\n  @@unique([token])\n  @@index([userId])\n  @@map(\"session\")\n}\n\nmodel Account {\n  id                    String    @id\n  accountId             String\n  providerId            String\n  userId                String\n  user                  User      @relation(fields: [userId], references: [id], onDelete: Cascade)\n  accessToken           String?\n  refreshToken          String?\n  idToken               String?\n  accessTokenExpiresAt  DateTime?\n  refreshTokenExpiresAt DateTime?\n  scope                 String?\n  password              String?\n  createdAt             DateTime  @default(now())\n  updatedAt             DateTime  @updatedAt\n\n  @@index([userId])\n  @@map(\"account\")\n}\n\nmodel Verification {\n  id         String   @id\n  identifier String\n  value      String\n  expiresAt  DateTime\n  createdAt  DateTime @default(now())\n  updatedAt  DateTime @updatedAt\n\n  @@index([identifier])\n  @@map(\"verification\")\n}\n"
  },
  {
    "path": "packages/template-generator/templates/auth/better-auth/web/astro/src/components/SignInForm.astro.hbs",
    "content": "---\nimport { authClient } from \"../lib/auth-client\";\n---\n\n<div class=\"mx-auto mt-10 w-full max-w-md p-6\">\n  <h1 class=\"mb-6 text-center font-bold text-3xl text-white\">Welcome Back</h1>\n\n  <form id=\"signin-form\" class=\"space-y-4\">\n    <div class=\"space-y-1\">\n      <label for=\"email\" class=\"block text-sm font-medium text-neutral-300\">Email</label>\n      <input\n        id=\"email\"\n        name=\"email\"\n        type=\"email\"\n        required\n        class=\"w-full rounded-lg border border-neutral-700 bg-neutral-800 px-4 py-2.5 text-white placeholder-neutral-500 focus:border-indigo-500 focus:outline-none focus:ring-1 focus:ring-indigo-500\"\n        placeholder=\"you@example.com\"\n      />\n      <p id=\"email-error\" class=\"text-sm text-red-500 hidden\"></p>\n    </div>\n\n    <div class=\"space-y-1\">\n      <label for=\"password\" class=\"block text-sm font-medium text-neutral-300\">Password</label>\n      <input\n        id=\"password\"\n        name=\"password\"\n        type=\"password\"\n        required\n        class=\"w-full rounded-lg border border-neutral-700 bg-neutral-800 px-4 py-2.5 text-white placeholder-neutral-500 focus:border-indigo-500 focus:outline-none focus:ring-1 focus:ring-indigo-500\"\n        placeholder=\"••••••••\"\n      />\n      <p id=\"password-error\" class=\"text-sm text-red-500 hidden\"></p>\n    </div>\n\n    <p id=\"form-error\" class=\"text-sm text-red-500 hidden\"></p>\n\n    <button\n      type=\"submit\"\n      class=\"w-full rounded-lg bg-indigo-600 px-4 py-2.5 text-sm font-medium text-white hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 focus:ring-offset-neutral-900 disabled:opacity-50 disabled:cursor-not-allowed transition-colors\"\n    >\n      Sign In\n    </button>\n  </form>\n\n  <div class=\"mt-4 text-center\">\n    <a href=\"/signup\" class=\"text-indigo-400 hover:text-indigo-300 text-sm\">\n      Need an account? Sign Up\n    </a>\n  </div>\n</div>\n\n<script>\n  import { authClient } from \"../lib/auth-client\";\n\n  const form = document.getElementById(\"signin-form\") as HTMLFormElement;\n  const emailInput = document.getElementById(\"email\") as HTMLInputElement;\n  const passwordInput = document.getElementById(\"password\") as HTMLInputElement;\n  const formError = document.getElementById(\"form-error\") as HTMLParagraphElement;\n  const submitButton = form.querySelector('button[type=\"submit\"]') as HTMLButtonElement;\n\n  form.addEventListener(\"submit\", async (e) => {\n    e.preventDefault();\n    \n    formError.classList.add(\"hidden\");\n    submitButton.disabled = true;\n    submitButton.textContent = \"Signing in...\";\n\n    try {\n      await authClient.signIn.email(\n        {\n          email: emailInput.value,\n          password: passwordInput.value,\n        },\n        {\n          onSuccess: () => {\n            window.location.href = \"/dashboard\";\n          },\n          onError: (ctx) => {\n            formError.textContent = ctx.error.message || \"Sign in failed. Please try again.\";\n            formError.classList.remove(\"hidden\");\n          },\n        }\n      );\n    } catch (error) {\n      formError.textContent = \"An unexpected error occurred.\";\n      formError.classList.remove(\"hidden\");\n    } finally {\n      submitButton.disabled = false;\n      submitButton.textContent = \"Sign In\";\n    }\n  });\n</script>\n"
  },
  {
    "path": "packages/template-generator/templates/auth/better-auth/web/astro/src/components/SignUpForm.astro.hbs",
    "content": "---\nimport { authClient } from \"../lib/auth-client\";\n---\n\n<div class=\"mx-auto mt-10 w-full max-w-md p-6\">\n  <h1 class=\"mb-6 text-center font-bold text-3xl text-white\">Create Account</h1>\n\n  <form id=\"signup-form\" class=\"space-y-4\">\n    <div class=\"space-y-1\">\n      <label for=\"name\" class=\"block text-sm font-medium text-neutral-300\">Name</label>\n      <input\n        id=\"name\"\n        name=\"name\"\n        type=\"text\"\n        required\n        class=\"w-full rounded-lg border border-neutral-700 bg-neutral-800 px-4 py-2.5 text-white placeholder-neutral-500 focus:border-indigo-500 focus:outline-none focus:ring-1 focus:ring-indigo-500\"\n        placeholder=\"John Doe\"\n      />\n    </div>\n\n    <div class=\"space-y-1\">\n      <label for=\"email\" class=\"block text-sm font-medium text-neutral-300\">Email</label>\n      <input\n        id=\"email\"\n        name=\"email\"\n        type=\"email\"\n        required\n        class=\"w-full rounded-lg border border-neutral-700 bg-neutral-800 px-4 py-2.5 text-white placeholder-neutral-500 focus:border-indigo-500 focus:outline-none focus:ring-1 focus:ring-indigo-500\"\n        placeholder=\"you@example.com\"\n      />\n    </div>\n\n    <div class=\"space-y-1\">\n      <label for=\"password\" class=\"block text-sm font-medium text-neutral-300\">Password</label>\n      <input\n        id=\"password\"\n        name=\"password\"\n        type=\"password\"\n        required\n        minlength=\"8\"\n        class=\"w-full rounded-lg border border-neutral-700 bg-neutral-800 px-4 py-2.5 text-white placeholder-neutral-500 focus:border-indigo-500 focus:outline-none focus:ring-1 focus:ring-indigo-500\"\n        placeholder=\"••••••••\"\n      />\n      <p class=\"text-xs text-neutral-500\">Must be at least 8 characters</p>\n    </div>\n\n    <p id=\"form-error\" class=\"text-sm text-red-500 hidden\"></p>\n\n    <button\n      type=\"submit\"\n      class=\"w-full rounded-lg bg-indigo-600 px-4 py-2.5 text-sm font-medium text-white hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 focus:ring-offset-neutral-900 disabled:opacity-50 disabled:cursor-not-allowed transition-colors\"\n    >\n      Sign Up\n    </button>\n  </form>\n\n  <div class=\"mt-4 text-center\">\n    <a href=\"/login\" class=\"text-indigo-400 hover:text-indigo-300 text-sm\">\n      Already have an account? Sign In\n    </a>\n  </div>\n</div>\n\n<script>\n  import { authClient } from \"../lib/auth-client\";\n\n  const form = document.getElementById(\"signup-form\") as HTMLFormElement;\n  const nameInput = document.getElementById(\"name\") as HTMLInputElement;\n  const emailInput = document.getElementById(\"email\") as HTMLInputElement;\n  const passwordInput = document.getElementById(\"password\") as HTMLInputElement;\n  const formError = document.getElementById(\"form-error\") as HTMLParagraphElement;\n  const submitButton = form.querySelector('button[type=\"submit\"]') as HTMLButtonElement;\n\n  form.addEventListener(\"submit\", async (e) => {\n    e.preventDefault();\n    \n    formError.classList.add(\"hidden\");\n    submitButton.disabled = true;\n    submitButton.textContent = \"Creating account...\";\n\n    try {\n      await authClient.signUp.email(\n        {\n          name: nameInput.value,\n          email: emailInput.value,\n          password: passwordInput.value,\n        },\n        {\n          onSuccess: () => {\n            window.location.href = \"/dashboard\";\n          },\n          onError: (ctx) => {\n            formError.textContent = ctx.error.message || \"Sign up failed. Please try again.\";\n            formError.classList.remove(\"hidden\");\n          },\n        }\n      );\n    } catch (error) {\n      formError.textContent = \"An unexpected error occurred.\";\n      formError.classList.remove(\"hidden\");\n    } finally {\n      submitButton.disabled = false;\n      submitButton.textContent = \"Sign Up\";\n    }\n  });\n</script>\n"
  },
  {
    "path": "packages/template-generator/templates/auth/better-auth/web/astro/src/lib/auth-client.ts.hbs",
    "content": "import { createAuthClient } from \"better-auth/client\";\n{{#if (eq payments \"polar\")}}\nimport { polarClient } from \"@polar-sh/better-auth\";\n{{/if}}\n{{#if (ne backend \"self\")}}\nimport { PUBLIC_SERVER_URL } from \"astro:env/client\";\n{{/if}}\n\nexport const authClient = createAuthClient({\n{{#if (ne backend \"self\")}}\n  baseURL: PUBLIC_SERVER_URL,\n{{/if}}\n{{#if (eq payments \"polar\")}}\n  plugins: [polarClient()],\n{{/if}}\n});\n"
  },
  {
    "path": "packages/template-generator/templates/auth/better-auth/web/astro/src/pages/dashboard.astro.hbs",
    "content": "---\nimport Layout from \"../layouts/Layout.astro\";\n---\n\n<Layout title=\"Dashboard - {{projectName}}\">\n  <div id=\"dashboard-content\" class=\"hidden\">\n    <main class=\"mx-auto max-w-4xl px-4 py-8\">\n      <div class=\"rounded-xl border border-neutral-800 bg-neutral-900/50 p-8\">\n        <h1 class=\"text-3xl font-bold text-white mb-6\">Dashboard</h1>\n        \n        <div class=\"space-y-4\">\n          <div class=\"rounded-lg bg-neutral-800/50 p-4\">\n            <p class=\"text-sm text-neutral-400\">Welcome back,</p>\n            <p id=\"user-name\" class=\"text-xl font-medium text-white\">Loading...</p>\n          </div>\n          \n          <div class=\"rounded-lg bg-neutral-800/50 p-4\">\n            <p class=\"text-sm text-neutral-400 mb-2\">Email</p>\n            <p id=\"user-email\" class=\"text-white\">Loading...</p>\n          </div>\n\n          {{#if (eq api \"orpc\")}}\n          <div class=\"rounded-lg bg-neutral-800/50 p-4\">\n            <p class=\"text-sm text-neutral-400 mb-2\">Server Message</p>\n            <p id=\"api-message\" class=\"text-white\">Loading...</p>\n          </div>\n          {{/if}}\n\n          {{#if (eq payments \"polar\")}}\n          <div class=\"rounded-lg bg-neutral-800/50 p-4\">\n            <p class=\"text-sm text-neutral-400 mb-2\">Subscription</p>\n            <div id=\"subscription-info\" class=\"space-y-2\">\n              <p class=\"text-white\">Loading...</p>\n            </div>\n          </div>\n          {{/if}}\n        </div>\n      </div>\n    </main>\n  </div>\n\n  <div id=\"loading\" class=\"flex h-[calc(100vh-4rem)] items-center justify-center\">\n    <p class=\"text-neutral-400\">Loading...</p>\n  </div>\n\n  <div id=\"redirect\" class=\"hidden flex h-[calc(100vh-4rem)] items-center justify-center\">\n    <p class=\"text-neutral-400\">Redirecting to login...</p>\n  </div>\n</Layout>\n\n<script>\n  import { authClient } from \"../lib/auth-client\";\n  {{#if (eq api \"orpc\")}}\n  import { orpc } from \"../lib/orpc\";\n  {{/if}}\n\n  const dashboardContent = document.getElementById(\"dashboard-content\")!;\n  const loading = document.getElementById(\"loading\")!;\n  const redirect = document.getElementById(\"redirect\")!;\n  const userName = document.getElementById(\"user-name\")!;\n  const userEmail = document.getElementById(\"user-email\")!;\n  {{#if (eq api \"orpc\")}}\n  const apiMessage = document.getElementById(\"api-message\")!;\n  {{/if}}\n\n  async function init() {\n    try {\n      const { data: session } = await authClient.getSession();\n      \n      if (!session?.user) {\n        loading.classList.add(\"hidden\");\n        redirect.classList.remove(\"hidden\");\n        window.location.href = \"/login\";\n        return;\n      }\n\n      userName.textContent = session.user.name || \"User\";\n      userEmail.textContent = session.user.email || \"\";\n\n      {{#if (eq api \"orpc\")}}\n      try {\n        const data = await orpc.privateData();\n        apiMessage.textContent = data.message || \"Connected to server\";\n      } catch (e) {\n        apiMessage.textContent = \"Failed to load server data\";\n      }\n      {{/if}}\n\n      {{#if (eq payments \"polar\")}}\n      try {\n        const { data: customerState } = await authClient.customer.state();\n        const subscriptionInfo = document.getElementById(\"subscription-info\")!;\n        if (customerState?.activeSubscriptions?.length > 0) {\n          subscriptionInfo.innerHTML = `\n            <p class=\"text-white\">Plan: <span class=\"text-green-400\">Pro</span></p>\n            <button\n              id=\"manage-subscription\"\n              class=\"mt-2 rounded px-3 py-1.5 text-sm bg-neutral-700 hover:bg-neutral-600 text-white transition-colors\"\n            >\n              Manage Subscription\n            </button>\n          `;\n          document.getElementById(\"manage-subscription\")?.addEventListener(\"click\", async () => {\n            await authClient.customer.portal();\n          });\n        } else {\n          subscriptionInfo.innerHTML = `\n            <p class=\"text-white\">Plan: <span class=\"text-neutral-400\">Free</span></p>\n            <button\n              id=\"upgrade-button\"\n              class=\"mt-2 rounded px-3 py-1.5 text-sm bg-indigo-600 hover:bg-indigo-700 text-white transition-colors\"\n            >\n              Upgrade to Pro\n            </button>\n          `;\n          document.getElementById(\"upgrade-button\")?.addEventListener(\"click\", async () => {\n            await authClient.checkout({ slug: \"pro\" });\n          });\n        }\n      } catch (e) {\n        console.error(\"Failed to load subscription info\", e);\n      }\n      {{/if}}\n\n      loading.classList.add(\"hidden\");\n      dashboardContent.classList.remove(\"hidden\");\n    } catch (error) {\n      loading.classList.add(\"hidden\");\n      redirect.classList.remove(\"hidden\");\n      window.location.href = \"/login\";\n    }\n  }\n\n  init();\n</script>\n"
  },
  {
    "path": "packages/template-generator/templates/auth/better-auth/web/astro/src/pages/login.astro.hbs",
    "content": "---\nimport SignInForm from \"../components/SignInForm.astro\";\nimport Layout from \"../layouts/Layout.astro\";\n---\n\n<Layout title=\"Sign In - {{projectName}}\">\n  <div class=\"flex min-h-[calc(100vh-4rem)] items-center justify-center\">\n    <SignInForm />\n  </div>\n</Layout>\n"
  },
  {
    "path": "packages/template-generator/templates/auth/better-auth/web/astro/src/pages/signup.astro.hbs",
    "content": "---\nimport SignUpForm from \"../components/SignUpForm.astro\";\nimport Layout from \"../layouts/Layout.astro\";\n---\n\n<Layout title=\"Sign Up - {{projectName}}\">\n  <div class=\"flex min-h-[calc(100vh-4rem)] items-center justify-center\">\n    <SignUpForm />\n  </div>\n</Layout>\n"
  },
  {
    "path": "packages/template-generator/templates/auth/better-auth/web/nuxt/app/components/SignInForm.vue.hbs",
    "content": "<script setup lang=\"ts\">\nimport * as z from 'zod'\nimport type { FormSubmitEvent, AuthFormField } from '@nuxt/ui'\n\nconst { $authClient } = useNuxtApp()\n\nconst emit = defineEmits(['switchToSignUp'])\n\nconst toast = useToast()\nconst loading = ref(false)\n\nconst fields: AuthFormField[] = [\n  {\n    name: 'email',\n    type: 'email',\n    label: 'Email',\n    placeholder: 'Enter your email',\n    required: true\n  },\n  {\n    name: 'password',\n    type: 'password',\n    label: 'Password',\n    placeholder: 'Enter your password',\n    required: true\n  }\n]\n\nconst schema = z.object({\n  email: z.email('Invalid email address'),\n  password: z.string().min(8, 'Password must be at least 8 characters'),\n})\n\ntype Schema = z.output<typeof schema>\n\nasync function onSubmit(event: FormSubmitEvent<Schema>) {\n  loading.value = true\n  try {\n    await $authClient.signIn.email(\n      {\n        email: event.data.email,\n        password: event.data.password,\n      },\n      {\n        onSuccess: () => {\n          toast.add({ title: 'Sign in successful' })\n          navigateTo('/dashboard', { replace: true })\n        },\n        onError: (error) => {\n          toast.add({ title: 'Sign in failed', description: error.error.message })\n        },\n      },\n    )\n  } catch (error: any) {\n    toast.add({ title: 'An unexpected error occurred', description: error.message || 'Please try again.' })\n  } finally {\n    loading.value = false\n  }\n}\n</script>\n\n<template>\n  <div class=\"flex flex-col items-center justify-center gap-4 p-4\">\n    <UPageCard class=\"w-full max-w-md\">\n      <UAuthForm\n        :schema=\"schema\"\n        :fields=\"fields\"\n        title=\"Welcome Back\"\n        icon=\"i-lucide-log-in\"\n        :submit=\"{ label: 'Sign In', loading }\"\n        @submit=\"onSubmit\"\n      >\n        <template #description>\n          Need an account?\n          <ULink class=\"text-primary font-medium\" @click=\"$emit('switchToSignUp')\">\n            Sign Up\n          </ULink>\n        </template>\n      </UAuthForm>\n    </UPageCard>\n  </div>\n</template>\n"
  },
  {
    "path": "packages/template-generator/templates/auth/better-auth/web/nuxt/app/components/SignUpForm.vue.hbs",
    "content": "<script setup lang=\"ts\">\nimport * as z from 'zod'\nimport type { FormSubmitEvent, AuthFormField } from '@nuxt/ui'\n\nconst { $authClient } = useNuxtApp()\n\nconst emit = defineEmits(['switchToSignIn'])\n\nconst toast = useToast()\nconst loading = ref(false)\n\nconst fields: AuthFormField[] = [\n  {\n    name: 'name',\n    type: 'text',\n    label: 'Name',\n    placeholder: 'Enter your name',\n    required: true\n  },\n  {\n    name: 'email',\n    type: 'email',\n    label: 'Email',\n    placeholder: 'Enter your email',\n    required: true\n  },\n  {\n    name: 'password',\n    type: 'password',\n    label: 'Password',\n    placeholder: 'Enter your password',\n    required: true\n  }\n]\n\nconst schema = z.object({\n  name: z.string().min(2, 'Name must be at least 2 characters'),\n  email: z.email('Invalid email address'),\n  password: z.string().min(8, 'Password must be at least 8 characters'),\n})\n\ntype Schema = z.output<typeof schema>\n\nasync function onSubmit(event: FormSubmitEvent<Schema>) {\n  loading.value = true\n  try {\n    await $authClient.signUp.email(\n      {\n        name: event.data.name,\n        email: event.data.email,\n        password: event.data.password,\n      },\n      {\n        onSuccess: () => {\n          toast.add({ title: 'Sign up successful' })\n          navigateTo('/dashboard', { replace: true })\n        },\n        onError: (error) => {\n          toast.add({ title: 'Sign up failed', description: error.error.message })\n        },\n      },\n    )\n  } catch (error: any) {\n    toast.add({ title: 'An unexpected error occurred', description: error.message || 'Please try again.' })\n  } finally {\n    loading.value = false\n  }\n}\n</script>\n\n<template>\n  <div class=\"flex flex-col items-center justify-center gap-4 p-4\">\n    <UPageCard class=\"w-full max-w-md\">\n      <UAuthForm\n        :schema=\"schema\"\n        :fields=\"fields\"\n        title=\"Create Account\"\n        icon=\"i-lucide-user-plus\"\n        :submit=\"{ label: 'Sign Up', loading }\"\n        @submit=\"onSubmit\"\n      >\n        <template #description>\n          Already have an account?\n          <ULink class=\"text-primary font-medium\" @click=\"$emit('switchToSignIn')\">\n            Sign In\n          </ULink>\n        </template>\n      </UAuthForm>\n    </UPageCard>\n  </div>\n</template>\n"
  },
  {
    "path": "packages/template-generator/templates/auth/better-auth/web/nuxt/app/components/UserMenu.vue.hbs",
    "content": "<script setup lang=\"ts\">\n\nconst {$authClient} = useNuxtApp()\nconst session = $authClient.useSession()\nconst toast = useToast()\n\nconst handleSignOut = async () => {\n  try {\n    await $authClient.signOut({\n      fetchOptions: {\n        onSuccess: async () => {\n          toast.add({ title: 'Signed out successfully' })\n          await navigateTo('/', { replace: true, external: true })\n        },\n        onError: (error) => {\n           toast.add({ title: 'Sign out failed', description: error?.error?.message || 'Unknown error'})\n        }\n      },\n    })\n  } catch (error: any) {\n     toast.add({ title: 'An unexpected error occurred during sign out', description: error.message || 'Please try again.'})\n  }\n}\n</script>\n\n<template>\n  <div>\n    <USkeleton v-if=\"session.isPending\" class=\"h-9 w-24\" />\n\n    <UButton v-else-if=\"!session.data\" variant=\"outline\" to=\"/login\">\n      Sign In\n    </UButton>\n\n    <UButton\n      v-else\n      variant=\"solid\"\n      icon=\"i-lucide-log-out\"\n      label=\"Sign out\"\n      @click=\"handleSignOut()\"\n    />\n  </div>\n</template>\n"
  },
  {
    "path": "packages/template-generator/templates/auth/better-auth/web/nuxt/app/middleware/auth.ts.hbs",
    "content": "export default defineNuxtRouteMiddleware(async (to, from) => {\n  if (import.meta.server) return;\n\n  const { $authClient } = useNuxtApp();\n  const session = $authClient.useSession();\n\n  if (session.value.isPending) {\n    return;\n  }\n\n  if (!session.value.data) {\n    return navigateTo(\"/login\");\n  }\n});\n"
  },
  {
    "path": "packages/template-generator/templates/auth/better-auth/web/nuxt/app/pages/dashboard.vue.hbs",
    "content": "<script setup lang=\"ts\">\n{{#if (eq api \"orpc\")}}\nimport { useQuery } from '@tanstack/vue-query'\n{{/if}}\n\nconst { $authClient, $orpc } = useNuxtApp()\n\ndefinePageMeta({\n  middleware: ['auth']\n})\n\nconst session = $authClient.useSession()\n\n{{#if (eq payments \"polar\")}}\nconst customerState = ref<any>(null)\n{{/if}}\n\n{{#if (eq api \"orpc\")}}\nconst privateData = useQuery({\n  ...$orpc.privateData.queryOptions(),\n  enabled: computed(() => !!session.value?.data?.user)\n})\n{{/if}}\n\n{{#if (eq payments \"polar\")}}\nonMounted(async () => {\n  if (session.value?.data) {\n    const { data } = await $authClient.customer.state()\n    customerState.value = data\n  }\n})\n\nconst hasProSubscription = computed(() => \n  customerState.value?.activeSubscriptions?.length! > 0\n)\n{{/if}}\n</script>\n\n<template>\n  <UContainer class=\"py-8\">\n    <UPageHeader\n      title=\"Dashboard\"\n      :description=\"session?.data?.user ? `Welcome back, ${session.data.user.name}!` : 'Loading...'\"\n    />\n\n    <div class=\"mt-6 space-y-4\">\n      {{#if (eq api \"orpc\")}}\n      <UCard>\n        <template #header>\n          <div class=\"font-medium\">Private Data</div>\n        </template>\n\n        <USkeleton v-if=\"privateData.status.value === 'pending'\" class=\"h-6 w-48\" />\n\n        <UAlert\n          v-else-if=\"privateData.status.value === 'error'\"\n          color=\"error\"\n          icon=\"i-lucide-alert-circle\"\n          title=\"Error loading data\"\n          :description=\"privateData.error.value?.message || 'Failed to load private data'\"\n        />\n\n        <div v-else-if=\"privateData.data.value\" class=\"flex items-center gap-2\">\n          <UIcon name=\"i-lucide-check-circle\" class=\"text-success\" />\n          <span>\\{{ privateData.data.value.message }}</span>\n        </div>\n      </UCard>\n      {{/if}}\n\n      {{#if (eq payments \"polar\")}}\n      <UCard>\n        <template #header>\n          <div class=\"font-medium\">Subscription</div>\n        </template>\n\n        <div class=\"flex items-center justify-between\">\n          <div class=\"flex items-center gap-2\">\n            <UIcon :name=\"hasProSubscription ? 'i-lucide-crown' : 'i-lucide-user'\" :class=\"hasProSubscription ? 'text-warning' : 'text-muted'\" />\n            <span>Plan: \\{{ hasProSubscription ? \"Pro\" : \"Free\" }}</span>\n          </div>\n          <UButton \n            v-if=\"hasProSubscription\"\n            variant=\"outline\"\n            @click=\"() => { $authClient.customer.portal() }\"\n          >\n            Manage Subscription\n          </UButton>\n          <UButton \n            v-else\n            @click=\"() => { $authClient.checkout({ slug: 'pro' }) }\"\n          >\n            Upgrade to Pro\n          </UButton>\n        </div>\n      </UCard>\n      {{/if}}\n    </div>\n  </UContainer>\n</template>\n"
  },
  {
    "path": "packages/template-generator/templates/auth/better-auth/web/nuxt/app/pages/login.vue.hbs",
    "content": "<script setup lang=\"ts\">\nconst { $authClient } = useNuxtApp();\nimport SignInForm from \"~/components/SignInForm.vue\";\nimport SignUpForm from \"~/components/SignUpForm.vue\";\n\nconst session = $authClient.useSession();\nconst showSignIn = ref(true);\n\nwatchEffect(() => {\n  if (!session?.value.isPending && session?.value.data) {\n    navigateTo(\"/dashboard\", { replace: true });\n  }\n});\n</script>\n\n<template>\n  <UContainer class=\"py-8\">\n    <div v-if=\"session.isPending\" class=\"flex flex-col items-center justify-center gap-4 py-12\">\n      <UIcon name=\"i-lucide-loader-2\" class=\"animate-spin text-4xl text-primary\" />\n      <span class=\"text-muted\">Loading...</span>\n    </div>\n    <div v-else-if=\"!session.data\">\n      <SignInForm v-if=\"showSignIn\" @switch-to-sign-up=\"showSignIn = false\" />\n      <SignUpForm v-else @switch-to-sign-in=\"showSignIn = true\" />\n    </div>\n  </UContainer>\n</template>\n"
  },
  {
    "path": "packages/template-generator/templates/auth/better-auth/web/nuxt/app/plugins/auth-client.ts.hbs",
    "content": "import { createAuthClient } from \"better-auth/vue\";\n{{#if (eq payments \"polar\")}}\nimport { polarClient } from \"@polar-sh/better-auth\";\n{{/if}}\n\nexport default defineNuxtPlugin(() => {\n  {{#if (ne backend \"self\")}}\n  const config = useRuntimeConfig();\n  {{/if}}\n\n  const authClient = createAuthClient({\n    {{#if (ne backend \"self\")}}\n    baseURL: config.public.serverUrl,\n    {{/if}}\n    {{#if (eq payments \"polar\")}}\n    plugins: [polarClient()],\n    {{/if}}\n  });\n\n  return {\n    provide: {\n      authClient: authClient,\n    },\n  };\n});\n"
  },
  {
    "path": "packages/template-generator/templates/auth/better-auth/web/react/base/src/lib/auth-client.ts.hbs",
    "content": "import { createAuthClient } from \"better-auth/react\";\n{{#if (eq payments \"polar\")}}\nimport { polarClient } from \"@polar-sh/better-auth\";\n{{/if}}\n{{#unless (eq backend \"self\")}}\nimport { env } from \"@{{projectName}}/env/web\";\n{{/unless}}\n\nexport const authClient = createAuthClient({\n{{#unless (eq backend \"self\")}}\n\tbaseURL: env.{{#if (includes frontend \"next\")}}NEXT_PUBLIC_SERVER_URL{{else}}VITE_SERVER_URL{{/if}},\n{{/unless}}\n{{#if (eq payments \"polar\")}}\n\tplugins: [polarClient()]\n{{/if}}\n});\n"
  },
  {
    "path": "packages/template-generator/templates/auth/better-auth/web/react/next/src/app/dashboard/dashboard.tsx.hbs",
    "content": "\"use client\";\n{{#if (eq payments \"polar\")}}\nimport { Button } from \"@{{projectName}}/ui/components/button\";\n{{/if}}\nimport { authClient } from \"@/lib/auth-client\";\n{{#if (eq api \"orpc\")}}\nimport { useQuery } from \"@tanstack/react-query\";\nimport { orpc } from \"@/utils/orpc\";\n{{/if}}\n{{#if (eq api \"trpc\")}}\nimport { useQuery } from \"@tanstack/react-query\";\nimport { trpc } from \"@/utils/trpc\";\n{{/if}}\n\nexport default function Dashboard({\n\t{{#if (eq payments \"polar\")}}\n\tcustomerState,\n\t{{/if}}\n\tsession\n}: {\n\t{{#if (eq payments \"polar\")}}\n\tcustomerState: ReturnType<typeof authClient.customer.state>;\n\t{{/if}}\n\tsession: typeof authClient.$Infer.Session;\n}) {\n\t{{#if (eq api \"orpc\")}}\n\tconst privateData = useQuery(orpc.privateData.queryOptions());\n\t{{/if}}\n\t{{#if (eq api \"trpc\")}}\n\tconst privateData = useQuery(trpc.privateData.queryOptions());\n\t{{/if}}\n\n\t{{#if (eq payments \"polar\")}}\n\tconst hasProSubscription = customerState?.activeSubscriptions?.length! > 0;\n\tconsole.log(\"Active subscriptions:\", customerState?.activeSubscriptions);\n\t{{/if}}\n\n\treturn (\n\t\t<>\n\t\t\t{{#if (eq api \"orpc\")}}\n\t\t\t<p>API: {privateData.data?.message}</p>\n\t\t\t{{/if}}\n\t\t\t{{#if (eq api \"trpc\")}}\n\t\t\t<p>API: {privateData.data?.message}</p>\n\t\t\t{{/if}}\n\t\t\t{{#if (eq payments \"polar\")}}\n\t\t\t<p>Plan: {hasProSubscription ? \"Pro\" : \"Free\"}</p>\n\t\t\t{hasProSubscription ? (\n\t\t\t\t<Button onClick={async () => await authClient.customer.portal()}>\n\t\t\t\t\tManage Subscription\n\t\t\t\t</Button>\n\t\t\t) : (\n\t\t\t\t<Button onClick={async () => await authClient.checkout({ slug: \"pro\" })}>\n\t\t\t\t\tUpgrade to Pro\n\t\t\t\t</Button>\n\t\t\t)}\n\t\t\t{{/if}}\n\t\t</>\n\t);\n}\n"
  },
  {
    "path": "packages/template-generator/templates/auth/better-auth/web/react/next/src/app/dashboard/page.tsx.hbs",
    "content": "import { redirect } from \"next/navigation\";\nimport Dashboard from \"./dashboard\";\nimport { headers } from \"next/headers\";\n{{#if (eq backend \"self\")}}\n{{#if (or (eq runtime \"workers\") (eq serverDeploy \"cloudflare\") (and (eq backend \"self\") (eq webDeploy \"cloudflare\")))}}\nimport { createAuth } from \"@{{projectName}}/auth\";\n{{else}}\nimport { auth } from \"@{{projectName}}/auth\";\n{{/if}}\n{{/if}}\n{{#if (or (ne backend \"self\") (eq payments \"polar\"))}}\nimport { authClient } from \"@/lib/auth-client\";\n{{/if}}\n\nexport default async function DashboardPage() {\n\t{{#if (eq backend \"self\")}}\n\tconst session = await {{#if (or (eq runtime \"workers\") (eq serverDeploy \"cloudflare\") (and (eq backend \"self\") (eq webDeploy \"cloudflare\")))}}createAuth(){{else}}auth{{/if}}.api.getSession({\n\t\theaders: await headers(),\n\t});\n\t{{else}}\n\tconst session = await authClient.getSession({\n\t\tfetchOptions: {\n\t\t\theaders: await headers(),\n\t\t\tthrow: true\n\t\t}\n\t});\n\t{{/if}}\n\n\tif (!session?.user) {\n\t\tredirect(\"/login\");\n\t}\n\n\t{{#if (eq payments \"polar\")}}\n\tconst { data: customerState } = await authClient.customer.state({\n\t\tfetchOptions: {\n\t\t\theaders: await headers(),\n\t\t},\n\t});\n\t{{/if}}\n\n\treturn (\n\t\t<div>\n\t\t\t<h1>Dashboard</h1>\n\t\t\t<p>Welcome {session.user.name}</p>\n\t\t\t<Dashboard session={session} {{#if (eq payments \"polar\")}}customerState={customerState}{{/if}} />\n\t\t</div>\n\t);\n}\n"
  },
  {
    "path": "packages/template-generator/templates/auth/better-auth/web/react/next/src/app/login/page.tsx.hbs",
    "content": "\"use client\"\n\nimport SignInForm from \"@/components/sign-in-form\";\nimport SignUpForm from \"@/components/sign-up-form\";\nimport { useState } from \"react\";\n\n\nexport default function LoginPage() {\n  const [showSignIn, setShowSignIn] = useState(false);\n\n  return showSignIn ? (\n    <SignInForm onSwitchToSignUp={() => setShowSignIn(false)} />\n  ) : (\n    <SignUpForm onSwitchToSignIn={() => setShowSignIn(true)} />\n  );\n}\n"
  },
  {
    "path": "packages/template-generator/templates/auth/better-auth/web/react/next/src/components/sign-in-form.tsx.hbs",
    "content": "import { authClient } from \"@/lib/auth-client\";\nimport { useForm } from \"@tanstack/react-form\";\nimport { toast } from \"sonner\";\nimport z from \"zod\";\nimport Loader from \"./loader\";\nimport { Button } from \"@{{projectName}}/ui/components/button\";\nimport { Input } from \"@{{projectName}}/ui/components/input\";\nimport { Label } from \"@{{projectName}}/ui/components/label\";\nimport { useRouter } from \"next/navigation\";\n\nexport default function SignInForm({\n  onSwitchToSignUp,\n}: {\n  onSwitchToSignUp: () => void;\n}) {\n  const router = useRouter()\n  const { isPending } = authClient.useSession();\n\n  const form = useForm({\n    defaultValues: {\n      email: \"\",\n      password: \"\",\n    },\n    onSubmit: async ({ value }) => {\n      await authClient.signIn.email(\n        {\n          email: value.email,\n          password: value.password,\n        },\n        {\n          onSuccess: () => {\n            router.push(\"/dashboard\")\n            toast.success(\"Sign in successful\");\n          },\n          onError: (error) => {\n            toast.error(error.error.message || error.error.statusText);\n          },\n        },\n      );\n    },\n    validators: {\n      onSubmit: z.object({\n        email: z.email(\"Invalid email address\"),\n        password: z.string().min(8, \"Password must be at least 8 characters\"),\n      }),\n    },\n  });\n\n  if (isPending) {\n    return <Loader />;\n  }\n\n  return (\n    <div className=\"mx-auto w-full mt-10 max-w-md p-6\">\n      <h1 className=\"mb-6 text-center text-3xl font-bold\">Welcome Back</h1>\n\n      <form\n        onSubmit={(e) => {\n          e.preventDefault();\n          e.stopPropagation();\n          form.handleSubmit();\n        }}\n        className=\"space-y-4\"\n      >\n        <div>\n          <form.Field name=\"email\">\n            {(field) => (\n              <div className=\"space-y-2\">\n                <Label htmlFor={field.name}>Email</Label>\n                <Input\n                  id={field.name}\n                  name={field.name}\n                  type=\"email\"\n                  value={field.state.value}\n                  onBlur={field.handleBlur}\n                  onChange={(e) => field.handleChange(e.target.value)}\n                />\n                {field.state.meta.errors.map((error) => (\n                  <p key={error?.message} className=\"text-red-500\">\n                    {error?.message}\n                  </p>\n                ))}\n              </div>\n            )}\n          </form.Field>\n        </div>\n\n        <div>\n          <form.Field name=\"password\">\n            {(field) => (\n              <div className=\"space-y-2\">\n                <Label htmlFor={field.name}>Password</Label>\n                <Input\n                  id={field.name}\n                  name={field.name}\n                  type=\"password\"\n                  value={field.state.value}\n                  onBlur={field.handleBlur}\n                  onChange={(e) => field.handleChange(e.target.value)}\n                />\n                {field.state.meta.errors.map((error) => (\n                  <p key={error?.message} className=\"text-red-500\">\n                    {error?.message}\n                  </p>\n                ))}\n              </div>\n            )}\n          </form.Field>\n        </div>\n\n        <form.Subscribe selector={(state) => ({ canSubmit: state.canSubmit, isSubmitting: state.isSubmitting })}>\n          {({ canSubmit, isSubmitting }) => (\n            <Button\n              type=\"submit\"\n              className=\"w-full\"\n              disabled={!canSubmit || isSubmitting}\n            >\n              {isSubmitting ? \"Submitting...\" : \"Sign In\"}\n            </Button>\n          )}\n        </form.Subscribe>\n      </form>\n\n      <div className=\"mt-4 text-center\">\n        <Button\n          variant=\"link\"\n          onClick={onSwitchToSignUp}\n          className=\"text-indigo-600 hover:text-indigo-800\"\n        >\n          Need an account? Sign Up\n        </Button>\n      </div>\n    </div>\n  );\n}\n"
  },
  {
    "path": "packages/template-generator/templates/auth/better-auth/web/react/next/src/components/sign-up-form.tsx.hbs",
    "content": "import { authClient } from \"@/lib/auth-client\";\nimport { useForm } from \"@tanstack/react-form\";\nimport { toast } from \"sonner\";\nimport z from \"zod\";\nimport Loader from \"./loader\";\nimport { Button } from \"@{{projectName}}/ui/components/button\";\nimport { Input } from \"@{{projectName}}/ui/components/input\";\nimport { Label } from \"@{{projectName}}/ui/components/label\";\nimport { useRouter } from \"next/navigation\";\n\nexport default function SignUpForm({\n  onSwitchToSignIn,\n}: {\n  onSwitchToSignIn: () => void;\n}) {\n  const router = useRouter();\n  const { isPending } = authClient.useSession();\n\n  const form = useForm({\n    defaultValues: {\n      email: \"\",\n      password: \"\",\n      name: \"\",\n    },\n    onSubmit: async ({ value }) => {\n      await authClient.signUp.email(\n        {\n          email: value.email,\n          password: value.password,\n          name: value.name,\n        },\n        {\n          onSuccess: () => {\n            router.push(\"/dashboard\");\n            toast.success(\"Sign up successful\");\n          },\n          onError: (error) => {\n            toast.error(error.error.message || error.error.statusText);\n          },\n        },\n      );\n    },\n    validators: {\n      onSubmit: z.object({\n        name: z.string().min(2, \"Name must be at least 2 characters\"),\n        email: z.email(\"Invalid email address\"),\n        password: z.string().min(8, \"Password must be at least 8 characters\"),\n      }),\n    },\n  });\n\n  if (isPending) {\n    return <Loader />;\n  }\n\n  return (\n    <div className=\"mx-auto w-full mt-10 max-w-md p-6\">\n      <h1 className=\"mb-6 text-center text-3xl font-bold\">Create Account</h1>\n\n      <form\n        onSubmit={(e) => {\n          e.preventDefault();\n          e.stopPropagation();\n          form.handleSubmit();\n        }}\n        className=\"space-y-4\"\n      >\n        <div>\n          <form.Field name=\"name\">\n            {(field) => (\n              <div className=\"space-y-2\">\n                <Label htmlFor={field.name}>Name</Label>\n                <Input\n                  id={field.name}\n                  name={field.name}\n                  value={field.state.value}\n                  onBlur={field.handleBlur}\n                  onChange={(e) => field.handleChange(e.target.value)}\n                />\n                {field.state.meta.errors.map((error) => (\n                  <p key={error?.message} className=\"text-red-500\">\n                    {error?.message}\n                  </p>\n                ))}\n              </div>\n            )}\n          </form.Field>\n        </div>\n\n        <div>\n          <form.Field name=\"email\">\n            {(field) => (\n              <div className=\"space-y-2\">\n                <Label htmlFor={field.name}>Email</Label>\n                <Input\n                  id={field.name}\n                  name={field.name}\n                  type=\"email\"\n                  value={field.state.value}\n                  onBlur={field.handleBlur}\n                  onChange={(e) => field.handleChange(e.target.value)}\n                />\n                {field.state.meta.errors.map((error) => (\n                  <p key={error?.message} className=\"text-red-500\">\n                    {error?.message}\n                  </p>\n                ))}\n              </div>\n            )}\n          </form.Field>\n        </div>\n\n        <div>\n          <form.Field name=\"password\">\n            {(field) => (\n              <div className=\"space-y-2\">\n                <Label htmlFor={field.name}>Password</Label>\n                <Input\n                  id={field.name}\n                  name={field.name}\n                  type=\"password\"\n                  value={field.state.value}\n                  onBlur={field.handleBlur}\n                  onChange={(e) => field.handleChange(e.target.value)}\n                />\n                {field.state.meta.errors.map((error) => (\n                  <p key={error?.message} className=\"text-red-500\">\n                    {error?.message}\n                  </p>\n                ))}\n              </div>\n            )}\n          </form.Field>\n        </div>\n\n        <form.Subscribe selector={(state) => ({ canSubmit: state.canSubmit, isSubmitting: state.isSubmitting })}>\n          {({ canSubmit, isSubmitting }) => (\n            <Button\n              type=\"submit\"\n              className=\"w-full\"\n              disabled={!canSubmit || isSubmitting}\n            >\n              {isSubmitting ? \"Submitting...\" : \"Sign Up\"}\n            </Button>\n          )}\n        </form.Subscribe>\n      </form>\n\n      <div className=\"mt-4 text-center\">\n        <Button\n          variant=\"link\"\n          onClick={onSwitchToSignIn}\n          className=\"text-indigo-600 hover:text-indigo-800\"\n        >\n          Already have an account? Sign In\n        </Button>\n      </div>\n    </div>\n  );\n}\n"
  },
  {
    "path": "packages/template-generator/templates/auth/better-auth/web/react/next/src/components/user-menu.tsx.hbs",
    "content": "import Link from \"next/link\";\nimport { useRouter } from \"next/navigation\";\n\nimport {\n  DropdownMenu,\n  DropdownMenuContent,\n  DropdownMenuGroup,\n  DropdownMenuItem,\n  DropdownMenuLabel,\n  DropdownMenuSeparator,\n  DropdownMenuTrigger,\n} from \"@{{projectName}}/ui/components/dropdown-menu\";\nimport { authClient } from \"@/lib/auth-client\";\n\nimport { Button } from \"@{{projectName}}/ui/components/button\";\nimport { Skeleton } from \"@{{projectName}}/ui/components/skeleton\";\n\nexport default function UserMenu() {\n  const router = useRouter();\n  const { data: session, isPending } = authClient.useSession();\n\n  if (isPending) {\n    return <Skeleton className=\"h-9 w-24\" />;\n  }\n\n  if (!session) {\n    return (\n      <Link href=\"/login\">\n        <Button variant=\"outline\">Sign In</Button>\n      </Link>\n    );\n  }\n\n  return (\n    <DropdownMenu>\n      <DropdownMenuTrigger render={<Button variant=\"outline\" />}>\n        {session.user.name}\n      </DropdownMenuTrigger>\n      <DropdownMenuContent className=\"bg-card\">\n        <DropdownMenuGroup>\n          <DropdownMenuLabel>My Account</DropdownMenuLabel>\n          <DropdownMenuSeparator />\n          <DropdownMenuItem>{session.user.email}</DropdownMenuItem>\n          <DropdownMenuItem\n            variant=\"destructive\"\n            onClick={() => {\n              authClient.signOut({\n                fetchOptions: {\n                  onSuccess: () => {\n                    router.push(\"/\");\n                  },\n                },\n              });\n            }}\n          >\n            Sign Out\n          </DropdownMenuItem>\n        </DropdownMenuGroup>\n      </DropdownMenuContent>\n    </DropdownMenu>\n  );\n}\n"
  },
  {
    "path": "packages/template-generator/templates/auth/better-auth/web/react/react-router/src/components/sign-in-form.tsx.hbs",
    "content": "import { authClient } from \"@/lib/auth-client\";\nimport { useForm } from \"@tanstack/react-form\";\nimport { useNavigate } from \"react-router\";\nimport { toast } from \"sonner\";\nimport z from \"zod\";\nimport Loader from \"./loader\";\nimport { Button } from \"@{{projectName}}/ui/components/button\";\nimport { Input } from \"@{{projectName}}/ui/components/input\";\nimport { Label } from \"@{{projectName}}/ui/components/label\";\n\nexport default function SignInForm({\n  onSwitchToSignUp,\n}: {\n  onSwitchToSignUp: () => void;\n}) {\n  const navigate = useNavigate();\n  const { isPending } = authClient.useSession();\n\n  const form = useForm({\n    defaultValues: {\n      email: \"\",\n      password: \"\",\n    },\n    onSubmit: async ({ value }) => {\n      await authClient.signIn.email(\n        {\n          email: value.email,\n          password: value.password,\n        },\n        {\n          onSuccess: () => {\n            navigate(\"/dashboard\");\n            toast.success(\"Sign in successful\");\n          },\n          onError: (error) => {\n            toast.error(error.error.message || error.error.statusText);\n          },\n        }\n      );\n    },\n    validators: {\n      onSubmit: z.object({\n        email: z.email(\"Invalid email address\"),\n        password: z.string().min(8, \"Password must be at least 8 characters\"),\n      }),\n    },\n  });\n\n  if (isPending) {\n    return <Loader />;\n  }\n\n  return (\n    <div className=\"mx-auto w-full mt-10 max-w-md p-6\">\n      <h1 className=\"mb-6 text-center text-3xl font-bold\">Welcome Back</h1>\n\n      <form\n        onSubmit={(e) => {\n          e.preventDefault();\n          e.stopPropagation();\n          form.handleSubmit();\n        }}\n        className=\"space-y-4\"\n      >\n        <div>\n          <form.Field name=\"email\">\n            {(field) => (\n              <div className=\"space-y-2\">\n                <Label htmlFor={field.name}>Email</Label>\n                <Input\n                  id={field.name}\n                  name={field.name}\n                  type=\"email\"\n                  value={field.state.value}\n                  onBlur={field.handleBlur}\n                  onChange={(e) => field.handleChange(e.target.value)}\n                />\n                {field.state.meta.errors.map((error) => (\n                  <p key={error?.message} className=\"text-red-500\">\n                    {error?.message}\n                  </p>\n                ))}\n              </div>\n            )}\n          </form.Field>\n        </div>\n\n        <div>\n          <form.Field name=\"password\">\n            {(field) => (\n              <div className=\"space-y-2\">\n                <Label htmlFor={field.name}>Password</Label>\n                <Input\n                  id={field.name}\n                  name={field.name}\n                  type=\"password\"\n                  value={field.state.value}\n                  onBlur={field.handleBlur}\n                  onChange={(e) => field.handleChange(e.target.value)}\n                />\n                {field.state.meta.errors.map((error) => (\n                  <p key={error?.message} className=\"text-red-500\">\n                    {error?.message}\n                  </p>\n                ))}\n              </div>\n            )}\n          </form.Field>\n        </div>\n\n        <form.Subscribe selector={(state) => ({ canSubmit: state.canSubmit, isSubmitting: state.isSubmitting })}>\n          {({ canSubmit, isSubmitting }) => (\n            <Button\n              type=\"submit\"\n              className=\"w-full\"\n              disabled={!canSubmit || isSubmitting}\n            >\n              {isSubmitting ? \"Submitting...\" : \"Sign In\"}\n            </Button>\n          )}\n        </form.Subscribe>\n      </form>\n\n      <div className=\"mt-4 text-center\">\n        <Button\n          variant=\"link\"\n          onClick={onSwitchToSignUp}\n          className=\"text-indigo-600 hover:text-indigo-800\"\n        >\n          Need an account? Sign Up\n        </Button>\n      </div>\n    </div>\n  );\n}\n"
  },
  {
    "path": "packages/template-generator/templates/auth/better-auth/web/react/react-router/src/components/sign-up-form.tsx.hbs",
    "content": "import { authClient } from \"@/lib/auth-client\";\nimport { useForm } from \"@tanstack/react-form\";\nimport { useNavigate } from \"react-router\";\nimport { toast } from \"sonner\";\nimport z from \"zod\";\nimport Loader from \"./loader\";\nimport { Button } from \"@{{projectName}}/ui/components/button\";\nimport { Input } from \"@{{projectName}}/ui/components/input\";\nimport { Label } from \"@{{projectName}}/ui/components/label\";\n\nexport default function SignUpForm({\n  onSwitchToSignIn,\n}: {\n  onSwitchToSignIn: () => void;\n}) {\n  const navigate = useNavigate();\n  const { isPending } = authClient.useSession();\n\n  const form = useForm({\n    defaultValues: {\n      email: \"\",\n      password: \"\",\n      name: \"\",\n    },\n    onSubmit: async ({ value }) => {\n      await authClient.signUp.email(\n        {\n          email: value.email,\n          password: value.password,\n          name: value.name,\n        },\n        {\n          onSuccess: () => {\n            navigate(\"/dashboard\");\n            toast.success(\"Sign up successful\");\n          },\n          onError: (error) => {\n            toast.error(error.error.message || error.error.statusText);\n          },\n        }\n      );\n    },\n    validators: {\n      onSubmit: z.object({\n        name: z.string().min(2, \"Name must be at least 2 characters\"),\n        email: z.email(\"Invalid email address\"),\n        password: z.string().min(8, \"Password must be at least 8 characters\"),\n      }),\n    },\n  });\n\n  if (isPending) {\n    return <Loader />;\n  }\n\n  return (\n    <div className=\"mx-auto w-full mt-10 max-w-md p-6\">\n      <h1 className=\"mb-6 text-center text-3xl font-bold\">Create Account</h1>\n\n      <form\n        onSubmit={(e) => {\n          e.preventDefault();\n          e.stopPropagation();\n          form.handleSubmit();\n        }}\n        className=\"space-y-4\"\n      >\n        <div>\n          <form.Field name=\"name\">\n            {(field) => (\n              <div className=\"space-y-2\">\n                <Label htmlFor={field.name}>Name</Label>\n                <Input\n                  id={field.name}\n                  name={field.name}\n                  value={field.state.value}\n                  onBlur={field.handleBlur}\n                  onChange={(e) => field.handleChange(e.target.value)}\n                />\n                {field.state.meta.errors.map((error) => (\n                  <p key={error?.message} className=\"text-red-500\">\n                    {error?.message}\n                  </p>\n                ))}\n              </div>\n            )}\n          </form.Field>\n        </div>\n\n        <div>\n          <form.Field name=\"email\">\n            {(field) => (\n              <div className=\"space-y-2\">\n                <Label htmlFor={field.name}>Email</Label>\n                <Input\n                  id={field.name}\n                  name={field.name}\n                  type=\"email\"\n                  value={field.state.value}\n                  onBlur={field.handleBlur}\n                  onChange={(e) => field.handleChange(e.target.value)}\n                />\n                {field.state.meta.errors.map((error) => (\n                  <p key={error?.message} className=\"text-red-500\">\n                    {error?.message}\n                  </p>\n                ))}\n              </div>\n            )}\n          </form.Field>\n        </div>\n\n        <div>\n          <form.Field name=\"password\">\n            {(field) => (\n              <div className=\"space-y-2\">\n                <Label htmlFor={field.name}>Password</Label>\n                <Input\n                  id={field.name}\n                  name={field.name}\n                  type=\"password\"\n                  value={field.state.value}\n                  onBlur={field.handleBlur}\n                  onChange={(e) => field.handleChange(e.target.value)}\n                />\n                {field.state.meta.errors.map((error) => (\n                  <p key={error?.message} className=\"text-red-500\">\n                    {error?.message}\n                  </p>\n                ))}\n              </div>\n            )}\n          </form.Field>\n        </div>\n\n        <form.Subscribe selector={(state) => ({ canSubmit: state.canSubmit, isSubmitting: state.isSubmitting })}>\n          {({ canSubmit, isSubmitting }) => (\n            <Button\n              type=\"submit\"\n              className=\"w-full\"\n              disabled={!canSubmit || isSubmitting}\n            >\n              {isSubmitting ? \"Submitting...\" : \"Sign Up\"}\n            </Button>\n          )}\n        </form.Subscribe>\n      </form>\n\n      <div className=\"mt-4 text-center\">\n        <Button\n          variant=\"link\"\n          onClick={onSwitchToSignIn}\n          className=\"text-indigo-600 hover:text-indigo-800\"\n        >\n          Already have an account? Sign In\n        </Button>\n      </div>\n    </div>\n  );\n}\n"
  },
  {
    "path": "packages/template-generator/templates/auth/better-auth/web/react/react-router/src/components/user-menu.tsx.hbs",
    "content": "import { Link, useNavigate } from \"react-router\";\n\nimport {\n  DropdownMenu,\n  DropdownMenuContent,\n  DropdownMenuGroup,\n  DropdownMenuItem,\n  DropdownMenuLabel,\n  DropdownMenuSeparator,\n  DropdownMenuTrigger,\n} from \"@{{projectName}}/ui/components/dropdown-menu\";\nimport { authClient } from \"@/lib/auth-client\";\n\nimport { Button } from \"@{{projectName}}/ui/components/button\";\nimport { Skeleton } from \"@{{projectName}}/ui/components/skeleton\";\n\nexport default function UserMenu() {\n  const navigate = useNavigate();\n  const { data: session, isPending } = authClient.useSession();\n\n  if (isPending) {\n    return <Skeleton className=\"h-9 w-24\" />;\n  }\n\n  if (!session) {\n    return (\n      <Link to=\"/login\">\n        <Button variant=\"outline\">Sign In</Button>\n      </Link>\n    );\n  }\n\n  return (\n    <DropdownMenu>\n      <DropdownMenuTrigger render={<Button variant=\"outline\" />}>\n        {session.user.name}\n      </DropdownMenuTrigger>\n      <DropdownMenuContent className=\"bg-card\">\n        <DropdownMenuGroup>\n          <DropdownMenuLabel>My Account</DropdownMenuLabel>\n          <DropdownMenuSeparator />\n          <DropdownMenuItem>{session.user.email}</DropdownMenuItem>\n          <DropdownMenuItem\n            variant=\"destructive\"\n            onClick={() => {\n              authClient.signOut({\n                fetchOptions: {\n                  onSuccess: () => {\n                    navigate(\"/\");\n                  },\n                },\n              });\n            }}\n          >\n            Sign Out\n          </DropdownMenuItem>\n        </DropdownMenuGroup>\n      </DropdownMenuContent>\n    </DropdownMenu>\n  );\n}\n"
  },
  {
    "path": "packages/template-generator/templates/auth/better-auth/web/react/react-router/src/routes/dashboard.tsx.hbs",
    "content": "{{#if (eq payments \"polar\")}}\nimport { Button } from \"@{{projectName}}/ui/components/button\";\n{{/if}}\nimport { authClient } from \"@/lib/auth-client\";\n{{#if (eq api \"orpc\")}}\nimport { orpc } from \"@/utils/orpc\";\n{{/if}}\n{{#if (eq api \"trpc\")}}\nimport { trpc } from \"@/utils/trpc\";\n{{/if}}\n{{#if ( or (eq api \"orpc\") (eq api \"trpc\"))}}\nimport { useQuery } from \"@tanstack/react-query\";\n{{/if}}\nimport { useEffect, useState } from \"react\";\nimport { useNavigate } from \"react-router\";\n\nexport default function Dashboard() {\n  const { data: session, isPending } = authClient.useSession();\n  const navigate = useNavigate();\n  {{#if (eq payments \"polar\")}}\n  const [customerState, setCustomerState] = useState<any>(null);\n  {{/if}}\n\n  {{#if (eq api \"orpc\")}}\n  const privateData = useQuery(orpc.privateData.queryOptions());\n  {{/if}}\n  {{#if (eq api \"trpc\")}}\n  const privateData = useQuery(trpc.privateData.queryOptions());\n  {{/if}}\n\n  useEffect(() => {\n    if (!session && !isPending) {\n      navigate(\"/login\");\n    }\n  }, [session, isPending, navigate]);\n\n  {{#if (eq payments \"polar\")}}\n  useEffect(() => {\n    async function fetchCustomerState() {\n      if (session) {\n        const { data } = await authClient.customer.state();\n        setCustomerState(data);\n      }\n    }\n\n    fetchCustomerState();\n  }, [session]);\n  {{/if}}\n\n  if (isPending) {\n    return <div>Loading...</div>;\n  }\n\n  {{#if (eq payments \"polar\")}}\n  const hasProSubscription = customerState?.activeSubscriptions?.length! > 0;\n  console.log(\"Active subscriptions:\", customerState?.activeSubscriptions);\n  {{/if}}\n\n  return (\n    <div>\n      <h1>Dashboard</h1>\n      <p>Welcome {session?.user.name}</p>\n      {{#if ( or (eq api \"orpc\") (eq api \"trpc\"))}}\n      <p>API: {privateData.data?.message}</p>\n      {{/if}}\n      {{#if (eq payments \"polar\")}}\n      <p>Plan: {hasProSubscription ? \"Pro\" : \"Free\"}</p>\n      {hasProSubscription ? (\n        <Button onClick={async () => await authClient.customer.portal()}>\n          Manage Subscription\n        </Button>\n      ) : (\n        <Button onClick={async () => await authClient.checkout({ slug: \"pro\" })}>\n          Upgrade to Pro\n        </Button>\n      )}\n      {{/if}}\n    </div>\n  );\n}\n"
  },
  {
    "path": "packages/template-generator/templates/auth/better-auth/web/react/react-router/src/routes/login.tsx.hbs",
    "content": "import SignInForm from \"@/components/sign-in-form\";\nimport SignUpForm from \"@/components/sign-up-form\";\nimport { useState } from \"react\";\n\nexport default function Login() {\n  const [showSignIn, setShowSignIn] = useState(false);\n\n  return showSignIn ? (\n    <SignInForm onSwitchToSignUp={() => setShowSignIn(false)} />\n  ) : (\n    <SignUpForm onSwitchToSignIn={() => setShowSignIn(true)} />\n  );\n}\n"
  },
  {
    "path": "packages/template-generator/templates/auth/better-auth/web/react/tanstack-router/src/components/sign-in-form.tsx.hbs",
    "content": "import { authClient } from \"@/lib/auth-client\";\nimport { useForm } from \"@tanstack/react-form\";\nimport { useNavigate } from \"@tanstack/react-router\";\nimport { toast } from \"sonner\";\nimport z from \"zod\";\nimport Loader from \"./loader\";\nimport { Button } from \"@{{projectName}}/ui/components/button\";\nimport { Input } from \"@{{projectName}}/ui/components/input\";\nimport { Label } from \"@{{projectName}}/ui/components/label\";\n\nexport default function SignInForm({ onSwitchToSignUp }: { onSwitchToSignUp: () => void }) {\n  const navigate = useNavigate({\n    from: \"/\",\n  });\n  const { isPending } = authClient.useSession();\n\n  const form = useForm({\n    defaultValues: {\n      email: \"\",\n      password: \"\",\n    },\n    onSubmit: async ({ value }) => {\n      await authClient.signIn.email(\n        {\n          email: value.email,\n          password: value.password,\n        },\n        {\n          onSuccess: () => {\n            navigate({\n              to: \"/dashboard\",\n            });\n            toast.success(\"Sign in successful\");\n          },\n          onError: (error) => {\n            toast.error(error.error.message || error.error.statusText);\n          },\n        },\n      );\n    },\n    validators: {\n      onSubmit: z.object({\n        email: z.email(\"Invalid email address\"),\n        password: z.string().min(8, \"Password must be at least 8 characters\"),\n      }),\n    },\n  });\n\n  if (isPending) {\n    return <Loader />;\n  }\n\n  return (\n    <div className=\"mx-auto w-full mt-10 max-w-md p-6\">\n      <h1 className=\"mb-6 text-center text-3xl font-bold\">Welcome Back</h1>\n\n      <form\n        onSubmit={(e) => {\n          e.preventDefault();\n          e.stopPropagation();\n          form.handleSubmit();\n        }}\n        className=\"space-y-4\"\n      >\n        <div>\n          <form.Field name=\"email\">\n            {(field) => (\n              <div className=\"space-y-2\">\n                <Label htmlFor={field.name}>Email</Label>\n                <Input\n                  id={field.name}\n                  name={field.name}\n                  type=\"email\"\n                  value={field.state.value}\n                  onBlur={field.handleBlur}\n                  onChange={(e) => field.handleChange(e.target.value)}\n                />\n                {field.state.meta.errors.map((error) => (\n                  <p key={error?.message} className=\"text-red-500\">\n                    {error?.message}\n                  </p>\n                ))}\n              </div>\n            )}\n          </form.Field>\n        </div>\n\n        <div>\n          <form.Field name=\"password\">\n            {(field) => (\n              <div className=\"space-y-2\">\n                <Label htmlFor={field.name}>Password</Label>\n                <Input\n                  id={field.name}\n                  name={field.name}\n                  type=\"password\"\n                  value={field.state.value}\n                  onBlur={field.handleBlur}\n                  onChange={(e) => field.handleChange(e.target.value)}\n                />\n                {field.state.meta.errors.map((error) => (\n                  <p key={error?.message} className=\"text-red-500\">\n                    {error?.message}\n                  </p>\n                ))}\n              </div>\n            )}\n          </form.Field>\n        </div>\n\n        <form.Subscribe selector={(state) => ({ canSubmit: state.canSubmit, isSubmitting: state.isSubmitting })}>\n          {({ canSubmit, isSubmitting }) => (\n            <Button\n              type=\"submit\"\n              className=\"w-full\"\n              disabled={!canSubmit || isSubmitting}\n            >\n              {isSubmitting ? \"Submitting...\" : \"Sign In\"}\n            </Button>\n          )}\n        </form.Subscribe>\n      </form>\n\n      <div className=\"mt-4 text-center\">\n        <Button\n          variant=\"link\"\n          onClick={onSwitchToSignUp}\n          className=\"text-indigo-600 hover:text-indigo-800\"\n        >\n          Need an account? Sign Up\n        </Button>\n      </div>\n    </div>\n  );\n}\n"
  },
  {
    "path": "packages/template-generator/templates/auth/better-auth/web/react/tanstack-router/src/components/sign-up-form.tsx.hbs",
    "content": "import { authClient } from \"@/lib/auth-client\";\nimport { useForm } from \"@tanstack/react-form\";\nimport { useNavigate } from \"@tanstack/react-router\";\nimport { toast } from \"sonner\";\nimport z from \"zod\";\nimport Loader from \"./loader\";\nimport { Button } from \"@{{projectName}}/ui/components/button\";\nimport { Input } from \"@{{projectName}}/ui/components/input\";\nimport { Label } from \"@{{projectName}}/ui/components/label\";\n\nexport default function SignUpForm({ onSwitchToSignIn }: { onSwitchToSignIn: () => void }) {\n  const navigate = useNavigate({\n    from: \"/\",\n  });\n  const { isPending } = authClient.useSession();\n\n  const form = useForm({\n    defaultValues: {\n      email: \"\",\n      password: \"\",\n      name: \"\",\n    },\n    onSubmit: async ({ value }) => {\n      await authClient.signUp.email(\n        {\n          email: value.email,\n          password: value.password,\n          name: value.name,\n        },\n        {\n          onSuccess: () => {\n            navigate({\n              to: \"/dashboard\",\n            });\n            toast.success(\"Sign up successful\");\n          },\n          onError: (error) => {\n            toast.error(error.error.message || error.error.statusText);\n          },\n        },\n      );\n    },\n    validators: {\n      onSubmit: z.object({\n        name: z.string().min(2, \"Name must be at least 2 characters\"),\n        email: z.email(\"Invalid email address\"),\n        password: z.string().min(8, \"Password must be at least 8 characters\"),\n      }),\n    },\n  });\n\n  if (isPending) {\n    return <Loader />;\n  }\n\n  return (\n    <div className=\"mx-auto w-full mt-10 max-w-md p-6\">\n      <h1 className=\"mb-6 text-center text-3xl font-bold\">Create Account</h1>\n\n      <form\n        onSubmit={(e) => {\n          e.preventDefault();\n          e.stopPropagation();\n          form.handleSubmit();\n        }}\n        className=\"space-y-4\"\n      >\n        <div>\n          <form.Field name=\"name\">\n            {(field) => (\n              <div className=\"space-y-2\">\n                <Label htmlFor={field.name}>Name</Label>\n                <Input\n                  id={field.name}\n                  name={field.name}\n                  value={field.state.value}\n                  onBlur={field.handleBlur}\n                  onChange={(e) => field.handleChange(e.target.value)}\n                />\n                {field.state.meta.errors.map((error) => (\n                  <p key={error?.message} className=\"text-red-500\">\n                    {error?.message}\n                  </p>\n                ))}\n              </div>\n            )}\n          </form.Field>\n        </div>\n\n        <div>\n          <form.Field name=\"email\">\n            {(field) => (\n              <div className=\"space-y-2\">\n                <Label htmlFor={field.name}>Email</Label>\n                <Input\n                  id={field.name}\n                  name={field.name}\n                  type=\"email\"\n                  value={field.state.value}\n                  onBlur={field.handleBlur}\n                  onChange={(e) => field.handleChange(e.target.value)}\n                />\n                {field.state.meta.errors.map((error) => (\n                  <p key={error?.message} className=\"text-red-500\">\n                    {error?.message}\n                  </p>\n                ))}\n              </div>\n            )}\n          </form.Field>\n        </div>\n\n        <div>\n          <form.Field name=\"password\">\n            {(field) => (\n              <div className=\"space-y-2\">\n                <Label htmlFor={field.name}>Password</Label>\n                <Input\n                  id={field.name}\n                  name={field.name}\n                  type=\"password\"\n                  value={field.state.value}\n                  onBlur={field.handleBlur}\n                  onChange={(e) => field.handleChange(e.target.value)}\n                />\n                {field.state.meta.errors.map((error) => (\n                  <p key={error?.message} className=\"text-red-500\">\n                    {error?.message}\n                  </p>\n                ))}\n              </div>\n            )}\n          </form.Field>\n        </div>\n\n        <form.Subscribe selector={(state) => ({ canSubmit: state.canSubmit, isSubmitting: state.isSubmitting })}>\n          {({ canSubmit, isSubmitting }) => (\n            <Button\n              type=\"submit\"\n              className=\"w-full\"\n              disabled={!canSubmit || isSubmitting}\n            >\n              {isSubmitting ? \"Submitting...\" : \"Sign Up\"}\n            </Button>\n          )}\n        </form.Subscribe>\n      </form>\n\n      <div className=\"mt-4 text-center\">\n        <Button\n          variant=\"link\"\n          onClick={onSwitchToSignIn}\n          className=\"text-indigo-600 hover:text-indigo-800\"\n        >\n          Already have an account? Sign In\n        </Button>\n      </div>\n    </div>\n  );\n}\n"
  },
  {
    "path": "packages/template-generator/templates/auth/better-auth/web/react/tanstack-router/src/components/user-menu.tsx.hbs",
    "content": "import { Link, useNavigate } from \"@tanstack/react-router\";\n\nimport {\n  DropdownMenu,\n  DropdownMenuContent,\n  DropdownMenuGroup,\n  DropdownMenuItem,\n  DropdownMenuLabel,\n  DropdownMenuSeparator,\n  DropdownMenuTrigger,\n} from \"@{{projectName}}/ui/components/dropdown-menu\";\nimport { authClient } from \"@/lib/auth-client\";\n\nimport { Button } from \"@{{projectName}}/ui/components/button\";\nimport { Skeleton } from \"@{{projectName}}/ui/components/skeleton\";\n\nexport default function UserMenu() {\n  const navigate = useNavigate();\n  const { data: session, isPending } = authClient.useSession();\n\n  if (isPending) {\n    return <Skeleton className=\"h-9 w-24\" />;\n  }\n\n  if (!session) {\n    return (\n      <Link to=\"/login\">\n        <Button variant=\"outline\">Sign In</Button>\n      </Link>\n    );\n  }\n\n  return (\n    <DropdownMenu>\n      <DropdownMenuTrigger render={<Button variant=\"outline\" />}>\n        {session.user.name}\n      </DropdownMenuTrigger>\n      <DropdownMenuContent className=\"bg-card\">\n        <DropdownMenuGroup>\n          <DropdownMenuLabel>My Account</DropdownMenuLabel>\n          <DropdownMenuSeparator />\n          <DropdownMenuItem>{session.user.email}</DropdownMenuItem>\n          <DropdownMenuItem\n            variant=\"destructive\"\n            onClick={() => {\n              authClient.signOut({\n                fetchOptions: {\n                  onSuccess: () => {\n                    navigate({\n                      to: \"/\",\n                    });\n                  },\n                },\n              });\n            }}\n          >\n            Sign Out\n          </DropdownMenuItem>\n        </DropdownMenuGroup>\n      </DropdownMenuContent>\n    </DropdownMenu>\n  );\n}\n"
  },
  {
    "path": "packages/template-generator/templates/auth/better-auth/web/react/tanstack-router/src/routes/dashboard.tsx.hbs",
    "content": "{{#if (eq payments \"polar\")}}\nimport { Button } from \"@{{projectName}}/ui/components/button\";\n{{/if}}\nimport { authClient } from \"@/lib/auth-client\";\n{{#if (eq api \"orpc\")}}\nimport { orpc } from \"@/utils/orpc\";\n{{/if}}\n{{#if (eq api \"trpc\")}}\nimport { trpc } from \"@/utils/trpc\";\n{{/if}}\n{{#if ( or (eq api \"orpc\") (eq api \"trpc\"))}}\nimport { useQuery } from \"@tanstack/react-query\";\n{{/if}}\nimport { createFileRoute, redirect } from \"@tanstack/react-router\";\n\nexport const Route = createFileRoute(\"/dashboard\")({\n\tcomponent: RouteComponent,\n\tbeforeLoad: async () => {\n\t\tconst session = await authClient.getSession();\n\t\tif (!session.data) {\n\t\t\tredirect({\n\t\t\t\tto: \"/login\",\n\t\t\t\tthrow: true\n\t\t\t});\n\t\t}\n\t\t{{#if (eq payments \"polar\")}}\n\t\tconst {data: customerState} = await authClient.customer.state()\n\t\treturn { session, customerState };\n\t\t{{else}}\n\t\treturn { session };\n\t\t{{/if}}\n\t}\n});\n\nfunction RouteComponent() {\n\tconst { session{{#if (eq payments \"polar\")}}, customerState{{/if}} } = Route.useRouteContext();\n\n\t{{#if (eq api \"orpc\")}}\n\tconst privateData = useQuery(orpc.privateData.queryOptions());\n\t{{/if}}\n\t{{#if (eq api \"trpc\")}}\n\tconst privateData = useQuery(trpc.privateData.queryOptions());\n\t{{/if}}\n\n\t{{#if (eq payments \"polar\")}}\n\tconst hasProSubscription = customerState?.activeSubscriptions?.length! > 0\n    console.log(\"Active subscriptions:\", customerState?.activeSubscriptions)\n\t{{/if}}\n\n\treturn (\n\t\t<div>\n\t\t\t<h1>Dashboard</h1>\n\t\t\t<p>Welcome {session.data?.user.name}</p>\n\t\t\t{{#if ( or (eq api \"orpc\") (eq api \"trpc\"))}}\n\t\t\t<p>API: {privateData.data?.message}</p>\n\t\t\t{{/if}}\n\t\t\t{{#if (eq payments \"polar\")}}\n\t\t\t<p>Plan: {hasProSubscription ? \"Pro\" : \"Free\"}</p>\n\t\t\t{hasProSubscription ? (\n\t\t\t\t<Button onClick={async () => await authClient.customer.portal()}>\n\t\t\t\t\tManage Subscription\n\t\t\t\t</Button>\n\t\t\t) : (\n\t\t\t\t<Button onClick={async () => await authClient.checkout({ slug: \"pro\" })}>\n\t\t\t\t\tUpgrade to Pro\n\t\t\t\t</Button>\n\t\t\t)}\n\t\t\t{{/if}}\n\t\t</div>\n\t);\n}\n"
  },
  {
    "path": "packages/template-generator/templates/auth/better-auth/web/react/tanstack-router/src/routes/login.tsx.hbs",
    "content": "import SignInForm from \"@/components/sign-in-form\";\nimport SignUpForm from \"@/components/sign-up-form\";\nimport { createFileRoute } from \"@tanstack/react-router\";\nimport { useState } from \"react\";\n\nexport const Route = createFileRoute(\"/login\")({\n  component: RouteComponent,\n});\n\nfunction RouteComponent() {\n  const [showSignIn, setShowSignIn] = useState(false);\n\n  return showSignIn ? (\n    <SignInForm onSwitchToSignUp={() => setShowSignIn(false)} />\n  ) : (\n    <SignUpForm onSwitchToSignIn={() => setShowSignIn(true)} />\n  );\n}\n"
  },
  {
    "path": "packages/template-generator/templates/auth/better-auth/web/react/tanstack-start/src/components/sign-in-form.tsx.hbs",
    "content": "import { authClient } from \"@/lib/auth-client\";\nimport { useForm } from \"@tanstack/react-form\";\nimport { useNavigate } from \"@tanstack/react-router\";\nimport { toast } from \"sonner\";\nimport z from \"zod\";\nimport Loader from \"./loader\";\nimport { Button } from \"@{{projectName}}/ui/components/button\";\nimport { Input } from \"@{{projectName}}/ui/components/input\";\nimport { Label } from \"@{{projectName}}/ui/components/label\";\n\nexport default function SignInForm({ onSwitchToSignUp }: { onSwitchToSignUp: () => void }) {\n  const navigate = useNavigate({\n    from: \"/\",\n  });\n  const { isPending } = authClient.useSession();\n\n  const form = useForm({\n    defaultValues: {\n      email: \"\",\n      password: \"\",\n    },\n    onSubmit: async ({ value }) => {\n      await authClient.signIn.email(\n        {\n          email: value.email,\n          password: value.password,\n        },\n        {\n          onSuccess: () => {\n            navigate({\n              to: \"/dashboard\",\n            });\n            toast.success(\"Sign in successful\");\n          },\n          onError: (error) => {\n            toast.error(error.error.message || error.error.statusText);\n          },\n        },\n      );\n    },\n    validators: {\n      onSubmit: z.object({\n        email: z.email(\"Invalid email address\"),\n        password: z.string().min(8, \"Password must be at least 8 characters\"),\n      }),\n    },\n  });\n\n  if (isPending) {\n    return <Loader />;\n  }\n\n  return (\n    <div className=\"mx-auto w-full mt-10 max-w-md p-6\">\n      <h1 className=\"mb-6 text-center text-3xl font-bold\">Welcome Back</h1>\n\n      <form\n        onSubmit={(e) => {\n          e.preventDefault();\n          e.stopPropagation();\n          form.handleSubmit();\n        }}\n        className=\"space-y-4\"\n      >\n        <div>\n          <form.Field name=\"email\">\n            {(field) => (\n              <div className=\"space-y-2\">\n                <Label htmlFor={field.name}>Email</Label>\n                <Input\n                  id={field.name}\n                  name={field.name}\n                  type=\"email\"\n                  value={field.state.value}\n                  onBlur={field.handleBlur}\n                  onChange={(e) => field.handleChange(e.target.value)}\n                />\n                {field.state.meta.errors.map((error) => (\n                  <p key={error?.message} className=\"text-red-500\">\n                    {error?.message}\n                  </p>\n                ))}\n              </div>\n            )}\n          </form.Field>\n        </div>\n\n        <div>\n          <form.Field name=\"password\">\n            {(field) => (\n              <div className=\"space-y-2\">\n                <Label htmlFor={field.name}>Password</Label>\n                <Input\n                  id={field.name}\n                  name={field.name}\n                  type=\"password\"\n                  value={field.state.value}\n                  onBlur={field.handleBlur}\n                  onChange={(e) => field.handleChange(e.target.value)}\n                />\n                {field.state.meta.errors.map((error) => (\n                  <p key={error?.message} className=\"text-red-500\">\n                    {error?.message}\n                  </p>\n                ))}\n              </div>\n            )}\n          </form.Field>\n        </div>\n\n        <form.Subscribe selector={(state) => ({ canSubmit: state.canSubmit, isSubmitting: state.isSubmitting })}>\n          {({ canSubmit, isSubmitting }) => (\n            <Button\n              type=\"submit\"\n              className=\"w-full\"\n              disabled={!canSubmit || isSubmitting}\n            >\n              {isSubmitting ? \"Submitting...\" : \"Sign In\"}\n            </Button>\n          )}\n        </form.Subscribe>\n      </form>\n\n      <div className=\"mt-4 text-center\">\n        <Button\n          variant=\"link\"\n          onClick={onSwitchToSignUp}\n          className=\"text-indigo-600 hover:text-indigo-800\"\n        >\n          Need an account? Sign Up\n        </Button>\n      </div>\n    </div>\n  );\n}\n"
  },
  {
    "path": "packages/template-generator/templates/auth/better-auth/web/react/tanstack-start/src/components/sign-up-form.tsx.hbs",
    "content": "import { authClient } from \"@/lib/auth-client\";\nimport { useForm } from \"@tanstack/react-form\";\nimport { useNavigate } from \"@tanstack/react-router\";\nimport { toast } from \"sonner\";\nimport z from \"zod\";\nimport Loader from \"./loader\";\nimport { Button } from \"@{{projectName}}/ui/components/button\";\nimport { Input } from \"@{{projectName}}/ui/components/input\";\nimport { Label } from \"@{{projectName}}/ui/components/label\";\n\nexport default function SignUpForm({ onSwitchToSignIn }: { onSwitchToSignIn: () => void }) {\n  const navigate = useNavigate({\n    from: \"/\",\n  });\n  const { isPending } = authClient.useSession();\n\n  const form = useForm({\n    defaultValues: {\n      email: \"\",\n      password: \"\",\n      name: \"\",\n    },\n    onSubmit: async ({ value }) => {\n      await authClient.signUp.email(\n        {\n          email: value.email,\n          password: value.password,\n          name: value.name,\n        },\n        {\n          onSuccess: () => {\n            navigate({\n              to: \"/dashboard\",\n            });\n            toast.success(\"Sign up successful\");\n          },\n          onError: (error) => {\n            toast.error(error.error.message || error.error.statusText);\n          },\n        },\n      );\n    },\n    validators: {\n      onSubmit: z.object({\n        name: z.string().min(2, \"Name must be at least 2 characters\"),\n        email: z.email(\"Invalid email address\"),\n        password: z.string().min(8, \"Password must be at least 8 characters\"),\n      }),\n    },\n  });\n\n  if (isPending) {\n    return <Loader />;\n  }\n\n  return (\n    <div className=\"mx-auto w-full mt-10 max-w-md p-6\">\n      <h1 className=\"mb-6 text-center text-3xl font-bold\">Create Account</h1>\n\n      <form\n        onSubmit={(e) => {\n          e.preventDefault();\n          e.stopPropagation();\n          form.handleSubmit();\n        }}\n        className=\"space-y-4\"\n      >\n        <div>\n          <form.Field name=\"name\">\n            {(field) => (\n              <div className=\"space-y-2\">\n                <Label htmlFor={field.name}>Name</Label>\n                <Input\n                  id={field.name}\n                  name={field.name}\n                  value={field.state.value}\n                  onBlur={field.handleBlur}\n                  onChange={(e) => field.handleChange(e.target.value)}\n                />\n                {field.state.meta.errors.map((error) => (\n                  <p key={error?.message} className=\"text-red-500\">\n                    {error?.message}\n                  </p>\n                ))}\n              </div>\n            )}\n          </form.Field>\n        </div>\n\n        <div>\n          <form.Field name=\"email\">\n            {(field) => (\n              <div className=\"space-y-2\">\n                <Label htmlFor={field.name}>Email</Label>\n                <Input\n                  id={field.name}\n                  name={field.name}\n                  type=\"email\"\n                  value={field.state.value}\n                  onBlur={field.handleBlur}\n                  onChange={(e) => field.handleChange(e.target.value)}\n                />\n                {field.state.meta.errors.map((error) => (\n                  <p key={error?.message} className=\"text-red-500\">\n                    {error?.message}\n                  </p>\n                ))}\n              </div>\n            )}\n          </form.Field>\n        </div>\n\n        <div>\n          <form.Field name=\"password\">\n            {(field) => (\n              <div className=\"space-y-2\">\n                <Label htmlFor={field.name}>Password</Label>\n                <Input\n                  id={field.name}\n                  name={field.name}\n                  type=\"password\"\n                  value={field.state.value}\n                  onBlur={field.handleBlur}\n                  onChange={(e) => field.handleChange(e.target.value)}\n                />\n                {field.state.meta.errors.map((error) => (\n                  <p key={error?.message} className=\"text-red-500\">\n                    {error?.message}\n                  </p>\n                ))}\n              </div>\n            )}\n          </form.Field>\n        </div>\n\n        <form.Subscribe selector={(state) => ({ canSubmit: state.canSubmit, isSubmitting: state.isSubmitting })}>\n          {({ canSubmit, isSubmitting }) => (\n            <Button\n              type=\"submit\"\n              className=\"w-full\"\n              disabled={!canSubmit || isSubmitting}\n            >\n              {isSubmitting ? \"Submitting...\" : \"Sign Up\"}\n            </Button>\n          )}\n        </form.Subscribe>\n      </form>\n\n      <div className=\"mt-4 text-center\">\n        <Button\n          variant=\"link\"\n          onClick={onSwitchToSignIn}\n          className=\"text-indigo-600 hover:text-indigo-800\"\n        >\n          Already have an account? Sign In\n        </Button>\n      </div>\n    </div>\n  );\n}\n"
  },
  {
    "path": "packages/template-generator/templates/auth/better-auth/web/react/tanstack-start/src/components/user-menu.tsx.hbs",
    "content": "import { Link, useNavigate } from \"@tanstack/react-router\";\n\nimport {\n  DropdownMenu,\n  DropdownMenuContent,\n  DropdownMenuGroup,\n  DropdownMenuItem,\n  DropdownMenuLabel,\n  DropdownMenuSeparator,\n  DropdownMenuTrigger,\n} from \"@{{projectName}}/ui/components/dropdown-menu\";\nimport { authClient } from \"@/lib/auth-client\";\n\nimport { Button } from \"@{{projectName}}/ui/components/button\";\nimport { Skeleton } from \"@{{projectName}}/ui/components/skeleton\";\n\nexport default function UserMenu() {\n  const navigate = useNavigate();\n  const { data: session, isPending } = authClient.useSession();\n\n  if (isPending) {\n    return <Skeleton className=\"h-9 w-24\" />;\n  }\n\n  if (!session) {\n    return (\n      <Link to=\"/login\">\n        <Button variant=\"outline\">Sign In</Button>\n      </Link>\n    );\n  }\n\n  return (\n    <DropdownMenu>\n      <DropdownMenuTrigger render={<Button variant=\"outline\" />}>\n        {session.user.name}\n      </DropdownMenuTrigger>\n      <DropdownMenuContent className=\"bg-card\">\n        <DropdownMenuGroup>\n          <DropdownMenuLabel>My Account</DropdownMenuLabel>\n          <DropdownMenuSeparator />\n          <DropdownMenuItem>{session.user.email}</DropdownMenuItem>\n          <DropdownMenuItem\n            variant=\"destructive\"\n            onClick={() => {\n              authClient.signOut({\n                fetchOptions: {\n                  onSuccess: () => {\n                    navigate({\n                      to: \"/\",\n                    });\n                  },\n                },\n              });\n            }}\n          >\n            Sign Out\n          </DropdownMenuItem>\n        </DropdownMenuGroup>\n      </DropdownMenuContent>\n    </DropdownMenu>\n  );\n}\n"
  },
  {
    "path": "packages/template-generator/templates/auth/better-auth/web/react/tanstack-start/src/functions/get-user.ts.hbs",
    "content": "import { authMiddleware } from \"@/middleware/auth\";\nimport { createServerFn } from \"@tanstack/react-start\";\n\nexport const getUser = createServerFn({ method: \"GET\" }).middleware([authMiddleware]).handler(async ({ context }) => {\n    return context.session\n})"
  },
  {
    "path": "packages/template-generator/templates/auth/better-auth/web/react/tanstack-start/src/middleware/auth.ts.hbs",
    "content": "{{#if (eq backend \"self\")}}\n{{#if (or (eq runtime \"workers\") (eq serverDeploy \"cloudflare\") (and (eq backend \"self\") (eq webDeploy \"cloudflare\")))}}\nimport { createAuth } from \"@{{projectName}}/auth\";\n{{else}}\nimport { auth } from \"@{{projectName}}/auth\";\n{{/if}}\nimport { createMiddleware } from \"@tanstack/react-start\";\n\n\nexport const authMiddleware = createMiddleware().server(async ({ next, request }) => {\n    const session = await {{#if (or (eq runtime \"workers\") (eq serverDeploy \"cloudflare\") (and (eq backend \"self\") (eq webDeploy \"cloudflare\")))}}createAuth(){{else}}auth{{/if}}.api.getSession({\n        headers: request.headers,\n    })\n    return next({\n        context: { session }\n    })\n})\n{{else}}\nimport { authClient } from \"@/lib/auth-client\";\nimport { createMiddleware } from \"@tanstack/react-start\";\n\nexport const authMiddleware = createMiddleware().server(\n\tasync ({ next, request }) => {\n\t\tconst session = await authClient.getSession({\n\t\t\tfetchOptions: {\n\t\t\t\theaders: request.headers,\n\t\t\t\tthrow: true\n\t\t\t}\n\t\t})\n\t\treturn next({\n\t\t\tcontext: { session },\n\t\t});\n\t},\n);\n{{/if}}\n"
  },
  {
    "path": "packages/template-generator/templates/auth/better-auth/web/react/tanstack-start/src/routes/dashboard.tsx.hbs",
    "content": "import { getUser } from \"@/functions/get-user\";\n{{#if (eq payments \"polar\") }}\nimport { Button } from \"@{{projectName}}/ui/components/button\";\nimport { authClient } from \"@/lib/auth-client\";\nimport { getPayment } from \"@/functions/get-payment\";\n{{/if}}\n{{#if (eq api \"trpc\") }}\nimport { useTRPC } from \"@/utils/trpc\";\nimport { useQuery } from \"@tanstack/react-query\";\n{{/if}}\n{{#if (eq api \"orpc\") }}\nimport { orpc } from \"@/utils/orpc\";\nimport { useQuery } from \"@tanstack/react-query\";\n{{/if}}\nimport { createFileRoute, redirect } from \"@tanstack/react-router\";\n\nexport const Route = createFileRoute(\"/dashboard\")({\n  component: RouteComponent,\n  beforeLoad: async () => {\n    const session = await getUser();\n    {{#if (eq payments \"polar\") }}\n    const customerState = await getPayment();\n    return { session, customerState };\n    {{else}}\n    return { session };\n    {{/if}}\n  },\n  loader: async ({ context }) => {\n    if (!context.session) {\n      throw redirect({\n        to: \"/login\",\n      });\n    }\n  },\n});\n\nfunction RouteComponent() {\n  const { session{{#if (eq payments \"polar\") }}, customerState{{/if}} } = Route.useRouteContext();\n\n  {{#if (eq api \"trpc\") }}\n  const trpc = useTRPC();\n  const privateData = useQuery(trpc.privateData.queryOptions());\n  {{/if}}\n  {{#if (eq api \"orpc\") }}\n  const privateData = useQuery(orpc.privateData.queryOptions());\n  {{/if}}\n\n  {{#if (eq payments \"polar\") }}\n  const hasProSubscription = (customerState?.activeSubscriptions?.length ?? 0) > 0;\n  // For debugging: console.log(\"Active subscriptions:\", customerState?.activeSubscriptions);\n  {{/if}}\n\n  return (\n    <div>\n      <h1>Dashboard</h1>\n      <p>Welcome {session?.user.name}</p>\n      {{#if (eq api \"trpc\") }}\n      <p>API: {privateData.data?.message}</p>\n      {{else if (eq api \"orpc\") }}\n      <p>API: {privateData.data?.message}</p>\n      {{/if}}\n      {{#if (eq payments \"polar\") }}\n      <p>Plan: {hasProSubscription ? \"Pro\" : \"Free\"}</p>\n      {hasProSubscription ? (\n        <Button\n          onClick={async function handlePortal() {\n            await authClient.customer.portal();\n          }}\n        >\n          Manage Subscription\n        </Button>\n      ) : (\n        <Button\n          onClick={async function handleUpgrade() {\n            await authClient.checkout({ slug: \"pro\" });\n          }}\n        >\n          Upgrade to Pro\n        </Button>\n      )}\n      {{/if}}\n    </div>\n  );\n}"
  },
  {
    "path": "packages/template-generator/templates/auth/better-auth/web/react/tanstack-start/src/routes/login.tsx.hbs",
    "content": "import SignInForm from \"@/components/sign-in-form\";\nimport SignUpForm from \"@/components/sign-up-form\";\nimport { createFileRoute } from \"@tanstack/react-router\";\nimport { useState } from \"react\";\n\nexport const Route = createFileRoute(\"/login\")({\n  component: RouteComponent,\n});\n\nfunction RouteComponent() {\n  const [showSignIn, setShowSignIn] = useState(false);\n\n  return showSignIn ? (\n    <SignInForm onSwitchToSignUp={() => setShowSignIn(false)} />\n  ) : (\n    <SignUpForm onSwitchToSignIn={() => setShowSignIn(true)} />\n  );\n}\n"
  },
  {
    "path": "packages/template-generator/templates/auth/better-auth/web/solid/src/components/sign-in-form.tsx.hbs",
    "content": "import { authClient } from \"@/lib/auth-client\";\nimport { createForm } from \"@tanstack/solid-form\";\nimport { useNavigate } from \"@tanstack/solid-router\";\nimport z from \"zod\";\nimport { For } from \"solid-js\";\n\nexport default function SignInForm({ onSwitchToSignUp }: { onSwitchToSignUp: () => void }) {\n  const navigate = useNavigate({\n    from: \"/\",\n  });\n\n  const form = createForm(() => ({\n    defaultValues: {\n      email: \"\",\n      password: \"\",\n    },\n    onSubmit: async ({ value }) => {\n      await authClient.signIn.email(\n        {\n          email: value.email,\n          password: value.password,\n        },\n        {\n          onSuccess: () => {\n            navigate({\n              to: \"/dashboard\",\n            });\n            console.log(\"Sign in successful\");\n          },\n          onError: (error) => {\n            console.error(error.error.message);\n          },\n        },\n      );\n    },\n    validators: {\n      onSubmit: z.object({\n        email: z.email(\"Invalid email address\"),\n        password: z.string().min(8, \"Password must be at least 8 characters\"),\n      }),\n    },\n  }));\n\n  return (\n    <div class=\"mx-auto w-full mt-10 max-w-md p-6\">\n      <h1 class=\"mb-6 text-center text-3xl font-bold\">Welcome Back</h1>\n\n      <form\n        onSubmit={(e) => {\n          e.preventDefault();\n          e.stopPropagation();\n          form.handleSubmit();\n        }}\n        class=\"space-y-4\"\n      >\n        <div>\n          <form.Field name=\"email\">\n            {(field) => (\n              <div class=\"space-y-2\">\n                <label for={field().name}>Email</label>\n                <input\n                  id={field().name}\n                  name={field().name}\n                  type=\"email\"\n                  value={field().state.value}\n                  onBlur={field().handleBlur}\n                  onInput={(e) => field().handleChange(e.currentTarget.value)}\n                  class=\"w-full rounded border p-2\"\n                />\n                <For each={field().state.meta.errors}>\n                  {(error) => <p class=\"text-sm text-red-600\">{error?.message}</p>}\n                </For>\n              </div>\n            )}\n          </form.Field>\n        </div>\n\n        <div>\n          <form.Field name=\"password\">\n            {(field) => (\n              <div class=\"space-y-2\">\n                <label for={field().name}>Password</label>\n                <input\n                  id={field().name}\n                  name={field().name}\n                  type=\"password\"\n                  value={field().state.value}\n                  onBlur={field().handleBlur}\n                  onInput={(e) => field().handleChange(e.currentTarget.value)}\n                  class=\"w-full rounded border p-2\"\n                />\n                <For each={field().state.meta.errors}>\n                  {(error) => <p class=\"text-sm text-red-600\">{error?.message}</p>}\n                </For>\n              </div>\n            )}\n          </form.Field>\n        </div>\n\n        <form.Subscribe>\n          {(state) => (\n            <button\n              type=\"submit\"\n              class=\"w-full rounded bg-indigo-600 p-2 text-white hover:bg-indigo-700 disabled:opacity-50\"\n              disabled={!state().canSubmit || state().isSubmitting}\n            >\n              {state().isSubmitting ? \"Submitting...\" : \"Sign In\"}\n            </button>\n          )}\n        </form.Subscribe>\n      </form>\n\n      <div class=\"mt-4 text-center\">\n        <button\n          type=\"button\"\n          onClick={onSwitchToSignUp}\n          class=\"text-sm text-indigo-600 hover:text-indigo-800 hover:underline\"\n        >\n          Need an account? Sign Up\n        </button>\n      </div>\n    </div>\n  );\n}\n"
  },
  {
    "path": "packages/template-generator/templates/auth/better-auth/web/solid/src/components/sign-up-form.tsx.hbs",
    "content": "import { authClient } from \"@/lib/auth-client\";\nimport { createForm } from \"@tanstack/solid-form\";\nimport { useNavigate } from \"@tanstack/solid-router\";\nimport z from \"zod\";\nimport { For } from \"solid-js\";\n\nexport default function SignUpForm({ onSwitchToSignIn }: { onSwitchToSignIn: () => void }) {\n  const navigate = useNavigate({\n    from: \"/\",\n  });\n\n  const form = createForm(() => ({\n    defaultValues: {\n      email: \"\",\n      password: \"\",\n      name: \"\",\n    },\n    onSubmit: async ({ value }) => {\n      await authClient.signUp.email(\n        {\n          email: value.email,\n          password: value.password,\n          name: value.name,\n        },\n        {\n          onSuccess: () => {\n            navigate({\n              to: \"/dashboard\",\n            });\n            console.log(\"Sign up successful\");\n          },\n          onError: (error) => {\n            console.error(error.error.message);\n          },\n        },\n      );\n    },\n    validators: {\n      onSubmit: z.object({\n        name: z.string().min(2, \"Name must be at least 2 characters\"),\n        email: z.email(\"Invalid email address\"),\n        password: z.string().min(8, \"Password must be at least 8 characters\"),\n      }),\n    },\n  }));\n\n  return (\n    <div class=\"mx-auto w-full mt-10 max-w-md p-6\">\n      <h1 class=\"mb-6 text-center text-3xl font-bold\">Create Account</h1>\n\n      <form\n        onSubmit={(e) => {\n          e.preventDefault();\n          e.stopPropagation();\n          form.handleSubmit();\n        }}\n        class=\"space-y-4\"\n      >\n        <div>\n          <form.Field name=\"name\">\n            {(field) => (\n              <div class=\"space-y-2\">\n                <label for={field().name}>Name</label>\n                <input\n                  id={field().name}\n                  name={field().name}\n                  value={field().state.value}\n                  onBlur={field().handleBlur}\n                  onInput={(e) => field().handleChange(e.currentTarget.value)}\n                  class=\"w-full rounded border p-2\"\n                />\n                <For each={field().state.meta.errors}>\n                  {(error) => <p class=\"text-sm text-red-600\">{error?.message}</p>}\n                </For>\n              </div>\n            )}\n          </form.Field>\n        </div>\n\n        <div>\n          <form.Field name=\"email\">\n            {(field) => (\n              <div class=\"space-y-2\">\n                <label for={field().name}>Email</label>\n                <input\n                  id={field().name}\n                  name={field().name}\n                  type=\"email\"\n                  value={field().state.value}\n                  onBlur={field().handleBlur}\n                  onInput={(e) => field().handleChange(e.currentTarget.value)}\n                  class=\"w-full rounded border p-2\"\n                />\n                <For each={field().state.meta.errors}>\n                  {(error) => <p class=\"text-sm text-red-600\">{error?.message}</p>}\n                </For>\n              </div>\n            )}\n          </form.Field>\n        </div>\n\n        <div>\n          <form.Field name=\"password\">\n            {(field) => (\n              <div class=\"space-y-2\">\n                <label for={field().name}>Password</label>\n                <input\n                  id={field().name}\n                  name={field().name}\n                  type=\"password\"\n                  value={field().state.value}\n                  onBlur={field().handleBlur}\n                  onInput={(e) => field().handleChange(e.currentTarget.value)}\n                  class=\"w-full rounded border p-2\"\n                />\n                <For each={field().state.meta.errors}>\n                  {(error) => <p class=\"text-sm text-red-600\">{error?.message}</p>}\n                </For>\n              </div>\n            )}\n          </form.Field>\n        </div>\n\n        <form.Subscribe>\n          {(state) => (\n            <button\n              type=\"submit\"\n              class=\"w-full rounded bg-indigo-600 p-2 text-white hover:bg-indigo-700 disabled:opacity-50\"\n              disabled={!state().canSubmit || state().isSubmitting}\n            >\n              {state().isSubmitting ? \"Submitting...\" : \"Sign Up\"}\n            </button>\n          )}\n        </form.Subscribe>\n      </form>\n\n      <div class=\"mt-4 text-center\">\n        <button\n          type=\"button\"\n          onClick={onSwitchToSignIn}\n          class=\"text-sm text-indigo-600 hover:text-indigo-800 hover:underline\"\n        >\n          Already have an account? Sign In\n        </button>\n      </div>\n    </div>\n  );\n}\n"
  },
  {
    "path": "packages/template-generator/templates/auth/better-auth/web/solid/src/components/user-menu.tsx.hbs",
    "content": "import { authClient } from \"@/lib/auth-client\";\nimport { useNavigate, Link } from \"@tanstack/solid-router\";\nimport { createSignal, Show } from \"solid-js\";\n\nexport default function UserMenu() {\n  const navigate = useNavigate();\n  const session = authClient.useSession();\n  const [isMenuOpen, setIsMenuOpen] = createSignal(false);\n\n  return (\n    <div class=\"relative inline-block text-left\">\n      <Show when={session().isPending}>\n        <div class=\"h-9 w-24 animate-pulse rounded\" />\n      </Show>\n\n      <Show when={!session().isPending && !session().data}>\n        <Link to=\"/login\" class=\"inline-block border rounded px-4  text-sm\">\n          Sign In\n        </Link>\n      </Show>\n\n      <Show when={!session().isPending && session().data}>\n        <button\n          type=\"button\"\n          class=\"inline-block border rounded px-4  text-sm\"\n          onClick={() => setIsMenuOpen(!isMenuOpen())}\n        >\n          {session().data?.user.name}\n        </button>\n\n        <Show when={isMenuOpen()}>\n          <div class=\"absolute right-0 mt-2 w-56 rounded p-1 shadow-sm\">\n            <div class=\"px-4  text-sm\">{session().data?.user.email}</div>\n            <button\n              type=\"button\"\n              class=\"mt-1 w-full border rounded px-4  text-center text-sm\"\n              onClick={() => {\n                setIsMenuOpen(false);\n                authClient.signOut({\n                  fetchOptions: {\n                    onSuccess: () => {\n                      navigate({ to: \"/\" });\n                    },\n                  },\n                });\n              }}\n            >\n              Sign Out\n            </button>\n          </div>\n        </Show>\n      </Show>\n    </div>\n  );\n}\n"
  },
  {
    "path": "packages/template-generator/templates/auth/better-auth/web/solid/src/lib/auth-client.ts.hbs",
    "content": "import { createAuthClient } from \"better-auth/solid\";\n{{#if (eq payments \"polar\")}}\nimport { polarClient } from \"@polar-sh/better-auth\";\n{{/if}}\nimport { env } from \"@{{projectName}}/env/web\";\n\nexport const authClient = createAuthClient({\n\tbaseURL: env.VITE_SERVER_URL,\n{{#if (eq payments \"polar\")}}\n\tplugins: [polarClient()]\n{{/if}}\n});\n"
  },
  {
    "path": "packages/template-generator/templates/auth/better-auth/web/solid/src/routes/dashboard.tsx.hbs",
    "content": "import { authClient } from \"@/lib/auth-client\";\n{{#if (eq api \"orpc\")}}\nimport { orpc } from \"@/utils/orpc\";\nimport { useQuery } from \"@tanstack/solid-query\";\n{{/if}}\nimport { createFileRoute, redirect } from \"@tanstack/solid-router\";\n\nexport const Route = createFileRoute(\"/dashboard\")({\n\tcomponent: RouteComponent,\n\tbeforeLoad: async () => {\n\t\tconst session = await authClient.getSession();\n\t\tif (!session.data) {\n\t\t\tredirect({\n\t\t\t\tto: \"/login\",\n\t\t\t\tthrow: true,\n\t\t\t});\n\t\t}\n\t\t{{#if (eq payments \"polar\")}}\n\t\tconst { data: customerState } = await authClient.customer.state();\n\t\treturn { session, customerState };\n\t\t{{else}}\n\t\treturn { session };\n\t\t{{/if}}\n\t},\n});\n\nfunction RouteComponent() {\n\tconst context = Route.useRouteContext();\n\n\tconst session = context().session;\n\t{{#if (eq payments \"polar\")}}\n\tconst customerState = context().customerState;\n\t{{/if}}\n\n\t{{#if (eq api \"orpc\")}}\n\tconst privateData = useQuery(() => orpc.privateData.queryOptions());\n\t{{/if}}\n\n\t{{#if (eq payments \"polar\")}}\n\tconst hasProSubscription = () =>\n\t\tcustomerState?.activeSubscriptions?.length! > 0;\n\t{{/if}}\n\n\treturn (\n\t\t<div>\n\t\t\t<h1>Dashboard</h1>\n\t\t\t<p>Welcome {session.data?.user.name}</p>\n\t\t\t{{#if (eq api \"orpc\")}}\n\t\t\t<p>API: {privateData.data?.message}</p>\n\t\t\t{{/if}}\n\t\t\t{{#if (eq payments \"polar\")}}\n\t\t\t<p>Plan: {hasProSubscription() ? \"Pro\" : \"Free\"}</p>\n\t\t\t{hasProSubscription() ? (\n\t\t\t\t<button onClick={async () => await authClient.customer.portal()}>\n\t\t\t\t\tManage Subscription\n\t\t\t\t</button>\n\t\t\t) : (\n\t\t\t\t<button\n\t\t\t\t\tonClick={async () => await authClient.checkout({ slug: \"pro\" })}\n\t\t\t\t>\n\t\t\t\t\tUpgrade to Pro\n\t\t\t\t</button>\n\t\t\t)}\n\t\t\t{{/if}}\n\t\t</div>\n\t);\n}\n"
  },
  {
    "path": "packages/template-generator/templates/auth/better-auth/web/solid/src/routes/login.tsx.hbs",
    "content": "import SignInForm from \"@/components/sign-in-form\";\nimport SignUpForm from \"@/components/sign-up-form\";\nimport { createFileRoute } from \"@tanstack/solid-router\";\nimport { createSignal, Match, Switch } from \"solid-js\";\n\nexport const Route = createFileRoute(\"/login\")({\n  component: RouteComponent,\n});\n\nfunction RouteComponent() {\n  const [showSignIn, setShowSignIn] = createSignal(false);\n\n  return (\n    <Switch>\n      <Match when={showSignIn()}>\n        <SignInForm onSwitchToSignUp={() => setShowSignIn(false)} />\n      </Match>\n      <Match when={!showSignIn()}>\n        <SignUpForm onSwitchToSignIn={() => setShowSignIn(true)} />\n      </Match>\n    </Switch>\n  );\n}\n"
  },
  {
    "path": "packages/template-generator/templates/auth/better-auth/web/svelte/src/components/SignInForm.svelte.hbs",
    "content": "<script lang=\"ts\">\n\timport { createForm } from '@tanstack/svelte-form';\n\timport { z } from 'zod';\n\timport { authClient } from '$lib/auth-client';\n\timport { goto } from '$app/navigation';\n\n\tlet { switchToSignUp } = $props<{ switchToSignUp: () => void }>();\n\n\tconst validationSchema = z.object({\n\t\temail: z.email('Invalid email address'),\n\t\tpassword: z.string().min(1, 'Password is required'),\n\t});\n\n\tconst form = createForm(() => ({\n\t\tdefaultValues: { email: '', password: '' },\n\t\tonSubmit: async ({ value }) => {\n\t\t\t\tawait authClient.signIn.email(\n\t\t\t\t\t{ email: value.email, password: value.password },\n\t\t\t\t\t{\n\t\t\t\t\t\tonSuccess: () => goto('/dashboard'),\n\t\t\t\t\t\tonError: (error) => {\n\t\t\t\t\t\t\tconsole.log(error.error.message || 'Sign in failed. Please try again.');\n\t\t\t\t\t\t},\n\t\t\t\t\t}\n\t\t\t\t);\n\n\t\t},\n\t\tvalidators: {\n\t\t\tonSubmit: validationSchema,\n\t\t},\n\t}));\n\n\ttype SubmitState = Pick<typeof form.state, 'canSubmit' | 'isSubmitting'>;\n</script>\n\n<div class=\"mx-auto mt-10 w-full max-w-md p-6\">\n\t<h1 class=\"mb-6 text-center font-bold text-3xl\">Welcome Back</h1>\n\n\t<form\n\t\tclass=\"space-y-4\"\n\t\tonsubmit={(e) => {\n\t\t\te.preventDefault();\n\t\t\te.stopPropagation();\n\t\t\tform.handleSubmit();\n\t\t}}\n\t>\n\t\t<form.Field name=\"email\">\n\t\t\t{#snippet children(field)}\n\t\t\t\t<div class=\"space-y-1\">\n\t\t\t\t\t<label for={field.name}>Email</label>\n\t\t\t\t\t<input\n\t\t\t\t\t\tid={field.name}\n\t\t\t\t\t\tname={field.name}\n\t\t\t\t\t\ttype=\"email\"\n\t\t\t\t\t\tclass=\"w-full border\"\n\t\t\t\t\t\tonblur={field.handleBlur}\n\t\t\t\t\t\tvalue={field.state.value}\n\t\t\t\t\t\toninput={(e: Event) => {\n\t\t\t\t\t\t\tconst target = e.target as HTMLInputElement;\n\t\t\t\t\t\t\tfield.handleChange(target.value);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t\t{#if field.state.meta.isTouched}\n\t\t\t\t\t\t{#each field.state.meta.errors as error}\n\t\t\t\t\t\t\t<p class=\"text-sm text-red-500\" role=\"alert\">{error}</p>\n\t\t\t\t\t\t{/each}\n\t\t\t\t\t{/if}\n\t\t\t\t</div>\n\t\t\t{/snippet}\n\t\t</form.Field>\n\n\t\t<form.Field name=\"password\">\n\t\t\t{#snippet children(field)}\n\t\t\t\t<div class=\"space-y-1\">\n\t\t\t\t\t<label for={field.name}>Password</label>\n\t\t\t\t\t<input\n\t\t\t\t\t\tid={field.name}\n\t\t\t\t\t\tname={field.name}\n\t\t\t\t\t\ttype=\"password\"\n\t\t\t\t\t\tclass=\"w-full border\"\n\t\t\t\t\t\tonblur={field.handleBlur}\n\t\t\t\t\t\tvalue={field.state.value}\n\t\t\t\t\t\toninput={(e: Event) => {\n\t\t\t\t\t\t\tconst target = e.target as HTMLInputElement;\n\t\t\t\t\t\t\tfield.handleChange(target.value);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t\t{#if field.state.meta.isTouched}\n\t\t\t\t\t\t{#each field.state.meta.errors as error}\n\t\t\t\t\t\t\t<p class=\"text-sm text-red-500\" role=\"alert\">{error}</p>\n\t\t\t\t\t\t{/each}\n\t\t\t\t\t{/if}\n\t\t\t\t</div>\n\t\t\t{/snippet}\n\t\t</form.Field>\n\n\t\t<form.Subscribe selector={(state: typeof form.state): SubmitState => ({ canSubmit: state.canSubmit, isSubmitting: state.isSubmitting })}>\n\t\t\t{#snippet children(state: SubmitState)}\n\t\t\t\t<button type=\"submit\" class=\"w-full\" disabled={!state.canSubmit || state.isSubmitting}>\n\t\t\t\t\t{state.isSubmitting ? 'Submitting...' : 'Sign In'}\n\t\t\t\t</button>\n\t\t\t{/snippet}\n\t\t</form.Subscribe>\n\t</form>\n\n\t<div class=\"mt-4 text-center\">\n\t\t<button type=\"button\" class=\"text-indigo-600 hover:text-indigo-800\" onclick={switchToSignUp}>\n\t\t\tNeed an account? Sign Up\n\t\t</button>\n\t</div>\n</div>\n"
  },
  {
    "path": "packages/template-generator/templates/auth/better-auth/web/svelte/src/components/SignUpForm.svelte.hbs",
    "content": "<script lang=\"ts\">\n\timport { createForm } from '@tanstack/svelte-form';\n\timport { z } from 'zod';\n\timport { authClient } from '$lib/auth-client';\n\timport { goto } from '$app/navigation';\n\n\tlet { switchToSignIn } = $props<{ switchToSignIn: () => void }>();\n\n\tconst validationSchema = z.object({\n\t\tname: z.string().min(2, 'Name must be at least 2 characters'),\n\t\temail: z.email('Invalid email address'),\n\t\tpassword: z.string().min(8, 'Password must be at least 8 characters'),\n\t});\n\n\n\tconst form = createForm(() => ({\n\t\tdefaultValues: { name: '', email: '', password: '' },\n\t\tonSubmit: async ({ value }) => {\n\t\t\t\tawait authClient.signUp.email(\n\t\t\t\t\t{\n\t\t\t\t\t\temail: value.email,\n\t\t\t\t\t\tpassword: value.password,\n\t\t\t\t\t\tname: value.name,\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tonSuccess: () => {\n\t\t\t\t\t\t\tgoto('/dashboard');\n\t\t\t\t\t\t},\n\t\t\t\t\t\tonError: (error) => {\n\t\t\t\t\t\t\tconsole.log(error.error.message || 'Sign up failed. Please try again.');\n\t\t\t\t\t\t},\n\t\t\t\t\t}\n\t\t\t\t);\n\n\t\t},\n\t\tvalidators: {\n\t\t\tonSubmit: validationSchema,\n\t\t},\n\t}));\n\n\ttype SubmitState = Pick<typeof form.state, 'canSubmit' | 'isSubmitting'>;\n</script>\n\n<div class=\"mx-auto mt-10 w-full max-w-md p-6\">\n\t<h1 class=\"mb-6 text-center font-bold text-3xl\">Create Account</h1>\n\n\t<form\n\t\tid=\"form\"\n\t\tclass=\"space-y-4\"\n\t\tonsubmit={(e) => {\n\t\t\te.preventDefault();\n\t\t\te.stopPropagation();\n\t\t\tform.handleSubmit();\n\t\t}}\n\t>\n\t\t<form.Field name=\"name\">\n\t\t\t{#snippet children(field)}\n\t\t\t\t<div class=\"space-y-1\">\n\t\t\t\t\t<label for={field.name}>Name</label>\n\t\t\t\t\t<input\n\t\t\t\t\t\tid={field.name}\n\t\t\t\t\t\tname={field.name}\n\t\t\t\t\t\tclass=\"w-full border\"\n\t\t\t\t\t\tonblur={field.handleBlur}\n\t\t\t\t\t\tvalue={field.state.value}\n\t\t\t\t\t\toninput={(e: Event) => {\n\t\t\t\t\t\t\tconst target = e.target as HTMLInputElement;\n\t\t\t\t\t\t\tfield.handleChange(target.value);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t\t{#if field.state.meta.isTouched}\n\t\t\t\t\t\t{#each field.state.meta.errors as error}\n\t\t\t\t\t\t\t<p class=\"text-sm text-red-500\" role=\"alert\">{error}</p>\n\t\t\t\t\t\t{/each}\n\t\t\t\t\t{/if}\n\t\t\t\t</div>\n\t\t\t{/snippet}\n\t\t</form.Field>\n\n\t\t<form.Field name=\"email\">\n\t\t\t{#snippet children(field)}\n\t\t\t\t<div class=\"space-y-1\">\n\t\t\t\t\t<label for={field.name}>Email</label>\n\t\t\t\t\t<input\n\t\t\t\t\t\tid={field.name}\n\t\t\t\t\t\tname={field.name}\n\t\t\t\t\t\ttype=\"email\"\n\t\t\t\t\t\tclass=\"w-full border\"\n\t\t\t\t\t\tonblur={field.handleBlur}\n\t\t\t\t\t\tvalue={field.state.value}\n\t\t\t\t\t\toninput={(e: Event) => {\n\t\t\t\t\t\t\tconst target = e.target as HTMLInputElement;\n\t\t\t\t\t\t\tfield.handleChange(target.value);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t\t{#if field.state.meta.isTouched}\n\t\t\t\t\t\t{#each field.state.meta.errors as error}\n\t\t\t\t\t\t\t<p class=\"text-sm text-red-500\" role=\"alert\">{error}</p>\n\t\t\t\t\t\t{/each}\n\t\t\t\t\t{/if}\n\t\t\t\t</div>\n\t\t\t{/snippet}\n\t\t</form.Field>\n\n\t\t<form.Field name=\"password\">\n\t\t\t{#snippet children(field)}\n\t\t\t\t<div class=\"space-y-1\">\n\t\t\t\t\t<label for={field.name}>Password</label>\n\t\t\t\t\t<input\n\t\t\t\t\t\tid={field.name}\n\t\t\t\t\t\tname={field.name}\n\t\t\t\t\t\ttype=\"password\"\n\t\t\t\t\t\tclass=\"w-full border\"\n\t\t\t\t\t\tonblur={field.handleBlur}\n\t\t\t\t\t\tvalue={field.state.value}\n\t\t\t\t\t\toninput={(e: Event) => {\n\t\t\t\t\t\t\tconst target = e.target as HTMLInputElement;\n\t\t\t\t\t\t\tfield.handleChange(target.value);\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t\t{#if field.state.meta.isTouched}\n\t\t\t\t\t\t{#each field.state.meta.errors as error}\n\t\t\t\t\t\t\t<p class=\"text-sm text-red-500\" role=\"alert\">{error}</p>\n\t\t\t\t\t\t{/each}\n\t\t\t\t\t{/if}\n\t\t\t\t</div>\n\t\t\t{/snippet}\n\t\t</form.Field>\n\n\t\t<form.Subscribe selector={(state: typeof form.state): SubmitState => ({ canSubmit: state.canSubmit, isSubmitting: state.isSubmitting })}>\n\t\t\t{#snippet children(state: SubmitState)}\n\t\t\t\t<button type=\"submit\" class=\"w-full\" disabled={!state.canSubmit || state.isSubmitting}>\n\t\t\t\t\t{state.isSubmitting ? 'Submitting...' : 'Sign Up'}\n\t\t\t\t</button>\n\t\t\t{/snippet}\n\t\t</form.Subscribe>\n\t</form>\n\n\t<div class=\"mt-4 text-center\">\n\t\t<button type=\"button\" class=\"text-indigo-600 hover:text-indigo-800\" onclick={switchToSignIn}>\n\t\t\tAlready have an account? Sign In\n\t\t</button>\n\t</div>\n</div>\n"
  },
  {
    "path": "packages/template-generator/templates/auth/better-auth/web/svelte/src/components/UserMenu.svelte.hbs",
    "content": "<script lang=\"ts\">\n\timport { authClient } from '$lib/auth-client';\n\timport { goto } from '$app/navigation';\n\n\tconst sessionQuery = authClient.useSession();\n\n\tasync function handleSignOut() {\n\t\tawait authClient.signOut({\n\t\tfetchOptions: {\n\t\t\tonSuccess: () => {\n\t\t\t\tgoto('/');\n\t\t\t},\n\t\t\tonError: (error) => {\n\t\t\t\tconsole.error('Sign out failed:', error);\n\t\t\t}\n\t\t}\n\t\t});\n\t}\n\n\tfunction goToLogin() {\n\t\tgoto('/login');\n\t}\n\n</script>\n\n<div class=\"relative\">\n\t{#if $sessionQuery.isPending}\n\t\t<div class=\"h-8 w-24 animate-pulse rounded bg-neutral-700\"></div>\n\t{:else if $sessionQuery.data?.user}\n\t\t{@const user = $sessionQuery.data.user}\n\t\t<div class=\"flex items-center gap-3\">\n\t\t\t<span class=\"text-sm text-neutral-300 hidden sm:inline\" title={user.email}>\n\t\t\t\t{user.name || user.email?.split('@')[0] || 'User'}\n\t\t\t</span>\n\t\t\t<button\n\t\t\t\tonclick={handleSignOut}\n\t\t\t\tclass=\"rounded px-3 py-1 text-sm bg-red-600 hover:bg-red-700 text-white transition-colors\"\n\t\t\t>\n\t\t\t\tSign Out\n\t\t\t</button>\n\t\t</div>\n\t{:else}\n\t\t<div class=\"flex items-center gap-2\">\n\t\t\t<button\n\t\t\t\tonclick={goToLogin}\n\t\t\t\tclass=\"rounded px-3 py-1 text-sm bg-indigo-600 hover:bg-indigo-700 text-white transition-colors\"\n\t\t\t>\n\t\t\t\tSign In\n\t\t\t</button>\n\t\t</div>\n\t{/if}\n</div>\n"
  },
  {
    "path": "packages/template-generator/templates/auth/better-auth/web/svelte/src/lib/auth-client.ts.hbs",
    "content": "{{#unless (eq backend \"self\")}}\nimport { PUBLIC_SERVER_URL } from \"$env/static/public\";\n{{/unless}}\nimport { createAuthClient } from \"better-auth/svelte\";\n{{#if (eq payments \"polar\")}}\nimport { polarClient } from \"@polar-sh/better-auth\";\n{{/if}}\n\nexport const authClient = createAuthClient({\n{{#unless (eq backend \"self\")}}\n\tbaseURL: PUBLIC_SERVER_URL,\n{{/unless}}\n{{#if (eq payments \"polar\")}}\n\tplugins: [polarClient()]\n{{/if}}\n});\n"
  },
  {
    "path": "packages/template-generator/templates/auth/better-auth/web/svelte/src/routes/dashboard/+page.svelte.hbs",
    "content": "<script lang=\"ts\">\n\timport { goto } from '$app/navigation';\n\timport { authClient } from '$lib/auth-client';\n\t{{#if (eq api \"orpc\")}}\n\timport { orpc } from '$lib/orpc';\n\timport { createQuery } from '@tanstack/svelte-query';\n\t{{/if}}\n\t{{#if (eq payments \"polar\")}}\n\tlet customerState = $state<{ activeSubscriptions?: unknown[] } | null>(null);\n\t{{/if}}\n\n\tconst sessionQuery = authClient.useSession();\n\n\t{{#if (eq api \"orpc\")}}\n\tconst privateDataQuery = createQuery(orpc.privateData.queryOptions());\n\t{{/if}}\n\n\t$effect(() => {\n\t\tif (!$sessionQuery.isPending && !$sessionQuery.data) {\n\t\t\tgoto('/login');\n\t\t}\n\t});\n\n\t{{#if (eq payments \"polar\")}}\n\t$effect(() => {\n\t\tif ($sessionQuery.data) {\n\t\t\tauthClient.customer.state().then(({ data }) => {\n\t\t\t\tcustomerState = data;\n\t\t\t});\n\t\t}\n\t});\n\t{{/if}}\n</script>\n\n{#if $sessionQuery.isPending}\n\t<div>Loading...</div>\n{:else if !$sessionQuery.data}\n\t<div>Redirecting to login...</div>\n{:else}\n\t<div>\n\t\t<h1>Dashboard</h1>\n\t\t<p>Welcome {$sessionQuery.data.user.name}</p>\n\t\t{{#if (eq api \"orpc\")}}\n\t\t<p>API: {$privateDataQuery.data?.message}</p>\n\t\t{{/if}}\n\t\t{{#if (eq payments \"polar\")}}\n\t\t<p>Plan: {customerState?.activeSubscriptions?.length > 0 ? \"Pro\" : \"Free\"}</p>\n\t\t{#if customerState?.activeSubscriptions?.length > 0}\n\t\t\t<button onclick={async () => await authClient.customer.portal()}>\n\t\t\t\tManage Subscription\n\t\t\t</button>\n\t\t{:else}\n\t\t\t<button onclick={async () => await authClient.checkout({ slug: \"pro\" })}>\n\t\t\t\tUpgrade to Pro\n\t\t\t</button>\n\t\t{/if}\n\t\t{{/if}}\n\t</div>\n{/if}\n"
  },
  {
    "path": "packages/template-generator/templates/auth/better-auth/web/svelte/src/routes/login/+page.svelte.hbs",
    "content": "<script lang=\"ts\">\n\timport SignInForm from '../../components/SignInForm.svelte';\n\timport SignUpForm from '../../components/SignUpForm.svelte';\n\n\tlet showSignIn = $state(true);\n</script>\n\n{#if showSignIn}\n\t<SignInForm switchToSignUp={() => showSignIn = false} />\n{:else}\n\t<SignUpForm switchToSignIn={() => showSignIn = true} />\n{/if}\n"
  },
  {
    "path": "packages/template-generator/templates/auth/clerk/convex/backend/convex/auth.config.ts.hbs",
    "content": "export default {\n\tproviders: [\n\t\t{\n\t\t\t// Replace with your own Clerk Issuer URL from your \"convex\" JWT template\n\t\t\t// or with `process.env.CLERK_JWT_ISSUER_DOMAIN`\n\t\t\t// and configure CLERK_JWT_ISSUER_DOMAIN on the Convex Dashboard\n\t\t\t// See https://docs.convex.dev/auth/clerk#configuring-dev-and-prod-instances\n\t\t\tdomain: process.env.CLERK_JWT_ISSUER_DOMAIN,\n\t\t\tapplicationID: \"convex\",\n\t\t},\n\t],\n};\n"
  },
  {
    "path": "packages/template-generator/templates/auth/clerk/convex/backend/convex/privateData.ts.hbs",
    "content": "import { query } from \"./_generated/server\";\n\nexport const get = query({\n\targs: {},\n\thandler: async (ctx) => {\n\t\tconst identity = await ctx.auth.getUserIdentity();\n\t\tif (identity === null) {\n\t\t\treturn {\n\t\t\t\tmessage: \"Not authenticated\",\n\t\t\t};\n\t\t}\n\t\treturn {\n\t\t\tmessage: \"This is private\",\n\t\t};\n\t},\n});\n"
  },
  {
    "path": "packages/template-generator/templates/auth/clerk/convex/native/base/app/(auth)/_layout.tsx.hbs",
    "content": "import { Redirect, Stack } from \"expo-router\";\nimport { useAuth } from \"@clerk/expo\";\n\nexport default function AuthRoutesLayout() {\n  const { isLoaded, isSignedIn } = useAuth();\n\n  if (!isLoaded) {\n    return null;\n  }\n\n  if (isSignedIn) {\n    return <Redirect href={\"/\"} />;\n  }\n\n  return <Stack />;\n}\n"
  },
  {
    "path": "packages/template-generator/templates/auth/clerk/convex/native/base/app/(auth)/sign-in.tsx.hbs",
    "content": "import { useSignIn } from \"@clerk/expo\";\nimport { type Href, Link, useRouter } from \"expo-router\";\nimport React from \"react\";\nimport { Pressable, StyleSheet, Text, TextInput, View } from \"react-native\";\n\nfunction pushDecoratedUrl(router: ReturnType<typeof useRouter>, decorateUrl: (url: string) => string, href: string) {\n  const url = decorateUrl(href);\n  const nextHref = url.startsWith(\"http\") ? new URL(url).pathname : url;\n  router.push(nextHref as Href);\n}\n\nexport default function Page() {\n  const { signIn, errors, fetchStatus } = useSignIn();\n  const router = useRouter();\n  const [emailAddress, setEmailAddress] = React.useState(\"\");\n  const [password, setPassword] = React.useState(\"\");\n  const [code, setCode] = React.useState(\"\");\n  const [statusMessage, setStatusMessage] = React.useState<string | null>(null);\n\n  const emailCodeFactor = signIn.supportedSecondFactors.find(\n    (factor) => factor.strategy === \"email_code\",\n  );\n  const requiresEmailCode =\n    signIn.status === \"needs_client_trust\" ||\n    (signIn.status === \"needs_second_factor\" && !!emailCodeFactor);\n\n  const handleSubmit = async () => {\n    setStatusMessage(null);\n\n    const { error } = await signIn.password({\n      emailAddress,\n      password,\n    });\n\n    if (error) {\n      console.error(JSON.stringify(error, null, 2));\n      setStatusMessage(error.longMessage ?? \"Unable to sign in. Please try again.\");\n      return;\n    }\n\n    if (signIn.status === \"complete\") {\n      await signIn.finalize({\n        navigate: ({ session, decorateUrl }) => {\n          if (session?.currentTask) {\n            console.log(session.currentTask);\n            return;\n          }\n\n          pushDecoratedUrl(router, decorateUrl, \"/\");\n        },\n      });\n    } else if (signIn.status === \"needs_second_factor\" || signIn.status === \"needs_client_trust\") {\n      if (emailCodeFactor) {\n        await signIn.mfa.sendEmailCode();\n        setStatusMessage(`We sent a verification code to ${emailCodeFactor.safeIdentifier}.`);\n      } else {\n        console.error(\"Second factor is required, but email_code is not available:\", signIn);\n        setStatusMessage(\"A second factor is required, but this screen only supports email codes right now.\");\n      }\n    } else {\n      console.error(\"Sign-in attempt not complete:\", signIn);\n      setStatusMessage(\"Sign-in could not be completed. Check the logs for more details.\");\n    }\n  };\n\n  const handleVerify = async () => {\n    setStatusMessage(null);\n\n    await signIn.mfa.verifyEmailCode({ code });\n\n    if (signIn.status === \"complete\") {\n      await signIn.finalize({\n        navigate: ({ session, decorateUrl }) => {\n          if (session?.currentTask) {\n            console.log(session.currentTask);\n            return;\n          }\n\n          pushDecoratedUrl(router, decorateUrl, \"/\");\n        },\n      });\n    } else {\n      console.error(\"Sign-in attempt not complete:\", signIn);\n      setStatusMessage(\"That code did not complete sign-in. Please try again.\");\n    }\n  };\n\n  if (requiresEmailCode) {\n    return (\n      <View style={styles.container}>\n        <Text style={styles.title}>Verify your account</Text>\n        {statusMessage && <Text style={styles.helper}>{statusMessage}</Text>}\n        <TextInput\n          style={styles.input}\n          value={code}\n          placeholder=\"Enter your verification code\"\n          placeholderTextColor=\"#666666\"\n          onChangeText={(value) => setCode(value)}\n          keyboardType=\"numeric\"\n        />\n        {errors.fields.code && <Text style={styles.error}>{errors.fields.code.message}</Text>}\n        <Pressable\n          style={({ pressed }) => [\n            styles.button,\n            fetchStatus === \"fetching\" && styles.buttonDisabled,\n            pressed && styles.buttonPressed,\n          ]}\n          onPress={handleVerify}\n          disabled={fetchStatus === \"fetching\"}\n        >\n          <Text style={styles.buttonText}>Verify</Text>\n        </Pressable>\n        <Pressable\n          style={({ pressed }) => [styles.secondaryButton, pressed && styles.buttonPressed]}\n          onPress={() => signIn.mfa.sendEmailCode()}\n        >\n          <Text style={styles.secondaryButtonText}>I need a new code</Text>\n        </Pressable>\n      </View>\n    );\n  }\n\n  return (\n    <View style={styles.container}>\n      <Text style={styles.title}>Sign in</Text>\n      {statusMessage && <Text style={styles.helper}>{statusMessage}</Text>}\n      <Text style={styles.label}>Email address</Text>\n      <TextInput\n        style={styles.input}\n        autoCapitalize=\"none\"\n        value={emailAddress}\n        placeholder=\"Enter email\"\n        placeholderTextColor=\"#666666\"\n        onChangeText={(value) => setEmailAddress(value)}\n        keyboardType=\"email-address\"\n      />\n      {errors.fields.identifier && <Text style={styles.error}>{errors.fields.identifier.message}</Text>}\n      <Text style={styles.label}>Password</Text>\n      <TextInput\n        style={styles.input}\n        value={password}\n        placeholder=\"Enter password\"\n        placeholderTextColor=\"#666666\"\n        secureTextEntry={true}\n        onChangeText={(value) => setPassword(value)}\n      />\n      {errors.fields.password && <Text style={styles.error}>{errors.fields.password.message}</Text>}\n      <Pressable\n        style={({ pressed }) => [\n          styles.button,\n          (!emailAddress || !password || fetchStatus === \"fetching\") && styles.buttonDisabled,\n          pressed && styles.buttonPressed,\n        ]}\n        onPress={handleSubmit}\n        disabled={!emailAddress || !password || fetchStatus === \"fetching\"}\n      >\n        <Text style={styles.buttonText}>Sign in</Text>\n      </Pressable>\n      <View style={styles.linkContainer}>\n        <Text>Don't have an account? </Text>\n        <Link href=\"/sign-up\">\n          <Text style={styles.linkText}>Sign up</Text>\n        </Link>\n      </View>\n    </View>\n  );\n}\n\nconst styles = StyleSheet.create({\n  container: {\n    flex: 1,\n    padding: 20,\n    gap: 12,\n  },\n  title: {\n    marginBottom: 8,\n    fontSize: 24,\n    fontWeight: \"700\",\n  },\n  label: {\n    fontWeight: \"600\",\n    fontSize: 14,\n  },\n  input: {\n    borderWidth: 1,\n    borderColor: \"#ccc\",\n    borderRadius: 8,\n    padding: 12,\n    fontSize: 16,\n    backgroundColor: \"#fff\",\n  },\n  button: {\n    backgroundColor: \"#0a7ea4\",\n    paddingVertical: 12,\n    paddingHorizontal: 24,\n    borderRadius: 8,\n    alignItems: \"center\",\n    marginTop: 8,\n  },\n  buttonPressed: {\n    opacity: 0.7,\n  },\n  buttonDisabled: {\n    opacity: 0.5,\n  },\n  buttonText: {\n    color: \"#fff\",\n    fontWeight: \"600\",\n  },\n  secondaryButton: {\n    paddingVertical: 12,\n    paddingHorizontal: 24,\n    borderRadius: 8,\n    alignItems: \"center\",\n    marginTop: 8,\n  },\n  secondaryButtonText: {\n    color: \"#0a7ea4\",\n    fontWeight: \"600\",\n  },\n  linkContainer: {\n    flexDirection: \"row\",\n    gap: 4,\n    marginTop: 12,\n    alignItems: \"center\",\n  },\n  linkText: {\n    color: \"#0a7ea4\",\n    fontWeight: \"600\",\n  },\n  error: {\n    color: \"#d32f2f\",\n    fontSize: 12,\n    marginTop: -8,\n  },\n  helper: {\n    color: \"#555555\",\n    fontSize: 13,\n  },\n});\n"
  },
  {
    "path": "packages/template-generator/templates/auth/clerk/convex/native/base/app/(auth)/sign-up.tsx.hbs",
    "content": "import { useAuth, useSignUp } from \"@clerk/expo\";\nimport { type Href, Link, useRouter } from \"expo-router\";\nimport React from \"react\";\nimport { Pressable, StyleSheet, Text, TextInput, View } from \"react-native\";\n\nfunction pushDecoratedUrl(router: ReturnType<typeof useRouter>, decorateUrl: (url: string) => string, href: string) {\n  const url = decorateUrl(href);\n  const nextHref = url.startsWith(\"http\") ? new URL(url).pathname : url;\n  router.push(nextHref as Href);\n}\n\nexport default function Page() {\n  const { signUp, errors, fetchStatus } = useSignUp();\n  const { isSignedIn } = useAuth();\n  const router = useRouter();\n  const [emailAddress, setEmailAddress] = React.useState(\"\");\n  const [password, setPassword] = React.useState(\"\");\n  const [code, setCode] = React.useState(\"\");\n  const [statusMessage, setStatusMessage] = React.useState<string | null>(null);\n\n  const handleSubmit = async () => {\n    setStatusMessage(null);\n\n    const { error } = await signUp.password({\n      emailAddress,\n      password,\n    });\n\n    if (error) {\n      console.error(JSON.stringify(error, null, 2));\n      setStatusMessage(error.longMessage ?? \"Unable to sign up. Please try again.\");\n      return;\n    }\n\n    await signUp.verifications.sendEmailCode();\n    setStatusMessage(`We sent a verification code to ${emailAddress}.`);\n  };\n\n  const handleVerify = async () => {\n    setStatusMessage(null);\n\n    await signUp.verifications.verifyEmailCode({\n      code,\n    });\n\n    if (signUp.status === \"complete\") {\n      await signUp.finalize({\n        navigate: ({ session, decorateUrl }) => {\n          if (session?.currentTask) {\n            console.log(session.currentTask);\n            return;\n          }\n\n          pushDecoratedUrl(router, decorateUrl, \"/\");\n        },\n      });\n    } else {\n      console.error(\"Sign-up attempt not complete:\", signUp);\n      setStatusMessage(\"That code did not complete sign-up. Please try again.\");\n    }\n  };\n\n  if (signUp.status === \"complete\" || isSignedIn) {\n    return null;\n  }\n\n  if (\n    signUp.status === \"missing_requirements\" &&\n    signUp.unverifiedFields.includes(\"email_address\") &&\n    signUp.missingFields.length === 0\n  ) {\n    return (\n      <View style={styles.container}>\n        <Text style={styles.title}>Verify your account</Text>\n        {statusMessage && <Text style={styles.helper}>{statusMessage}</Text>}\n        <TextInput\n          style={styles.input}\n          value={code}\n          placeholder=\"Enter your verification code\"\n          placeholderTextColor=\"#666666\"\n          onChangeText={(value) => setCode(value)}\n          keyboardType=\"numeric\"\n        />\n        {errors.fields.code && <Text style={styles.error}>{errors.fields.code.message}</Text>}\n        <Pressable\n          style={({ pressed }) => [\n            styles.button,\n            fetchStatus === \"fetching\" && styles.buttonDisabled,\n            pressed && styles.buttonPressed,\n          ]}\n          onPress={handleVerify}\n          disabled={fetchStatus === \"fetching\"}\n        >\n          <Text style={styles.buttonText}>Verify</Text>\n        </Pressable>\n        <Pressable\n          style={({ pressed }) => [styles.secondaryButton, pressed && styles.buttonPressed]}\n          onPress={() => signUp.verifications.sendEmailCode()}\n        >\n          <Text style={styles.secondaryButtonText}>I need a new code</Text>\n        </Pressable>\n      </View>\n    );\n  }\n\n  return (\n    <View style={styles.container}>\n      <Text style={styles.title}>Sign up</Text>\n      {statusMessage && <Text style={styles.helper}>{statusMessage}</Text>}\n      <Text style={styles.label}>Email address</Text>\n      <TextInput\n        style={styles.input}\n        autoCapitalize=\"none\"\n        value={emailAddress}\n        placeholder=\"Enter email\"\n        placeholderTextColor=\"#666666\"\n        onChangeText={(value) => setEmailAddress(value)}\n        keyboardType=\"email-address\"\n      />\n      {errors.fields.emailAddress && (\n        <Text style={styles.error}>{errors.fields.emailAddress.message}</Text>\n      )}\n      <Text style={styles.label}>Password</Text>\n      <TextInput\n        style={styles.input}\n        value={password}\n        placeholder=\"Enter password\"\n        placeholderTextColor=\"#666666\"\n        secureTextEntry={true}\n        onChangeText={(value) => setPassword(value)}\n      />\n      {errors.fields.password && <Text style={styles.error}>{errors.fields.password.message}</Text>}\n      <Pressable\n        style={({ pressed }) => [\n          styles.button,\n          (!emailAddress || !password || fetchStatus === \"fetching\") && styles.buttonDisabled,\n          pressed && styles.buttonPressed,\n        ]}\n        onPress={handleSubmit}\n        disabled={!emailAddress || !password || fetchStatus === \"fetching\"}\n      >\n        <Text style={styles.buttonText}>Sign up</Text>\n      </Pressable>\n      <View style={styles.linkContainer}>\n        <Text>Already have an account? </Text>\n        <Link href=\"/sign-in\">\n          <Text style={styles.linkText}>Sign in</Text>\n        </Link>\n      </View>\n      <View nativeID=\"clerk-captcha\" />\n    </View>\n  );\n}\n\nconst styles = StyleSheet.create({\n  container: {\n    flex: 1,\n    padding: 20,\n    gap: 12,\n  },\n  title: {\n    marginBottom: 8,\n    fontSize: 24,\n    fontWeight: \"700\",\n  },\n  label: {\n    fontWeight: \"600\",\n    fontSize: 14,\n  },\n  input: {\n    borderWidth: 1,\n    borderColor: \"#ccc\",\n    borderRadius: 8,\n    padding: 12,\n    fontSize: 16,\n    backgroundColor: \"#fff\",\n  },\n  button: {\n    backgroundColor: \"#0a7ea4\",\n    paddingVertical: 12,\n    paddingHorizontal: 24,\n    borderRadius: 8,\n    alignItems: \"center\",\n    marginTop: 8,\n  },\n  buttonPressed: {\n    opacity: 0.7,\n  },\n  buttonDisabled: {\n    opacity: 0.5,\n  },\n  buttonText: {\n    color: \"#fff\",\n    fontWeight: \"600\",\n  },\n  secondaryButton: {\n    paddingVertical: 12,\n    paddingHorizontal: 24,\n    borderRadius: 8,\n    alignItems: \"center\",\n    marginTop: 8,\n  },\n  secondaryButtonText: {\n    color: \"#0a7ea4\",\n    fontWeight: \"600\",\n  },\n  linkContainer: {\n    flexDirection: \"row\",\n    gap: 4,\n    marginTop: 12,\n    alignItems: \"center\",\n  },\n  linkText: {\n    color: \"#0a7ea4\",\n    fontWeight: \"600\",\n  },\n  error: {\n    color: \"#d32f2f\",\n    fontSize: 12,\n    marginTop: -8,\n  },\n  helper: {\n    color: \"#555555\",\n    fontSize: 13,\n  },\n});\n"
  },
  {
    "path": "packages/template-generator/templates/auth/clerk/convex/native/base/components/sign-out-button.tsx.hbs",
    "content": "import { useClerk } from \"@clerk/expo\";\nimport { useRouter } from \"expo-router\";\nimport { Text, TouchableOpacity } from \"react-native\";\n\nexport const SignOutButton = () => {\n  // Use `useClerk()` to access the `signOut()` function\n  const { signOut } = useClerk();\n  const router = useRouter();\n\n  const handleSignOut = async () => {\n    try {\n      await signOut();\n      // Redirect to your desired page\n      router.replace(\"/\");\n    } catch (err) {\n      // See https://clerk.com/docs/custom-flows/error-handling\n      // for more info on error handling\n      console.error(JSON.stringify(err, null, 2));\n    }\n  };\n\n  return (\n    <TouchableOpacity onPress={handleSignOut}>\n      <Text>Sign out</Text>\n    </TouchableOpacity>\n  );\n};\n"
  },
  {
    "path": "packages/template-generator/templates/auth/clerk/convex/web/react/next/src/app/dashboard/page.tsx.hbs",
    "content": "\"use client\";\n\nimport { api } from \"@{{projectName}}/backend/convex/_generated/api\";\nimport { SignInButton, UserButton, useUser } from \"@clerk/nextjs\";\nimport { Authenticated, AuthLoading, Unauthenticated, useQuery } from \"convex/react\";\n\nexport default function Dashboard() {\n  const user = useUser();\n  const privateData = useQuery(api.privateData.get);\n\n  return (\n    <>\n      <Authenticated>\n        <div>\n          <h1>Dashboard</h1>\n          <p>Welcome {user.user?.fullName}</p>\n          <p>privateData: {privateData?.message}</p>\n          <UserButton />\n        </div>\n      </Authenticated>\n      <Unauthenticated>\n        <SignInButton />\n      </Unauthenticated>\n      <AuthLoading>\n        <div>Loading...</div>\n      </AuthLoading>\n    </>\n  );\n}\n"
  },
  {
    "path": "packages/template-generator/templates/auth/clerk/convex/web/react/next/src/proxy.ts.hbs",
    "content": "import { clerkMiddleware } from \"@clerk/nextjs/server\";\n\nexport default clerkMiddleware();\n\nexport const config = {\n\tmatcher: [\n\t\t// Skip Next.js internals and all static files, unless found in search params\n\t\t\"/((?!_next|[^?]*\\\\.(?:html?|css|js(?!on)|jpe?g|webp|png|gif|svg|ttf|woff2?|ico|csv|docx?|xlsx?|zip|webmanifest)).*)\",\n\t\t// Always run for API routes\n\t\t\"/(api|trpc)(.*)\",\n\t],\n};\n"
  },
  {
    "path": "packages/template-generator/templates/auth/clerk/convex/web/react/react-router/src/routes/dashboard.tsx.hbs",
    "content": "import { SignInButton, UserButton, useUser } from \"@clerk/react-router\";\nimport { api } from \"@{{projectName}}/backend/convex/_generated/api\";\nimport {\n\tAuthenticated,\n\tAuthLoading,\n\tUnauthenticated,\n\tuseQuery,\n} from \"convex/react\";\n\nexport default function Dashboard() {\n\tconst privateData = useQuery(api.privateData.get);\n\tconst user = useUser();\n\n\treturn (\n\t\t<>\n\t\t\t<Authenticated>\n\t\t\t\t<div>\n\t\t\t\t\t<h1>Dashboard</h1>\n\t\t\t\t\t<p>Welcome {user.user?.fullName}</p>\n\t\t\t\t\t<p>privateData: {privateData?.message}</p>\n\t\t\t\t\t<UserButton />\n\t\t\t\t</div>\n\t\t\t</Authenticated>\n\t\t\t<Unauthenticated>\n\t\t\t\t<SignInButton />\n\t\t\t</Unauthenticated>\n\t\t\t<AuthLoading>\n\t\t\t\t<div>Loading...</div>\n\t\t\t</AuthLoading>\n\t\t</>\n\t);\n}\n"
  },
  {
    "path": "packages/template-generator/templates/auth/clerk/convex/web/react/tanstack-router/src/routes/dashboard.tsx.hbs",
    "content": "import { SignInButton, UserButton, useUser } from \"@clerk/react\";\nimport { api } from \"@{{projectName}}/backend/convex/_generated/api\";\nimport { createFileRoute } from \"@tanstack/react-router\";\nimport {\n\tAuthenticated,\n\tAuthLoading,\n\tUnauthenticated,\n\tuseQuery,\n} from \"convex/react\";\n\nexport const Route = createFileRoute(\"/dashboard\")({\n\tcomponent: RouteComponent,\n});\n\nfunction RouteComponent() {\n\tconst privateData = useQuery(api.privateData.get);\n\tconst user = useUser()\n\n\treturn (\n\t\t<>\n\t\t\t<Authenticated>\n\t\t\t\t<div>\n\t\t\t\t\t<h1>Dashboard</h1>\n\t\t\t\t\t<p>Welcome {user.user?.fullName}</p>\n\t\t\t\t\t<p>privateData: {privateData?.message}</p>\n\t\t\t\t\t<UserButton />\n\t\t\t\t</div>\n\t\t\t</Authenticated>\n\t\t\t<Unauthenticated>\n\t\t\t\t<SignInButton />\n\t\t\t</Unauthenticated>\n\t\t\t<AuthLoading>\n\t\t\t\t<div>Loading...</div>\n\t\t\t</AuthLoading>\n\t\t</>\n\t);\n}\n"
  },
  {
    "path": "packages/template-generator/templates/auth/clerk/convex/web/react/tanstack-start/src/routes/dashboard.tsx.hbs",
    "content": "import { SignInButton, UserButton, useUser } from \"@clerk/tanstack-react-start\";\nimport { api } from \"@{{projectName}}/backend/convex/_generated/api\";\nimport { createFileRoute } from \"@tanstack/react-router\";\nimport {\n\tAuthenticated,\n\tAuthLoading,\n\tUnauthenticated,\n\tuseQuery,\n} from \"convex/react\";\n\nexport const Route = createFileRoute(\"/dashboard\")({\n\tcomponent: RouteComponent,\n});\n\nfunction RouteComponent() {\n\tconst privateData = useQuery(api.privateData.get);\n\tconst user = useUser();\n\n\treturn (\n\t\t<>\n\t\t\t<Authenticated>\n\t\t\t\t<div>\n\t\t\t\t\t<h1>Dashboard</h1>\n\t\t\t\t\t<p>Welcome {user.user?.fullName}</p>\n\t\t\t\t\t<p>privateData: {privateData?.message}</p>\n\t\t\t\t\t<UserButton />\n\t\t\t\t</div>\n\t\t\t</Authenticated>\n\t\t\t<Unauthenticated>\n\t\t\t\t<SignInButton />\n\t\t\t</Unauthenticated>\n\t\t\t<AuthLoading>\n\t\t\t\t<div>Loading...</div>\n\t\t\t</AuthLoading>\n\t\t</>\n\t);\n}\n"
  },
  {
    "path": "packages/template-generator/templates/auth/clerk/convex/web/react/tanstack-start/src/start.ts.hbs",
    "content": "import { clerkMiddleware } from '@clerk/tanstack-react-start/server'\nimport { createStart } from '@tanstack/react-start'\n\nexport const startInstance = createStart(() => {\n\treturn {\n\t\trequestMiddleware: [clerkMiddleware()],\n\t}\n})"
  },
  {
    "path": "packages/template-generator/templates/auth/clerk/native/base/app/(auth)/_layout.tsx.hbs",
    "content": "import { Redirect, Stack } from \"expo-router\";\nimport { useAuth } from \"@clerk/expo\";\n\nexport default function AuthRoutesLayout() {\n  const { isLoaded, isSignedIn } = useAuth();\n\n  if (!isLoaded) {\n    return null;\n  }\n\n  if (isSignedIn) {\n    return <Redirect href={\"/\"} />;\n  }\n\n  return <Stack />;\n}\n"
  },
  {
    "path": "packages/template-generator/templates/auth/clerk/native/base/app/(auth)/sign-in.tsx.hbs",
    "content": "import { useSignIn } from \"@clerk/expo\";\nimport { type Href, Link, useRouter } from \"expo-router\";\nimport React from \"react\";\nimport { Pressable, StyleSheet, Text, TextInput, View } from \"react-native\";\n\nfunction pushDecoratedUrl(router: ReturnType<typeof useRouter>, decorateUrl: (url: string) => string, href: string) {\n  const url = decorateUrl(href);\n  const nextHref = url.startsWith(\"http\") ? new URL(url).pathname : url;\n  router.push(nextHref as Href);\n}\n\nexport default function Page() {\n  const { signIn, errors, fetchStatus } = useSignIn();\n  const router = useRouter();\n  const [emailAddress, setEmailAddress] = React.useState(\"\");\n  const [password, setPassword] = React.useState(\"\");\n  const [code, setCode] = React.useState(\"\");\n  const [statusMessage, setStatusMessage] = React.useState<string | null>(null);\n\n  const emailCodeFactor = signIn.supportedSecondFactors.find(\n    (factor) => factor.strategy === \"email_code\",\n  );\n  const requiresEmailCode =\n    signIn.status === \"needs_client_trust\" ||\n    (signIn.status === \"needs_second_factor\" && !!emailCodeFactor);\n\n  const handleSubmit = async () => {\n    setStatusMessage(null);\n\n    const { error } = await signIn.password({\n      emailAddress,\n      password,\n    });\n\n    if (error) {\n      console.error(JSON.stringify(error, null, 2));\n      setStatusMessage(error.longMessage ?? \"Unable to sign in. Please try again.\");\n      return;\n    }\n\n    if (signIn.status === \"complete\") {\n      await signIn.finalize({\n        navigate: ({ session, decorateUrl }) => {\n          if (session?.currentTask) {\n            console.log(session.currentTask);\n            return;\n          }\n\n          pushDecoratedUrl(router, decorateUrl, \"/\");\n        },\n      });\n    } else if (signIn.status === \"needs_second_factor\" || signIn.status === \"needs_client_trust\") {\n      if (emailCodeFactor) {\n        await signIn.mfa.sendEmailCode();\n        setStatusMessage(`We sent a verification code to ${emailCodeFactor.safeIdentifier}.`);\n      } else {\n        console.error(\"Second factor is required, but email_code is not available:\", signIn);\n        setStatusMessage(\"A second factor is required, but this screen only supports email codes right now.\");\n      }\n    } else {\n      console.error(\"Sign-in attempt not complete:\", signIn);\n      setStatusMessage(\"Sign-in could not be completed. Check the logs for more details.\");\n    }\n  };\n\n  const handleVerify = async () => {\n    setStatusMessage(null);\n\n    await signIn.mfa.verifyEmailCode({ code });\n\n    if (signIn.status === \"complete\") {\n      await signIn.finalize({\n        navigate: ({ session, decorateUrl }) => {\n          if (session?.currentTask) {\n            console.log(session.currentTask);\n            return;\n          }\n\n          pushDecoratedUrl(router, decorateUrl, \"/\");\n        },\n      });\n    } else {\n      console.error(\"Sign-in attempt not complete:\", signIn);\n      setStatusMessage(\"That code did not complete sign-in. Please try again.\");\n    }\n  };\n\n  if (requiresEmailCode) {\n    return (\n      <View style={styles.container}>\n        <Text style={styles.title}>Verify your account</Text>\n        {statusMessage && <Text style={styles.helper}>{statusMessage}</Text>}\n        <TextInput\n          style={styles.input}\n          value={code}\n          placeholder=\"Enter your verification code\"\n          placeholderTextColor=\"#666666\"\n          onChangeText={(value) => setCode(value)}\n          keyboardType=\"numeric\"\n        />\n        {errors.fields.code && <Text style={styles.error}>{errors.fields.code.message}</Text>}\n        <Pressable\n          style={({ pressed }) => [\n            styles.button,\n            fetchStatus === \"fetching\" && styles.buttonDisabled,\n            pressed && styles.buttonPressed,\n          ]}\n          onPress={handleVerify}\n          disabled={fetchStatus === \"fetching\"}\n        >\n          <Text style={styles.buttonText}>Verify</Text>\n        </Pressable>\n        <Pressable\n          style={({ pressed }) => [styles.secondaryButton, pressed && styles.buttonPressed]}\n          onPress={() => signIn.mfa.sendEmailCode()}\n        >\n          <Text style={styles.secondaryButtonText}>I need a new code</Text>\n        </Pressable>\n      </View>\n    );\n  }\n\n  return (\n    <View style={styles.container}>\n      <Text style={styles.title}>Sign in</Text>\n      {statusMessage && <Text style={styles.helper}>{statusMessage}</Text>}\n      <Text style={styles.label}>Email address</Text>\n      <TextInput\n        style={styles.input}\n        autoCapitalize=\"none\"\n        value={emailAddress}\n        placeholder=\"Enter email\"\n        placeholderTextColor=\"#666666\"\n        onChangeText={(value) => setEmailAddress(value)}\n        keyboardType=\"email-address\"\n      />\n      {errors.fields.identifier && <Text style={styles.error}>{errors.fields.identifier.message}</Text>}\n      <Text style={styles.label}>Password</Text>\n      <TextInput\n        style={styles.input}\n        value={password}\n        placeholder=\"Enter password\"\n        placeholderTextColor=\"#666666\"\n        secureTextEntry={true}\n        onChangeText={(value) => setPassword(value)}\n      />\n      {errors.fields.password && <Text style={styles.error}>{errors.fields.password.message}</Text>}\n      <Pressable\n        style={({ pressed }) => [\n          styles.button,\n          (!emailAddress || !password || fetchStatus === \"fetching\") && styles.buttonDisabled,\n          pressed && styles.buttonPressed,\n        ]}\n        onPress={handleSubmit}\n        disabled={!emailAddress || !password || fetchStatus === \"fetching\"}\n      >\n        <Text style={styles.buttonText}>Sign in</Text>\n      </Pressable>\n      <View style={styles.linkContainer}>\n        <Text>Don't have an account? </Text>\n        <Link href=\"/sign-up\">\n          <Text style={styles.linkText}>Sign up</Text>\n        </Link>\n      </View>\n    </View>\n  );\n}\n\nconst styles = StyleSheet.create({\n  container: {\n    flex: 1,\n    padding: 20,\n    gap: 12,\n  },\n  title: {\n    marginBottom: 8,\n    fontSize: 24,\n    fontWeight: \"700\",\n  },\n  label: {\n    fontWeight: \"600\",\n    fontSize: 14,\n  },\n  input: {\n    borderWidth: 1,\n    borderColor: \"#ccc\",\n    borderRadius: 8,\n    padding: 12,\n    fontSize: 16,\n    backgroundColor: \"#fff\",\n  },\n  button: {\n    backgroundColor: \"#0a7ea4\",\n    paddingVertical: 12,\n    paddingHorizontal: 24,\n    borderRadius: 8,\n    alignItems: \"center\",\n    marginTop: 8,\n  },\n  buttonPressed: {\n    opacity: 0.7,\n  },\n  buttonDisabled: {\n    opacity: 0.5,\n  },\n  buttonText: {\n    color: \"#fff\",\n    fontWeight: \"600\",\n  },\n  secondaryButton: {\n    paddingVertical: 12,\n    paddingHorizontal: 24,\n    borderRadius: 8,\n    alignItems: \"center\",\n    marginTop: 8,\n  },\n  secondaryButtonText: {\n    color: \"#0a7ea4\",\n    fontWeight: \"600\",\n  },\n  linkContainer: {\n    flexDirection: \"row\",\n    gap: 4,\n    marginTop: 12,\n    alignItems: \"center\",\n  },\n  linkText: {\n    color: \"#0a7ea4\",\n    fontWeight: \"600\",\n  },\n  error: {\n    color: \"#d32f2f\",\n    fontSize: 12,\n    marginTop: -8,\n  },\n  helper: {\n    color: \"#555555\",\n    fontSize: 13,\n  },\n});\n"
  },
  {
    "path": "packages/template-generator/templates/auth/clerk/native/base/app/(auth)/sign-up.tsx.hbs",
    "content": "import { useAuth, useSignUp } from \"@clerk/expo\";\nimport { type Href, Link, useRouter } from \"expo-router\";\nimport React from \"react\";\nimport { Pressable, StyleSheet, Text, TextInput, View } from \"react-native\";\n\nfunction pushDecoratedUrl(router: ReturnType<typeof useRouter>, decorateUrl: (url: string) => string, href: string) {\n  const url = decorateUrl(href);\n  const nextHref = url.startsWith(\"http\") ? new URL(url).pathname : url;\n  router.push(nextHref as Href);\n}\n\nexport default function Page() {\n  const { signUp, errors, fetchStatus } = useSignUp();\n  const { isSignedIn } = useAuth();\n  const router = useRouter();\n  const [emailAddress, setEmailAddress] = React.useState(\"\");\n  const [password, setPassword] = React.useState(\"\");\n  const [code, setCode] = React.useState(\"\");\n  const [statusMessage, setStatusMessage] = React.useState<string | null>(null);\n\n  const handleSubmit = async () => {\n    setStatusMessage(null);\n\n    const { error } = await signUp.password({\n      emailAddress,\n      password,\n    });\n\n    if (error) {\n      console.error(JSON.stringify(error, null, 2));\n      setStatusMessage(error.longMessage ?? \"Unable to sign up. Please try again.\");\n      return;\n    }\n\n    await signUp.verifications.sendEmailCode();\n    setStatusMessage(`We sent a verification code to ${emailAddress}.`);\n  };\n\n  const handleVerify = async () => {\n    setStatusMessage(null);\n\n    await signUp.verifications.verifyEmailCode({\n      code,\n    });\n\n    if (signUp.status === \"complete\") {\n      await signUp.finalize({\n        navigate: ({ session, decorateUrl }) => {\n          if (session?.currentTask) {\n            console.log(session.currentTask);\n            return;\n          }\n\n          pushDecoratedUrl(router, decorateUrl, \"/\");\n        },\n      });\n    } else {\n      console.error(\"Sign-up attempt not complete:\", signUp);\n      setStatusMessage(\"That code did not complete sign-up. Please try again.\");\n    }\n  };\n\n  if (signUp.status === \"complete\" || isSignedIn) {\n    return null;\n  }\n\n  if (\n    signUp.status === \"missing_requirements\" &&\n    signUp.unverifiedFields.includes(\"email_address\") &&\n    signUp.missingFields.length === 0\n  ) {\n    return (\n      <View style={styles.container}>\n        <Text style={styles.title}>Verify your account</Text>\n        {statusMessage && <Text style={styles.helper}>{statusMessage}</Text>}\n        <TextInput\n          style={styles.input}\n          value={code}\n          placeholder=\"Enter your verification code\"\n          placeholderTextColor=\"#666666\"\n          onChangeText={(value) => setCode(value)}\n          keyboardType=\"numeric\"\n        />\n        {errors.fields.code && <Text style={styles.error}>{errors.fields.code.message}</Text>}\n        <Pressable\n          style={({ pressed }) => [\n            styles.button,\n            fetchStatus === \"fetching\" && styles.buttonDisabled,\n            pressed && styles.buttonPressed,\n          ]}\n          onPress={handleVerify}\n          disabled={fetchStatus === \"fetching\"}\n        >\n          <Text style={styles.buttonText}>Verify</Text>\n        </Pressable>\n        <Pressable\n          style={({ pressed }) => [styles.secondaryButton, pressed && styles.buttonPressed]}\n          onPress={() => signUp.verifications.sendEmailCode()}\n        >\n          <Text style={styles.secondaryButtonText}>I need a new code</Text>\n        </Pressable>\n      </View>\n    );\n  }\n\n  return (\n    <View style={styles.container}>\n      <Text style={styles.title}>Sign up</Text>\n      {statusMessage && <Text style={styles.helper}>{statusMessage}</Text>}\n      <Text style={styles.label}>Email address</Text>\n      <TextInput\n        style={styles.input}\n        autoCapitalize=\"none\"\n        value={emailAddress}\n        placeholder=\"Enter email\"\n        placeholderTextColor=\"#666666\"\n        onChangeText={(value) => setEmailAddress(value)}\n        keyboardType=\"email-address\"\n      />\n      {errors.fields.emailAddress && (\n        <Text style={styles.error}>{errors.fields.emailAddress.message}</Text>\n      )}\n      <Text style={styles.label}>Password</Text>\n      <TextInput\n        style={styles.input}\n        value={password}\n        placeholder=\"Enter password\"\n        placeholderTextColor=\"#666666\"\n        secureTextEntry={true}\n        onChangeText={(value) => setPassword(value)}\n      />\n      {errors.fields.password && <Text style={styles.error}>{errors.fields.password.message}</Text>}\n      <Pressable\n        style={({ pressed }) => [\n          styles.button,\n          (!emailAddress || !password || fetchStatus === \"fetching\") && styles.buttonDisabled,\n          pressed && styles.buttonPressed,\n        ]}\n        onPress={handleSubmit}\n        disabled={!emailAddress || !password || fetchStatus === \"fetching\"}\n      >\n        <Text style={styles.buttonText}>Sign up</Text>\n      </Pressable>\n      <View style={styles.linkContainer}>\n        <Text>Already have an account? </Text>\n        <Link href=\"/sign-in\">\n          <Text style={styles.linkText}>Sign in</Text>\n        </Link>\n      </View>\n      <View nativeID=\"clerk-captcha\" />\n    </View>\n  );\n}\n\nconst styles = StyleSheet.create({\n  container: {\n    flex: 1,\n    padding: 20,\n    gap: 12,\n  },\n  title: {\n    marginBottom: 8,\n    fontSize: 24,\n    fontWeight: \"700\",\n  },\n  label: {\n    fontWeight: \"600\",\n    fontSize: 14,\n  },\n  input: {\n    borderWidth: 1,\n    borderColor: \"#ccc\",\n    borderRadius: 8,\n    padding: 12,\n    fontSize: 16,\n    backgroundColor: \"#fff\",\n  },\n  button: {\n    backgroundColor: \"#0a7ea4\",\n    paddingVertical: 12,\n    paddingHorizontal: 24,\n    borderRadius: 8,\n    alignItems: \"center\",\n    marginTop: 8,\n  },\n  buttonPressed: {\n    opacity: 0.7,\n  },\n  buttonDisabled: {\n    opacity: 0.5,\n  },\n  buttonText: {\n    color: \"#fff\",\n    fontWeight: \"600\",\n  },\n  secondaryButton: {\n    paddingVertical: 12,\n    paddingHorizontal: 24,\n    borderRadius: 8,\n    alignItems: \"center\",\n    marginTop: 8,\n  },\n  secondaryButtonText: {\n    color: \"#0a7ea4\",\n    fontWeight: \"600\",\n  },\n  linkContainer: {\n    flexDirection: \"row\",\n    gap: 4,\n    marginTop: 12,\n    alignItems: \"center\",\n  },\n  linkText: {\n    color: \"#0a7ea4\",\n    fontWeight: \"600\",\n  },\n  error: {\n    color: \"#d32f2f\",\n    fontSize: 12,\n    marginTop: -8,\n  },\n  helper: {\n    color: \"#555555\",\n    fontSize: 13,\n  },\n});\n"
  },
  {
    "path": "packages/template-generator/templates/auth/clerk/native/base/components/sign-out-button.tsx.hbs",
    "content": "import { useClerk } from \"@clerk/expo\";\nimport { useRouter } from \"expo-router\";\nimport { Text, TouchableOpacity } from \"react-native\";\n\nexport const SignOutButton = () => {\n  const { signOut } = useClerk();\n  const router = useRouter();\n\n  const handleSignOut = async () => {\n    try {\n      await signOut();\n      router.replace(\"/\");\n    } catch (err) {\n      console.error(JSON.stringify(err, null, 2));\n    }\n  };\n\n  return (\n    <TouchableOpacity onPress={handleSignOut}>\n      <Text>Sign out</Text>\n    </TouchableOpacity>\n  );\n};\n"
  },
  {
    "path": "packages/template-generator/templates/auth/clerk/native/base/utils/clerk-auth.ts.hbs",
    "content": "type ClerkTokenGetter = () => Promise<string | null>;\n\nlet clerkTokenGetter: ClerkTokenGetter | null = null;\n\nexport function setClerkAuthTokenGetter(getToken: ClerkTokenGetter | null) {\n\tclerkTokenGetter = getToken;\n}\n\nexport async function getClerkAuthToken() {\n\treturn (await clerkTokenGetter?.()) ?? null;\n}\n"
  },
  {
    "path": "packages/template-generator/templates/auth/clerk/web/react/base/src/utils/clerk-auth.ts.hbs",
    "content": "type ClerkTokenGetter = () => Promise<string | null>;\n\nlet clerkTokenGetter: ClerkTokenGetter | null = null;\n\nexport function setClerkAuthTokenGetter(getToken: ClerkTokenGetter | null) {\n\tclerkTokenGetter = getToken;\n}\n\nexport async function getClerkAuthToken() {\n\treturn (await clerkTokenGetter?.()) ?? null;\n}\n"
  },
  {
    "path": "packages/template-generator/templates/auth/clerk/web/react/next/src/app/dashboard/page.tsx.hbs",
    "content": "\"use client\";\n\n{{#if (eq api \"orpc\")}}\nimport { useQuery } from \"@tanstack/react-query\";\nimport { orpc } from \"@/utils/orpc\";\n{{/if}}\n{{#if (eq api \"trpc\")}}\nimport { useQuery } from \"@tanstack/react-query\";\nimport { trpc } from \"@/utils/trpc\";\n{{/if}}\nimport { SignInButton, UserButton, useUser } from \"@clerk/nextjs\";\n\nexport default function Dashboard() {\n  const user = useUser();\n  const nameFromParts = [user.user?.firstName, user.user?.lastName].filter(Boolean).join(\" \");\n  const displayName =\n    user.user?.fullName ||\n    nameFromParts ||\n    user.user?.username ||\n    user.user?.primaryEmailAddress?.emailAddress ||\n    user.user?.primaryPhoneNumber?.phoneNumber ||\n    \"User\";\n  {{#if (eq api \"orpc\")}}\n  const privateData = useQuery({\n    ...orpc.privateData.queryOptions(),\n    enabled: user.isLoaded && !!user.user,\n  });\n  {{/if}}\n  {{#if (eq api \"trpc\")}}\n  const privateData = useQuery({\n    ...trpc.privateData.queryOptions(),\n    enabled: user.isLoaded && !!user.user,\n  });\n  {{/if}}\n\n  if (!user.isLoaded) {\n    return <div className=\"p-6\">Loading...</div>;\n  }\n\n  if (!user.user) {\n    return (\n      <div className=\"p-6\">\n        <SignInButton />\n      </div>\n    );\n  }\n\n  return (\n    <div className=\"space-y-4 p-6\">\n      <h1 className=\"text-2xl font-semibold\">Dashboard</h1>\n      <p>Welcome {displayName}</p>\n      {{#if (or (eq api \"orpc\") (eq api \"trpc\"))}}\n      <p>API: {privateData.data?.message}</p>\n      {{/if}}\n      <UserButton />\n    </div>\n  );\n}\n"
  },
  {
    "path": "packages/template-generator/templates/auth/clerk/web/react/next/src/proxy.ts.hbs",
    "content": "import { clerkMiddleware } from \"@clerk/nextjs/server\";\n\nexport default clerkMiddleware();\n\nexport const config = {\n\tmatcher: [\n\t\t// Skip Next.js internals and all static files, unless found in search params\n\t\t\"/((?!_next|[^?]*\\\\.(?:html?|css|js(?!on)|jpe?g|webp|png|gif|svg|ttf|woff2?|ico|csv|docx?|xlsx?|zip|webmanifest)).*)\",\n\t\t// Always run for API routes\n\t\t\"/(api|trpc)(.*)\",\n\t],\n};\n"
  },
  {
    "path": "packages/template-generator/templates/auth/clerk/web/react/react-router/src/routes/dashboard.tsx.hbs",
    "content": "{{#if (eq api \"orpc\")}}\nimport { useQuery } from \"@tanstack/react-query\";\nimport { orpc } from \"@/utils/orpc\";\n{{/if}}\n{{#if (eq api \"trpc\")}}\nimport { useQuery } from \"@tanstack/react-query\";\nimport { trpc } from \"@/utils/trpc\";\n{{/if}}\nimport { SignInButton, UserButton, useUser } from \"@clerk/react-router\";\n\nexport default function Dashboard() {\n  const user = useUser();\n  const nameFromParts = [user.user?.firstName, user.user?.lastName].filter(Boolean).join(\" \");\n  const displayName =\n    user.user?.fullName ||\n    nameFromParts ||\n    user.user?.username ||\n    user.user?.primaryEmailAddress?.emailAddress ||\n    user.user?.primaryPhoneNumber?.phoneNumber ||\n    \"User\";\n  {{#if (eq api \"orpc\")}}\n  const privateData = useQuery({\n    ...orpc.privateData.queryOptions(),\n    enabled: user.isLoaded && !!user.user,\n  });\n  {{/if}}\n  {{#if (eq api \"trpc\")}}\n  const privateData = useQuery({\n    ...trpc.privateData.queryOptions(),\n    enabled: user.isLoaded && !!user.user,\n  });\n  {{/if}}\n\n  if (!user.isLoaded) {\n    return <div className=\"p-6\">Loading...</div>;\n  }\n\n  if (!user.user) {\n    return (\n      <div className=\"p-6\">\n        <SignInButton />\n      </div>\n    );\n  }\n\n  return (\n    <div className=\"space-y-4 p-6\">\n      <h1 className=\"text-2xl font-semibold\">Dashboard</h1>\n      <p>Welcome {displayName}</p>\n      {{#if (or (eq api \"orpc\") (eq api \"trpc\"))}}\n      <p>API: {privateData.data?.message}</p>\n      {{/if}}\n      <UserButton />\n    </div>\n  );\n}\n"
  },
  {
    "path": "packages/template-generator/templates/auth/clerk/web/react/tanstack-router/src/routes/dashboard.tsx.hbs",
    "content": "{{#if (eq api \"orpc\")}}\nimport { useQuery } from \"@tanstack/react-query\";\nimport { orpc } from \"@/utils/orpc\";\n{{/if}}\n{{#if (eq api \"trpc\")}}\nimport { useQuery } from \"@tanstack/react-query\";\nimport { trpc } from \"@/utils/trpc\";\n{{/if}}\nimport { SignInButton, UserButton, useUser } from \"@clerk/react\";\nimport { createFileRoute } from \"@tanstack/react-router\";\n\nexport const Route = createFileRoute(\"/dashboard\")({\n\tcomponent: RouteComponent,\n});\n\nfunction RouteComponent() {\n\tconst user = useUser();\n\tconst nameFromParts = [user.user?.firstName, user.user?.lastName].filter(Boolean).join(\" \");\n\tconst displayName =\n\t\tuser.user?.fullName ||\n\t\tnameFromParts ||\n\t\tuser.user?.username ||\n\t\tuser.user?.primaryEmailAddress?.emailAddress ||\n\t\tuser.user?.primaryPhoneNumber?.phoneNumber ||\n\t\t\"User\";\n\t{{#if (eq api \"orpc\")}}\n\tconst privateData = useQuery({\n\t\t...orpc.privateData.queryOptions(),\n\t\tenabled: user.isLoaded && !!user.user,\n\t});\n\t{{/if}}\n\t{{#if (eq api \"trpc\")}}\n\tconst privateData = useQuery({\n\t\t...trpc.privateData.queryOptions(),\n\t\tenabled: user.isLoaded && !!user.user,\n\t});\n\t{{/if}}\n\n\tif (!user.isLoaded) {\n\t\treturn <div className=\"p-6\">Loading...</div>;\n\t}\n\n\tif (!user.user) {\n\t\treturn (\n\t\t\t<div className=\"p-6\">\n\t\t\t\t<SignInButton />\n\t\t\t</div>\n\t\t);\n\t}\n\n\treturn (\n\t\t<div className=\"space-y-4 p-6\">\n\t\t\t<h1 className=\"text-2xl font-semibold\">Dashboard</h1>\n\t\t\t<p>Welcome {displayName}</p>\n\t\t\t{{#if (or (eq api \"orpc\") (eq api \"trpc\"))}}\n\t\t\t<p>API: {privateData.data?.message}</p>\n\t\t\t{{/if}}\n\t\t\t<UserButton />\n\t\t</div>\n\t);\n}\n"
  },
  {
    "path": "packages/template-generator/templates/auth/clerk/web/react/tanstack-start/src/routes/dashboard.tsx.hbs",
    "content": "{{#if (eq api \"trpc\")}}\nimport { useTRPC } from \"@/utils/trpc\";\nimport { useQuery } from \"@tanstack/react-query\";\n{{/if}}\n{{#if (eq api \"orpc\")}}\nimport { useQuery } from \"@tanstack/react-query\";\nimport { orpc } from \"@/utils/orpc\";\n{{/if}}\nimport { SignInButton, UserButton, useUser } from \"@clerk/tanstack-react-start\";\nimport { createFileRoute } from \"@tanstack/react-router\";\n\nexport const Route = createFileRoute(\"/dashboard\")({\n\tcomponent: RouteComponent,\n});\n\nfunction RouteComponent() {\n\tconst user = useUser();\n\tconst nameFromParts = [user.user?.firstName, user.user?.lastName].filter(Boolean).join(\" \");\n\tconst displayName =\n\t\tuser.user?.fullName ||\n\t\tnameFromParts ||\n\t\tuser.user?.username ||\n\t\tuser.user?.primaryEmailAddress?.emailAddress ||\n\t\tuser.user?.primaryPhoneNumber?.phoneNumber ||\n\t\t\"User\";\n\t{{#if (eq api \"trpc\")}}\n\tconst trpc = useTRPC();\n\tconst privateData = useQuery({\n\t\t...trpc.privateData.queryOptions(),\n\t\tenabled: user.isLoaded && !!user.user,\n\t});\n\t{{/if}}\n\t{{#if (eq api \"orpc\")}}\n\tconst privateData = useQuery({\n\t\t...orpc.privateData.queryOptions(),\n\t\tenabled: user.isLoaded && !!user.user,\n\t});\n\t{{/if}}\n\n\tif (!user.isLoaded) {\n\t\treturn <div className=\"p-6\">Loading...</div>;\n\t}\n\n\tif (!user.user) {\n\t\treturn (\n\t\t\t<div className=\"p-6\">\n\t\t\t\t<SignInButton />\n\t\t\t</div>\n\t\t);\n\t}\n\n\treturn (\n\t\t<div className=\"space-y-4 p-6\">\n\t\t\t<h1 className=\"text-2xl font-semibold\">Dashboard</h1>\n\t\t\t<p>Welcome {displayName}</p>\n\t\t\t{{#if (or (eq api \"orpc\") (eq api \"trpc\"))}}\n\t\t\t<p>API: {privateData.data?.message}</p>\n\t\t\t{{/if}}\n\t\t\t<UserButton />\n\t\t</div>\n\t);\n}\n"
  },
  {
    "path": "packages/template-generator/templates/auth/clerk/web/react/tanstack-start/src/start.ts.hbs",
    "content": "import { clerkMiddleware } from '@clerk/tanstack-react-start/server'\nimport { createStart } from '@tanstack/react-start'\n\nexport const startInstance = createStart(() => {\n\treturn {\n\t\trequestMiddleware: [clerkMiddleware()],\n\t}\n})\n"
  },
  {
    "path": "packages/template-generator/templates/backend/convex/packages/backend/_gitignore",
    "content": "\n.env.local\n"
  },
  {
    "path": "packages/template-generator/templates/backend/convex/packages/backend/convex/README.md",
    "content": "# Welcome to your Convex functions directory!\n\nWrite your Convex functions here.\nSee https://docs.convex.dev/functions for more.\n\nA query function that takes two arguments looks like:\n\n```ts\n// convex/myFunctions.ts\nimport { query } from \"./_generated/server\";\nimport { v } from \"convex/values\";\n\nexport const myQueryFunction = query({\n  // Validators for arguments.\n  args: {\n    first: v.number(),\n    second: v.string(),\n  },\n\n  // Function implementation.\n  handler: async (ctx, args) => {\n    // Read the database as many times as you need here.\n    // See https://docs.convex.dev/database/reading-data.\n    const documents = await ctx.db.query(\"tablename\").collect();\n\n    // Arguments passed from the client are properties of the args object.\n    console.log(args.first, args.second);\n\n    // Write arbitrary JavaScript here: filter, aggregate, build derived data,\n    // remove non-public properties, or create new objects.\n    return documents;\n  },\n});\n```\n\nUsing this query function in a React component looks like:\n\n```ts\nconst data = useQuery(api.myFunctions.myQueryFunction, {\n  first: 10,\n  second: \"hello\",\n});\n```\n\nA mutation function looks like:\n\n```ts\n// convex/myFunctions.ts\nimport { mutation } from \"./_generated/server\";\nimport { v } from \"convex/values\";\n\nexport const myMutationFunction = mutation({\n  // Validators for arguments.\n  args: {\n    first: v.string(),\n    second: v.string(),\n  },\n\n  // Function implementation.\n  handler: async (ctx, args) => {\n    // Insert or modify documents in the database here.\n    // Mutations can also read from the database like queries.\n    // See https://docs.convex.dev/database/writing-data.\n    const message = { body: args.first, author: args.second };\n    const id = await ctx.db.insert(\"messages\", message);\n\n    // Optionally, return a value from your mutation.\n    return await ctx.db.get(\"messages\", id);\n  },\n});\n```\n\nUsing this mutation function in a React component looks like:\n\n```ts\nconst mutation = useMutation(api.myFunctions.myMutationFunction);\nfunction handleButtonPress() {\n  // fire and forget, the most common way to use mutations\n  mutation({ first: \"Hello!\", second: \"me\" });\n  // OR\n  // use the result once the mutation has completed\n  mutation({ first: \"Hello!\", second: \"me\" }).then((result) => console.log(result));\n}\n```\n\nUse the Convex CLI to push your functions to a deployment. See everything\nthe Convex CLI can do by running `npx convex -h` in your project root\ndirectory. To learn more, launch the docs with `npx convex docs`.\n"
  },
  {
    "path": "packages/template-generator/templates/backend/convex/packages/backend/convex/convex.config.ts.hbs",
    "content": "import { defineApp } from \"convex/server\";\n{{#if (eq auth \"better-auth\")}}\nimport betterAuth from \"@convex-dev/better-auth/convex.config\";\n{{/if}}\n{{#if (includes examples \"ai\")}}\nimport agent from \"@convex-dev/agent/convex.config\";\n{{/if}}\n\nconst app = defineApp();\n{{#if (eq auth \"better-auth\")}}\napp.use(betterAuth);\n{{/if}}\n{{#if (includes examples \"ai\")}}\napp.use(agent);\n{{/if}}\n\nexport default app;\n"
  },
  {
    "path": "packages/template-generator/templates/backend/convex/packages/backend/convex/healthCheck.ts.hbs",
    "content": "import { query } from \"./_generated/server\";\n\nexport const get = query({\n  handler: async () => {\n    return \"OK\";\n  },\n});\n"
  },
  {
    "path": "packages/template-generator/templates/backend/convex/packages/backend/convex/schema.ts.hbs",
    "content": "import { defineSchema, defineTable } from \"convex/server\";\nimport { v } from \"convex/values\";\n\nexport default defineSchema({\n{{#if (includes examples \"todo\")}}\n  todos: defineTable({\n    text: v.string(),\n    completed: v.boolean(),\n  }),\n{{/if}}\n});\n"
  },
  {
    "path": "packages/template-generator/templates/backend/convex/packages/backend/convex/tsconfig.json.hbs",
    "content": "{\n  /* This TypeScript project config describes the environment that\n   * Convex functions run in and is used to typecheck them.\n   * You can modify it, but some settings are required to use Convex.\n   */\n  \"compilerOptions\": {\n    /* These settings are not required by Convex and can be modified. */\n    \"allowJs\": true,\n    \"strict\": true,\n    \"moduleResolution\": \"Bundler\",\n    \"jsx\": \"react-jsx\",\n    \"skipLibCheck\": true,\n    \"allowSyntheticDefaultImports\": true,\n\n    /* These compiler options are required by Convex */\n    \"target\": \"ESNext\",\n    \"lib\": [\"ES2021\", \"dom\"],\n    \"forceConsistentCasingInFileNames\": true,\n    \"module\": \"ESNext\",\n    \"isolatedModules\": true,\n    \"noEmit\": true\n  },\n  \"include\": [\"./**/*\"],\n  \"exclude\": [\"./_generated\"]\n}\n"
  },
  {
    "path": "packages/template-generator/templates/backend/convex/packages/backend/package.json.hbs",
    "content": "{\n  \"name\": \"@{{projectName}}/backend\",\n  \"version\": \"1.0.0\",\n  \"scripts\": {\n    \"dev\": \"convex dev\",\n    \"dev:setup\": \"convex dev --configure --until-success\"\n  },\n  \"author\": \"\",\n  \"license\": \"ISC\",\n  \"description\": \"\",\n  \"devDependencies\": {\n    \"@types/node\": \"^24.3.0\"\n  },\n  \"dependencies\": {}\n}\n"
  },
  {
    "path": "packages/template-generator/templates/backend/server/base/_gitignore",
    "content": "# prod\ndist/\n/build\n/out/\n\n# dev\n.yarn/\n!.yarn/patches\n!.yarn/plugins\n!.yarn/releases\n!.yarn/versions\n.vscode/*\n!.vscode/launch.json\n!.vscode/*.code-snippets\n.idea/workspace.xml\n.idea/usage.statistics.xml\n.idea/shelf\n.wrangler\n.alchemy\n/.next/\n.vercel\nprisma/generated/\n\n\n# deps\nnode_modules/\n/node_modules\n/.pnp\n.pnp.*\n\n# env\n.env*\n.env.production\n!.env.example\n.dev.vars\n\n# logs\nlogs/\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\npnpm-debug.log*\nlerna-debug.log*\n\n# misc\n.DS_Store\n*.pem\n\n# local db\n*.db*\n\n# typescript\n*.tsbuildinfo\nnext-env.d.ts\n"
  },
  {
    "path": "packages/template-generator/templates/backend/server/base/package.json.hbs",
    "content": "{\n\t\"name\": \"server\",\n\t\"main\": \"src/index.ts\",\n\t\"type\": \"module\",\n\t\"scripts\": {\n\t\t\"build\": \"tsdown\",\n\t\t\"check-types\": \"tsc -b\",\n\t\t\"compile\": \"bun build --compile --minify --sourcemap --bytecode ./src/index.ts --outfile server\"\n\t},\n\t\"dependencies\": {},\n\t{{#if (eq dbSetup 'supabase')}}\n\t\"trustedDependencies\": [\n        \"supabase\"\n    ],\n    {{/if}}\n\t\"devDependencies\": {}\n}\n"
  },
  {
    "path": "packages/template-generator/templates/backend/server/base/tsconfig.json.hbs",
    "content": "{\n  \"extends\": \"@{{projectName}}/config/tsconfig.base.json\",\n  \"compilerOptions\": {\n    \"composite\": true,\n\t\t\"outDir\": \"dist\",\n\t\t\"paths\": {\n      \"@/*\": [\"./src/*\"]\n    },\n    \"jsx\": \"react-jsx\"{{#if (eq backend \"hono\")}},\n    \"jsxImportSource\": \"hono/jsx\"{{/if}}\n  }\n}\n"
  },
  {
    "path": "packages/template-generator/templates/backend/server/base/tsdown.config.ts.hbs",
    "content": "import { defineConfig } from 'tsdown';\n\nexport default defineConfig({\n    entry: './src/index.ts',\n    format: 'esm',\n    outDir: './dist',\n    clean: true,\n    noExternal: [/@{{projectName}}\\/.*/]\n});\n"
  },
  {
    "path": "packages/template-generator/templates/backend/server/elysia/src/index.ts.hbs",
    "content": "import { env } from \"@{{projectName}}/env/server\";\n{{#if (eq runtime \"node\")}}\nimport { node } from \"@elysiajs/node\";\n{{/if}}\nimport { Elysia } from \"elysia\";\nimport { cors } from \"@elysiajs/cors\";\n{{#if (includes examples \"ai\")}}\nimport { google } from \"@ai-sdk/google\";\nimport { convertToModelMessages, streamText, type UIMessage, wrapLanguageModel } from \"ai\";\nimport { devToolsMiddleware } from \"@ai-sdk/devtools\";\n{{/if}}\n{{#if (eq api \"trpc\")}}\nimport { createContext } from \"@{{projectName}}/api/context\";\nimport { appRouter } from \"@{{projectName}}/api/routers/index\";\nimport { fetchRequestHandler } from \"@trpc/server/adapters/fetch\";\n{{/if}}\n{{#if (eq api \"orpc\")}}\nimport { OpenAPIHandler } from \"@orpc/openapi/fetch\";\nimport { OpenAPIReferencePlugin } from \"@orpc/openapi/plugins\";\nimport { ZodToJsonSchemaConverter } from \"@orpc/zod/zod4\";\nimport { RPCHandler } from \"@orpc/server/fetch\";\nimport { onError } from \"@orpc/server\";\nimport { appRouter } from \"@{{projectName}}/api/routers/index\";\nimport { createContext } from \"@{{projectName}}/api/context\";\n{{/if}}\n{{#if (eq auth \"better-auth\")}}\nimport { auth } from \"@{{projectName}}/auth\";\n{{/if}}\n\n{{#if (eq api \"orpc\")}}\nconst rpcHandler = new RPCHandler(appRouter, {\n\tinterceptors: [\n\t\tonError((error) => {\n\t\t\tconsole.error(error);\n\t\t}),\n\t],\n});\nconst apiHandler = new OpenAPIHandler(appRouter, {\n\tplugins: [\n\t\tnew OpenAPIReferencePlugin({\n\t\t\tschemaConverters: [new ZodToJsonSchemaConverter()],\n\t\t}),\n\t],\n\tinterceptors: [\n\t\tonError((error) => {\n\t\t\tconsole.error(error);\n\t\t}),\n\t],\n});\n{{/if}}\n\n{{#if (eq runtime \"node\")}}\nnew Elysia({ adapter: node() })\n{{else}}\nnew Elysia()\n{{/if}}\n\t.use(\n\t\tcors({\n\t\t\torigin: env.CORS_ORIGIN,\n\t\t\tmethods: [\"GET\", \"POST\", \"OPTIONS\"],\n{{#if (or (eq auth \"better-auth\") (eq auth \"clerk\"))}}\n\t\t\tallowedHeaders: [\"Content-Type\", \"Authorization\"],\n{{/if}}\n{{#if (eq auth \"better-auth\")}}\n\t\t\tcredentials: true,\n{{/if}}\n\t\t}),\n\t)\n{{#if (eq auth \"better-auth\")}}\n\t.all(\"/api/auth/*\", async (context) => {\n\t\tconst { request, status } = context;\n\t\tif ([\"POST\", \"GET\"].includes(request.method)) {\n\t\t\treturn auth.handler(request);\n\t\t}\n\t\treturn status(405)\n\t})\n{{/if}}\n{{#if (eq api \"orpc\")}}\n\t.all(\n\t\t\"/rpc*\",\n\t\tasync (context) => {\n\t\t\tconst { response } = await rpcHandler.handle(context.request, {\n\t\t\t\tprefix: \"/rpc\",\n\t\t\t\tcontext: await createContext({ context }),\n\t\t\t});\n\t\t\treturn response ?? new Response(\"Not Found\", { status: 404 });\n\t\t},\n\t\t{\n\t\t\tparse: \"none\",\n\t\t}\n\t)\n\t.all(\n\t\t\"/api-reference*\",\n\t\tasync (context) => {\n\t\t\tconst { response } = await apiHandler.handle(context.request, {\n\t\t\t\tprefix: \"/api-reference\",\n\t\t\t\tcontext: await createContext({ context }),\n\t\t\t});\n\t\t\treturn response ?? new Response(\"Not Found\", { status: 404 });\n\t\t},\n\t\t{\n\t\t\tparse: \"none\",\n\t\t}\n\t)\n{{/if}}\n{{#if (eq api \"trpc\")}}\n\t.all(\"/trpc/*\", async (context) => {\n\t\tconst res = await fetchRequestHandler({\n\t\t\tendpoint: \"/trpc\",\n\t\t\trouter: appRouter,\n\t\t\treq: context.request,\n\t\t\tcreateContext: () => createContext({ context }),\n\t\t});\n\t\treturn res;\n\t})\n{{/if}}\n{{#if (includes examples \"ai\")}}\n\t.post(\"/ai\", async (context) => {\n\t\tconst body = (await context.request.json()) as { messages?: UIMessage[] };\n\t\tconst uiMessages = body.messages || [];\n\t\tconst model = wrapLanguageModel({\n\t\t\tmodel: google(\"gemini-2.5-flash\"),\n\t\t\tmiddleware: devToolsMiddleware(),\n\t\t});\n\t\tconst result = streamText({\n\t\t\tmodel,\n\t\t\tmessages: await convertToModelMessages(uiMessages),\n\t\t});\n\n\t\treturn result.toUIMessageStreamResponse();\n\t})\n{{/if}}\n\t.get(\"/\", () => \"OK\")\n\t.listen(3000, () => {\n\t\tconsole.log(\"Server is running on http://localhost:3000\");\n\t});\n"
  },
  {
    "path": "packages/template-generator/templates/backend/server/express/src/index.ts.hbs",
    "content": "import { env } from \"@{{projectName}}/env/server\";\n{{#if (eq api \"trpc\")}}\nimport { createExpressMiddleware } from \"@trpc/server/adapters/express\";\nimport { createContext } from \"@{{projectName}}/api/context\";\nimport { appRouter } from \"@{{projectName}}/api/routers/index\";\n{{/if}}\n{{#if (eq api \"orpc\")}}\nimport { OpenAPIHandler } from \"@orpc/openapi/node\";\nimport { OpenAPIReferencePlugin } from \"@orpc/openapi/plugins\";\nimport { ZodToJsonSchemaConverter } from \"@orpc/zod/zod4\";\nimport { RPCHandler } from \"@orpc/server/node\";\nimport { onError } from \"@orpc/server\";\nimport { appRouter } from \"@{{projectName}}/api/routers/index\";\n{{#if (or (eq auth \"better-auth\") (eq auth \"clerk\"))}}\nimport { createContext } from \"@{{projectName}}/api/context\";\n{{/if}}\n{{/if}}\nimport cors from \"cors\";\nimport express from \"express\";\n{{#if (includes examples \"ai\")}}\nimport { streamText, type UIMessage, convertToModelMessages, wrapLanguageModel } from \"ai\";\nimport { google } from \"@ai-sdk/google\";\nimport { devToolsMiddleware } from \"@ai-sdk/devtools\";\n{{/if}}\n{{#if (eq auth \"better-auth\")}}\nimport { auth } from \"@{{projectName}}/auth\";\nimport { toNodeHandler } from \"better-auth/node\";\n{{/if}}\n{{#if (eq auth \"clerk\")}}\nimport { clerkMiddleware } from \"@clerk/express\";\n{{/if}}\n\nconst app = express();\n\napp.use(\n\tcors({\n\t\torigin: env.CORS_ORIGIN,\n\t\tmethods: [\"GET\", \"POST\", \"OPTIONS\"],\n{{#if (or (eq auth \"better-auth\") (eq auth \"clerk\"))}}\n\t\tallowedHeaders: [\"Content-Type\", \"Authorization\"],\n{{/if}}\n{{#if (eq auth \"better-auth\")}}\n\t\tcredentials: true,\n{{/if}}\n\t})\n);\n\n{{#if (eq auth \"clerk\")}}\napp.use(clerkMiddleware());\n{{/if}}\n\n{{#if (eq auth \"better-auth\")}}\napp.all(\"/api/auth{/*path}\", toNodeHandler(auth));\n{{/if}}\n\n{{#if (eq api \"trpc\")}}\napp.use(\n\t\"/trpc\",\n\tcreateExpressMiddleware({\n\t\trouter: appRouter,\n\t\tcreateContext,\n\t})\n);\n{{/if}}\n\n{{#if (eq api \"orpc\")}}\nconst rpcHandler = new RPCHandler(appRouter, {\n\tinterceptors: [\n\t\tonError((error) => {\n\t\t\tconsole.error(error);\n\t\t}),\n\t],\n});\nconst apiHandler = new OpenAPIHandler(appRouter, {\n\tplugins: [\n\t\tnew OpenAPIReferencePlugin({\n\t\t\tschemaConverters: [new ZodToJsonSchemaConverter()],\n\t\t}),\n\t],\n\tinterceptors: [\n\t\tonError((error) => {\n\t\t\tconsole.error(error);\n\t\t}),\n\t],\n});\n\napp.use(async (req, res, next) => {\n\tconst rpcResult = await rpcHandler.handle(req, res, {\n\t\tprefix: \"/rpc\",\n{{#if (or (eq auth \"better-auth\") (eq auth \"clerk\"))}}\n\t\tcontext: await createContext({ req }),\n{{else}}\n\t\tcontext: {},\n{{/if}}\n\t});\n\tif (rpcResult.matched) return;\n\n\tconst apiResult = await apiHandler.handle(req, res, {\n\t\tprefix: \"/api-reference\",\n{{#if (or (eq auth \"better-auth\") (eq auth \"clerk\"))}}\n\t\tcontext: await createContext({ req }),\n{{else}}\n\t\tcontext: {},\n{{/if}}\n\t});\n\tif (apiResult.matched) return;\n\n\tnext();\n});\n{{/if}}\n\napp.use(express.json());\n\n{{#if (includes examples \"ai\")}}\napp.post(\"/ai\", async (req, res) => {\n\tconst { messages = [] } = (req.body || {}) as { messages: UIMessage[] };\n\tconst model = wrapLanguageModel({\n\t\tmodel: google(\"gemini-2.5-flash\"),\n\t\tmiddleware: devToolsMiddleware(),\n\t});\n\tconst result = streamText({\n\t\tmodel,\n\t\tmessages: await convertToModelMessages(messages),\n\t});\n\tresult.pipeUIMessageStreamToResponse(res);\n});\n{{/if}}\n\napp.get(\"/\", (_req, res) => {\n\tres.status(200).send(\"OK\");\n});\n\napp.listen(3000, () => {\n\tconsole.log(\"Server is running on http://localhost:3000\");\n});\n"
  },
  {
    "path": "packages/template-generator/templates/backend/server/fastify/src/index.ts.hbs",
    "content": "import { env } from \"@{{projectName}}/env/server\";\nimport Fastify from \"fastify\";\nimport fastifyCors from \"@fastify/cors\";\n\n{{#if (eq api \"trpc\")}}\nimport { fastifyTRPCPlugin, type FastifyTRPCPluginOptions } from \"@trpc/server/adapters/fastify\";\nimport { createContext } from \"@{{projectName}}/api/context\";\nimport { appRouter, type AppRouter } from \"@{{projectName}}/api/routers/index\";\n{{/if}}\n\n{{#if (eq api \"orpc\")}}\nimport { OpenAPIHandler } from \"@orpc/openapi/fastify\";\nimport { OpenAPIReferencePlugin } from \"@orpc/openapi/plugins\";\nimport { ZodToJsonSchemaConverter } from \"@orpc/zod/zod4\";\nimport { RPCHandler } from \"@orpc/server/fastify\";\nimport { onError } from \"@orpc/server\";\nimport { createContext } from \"@{{projectName}}/api/context\";\nimport { appRouter } from \"@{{projectName}}/api/routers/index\";\n{{/if}}\n\n{{#if (includes examples \"ai\")}}\nimport { streamText, type UIMessage, convertToModelMessages, wrapLanguageModel } from \"ai\";\nimport { google } from \"@ai-sdk/google\";\nimport { devToolsMiddleware } from \"@ai-sdk/devtools\";\n{{/if}}\n\n{{#if (eq auth \"better-auth\")}}\nimport { auth } from \"@{{projectName}}/auth\";\n{{/if}}\n{{#if (eq auth \"clerk\")}}\nimport { clerkPlugin } from \"@clerk/fastify\";\n{{/if}}\n\nconst baseCorsConfig = {\n\torigin: env.CORS_ORIGIN,\n\tmethods: [\"GET\", \"POST\", \"PUT\", \"DELETE\", \"OPTIONS\"],\n\tallowedHeaders: [\n\t\t\"Content-Type\",\n\t\t\"Authorization\",\n\t\t\"X-Requested-With\"\n\t],\n\tcredentials: true,\n\tmaxAge: 86400,\n};\n\n{{#if (eq api \"orpc\")}}\nconst rpcHandler = new RPCHandler(appRouter, {\n\tinterceptors: [\n\t\tonError((error) => {\n\t\t\tconsole.error(error);\n\t\t}),\n\t],\n});\n\nconst apiHandler = new OpenAPIHandler(appRouter, {\n\tplugins: [\n\t\tnew OpenAPIReferencePlugin({\n\t\t\tschemaConverters: [new ZodToJsonSchemaConverter()],\n\t\t}),\n\t],\n\tinterceptors: [\n\t\tonError((error) => {\n\t\t\tconsole.error(error);\n\t\t}),\n\t],\n});\n\nconst fastify = Fastify({\n\tlogger: true,\n});\n{{else}}\nconst fastify = Fastify({\n\tlogger: true,\n});\n{{/if}}\n\nfastify.register(fastifyCors, baseCorsConfig);\n{{#if (eq auth \"clerk\")}}\nfastify.register(clerkPlugin);\n{{/if}}\n\n{{#if (eq api \"orpc\")}}\nfastify.register(async (rpcApp) => {\n\t// Fully utilize oRPC features by letting oRPC parse the request body.\n\trpcApp.addContentTypeParser(\"*\", (_, _payload, done) => {\n\t\tdone(null, undefined);\n\t});\n\n\trpcApp.all(\"/rpc/*\", async (request, reply) => {\n\t\tconst { matched } = await rpcHandler.handle(request, reply, {\n\t\t\tcontext: await createContext({{#if (eq auth \"clerk\")}}request{{else}}request.headers{{/if}}),\n\t\t\tprefix: \"/rpc\",\n\t\t});\n\n\t\tif (!matched) {\n\t\t\treply.status(404).send();\n\t\t}\n\t});\n\n\trpcApp.all(\"/api-reference/*\", async (request, reply) => {\n\t\tconst { matched } = await apiHandler.handle(request, reply, {\n\t\t\tcontext: await createContext({{#if (eq auth \"clerk\")}}request{{else}}request.headers{{/if}}),\n\t\t\tprefix: \"/api-reference\",\n\t\t});\n\n\t\tif (!matched) {\n\t\t\treply.status(404).send();\n\t\t}\n\t});\n});\n{{/if}}\n\n{{#if (eq auth \"better-auth\")}}\nfastify.route({\n\tmethod: [\"GET\", \"POST\"],\n\turl: \"/api/auth/*\",\n\tasync handler(request, reply) {\n\t\ttry {\n\t\t\tconst url = new URL(request.url, `http://${request.headers.host}`);\n\t\t\tconst headers = new Headers();\n\t\t\tObject.entries(request.headers).forEach(([key, value]) => {\n\t\t\t\tif (value) headers.append(key, value.toString());\n\t\t\t});\n\t\t\tconst req = new Request(url.toString(), {\n\t\t\t\tmethod: request.method,\n\t\t\t\theaders,\n\t\t\t\tbody: request.body ? JSON.stringify(request.body) : undefined,\n\t\t\t});\n\t\t\tconst response = await auth.handler(req);\n\t\t\treply.status(response.status);\n\t\t\tresponse.headers.forEach((value, key) => reply.header(key, value));\n\t\t\treply.send(response.body ? await response.text() : null);\n\t\t} catch (error) {\n\t\t\tfastify.log.error({ err: error }, \"Authentication Error:\");\n\t\t\treply.status(500).send({\n\t\t\t\terror: \"Internal authentication error\",\n\t\t\t\tcode: \"AUTH_FAILURE\"\n\t\t\t});\n\t\t}\n\t}\n});\n{{/if}}\n\n{{#if (eq api \"trpc\")}}\nfastify.register(fastifyTRPCPlugin, {\n\tprefix: \"/trpc\",\n\ttrpcOptions: {\n\t\trouter: appRouter,\n\t\tcreateContext,\n\t\tonError({ path, error }) {\n\t\t\tconsole.error(`Error in tRPC handler on path '${path}':`, error);\n\t\t},\n\t} satisfies FastifyTRPCPluginOptions<AppRouter>[\"trpcOptions\"],\n});\n{{/if}}\n\n{{#if (includes examples \"ai\")}}\ninterface AiRequestBody {\n\tid?: string;\n\tmessages: UIMessage[];\n}\n\nfastify.post('/ai', async function (request) {\n\tconst { messages } = request.body as AiRequestBody;\n\tconst model = wrapLanguageModel({\n\t\tmodel: google('gemini-2.5-flash'),\n\t\tmiddleware: devToolsMiddleware(),\n\t});\n\tconst result = streamText({\n\t\tmodel,\n\t\tmessages: await convertToModelMessages(messages),\n\t});\n\n\treturn result.toUIMessageStreamResponse();\n});\n{{/if}}\n\nfastify.get('/', async () => {\n\treturn 'OK';\n});\n\nfastify.listen({ port: 3000 }, (err) => {\n\tif (err) {\n\t\tfastify.log.error(err);\n\t\tprocess.exit(1);\n\t}\n\tconsole.log(\"Server running on port 3000\");\n});\n"
  },
  {
    "path": "packages/template-generator/templates/backend/server/hono/src/index.ts.hbs",
    "content": "import { env } from \"@{{projectName}}/env/server\";\n{{#if (eq api \"orpc\")}}\nimport { OpenAPIHandler } from \"@orpc/openapi/fetch\";\nimport { OpenAPIReferencePlugin } from \"@orpc/openapi/plugins\";\nimport { ZodToJsonSchemaConverter } from \"@orpc/zod/zod4\";\nimport { RPCHandler } from \"@orpc/server/fetch\";\nimport { onError } from \"@orpc/server\";\nimport { createContext } from \"@{{projectName}}/api/context\";\nimport { appRouter } from \"@{{projectName}}/api/routers/index\";\n{{/if}}\n{{#if (eq api \"trpc\")}}\nimport { trpcServer } from \"@hono/trpc-server\";\nimport { createContext } from \"@{{projectName}}/api/context\";\nimport { appRouter } from \"@{{projectName}}/api/routers/index\";\n{{/if}}\n{{#if (eq auth \"better-auth\")}}\n{{#if (or (eq runtime \"workers\") (eq serverDeploy \"cloudflare\") (and (eq backend \"self\") (eq webDeploy \"cloudflare\")))}}\nimport { createAuth } from \"@{{projectName}}/auth\";\n{{else}}\nimport { auth } from \"@{{projectName}}/auth\";\n{{/if}}\n{{/if}}\nimport { Hono } from \"hono\";\nimport { cors } from \"hono/cors\";\nimport { logger } from \"hono/logger\";\n{{#if (and (includes examples \"ai\") (or (eq runtime \"bun\") (eq runtime \"node\")))}}\nimport { streamText, convertToModelMessages, wrapLanguageModel } from \"ai\";\nimport { google } from \"@ai-sdk/google\";\nimport { devToolsMiddleware } from \"@ai-sdk/devtools\";\n{{/if}}\n{{#if (and (includes examples \"ai\") (eq runtime \"workers\"))}}\nimport { streamText, convertToModelMessages, wrapLanguageModel } from \"ai\";\nimport { createGoogleGenerativeAI } from \"@ai-sdk/google\";\nimport { devToolsMiddleware } from \"@ai-sdk/devtools\";\n{{/if}}\n\nconst app = new Hono();\n\napp.use(logger());\napp.use(\n\t\"/*\",\n\tcors({\n\t\torigin: env.CORS_ORIGIN,\n\t\tallowMethods: [\"GET\", \"POST\", \"OPTIONS\"],\n{{#if (or (eq auth \"better-auth\") (eq auth \"clerk\"))}}\n\t\tallowHeaders: [\"Content-Type\", \"Authorization\"],\n{{/if}}\n{{#if (eq auth \"better-auth\")}}\n\t\tcredentials: true,\n{{/if}}\n\t})\n);\n\n{{#if (eq auth \"better-auth\")}}\napp.on(\n\t[\"POST\", \"GET\"],\n\t\"/api/auth/*\",\n\t(c) =>\n{{#if (or (eq runtime \"workers\") (eq serverDeploy \"cloudflare\") (and (eq backend \"self\") (eq webDeploy \"cloudflare\")))}}\n\t\tcreateAuth().handler(c.req.raw)\n{{else}}\n\t\tauth.handler(c.req.raw)\n{{/if}}\n);\n{{/if}}\n\n{{#if (eq api \"orpc\")}}\nexport const apiHandler = new OpenAPIHandler(appRouter, {\n\tplugins: [\n\t\tnew OpenAPIReferencePlugin({\n\t\t\tschemaConverters: [new ZodToJsonSchemaConverter()],\n\t\t}),\n\t],\n\tinterceptors: [\n\t\tonError((error) => {\n\t\t\tconsole.error(error);\n\t\t}),\n\t],\n});\n\nexport const rpcHandler = new RPCHandler(appRouter, {\n\tinterceptors: [\n\t\tonError((error) => {\n\t\t\tconsole.error(error);\n\t\t}),\n\t],\n});\n\napp.use(\"/*\", async (c, next) => {\n\tconst context = await createContext({ context: c });\n\n\tconst rpcResult = await rpcHandler.handle(c.req.raw, {\n\t\tprefix: \"/rpc\",\n\t\tcontext: context,\n\t});\n\n\tif (rpcResult.matched) {\n\t\treturn c.newResponse(rpcResult.response.body, rpcResult.response);\n\t}\n\n\tconst apiResult = await apiHandler.handle(c.req.raw, {\n\t\tprefix: \"/api-reference\",\n\t\tcontext: context,\n\t});\n\n\tif (apiResult.matched) {\n\t\treturn c.newResponse(apiResult.response.body, apiResult.response);\n\t}\n\n\tawait next();\n});\n{{/if}}\n\n{{#if (eq api \"trpc\")}}\napp.use(\n\t\"/trpc/*\",\n\ttrpcServer({\n\t\trouter: appRouter,\n\t\tcreateContext: (_opts, context) => {\n\t\t\treturn createContext({ context });\n\t\t},\n\t})\n);\n{{/if}}\n\n{{#if (and (includes examples \"ai\") (or (eq runtime \"bun\") (eq runtime \"node\")))}}\napp.post(\"/ai\", async (c) => {\n\tconst body = await c.req.json();\n\tconst uiMessages = body.messages || [];\n\tconst model = wrapLanguageModel({\n\t\tmodel: google(\"gemini-2.5-flash\"),\n\t\tmiddleware: devToolsMiddleware(),\n\t});\n\tconst result = streamText({\n\t\tmodel,\n\t\tmessages: await convertToModelMessages(uiMessages),\n\t});\n\n\treturn result.toUIMessageStreamResponse();\n});\n{{/if}}\n\n{{#if (and (includes examples \"ai\") (eq runtime \"workers\"))}}\napp.post(\"/ai\", async (c) => {\n\tconst body = await c.req.json();\n\tconst uiMessages = body.messages || [];\n\tconst google = createGoogleGenerativeAI({\n\t\tapiKey: env.GOOGLE_GENERATIVE_AI_API_KEY,\n\t});\n\tconst model = wrapLanguageModel({\n\t\tmodel: google(\"gemini-2.5-flash\"),\n\t\tmiddleware: devToolsMiddleware(),\n\t});\n\tconst result = streamText({\n\t\tmodel,\n\t\tmessages: await convertToModelMessages(uiMessages),\n\t});\n\n\treturn result.toUIMessageStreamResponse();\n});\n{{/if}}\n\napp.get(\"/\", (c) => {\n\treturn c.text(\"OK\");\n});\n\n{{#if (eq runtime \"node\")}}\nimport { serve } from \"@hono/node-server\";\n\nserve(\n\t{\n\t\tfetch: app.fetch,\n\t\tport: 3000,\n\t},\n\t(info) => {\n\t\tconsole.log(`Server is running on http://localhost:${info.port}`);\n\t}\n);\n{{else}}\n{{#if (eq runtime \"bun\")}}\nexport default app;\n{{/if}}\n{{#if (eq runtime \"workers\")}}\nexport default app;\n{{/if}}\n{{/if}}\n"
  },
  {
    "path": "packages/template-generator/templates/base/_gitignore",
    "content": "# Dependencies\nnode_modules\n.pnp\n.pnp.js\n\n# Build outputs\ndist\nbuild\n*.tsbuildinfo\n\n# Environment variables\n.env\n.env*.local\n\n# IDEs and editors\n.vscode/*\n!.vscode/settings.json\n!.vscode/tasks.json\n!.vscode/launch.json\n!.vscode/extensions.json\n.idea\n*.swp\n*.swo\n*~\n.DS_Store\n\n# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\nlerna-debug.log*\n.pnpm-debug.log*\n\n# Turbo\n.turbo\n.nx\n\n# Better-T-Stack\n.alchemy\n\n# Testing\ncoverage\n.nyc_output\n\n# Misc\n*.tgz\n.cache\ntmp\ntemp\n"
  },
  {
    "path": "packages/template-generator/templates/base/package.json.hbs",
    "content": "{\n  \"name\": \"better-t-stack\",\n  \"private\": true,\n  \"type\": \"module\",\n  \"workspaces\": [\n    \"apps/*\",\n    \"packages/*\"\n  ],\n  \"scripts\": {}\n}\n"
  },
  {
    "path": "packages/template-generator/templates/base/tsconfig.json.hbs",
    "content": "{\n  \"extends\": \"@{{projectName}}/config/tsconfig.base.json\",\n}\n"
  },
  {
    "path": "packages/template-generator/templates/db/base/_gitignore",
    "content": "# dependencies (bun install)\nnode_modules\n\n# output\nout\ndist\n*.tgz\n/prisma/generated\n\n# code coverage\ncoverage\n*.lcov\n\n# logs\nlogs\n_.log\nreport.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json\n\n# dotenv environment variable files\n.env\n.env.development.local\n.env.test.local\n.env.production.local\n.env.local\n\n# caches\n.eslintcache\n.cache\n*.tsbuildinfo\n\n# IntelliJ based IDEs\n.idea\n\n# Finder (MacOS) folder config\n.DS_Store\n"
  },
  {
    "path": "packages/template-generator/templates/db/base/package.json.hbs",
    "content": "{\n  \"name\": \"@{{projectName}}/db\",\n  \"type\": \"module\",\n  \"exports\": {\n    \".\": {\n      \"default\": \"./src/index.ts\"\n    },\n    \"./*\": {\n      \"default\": \"./src/*.ts\"\n    }\n  },\n  \"scripts\": {},\n  \"devDependencies\": {}\n}"
  },
  {
    "path": "packages/template-generator/templates/db/base/tsconfig.json.hbs",
    "content": "{\n  \"extends\": \"@{{projectName}}/config/tsconfig.base.json\",\n  \"compilerOptions\": {\n    \"declaration\": true,\n    \"declarationMap\": true,\n    \"sourceMap\": true,\n    \"outDir\": \"dist\",\n    \"composite\": true\n  }\n}"
  },
  {
    "path": "packages/template-generator/templates/db/drizzle/base/src/schema/index.ts.hbs",
    "content": "{{#if (eq auth \"better-auth\")}}\nexport * from \"./auth\";\n{{/if}}\n{{#if (includes examples \"todo\")}}\nexport * from \"./todo\";\n{{/if}}\nexport {};"
  },
  {
    "path": "packages/template-generator/templates/db/drizzle/mysql/drizzle.config.ts.hbs",
    "content": "import { defineConfig } from \"drizzle-kit\";\nimport dotenv from \"dotenv\";\n\ndotenv.config({\n    {{#if (eq backend \"self\")}}\n    path: \"../../apps/web/.env\",\n    {{else}}\n    path: \"../../apps/server/.env\",\n    {{/if}}\n});\n\nexport default defineConfig({\n  schema: \"./src/schema\",\n  out: \"./src/migrations\",\n  dialect: \"mysql\",\n  dbCredentials: {\n    url: process.env.DATABASE_URL || \"\",\n  },\n});\n"
  },
  {
    "path": "packages/template-generator/templates/db/drizzle/mysql/src/index.ts.hbs",
    "content": "{{#if (or (eq runtime \"bun\") (eq runtime \"node\") (eq runtime \"none\"))}}\n{{#if (and (eq backend \"self\") (eq webDeploy \"cloudflare\") (includes frontend \"svelte\"))}}\nimport type {} from \"@{{projectName}}/env/server\";\n{{else}}\nimport { env } from \"@{{projectName}}/env/server\";\n{{/if}}\nimport * as schema from \"./schema\";\n\n{{#if (eq dbSetup \"planetscale\")}}\nimport { drizzle } from \"drizzle-orm/planetscale-serverless\";\n\nexport function createDb({{#if (and (eq backend \"self\") (eq webDeploy \"cloudflare\") (includes frontend \"svelte\"))}}env: Env{{/if}}) {\n\treturn drizzle({\n\t\tconnection: {\n\t\t\thost: env.DATABASE_HOST,\n\t\t\tusername: env.DATABASE_USERNAME,\n\t\t\tpassword: env.DATABASE_PASSWORD,\n\t\t},\n\t\tschema,\n\t});\n}\n{{else}}\nimport { drizzle } from \"drizzle-orm/mysql2\";\n\nexport function createDb({{#if (and (eq backend \"self\") (eq webDeploy \"cloudflare\") (includes frontend \"svelte\"))}}env: Env{{/if}}) {\n\treturn drizzle({\n\t\tconnection: {\n\t\t\turi: env.DATABASE_URL,\n\t\t},\n\t\tschema,\n\t});\n}\n{{/if}}\n\n{{#if (and (ne serverDeploy \"cloudflare\") (or (ne backend \"self\") (ne webDeploy \"cloudflare\")))}}\nexport const db = createDb();\n{{/if}}\n{{/if}}\n\n{{#if (eq runtime \"workers\")}}\nimport * as schema from \"./schema\";\n\n{{#if (eq dbSetup \"planetscale\")}}\nimport { drizzle } from \"drizzle-orm/planetscale-serverless\";\nimport { env } from \"@{{projectName}}/env/server\";\n\nexport function createDb() {\n\treturn drizzle({\n\t\tconnection: {\n\t\t\thost: env.DATABASE_HOST,\n\t\t\tusername: env.DATABASE_USERNAME,\n\t\t\tpassword: env.DATABASE_PASSWORD,\n\t\t},\n\t\tschema,\n\t});\n}\n{{else}}\nimport { drizzle } from \"drizzle-orm/mysql2\";\nimport { env } from \"@{{projectName}}/env/server\";\n\nexport function createDb() {\n\treturn drizzle({\n\t\tconnection: {\n\t\t\turi: env.DATABASE_URL,\n\t\t},\n\t\tschema,\n\t});\n}\n{{/if}}\n{{/if}}\n"
  },
  {
    "path": "packages/template-generator/templates/db/drizzle/postgres/drizzle.config.ts.hbs",
    "content": "import { defineConfig } from \"drizzle-kit\";\nimport dotenv from \"dotenv\";\n\ndotenv.config({\n    {{#if (eq backend \"self\")}}\n    path: \"../../apps/web/.env\",\n    {{else}}\n    path: \"../../apps/server/.env\",\n    {{/if}}\n});\n\nexport default defineConfig({\n  schema: \"./src/schema\",\n  out: \"./src/migrations\",\n  dialect: \"postgresql\",\n  dbCredentials: {\n    url: process.env.DATABASE_URL || \"\",\n  },\n});\n"
  },
  {
    "path": "packages/template-generator/templates/db/drizzle/postgres/src/index.ts.hbs",
    "content": "{{#if (or (eq runtime \"bun\") (eq runtime \"node\") (eq runtime \"none\"))}}\n{{#if (and (eq backend \"self\") (eq webDeploy \"cloudflare\") (includes frontend \"svelte\"))}}\nimport type {} from \"@{{projectName}}/env/server\";\n{{else}}\nimport { env } from \"@{{projectName}}/env/server\";\n{{/if}}\nimport * as schema from \"./schema\";\n\n{{#if (eq dbSetup \"neon\")}}\nimport { neon } from '@neondatabase/serverless';\nimport { drizzle } from 'drizzle-orm/neon-http';\n\nexport function createDb({{#if (and (eq backend \"self\") (eq webDeploy \"cloudflare\") (includes frontend \"svelte\"))}}env: Env{{/if}}) {\n\tconst sql = neon(env.DATABASE_URL);\n\treturn drizzle(sql, { schema });\n}\n{{else}}\nimport { drizzle } from \"drizzle-orm/node-postgres\";\n{{#if (and (eq backend \"self\") (eq webDeploy \"cloudflare\"))}}\nimport { Pool } from \"pg\";\n{{/if}}\n\nexport function createDb({{#if (and (eq backend \"self\") (eq webDeploy \"cloudflare\") (includes frontend \"svelte\"))}}env: Env{{/if}}) {\n{{#if (and (eq backend \"self\") (eq webDeploy \"cloudflare\"))}}\n\tconst pool = new Pool({\n\t\tconnectionString: env.DATABASE_URL,\n\t\tmaxUses: 1,\n\t});\n\n\treturn drizzle({ client: pool, schema });\n{{else}}\n\treturn drizzle(env.DATABASE_URL, { schema });\n{{/if}}\n}\n{{/if}}\n\n{{#if (and (ne serverDeploy \"cloudflare\") (or (ne backend \"self\") (ne webDeploy \"cloudflare\")))}}\nexport const db = createDb();\n{{/if}}\n{{/if}}\n\n{{#if (eq runtime \"workers\")}}\nimport * as schema from \"./schema\";\n\n{{#if (eq dbSetup \"neon\")}}\nimport { neon } from '@neondatabase/serverless';\nimport { drizzle } from 'drizzle-orm/neon-http';\nimport { env } from \"@{{projectName}}/env/server\";\n\nexport function createDb() {\n\tconst sql = neon(env.DATABASE_URL || \"\");\n\treturn drizzle(sql, { schema });\n}\n{{else}}\nimport { drizzle } from \"drizzle-orm/node-postgres\";\nimport { env } from \"@{{projectName}}/env/server\";\nimport { Pool } from \"pg\";\n\nexport function createDb() {\n\tconst pool = new Pool({\n\t\tconnectionString: env.DATABASE_URL || \"\",\n\t\tmaxUses: 1,\n\t});\n\n\treturn drizzle({ client: pool, schema });\n}\n{{/if}}\n{{/if}}\n"
  },
  {
    "path": "packages/template-generator/templates/db/drizzle/sqlite/drizzle.config.ts.hbs",
    "content": "import { defineConfig } from \"drizzle-kit\";\nimport dotenv from \"dotenv\";\n\ndotenv.config({\n    {{#if (eq backend \"self\")}}\n    path: \"../../apps/web/.env\",\n    {{else}}\n    path: \"../../apps/server/.env\",\n    {{/if}}\n});\n\nexport default defineConfig({\n  schema: \"./src/schema\",\n  out: \"./src/migrations\",\n  {{#if (eq dbSetup \"d1\")}}\n  // DOCS: https://orm.drizzle.team/docs/guides/d1-http-with-drizzle-kit\n  dialect: \"sqlite\",\n  driver: \"d1-http\",\n  {{else}}\n  dialect: \"turso\",\n  dbCredentials: {\n    url: process.env.DATABASE_URL || \"\",\n    {{#if (eq dbSetup \"turso\")}}\n    authToken: process.env.DATABASE_AUTH_TOKEN,\n    {{/if}}\n  },\n  {{/if}}\n});\n"
  },
  {
    "path": "packages/template-generator/templates/db/drizzle/sqlite/src/index.ts.hbs",
    "content": "{{#if (eq dbSetup \"d1\")}}\nimport * as schema from \"./schema\";\nimport { drizzle } from \"drizzle-orm/d1\";\n{{#if (and (eq backend \"self\") (eq webDeploy \"cloudflare\") (includes frontend \"svelte\"))}}\nimport type {} from \"@{{projectName}}/env/server\";\n{{else}}\nimport { env } from \"@{{projectName}}/env/server\";\n{{/if}}\n\nexport function createDb({{#if (and (eq backend \"self\") (eq webDeploy \"cloudflare\") (includes frontend \"svelte\"))}}env: Env{{/if}}) {\n\treturn drizzle(env.DB, { schema });\n}\n{{else if (or (eq runtime \"bun\") (eq runtime \"node\") (eq runtime \"none\"))}}\n{{#if (and (eq backend \"self\") (eq webDeploy \"cloudflare\") (includes frontend \"svelte\"))}}\nimport type {} from \"@{{projectName}}/env/server\";\n{{else}}\nimport { env } from \"@{{projectName}}/env/server\";\n{{/if}}\nimport * as schema from \"./schema\";\nimport { drizzle } from \"drizzle-orm/libsql\";\nimport { createClient } from \"@libsql/client\";\n\nexport function createDb({{#if (and (eq backend \"self\") (eq webDeploy \"cloudflare\") (includes frontend \"svelte\"))}}env: Env{{/if}}) {\n\tconst client = createClient({\n\t\turl: env.DATABASE_URL,\n{{#if (eq dbSetup \"turso\")}}\n\t\tauthToken: env.DATABASE_AUTH_TOKEN,\n{{/if}}\n\t});\n\n\treturn drizzle({ client, schema });\n}\n\n{{#if (and (ne serverDeploy \"cloudflare\") (or (ne backend \"self\") (ne webDeploy \"cloudflare\")))}}\nexport const db = createDb();\n{{/if}}\n{{else if (eq runtime \"workers\")}}\nimport * as schema from \"./schema\";\nimport { drizzle } from \"drizzle-orm/libsql\";\nimport { env } from \"@{{projectName}}/env/server\";\nimport { createClient } from \"@libsql/client\";\n\nexport function createDb() {\n\tconst client = createClient({\n\t\turl: env.DATABASE_URL || \"\",\n{{#if (eq dbSetup \"turso\")}}\n\t\tauthToken: env.DATABASE_AUTH_TOKEN,\n{{/if}}\n\t});\n\n\treturn drizzle({ client, schema });\n}\n{{/if}}\n"
  },
  {
    "path": "packages/template-generator/templates/db/mongoose/mongodb/src/index.ts.hbs",
    "content": "import mongoose from \"mongoose\";\nimport { env } from \"@{{projectName}}/env/server\";\n\nawait mongoose.connect(env.DATABASE_URL).catch((error) => {\n\tconsole.log(\"Error connecting to database:\", error);\n});\n\nconst client = mongoose.connection.getClient().db(\"myDB\");\n\nexport { client };\n"
  },
  {
    "path": "packages/template-generator/templates/db/prisma/mongodb/prisma/schema/schema.prisma.hbs",
    "content": "generator client {\n  provider = \"prisma-client\"\n  output   = \"../generated\"\n  moduleFormat = \"esm\"\n  {{#if (eq runtime \"bun\")}}\n  runtime = \"bun\"\n  {{/if}}\n  {{#if (eq runtime \"node\")}}\n  runtime = \"nodejs\"\n  {{/if}}\n  {{#if (or (eq runtime \"workers\") (and (eq backend \"self\") (eq webDeploy \"cloudflare\")))}}\n  runtime = \"workerd\"\n  {{/if}}\n}\n\ndatasource db {\n  provider = \"mongodb\"\n  url      = env(\"DATABASE_URL\")\n}\n"
  },
  {
    "path": "packages/template-generator/templates/db/prisma/mongodb/prisma.config.ts.hbs",
    "content": "import path from \"node:path\";\nimport type { PrismaConfig } from \"prisma\";\nimport dotenv from \"dotenv\";\n\ndotenv.config({\n    {{#if (eq backend \"self\")}}\n    path: \"../../apps/web/.env\",\n    {{else}}\n    path: \"../../apps/server/.env\",\n    {{/if}}\n});\n\nexport default {\n  schema: path.join(\"prisma\", \"schema\"),\n  migrations: {\n    path: path.join(\"prisma\", \"migrations\"),\n  }\n} satisfies PrismaConfig;\n"
  },
  {
    "path": "packages/template-generator/templates/db/prisma/mongodb/src/index.ts.hbs",
    "content": "import { PrismaClient } from \"../prisma/generated/client\";\n\nconst prisma = new PrismaClient();\n\nexport default prisma;\n"
  },
  {
    "path": "packages/template-generator/templates/db/prisma/mysql/prisma/schema/schema.prisma.hbs",
    "content": "generator client {\n  provider      = \"prisma-client\"\n  output        = \"../generated\"\n  moduleFormat  = \"esm\"\n  {{#if (eq runtime \"bun\")}}\n  runtime       = \"bun\"\n  {{/if}}\n  {{#if (eq runtime \"node\")}}\n  runtime       = \"nodejs\"\n  {{/if}}\n  {{#if (or (eq runtime \"workers\") (and (eq backend \"self\") (eq webDeploy \"cloudflare\")))}}\n  runtime       = \"workerd\"\n  {{/if}}\n}\n\ndatasource db {\n  provider = \"mysql\"\n  {{#if (eq dbSetup \"planetscale\")}}\n  relationMode = \"prisma\"\n  {{/if}}\n}"
  },
  {
    "path": "packages/template-generator/templates/db/prisma/mysql/prisma.config.ts.hbs",
    "content": "import path from \"node:path\";\nimport { defineConfig, env } from \"prisma/config\";\nimport dotenv from \"dotenv\";\n\ndotenv.config({\n  {{#if (eq backend \"self\")}}\n  path: \"../../apps/web/.env\",\n  {{else}}\n  path: \"../../apps/server/.env\",\n  {{/if}}\n});\n\nexport default defineConfig({\n  schema: path.join(\"prisma\", \"schema\"),\n  migrations: {\n    path: path.join(\"prisma\", \"migrations\"),\n  },\n  datasource: {\n    url: env(\"DATABASE_URL\"),\n  },\n});"
  },
  {
    "path": "packages/template-generator/templates/db/prisma/mysql/src/index.ts.hbs",
    "content": "{{#if (eq runtime \"workers\")}}\nimport { PrismaClient } from \"../prisma/generated/client\";\nimport { env } from \"@{{projectName}}/env/server\";\n\n{{#if (eq dbSetup \"planetscale\")}}\nimport { PrismaPlanetScale } from \"@prisma/adapter-planetscale\";\n\nexport function createPrismaClient() {\n\tconst adapter = new PrismaPlanetScale({ url: env.DATABASE_URL });\n\treturn new PrismaClient({ adapter });\n}\n{{else}}\nimport { PrismaMariaDb } from \"@prisma/adapter-mariadb\";\n\nexport function createPrismaClient() {\n\tconst databaseUrl: string = env.DATABASE_URL;\n\tconst url: URL = new URL(databaseUrl);\n\tconst connectionConfig = {\n\t\thost: url.hostname,\n\t\tport: parseInt(url.port || \"3306\"),\n\t\tuser: url.username,\n\t\tpassword: url.password,\n\t\tdatabase: url.pathname.slice(1),\n\t};\n\n\tconst adapter = new PrismaMariaDb(connectionConfig);\n\treturn new PrismaClient({ adapter });\n}\n{{/if}}\n{{else}}\nimport { PrismaClient } from \"../prisma/generated/client\";\n{{#if (and (eq backend \"self\") (eq webDeploy \"cloudflare\") (includes frontend \"svelte\"))}}\nimport type {} from \"@{{projectName}}/env/server\";\n{{else}}\nimport { env } from \"@{{projectName}}/env/server\";\n{{/if}}\n\n{{#if (eq dbSetup \"planetscale\")}}\nimport { PrismaPlanetScale } from \"@prisma/adapter-planetscale\";\n\nexport function createPrismaClient({{#if (and (eq backend \"self\") (eq webDeploy \"cloudflare\") (includes frontend \"svelte\"))}}env: Env{{/if}}) {\n\tconst adapter = new PrismaPlanetScale({ url: env.DATABASE_URL });\n\treturn new PrismaClient({ adapter });\n}\n{{else}}\nimport { PrismaMariaDb } from \"@prisma/adapter-mariadb\";\n\nexport function createPrismaClient({{#if (and (eq backend \"self\") (eq webDeploy \"cloudflare\") (includes frontend \"svelte\"))}}env: Env{{/if}}) {\n\tconst databaseUrl: string = env.DATABASE_URL;\n\tconst url: URL = new URL(databaseUrl);\n\tconst connectionConfig = {\n\t\thost: url.hostname,\n\t\tport: parseInt(url.port || \"3306\"),\n\t\tuser: url.username,\n\t\tpassword: url.password,\n\t\tdatabase: url.pathname.slice(1),\n\t};\n\n\tconst adapter = new PrismaMariaDb(connectionConfig);\n\treturn new PrismaClient({ adapter });\n}\n{{/if}}\n\n{{#if (and (ne serverDeploy \"cloudflare\") (or (ne backend \"self\") (ne webDeploy \"cloudflare\")))}}\nconst prisma = createPrismaClient();\nexport default prisma;\n{{/if}}\n{{/if}}\n"
  },
  {
    "path": "packages/template-generator/templates/db/prisma/postgres/prisma/schema/schema.prisma.hbs",
    "content": "generator client {\n  provider = \"prisma-client\"\n  output   = \"../generated\"\n  moduleFormat = \"esm\"\n  {{#if (eq runtime \"bun\")}}\n  runtime = \"bun\"\n  {{/if}}\n  {{#if (eq runtime \"node\")}}\n  runtime = \"nodejs\"\n  {{/if}}\n  {{#if (or (eq runtime \"workers\") (and (eq backend \"self\") (eq webDeploy \"cloudflare\")))}}\n  runtime = \"workerd\"\n  {{/if}}\n}\n\ndatasource db {\n  provider = \"postgresql\"\n  {{#if (eq dbSetup \"planetscale\")}}\n  relationMode = \"prisma\"\n  {{/if}}\n}\n"
  },
  {
    "path": "packages/template-generator/templates/db/prisma/postgres/prisma.config.ts.hbs",
    "content": "import path from \"node:path\";\nimport { defineConfig, env } from 'prisma/config'\nimport dotenv from 'dotenv'\n\ndotenv.config({\n    {{#if (eq backend \"self\")}}\n    path: \"../../apps/web/.env\",\n    {{else}}\n    path: \"../../apps/server/.env\",\n    {{/if}}\n})\n\nexport default defineConfig({\n  schema: path.join(\"prisma\", \"schema\"),\n  migrations: {\n    path: path.join(\"prisma\", \"migrations\"),\n    },\n    datasource: {\n        url: env('DATABASE_URL'),\n    },\n})\n"
  },
  {
    "path": "packages/template-generator/templates/db/prisma/postgres/src/index.ts.hbs",
    "content": "{{#if (eq runtime \"workers\")}}\nimport { PrismaClient } from \"../prisma/generated/client\";\nimport { env } from \"@{{projectName}}/env/server\";\n{{#if (eq dbSetup \"neon\")}}\nimport { PrismaNeon } from \"@prisma/adapter-neon\";\nimport { neonConfig } from \"@neondatabase/serverless\";\n\nneonConfig.poolQueryViaFetch = true;\n\nexport function createPrismaClient() {\n\treturn new PrismaClient({\n\t\tadapter: new PrismaNeon({\n\t\t\tconnectionString: env.DATABASE_URL,\n\t\t}),\n\t});\n}\n\n{{else if (eq dbSetup \"prisma-postgres\")}}\nimport { PrismaPg } from \"@prisma/adapter-pg\";\n\nexport function createPrismaClient() {\n\tconst adapter = new PrismaPg({\n\t\tconnectionString: env.DATABASE_URL,\n\t\tmaxUses: 1,\n\t});\n\n\treturn new PrismaClient({ adapter });\n}\n\n{{else}}\nimport { PrismaPg } from \"@prisma/adapter-pg\";\n\nexport function createPrismaClient() {\n\tconst adapter = new PrismaPg({\n\t\tconnectionString: env.DATABASE_URL,\n\t\tmaxUses: 1,\n\t});\n\treturn new PrismaClient({ adapter });\n}\n\n{{/if}}\n{{else}}\nimport { PrismaClient } from \"../prisma/generated/client\";\n{{#if (and (eq backend \"self\") (eq webDeploy \"cloudflare\") (includes frontend \"svelte\"))}}\nimport type {} from \"@{{projectName}}/env/server\";\n{{else}}\nimport { env } from \"@{{projectName}}/env/server\";\n{{/if}}\n{{#if (eq dbSetup \"neon\")}}\nimport { PrismaNeon } from \"@prisma/adapter-neon\";\n\nexport function createPrismaClient({{#if (and (eq backend \"self\") (eq webDeploy \"cloudflare\") (includes frontend \"svelte\"))}}env: Env{{/if}}) {\n\tconst adapter = new PrismaNeon({\n\t\tconnectionString: env.DATABASE_URL,\n\t});\n\n\treturn new PrismaClient({ adapter });\n}\n\n{{else if (eq dbSetup \"prisma-postgres\")}}\nimport { PrismaPg } from \"@prisma/adapter-pg\";\n\nexport function createPrismaClient({{#if (and (eq backend \"self\") (eq webDeploy \"cloudflare\") (includes frontend \"svelte\"))}}env: Env{{/if}}) {\n\tconst adapter = new PrismaPg({\n\t\tconnectionString: env.DATABASE_URL,\n{{#if (and (eq backend \"self\") (eq webDeploy \"cloudflare\"))}}\n\t\tmaxUses: 1,\n{{/if}}\n\t});\n\n\treturn new PrismaClient({ adapter });\n}\n\n{{else}}\nimport { PrismaPg } from \"@prisma/adapter-pg\";\n\nexport function createPrismaClient({{#if (and (eq backend \"self\") (eq webDeploy \"cloudflare\") (includes frontend \"svelte\"))}}env: Env{{/if}}) {\n\tconst adapter = new PrismaPg({\n\t\tconnectionString: env.DATABASE_URL,\n{{#if (and (eq backend \"self\") (eq webDeploy \"cloudflare\"))}}\n\t\tmaxUses: 1,\n{{/if}}\n\t});\n\treturn new PrismaClient({ adapter });\n}\n\n{{/if}}\n{{#if (and (ne serverDeploy \"cloudflare\") (or (ne backend \"self\") (ne webDeploy \"cloudflare\")))}}\nconst prisma = createPrismaClient();\nexport default prisma;\n{{/if}}\n{{/if}}\n"
  },
  {
    "path": "packages/template-generator/templates/db/prisma/sqlite/prisma/schema/schema.prisma.hbs",
    "content": "generator client {\n  provider = \"prisma-client\"\n  output   = \"../generated\"\n  moduleFormat = \"esm\"\n  {{#if (eq runtime \"bun\")}}\n  runtime = \"bun\"\n  {{/if}}\n  {{#if (eq runtime \"node\")}}\n  runtime = \"nodejs\"\n  {{/if}}\n  {{#if (or (eq runtime \"workers\") (and (eq backend \"self\") (eq webDeploy \"cloudflare\")))}}\n  runtime = \"workerd\"\n  {{/if}}\n}\n\ndatasource db {\n  provider = \"sqlite\"\n}\n"
  },
  {
    "path": "packages/template-generator/templates/db/prisma/sqlite/prisma.config.ts.hbs",
    "content": "import path from \"node:path\";\nimport { defineConfig, env } from \"prisma/config\";\nimport dotenv from \"dotenv\";\n\ndotenv.config({\n  {{#if (eq backend \"self\")}}\n  path: \"../../apps/web/.env\",\n  {{else}}\n  path: \"../../apps/server/.env\",\n  {{/if}}\n});\n\nexport default defineConfig({\n  schema: path.join(\"prisma\", \"schema\"),\n  migrations: {\n    path: path.join(\"prisma\", \"migrations\"),\n  },\n  datasource: {\n    {{#if (eq dbSetup \"turso\")}}\n    url: \"file:./dev.db\",\n    {{else}}\n    url: env(\"DATABASE_URL\"),\n    {{/if}}\n  },\n});"
  },
  {
    "path": "packages/template-generator/templates/db/prisma/sqlite/src/index.ts.hbs",
    "content": "import { PrismaClient } from \"../prisma/generated/client\";\n\n{{#if (eq dbSetup \"d1\")}}\nimport { PrismaD1 } from \"@prisma/adapter-d1\";\n{{#if (and (eq backend \"self\") (eq webDeploy \"cloudflare\") (includes frontend \"svelte\"))}}\nimport type {} from \"@{{projectName}}/env/server\";\n{{else}}\nimport { env } from \"@{{projectName}}/env/server\";\n{{/if}}\n\nexport function createPrismaClient({{#if (and (eq backend \"self\") (eq webDeploy \"cloudflare\") (includes frontend \"svelte\"))}}env: Env{{/if}}) {\n\tconst adapter = new PrismaD1(env.DB);\n\treturn new PrismaClient({ adapter });\n}\n\n{{#if (and (ne runtime \"workers\") (ne serverDeploy \"cloudflare\") (or (ne backend \"self\") (ne webDeploy \"cloudflare\")))}}\nconst prisma = createPrismaClient();\nexport default prisma;\n{{/if}}\n{{else}}\nimport { PrismaLibSql } from \"@prisma/adapter-libsql\";\n{{#if (and (eq backend \"self\") (eq webDeploy \"cloudflare\") (includes frontend \"svelte\"))}}\nimport type {} from \"@{{projectName}}/env/server\";\n{{else}}\nimport { env } from \"@{{projectName}}/env/server\";\n{{/if}}\n\nexport function createPrismaClient({{#if (and (eq backend \"self\") (eq webDeploy \"cloudflare\") (includes frontend \"svelte\"))}}env: Env{{/if}}) {\n\tconst adapter = new PrismaLibSql({\n\t\turl: env.DATABASE_URL,\n{{#if (eq dbSetup \"turso\")}}\n\t\tauthToken: env.DATABASE_AUTH_TOKEN || \"\",\n{{/if}}\n\t});\n\n\treturn new PrismaClient({ adapter });\n}\n\n{{#if (and (ne runtime \"workers\") (ne serverDeploy \"cloudflare\") (or (ne backend \"self\") (ne webDeploy \"cloudflare\")))}}\nconst prisma = createPrismaClient();\nexport default prisma;\n{{/if}}\n{{/if}}\n"
  },
  {
    "path": "packages/template-generator/templates/db-setup/docker-compose/mongodb/docker-compose.yml.hbs",
    "content": "name: {{projectName}}\n\nservices:\n  mongodb:\n    image: mongo\n    container_name: {{projectName}}-mongodb\n    environment:\n      MONGO_INITDB_ROOT_USERNAME: root\n      MONGO_INITDB_ROOT_PASSWORD: password\n      MONGO_INITDB_DATABASE: {{projectName}}\n    ports:\n      - \"27017:27017\"\n    volumes:\n      - {{projectName}}_mongodb_data:/data/db\n    healthcheck:\n      test: [\"CMD\", \"mongosh\", \"--eval\", \"db.adminCommand('ping')\"]\n      interval: 10s\n      timeout: 5s\n      retries: 5\n    restart: unless-stopped\n\nvolumes:\n  {{projectName}}_mongodb_data:"
  },
  {
    "path": "packages/template-generator/templates/db-setup/docker-compose/mysql/docker-compose.yml.hbs",
    "content": "name: {{projectName}}\n\nservices:\n  mysql:\n    image: mysql\n    container_name: {{projectName}}-mysql\n    environment:\n      MYSQL_ROOT_PASSWORD: password\n      MYSQL_DATABASE: {{projectName}}\n      MYSQL_USER: user\n      MYSQL_PASSWORD: password\n    ports:\n      - \"3306:3306\"\n    volumes:\n      - {{projectName}}_mysql_data:/var/lib/mysql\n    healthcheck:\n      test: [\"CMD\", \"mysqladmin\", \"ping\", \"-h\", \"localhost\"]\n      interval: 10s\n      timeout: 5s\n      retries: 5\n    restart: unless-stopped\n\nvolumes:\n  {{projectName}}_mysql_data:"
  },
  {
    "path": "packages/template-generator/templates/db-setup/docker-compose/postgres/docker-compose.yml.hbs",
    "content": "name: {{projectName}}\n\nservices:\n  postgres:\n    image: postgres\n    container_name: {{projectName}}-postgres\n    environment:\n      POSTGRES_DB: {{projectName}}\n      POSTGRES_USER: postgres\n      POSTGRES_PASSWORD: password\n    ports:\n      - \"5432:5432\"\n    volumes:\n      - {{projectName}}_postgres_data:/var/lib/postgresql\n    healthcheck:\n      test: [\"CMD-SHELL\", \"pg_isready -U postgres\"]\n      interval: 10s\n      timeout: 5s\n      retries: 5\n    restart: unless-stopped\n\nvolumes:\n  {{projectName}}_postgres_data:"
  },
  {
    "path": "packages/template-generator/templates/examples/ai/convex/packages/backend/convex/agent.ts.hbs",
    "content": "import { Agent } from \"@convex-dev/agent\";\nimport { google } from \"@ai-sdk/google\";\nimport { components } from \"./_generated/api\";\n\nexport const chatAgent = new Agent(components.agent, {\n  name: \"Chat Agent\",\n  languageModel: google(\"gemini-2.5-flash\"),\n  instructions: \"You are a helpful AI assistant. Be concise and friendly in your responses.\",\n});\n"
  },
  {
    "path": "packages/template-generator/templates/examples/ai/convex/packages/backend/convex/chat.ts.hbs",
    "content": "import {\n  createThread,\n  listUIMessages,\n  saveMessage,\n  syncStreams,\n  vStreamArgs,\n} from \"@convex-dev/agent\";\nimport { paginationOptsValidator } from \"convex/server\";\nimport { v } from \"convex/values\";\n\nimport { components, internal } from \"./_generated/api\";\nimport { internalAction, mutation, query } from \"./_generated/server\";\nimport { chatAgent } from \"./agent\";\n\nexport const createNewThread = mutation({\n  args: {},\n  handler: async (ctx) => {\n    const threadId = await createThread(ctx, components.agent, {});\n    return threadId;\n  },\n});\n\nexport const listMessages = query({\n  args: {\n    threadId: v.string(),\n    paginationOpts: paginationOptsValidator,\n    streamArgs: vStreamArgs,\n  },\n  handler: async (ctx, args) => {\n    const paginated = await listUIMessages(ctx, components.agent, args);\n    const streams = await syncStreams(ctx, components.agent, args);\n    return { ...paginated, streams };\n  },\n});\n\nexport const sendMessage = mutation({\n  args: {\n    threadId: v.string(),\n    prompt: v.string(),\n  },\n  handler: async (ctx, { threadId, prompt }) => {\n    const { messageId } = await saveMessage(ctx, components.agent, {\n      threadId,\n      prompt,\n    });\n    await ctx.scheduler.runAfter(0, internal.chat.generateResponseAsync, {\n      threadId,\n      promptMessageId: messageId,\n    });\n    return messageId;\n  },\n});\n\nexport const generateResponseAsync = internalAction({\n  args: {\n    threadId: v.string(),\n    promptMessageId: v.string(),\n  },\n  handler: async (ctx, { threadId, promptMessageId }) => {\n    await chatAgent.streamText(\n      ctx,\n      { threadId },\n      { promptMessageId },\n      { saveStreamDeltas: true },\n    );\n  },\n});\n"
  },
  {
    "path": "packages/template-generator/templates/examples/ai/fullstack/next/src/app/api/ai/route.ts.hbs",
    "content": "import { google } from \"@ai-sdk/google\";\nimport { streamText, type UIMessage, convertToModelMessages, wrapLanguageModel } from \"ai\";\nimport { devToolsMiddleware } from \"@ai-sdk/devtools\";\n\nexport const maxDuration = 30;\n\nexport async function POST(req: Request) {\n\tconst { messages }: { messages: UIMessage[] } = await req.json();\n\n\tconst model = wrapLanguageModel({\n\t\tmodel: google(\"gemini-2.5-flash\"),\n\t\tmiddleware: devToolsMiddleware(),\n\t});\n\tconst result = streamText({\n\t\tmodel,\n\t\tmessages: await convertToModelMessages(messages),\n\t});\n\n\treturn result.toUIMessageStreamResponse();\n}\n"
  },
  {
    "path": "packages/template-generator/templates/examples/ai/fullstack/nuxt/server/api/ai.post.ts.hbs",
    "content": "import { devToolsMiddleware } from \"@ai-sdk/devtools\";\nimport { google } from \"@ai-sdk/google\";\nimport { streamText, convertToModelMessages, wrapLanguageModel } from \"ai\";\n\nexport default defineEventHandler(async (event) => {\n  const body = await readBody(event);\n  const uiMessages = body.messages || [];\n\n  const model = wrapLanguageModel({\n    model: google(\"gemini-2.5-flash\"),\n    middleware: devToolsMiddleware(),\n  });\n\n  const result = streamText({\n    model,\n    messages: await convertToModelMessages(uiMessages),\n  });\n\n  return result.toUIMessageStreamResponse();\n});\n"
  },
  {
    "path": "packages/template-generator/templates/examples/ai/fullstack/svelte/src/routes/api/ai/+server.ts.hbs",
    "content": "import { devToolsMiddleware } from \"@ai-sdk/devtools\";\nimport { google } from \"@ai-sdk/google\";\nimport { convertToModelMessages, streamText, type UIMessage, wrapLanguageModel } from \"ai\";\nimport type { RequestHandler } from \"@sveltejs/kit\";\n\nexport const POST: RequestHandler = async ({ request }) => {\n\tconst { messages }: { messages: UIMessage[] } = await request.json();\n\n\tconst model = wrapLanguageModel({\n\t\tmodel: google(\"gemini-2.5-flash\"),\n\t\tmiddleware: devToolsMiddleware(),\n\t});\n\tconst result = streamText({\n\t\tmodel,\n\t\tmessages: await convertToModelMessages(messages),\n\t});\n\n\treturn result.toUIMessageStreamResponse();\n};\n"
  },
  {
    "path": "packages/template-generator/templates/examples/ai/fullstack/tanstack-start/src/routes/api/ai/$.ts.hbs",
    "content": "import { createFileRoute } from \"@tanstack/react-router\";\nimport { google } from \"@ai-sdk/google\";\nimport { streamText, type UIMessage, convertToModelMessages, wrapLanguageModel } from \"ai\";\nimport { devToolsMiddleware } from \"@ai-sdk/devtools\";\n\nexport const Route = createFileRoute(\"/api/ai/$\")({\n  server: {\n    handlers: {\n      POST: async ({ request }) => {\n        try {\n          const { messages }: { messages: UIMessage[] } = await request.json();\n\n          const model = wrapLanguageModel({\n            model: google(\"gemini-2.5-flash\"),\n            middleware: devToolsMiddleware(),\n          });\n          const result = streamText({\n            model,\n            messages: await convertToModelMessages(messages),\n          });\n\n          return result.toUIMessageStreamResponse();\n        } catch (error) {\n          console.error(\"AI API error:\", error);\n          return new Response(\n            JSON.stringify({ error: \"Failed to process AI request\" }),\n            {\n              status: 500,\n              headers: { \"Content-Type\": \"application/json\" },\n            },\n          );\n        }\n      },\n    },\n  },\n});\n"
  },
  {
    "path": "packages/template-generator/templates/examples/ai/native/bare/app/(drawer)/ai.tsx.hbs",
    "content": "{{#if (eq backend \"convex\")}}\nimport { Ionicons } from \"@expo/vector-icons\";\nimport {\n  useUIMessages,\n  useSmoothText,\n  type UIMessage,\n} from \"@convex-dev/agent/react\";\nimport { api } from \"@{{projectName}}/backend/convex/_generated/api\";\nimport { useMutation } from \"convex/react\";\nimport { useRef, useEffect, useState } from \"react\";\nimport {\n  View,\n  Text,\n  TextInput,\n  TouchableOpacity,\n  ScrollView,\n  KeyboardAvoidingView,\n  Platform,\n  StyleSheet,\n  ActivityIndicator,\n} from \"react-native\";\n\nimport { Container } from \"@/components/container\";\nimport { useColorScheme } from \"@/lib/use-color-scheme\";\nimport { NAV_THEME } from \"@/lib/constants\";\n\nfunction MessageContent({\n  text,\n  isStreaming,\n  textColor,\n}: {\n  text: string;\n  isStreaming: boolean;\n  textColor: string;\n}) {\n  const [visibleText] = useSmoothText(text, {\n    startStreaming: isStreaming,\n  });\n  return <Text style={[styles.messageText, { color: textColor }]}>{visibleText}</Text>;\n}\n\nexport default function AIScreen() {\n  const { colorScheme } = useColorScheme();\n  const theme = colorScheme === \"dark\" ? NAV_THEME.dark : NAV_THEME.light;\n  const [input, setInput] = useState(\"\");\n  const [threadId, setThreadId] = useState<string | null>(null);\n  const [isLoading, setIsLoading] = useState(false);\n  const scrollViewRef = useRef<ScrollView>(null);\n\n  const createThread = useMutation(api.chat.createNewThread);\n  const sendMessage = useMutation(api.chat.sendMessage);\n\n  const { results: messages } = useUIMessages(\n    api.chat.listMessages,\n    threadId ? { threadId } : \"skip\",\n    { initialNumItems: 50, stream: true },\n  );\n\n  const hasStreamingMessage = messages?.some(\n    (m: UIMessage) => m.status === \"streaming\",\n  );\n\n  useEffect(() => {\n    scrollViewRef.current?.scrollToEnd({ animated: true });\n  }, [messages]);\n\n  async function onSubmit() {\n    const value = input.trim();\n    if (!value || isLoading) return;\n\n    setIsLoading(true);\n    setInput(\"\");\n\n    try {\n      let currentThreadId = threadId;\n      if (!currentThreadId) {\n        currentThreadId = await createThread();\n        setThreadId(currentThreadId);\n      }\n\n      await sendMessage({ threadId: currentThreadId, prompt: value });\n    } catch (error) {\n      console.error(\"Failed to send message:\", error);\n    } finally {\n      setIsLoading(false);\n    }\n  }\n\n  return (\n    <Container>\n      <KeyboardAvoidingView\n        style={styles.container}\n        behavior={Platform.OS === \"ios\" ? \"padding\" : \"height\"}\n      >\n        <View style={styles.content}>\n          <View style={styles.header}>\n            <Text style={[styles.headerTitle, { color: theme.text }]}>\n              AI Chat\n            </Text>\n            <Text style={[styles.headerSubtitle, { color: theme.text, opacity: 0.7 }]}>\n              Chat with our AI assistant\n            </Text>\n          </View>\n          <ScrollView\n            ref={scrollViewRef}\n            style={styles.scrollView}\n            showsVerticalScrollIndicator={false}\n          >\n            {!messages || messages.length === 0 ? (\n              <View style={styles.emptyContainer}>\n                <Text style={[styles.emptyText, { color: theme.text, opacity: 0.7 }]}>\n                  Ask me anything to get started!\n                </Text>\n              </View>\n            ) : (\n              <View style={styles.messagesList}>\n                {messages.map((message: UIMessage) => (\n                  <View\n                    key={message.key}\n                    style={[\n                      styles.messageCard,\n                      {\n                        backgroundColor: message.role === \"user\"\n                          ? theme.primary + \"20\"\n                          : theme.card,\n                        borderColor: theme.border,\n                        alignSelf: message.role === \"user\" ? \"flex-end\" : \"flex-start\",\n                        marginLeft: message.role === \"user\" ? 32 : 0,\n                        marginRight: message.role === \"user\" ? 0 : 32,\n                      },\n                    ]}\n                  >\n                    <Text style={[styles.messageRole, { color: theme.text }]}>\n                      {message.role === \"user\" ? \"You\" : \"AI Assistant\"}\n                    </Text>\n                    <MessageContent\n                      text={message.text ?? \"\"}\n                      isStreaming={message.status === \"streaming\"}\n                      textColor={theme.text}\n                    />\n                  </View>\n                ))}\n                {isLoading && !hasStreamingMessage && (\n                  <View\n                    style={[\n                      styles.messageCard,\n                      {\n                        backgroundColor: theme.card,\n                        borderColor: theme.border,\n                        alignSelf: \"flex-start\",\n                        marginRight: 32,\n                      },\n                    ]}\n                  >\n                    <Text style={[styles.messageRole, { color: theme.text }]}>\n                      AI Assistant\n                    </Text>\n                    <View style={styles.loadingContainer}>\n                      <ActivityIndicator size=\"small\" color={theme.primary} />\n                      <Text style={[styles.loadingText, { color: theme.text, opacity: 0.7 }]}>\n                        Thinking...\n                      </Text>\n                    </View>\n                  </View>\n                )}\n              </View>\n            )}\n          </ScrollView>\n          <View style={[styles.inputContainer, { borderTopColor: theme.border }]}>\n            <View style={styles.inputRow}>\n              <TextInput\n                value={input}\n                onChangeText={setInput}\n                placeholder=\"Type your message...\"\n                placeholderTextColor={theme.text}\n                style={[\n                  styles.input,\n                  {\n                    color: theme.text,\n                    borderColor: theme.border,\n                    backgroundColor: theme.background,\n                  },\n                ]}\n                onSubmitEditing={(e) => {\n                  e.preventDefault();\n                  onSubmit();\n                }}\n                editable={!isLoading}\n                autoFocus={true}\n                multiline\n              />\n              <TouchableOpacity\n                onPress={onSubmit}\n                disabled={!input.trim() || isLoading}\n                style={[\n                  styles.sendButton,\n                  {\n                    backgroundColor: input.trim() && !isLoading ? theme.primary : theme.border,\n                    opacity: input.trim() && !isLoading ? 1 : 0.5,\n                  },\n                ]}\n              >\n                <Ionicons\n                  name=\"send\"\n                  size={20}\n                  color=\"#ffffff\"\n                />\n              </TouchableOpacity>\n            </View>\n          </View>\n        </View>\n      </KeyboardAvoidingView>\n    </Container>\n  );\n}\n\nconst styles = StyleSheet.create({\n  container: {\n    flex: 1,\n  },\n  content: {\n    flex: 1,\n    padding: 16,\n  },\n  header: {\n    marginBottom: 16,\n  },\n  headerTitle: {\n    fontSize: 20,\n    fontWeight: \"bold\",\n    marginBottom: 4,\n  },\n  headerSubtitle: {\n    fontSize: 14,\n  },\n  scrollView: {\n    flex: 1,\n    marginBottom: 16,\n  },\n  emptyContainer: {\n    flex: 1,\n    justifyContent: \"center\",\n    alignItems: \"center\",\n  },\n  emptyText: {\n    fontSize: 16,\n    textAlign: \"center\",\n  },\n  messagesList: {\n    gap: 8,\n    paddingBottom: 16,\n  },\n  messageCard: {\n    borderWidth: 1,\n    padding: 12,\n    maxWidth: \"80%\",\n  },\n  messageRole: {\n    fontSize: 12,\n    fontWeight: \"bold\",\n    marginBottom: 4,\n  },\n  messageText: {\n    fontSize: 14,\n    lineHeight: 20,\n  },\n  loadingContainer: {\n    flexDirection: \"row\",\n    alignItems: \"center\",\n    gap: 8,\n  },\n  loadingText: {\n    fontSize: 14,\n  },\n  inputContainer: {\n    borderTopWidth: 1,\n    paddingTop: 12,\n  },\n  inputRow: {\n    flexDirection: \"row\",\n    alignItems: \"flex-end\",\n    gap: 8,\n  },\n  input: {\n    flex: 1,\n    borderWidth: 1,\n    padding: 8,\n    fontSize: 14,\n    minHeight: 36,\n    maxHeight: 100,\n  },\n  sendButton: {\n    padding: 8,\n    justifyContent: \"center\",\n    alignItems: \"center\",\n  },\n});\n{{else}}\nimport { useRef, useEffect, useState } from \"react\";\nimport {\n  View,\n  Text,\n  TextInput,\n  TouchableOpacity,\n  ScrollView,\n  KeyboardAvoidingView,\n  Platform,\n  StyleSheet,\n} from \"react-native\";\nimport { useChat } from \"@ai-sdk/react\";\nimport { DefaultChatTransport } from \"ai\";\nimport { fetch as expoFetch } from \"expo/fetch\";\nimport { Ionicons } from \"@expo/vector-icons\";\nimport { Container } from \"@/components/container\";\nimport { useColorScheme } from \"@/lib/use-color-scheme\";\nimport { NAV_THEME } from \"@/lib/constants\";\nimport { env } from \"@{{projectName}}/env/native\";\n\nconst generateAPIUrl = (relativePath: string) => {\n  const serverUrl = env.EXPO_PUBLIC_SERVER_URL;\n  if (!serverUrl) {\n    throw new Error(\n      \"EXPO_PUBLIC_SERVER_URL environment variable is not defined\"\n    );\n  }\n  const path = relativePath.startsWith(\"/\") ? relativePath : `/${relativePath}`;\n  return serverUrl.concat(path);\n};\n\nexport default function AIScreen() {\n  const { colorScheme } = useColorScheme();\n  const theme = colorScheme === \"dark\" ? NAV_THEME.dark : NAV_THEME.light;\n  const [input, setInput] = useState(\"\");\n  const { messages, error, sendMessage } = useChat({\n    transport: new DefaultChatTransport({\n      fetch: expoFetch as unknown as typeof globalThis.fetch,\n      api: generateAPIUrl(\"/ai\"),\n    }),\n    onError: (error) => console.error(error, \"AI Chat Error\"),\n  });\n  const scrollViewRef = useRef<ScrollView>(null);\n\n  useEffect(() => {\n    scrollViewRef.current?.scrollToEnd({ animated: true });\n  }, [messages]);\n\n  function onSubmit() {\n    const value = input.trim();\n    if (value) {\n      sendMessage({ text: value });\n      setInput(\"\");\n    }\n  }\n\n  if (error) {\n    return (\n      <Container>\n        <View style={styles.errorContainer}>\n          <View style={[styles.errorCard, { backgroundColor: theme.notification + \"20\", borderColor: theme.notification }]}>\n            <Text style={[styles.errorTitle, { color: theme.notification }]}>\n              Error: {error.message}\n            </Text>\n            <Text style={[styles.errorText, { color: theme.text, opacity: 0.7 }]}>\n              Please check your connection and try again.\n            </Text>\n          </View>\n        </View>\n      </Container>\n    );\n  }\n\n  return (\n    <Container>\n      <KeyboardAvoidingView\n        style={styles.container}\n        behavior={Platform.OS === \"ios\" ? \"padding\" : \"height\"}\n      >\n        <View style={styles.content}>\n          <View style={styles.header}>\n            <Text style={[styles.headerTitle, { color: theme.text }]}>\n              AI Chat\n            </Text>\n            <Text style={[styles.headerSubtitle, { color: theme.text, opacity: 0.7 }]}>\n              Chat with our AI assistant\n            </Text>\n          </View>\n          <ScrollView\n            ref={scrollViewRef}\n            style={styles.scrollView}\n            showsVerticalScrollIndicator={false}\n          >\n            {messages.length === 0 ? (\n              <View style={styles.emptyContainer}>\n                <Text style={[styles.emptyText, { color: theme.text, opacity: 0.7 }]}>\n                  Ask me anything to get started!\n                </Text>\n              </View>\n            ) : (\n              <View style={styles.messagesList}>\n                {messages.map((message) => (\n                  <View\n                    key={message.id}\n                    style={[\n                      styles.messageCard,\n                      {\n                        backgroundColor: message.role === \"user\"\n                          ? theme.primary + \"20\"\n                          : theme.card,\n                        borderColor: theme.border,\n                        alignSelf: message.role === \"user\" ? \"flex-end\" : \"flex-start\",\n                        marginLeft: message.role === \"user\" ? 32 : 0,\n                        marginRight: message.role === \"user\" ? 0 : 32,\n                      },\n                    ]}\n                  >\n                    <Text style={[styles.messageRole, { color: theme.text }]}>\n                      {message.role === \"user\" ? \"You\" : \"AI Assistant\"}\n                    </Text>\n                    <View style={styles.messageParts}>\n                      {message.parts.map((part, i) =>\n                        part.type === \"text\" ? (\n                          <Text\n                            key={`${message.id}-${i}`}\n                            style={[styles.messageText, { color: theme.text }]}\n                          >\n                            {part.text}\n                          </Text>\n                        ) : (\n                          <Text\n                            key={`${message.id}-${i}`}\n                            style={[styles.messageText, { color: theme.text }]}\n                          >\n                            {JSON.stringify(part)}\n                          </Text>\n                        )\n                      )}\n                    </View>\n                  </View>\n                ))}\n              </View>\n            )}\n          </ScrollView>\n          <View style={[styles.inputContainer, { borderTopColor: theme.border }]}>\n            <View style={styles.inputRow}>\n              <TextInput\n                value={input}\n                onChangeText={setInput}\n                placeholder=\"Type your message...\"\n                placeholderTextColor={theme.text}\n                style={[\n                  styles.input,\n                  {\n                    color: theme.text,\n                    borderColor: theme.border,\n                    backgroundColor: theme.background,\n                  },\n                ]}\n                onSubmitEditing={(e) => {\n                  e.preventDefault();\n                  onSubmit();\n                }}\n                autoFocus={true}\n                multiline\n              />\n              <TouchableOpacity\n                onPress={onSubmit}\n                disabled={!input.trim()}\n                style={[\n                  styles.sendButton,\n                  {\n                    backgroundColor: input.trim() ? theme.primary : theme.border,\n                    opacity: input.trim() ? 1 : 0.5,\n                  },\n                ]}\n              >\n                <Ionicons\n                  name=\"send\"\n                  size={20}\n                  color=\"#ffffff\"\n                />\n              </TouchableOpacity>\n            </View>\n          </View>\n        </View>\n      </KeyboardAvoidingView>\n    </Container>\n  );\n}\n\nconst styles = StyleSheet.create({\n  container: {\n    flex: 1,\n  },\n  content: {\n    flex: 1,\n    padding: 16,\n  },\n  header: {\n    marginBottom: 16,\n  },\n  headerTitle: {\n    fontSize: 20,\n    fontWeight: \"bold\",\n    marginBottom: 4,\n  },\n  headerSubtitle: {\n    fontSize: 14,\n  },\n  scrollView: {\n    flex: 1,\n    marginBottom: 16,\n  },\n  emptyContainer: {\n    flex: 1,\n    justifyContent: \"center\",\n    alignItems: \"center\",\n  },\n  emptyText: {\n    fontSize: 16,\n    textAlign: \"center\",\n  },\n  messagesList: {\n    gap: 8,\n    paddingBottom: 16,\n  },\n  messageCard: {\n    borderWidth: 1,\n    padding: 12,\n    maxWidth: \"80%\",\n  },\n  messageRole: {\n    fontSize: 12,\n    fontWeight: \"bold\",\n    marginBottom: 4,\n  },\n  messageParts: {\n    gap: 4,\n  },\n  messageText: {\n    fontSize: 14,\n    lineHeight: 20,\n  },\n  inputContainer: {\n    borderTopWidth: 1,\n    paddingTop: 12,\n  },\n  inputRow: {\n    flexDirection: \"row\",\n    alignItems: \"flex-end\",\n    gap: 8,\n  },\n  input: {\n    flex: 1,\n    borderWidth: 1,\n    padding: 8,\n    fontSize: 14,\n    minHeight: 36,\n    maxHeight: 100,\n  },\n  sendButton: {\n    padding: 8,\n    justifyContent: \"center\",\n    alignItems: \"center\",\n  },\n  errorContainer: {\n    flex: 1,\n    justifyContent: \"center\",\n    alignItems: \"center\",\n    padding: 16,\n  },\n  errorCard: {\n    borderWidth: 1,\n    padding: 16,\n  },\n  errorTitle: {\n    fontSize: 16,\n    fontWeight: \"bold\",\n    marginBottom: 8,\n    textAlign: \"center\",\n  },\n  errorText: {\n    fontSize: 14,\n    textAlign: \"center\",\n  },\n});\n{{/if}}\n"
  },
  {
    "path": "packages/template-generator/templates/examples/ai/native/bare/polyfills.js",
    "content": "import structuredClone from \"@ungap/structured-clone\";\nimport { Platform } from \"react-native\";\n\nif (Platform.OS !== \"web\") {\n  const setupPolyfills = async () => {\n    const { polyfillGlobal } = await import(\"react-native/Libraries/Utilities/PolyfillFunctions\");\n\n    const { TextEncoderStream, TextDecoderStream } =\n      await import(\"@stardazed/streams-text-encoding\");\n\n    if (!(\"structuredClone\" in global)) {\n      polyfillGlobal(\"structuredClone\", () => structuredClone);\n    }\n\n    polyfillGlobal(\"TextEncoderStream\", () => TextEncoderStream);\n    polyfillGlobal(\"TextDecoderStream\", () => TextDecoderStream);\n  };\n\n  setupPolyfills();\n}\n\nexport {};\n"
  },
  {
    "path": "packages/template-generator/templates/examples/ai/native/unistyles/app/(drawer)/ai.tsx.hbs",
    "content": "{{#if (eq backend \"convex\")}}\nimport { Ionicons } from \"@expo/vector-icons\";\nimport {\n  useUIMessages,\n  useSmoothText,\n  type UIMessage,\n} from \"@convex-dev/agent/react\";\nimport { api } from \"@{{projectName}}/backend/convex/_generated/api\";\nimport { useMutation } from \"convex/react\";\nimport React, { useRef, useEffect, useState } from \"react\";\nimport {\n  View,\n  Text,\n  TextInput,\n  TouchableOpacity,\n  ScrollView,\n  KeyboardAvoidingView,\n  Platform,\n  ActivityIndicator,\n} from \"react-native\";\nimport { StyleSheet, useUnistyles } from \"react-native-unistyles\";\n\nimport { Container } from \"@/components/container\";\n\nfunction MessageContent({\n  text,\n  isStreaming,\n  style,\n}: {\n  text: string;\n  isStreaming: boolean;\n  style: object;\n}) {\n  const [visibleText] = useSmoothText(text, {\n    startStreaming: isStreaming,\n  });\n  return <Text style={style}>{visibleText}</Text>;\n}\n\nexport default function AIScreen() {\n  const { theme } = useUnistyles();\n  const [input, setInput] = useState(\"\");\n  const [threadId, setThreadId] = useState<string | null>(null);\n  const [isLoading, setIsLoading] = useState(false);\n  const scrollViewRef = useRef<ScrollView>(null);\n\n  const createThread = useMutation(api.chat.createNewThread);\n  const sendMessage = useMutation(api.chat.sendMessage);\n\n  const { results: messages } = useUIMessages(\n    api.chat.listMessages,\n    threadId ? { threadId } : \"skip\",\n    { initialNumItems: 50, stream: true },\n  );\n\n  const hasStreamingMessage = messages?.some(\n    (m: UIMessage) => m.status === \"streaming\",\n  );\n\n  useEffect(() => {\n    scrollViewRef.current?.scrollToEnd({ animated: true });\n  }, [messages]);\n\n  const onSubmit = async () => {\n    const value = input.trim();\n    if (!value || isLoading) return;\n\n    setIsLoading(true);\n    setInput(\"\");\n\n    try {\n      let currentThreadId = threadId;\n      if (!currentThreadId) {\n        currentThreadId = await createThread();\n        setThreadId(currentThreadId);\n      }\n\n      await sendMessage({ threadId: currentThreadId, prompt: value });\n    } catch (error) {\n      console.error(\"Failed to send message:\", error);\n    } finally {\n      setIsLoading(false);\n    }\n  };\n\n  return (\n    <Container>\n      <KeyboardAvoidingView\n        style={styles.container}\n        behavior={Platform.OS === \"ios\" ? \"padding\" : \"height\"}\n      >\n        <View style={styles.content}>\n          <View style={styles.header}>\n            <Text style={styles.headerTitle}>AI Chat</Text>\n            <Text style={styles.headerSubtitle}>\n              Chat with our AI assistant\n            </Text>\n          </View>\n\n          <ScrollView\n            ref={scrollViewRef}\n            style={styles.messagesContainer}\n            showsVerticalScrollIndicator={false}\n          >\n            {!messages || messages.length === 0 ? (\n              <View style={styles.emptyContainer}>\n                <Text style={styles.emptyText}>\n                  Ask me anything to get started!\n                </Text>\n              </View>\n            ) : (\n              <View style={styles.messagesWrapper}>\n                {messages.map((message: UIMessage) => (\n                  <View\n                    key={message.key}\n                    style={[\n                      styles.messageContainer,\n                      message.role === \"user\"\n                        ? styles.userMessage\n                        : styles.assistantMessage,\n                    ]}\n                  >\n                    <Text style={styles.messageRole}>\n                      {message.role === \"user\" ? \"You\" : \"AI Assistant\"}\n                    </Text>\n                    <MessageContent\n                      text={message.text ?? \"\"}\n                      isStreaming={message.status === \"streaming\"}\n                      style={styles.messageContent}\n                    />\n                  </View>\n                ))}\n                {isLoading && !hasStreamingMessage && (\n                  <View style={[styles.messageContainer, styles.assistantMessage]}>\n                    <Text style={styles.messageRole}>AI Assistant</Text>\n                    <View style={styles.loadingContainer}>\n                      <ActivityIndicator size=\"small\" color={theme.colors.primary} />\n                      <Text style={styles.loadingText}>Thinking...</Text>\n                    </View>\n                  </View>\n                )}\n              </View>\n            )}\n          </ScrollView>\n\n          <View style={styles.inputSection}>\n            <View style={styles.inputContainer}>\n              <TextInput\n                value={input}\n                onChangeText={setInput}\n                placeholder=\"Type your message...\"\n                placeholderTextColor={theme.colors.border}\n                style={styles.textInput}\n                onSubmitEditing={(e) => {\n                  e.preventDefault();\n                  onSubmit();\n                }}\n                editable={!isLoading}\n                autoFocus={true}\n              />\n              <TouchableOpacity\n                onPress={onSubmit}\n                disabled={!input.trim() || isLoading}\n                style={[\n                  styles.sendButton,\n                  (!input.trim() || isLoading) && styles.sendButtonDisabled,\n                ]}\n              >\n                <Ionicons\n                  name=\"send\"\n                  size={20}\n                  color={\n                    input.trim() && !isLoading\n                      ? theme.colors.background\n                      : theme.colors.border\n                  }\n                />\n              </TouchableOpacity>\n            </View>\n          </View>\n        </View>\n      </KeyboardAvoidingView>\n    </Container>\n  );\n}\n\nconst styles = StyleSheet.create((theme) => ({\n  container: {\n    flex: 1,\n  },\n  content: {\n    flex: 1,\n    paddingHorizontal: theme.spacing.md,\n    paddingVertical: theme.spacing.lg,\n  },\n  header: {\n    marginBottom: theme.spacing.lg,\n  },\n  headerTitle: {\n    fontSize: 28,\n    fontWeight: \"bold\",\n    color: theme.colors.typography,\n    marginBottom: theme.spacing.sm,\n  },\n  headerSubtitle: {\n    fontSize: 16,\n    color: theme.colors.typography,\n  },\n  messagesContainer: {\n    flex: 1,\n    marginBottom: theme.spacing.md,\n  },\n  emptyContainer: {\n    flex: 1,\n    justifyContent: \"center\",\n    alignItems: \"center\",\n  },\n  emptyText: {\n    textAlign: \"center\",\n    color: theme.colors.typography,\n    fontSize: 18,\n  },\n  messagesWrapper: {\n    gap: theme.spacing.md,\n  },\n  messageContainer: {\n    padding: theme.spacing.md,\n    borderRadius: 8,\n  },\n  userMessage: {\n    backgroundColor: theme.colors.primary + \"20\",\n    marginLeft: theme.spacing.xl,\n    alignSelf: \"flex-end\",\n  },\n  assistantMessage: {\n    backgroundColor: theme.colors.background,\n    marginRight: theme.spacing.xl,\n    borderWidth: 1,\n    borderColor: theme.colors.border,\n  },\n  messageRole: {\n    fontSize: 14,\n    fontWeight: \"600\",\n    marginBottom: theme.spacing.sm,\n    color: theme.colors.typography,\n  },\n  messageContent: {\n    color: theme.colors.typography,\n    lineHeight: 20,\n  },\n  loadingContainer: {\n    flexDirection: \"row\",\n    alignItems: \"center\",\n    gap: theme.spacing.sm,\n  },\n  loadingText: {\n    color: theme.colors.typography,\n    opacity: 0.7,\n  },\n  inputSection: {\n    borderTopWidth: 1,\n    borderTopColor: theme.colors.border,\n    paddingTop: theme.spacing.md,\n  },\n  inputContainer: {\n    flexDirection: \"row\",\n    alignItems: \"flex-end\",\n    gap: theme.spacing.sm,\n  },\n  textInput: {\n    flex: 1,\n    borderWidth: 1,\n    borderColor: theme.colors.border,\n    borderRadius: 8,\n    paddingHorizontal: theme.spacing.md,\n    paddingVertical: theme.spacing.sm,\n    color: theme.colors.typography,\n    backgroundColor: theme.colors.background,\n    fontSize: 16,\n    minHeight: 40,\n    maxHeight: 120,\n  },\n  sendButton: {\n    backgroundColor: theme.colors.primary,\n    padding: theme.spacing.sm,\n    borderRadius: 8,\n    justifyContent: \"center\",\n    alignItems: \"center\",\n  },\n  sendButtonDisabled: {\n    backgroundColor: theme.colors.border,\n  },\n}));\n{{else}}\nimport React, { useRef, useEffect, useState } from \"react\";\nimport {\n  View,\n  Text,\n  TextInput,\n  TouchableOpacity,\n  ScrollView,\n  KeyboardAvoidingView,\n  Platform,\n} from \"react-native\";\nimport { useChat } from \"@ai-sdk/react\";\nimport { DefaultChatTransport } from \"ai\";\nimport { fetch as expoFetch } from \"expo/fetch\";\nimport { Ionicons } from \"@expo/vector-icons\";\nimport { StyleSheet, useUnistyles } from \"react-native-unistyles\";\nimport { Container } from \"@/components/container\";\nimport { env } from \"@{{projectName}}/env/native\";\n\nconst generateAPIUrl = (relativePath: string) => {\n  const serverUrl = env.EXPO_PUBLIC_SERVER_URL;\n  if (!serverUrl) {\n    throw new Error(\n      \"EXPO_PUBLIC_SERVER_URL environment variable is not defined\"\n    );\n  }\n  const path = relativePath.startsWith(\"/\") ? relativePath : `/${relativePath}`;\n  return serverUrl.concat(path);\n};\n\nexport default function AIScreen() {\n  const { theme } = useUnistyles();\n  const [input, setInput] = useState(\"\");\n  const { messages, error, sendMessage } = useChat({\n    transport: new DefaultChatTransport({\n      fetch: expoFetch as unknown as typeof globalThis.fetch,\n      api: generateAPIUrl(\"/ai\"),\n    }),\n    onError: (error) => console.error(error, \"AI Chat Error\"),\n  });\n\n  const scrollViewRef = useRef<ScrollView>(null);\n\n  useEffect(() => {\n    scrollViewRef.current?.scrollToEnd({ animated: true });\n  }, [messages]);\n\n  const onSubmit = () => {\n    const value = input.trim();\n    if (value) {\n      sendMessage({ text: value });\n      setInput(\"\");\n    }\n  };\n\n  if (error) {\n    return (\n      <Container>\n        <View style={styles.errorContainer}>\n          <Text style={styles.errorText}>Error: {error.message}</Text>\n          <Text style={styles.errorSubtext}>\n            Please check your connection and try again.\n          </Text>\n        </View>\n      </Container>\n    );\n  }\n\n  return (\n    <Container>\n      <KeyboardAvoidingView\n        style={styles.container}\n        behavior={Platform.OS === \"ios\" ? \"padding\" : \"height\"}\n      >\n        <View style={styles.content}>\n          <View style={styles.header}>\n            <Text style={styles.headerTitle}>AI Chat</Text>\n            <Text style={styles.headerSubtitle}>\n              Chat with our AI assistant\n            </Text>\n          </View>\n\n          <ScrollView\n            ref={scrollViewRef}\n            style={styles.messagesContainer}\n            showsVerticalScrollIndicator={false}\n          >\n            {messages.length === 0 ? (\n              <View style={styles.emptyContainer}>\n                <Text style={styles.emptyText}>\n                  Ask me anything to get started!\n                </Text>\n              </View>\n            ) : (\n              <View style={styles.messagesWrapper}>\n                {messages.map((message) => (\n                  <View\n                    key={message.id}\n                    style={[\n                      styles.messageContainer,\n                      message.role === \"user\"\n                        ? styles.userMessage\n                        : styles.assistantMessage,\n                    ]}\n                  >\n                    <Text style={styles.messageRole}>\n                      {message.role === \"user\" ? \"You\" : \"AI Assistant\"}\n                    </Text>\n                    <View style={styles.messageContentWrapper}>\n                      {message.parts.map((part, i) => {\n                        if (part.type === \"text\") {\n                          return (\n                            <Text\n                              key={`${message.id}-${i}`}\n                              style={styles.messageContent}\n                            >\n                              {part.text}\n                            </Text>\n                          );\n                        }\n                        return (\n                          <Text\n                            key={`${message.id}-${i}`}\n                            style={styles.messageContent}\n                          >\n                            {JSON.stringify(part)}\n                          </Text>\n                        );\n                      })}\n                    </View>\n                  </View>\n                ))}\n              </View>\n            )}\n          </ScrollView>\n\n          <View style={styles.inputSection}>\n            <View style={styles.inputContainer}>\n              <TextInput\n                value={input}\n                onChangeText={setInput}\n                placeholder=\"Type your message...\"\n                placeholderTextColor={theme.colors.border}\n                style={styles.textInput}\n                onSubmitEditing={(e) => {\n                  e.preventDefault();\n                  onSubmit();\n                }}\n                autoFocus={true}\n              />\n              <TouchableOpacity\n                onPress={onSubmit}\n                disabled={!input.trim()}\n                style={[\n                  styles.sendButton,\n                  !input.trim() && styles.sendButtonDisabled,\n                ]}\n              >\n                <Ionicons\n                  name=\"send\"\n                  size={20}\n                  color={\n                    input.trim()\n                      ? theme.colors.background\n                      : theme.colors.border\n                  }\n                />\n              </TouchableOpacity>\n            </View>\n          </View>\n        </View>\n      </KeyboardAvoidingView>\n    </Container>\n  );\n}\n\nconst styles = StyleSheet.create((theme) => ({\n  container: {\n    flex: 1,\n  },\n  content: {\n    flex: 1,\n    paddingHorizontal: theme.spacing.md,\n    paddingVertical: theme.spacing.lg,\n  },\n  errorContainer: {\n    flex: 1,\n    justifyContent: \"center\",\n    alignItems: \"center\",\n    paddingHorizontal: theme.spacing.md,\n  },\n  errorText: {\n    color: theme.colors.destructive,\n    textAlign: \"center\",\n    fontSize: 18,\n    marginBottom: theme.spacing.md,\n  },\n  errorSubtext: {\n    color: theme.colors.typography,\n    textAlign: \"center\",\n    fontSize: 16,\n  },\n  header: {\n    marginBottom: theme.spacing.lg,\n  },\n  headerTitle: {\n    fontSize: 28,\n    fontWeight: \"bold\",\n    color: theme.colors.typography,\n    marginBottom: theme.spacing.sm,\n  },\n  headerSubtitle: {\n    fontSize: 16,\n    color: theme.colors.typography,\n  },\n  messagesContainer: {\n    flex: 1,\n    marginBottom: theme.spacing.md,\n  },\n  emptyContainer: {\n    flex: 1,\n    justifyContent: \"center\",\n    alignItems: \"center\",\n  },\n  emptyText: {\n    textAlign: \"center\",\n    color: theme.colors.typography,\n    fontSize: 18,\n  },\n  messagesWrapper: {\n    gap: theme.spacing.md,\n  },\n  messageContainer: {\n    padding: theme.spacing.md,\n    borderRadius: 8,\n  },\n  userMessage: {\n    backgroundColor: theme.colors.primary + \"20\",\n    marginLeft: theme.spacing.xl,\n    alignSelf: \"flex-end\",\n  },\n  assistantMessage: {\n    backgroundColor: theme.colors.background,\n    marginRight: theme.spacing.xl,\n    borderWidth: 1,\n    borderColor: theme.colors.border,\n  },\n  messageRole: {\n    fontSize: 14,\n    fontWeight: \"600\",\n    marginBottom: theme.spacing.sm,\n    color: theme.colors.typography,\n  },\n  messageContentWrapper: {\n    gap: theme.spacing.xs,\n  },\n  messageContent: {\n    color: theme.colors.typography,\n    lineHeight: 20,\n  },\n  inputSection: {\n    borderTopWidth: 1,\n    borderTopColor: theme.colors.border,\n    paddingTop: theme.spacing.md,\n  },\n  inputContainer: {\n    flexDirection: \"row\",\n    alignItems: \"flex-end\",\n    gap: theme.spacing.sm,\n  },\n  textInput: {\n    flex: 1,\n    borderWidth: 1,\n    borderColor: theme.colors.border,\n    borderRadius: 8,\n    paddingHorizontal: theme.spacing.md,\n    paddingVertical: theme.spacing.sm,\n    color: theme.colors.typography,\n    backgroundColor: theme.colors.background,\n    fontSize: 16,\n    minHeight: 40,\n    maxHeight: 120,\n  },\n  sendButton: {\n    backgroundColor: theme.colors.primary,\n    padding: theme.spacing.sm,\n    borderRadius: 8,\n    justifyContent: \"center\",\n    alignItems: \"center\",\n  },\n  sendButtonDisabled: {\n    backgroundColor: theme.colors.border,\n  },\n}));\n{{/if}}\n"
  },
  {
    "path": "packages/template-generator/templates/examples/ai/native/unistyles/polyfills.js",
    "content": "import structuredClone from \"@ungap/structured-clone\";\nimport { Platform } from \"react-native\";\n\nif (Platform.OS !== \"web\") {\n  const setupPolyfills = async () => {\n    const { polyfillGlobal } = await import(\"react-native/Libraries/Utilities/PolyfillFunctions\");\n\n    const { TextEncoderStream, TextDecoderStream } =\n      await import(\"@stardazed/streams-text-encoding\");\n\n    if (!(\"structuredClone\" in global)) {\n      polyfillGlobal(\"structuredClone\", () => structuredClone);\n    }\n\n    polyfillGlobal(\"TextEncoderStream\", () => TextEncoderStream);\n    polyfillGlobal(\"TextDecoderStream\", () => TextDecoderStream);\n  };\n\n  setupPolyfills();\n}\n\nexport {};\n"
  },
  {
    "path": "packages/template-generator/templates/examples/ai/native/uniwind/app/(drawer)/ai.tsx.hbs",
    "content": "{{#if (eq backend \"convex\")}}\nimport { Ionicons } from \"@expo/vector-icons\";\nimport {\n  useUIMessages,\n  useSmoothText,\n  type UIMessage,\n} from \"@convex-dev/agent/react\";\nimport { api } from \"@{{projectName}}/backend/convex/_generated/api\";\nimport { useMutation } from \"convex/react\";\nimport { Button, Separator, Spinner, Surface, Input, TextField, useThemeColor } from \"heroui-native\";\nimport { useRef, useEffect, useState } from \"react\";\nimport {\n  View,\n  Text,\n  ScrollView,\n  KeyboardAvoidingView,\n  Platform,\n} from \"react-native\";\n\nimport { Container } from \"@/components/container\";\n\nfunction MessageContent({\n  text,\n  isStreaming,\n}: {\n  text: string;\n  isStreaming: boolean;\n}) {\n  const [visibleText] = useSmoothText(text, {\n    startStreaming: isStreaming,\n  });\n  return <Text className=\"text-foreground text-sm leading-relaxed\">{visibleText}</Text>;\n}\n\nexport default function AIScreen() {\n  const [input, setInput] = useState(\"\");\n  const [threadId, setThreadId] = useState<string | null>(null);\n  const [isLoading, setIsLoading] = useState(false);\n  const scrollViewRef = useRef<ScrollView>(null);\n  const mutedColor = useThemeColor(\"muted\");\n  const foregroundColor = useThemeColor(\"foreground\");\n\n  const createThread = useMutation(api.chat.createNewThread);\n  const sendMessage = useMutation(api.chat.sendMessage);\n\n  const { results: messages } = useUIMessages(\n    api.chat.listMessages,\n    threadId ? { threadId } : \"skip\",\n    { initialNumItems: 50, stream: true },\n  );\n\n  const hasStreamingMessage = messages?.some(\n    (m: UIMessage) => m.status === \"streaming\",\n  );\n\n  useEffect(() => {\n    scrollViewRef.current?.scrollToEnd({ animated: true });\n  }, [messages]);\n\n  const onSubmit = async () => {\n    const value = input.trim();\n    if (!value || isLoading) return;\n\n    setIsLoading(true);\n    setInput(\"\");\n\n    try {\n      let currentThreadId = threadId;\n      if (!currentThreadId) {\n        currentThreadId = await createThread();\n        setThreadId(currentThreadId);\n      }\n\n      await sendMessage({ threadId: currentThreadId, prompt: value });\n    } catch (error) {\n      console.error(\"Failed to send message:\", error);\n    } finally {\n      setIsLoading(false);\n    }\n  };\n\n  return (\n    <Container isScrollable={false}>\n      <KeyboardAvoidingView\n        className=\"flex-1\"\n        behavior={Platform.OS === \"ios\" ? \"padding\" : \"height\"}\n      >\n        <View className=\"flex-1 px-4 py-4\">\n          <View className=\"py-4 mb-4\">\n            <Text className=\"text-2xl font-semibold text-foreground tracking-tight\">AI Chat</Text>\n            <Text className=\"text-muted text-sm mt-1\">Chat with our AI assistant</Text>\n          </View>\n\n          <ScrollView\n            ref={scrollViewRef}\n            className=\"flex-1 mb-4\"\n            showsVerticalScrollIndicator={false}\n            contentContainerStyle=\\{{ flexGrow: 1, paddingBottom: 8 }}\n            keyboardShouldPersistTaps=\"handled\"\n          >\n            {!messages || messages.length === 0 ? (\n              <Surface variant=\"secondary\" className=\"flex-1 justify-center items-center py-8 rounded-xl\">\n                <Ionicons name=\"chatbubble-ellipses-outline\" size={32} color={mutedColor} />\n                <Text className=\"text-muted text-sm mt-3\">Ask me anything to get started</Text>\n              </Surface>\n            ) : (\n              <View className=\"gap-3\">\n                {messages.map((message: UIMessage) => (\n                  <Surface\n                    key={message.key}\n                    variant={message.role === \"user\" ? \"tertiary\" : \"secondary\"}\n                    className={`p-3 rounded-xl ${message.role === \"user\" ? \"ml-8\" : \"mr-8\"}`}\n                  >\n                    <Text className=\"text-xs font-medium mb-1 text-muted\">\n                      {message.role === \"user\" ? \"You\" : \"AI\"}\n                    </Text>\n                    <MessageContent\n                      text={message.text ?? \"\"}\n                      isStreaming={message.status === \"streaming\"}\n                    />\n                  </Surface>\n                ))}\n                {isLoading && !hasStreamingMessage && (\n                  <Surface variant=\"secondary\" className=\"p-3 mr-10 rounded-lg\">\n                    <Text className=\"text-xs font-medium mb-1 text-muted\">AI</Text>\n                    <View className=\"flex-row items-center gap-2\">\n                      <Spinner size=\"sm\" />\n                      <Text className=\"text-muted text-sm\">Thinking...</Text>\n                    </View>\n                  </Surface>\n                )}\n              </View>\n            )}\n          </ScrollView>\n\n          <Separator className=\"mb-3\" />\n\n          <View className=\"flex-row items-center gap-2\">\n            <View className=\"flex-1\">\n                <TextField>\n                  <Input\n                    value={input}\n                    onChangeText={setInput}\n                    placeholder=\"Type a message...\"\n                    onSubmitEditing={onSubmit}\n                    editable={!isLoading}\n                    returnKeyType=\"send\"\n                  />\n                </TextField>\n              </View>\n            <Button\n              isIconOnly\n              variant={input.trim() && !isLoading ? \"primary\" : \"secondary\"}\n              onPress={onSubmit}\n              isDisabled={!input.trim() || isLoading}\n              size=\"sm\"\n            >\n              <Ionicons\n                name=\"arrow-up\"\n                size={18}\n                color={input.trim() && !isLoading ? foregroundColor : mutedColor}\n              />\n            </Button>\n          </View>\n        </View>\n      </KeyboardAvoidingView>\n    </Container>\n  );\n}\n{{else}}\nimport { useRef, useEffect, useState } from \"react\";\nimport {\n  View,\n  Text,\n  ScrollView,\n  KeyboardAvoidingView,\n  Platform,\n} from \"react-native\";\nimport { useChat } from \"@ai-sdk/react\";\nimport { DefaultChatTransport } from \"ai\";\nimport { fetch as expoFetch } from \"expo/fetch\";\nimport { Ionicons } from \"@expo/vector-icons\";\nimport { Container } from \"@/components/container\";\nimport { Button, Separator, FieldError, Spinner, Surface, Input, TextField, useThemeColor } from \"heroui-native\";\nimport { env } from \"@{{projectName}}/env/native\";\n\nconst generateAPIUrl = (relativePath: string) => {\n  const serverUrl = env.EXPO_PUBLIC_SERVER_URL;\n  if (!serverUrl) {\n    throw new Error(\n      \"EXPO_PUBLIC_SERVER_URL environment variable is not defined\"\n    );\n  }\n  const path = relativePath.startsWith(\"/\") ? relativePath : `/${relativePath}`;\n  return serverUrl.concat(path);\n};\n\nexport default function AIScreen() {\n  const [input, setInput] = useState(\"\");\n  const { messages, error, sendMessage, status } = useChat({\n    transport: new DefaultChatTransport({\n      fetch: expoFetch as unknown as typeof globalThis.fetch,\n      api: generateAPIUrl(\"/ai\"),\n    }),\n    onError: (error) => console.error(error, \"AI Chat Error\"),\n  });\n  const scrollViewRef = useRef<ScrollView>(null);\n  const foregroundColor = useThemeColor(\"foreground\");\n  const mutedColor = useThemeColor(\"muted\");\n  const isBusy = status === \"submitted\" || status === \"streaming\";\n\n  useEffect(() => {\n    scrollViewRef.current?.scrollToEnd({ animated: true });\n  }, [messages]);\n\n  const onSubmit = () => {\n    const value = input.trim();\n    if (value && !isBusy) {\n      sendMessage({ text: value });\n      setInput(\"\");\n    }\n  };\n\n  if (error) {\n    return (\n      <Container isScrollable={false}>\n        <View className=\"flex-1 justify-center items-center px-4\">\n          <Surface variant=\"secondary\" className=\"p-4 rounded-lg\">\n            <FieldError isInvalid>\n              <Text className=\"text-danger text-center font-medium mb-1\">\n                {error.message}\n              </Text>\n              <Text className=\"text-muted text-center text-xs\">\n                Please check your connection and try again.\n              </Text>\n            </FieldError>\n          </Surface>\n        </View>\n      </Container>\n    );\n  }\n\n  return (\n    <Container isScrollable={false}>\n      <KeyboardAvoidingView\n        className=\"flex-1\"\n        behavior={Platform.OS === \"ios\" ? \"padding\" : \"height\"}\n      >\n        <View className=\"flex-1 px-4 py-4\">\n          <View className=\"py-4 mb-4\">\n            <Text className=\"text-2xl font-semibold text-foreground tracking-tight\">AI Chat</Text>\n            <Text className=\"text-muted text-sm mt-1\">Chat with our AI assistant</Text>\n          </View>\n\n          <ScrollView\n            ref={scrollViewRef}\n            className=\"flex-1 mb-4\"\n            showsVerticalScrollIndicator={false}\n            contentContainerStyle=\\{{ flexGrow: 1, paddingBottom: 8 }}\n            keyboardShouldPersistTaps=\"handled\"\n          >\n            {messages.length === 0 ? (\n              <Surface variant=\"secondary\" className=\"flex-1 justify-center items-center py-8 rounded-xl\">\n                <Ionicons name=\"chatbubble-ellipses-outline\" size={32} color={mutedColor} />\n                <Text className=\"text-muted text-sm mt-3\">Ask me anything to get started</Text>\n              </Surface>\n            ) : (\n              <View className=\"gap-3\">\n                {messages.map((message) => (\n                  <Surface\n                    key={message.id}\n                    variant={message.role === \"user\" ? \"tertiary\" : \"secondary\"}\n                    className={`p-3 rounded-xl ${message.role === \"user\" ? \"ml-8\" : \"mr-8\"}`}\n                  >\n                    <Text className=\"text-xs font-medium mb-1 text-muted\">\n                      {message.role === \"user\" ? \"You\" : \"AI\"}\n                    </Text>\n                    <View className=\"gap-1\">\n                      {message.parts.map((part, i) =>\n                        part.type === \"text\" ? (\n                          <Text\n                            key={`${message.id}-${i}`}\n                            className=\"text-foreground text-sm leading-relaxed\"\n                          >\n                            {part.text}\n                          </Text>\n                        ) : (\n                          <Text\n                            key={`${message.id}-${i}`}\n                            className=\"text-foreground text-sm leading-relaxed\"\n                          >\n                            {JSON.stringify(part)}\n                          </Text>\n                        )\n                      )}\n                    </View>\n                  </Surface>\n                ))}\n                {isBusy && (\n                  <Surface variant=\"secondary\" className=\"p-3 mr-8 rounded-xl\">\n                    <Text className=\"text-xs font-medium mb-1 text-muted\">AI</Text>\n                    <View className=\"flex-row items-center gap-2\">\n                      <Spinner size=\"sm\" />\n                      <Text className=\"text-muted text-sm\">Thinking...</Text>\n                    </View>\n                  </Surface>\n                )}\n              </View>\n            )}\n          </ScrollView>\n\n          <Separator className=\"mb-3\" />\n\n          <View className=\"flex-row items-center gap-2\">\n            <View className=\"flex-1\">\n              <TextField>\n                <Input\n                  value={input}\n                  onChangeText={setInput}\n                  placeholder=\"Type a message...\"\n                  onSubmitEditing={onSubmit}\n                  returnKeyType=\"send\"\n                  editable={!isBusy}\n                />\n              </TextField>\n            </View>\n            <Button\n              isIconOnly\n              variant={input.trim() && !isBusy ? \"primary\" : \"secondary\"}\n              onPress={onSubmit}\n              isDisabled={!input.trim() || isBusy}\n              size=\"sm\"\n            >\n              <Ionicons\n                name=\"arrow-up\"\n                size={18}\n                color={input.trim() && !isBusy ? foregroundColor : mutedColor}\n              />\n            </Button>\n          </View>\n        </View>\n      </KeyboardAvoidingView>\n    </Container>\n  );\n}\n{{/if}}\n"
  },
  {
    "path": "packages/template-generator/templates/examples/ai/native/uniwind/polyfills.js",
    "content": "import structuredClone from \"@ungap/structured-clone\";\nimport { Platform } from \"react-native\";\n\nif (Platform.OS !== \"web\") {\n  const setupPolyfills = async () => {\n    const { polyfillGlobal } = await import(\"react-native/Libraries/Utilities/PolyfillFunctions\");\n\n    const { TextEncoderStream, TextDecoderStream } =\n      await import(\"@stardazed/streams-text-encoding\");\n\n    if (!(\"structuredClone\" in global)) {\n      polyfillGlobal(\"structuredClone\", () => structuredClone);\n    }\n\n    polyfillGlobal(\"TextEncoderStream\", () => TextEncoderStream);\n    polyfillGlobal(\"TextDecoderStream\", () => TextDecoderStream);\n  };\n\n  setupPolyfills();\n}\n\nexport {};\n"
  },
  {
    "path": "packages/template-generator/templates/examples/ai/web/nuxt/app/pages/ai.vue.hbs",
    "content": "<script setup lang=\"ts\">\nimport { Chat } from '@ai-sdk/vue'\nimport type { UIMessage } from 'ai'\nimport { getTextFromMessage } from '@nuxt/ui/utils/ai'\nimport { DefaultChatTransport } from 'ai'\nimport { computed, ref } from 'vue'\n\nconst SUGGESTIONS = [\n  {\n    title: 'Plan a feature',\n    prompt: 'Help me break down a small product feature into implementation steps.'\n  },\n  {\n    title: 'Design the schema',\n    prompt: 'Suggest a database schema for a collaborative notes app.'\n  },\n  {\n    title: 'Add auth flow',\n    prompt: 'What is the cleanest way to add login, signup, and protected routes here?'\n  },\n  {\n    title: 'Deploy checklist',\n    prompt: 'Give me a production deployment checklist for this stack.'\n  }\n] as const\n\nconst messages: UIMessage[] = []\nconst input = ref('')\nconst aiApiUrl = {{#if (eq backend \"self\")}}'/api/ai'{{else}}`${useRuntimeConfig().public.serverUrl}/ai`{{/if}}\n\nconst chat = new Chat({\n  messages,\n  transport: new DefaultChatTransport({\n    api: aiApiUrl,\n  }),\n  onError(error) {\n    console.error('Chat error:', error)\n  }\n})\n\nconst hasMessages = computed(() => chat.messages.length > 0)\nconst isLoading = computed(() => chat.status === 'submitted' || chat.status === 'streaming')\n\nfunction applySuggestion(prompt: string) {\n  input.value = prompt\n}\n\nasync function handleSubmit(e: Event) {\n  e.preventDefault()\n  const userInput = input.value\n  input.value = ''\n\n  if (!userInput.trim()) return\n\n  chat.sendMessage({ text: userInput })\n}\n</script>\n\n<template>\n  <UContainer class=\"flex min-h-[calc(100vh-var(--ui-header-height)-2rem)] max-w-5xl flex-col py-4 sm:py-6\">\n    <div class=\"min-h-0 flex-1\">\n      <div v-if=\"!hasMessages\" class=\"flex h-full items-center\">\n        <div class=\"mx-auto flex w-full max-w-3xl flex-col gap-8\">\n          <div class=\"space-y-3 text-center\">\n            <UBadge label=\"AI Chat\" color=\"primary\" variant=\"subtle\" class=\"rounded-full\" />\n            <div class=\"space-y-2\">\n              <h1 class=\"text-3xl font-semibold tracking-tight text-highlighted sm:text-4xl\">\n                Ask the starter for your next move.\n              </h1>\n              <p class=\"mx-auto max-w-2xl text-sm leading-6 text-muted sm:text-base\">\n                Use the built-in chat to plan features, sketch schemas, or unblock implementation work without leaving the app.\n              </p>\n            </div>\n          </div>\n\n          <div class=\"grid gap-3 sm:grid-cols-2\">\n            <UButton\n              v-for=\"suggestion in SUGGESTIONS\"\n              :key=\"suggestion.title\"\n              color=\"neutral\"\n              variant=\"soft\"\n              class=\"h-auto justify-start rounded-2xl px-4 py-4 text-left\"\n              @click=\"applySuggestion(suggestion.prompt)\"\n            >\n              <div class=\"space-y-1\">\n                <div class=\"text-sm font-medium text-highlighted\">\\{{ suggestion.title }}</div>\n                <div class=\"text-sm leading-6 text-muted\">\\{{ suggestion.prompt }}</div>\n              </div>\n            </UButton>\n          </div>\n        </div>\n      </div>\n\n      <div v-else class=\"mx-auto flex h-full w-full max-w-3xl min-h-0 flex-col\">\n        <UChatMessages\n          :messages=\"chat.messages\"\n          :status=\"chat.status\"\n          :assistant=\"{\n            variant: 'outline',\n            avatar: {\n              icon: 'i-lucide-bot'\n            }\n          }\"\n          :user=\"{\n            variant: 'soft',\n            avatar: {\n              icon: 'i-lucide-user'\n            }\n          }\"\n          class=\"min-h-0 flex-1 px-1\"\n        >\n          <template #content=\"{ message }\">\n            <div class=\"whitespace-pre-wrap text-sm leading-6\">\\{{ getTextFromMessage(message) }}</div>\n          </template>\n        </UChatMessages>\n      </div>\n    </div>\n\n    <div class=\"sticky bottom-0 mt-4 border-t border-default bg-default pt-4\">\n      <div class=\"mx-auto w-full max-w-3xl\">\n        <UChatPrompt\n          v-model=\"input\"\n          icon=\"i-lucide-sparkles\"\n          variant=\"soft\"\n          :rows=\"1\"\n          :maxrows=\"8\"\n          :loading=\"isLoading\"\n          :error=\"chat.error\"\n          :placeholder=\"hasMessages ? 'Keep the conversation going...' : 'Ask about your app, schema, auth, or deployment...'\"\n          @submit=\"handleSubmit\"\n        >\n          <UChatPromptSubmit\n            class=\"ms-auto\"\n            :status=\"chat.status\"\n            @stop=\"() => chat.stop()\"\n            @reload=\"() => chat.regenerate()\"\n          />\n        </UChatPrompt>\n\n        <div class=\"mt-2 flex items-center justify-between gap-3 px-1 text-xs text-muted\">\n          <span>Press Enter to send and Shift+Enter for a new line.</span>\n          <span>\\{{ hasMessages ? `${chat.messages.length} messages` : 'Ready when you are.' }}</span>\n        </div>\n      </div>\n    </div>\n  </UContainer>\n</template>\n"
  },
  {
    "path": "packages/template-generator/templates/examples/ai/web/react/next/src/app/ai/page.tsx.hbs",
    "content": "{{#if (eq backend \"convex\")}}\n\"use client\";\n\nimport { api } from \"@{{projectName}}/backend/convex/_generated/api\";\nimport {\n  useUIMessages,\n  useSmoothText,\n  type UIMessage,\n} from \"@convex-dev/agent/react\";\nimport { useMutation } from \"convex/react\";\nimport { Send, Loader2 } from \"lucide-react\";\n{{#if (eq webDeploy \"cloudflare\")}}\nimport dynamic from \"next/dynamic\";\n\nconst Streamdown = dynamic(\n  () => import(\"streamdown\").then((mod) => ({ default: mod.Streamdown })),\n  {\n    loading: () => (\n      <div className=\"flex h-full items-center justify-center\">\n        <div className=\"text-muted-foreground\">Loading response...</div>\n      </div>\n    ),\n    ssr: false,\n  }\n);\n{{else}}\nimport { Streamdown } from \"streamdown\";\n{{/if}}\nimport { useEffect, useRef, useState, type FormEvent } from \"react\";\n\nimport { Button } from \"@{{projectName}}/ui/components/button\";\nimport { Input } from \"@{{projectName}}/ui/components/input\";\n\nfunction MessageContent({\n  text,\n  isStreaming,\n}: {\n  text: string;\n  isStreaming: boolean;\n}) {\n  const [visibleText] = useSmoothText(text, {\n    startStreaming: isStreaming,\n  });\n  return <Streamdown>{visibleText}</Streamdown>;\n}\n\nexport default function AIPage() {\n  const [input, setInput] = useState(\"\");\n  const [threadId, setThreadId] = useState<string | null>(null);\n  const [isLoading, setIsLoading] = useState(false);\n  const messagesEndRef = useRef<HTMLDivElement>(null);\n\n  const createThread = useMutation(api.chat.createNewThread);\n  const sendMessage = useMutation(api.chat.sendMessage);\n\n  const { results: messages } = useUIMessages(\n    api.chat.listMessages,\n    threadId ? { threadId } : \"skip\",\n    { initialNumItems: 50, stream: true },\n  );\n\n  useEffect(() => {\n    messagesEndRef.current?.scrollIntoView({ behavior: \"smooth\" });\n  }, [messages]);\n\n  const hasStreamingMessage = messages?.some(\n    (m: UIMessage) => m.status === \"streaming\",\n  );\n\n  const handleSubmit = async (e: FormEvent<HTMLFormElement>) => {\n    e.preventDefault();\n    const text = input.trim();\n    if (!text || isLoading) return;\n\n    setIsLoading(true);\n    setInput(\"\");\n\n    try {\n      let currentThreadId = threadId;\n      if (!currentThreadId) {\n        currentThreadId = await createThread();\n        setThreadId(currentThreadId);\n      }\n\n      await sendMessage({ threadId: currentThreadId, prompt: text });\n    } catch (error) {\n      console.error(\"Failed to send message:\", error);\n    } finally {\n      setIsLoading(false);\n    }\n  };\n\n  return (\n    <div className=\"grid grid-rows-[1fr_auto] overflow-hidden w-full mx-auto p-4\">\n      <div className=\"overflow-y-auto space-y-4 pb-4\">\n        {!messages || messages.length === 0 ? (\n          <div className=\"text-center text-muted-foreground mt-8\">\n            Ask me anything to get started!\n          </div>\n        ) : (\n          messages.map((message: UIMessage) => (\n            <div\n              key={message.key}\n              className={`p-3 rounded-lg ${\n                message.role === \"user\"\n                  ? \"bg-primary/10 ml-8\"\n                  : \"bg-secondary/20 mr-8\"\n              }`}\n            >\n              <p className=\"text-sm font-semibold mb-1\">\n                {message.role === \"user\" ? \"You\" : \"AI Assistant\"}\n              </p>\n              <MessageContent\n                text={message.text ?? \"\"}\n                isStreaming={message.status === \"streaming\"}\n              />\n            </div>\n          ))\n        )}\n        {isLoading && !hasStreamingMessage && (\n          <div className=\"p-3 rounded-lg bg-secondary/20 mr-8\">\n            <p className=\"text-sm font-semibold mb-1\">AI Assistant</p>\n            <div className=\"flex items-center gap-2 text-muted-foreground\">\n              <Loader2 className=\"h-4 w-4 animate-spin\" />\n              <span>Thinking...</span>\n            </div>\n          </div>\n        )}\n        <div ref={messagesEndRef} />\n      </div>\n\n      <form\n        onSubmit={handleSubmit}\n        className=\"w-full flex items-center space-x-2 pt-2 border-t\"\n      >\n        <Input\n          name=\"prompt\"\n          value={input}\n          onChange={(e) => setInput(e.target.value)}\n          placeholder=\"Type your message...\"\n          className=\"flex-1\"\n          autoComplete=\"off\"\n          autoFocus\n          disabled={isLoading}\n        />\n        <Button type=\"submit\" size=\"icon\" disabled={isLoading || !input.trim()}>\n          {isLoading ? (\n            <Loader2 className=\"h-4 w-4 animate-spin\" />\n          ) : (\n            <Send size={18} />\n          )}\n        </Button>\n      </form>\n    </div>\n  );\n}\n{{else}}\n\"use client\";\n\nimport { useChat } from \"@ai-sdk/react\";\nimport { DefaultChatTransport } from \"ai\";\nimport { Send } from \"lucide-react\";\n{{#if (eq webDeploy \"cloudflare\")}}\nimport dynamic from \"next/dynamic\";\n\nconst Streamdown = dynamic(\n  () => import(\"streamdown\").then((mod) => ({ default: mod.Streamdown })),\n  {\n    loading: () => (\n      <div className=\"flex h-full items-center justify-center\">\n        <div className=\"text-muted-foreground\">Loading response...</div>\n      </div>\n    ),\n    ssr: false,\n  }\n);\n{{else}}\nimport { Streamdown } from \"streamdown\";\n{{/if}}\nimport { useEffect, useRef, useState, type FormEvent } from \"react\";\n\nimport { Button } from \"@{{projectName}}/ui/components/button\";\nimport { Input } from \"@{{projectName}}/ui/components/input\";\nimport { env } from \"@{{projectName}}/env/web\";\n\nexport default function AIPage() {\n  const [input, setInput] = useState(\"\");\n  const { messages, sendMessage, status } = useChat({\n    transport: new DefaultChatTransport({\n      api: {{#if (eq backend \"self\")}}\"/api/ai\"{{else}}`${env.NEXT_PUBLIC_SERVER_URL}/ai`{{/if}},\n    }),\n  });\n\n  const messagesEndRef = useRef<HTMLDivElement>(null);\n\n  useEffect(() => {\n    messagesEndRef.current?.scrollIntoView({ behavior: \"smooth\" });\n  }, [messages]);\n\n  const handleSubmit = (e: FormEvent<HTMLFormElement>) => {\n    e.preventDefault();\n    const text = input.trim();\n    if (!text) return;\n    sendMessage({ text });\n    setInput(\"\");\n  };\n\n  return (\n    <div className=\"grid grid-rows-[1fr_auto] overflow-hidden w-full mx-auto p-4\">\n      <div className=\"overflow-y-auto space-y-4 pb-4\">\n        {messages.length === 0 ? (\n          <div className=\"text-center text-muted-foreground mt-8\">\n            Ask me anything to get started!\n          </div>\n        ) : (\n          messages.map((message) => (\n            <div\n              key={message.id}\n              className={`p-3 rounded-lg ${\n                message.role === \"user\"\n                  ? \"bg-primary/10 ml-8\"\n                  : \"bg-secondary/20 mr-8\"\n              }`}\n            >\n              <p className=\"text-sm font-semibold mb-1\">\n                {message.role === \"user\" ? \"You\" : \"AI Assistant\"}\n              </p>\n              {message.parts?.map((part, index) => {\n                if (part.type === \"text\") {\n                  return (\n                    <Streamdown\n                      key={index}\n                      isAnimating={status === \"streaming\" && message.role === \"assistant\"}\n                    >\n                      {part.text}\n                    </Streamdown>\n                  );\n                }\n                return null;\n              })}\n            </div>\n          ))\n        )}\n        <div ref={messagesEndRef} />\n      </div>\n\n      <form\n        onSubmit={handleSubmit}\n        className=\"w-full flex items-center space-x-2 pt-2 border-t\"\n      >\n        <Input\n          name=\"prompt\"\n          value={input}\n          onChange={(e) => setInput(e.target.value)}\n          placeholder=\"Type your message...\"\n          className=\"flex-1\"\n          autoComplete=\"off\"\n          autoFocus\n        />\n        <Button type=\"submit\" size=\"icon\">\n          <Send size={18} />\n        </Button>\n      </form>\n    </div>\n  );\n}\n{{/if}}\n"
  },
  {
    "path": "packages/template-generator/templates/examples/ai/web/react/react-router/src/routes/ai.tsx.hbs",
    "content": "{{#if (eq backend \"convex\")}}\nimport { api } from \"@{{projectName}}/backend/convex/_generated/api\";\nimport {\n  useUIMessages,\n  useSmoothText,\n  type UIMessage,\n} from \"@convex-dev/agent/react\";\nimport { useMutation } from \"convex/react\";\nimport { Send, Loader2 } from \"lucide-react\";\nimport React, { useRef, useEffect, useState, type FormEvent } from \"react\";\nimport { Streamdown } from \"streamdown\";\n\nimport { Button } from \"@{{projectName}}/ui/components/button\";\nimport { Input } from \"@{{projectName}}/ui/components/input\";\n\nfunction MessageContent({\n  text,\n  isStreaming,\n}: {\n  text: string;\n  isStreaming: boolean;\n}) {\n  const [visibleText] = useSmoothText(text, {\n    startStreaming: isStreaming,\n  });\n  return <Streamdown>{visibleText}</Streamdown>;\n}\n\nconst AI: React.FC = () => {\n  const [input, setInput] = useState(\"\");\n  const [threadId, setThreadId] = useState<string | null>(null);\n  const [isLoading, setIsLoading] = useState(false);\n  const messagesEndRef = useRef<HTMLDivElement>(null);\n\n  const createThread = useMutation(api.chat.createNewThread);\n  const sendMessage = useMutation(api.chat.sendMessage);\n\n  const { results: messages } = useUIMessages(\n    api.chat.listMessages,\n    threadId ? { threadId } : \"skip\",\n    { initialNumItems: 50, stream: true },\n  );\n\n  useEffect(() => {\n    messagesEndRef.current?.scrollIntoView({ behavior: \"smooth\" });\n  }, [messages]);\n\n  const hasStreamingMessage = messages?.some(\n    (m: UIMessage) => m.status === \"streaming\",\n  );\n\n  const handleSubmit = async (e: FormEvent<HTMLFormElement>) => {\n    e.preventDefault();\n    const text = input.trim();\n    if (!text || isLoading) return;\n\n    setIsLoading(true);\n    setInput(\"\");\n\n    try {\n      let currentThreadId = threadId;\n      if (!currentThreadId) {\n        currentThreadId = await createThread();\n        setThreadId(currentThreadId);\n      }\n\n      await sendMessage({ threadId: currentThreadId, prompt: text });\n    } catch (error) {\n      console.error(\"Failed to send message:\", error);\n    } finally {\n      setIsLoading(false);\n    }\n  };\n\n  return (\n    <div className=\"grid grid-rows-[1fr_auto] overflow-hidden w-full mx-auto p-4\">\n      <div className=\"overflow-y-auto space-y-4 pb-4\">\n        {!messages || messages.length === 0 ? (\n          <div className=\"text-center text-muted-foreground mt-8\">\n            Ask me anything to get started!\n          </div>\n        ) : (\n          messages.map((message: UIMessage) => (\n            <div\n              key={message.key}\n              className={`p-3 rounded-lg ${\n                message.role === \"user\"\n                  ? \"bg-primary/10 ml-8\"\n                  : \"bg-secondary/20 mr-8\"\n              }`}\n            >\n              <p className=\"text-sm font-semibold mb-1\">\n                {message.role === \"user\" ? \"You\" : \"AI Assistant\"}\n              </p>\n              <MessageContent\n                text={message.text ?? \"\"}\n                isStreaming={message.status === \"streaming\"}\n              />\n            </div>\n          ))\n        )}\n        {isLoading && !hasStreamingMessage && (\n          <div className=\"p-3 rounded-lg bg-secondary/20 mr-8\">\n            <p className=\"text-sm font-semibold mb-1\">AI Assistant</p>\n            <div className=\"flex items-center gap-2 text-muted-foreground\">\n              <Loader2 className=\"h-4 w-4 animate-spin\" />\n              <span>Thinking...</span>\n            </div>\n          </div>\n        )}\n        <div ref={messagesEndRef} />\n      </div>\n\n      <form\n        onSubmit={handleSubmit}\n        className=\"w-full flex items-center space-x-2 pt-2 border-t\"\n      >\n        <Input\n          name=\"prompt\"\n          value={input}\n          onChange={(e) => setInput(e.target.value)}\n          placeholder=\"Type your message...\"\n          className=\"flex-1\"\n          autoComplete=\"off\"\n          autoFocus\n          disabled={isLoading}\n        />\n        <Button type=\"submit\" size=\"icon\" disabled={isLoading || !input.trim()}>\n          {isLoading ? (\n            <Loader2 className=\"h-4 w-4 animate-spin\" />\n          ) : (\n            <Send size={18} />\n          )}\n        </Button>\n      </form>\n    </div>\n  );\n};\n\nexport default AI;\n{{else}}\nimport React, { useRef, useEffect, useState, type FormEvent } from \"react\";\nimport { useChat } from \"@ai-sdk/react\";\nimport { DefaultChatTransport } from \"ai\";\nimport { Send } from \"lucide-react\";\nimport { Streamdown } from \"streamdown\";\nimport { env } from \"@{{projectName}}/env/web\";\n\nimport { Button } from \"@{{projectName}}/ui/components/button\";\nimport { Input } from \"@{{projectName}}/ui/components/input\";\n\nconst AI: React.FC = () => {\n  const [input, setInput] = useState(\"\");\n  const { messages, sendMessage, status } = useChat({\n    transport: new DefaultChatTransport({\n      api: `${env.VITE_SERVER_URL}/ai`,\n    }),\n  });\n\n  const messagesEndRef = useRef<HTMLDivElement>(null);\n\n  useEffect(() => {\n    messagesEndRef.current?.scrollIntoView({ behavior: \"smooth\" });\n  }, [messages]);\n\n  const handleSubmit = (e: FormEvent<HTMLFormElement>) => {\n    e.preventDefault();\n    const text = input.trim();\n    if (!text) return;\n    sendMessage({ text });\n    setInput(\"\");\n  };\n\n  return (\n    <div className=\"grid grid-rows-[1fr_auto] overflow-hidden w-full mx-auto p-4\">\n      <div className=\"overflow-y-auto space-y-4 pb-4\">\n        {messages.length === 0 ? (\n          <div className=\"text-center text-muted-foreground mt-8\">\n            Ask me anything to get started!\n          </div>\n        ) : (\n          messages.map((message) => (\n            <div\n              key={message.id}\n              className={`p-3 rounded-lg ${\n                message.role === \"user\"\n                  ? \"bg-primary/10 ml-8\"\n                  : \"bg-secondary/20 mr-8\"\n              }`}\n            >\n              <p className=\"text-sm font-semibold mb-1\">\n                {message.role === \"user\" ? \"You\" : \"AI Assistant\"}\n              </p>\n              {message.parts?.map((part, index) => {\n                if (part.type === \"text\") {\n                  return (\n                    <Streamdown\n                      key={index}\n                      isAnimating={status === \"streaming\" && message.role === \"assistant\"}\n                    >\n                      {part.text}\n                    </Streamdown>\n                  );\n                }\n                return null;\n              })}\n            </div>\n          ))\n        )}\n        <div ref={messagesEndRef} />\n      </div>\n\n      <form\n        onSubmit={handleSubmit}\n        className=\"w-full flex items-center space-x-2 pt-2 border-t\"\n      >\n        <Input\n          name=\"prompt\"\n          value={input}\n          onChange={(e) => setInput(e.target.value)}\n          placeholder=\"Type your message...\"\n          className=\"flex-1\"\n          autoComplete=\"off\"\n          autoFocus\n        />\n        <Button type=\"submit\" size=\"icon\">\n          <Send size={18} />\n        </Button>\n      </form>\n    </div>\n  );\n};\n\nexport default AI;\n{{/if}}\n"
  },
  {
    "path": "packages/template-generator/templates/examples/ai/web/react/tanstack-router/src/routes/ai.tsx.hbs",
    "content": "{{#if (eq backend \"convex\")}}\nimport { api } from \"@{{projectName}}/backend/convex/_generated/api\";\nimport {\n  useUIMessages,\n  useSmoothText,\n  type UIMessage,\n} from \"@convex-dev/agent/react\";\nimport { createFileRoute } from \"@tanstack/react-router\";\nimport { useMutation } from \"convex/react\";\nimport { Send, Loader2 } from \"lucide-react\";\nimport { useRef, useEffect, useState, type FormEvent } from \"react\";\nimport { Streamdown } from \"streamdown\";\n\nimport { Button } from \"@{{projectName}}/ui/components/button\";\nimport { Input } from \"@{{projectName}}/ui/components/input\";\n\nexport const Route = createFileRoute(\"/ai\")({\n  component: RouteComponent,\n});\n\nfunction MessageContent({\n  text,\n  isStreaming,\n}: {\n  text: string;\n  isStreaming: boolean;\n}) {\n  const [visibleText] = useSmoothText(text, {\n    startStreaming: isStreaming,\n  });\n  return <Streamdown>{visibleText}</Streamdown>;\n}\n\nfunction RouteComponent() {\n  const [input, setInput] = useState(\"\");\n  const [threadId, setThreadId] = useState<string | null>(null);\n  const [isLoading, setIsLoading] = useState(false);\n  const messagesEndRef = useRef<HTMLDivElement>(null);\n\n  const createThread = useMutation(api.chat.createNewThread);\n  const sendMessage = useMutation(api.chat.sendMessage);\n\n  const { results: messages } = useUIMessages(\n    api.chat.listMessages,\n    threadId ? { threadId } : \"skip\",\n    { initialNumItems: 50, stream: true },\n  );\n\n  useEffect(() => {\n    messagesEndRef.current?.scrollIntoView({ behavior: \"smooth\" });\n  }, [messages]);\n\n  const hasStreamingMessage = messages?.some(\n    (m: UIMessage) => m.status === \"streaming\",\n  );\n\n  const handleSubmit = async (e: FormEvent<HTMLFormElement>) => {\n    e.preventDefault();\n    const text = input.trim();\n    if (!text || isLoading) return;\n\n    setIsLoading(true);\n    setInput(\"\");\n\n    try {\n      let currentThreadId = threadId;\n      if (!currentThreadId) {\n        currentThreadId = await createThread();\n        setThreadId(currentThreadId);\n      }\n\n      await sendMessage({ threadId: currentThreadId, prompt: text });\n    } catch (error) {\n      console.error(\"Failed to send message:\", error);\n    } finally {\n      setIsLoading(false);\n    }\n  };\n\n  return (\n    <div className=\"grid grid-rows-[1fr_auto] overflow-hidden w-full mx-auto p-4\">\n      <div className=\"overflow-y-auto space-y-4 pb-4\">\n        {!messages || messages.length === 0 ? (\n          <div className=\"text-center text-muted-foreground mt-8\">\n            Ask me anything to get started!\n          </div>\n        ) : (\n          messages.map((message: UIMessage) => (\n            <div\n              key={message.key}\n              className={`p-3 rounded-lg ${\n                message.role === \"user\"\n                  ? \"bg-primary/10 ml-8\"\n                  : \"bg-secondary/20 mr-8\"\n              }`}\n            >\n              <p className=\"text-sm font-semibold mb-1\">\n                {message.role === \"user\" ? \"You\" : \"AI Assistant\"}\n              </p>\n              <MessageContent\n                text={message.text ?? \"\"}\n                isStreaming={message.status === \"streaming\"}\n              />\n            </div>\n          ))\n        )}\n        {isLoading && !hasStreamingMessage && (\n          <div className=\"p-3 rounded-lg bg-secondary/20 mr-8\">\n            <p className=\"text-sm font-semibold mb-1\">AI Assistant</p>\n            <div className=\"flex items-center gap-2 text-muted-foreground\">\n              <Loader2 className=\"h-4 w-4 animate-spin\" />\n              <span>Thinking...</span>\n            </div>\n          </div>\n        )}\n        <div ref={messagesEndRef} />\n      </div>\n\n      <form\n        onSubmit={handleSubmit}\n        className=\"w-full flex items-center space-x-2 pt-2 border-t\"\n      >\n        <Input\n          name=\"prompt\"\n          value={input}\n          onChange={(e) => setInput(e.target.value)}\n          placeholder=\"Type your message...\"\n          className=\"flex-1\"\n          autoComplete=\"off\"\n          autoFocus\n          disabled={isLoading}\n        />\n        <Button type=\"submit\" size=\"icon\" disabled={isLoading || !input.trim()}>\n          {isLoading ? (\n            <Loader2 className=\"h-4 w-4 animate-spin\" />\n          ) : (\n            <Send size={18} />\n          )}\n        </Button>\n      </form>\n    </div>\n  );\n}\n{{else}}\nimport { createFileRoute } from \"@tanstack/react-router\";\nimport { useChat } from \"@ai-sdk/react\";\nimport { DefaultChatTransport } from \"ai\";\nimport { Input } from \"@{{projectName}}/ui/components/input\";\nimport { Button } from \"@{{projectName}}/ui/components/button\";\nimport { Send } from \"lucide-react\";\nimport { useRef, useEffect, useState, type FormEvent } from \"react\";\nimport { Streamdown } from \"streamdown\";\n{{#unless (eq backend \"self\")}}\nimport { env } from \"@{{projectName}}/env/web\";\n{{/unless}}\n\nexport const Route = createFileRoute(\"/ai\")({\n  component: RouteComponent,\n});\n\nfunction RouteComponent() {\n  const [input, setInput] = useState(\"\");\n  const { messages, sendMessage, status } = useChat({\n    transport: new DefaultChatTransport({\n      api: {{#if (eq backend \"self\")}}\"/api/ai\"{{else}}`${env.VITE_SERVER_URL}/ai`{{/if}},\n    }),\n  });\n\n  const messagesEndRef = useRef<HTMLDivElement>(null);\n\n  useEffect(() => {\n    messagesEndRef.current?.scrollIntoView({ behavior: \"smooth\" });\n  }, [messages]);\n\n  const handleSubmit = (e: FormEvent<HTMLFormElement>) => {\n    e.preventDefault();\n    const text = input.trim();\n    if (!text) return;\n    sendMessage({ text });\n    setInput(\"\");\n  };\n\n  return (\n    <div className=\"grid grid-rows-[1fr_auto] overflow-hidden w-full mx-auto p-4\">\n      <div className=\"overflow-y-auto space-y-4 pb-4\">\n        {messages.length === 0 ? (\n          <div className=\"text-center text-muted-foreground mt-8\">\n            Ask me anything to get started!\n          </div>\n        ) : (\n          messages.map((message) => (\n            <div\n              key={message.id}\n              className={`p-3 rounded-lg ${\n                message.role === \"user\"\n                  ? \"bg-primary/10 ml-8\"\n                  : \"bg-secondary/20 mr-8\"\n              }`}\n            >\n              <p className=\"text-sm font-semibold mb-1\">\n                {message.role === \"user\" ? \"You\" : \"AI Assistant\"}\n              </p>\n              {message.parts?.map((part, index) => {\n                if (part.type === \"text\") {\n                  return (\n                    <Streamdown\n                      key={index}\n                      isAnimating={status === \"streaming\" && message.role === \"assistant\"}\n                    >\n                      {part.text}\n                    </Streamdown>\n                  );\n                }\n                return null;\n              })}\n            </div>\n          ))\n        )}\n        <div ref={messagesEndRef} />\n      </div>\n\n      <form\n        onSubmit={handleSubmit}\n        className=\"w-full flex items-center space-x-2 pt-2 border-t\"\n      >\n        <Input\n          name=\"prompt\"\n          value={input}\n          onChange={(e) => setInput(e.target.value)}\n          placeholder=\"Type your message...\"\n          className=\"flex-1\"\n          autoComplete=\"off\"\n          autoFocus\n        />\n        <Button type=\"submit\" size=\"icon\">\n          <Send size={18} />\n        </Button>\n      </form>\n    </div>\n  );\n}\n{{/if}}\n"
  },
  {
    "path": "packages/template-generator/templates/examples/ai/web/react/tanstack-start/src/routes/ai.tsx.hbs",
    "content": "{{#if (eq backend \"convex\")}}\nimport { api } from \"@{{projectName}}/backend/convex/_generated/api\";\nimport {\n  useUIMessages,\n  useSmoothText,\n  type UIMessage,\n} from \"@convex-dev/agent/react\";\nimport { createFileRoute } from \"@tanstack/react-router\";\nimport { useMutation } from \"convex/react\";\nimport { Send, Loader2 } from \"lucide-react\";\nimport { useRef, useEffect, useState, type FormEvent } from \"react\";\nimport { Streamdown } from \"streamdown\";\n\nimport { Button } from \"@{{projectName}}/ui/components/button\";\nimport { Input } from \"@{{projectName}}/ui/components/input\";\n\nexport const Route = createFileRoute(\"/ai\")({\n  component: RouteComponent,\n});\n\nfunction MessageContent({\n  text,\n  isStreaming,\n}: {\n  text: string;\n  isStreaming: boolean;\n}) {\n  const [visibleText] = useSmoothText(text, {\n    startStreaming: isStreaming,\n  });\n  return <Streamdown>{visibleText}</Streamdown>;\n}\n\nfunction RouteComponent() {\n  const [input, setInput] = useState(\"\");\n  const [threadId, setThreadId] = useState<string | null>(null);\n  const [isLoading, setIsLoading] = useState(false);\n  const messagesEndRef = useRef<HTMLDivElement>(null);\n\n  const createThread = useMutation(api.chat.createNewThread);\n  const sendMessage = useMutation(api.chat.sendMessage);\n\n  const { results: messages } = useUIMessages(\n    api.chat.listMessages,\n    threadId ? { threadId } : \"skip\",\n    { initialNumItems: 50, stream: true },\n  );\n\n  useEffect(() => {\n    messagesEndRef.current?.scrollIntoView({ behavior: \"smooth\" });\n  }, [messages]);\n\n  const hasStreamingMessage = messages?.some(\n    (m: UIMessage) => m.status === \"streaming\",\n  );\n\n  const handleSubmit = async (e: FormEvent<HTMLFormElement>) => {\n    e.preventDefault();\n    const text = input.trim();\n    if (!text || isLoading) return;\n\n    setIsLoading(true);\n    setInput(\"\");\n\n    try {\n      let currentThreadId = threadId;\n      if (!currentThreadId) {\n        currentThreadId = await createThread();\n        setThreadId(currentThreadId);\n      }\n\n      await sendMessage({ threadId: currentThreadId, prompt: text });\n    } catch (error) {\n      console.error(\"Failed to send message:\", error);\n    } finally {\n      setIsLoading(false);\n    }\n  };\n\n  return (\n    <div className=\"grid grid-rows-[1fr_auto] overflow-hidden w-full mx-auto p-4\">\n      <div className=\"overflow-y-auto space-y-4 pb-4\">\n        {!messages || messages.length === 0 ? (\n          <div className=\"text-center text-muted-foreground mt-8\">\n            Ask me anything to get started!\n          </div>\n        ) : (\n          messages.map((message: UIMessage) => (\n            <div\n              key={message.key}\n              className={`p-3 rounded-lg ${\n                message.role === \"user\"\n                  ? \"bg-primary/10 ml-8\"\n                  : \"bg-secondary/20 mr-8\"\n              }`}\n            >\n              <p className=\"text-sm font-semibold mb-1\">\n                {message.role === \"user\" ? \"You\" : \"AI Assistant\"}\n              </p>\n              <MessageContent\n                text={message.text ?? \"\"}\n                isStreaming={message.status === \"streaming\"}\n              />\n            </div>\n          ))\n        )}\n        {isLoading && !hasStreamingMessage && (\n          <div className=\"p-3 rounded-lg bg-secondary/20 mr-8\">\n            <p className=\"text-sm font-semibold mb-1\">AI Assistant</p>\n            <div className=\"flex items-center gap-2 text-muted-foreground\">\n              <Loader2 className=\"h-4 w-4 animate-spin\" />\n              <span>Thinking...</span>\n            </div>\n          </div>\n        )}\n        <div ref={messagesEndRef} />\n      </div>\n\n      <form\n        onSubmit={handleSubmit}\n        className=\"w-full flex items-center space-x-2 pt-2 border-t\"\n      >\n        <Input\n          name=\"prompt\"\n          value={input}\n          onChange={(e) => setInput(e.target.value)}\n          placeholder=\"Type your message...\"\n          className=\"flex-1\"\n          autoComplete=\"off\"\n          autoFocus\n          disabled={isLoading}\n        />\n        <Button type=\"submit\" size=\"icon\" disabled={isLoading || !input.trim()}>\n          {isLoading ? (\n            <Loader2 className=\"h-4 w-4 animate-spin\" />\n          ) : (\n            <Send size={18} />\n          )}\n        </Button>\n      </form>\n    </div>\n  );\n}\n{{else}}\nimport { createFileRoute } from \"@tanstack/react-router\";\nimport { useChat } from \"@ai-sdk/react\";\nimport { DefaultChatTransport } from \"ai\";\nimport { Send } from \"lucide-react\";\nimport { useRef, useEffect, useState, type FormEvent } from \"react\";\nimport { Streamdown } from \"streamdown\";\n{{#unless (eq backend \"self\")}}\nimport { env } from \"@{{projectName}}/env/web\";\n{{/unless}}\n\nimport { Button } from \"@{{projectName}}/ui/components/button\";\nimport { Input } from \"@{{projectName}}/ui/components/input\";\n\nexport const Route = createFileRoute(\"/ai\")({\n  component: RouteComponent,\n});\n\nfunction RouteComponent() {\n  const [input, setInput] = useState(\"\");\n  const { messages, sendMessage, status } = useChat({\n    transport: new DefaultChatTransport({\n      api: {{#if (eq backend \"self\")}}\"/api/ai\"{{else}}`${env.VITE_SERVER_URL}/ai`{{/if}},\n    }),\n  });\n\n  const messagesEndRef = useRef<HTMLDivElement>(null);\n\n  useEffect(() => {\n    messagesEndRef.current?.scrollIntoView({ behavior: \"smooth\" });\n  }, [messages]);\n\n  const handleSubmit = (e: FormEvent<HTMLFormElement>) => {\n    e.preventDefault();\n    const text = input.trim();\n    if (!text) return;\n    sendMessage({ text });\n    setInput(\"\");\n  };\n\n  return (\n    <div className=\"grid grid-rows-[1fr_auto] overflow-hidden w-full mx-auto p-4\">\n      <div className=\"overflow-y-auto space-y-4 pb-4\">\n        {messages.length === 0 ? (\n          <div className=\"text-center text-muted-foreground mt-8\">\n            Ask me anything to get started!\n          </div>\n        ) : (\n          messages.map((message) => (\n            <div\n              key={message.id}\n              className={`p-3 rounded-lg ${\n                message.role === \"user\"\n                  ? \"bg-primary/10 ml-8\"\n                  : \"bg-secondary/20 mr-8\"\n              }`}\n            >\n              <p className=\"text-sm font-semibold mb-1\">\n                {message.role === \"user\" ? \"You\" : \"AI Assistant\"}\n              </p>\n              {message.parts?.map((part, index) => {\n                if (part.type === \"text\") {\n                  return (\n                    <Streamdown\n                      key={index}\n                      isAnimating={status === \"streaming\" && message.role === \"assistant\"}\n                    >\n                      {part.text}\n                    </Streamdown>\n                  );\n                }\n                return null;\n              })}\n            </div>\n          ))\n        )}\n        <div ref={messagesEndRef} />\n      </div>\n\n      <form\n        onSubmit={handleSubmit}\n        className=\"w-full flex items-center space-x-2 pt-2 border-t\"\n      >\n        <Input\n          name=\"prompt\"\n          value={input}\n          onChange={(e) => setInput(e.target.value)}\n          placeholder=\"Type your message...\"\n          className=\"flex-1\"\n          autoComplete=\"off\"\n          autoFocus\n        />\n        <Button type=\"submit\" size=\"icon\">\n          <Send size={18} />\n        </Button>\n      </form>\n    </div>\n  );\n}\n{{/if}}\n"
  },
  {
    "path": "packages/template-generator/templates/examples/ai/web/svelte/src/routes/ai/+page.svelte.hbs",
    "content": "<script lang=\"ts\">\n\t{{#unless (eq backend \"self\")}}\n\timport { PUBLIC_SERVER_URL } from \"$env/static/public\";\n\t{{/unless}}\n\timport { Chat } from \"@ai-sdk/svelte\";\n\timport { DefaultChatTransport } from \"ai\";\n\n\tlet input = $state(\"\");\n\tconst chat = new Chat({\n\t\ttransport: new DefaultChatTransport({\n\t\t\t{{#if (eq backend \"self\")}}\n\t\t\tapi: \"/api/ai\",\n\t\t\t{{else}}\n\t\t\tapi: `${PUBLIC_SERVER_URL}/ai`,\n\t\t\t{{/if}}\n\t\t}),\n\t});\n\n\tlet messagesEndElement: HTMLDivElement | null = $state(null);\n\n\t$effect(() => {\n\t\tif (chat.messages.length > 0) {\n\t\t\tsetTimeout(() => {\n\t\t\t\tmessagesEndElement?.scrollIntoView({ behavior: \"smooth\" });\n\t\t\t}, 0);\n\t\t}\n\t});\n\n\tfunction handleSubmit(e: Event) {\n\t\te.preventDefault();\n\t\tconst text = input.trim();\n\t\tif (!text) return;\n\t\tchat.sendMessage({ text });\n\t\tinput = \"\";\n\t}\n</script>\n\n<div\n\tclass=\"mx-auto grid h-full w-full max-w-2xl grid-rows-[1fr_auto] overflow-hidden p-4\"\n>\n\t<div class=\"mb-4 space-y-4 overflow-y-auto pb-4\">\n\t\t{#if chat.messages.length === 0}\n\t\t\t<div class=\"mt-8 text-center text-neutral-500\">\n\t\t\t\tAsk me anything to get started!\n\t\t\t</div>\n\t\t{/if}\n\n\t\t{#each chat.messages as message (message.id)}\n\t\t\t<div\n\t\t\t\tclass=\"p-3 rounded-lg w-fit max-w-[85%] text-sm md:text-base\"\n\t\t\t\tclass:ml-auto={message.role === \"user\"}\n\t\t\t\tclass:bg-primary={message.role === \"user\"}\n\t\t\t\tclass:bg-secondary={message.role === \"assistant\"}\n\t\t\t>\n\t\t\t\t<p\n\t\t\t\t\tclass=\"mb-1 text-sm font-semibold\"\n\t\t\t\t\tclass:text-indigo-600={message.role === \"user\"}\n\t\t\t\t\tclass:text-neutral-400={message.role === \"assistant\"}\n\t\t\t\t>\n\t\t\t\t\t{message.role === \"user\" ? \"You\" : \"AI Assistant\"}\n\t\t\t\t</p>\n\t\t\t\t<div class=\"whitespace-pre-wrap break-words\">\n\t\t\t\t\t{#each message.parts as part, partIndex (partIndex)}\n\t\t\t\t\t\t{#if part.type === \"text\"}\n\t\t\t\t\t\t\t{part.text}\n\t\t\t\t\t\t{/if}\n\t\t\t\t\t{/each}\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t{/each}\n\t\t<div bind:this={messagesEndElement}></div>\n\t</div>\n\n\t<form\n\t\tonsubmit={handleSubmit}\n\t\tclass=\"w-full flex items-center space-x-2 pt-2 border-t\"\n\t>\n\t\t<input\n\t\t\tname=\"prompt\"\n\t\t\tbind:value={input}\n\t\t\tplaceholder=\"Type your message...\"\n\t\t\tclass=\"flex-1 rounded border border-neutral-600 bg-neutral-800 px-3 py-2 text-neutral-100 placeholder-neutral-500 focus:border-indigo-500 focus:outline-none focus:ring-1 focus:ring-indigo-500 disabled:opacity-50\"\n\t\t\tautocomplete=\"off\"\n\t\t\tonkeydown={(e) => {\n\t\t\t\tif (e.key === \"Enter\" && !e.shiftKey) {\n\t\t\t\t\te.preventDefault();\n\t\t\t\t\thandleSubmit(e);\n\t\t\t\t}\n\t\t\t}}\n\t\t/>\n\t\t<button\n\t\t\ttype=\"submit\"\n\t\t\tdisabled={!input.trim()}\n\t\t\tclass=\"inline-flex h-10 w-10 items-center justify-center rounded bg-indigo-600 text-white hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 focus:ring-offset-neutral-900 disabled:cursor-not-allowed disabled:opacity-50\"\n\t\t\taria-label=\"Send message\"\n\t\t>\n\t\t\t<svg\n\t\t\t\txmlns=\"http://www.w3.org/2000/svg\"\n\t\t\t\twidth=\"18\"\n\t\t\t\theight=\"18\"\n\t\t\t\tviewBox=\"0 0 24 24\"\n\t\t\t\tfill=\"none\"\n\t\t\t\tstroke=\"currentColor\"\n\t\t\t\tstroke-width=\"2\"\n\t\t\t\tstroke-linecap=\"round\"\n\t\t\t\tstroke-linejoin=\"round\"\n\t\t\t>\n\t\t\t\t<path d=\"m22 2-7 20-4-9-9-4Z\" />\n\t\t\t\t<path d=\"M22 2 11 13\" />\n\t\t\t</svg>\n\t\t</button>\n\t</form>\n</div>\n"
  },
  {
    "path": "packages/template-generator/templates/examples/todo/convex/packages/backend/convex/todos.ts.hbs",
    "content": "import { query, mutation } from \"./_generated/server\";\nimport { v } from \"convex/values\";\n\nexport const getAll = query({\n    handler: async (ctx) => {\n        return await ctx.db.query(\"todos\").collect();\n    },\n});\n\nexport const create = mutation({\n    args: {\n        text: v.string(),\n    },\n    handler: async (ctx, args) => {\n        const newTodoId = await ctx.db.insert(\"todos\", {\n            text: args.text,\n            completed: false,\n        });\n        return await ctx.db.get(\"todos\", newTodoId);\n    },\n});\n\nexport const toggle = mutation({\n    args: {\n        id: v.id(\"todos\"),\n        completed: v.boolean(),\n    },\n    handler: async (ctx, args) => {\n        await ctx.db.patch(\"todos\", args.id, { completed: args.completed });\n        return { success: true };\n    },\n});\n\nexport const deleteTodo = mutation({\n    args: {\n        id: v.id(\"todos\"),\n    },\n    handler: async (ctx, args) => {\n        await ctx.db.delete(\"todos\", args.id);\n        return { success: true };\n    },\n});"
  },
  {
    "path": "packages/template-generator/templates/examples/todo/native/bare/app/(drawer)/todos.tsx.hbs",
    "content": "import { useState } from \"react\";\nimport {\n  View,\n  Text,\n  TextInput,\n  ScrollView,\n  ActivityIndicator,\n  Alert,\n  TouchableOpacity,\n  StyleSheet,\n} from \"react-native\";\nimport { Ionicons } from \"@expo/vector-icons\";\n{{#if (eq backend \"convex\")}}\nimport { useMutation, useQuery } from \"convex/react\";\nimport { api } from \"@{{projectName}}/backend/convex/_generated/api\";\nimport type { Id } from \"@{{projectName}}/backend/convex/_generated/dataModel\";\n{{else}}\nimport { useMutation, useQuery } from \"@tanstack/react-query\";\n{{/if}}\nimport { Container } from \"@/components/container\";\nimport { useColorScheme } from \"@/lib/use-color-scheme\";\nimport { NAV_THEME } from \"@/lib/constants\";\n{{#unless (eq backend \"convex\")}}\n  {{#if (eq api \"orpc\")}}\nimport { orpc } from \"@/utils/orpc\";\n  {{/if}}\n  {{#if (eq api \"trpc\")}}\nimport { trpc } from \"@/utils/trpc\";\n  {{/if}}\n{{/unless}}\n\nexport default function TodosScreen() {\n  const { colorScheme } = useColorScheme();\n  const theme = colorScheme === \"dark\" ? NAV_THEME.dark : NAV_THEME.light;\n  const [newTodoText, setNewTodoText] = useState(\"\");\n\n  {{#if (eq backend \"convex\")}}\n  const todos = useQuery(api.todos.getAll);\n  const createTodoMutation = useMutation(api.todos.create);\n  const toggleTodoMutation = useMutation(api.todos.toggle);\n  const deleteTodoMutation = useMutation(api.todos.deleteTodo);\n\n  async function handleAddTodo() {\n    const text = newTodoText.trim();\n    if (!text) return;\n    await createTodoMutation({ text });\n    setNewTodoText(\"\");\n  }\n\n  function handleToggleTodo(id: Id<\"todos\">, currentCompleted: boolean) {\n    toggleTodoMutation({ id, completed: !currentCompleted });\n  }\n\n  function handleDeleteTodo(id: Id<\"todos\">) {\n    Alert.alert(\"Delete Todo\", \"Are you sure you want to delete this todo?\", [\n      { text: \"Cancel\", style: \"cancel\" },\n      {\n        text: \"Delete\",\n        style: \"destructive\",\n        onPress: () => deleteTodoMutation({ id }),\n      },\n    ]);\n  }\n\n  const isLoading = !todos;\n  const completedCount = todos?.filter((t) => t.completed).length || 0;\n  const totalCount = todos?.length || 0;\n  {{else}}\n    {{#if (eq api \"orpc\")}}\n  const todos = useQuery(orpc.todo.getAll.queryOptions());\n  const createMutation = useMutation(\n    orpc.todo.create.mutationOptions({\n      onSuccess: () => {\n        todos.refetch();\n        setNewTodoText(\"\");\n      },\n    })\n  );\n  const toggleMutation = useMutation(\n    orpc.todo.toggle.mutationOptions({\n      onSuccess: () => {\n        todos.refetch();\n      },\n    })\n  );\n  const deleteMutation = useMutation(\n    orpc.todo.delete.mutationOptions({\n      onSuccess: () => {\n        todos.refetch();\n      },\n    })\n  );\n    {{/if}}\n    {{#if (eq api \"trpc\")}}\n  const todos = useQuery(trpc.todo.getAll.queryOptions());\n  const createMutation = useMutation(\n    trpc.todo.create.mutationOptions({\n      onSuccess: () => {\n        todos.refetch();\n        setNewTodoText(\"\");\n      },\n    })\n  );\n  const toggleMutation = useMutation(\n    trpc.todo.toggle.mutationOptions({\n      onSuccess: () => {\n        todos.refetch();\n      },\n    })\n  );\n  const deleteMutation = useMutation(\n    trpc.todo.delete.mutationOptions({\n      onSuccess: () => {\n        todos.refetch();\n      },\n    })\n  );\n    {{/if}}\n\n  function handleAddTodo() {\n    if (newTodoText.trim()) {\n      createMutation.mutate({ text: newTodoText });\n    }\n  }\n\n  function handleToggleTodo(id: number, completed: boolean) {\n    toggleMutation.mutate({ id, completed: !completed });\n  }\n\n  function handleDeleteTodo(id: number) {\n    Alert.alert(\"Delete Todo\", \"Are you sure you want to delete this todo?\", [\n      { text: \"Cancel\", style: \"cancel\" },\n      {\n        text: \"Delete\",\n        style: \"destructive\",\n        onPress: () => deleteMutation.mutate({ id }),\n      },\n    ]);\n  }\n\n  const isLoading = todos?.isLoading;\n  const completedCount = todos?.data?.filter((t) => t.completed).length || 0;\n  const totalCount = todos?.data?.length || 0;\n  {{/if}}\n\n  return (\n    <Container>\n      <ScrollView\n        style={styles.scrollView}\n        contentContainerStyle={styles.contentContainer}\n      >\n        <View style={styles.header}>\n          <View style={styles.headerRow}>\n            <Text style={[styles.title, { color: theme.text }]}>\n              Todo List\n            </Text>\n            {totalCount > 0 && (\n              <View style={[styles.badge, { backgroundColor: theme.primary }]}>\n                <Text style={styles.badgeText}>\n                  {completedCount}/{totalCount}\n                </Text>\n              </View>\n            )}\n          </View>\n        </View>\n        <View\n          style={[\n            styles.inputCard,\n            { backgroundColor: theme.card, borderColor: theme.border },\n          ]}\n        >\n          <View style={styles.inputRow}>\n            <View style={styles.inputContainer}>\n              <TextInput\n                value={newTodoText}\n                onChangeText={setNewTodoText}\n                placeholder=\"Add a new task...\"\n                placeholderTextColor={theme.text}\n                {{#unless (eq backend \"convex\")}}\n                editable={!createMutation.isPending}\n                {{/unless}}\n                onSubmitEditing={handleAddTodo}\n                returnKeyType=\"done\"\n                style={[\n                  styles.input,\n                  {\n                    color: theme.text,\n                    borderColor: theme.border,\n                    backgroundColor: theme.background,\n                  },\n                ]}\n              />\n            </View>\n            <TouchableOpacity\n              onPress={handleAddTodo}\n              {{#if (eq backend \"convex\")}}\n              disabled={!newTodoText.trim()}\n              style={[\n                styles.addButton,\n                {\n                  backgroundColor: !newTodoText.trim()\n                    ? theme.border\n                    : theme.primary,\n                  opacity: !newTodoText.trim() ? 0.5 : 1,\n                },\n              ]}\n            >\n              <Ionicons\n                name=\"add\"\n                size={24}\n                color={newTodoText.trim() ? \"#ffffff\" : theme.text}\n              />\n              {{else}}\n              disabled={createMutation.isPending || !newTodoText.trim()}\n              style={[\n                styles.addButton,\n                {\n                  backgroundColor:\n                    createMutation.isPending || !newTodoText.trim()\n                      ? theme.border\n                      : theme.primary,\n                  opacity:\n                    createMutation.isPending || !newTodoText.trim() ? 0.5 : 1,\n                },\n              ]}\n            >\n              {createMutation.isPending ? (\n                <ActivityIndicator size=\"small\" color=\"#ffffff\" />\n              ) : (\n                <Ionicons name=\"add\" size={24} color=\"#ffffff\" />\n              )}\n              {{/if}}\n            </TouchableOpacity>\n          </View>\n        </View>\n\n        {{#if (eq backend \"convex\")}}\n        {isLoading && (\n          <View style={styles.centerContainer}>\n            <ActivityIndicator size=\"large\" color={theme.primary} />\n            <Text\n              style={[styles.loadingText, { color: theme.text, opacity: 0.7 }]}\n            >\n              Loading todos...\n            </Text>\n          </View>\n        )}\n\n        {todos && todos.length === 0 && !isLoading && (\n          <View\n            style={[\n              styles.emptyCard,\n              { backgroundColor: theme.card, borderColor: theme.border },\n            ]}\n          >\n            <Ionicons\n              name=\"checkbox-outline\"\n              size={64}\n              color={theme.text}\n              style=\\{{ opacity: 0.5, marginBottom: 16 }}\n            />\n            <Text style={[styles.emptyTitle, { color: theme.text }]}>\n              No todos yet\n            </Text>\n            <Text\n              style={[styles.emptyText, { color: theme.text, opacity: 0.7 }]}\n            >\n              Add your first task to get started!\n            </Text>\n          </View>\n        )}\n\n        {todos && todos.length > 0 && (\n          <View style={styles.todosList}>\n            {todos.map((todo) => (\n              <View\n                key={todo._id}\n                style={[\n                  styles.todoCard,\n                  { backgroundColor: theme.card, borderColor: theme.border },\n                ]}\n              >\n                <View style={styles.todoRow}>\n                  <TouchableOpacity\n                    onPress={() => handleToggleTodo(todo._id, todo.completed)}\n                    style={[styles.checkbox, { borderColor: theme.border }]}\n                  >\n                    {todo.completed && (\n                      <Ionicons\n                        name=\"checkmark\"\n                        size={16}\n                        color={theme.primary}\n                      />\n                    )}\n                  </TouchableOpacity>\n                  <View style={styles.todoTextContainer}>\n                    <Text\n                      style={[\n                        styles.todoText,\n                        { color: theme.text },\n                        todo.completed && {\n                          textDecorationLine: \"line-through\",\n                          opacity: 0.5,\n                        },\n                      ]}\n                    >\n                      {todo.text}\n                    </Text>\n                  </View>\n                  <TouchableOpacity\n                    onPress={() => handleDeleteTodo(todo._id)}\n                    style={styles.deleteButton}\n                  >\n                    <Ionicons\n                      name=\"trash-outline\"\n                      size={24}\n                      color={theme.notification}\n                    />\n                  </TouchableOpacity>\n                </View>\n              </View>\n            ))}\n          </View>\n        )}\n        {{else}}\n        {isLoading && (\n          <View style={styles.centerContainer}>\n            <ActivityIndicator size=\"large\" color={theme.primary} />\n            <Text\n              style={[styles.loadingText, { color: theme.text, opacity: 0.7 }]}\n            >\n              Loading todos...\n            </Text>\n          </View>\n        )}\n\n        {todos?.data && todos.data.length === 0 && !isLoading && (\n          <View\n            style={[\n              styles.emptyCard,\n              { backgroundColor: theme.card, borderColor: theme.border },\n            ]}\n          >\n            <Ionicons\n              name=\"checkbox-outline\"\n              size={64}\n              color={theme.text}\n              style=\\{{ opacity: 0.5, marginBottom: 16 }}\n            />\n            <Text style={[styles.emptyTitle, { color: theme.text }]}>\n              No todos yet\n            </Text>\n            <Text\n              style={[styles.emptyText, { color: theme.text, opacity: 0.7 }]}\n            >\n              Add your first task to get started!\n            </Text>\n          </View>\n        )}\n\n        {todos?.data && todos.data.length > 0 && (\n          <View style={styles.todosList}>\n            {todos.data.map((todo) => (\n              <View\n                key={todo.id}\n                style={[\n                  styles.todoCard,\n                  { backgroundColor: theme.card, borderColor: theme.border },\n                ]}\n              >\n                <View style={styles.todoRow}>\n                  <TouchableOpacity\n                    onPress={() => handleToggleTodo(todo.id, todo.completed)}\n                    style={[styles.checkbox, { borderColor: theme.border }]}\n                  >\n                    {todo.completed && (\n                      <Ionicons\n                        name=\"checkmark\"\n                        size={16}\n                        color={theme.primary}\n                      />\n                    )}\n                  </TouchableOpacity>\n                  <View style={styles.todoTextContainer}>\n                    <Text\n                      style={[\n                        styles.todoText,\n                        { color: theme.text },\n                        todo.completed && {\n                          textDecorationLine: \"line-through\",\n                          opacity: 0.5,\n                        },\n                      ]}\n                    >\n                      {todo.text}\n                    </Text>\n                  </View>\n                  <TouchableOpacity\n                    onPress={() => handleDeleteTodo(todo.id)}\n                    style={styles.deleteButton}\n                  >\n                    <Ionicons\n                      name=\"trash-outline\"\n                      size={24}\n                      color={theme.notification}\n                    />\n                  </TouchableOpacity>\n                </View>\n              </View>\n            ))}\n          </View>\n        )}\n        {{/if}}\n      </ScrollView>\n    </Container>\n  );\n}\n\nconst styles = StyleSheet.create({\n  scrollView: {\n    flex: 1,\n  },\n  contentContainer: {\n    padding: 16,\n  },\n  header: {\n    marginBottom: 16,\n  },\n  headerRow: {\n    flexDirection: \"row\",\n    alignItems: \"center\",\n    justifyContent: \"space-between\",\n  },\n  title: {\n    fontSize: 24,\n    fontWeight: \"bold\",\n  },\n  badge: {\n    paddingHorizontal: 8,\n    paddingVertical: 4,\n  },\n  badgeText: {\n    color: \"#ffffff\",\n    fontSize: 12,\n  },\n  inputCard: {\n    borderWidth: 1,\n    padding: 12,\n    marginBottom: 16,\n  },\n  inputRow: {\n    flexDirection: \"row\",\n    alignItems: \"center\",\n    gap: 8,\n  },\n  inputContainer: {\n    flex: 1,\n  },\n  input: {\n    borderWidth: 1,\n    padding: 12,\n    fontSize: 16,\n  },\n  addButton: {\n    padding: 12,\n    justifyContent: \"center\",\n    alignItems: \"center\",\n  },\n  centerContainer: {\n    alignItems: \"center\",\n    justifyContent: \"center\",\n    paddingVertical: 32,\n  },\n  loadingText: {\n    marginTop: 16,\n    fontSize: 14,\n  },\n  emptyCard: {\n    borderWidth: 1,\n    padding: 32,\n    alignItems: \"center\",\n    justifyContent: \"center\",\n  },\n  emptyTitle: {\n    fontSize: 16,\n    fontWeight: \"bold\",\n    marginBottom: 8,\n  },\n  emptyText: {\n    fontSize: 14,\n    textAlign: \"center\",\n  },\n  todosList: {\n    gap: 8,\n  },\n  todoCard: {\n    borderWidth: 1,\n    padding: 12,\n  },\n  todoRow: {\n    flexDirection: \"row\",\n    alignItems: \"center\",\n    gap: 12,\n  },\n  checkbox: {\n    width: 20,\n    height: 20,\n    borderWidth: 2,\n    justifyContent: \"center\",\n    alignItems: \"center\",\n  },\n  todoTextContainer: {\n    flex: 1,\n  },\n  todoText: {\n    fontSize: 16,\n  },\n  deleteButton: {\n    padding: 8,\n  },\n});"
  },
  {
    "path": "packages/template-generator/templates/examples/todo/native/unistyles/app/(drawer)/todos.tsx.hbs",
    "content": "import { useState } from \"react\";\nimport {\n  View,\n  Text,\n  TextInput,\n  TouchableOpacity,\n  ScrollView,\n  ActivityIndicator,\n  Alert,\n} from \"react-native\";\nimport { Ionicons } from \"@expo/vector-icons\";\nimport { StyleSheet, useUnistyles } from \"react-native-unistyles\";\n\n{{#if (eq backend \"convex\")}}\nimport { useMutation, useQuery } from \"convex/react\";\nimport { api } from \"@{{projectName}}/backend/convex/_generated/api\";\nimport type { Id } from \"@{{projectName}}/backend/convex/_generated/dataModel\";\n{{else}}\nimport { useMutation, useQuery } from \"@tanstack/react-query\";\n{{/if}}\n\nimport { Container } from \"@/components/container\";\n{{#unless (eq backend \"convex\")}}\n{{#if (eq api \"orpc\")}}\nimport { orpc } from \"@/utils/orpc\";\n{{/if}}\n{{#if (eq api \"trpc\")}}\nimport { trpc } from \"@/utils/trpc\";\n{{/if}}\n{{/unless}}\n\nexport default function TodosScreen() {\n  const [newTodoText, setNewTodoText] = useState(\"\");\n  const { theme } = useUnistyles();\n\n  {{#if (eq backend \"convex\")}}\n  const todos = useQuery(api.todos.getAll);\n  const createTodoMutation = useMutation(api.todos.create);\n  const toggleTodoMutation = useMutation(api.todos.toggle);\n  const deleteTodoMutation = useMutation(api.todos.deleteTodo);\n\n  const handleAddTodo = async () => {\n    const text = newTodoText.trim();\n    if (!text) return;\n    await createTodoMutation({ text });\n    setNewTodoText(\"\");\n  };\n\n  const handleToggleTodo = (id: Id<\"todos\">, currentCompleted: boolean) => {\n    toggleTodoMutation({ id, completed: !currentCompleted });\n  };\n\n  const handleDeleteTodo = (id: Id<\"todos\">) => {\n    Alert.alert(\"Delete Todo\", \"Are you sure you want to delete this todo?\", [\n      { text: \"Cancel\", style: \"cancel\" },\n      {\n        text: \"Delete\",\n        style: \"destructive\",\n        onPress: () => deleteTodoMutation({ id }),\n      },\n    ]);\n  };\n  {{else}}\n    {{#if (eq api \"orpc\")}}\n    const todos = useQuery(orpc.todo.getAll.queryOptions());\n    const createMutation = useMutation(\n      orpc.todo.create.mutationOptions({\n        onSuccess: () => {\n          todos.refetch();\n          setNewTodoText(\"\");\n        },\n      })\n    );\n    const toggleMutation = useMutation(\n      orpc.todo.toggle.mutationOptions({\n        onSuccess: () => { todos.refetch() },\n      })\n    );\n    const deleteMutation = useMutation(\n      orpc.todo.delete.mutationOptions({\n        onSuccess: () => { todos.refetch() },\n      })\n    );\n    {{/if}}\n    {{#if (eq api \"trpc\")}}\n    const todos = useQuery(trpc.todo.getAll.queryOptions());\n    const createMutation = useMutation(\n      trpc.todo.create.mutationOptions({\n        onSuccess: () => {\n          todos.refetch();\n          setNewTodoText(\"\");\n        },\n      })\n    );\n    const toggleMutation = useMutation(\n      trpc.todo.toggle.mutationOptions({\n        onSuccess: () => { todos.refetch() },\n      })\n    );\n    const deleteMutation = useMutation(\n      trpc.todo.delete.mutationOptions({\n        onSuccess: () => { todos.refetch() },\n      })\n    );\n    {{/if}}\n\n  const handleAddTodo = () => {\n    if (newTodoText.trim()) {\n      createMutation.mutate({ text: newTodoText });\n    }\n  };\n\n  const handleToggleTodo = (id: number, completed: boolean) => {\n    toggleMutation.mutate({ id, completed: !completed });\n  };\n\n  const handleDeleteTodo = (id: number) => {\n    Alert.alert(\"Delete Todo\", \"Are you sure you want to delete this todo?\", [\n      { text: \"Cancel\", style: \"cancel\" },\n      {\n        text: \"Delete\",\n        style: \"destructive\",\n        onPress: () => deleteMutation.mutate({ id }),\n      },\n    ]);\n  };\n  {{/if}}\n\n  const isLoading = {{#if (eq backend \"convex\")}}!todos{{else}}todos.isLoading{{/if}};\n  const isCreating = {{#if (eq backend \"convex\")}}false{{else}}createMutation.isPending{{/if}};\n  const primaryButtonTextColor = theme.colors.background;\n\n  return (\n    <Container>\n      <ScrollView style={styles.scrollView}>\n        <View style={styles.headerContainer}>\n          <Text style={styles.headerTitle}>Todo List</Text>\n          <Text style={styles.headerSubtitle}>\n            Manage your tasks efficiently\n          </Text>\n\n          <View style={styles.inputContainer}>\n            <TextInput\n              value={newTodoText}\n              onChangeText={setNewTodoText}\n              placeholder=\"Add a new task...\"\n              placeholderTextColor={theme.colors.border}\n              editable={!isCreating}\n              style={styles.textInput}\n              onSubmitEditing={handleAddTodo}\n              returnKeyType=\"done\"\n            />\n            <TouchableOpacity\n              onPress={handleAddTodo}\n              disabled={isCreating || !newTodoText.trim()}\n              style={[\n                styles.addButton,\n                (isCreating || !newTodoText.trim()) && styles.addButtonDisabled,\n              ]}\n            >\n              {isCreating ? (\n                <ActivityIndicator size=\"small\" color={primaryButtonTextColor} />\n              ) : (\n                <Ionicons\n                  name=\"add\"\n                  size={24}\n                  color={primaryButtonTextColor}\n                />\n              )}\n            </TouchableOpacity>\n          </View>\n        </View>\n\n        {isLoading && (\n          <View style={styles.loadingContainer}>\n            <ActivityIndicator size=\"large\" color={theme.colors.primary} />\n            <Text style={styles.loadingText}>Loading todos...</Text>\n          </View>\n        )}\n\n        {{#if (eq backend \"convex\")}}\n          {todos && todos.length === 0 && !isLoading && (\n            <Text style={styles.emptyText}>No todos yet. Add one!</Text>\n          )}\n          {todos?.map((todo) => (\n            <View key={todo._id} style={styles.todoItem}>\n              <TouchableOpacity\n                onPress={() => handleToggleTodo(todo._id, todo.completed)}\n                style={styles.todoContent}\n              >\n                <Ionicons\n                  name={todo.completed ? \"checkbox\" : \"square-outline\"}\n                  size={24}\n                  color={todo.completed ? theme.colors.primary : theme.colors.typography}\n                  style={styles.checkbox}\n                />\n                <Text\n                  style={[\n                    styles.todoText,\n                    todo.completed && styles.todoTextCompleted,\n                  ]}\n                >\n                  {todo.text}\n                </Text>\n              </TouchableOpacity>\n              <TouchableOpacity onPress={() => handleDeleteTodo(todo._id)}>\n                <Ionicons name=\"trash-outline\" size={24} color={theme.colors.destructive} />\n              </TouchableOpacity>\n            </View>\n          ))}\n        {{else}}\n          {todos.data && todos.data.length === 0 && !isLoading && (\n             <Text style={styles.emptyText}>No todos yet. Add one!</Text>\n          )}\n          {todos.data?.map((todo: { id: number; text: string; completed: boolean }) => (\n            <View key={todo.id} style={styles.todoItem}>\n              <TouchableOpacity\n                onPress={() => handleToggleTodo(todo.id, todo.completed)}\n                style={styles.todoContent}\n              >\n                <Ionicons\n                  name={todo.completed ? \"checkbox\" : \"square-outline\"}\n                  size={24}\n                  color={todo.completed ? theme.colors.primary : theme.colors.typography}\n                  style={styles.checkbox}\n                />\n                <Text\n                  style={[\n                    styles.todoText,\n                    todo.completed && styles.todoTextCompleted,\n                  ]}\n                >\n                  {todo.text}\n                </Text>\n              </TouchableOpacity>\n              <TouchableOpacity onPress={() => handleDeleteTodo(todo.id)}>\n                <Ionicons name=\"trash-outline\" size={24} color={theme.colors.destructive} />\n              </TouchableOpacity>\n            </View>\n          ))}\n        {{/if}}\n      </ScrollView>\n    </Container>\n  );\n}\n\nconst styles = StyleSheet.create((theme) => ({\n  scrollView: {\n    flex: 1,\n  },\n  headerContainer: {\n    paddingHorizontal: theme.spacing.md,\n    paddingVertical: theme.spacing.lg,\n    borderBottomWidth: 1,\n    borderBottomColor: theme.colors.border,\n    backgroundColor: theme.colors.background,\n  },\n  headerTitle: {\n    fontSize: 28,\n    fontWeight: \"bold\",\n    color: theme.colors.typography,\n    marginBottom: theme.spacing.sm,\n  },\n  headerSubtitle: {\n    fontSize: 16,\n    color: theme.colors.typography,\n    marginBottom: theme.spacing.md,\n  },\n  inputContainer: {\n    flexDirection: \"row\",\n    alignItems: \"center\",\n    marginBottom: theme.spacing.md,\n  },\n  textInput: {\n    flex: 1,\n    borderWidth: 1,\n    borderColor: theme.colors.border,\n    borderRadius: 8,\n    paddingHorizontal: theme.spacing.md,\n    paddingVertical: theme.spacing.sm,\n    color: theme.colors.typography,\n    backgroundColor: theme.colors.background,\n    marginRight: theme.spacing.sm,\n    fontSize: 16,\n  },\n  addButton: {\n    backgroundColor: theme.colors.primary,\n    padding: theme.spacing.sm,\n    borderRadius: 8,\n    justifyContent: \"center\",\n    alignItems: \"center\",\n  },\n  addButtonDisabled: {\n    backgroundColor: theme.colors.border,\n  },\n  loadingContainer: {\n    flex: 1,\n    justifyContent: \"center\",\n    alignItems: \"center\",\n    padding: theme.spacing.lg,\n  },\n  loadingText: {\n    marginTop: theme.spacing.sm,\n    fontSize: 16,\n    color: theme.colors.typography,\n  },\n  emptyText: {\n    textAlign: \"center\",\n    marginTop: theme.spacing.xl,\n    fontSize: 16,\n    color: theme.colors.typography,\n  },\n  todoItem: {\n    flexDirection: \"row\",\n    justifyContent: \"space-between\",\n    alignItems: \"center\",\n    paddingVertical: theme.spacing.md,\n    paddingHorizontal: theme.spacing.md,\n    borderBottomWidth: 1,\n    borderBottomColor: theme.colors.border,\n    backgroundColor: theme.colors.background,\n  },\n  todoContent: {\n    flexDirection: \"row\",\n    alignItems: \"center\",\n    flex: 1,\n  },\n  checkbox: {\n    marginRight: theme.spacing.md,\n  },\n  todoText: {\n    fontSize: 16,\n    color: theme.colors.typography,\n    flex: 1,\n  },\n  todoTextCompleted: {\n    textDecorationLine: \"line-through\",\n    color: theme.colors.border,\n  },\n}));\n"
  },
  {
    "path": "packages/template-generator/templates/examples/todo/native/uniwind/app/(drawer)/todos.tsx.hbs",
    "content": "import { useState } from \"react\";\nimport { View, Text, ScrollView, Alert } from \"react-native\";\nimport { Ionicons } from \"@expo/vector-icons\";\n{{#if (eq backend \"convex\")}}\nimport { useMutation, useQuery } from \"convex/react\";\nimport { api } from \"@{{projectName}}/backend/convex/_generated/api\";\nimport type { Id } from \"@{{projectName}}/backend/convex/_generated/dataModel\";\n{{else}}\nimport { useMutation, useQuery } from \"@tanstack/react-query\";\n{{/if}}\nimport { Container } from \"@/components/container\";\n{{#unless (eq backend \"convex\")}}\n  {{#if (eq api \"orpc\")}}\n    import { orpc } from \"@/utils/orpc\";\n  {{/if}}\n  {{#if (eq api \"trpc\")}}\n    import { trpc } from \"@/utils/trpc\";\n  {{/if}}\n{{/unless}}\nimport { Button, Checkbox, Chip, Spinner, Surface, Input, TextField, useThemeColor } from \"heroui-native\";\n\nexport default function TodosScreen() {\n  const [newTodoText, setNewTodoText] = useState(\"\");\n  {{#if (eq backend \"convex\")}}\n    const todos = useQuery(api.todos.getAll);\n    const createTodoMutation = useMutation(api.todos.create);\n    const toggleTodoMutation = useMutation(api.todos.toggle);\n    const deleteTodoMutation = useMutation(api.todos.deleteTodo);\n  {{else}}\n    {{#if (eq api \"orpc\")}}\n      const todos = useQuery(orpc.todo.getAll.queryOptions());\n      const createMutation = useMutation(orpc.todo.create.mutationOptions({\n        onSuccess: () => {\n          todos.refetch();\n          setNewTodoText(\"\");\n        },\n      }));\n      const toggleMutation = useMutation(orpc.todo.toggle.mutationOptions({\n        onSuccess: () => {\n          todos.refetch();\n        },\n      }));\n      const deleteMutation = useMutation(orpc.todo.delete.mutationOptions({\n        onSuccess: () => {\n          todos.refetch();\n        },\n      }));\n    {{/if}}\n    {{#if (eq api \"trpc\")}}\n      const todos = useQuery(trpc.todo.getAll.queryOptions());\n      const createMutation = useMutation(trpc.todo.create.mutationOptions({\n        onSuccess: () => {\n          todos.refetch();\n          setNewTodoText(\"\");\n        },\n      }));\n      const toggleMutation = useMutation(trpc.todo.toggle.mutationOptions({\n        onSuccess: () => {\n          todos.refetch();\n        },\n      }));\n      const deleteMutation = useMutation(trpc.todo.delete.mutationOptions({\n        onSuccess: () => {\n          todos.refetch();\n        },\n      }));\n    {{/if}}\n  {{/if}}\n\n  const mutedColor = useThemeColor(\"muted\");\n  const dangerColor = useThemeColor(\"danger\");\n  const foregroundColor = useThemeColor(\"foreground\");\n\n  {{#if (eq backend \"convex\")}}\n    const handleAddTodo = async () => {\n      const text = newTodoText.trim();\n      if (!text) return;\n      await createTodoMutation({ text });\n      setNewTodoText(\"\");\n    };\n\n    const handleToggleTodo = (id: Id<\"todos\">, currentCompleted: boolean) => {\n      toggleTodoMutation({ id, completed: !currentCompleted });\n    };\n\n    const handleDeleteTodo = (id: Id<\"todos\">) => {\n      Alert.alert(\"Delete Todo\", \"Are you sure you want to delete this todo?\", [\n        { text: \"Cancel\", style: \"cancel\" },\n        {\n          text: \"Delete\",\n          style: \"destructive\",\n          onPress: () => deleteTodoMutation({ id }),\n        },\n      ]);\n    };\n\n    const isLoading = !todos;\n    const completedCount = todos?.filter((t) => t.completed).length || 0;\n    const totalCount = todos?.length || 0;\n  {{else}}\n    const handleAddTodo = () => {\n      if (newTodoText.trim()) {\n        createMutation.mutate({ text: newTodoText });\n      }\n    };\n\n    const handleToggleTodo = (id: number, completed: boolean) => {\n      toggleMutation.mutate({ id, completed: !completed });\n    };\n\n    const handleDeleteTodo = (id: number) => {\n      Alert.alert(\"Delete Todo\", \"Are you sure you want to delete this todo?\", [\n        { text: \"Cancel\", style: \"cancel\" },\n        {\n          text: \"Delete\",\n          style: \"destructive\",\n          onPress: () => deleteMutation.mutate({ id }),\n        },\n      ]);\n    };\n\n    const isLoading = todos?.isLoading;\n    const completedCount = todos?.data?.filter((t) => t.completed).length || 0;\n    const totalCount = todos?.data?.length || 0;\n  {{/if}}\n\n  return (\n    <Container>\n      <ScrollView className=\"flex-1\" contentContainerClassName=\"p-4\">\n        <View className=\"py-4 mb-4\">\n          <View className=\"flex-row items-center justify-between\">\n            <Text className=\"text-2xl font-semibold text-foreground tracking-tight\">Tasks</Text>\n            {totalCount > 0 && (\n              <Chip variant=\"secondary\" color=\"accent\" size=\"sm\">\n                <Chip.Label>\n                  {completedCount}/{totalCount}\n                </Chip.Label>\n              </Chip>\n            )}\n          </View>\n        </View>\n\n        <Surface variant=\"secondary\" className=\"mb-4 p-3 rounded-lg\">\n          <View className=\"flex-row items-center gap-2\">\n            <View className=\"flex-1\">\n              <TextField>\n                <Input\n                  value={newTodoText}\n                  onChangeText={setNewTodoText}\n                  placeholder=\"Add a new task...\"\n                  {{#unless (eq backend \"convex\")}}\n                    editable={!createMutation.isPending}\n                  {{/unless}}\n                  onSubmitEditing={handleAddTodo}\n                  returnKeyType=\"done\"\n                />\n              </TextField>\n            </View>\n            <Button\n              isIconOnly\n              {{#if (eq backend \"convex\")}}\n                variant={!newTodoText.trim() ? \"secondary\" : \"primary\"}\n                isDisabled={!newTodoText.trim()}\n              {{else}}\n                variant={createMutation.isPending || !newTodoText.trim() ? \"secondary\" : \"primary\"}\n                isDisabled={createMutation.isPending || !newTodoText.trim()}\n              {{/if}}\n              onPress={handleAddTodo}\n              size=\"sm\"\n            >\n              {{#if (eq backend \"convex\")}}\n                <Ionicons\n                  name=\"add\"\n                  size={20}\n                  color={newTodoText.trim() ? foregroundColor : mutedColor}\n                />\n              {{else}}\n                {createMutation.isPending ? (\n                  <Spinner size=\"sm\" color=\"default\" />\n                ) : (\n                  <Ionicons\n                    name=\"add\"\n                    size={20}\n                    color={(createMutation.isPending || !newTodoText.trim()) ? mutedColor : foregroundColor}\n                  />\n                )}\n              {{/if}}\n            </Button>\n          </View>\n        </Surface>\n\n        {{#if (eq backend \"convex\")}}\n          {isLoading && (\n            <View className=\"items-center justify-center py-12\">\n              <Spinner size=\"lg\" />\n              <Text className=\"text-muted text-sm mt-3\">Loading tasks...</Text>\n            </View>\n          )}\n\n          {todos && todos.length === 0 && !isLoading && (\n            <Surface variant=\"secondary\" className=\"items-center justify-center py-10 rounded-lg\">\n              <Ionicons name=\"checkbox-outline\" size={40} color={mutedColor} />\n              <Text className=\"text-foreground font-medium mt-3\">No tasks yet</Text>\n              <Text className=\"text-muted text-xs mt-1\">Add your first task to get started</Text>\n            </Surface>\n          )}\n\n          {todos && todos.length > 0 && (\n            <View className=\"gap-2\">\n              {todos.map((todo) => (\n                <Surface key={todo._id} variant=\"secondary\" className=\"p-3 rounded-lg\">\n                  <View className=\"flex-row items-center gap-3\">\n                    <Checkbox\n                      isSelected={todo.completed}\n                      onSelectedChange={() => handleToggleTodo(todo._id, todo.completed)}\n                    />\n                    <View className=\"flex-1\">\n                      <Text className={`text-sm ${todo.completed ? \"text-muted line-through\" : \"text-foreground\"}`}>\n                        {todo.text}\n                      </Text>\n                    </View>\n                    <Button\n                      isIconOnly\n                      variant=\"ghost\"\n                      onPress={() => handleDeleteTodo(todo._id)}\n                      size=\"sm\"\n                    >\n                      <Ionicons name=\"trash-outline\" size={16} color={dangerColor} />\n                    </Button>\n                  </View>\n                </Surface>\n              ))}\n            </View>\n          )}\n        {{else}}\n          {isLoading && (\n            <View className=\"items-center justify-center py-12\">\n              <Spinner size=\"lg\" />\n              <Text className=\"text-muted text-sm mt-3\">Loading tasks...</Text>\n            </View>\n          )}\n\n          {todos?.data && todos.data.length === 0 && !isLoading && (\n            <Surface variant=\"secondary\" className=\"items-center justify-center py-10 rounded-lg\">\n              <Ionicons name=\"checkbox-outline\" size={40} color={mutedColor} />\n              <Text className=\"text-foreground font-medium mt-3\">No tasks yet</Text>\n              <Text className=\"text-muted text-xs mt-1\">Add your first task to get started</Text>\n            </Surface>\n          )}\n\n          {todos?.data && todos.data.length > 0 && (\n            <View className=\"gap-2\">\n              {todos.data.map((todo) => (\n                <Surface key={todo.id} variant=\"secondary\" className=\"p-3 rounded-lg\">\n                  <View className=\"flex-row items-center gap-3\">\n                    <Checkbox\n                      isSelected={todo.completed}\n                      onSelectedChange={() => handleToggleTodo(todo.id, todo.completed)}\n                    />\n                    <View className=\"flex-1\">\n                      <Text className={`text-sm ${todo.completed ? \"text-muted line-through\" : \"text-foreground\"}`}>\n                        {todo.text}\n                      </Text>\n                    </View>\n                    <Button\n                      isIconOnly\n                      variant=\"ghost\"\n                      onPress={() => handleDeleteTodo(todo.id)}\n                      size=\"sm\"\n                    >\n                      <Ionicons name=\"trash-outline\" size={16} color={dangerColor} />\n                    </Button>\n                  </View>\n                </Surface>\n              ))}\n            </View>\n          )}\n        {{/if}}\n      </ScrollView>\n    </Container>\n  );\n}"
  },
  {
    "path": "packages/template-generator/templates/examples/todo/server/drizzle/base/src/routers/todo.ts.hbs",
    "content": "{{#if (eq api \"orpc\")}}\nimport { eq } from \"drizzle-orm\";\nimport z from \"zod\";\n{{#if (or (eq runtime \"workers\") (eq serverDeploy \"cloudflare\") (and (eq backend \"self\") (eq webDeploy \"cloudflare\")))}}\nimport { createDb } from \"@{{projectName}}/db\";\n{{else}}\nimport { db } from \"@{{projectName}}/db\";\n{{/if}}\nimport { todo } from \"@{{projectName}}/db/schema/todo\";\nimport { publicProcedure } from \"../index\";\n\nexport const todoRouter = {\n  getAll: publicProcedure.handler(async ({{#if (and (eq backend \"self\") (eq webDeploy \"cloudflare\") (includes frontend \"svelte\"))}}{ context }{{/if}}) => {\n{{#if (or (eq runtime \"workers\") (eq serverDeploy \"cloudflare\") (and (eq backend \"self\") (eq webDeploy \"cloudflare\")))}}\n    const db = createDb({{#if (and (eq backend \"self\") (eq webDeploy \"cloudflare\") (includes frontend \"svelte\"))}}context.env{{/if}});\n{{/if}}\n    return await db.select().from(todo);\n  }),\n\n  create: publicProcedure\n    .input(z.object({ text: z.string().min(1) }))\n    .handler(async ({ input{{#if (and (eq backend \"self\") (eq webDeploy \"cloudflare\") (includes frontend \"svelte\"))}}, context{{/if}} }) => {\n{{#if (or (eq runtime \"workers\") (eq serverDeploy \"cloudflare\") (and (eq backend \"self\") (eq webDeploy \"cloudflare\")))}}\n      const db = createDb({{#if (and (eq backend \"self\") (eq webDeploy \"cloudflare\") (includes frontend \"svelte\"))}}context.env{{/if}});\n{{/if}}\n      return await db\n        .insert(todo)\n        .values({\n          text: input.text,\n        });\n    }),\n\n  toggle: publicProcedure\n    .input(z.object({ id: z.number(), completed: z.boolean() }))\n    .handler(async ({ input{{#if (and (eq backend \"self\") (eq webDeploy \"cloudflare\") (includes frontend \"svelte\"))}}, context{{/if}} }) => {\n{{#if (or (eq runtime \"workers\") (eq serverDeploy \"cloudflare\") (and (eq backend \"self\") (eq webDeploy \"cloudflare\")))}}\n      const db = createDb({{#if (and (eq backend \"self\") (eq webDeploy \"cloudflare\") (includes frontend \"svelte\"))}}context.env{{/if}});\n{{/if}}\n      return await db\n        .update(todo)\n        .set({ completed: input.completed })\n        .where(eq(todo.id, input.id));\n    }),\n\n  delete: publicProcedure\n    .input(z.object({ id: z.number() }))\n    .handler(async ({ input{{#if (and (eq backend \"self\") (eq webDeploy \"cloudflare\") (includes frontend \"svelte\"))}}, context{{/if}} }) => {\n{{#if (or (eq runtime \"workers\") (eq serverDeploy \"cloudflare\") (and (eq backend \"self\") (eq webDeploy \"cloudflare\")))}}\n      const db = createDb({{#if (and (eq backend \"self\") (eq webDeploy \"cloudflare\") (includes frontend \"svelte\"))}}context.env{{/if}});\n{{/if}}\n      return await db.delete(todo).where(eq(todo.id, input.id));\n    }),\n};\n{{/if}}\n\n{{#if (eq api \"trpc\")}}\nimport z from \"zod\";\nimport { router, publicProcedure } from \"../index\";\nimport { todo } from \"@{{projectName}}/db/schema/todo\";\nimport { eq } from \"drizzle-orm\";\n{{#if (or (eq runtime \"workers\") (eq serverDeploy \"cloudflare\") (and (eq backend \"self\") (eq webDeploy \"cloudflare\")))}}\nimport { createDb } from \"@{{projectName}}/db\";\n{{else}}\nimport { db } from \"@{{projectName}}/db\";\n{{/if}}\n\nexport const todoRouter = router({\n  getAll: publicProcedure.query(async () => {\n{{#if (or (eq runtime \"workers\") (eq serverDeploy \"cloudflare\") (and (eq backend \"self\") (eq webDeploy \"cloudflare\")))}}\n    const db = createDb();\n{{/if}}\n    return await db.select().from(todo);\n  }),\n\n  create: publicProcedure\n    .input(z.object({ text: z.string().min(1) }))\n    .mutation(async ({ input }) => {\n{{#if (or (eq runtime \"workers\") (eq serverDeploy \"cloudflare\") (and (eq backend \"self\") (eq webDeploy \"cloudflare\")))}}\n      const db = createDb();\n{{/if}}\n      return await db.insert(todo).values({\n        text: input.text,\n      });\n    }),\n\n  toggle: publicProcedure\n    .input(z.object({ id: z.number(), completed: z.boolean() }))\n    .mutation(async ({ input }) => {\n{{#if (or (eq runtime \"workers\") (eq serverDeploy \"cloudflare\") (and (eq backend \"self\") (eq webDeploy \"cloudflare\")))}}\n      const db = createDb();\n{{/if}}\n      return await db\n        .update(todo)\n        .set({ completed: input.completed })\n        .where(eq(todo.id, input.id));\n    }),\n\n  delete: publicProcedure\n    .input(z.object({ id: z.number() }))\n    .mutation(async ({ input }) => {\n{{#if (or (eq runtime \"workers\") (eq serverDeploy \"cloudflare\") (and (eq backend \"self\") (eq webDeploy \"cloudflare\")))}}\n      const db = createDb();\n{{/if}}\n      return await db.delete(todo).where(eq(todo.id, input.id));\n    }),\n});\n{{/if}}\n"
  },
  {
    "path": "packages/template-generator/templates/examples/todo/server/drizzle/mysql/src/schema/todo.ts",
    "content": "import { mysqlTable, varchar, int, boolean } from \"drizzle-orm/mysql-core\";\n\nexport const todo = mysqlTable(\"todo\", {\n  id: int(\"id\").primaryKey().autoincrement(),\n  text: varchar(\"text\", { length: 255 }).notNull(),\n  completed: boolean(\"completed\").default(false).notNull(),\n});\n"
  },
  {
    "path": "packages/template-generator/templates/examples/todo/server/drizzle/postgres/src/schema/todo.ts",
    "content": "import { pgTable, text, boolean, serial } from \"drizzle-orm/pg-core\";\n\nexport const todo = pgTable(\"todo\", {\n  id: serial(\"id\").primaryKey(),\n  text: text(\"text\").notNull(),\n  completed: boolean(\"completed\").default(false).notNull(),\n});\n"
  },
  {
    "path": "packages/template-generator/templates/examples/todo/server/drizzle/sqlite/src/schema/todo.ts",
    "content": "import { integer, sqliteTable, text } from \"drizzle-orm/sqlite-core\";\n\nexport const todo = sqliteTable(\"todo\", {\n  id: integer(\"id\").primaryKey({ autoIncrement: true }),\n  text: text(\"text\").notNull(),\n  completed: integer(\"completed\", { mode: \"boolean\" }).default(false).notNull(),\n});\n"
  },
  {
    "path": "packages/template-generator/templates/examples/todo/server/mongoose/base/src/routers/todo.ts.hbs",
    "content": "{{#if (eq api \"orpc\")}}\nimport z from \"zod\";\nimport { publicProcedure } from \"../index\";\nimport { Todo } from \"@{{projectName}}/db/models/todo.model\";\n\nexport const todoRouter = {\n    getAll: publicProcedure.handler(async () => {\n        return await Todo.find().lean();\n    }),\n\n    create: publicProcedure\n        .input(z.object({ text: z.string().min(1) }))\n        .handler(async ({ input }) => {\n            const newTodo = await Todo.create({ text: input.text });\n            return newTodo.toObject();\n    }),\n\n    toggle: publicProcedure\n        .input(z.object({ id: z.string(), completed: z.boolean() }))\n        .handler(async ({ input }) => {\n            await Todo.updateOne({ id: input.id }, { completed: input.completed });\n            return { success: true };\n    }),\n\n    delete: publicProcedure\n        .input(z.object({ id: z.string() }))\n        .handler(async ({ input }) => {\n            await Todo.deleteOne({ id: input.id });\n            return { success: true };\n    }),\n};\n\n{{/if}}\n\n{{#if (eq api \"trpc\")}}\nimport z from \"zod\";\nimport { router, publicProcedure } from \"../index\";\nimport { Todo } from \"@{{projectName}}/db/models/todo.model\";\n\nexport const todoRouter = router({\n    getAll: publicProcedure.query(async () => {\n        return await Todo.find().lean();\n    }),\n\n    create: publicProcedure\n        .input(z.object({ text: z.string().min(1) }))\n        .mutation(async ({ input }) => {\n            const newTodo = await Todo.create({ text: input.text });\n        return newTodo.toObject();\n    }),\n\n    toggle: publicProcedure\n        .input(z.object({ id: z.string(), completed: z.boolean() }))\n        .mutation(async ({ input }) => {\n            await Todo.updateOne({ id: input.id }, { completed: input.completed });\n            return { success: true };\n    }),\n\n    delete: publicProcedure\n        .input(z.object({ id: z.string() }))\n        .mutation(async ({ input }) => {\n            await Todo.deleteOne({ id: input.id });\n            return { success: true };\n    }),\n});\n{{/if}}\n"
  },
  {
    "path": "packages/template-generator/templates/examples/todo/server/mongoose/mongodb/src/models/todo.model.ts.hbs",
    "content": "import mongoose from 'mongoose';\n\nconst { Schema, model } = mongoose;\n\nconst todoSchema = new Schema({\n  id: {\n    type: mongoose.Schema.Types.ObjectId,\n    auto: true,\n  },\n  text: {\n    type: String,\n    required: true,\n  },\n  completed: {\n    type: Boolean,\n    default: false,\n  },\n}, {\n  collection: 'todo'\n});\n\nconst Todo = model('Todo', todoSchema);\n\nexport { Todo };\n"
  },
  {
    "path": "packages/template-generator/templates/examples/todo/server/prisma/base/src/routers/todo.ts.hbs",
    "content": "{{#if (eq api \"orpc\")}}\nimport z from \"zod\";\n{{#if (or (eq runtime \"workers\") (eq serverDeploy \"cloudflare\") (and (eq backend \"self\") (eq webDeploy \"cloudflare\")))}}\nimport { createPrismaClient } from \"@{{projectName}}/db\";\n{{else}}\nimport prisma from \"@{{projectName}}/db\";\n{{/if}}\nimport { publicProcedure } from \"../index\";\n\nexport const todoRouter = {\n  getAll: publicProcedure.handler(async ({{#if (and (eq backend \"self\") (eq webDeploy \"cloudflare\") (includes frontend \"svelte\"))}}{ context }{{/if}}) => {\n{{#if (or (eq runtime \"workers\") (eq serverDeploy \"cloudflare\") (and (eq backend \"self\") (eq webDeploy \"cloudflare\")))}}\n    const prisma = createPrismaClient({{#if (and (eq backend \"self\") (eq webDeploy \"cloudflare\") (includes frontend \"svelte\"))}}context.env{{/if}});\n{{/if}}\n    return await prisma.todo.findMany({\n      orderBy: {\n        id: \"asc\",\n      },\n    });\n  }),\n\n  create: publicProcedure\n    .input(z.object({ text: z.string().min(1) }))\n    .handler(async ({ input{{#if (and (eq backend \"self\") (eq webDeploy \"cloudflare\") (includes frontend \"svelte\"))}}, context{{/if}} }) => {\n{{#if (or (eq runtime \"workers\") (eq serverDeploy \"cloudflare\") (and (eq backend \"self\") (eq webDeploy \"cloudflare\")))}}\n      const prisma = createPrismaClient({{#if (and (eq backend \"self\") (eq webDeploy \"cloudflare\") (includes frontend \"svelte\"))}}context.env{{/if}});\n{{/if}}\n      return await prisma.todo.create({\n        data: {\n          text: input.text,\n        },\n      });\n    }),\n\n  toggle: publicProcedure\n    {{#if (eq database \"mongodb\")}}\n    .input(z.object({ id: z.string(), completed: z.boolean() }))\n    {{else}}\n    .input(z.object({ id: z.number(), completed: z.boolean() }))\n    {{/if}}\n    .handler(async ({ input{{#if (and (eq backend \"self\") (eq webDeploy \"cloudflare\") (includes frontend \"svelte\"))}}, context{{/if}} }) => {\n{{#if (or (eq runtime \"workers\") (eq serverDeploy \"cloudflare\") (and (eq backend \"self\") (eq webDeploy \"cloudflare\")))}}\n      const prisma = createPrismaClient({{#if (and (eq backend \"self\") (eq webDeploy \"cloudflare\") (includes frontend \"svelte\"))}}context.env{{/if}});\n{{/if}}\n      return await prisma.todo.update({\n        where: { id: input.id },\n        data: { completed: input.completed },\n      });\n    }),\n\n  delete: publicProcedure\n    {{#if (eq database \"mongodb\")}}\n    .input(z.object({ id: z.string() }))\n    {{else}}\n    .input(z.object({ id: z.number() }))\n    {{/if}}\n    .handler(async ({ input{{#if (and (eq backend \"self\") (eq webDeploy \"cloudflare\") (includes frontend \"svelte\"))}}, context{{/if}} }) => {\n{{#if (or (eq runtime \"workers\") (eq serverDeploy \"cloudflare\") (and (eq backend \"self\") (eq webDeploy \"cloudflare\")))}}\n      const prisma = createPrismaClient({{#if (and (eq backend \"self\") (eq webDeploy \"cloudflare\") (includes frontend \"svelte\"))}}context.env{{/if}});\n{{/if}}\n      return await prisma.todo.delete({\n        where: { id: input.id },\n      });\n    }),\n};\n{{/if}}\n\n{{#if (eq api \"trpc\")}}\nimport { TRPCError } from \"@trpc/server\";\nimport z from \"zod\";\n{{#if (or (eq runtime \"workers\") (eq serverDeploy \"cloudflare\") (and (eq backend \"self\") (eq webDeploy \"cloudflare\")))}}\nimport { createPrismaClient } from \"@{{projectName}}/db\";\n{{else}}\nimport prisma from \"@{{projectName}}/db\";\n{{/if}}\nimport { publicProcedure, router } from \"../index\";\n\nexport const todoRouter = router({\n  getAll: publicProcedure.query(async () => {\n{{#if (or (eq runtime \"workers\") (eq serverDeploy \"cloudflare\") (and (eq backend \"self\") (eq webDeploy \"cloudflare\")))}}\n    const prisma = createPrismaClient();\n{{/if}}\n    return await prisma.todo.findMany({\n      orderBy: {\n        id: \"asc\"\n      }\n    });\n  }),\n\n  create: publicProcedure\n    .input(z.object({ text: z.string().min(1) }))\n    .mutation(async ({ input }) => {\n{{#if (or (eq runtime \"workers\") (eq serverDeploy \"cloudflare\") (and (eq backend \"self\") (eq webDeploy \"cloudflare\")))}}\n      const prisma = createPrismaClient();\n{{/if}}\n      return await prisma.todo.create({\n        data: {\n          text: input.text,\n        },\n      });\n    }),\n\n  toggle: publicProcedure\n    {{#if (eq database \"mongodb\")}}\n    .input(z.object({ id: z.string(), completed: z.boolean() }))\n    {{else}}\n    .input(z.object({ id: z.number(), completed: z.boolean() }))\n    {{/if}}\n    .mutation(async ({ input }) => {\n{{#if (or (eq runtime \"workers\") (eq serverDeploy \"cloudflare\") (and (eq backend \"self\") (eq webDeploy \"cloudflare\")))}}\n      const prisma = createPrismaClient();\n{{/if}}\n      try {\n        return await prisma.todo.update({\n          where: { id: input.id },\n          data: { completed: input.completed },\n        });\n      } catch (error) {\n        throw new TRPCError({\n          code: \"NOT_FOUND\",\n          message: \"Todo not found\",\n        });\n      }\n    }),\n\n  delete: publicProcedure\n    {{#if (eq database \"mongodb\")}}\n    .input(z.object({ id: z.string() }))\n    {{else}}\n    .input(z.object({ id: z.number() }))\n    {{/if}}\n    .mutation(async ({ input }) => {\n{{#if (or (eq runtime \"workers\") (eq serverDeploy \"cloudflare\") (and (eq backend \"self\") (eq webDeploy \"cloudflare\")))}}\n      const prisma = createPrismaClient();\n{{/if}}\n      try {\n        return await prisma.todo.delete({\n          where: { id: input.id },\n        });\n      } catch (error) {\n        throw new TRPCError({\n          code: \"NOT_FOUND\",\n          message: \"Todo not found\",\n        });\n      }\n    }),\n});\n{{/if}}\n"
  },
  {
    "path": "packages/template-generator/templates/examples/todo/server/prisma/mongodb/prisma/schema/todo.prisma.hbs",
    "content": "model Todo {\n  id        String  @id @default(auto()) @map(\"_id\") @db.ObjectId\n  text      String\n  completed Boolean @default(false)\n\n  @@map(\"todo\")\n}\n"
  },
  {
    "path": "packages/template-generator/templates/examples/todo/server/prisma/mysql/prisma/schema/todo.prisma.hbs",
    "content": "model Todo {\n  id        Int     @id @default(autoincrement())\n  text      String\n  completed Boolean @default(false)\n\n  @@map(\"todo\")\n}\n"
  },
  {
    "path": "packages/template-generator/templates/examples/todo/server/prisma/postgres/prisma/schema/todo.prisma.hbs",
    "content": "model Todo {\n  id        Int     @id @default(autoincrement())\n  text      String\n  completed Boolean @default(false)\n\n  @@map(\"todo\")\n}\n"
  },
  {
    "path": "packages/template-generator/templates/examples/todo/server/prisma/sqlite/prisma/schema/todo.prisma.hbs",
    "content": "model Todo {\n  id        Int     @id @default(autoincrement())\n  text      String\n  completed Boolean @default(false)\n\n  @@map(\"todo\")\n}\n"
  },
  {
    "path": "packages/template-generator/templates/examples/todo/web/astro/src/pages/todos.astro.hbs",
    "content": "---\nimport Layout from \"../layouts/Layout.astro\";\n---\n\n<Layout title=\"Todos - {{projectName}}\">\n  <div class=\"p-4 max-w-2xl mx-auto\">\n    <h1 class=\"text-xl mb-4 text-white\">Todos</h1>\n\n    <form id=\"add-form\" class=\"flex gap-2 mb-4\">\n      <input\n        type=\"text\"\n        id=\"new-todo\"\n        placeholder=\"New task...\"\n        class=\"p-2 flex-grow rounded border border-neutral-700 bg-neutral-800 text-white placeholder-neutral-500\"\n      />\n      <button\n        type=\"submit\"\n        id=\"add-btn\"\n        class=\"bg-blue-500 text-white px-4 py-2 rounded disabled:opacity-50 hover:bg-blue-600 transition-colors\"\n      >\n        Add\n      </button>\n    </form>\n\n    <div id=\"loading\" class=\"text-neutral-400\">Loading...</div>\n    <div id=\"empty\" class=\"hidden text-neutral-400\">No todos yet.</div>\n    <ul id=\"todo-list\" class=\"space-y-1 hidden\"></ul>\n    <p id=\"error\" class=\"mt-4 text-red-500 hidden\"></p>\n  </div>\n</Layout>\n\n<script>\n  import { orpc } from \"../lib/orpc\";\n\n  interface Todo {\n    id: number;\n    text: string;\n    completed: boolean;\n  }\n\n  const newTodoInput = document.getElementById(\"new-todo\") as HTMLInputElement;\n  const addBtn = document.getElementById(\"add-btn\") as HTMLButtonElement;\n  const addForm = document.getElementById(\"add-form\") as HTMLFormElement;\n  const loadingEl = document.getElementById(\"loading\")!;\n  const emptyEl = document.getElementById(\"empty\")!;\n  const todoList = document.getElementById(\"todo-list\")!;\n  const errorEl = document.getElementById(\"error\")!;\n\n  let todos: Todo[] = [];\n\n  function showError(message: string) {\n    errorEl.textContent = message;\n    errorEl.classList.remove(\"hidden\");\n    setTimeout(() => errorEl.classList.add(\"hidden\"), 3000);\n  }\n\n  function renderTodos() {\n    loadingEl.classList.add(\"hidden\");\n    \n    if (todos.length === 0) {\n      emptyEl.classList.remove(\"hidden\");\n      todoList.classList.add(\"hidden\");\n      return;\n    }\n\n    emptyEl.classList.add(\"hidden\");\n    todoList.classList.remove(\"hidden\");\n    todoList.innerHTML = todos.map(todo => `\n      <li class=\"flex items-center justify-between p-2 rounded bg-neutral-800/50\" data-id=\"${todo.id}\">\n        <div class=\"flex items-center gap-2\">\n          <input\n            type=\"checkbox\"\n            id=\"todo-${todo.id}\"\n            ${todo.completed ? \"checked\" : \"\"}\n            class=\"toggle-checkbox cursor-pointer\"\n          />\n          <label for=\"todo-${todo.id}\" class=\"${todo.completed ? \"line-through text-neutral-500\" : \"text-white\"}\">\n            ${todo.text}\n          </label>\n        </div>\n        <button\n          class=\"delete-btn text-red-500 px-2 hover:text-red-400 transition-colors\"\n          aria-label=\"Delete todo\"\n        >\n          X\n        </button>\n      </li>\n    `).join(\"\");\n\n    // Add event listeners\n    todoList.querySelectorAll(\".toggle-checkbox\").forEach(checkbox => {\n      checkbox.addEventListener(\"change\", async (e) => {\n        const li = (e.target as HTMLElement).closest(\"li\")!;\n        const id = parseInt(li.dataset.id!);\n        const todo = todos.find(t => t.id === id);\n        if (todo) {\n          await toggleTodo(id, todo.completed);\n        }\n      });\n    });\n\n    todoList.querySelectorAll(\".delete-btn\").forEach(btn => {\n      btn.addEventListener(\"click\", async (e) => {\n        const li = (e.target as HTMLElement).closest(\"li\")!;\n        const id = parseInt(li.dataset.id!);\n        await deleteTodo(id);\n      });\n    });\n  }\n\n  async function loadTodos() {\n    try {\n      todos = await orpc.todo.getAll();\n      renderTodos();\n    } catch (e) {\n      loadingEl.classList.add(\"hidden\");\n      showError(\"Failed to load todos\");\n    }\n  }\n\n  async function addTodo(text: string) {\n    addBtn.disabled = true;\n    addBtn.textContent = \"Adding...\";\n    try {\n      await orpc.todo.create({ text });\n      newTodoInput.value = \"\";\n      await loadTodos();\n    } catch (e) {\n      showError(\"Failed to add todo\");\n    } finally {\n      addBtn.disabled = false;\n      addBtn.textContent = \"Add\";\n    }\n  }\n\n  async function toggleTodo(id: number, completed: boolean) {\n    try {\n      await orpc.todo.toggle({ id, completed: !completed });\n      await loadTodos();\n    } catch (e) {\n      showError(\"Failed to update todo\");\n    }\n  }\n\n  async function deleteTodo(id: number) {\n    try {\n      await orpc.todo.delete({ id });\n      await loadTodos();\n    } catch (e) {\n      showError(\"Failed to delete todo\");\n    }\n  }\n\n  addForm.addEventListener(\"submit\", async (e) => {\n    e.preventDefault();\n    const text = newTodoInput.value.trim();\n    if (text) {\n      await addTodo(text);\n    }\n  });\n\n  loadTodos();\n</script>\n"
  },
  {
    "path": "packages/template-generator/templates/examples/todo/web/nuxt/app/pages/todos.vue.hbs",
    "content": "<script setup lang=\"ts\">\nimport { ref } from 'vue'\n{{#if (eq backend \"convex\")}}\nimport { api } from \"@{{ projectName }}/backend/convex/_generated/api\";\nimport type { Id } from \"@{{ projectName }}/backend/convex/_generated/dataModel\";\nimport { useConvexMutation, useConvexQuery } from \"convex-vue\";\n\nconst { data, error, isPending } = useConvexQuery(api.todos.getAll, {});\n\nconst newTodoText = ref(\"\");\nconst { mutate: createTodo, isPending: isCreatePending } = useConvexMutation(api.todos.create);\n\nconst { mutate: toggleTodo } = useConvexMutation(api.todos.toggle);\nconst { mutate: deleteTodo, error: deleteError } = useConvexMutation(\n  api.todos.deleteTodo,\n);\n\nfunction handleAddTodo() {\n  const text = newTodoText.value.trim();\n  if (!text) return;\n\n  createTodo({ text });\n  newTodoText.value = \"\";\n}\n\nfunction handleToggleTodo(id: Id<\"todos\">, completed: boolean) {\n  toggleTodo({ id, completed: !completed });\n}\n\nfunction handleDeleteTodo(id: Id<\"todos\">) {\n  deleteTodo({ id });\n}\n{{else}}\nimport { useQuery, useMutation, useQueryClient } from '@tanstack/vue-query'\n\nconst { $orpc } = useNuxtApp()\n\nconst newTodoText = ref('')\nconst queryClient = useQueryClient()\n\nconst todos = useQuery($orpc.todo.getAll.queryOptions())\n\nonServerPrefetch(async () => {\n  try {\n    await todos.suspense()\n  } catch {}\n})\n\nconst createMutation = useMutation($orpc.todo.create.mutationOptions({\n  onSuccess: () => {\n    queryClient.invalidateQueries()\n    newTodoText.value = ''\n  }\n}))\n\nconst toggleMutation = useMutation($orpc.todo.toggle.mutationOptions({\n  onSuccess: () => queryClient.invalidateQueries()\n}))\n\nconst deleteMutation = useMutation($orpc.todo.delete.mutationOptions({\n  onSuccess: () => queryClient.invalidateQueries()\n}))\n\nfunction handleAddTodo() {\n  if (newTodoText.value.trim()) {\n    createMutation.mutate({ text: newTodoText.value })\n  }\n}\n\nfunction handleToggleTodo(id: number, completed: boolean) {\n  toggleMutation.mutate({ id, completed: !completed })\n}\n\nfunction handleDeleteTodo(id: number) {\n  deleteMutation.mutate({ id })\n}\n{{/if}}\n</script>\n\n<template>\n  <UContainer class=\"py-8 max-w-md\">\n    <UCard>\n      <template #header>\n        <div>\n          <div class=\"text-xl font-bold\">Todo List</div>\n          <div class=\"text-muted text-sm\">Manage your tasks efficiently</div>\n        </div>\n      </template>\n\n      <form @submit.prevent=\"handleAddTodo\" class=\"mb-6 flex items-center gap-2\">\n        <UInput\n          v-model=\"newTodoText\"\n          placeholder=\"Add a new task...\"\n          autocomplete=\"off\"\n          class=\"flex-1\"\n          {{#if (eq backend \"convex\")}}\n          :disabled=\"isCreatePending\"\n          {{/if}}\n        />\n        <UButton\n          type=\"submit\"\n          {{#if (eq backend \"convex\")}}\n          :loading=\"isCreatePending\"\n          :disabled=\"!newTodoText.trim()\"\n          {{else}}\n          :loading=\"createMutation.isPending.value\"\n          {{/if}}\n        >\n          Add\n        </UButton>\n      </form>\n\n      {{#if (eq backend \"convex\")}}\n      <!-- Loading State -->\n      <div v-if=\"isPending\" class=\"space-y-2\">\n        <USkeleton v-for=\"i in 3\" :key=\"i\" class=\"h-12 w-full\" />\n      </div>\n\n      <!-- Error State -->\n      <UAlert\n        v-else-if=\"error || deleteError\"\n        color=\"error\"\n        icon=\"i-lucide-alert-circle\"\n        title=\"Error\"\n        :description=\"error?.message || deleteError?.message\"\n      />\n\n      <!-- Empty State -->\n      <UEmpty\n        v-else-if=\"data?.length === 0\"\n        icon=\"i-lucide-clipboard-list\"\n        title=\"No todos yet\"\n        description=\"Add your first task above!\"\n      />\n\n      <!-- Todo List -->\n      <ul v-else-if=\"data\" class=\"space-y-2\">\n        <li\n          v-for=\"todo in data\"\n          :key=\"todo._id\"\n          class=\"flex items-center justify-between rounded-md border p-3\"\n        >\n          <div class=\"flex items-center gap-3\">\n            <UCheckbox\n              :model-value=\"todo.completed\"\n              @update:model-value=\"() => handleToggleTodo(todo._id, todo.completed)\"\n              :id=\"`todo-${todo._id}`\"\n            />\n            <label\n              :for=\"`todo-${todo._id}`\"\n              :class=\"{ 'line-through text-muted': todo.completed }\"\n              class=\"cursor-pointer\"\n            >\n              \\{{ todo.text }}\n            </label>\n          </div>\n          <UButton\n            color=\"error\"\n            variant=\"ghost\"\n            size=\"sm\"\n            square\n            @click=\"handleDeleteTodo(todo._id)\"\n            aria-label=\"Delete todo\"\n            icon=\"i-lucide-trash-2\"\n          />\n        </li>\n      </ul>\n      {{else}}\n      <!-- Loading State -->\n      <div v-if=\"todos.status.value === 'pending'\" class=\"space-y-2\">\n        <USkeleton v-for=\"i in 3\" :key=\"i\" class=\"h-12 w-full\" />\n      </div>\n\n      <!-- Error State -->\n      <UAlert\n        v-else-if=\"todos.status.value === 'error'\"\n        color=\"error\"\n        icon=\"i-lucide-alert-circle\"\n        title=\"Failed to load todos\"\n        :description=\"todos.error.value?.message\"\n      />\n\n      <!-- Empty State -->\n      <UEmpty\n        v-else-if=\"todos.data.value?.length === 0\"\n        icon=\"i-lucide-clipboard-list\"\n        title=\"No todos yet\"\n        description=\"Add your first task above!\"\n      />\n\n      <!-- Todo List -->\n      <ul v-else class=\"space-y-2\">\n        <li\n          v-for=\"todo in todos.data.value\"\n          :key=\"todo.id\"\n          class=\"flex items-center justify-between rounded-md border p-3\"\n        >\n          <div class=\"flex items-center gap-3\">\n            <UCheckbox\n              :model-value=\"todo.completed\"\n              @update:model-value=\"() => handleToggleTodo(todo.id, todo.completed)\"\n              :id=\"`todo-${todo.id}`\"\n            />\n            <label\n              :for=\"`todo-${todo.id}`\"\n              :class=\"{ 'line-through text-muted': todo.completed }\"\n              class=\"cursor-pointer\"\n            >\n              \\{{ todo.text }}\n            </label>\n          </div>\n          <UButton\n            color=\"error\"\n            variant=\"ghost\"\n            size=\"sm\"\n            square\n            @click=\"handleDeleteTodo(todo.id)\"\n            aria-label=\"Delete todo\"\n            icon=\"i-lucide-trash-2\"\n          />\n        </li>\n      </ul>\n      {{/if}}\n    </UCard>\n  </UContainer>\n</template>\n"
  },
  {
    "path": "packages/template-generator/templates/examples/todo/web/react/next/src/app/todos/page.tsx.hbs",
    "content": "\"use client\"\n\nimport { Button } from \"@{{projectName}}/ui/components/button\";\nimport {\n  Card,\n  CardContent,\n  CardDescription,\n  CardHeader,\n  CardTitle,\n} from \"@{{projectName}}/ui/components/card\";\nimport { Checkbox } from \"@{{projectName}}/ui/components/checkbox\";\nimport { Input } from \"@{{projectName}}/ui/components/input\";\nimport { Loader2, Trash2 } from \"lucide-react\";\nimport { useState, type FormEvent } from \"react\";\n\n{{#if (eq backend \"convex\")}}\nimport { useMutation, useQuery } from \"convex/react\";\nimport { api } from \"@{{projectName}}/backend/convex/_generated/api\";\nimport type { Id } from \"@{{projectName}}/backend/convex/_generated/dataModel\";\n{{else}}\nimport { useMutation, useQuery } from \"@tanstack/react-query\";\n  {{#if (eq api \"orpc\")}}\nimport { orpc } from \"@/utils/orpc\";\n  {{/if}}\n  {{#if (eq api \"trpc\")}}\nimport { trpc } from \"@/utils/trpc\";\n  {{/if}}\n{{/if}}\n\n\nexport default function TodosPage() {\n  const [newTodoText, setNewTodoText] = useState(\"\");\n\n  {{#if (eq backend \"convex\")}}\n  const todos = useQuery(api.todos.getAll);\n  const createTodoMutation = useMutation(api.todos.create);\n  const toggleTodoMutation = useMutation(api.todos.toggle);\n  const deleteTodoMutation = useMutation(api.todos.deleteTodo);\n\n  const handleAddTodo = async (e: FormEvent<HTMLFormElement>) => {\n    e.preventDefault();\n    const text = newTodoText.trim();\n    if (!text) return;\n    await createTodoMutation({ text });\n    setNewTodoText(\"\");\n  };\n\n  const handleToggleTodo = (id: Id<\"todos\">, currentCompleted: boolean) => {\n    toggleTodoMutation({ id, completed: !currentCompleted });\n  };\n\n  const handleDeleteTodo = (id: Id<\"todos\">) => {\n    deleteTodoMutation({ id });\n  };\n  {{else}}\n    {{#if (eq api \"orpc\")}}\n    const todos = useQuery(orpc.todo.getAll.queryOptions());\n    const createMutation = useMutation(\n      orpc.todo.create.mutationOptions({\n        onSuccess: () => {\n          todos.refetch();\n          setNewTodoText(\"\");\n        },\n      }),\n    );\n    const toggleMutation = useMutation(\n      orpc.todo.toggle.mutationOptions({\n        onSuccess: () => { todos.refetch() },\n      }),\n    );\n    const deleteMutation = useMutation(\n      orpc.todo.delete.mutationOptions({\n        onSuccess: () => { todos.refetch() },\n      }),\n    );\n    {{/if}}\n    {{#if (eq api \"trpc\")}}\n    const todos = useQuery(trpc.todo.getAll.queryOptions());\n    const createMutation = useMutation(\n      trpc.todo.create.mutationOptions({\n        onSuccess: () => {\n          todos.refetch();\n          setNewTodoText(\"\");\n        },\n      }),\n    );\n    const toggleMutation = useMutation(\n      trpc.todo.toggle.mutationOptions({\n        onSuccess: () => { todos.refetch() },\n      }),\n    );\n    const deleteMutation = useMutation(\n      trpc.todo.delete.mutationOptions({\n        onSuccess: () => { todos.refetch() },\n      }),\n    );\n    {{/if}}\n\n  const handleAddTodo = (e: FormEvent<HTMLFormElement>) => {\n    e.preventDefault();\n    if (newTodoText.trim()) {\n      createMutation.mutate({ text: newTodoText });\n    }\n  };\n\n  const handleToggleTodo = (id: number, completed: boolean) => {\n    toggleMutation.mutate({ id, completed: !completed });\n  };\n\n  const handleDeleteTodo = (id: number) => {\n    deleteMutation.mutate({ id });\n  };\n  {{/if}}\n\n  return (\n    <div className=\"mx-auto w-full max-w-md py-10\">\n      <Card>\n        <CardHeader>\n          <CardTitle>Todo List</CardTitle>\n          <CardDescription>Manage your tasks efficiently</CardDescription>\n        </CardHeader>\n        <CardContent>\n          <form\n            onSubmit={handleAddTodo}\n            className=\"mb-6 flex items-center space-x-2\"\n          >\n            <Input\n              value={newTodoText}\n              onChange={(e) => setNewTodoText(e.target.value)}\n              placeholder=\"Add a new task...\"\n              {{#if (eq backend \"convex\")}}\n              {{else}}\n              disabled={createMutation.isPending}\n              {{/if}}\n            />\n            <Button\n              type=\"submit\"\n              {{#if (eq backend \"convex\")}}\n              disabled={!newTodoText.trim()}\n              {{else}}\n              disabled={createMutation.isPending || !newTodoText.trim()}\n              {{/if}}\n            >\n              {{#if (eq backend \"convex\")}}\n                Add\n              {{else}}\n                {createMutation.isPending ? (\n                  <Loader2 className=\"h-4 w-4 animate-spin\" />\n                ) : (\n                  \"Add\"\n                )}\n              {{/if}}\n            </Button>\n          </form>\n\n          {{#if (eq backend \"convex\")}}\n            {todos === undefined ? (\n              <div className=\"flex justify-center py-4\">\n                <Loader2 className=\"h-6 w-6 animate-spin\" />\n              </div>\n            ) : todos.length === 0 ? (\n              <p className=\"py-4 text-center\">No todos yet. Add one above!</p>\n            ) : (\n              <ul className=\"space-y-2\">\n                {todos.map((todo) => (\n                  <li\n                    key={todo._id}\n                    className=\"flex items-center justify-between rounded-md border p-2\"\n                  >\n                    <div className=\"flex items-center space-x-2\">\n                      <Checkbox\n                        checked={todo.completed}\n                        onCheckedChange={() =>\n                          handleToggleTodo(todo._id, todo.completed)\n                        }\n                        id={`todo-${todo._id}`}\n                      />\n                      <label\n                        htmlFor={`todo-${todo._id}`}\n                        className={`${todo.completed ? \"line-through text-muted-foreground\" : \"\"}`}\n                      >\n                        {todo.text}\n                      </label>\n                    </div>\n                    <Button\n                      variant=\"ghost\"\n                      size=\"icon\"\n                      onClick={() => handleDeleteTodo(todo._id)}\n                      aria-label=\"Delete todo\"\n                    >\n                      <Trash2 className=\"h-4 w-4\" />\n                    </Button>\n                  </li>\n                ))}\n              </ul>\n            )}\n          {{else}}\n            {todos.isLoading ? (\n              <div className=\"flex justify-center py-4\">\n                <Loader2 className=\"h-6 w-6 animate-spin\" />\n              </div>\n            ) : todos.data?.length === 0 ? (\n              <p className=\"py-4 text-center\">\n                No todos yet. Add one above!\n              </p>\n            ) : (\n              <ul className=\"space-y-2\">\n                {todos.data?.map((todo) => (\n                  <li\n                    key={todo.id}\n                    className=\"flex items-center justify-between rounded-md border p-2\"\n                  >\n                    <div className=\"flex items-center space-x-2\">\n                      <Checkbox\n                        checked={todo.completed}\n                        onCheckedChange={() =>\n                          handleToggleTodo(todo.id, todo.completed)\n                        }\n                        id={`todo-${todo.id}`}\n                      />\n                      <label\n                        htmlFor={`todo-${todo.id}`}\n                        className={`${todo.completed ? \"line-through text-muted-foreground\" : \"\"}`}\n                      >\n                        {todo.text}\n                      </label>\n                    </div>\n                    <Button\n                      variant=\"ghost\"\n                      size=\"icon\"\n                      onClick={() => handleDeleteTodo(todo.id)}\n                      aria-label=\"Delete todo\"\n                    >\n                      <Trash2 className=\"h-4 w-4\" />\n                    </Button>\n                  </li>\n                ))}\n              </ul>\n            )}\n          {{/if}}\n        </CardContent>\n      </Card>\n    </div>\n  );\n}\n"
  },
  {
    "path": "packages/template-generator/templates/examples/todo/web/react/react-router/src/routes/todos.tsx.hbs",
    "content": "import { Button } from \"@{{projectName}}/ui/components/button\";\nimport {\n  Card,\n  CardContent,\n  CardDescription,\n  CardHeader,\n  CardTitle,\n} from \"@{{projectName}}/ui/components/card\";\nimport { Checkbox } from \"@{{projectName}}/ui/components/checkbox\";\nimport { Input } from \"@{{projectName}}/ui/components/input\";\nimport { Loader2, Trash2 } from \"lucide-react\";\nimport { useState, type FormEvent } from \"react\";\n\n{{#if (eq backend \"convex\")}}\nimport { useMutation, useQuery } from \"convex/react\";\nimport { api } from \"@{{projectName}}/backend/convex/_generated/api\";\nimport type { Id } from \"@{{projectName}}/backend/convex/_generated/dataModel\";\n{{else}}\n  {{#if (eq api \"orpc\")}}\n  import { orpc } from \"@/utils/orpc\";\n  {{/if}}\n  {{#if (eq api \"trpc\")}}\n  import { trpc } from \"@/utils/trpc\";\n  {{/if}}\nimport { useMutation, useQuery } from \"@tanstack/react-query\";\n{{/if}}\n\nexport default function Todos() {\n  const [newTodoText, setNewTodoText] = useState(\"\");\n\n  {{#if (eq backend \"convex\")}}\n  const todos = useQuery(api.todos.getAll);\n  const createTodo = useMutation(api.todos.create);\n  const toggleTodo = useMutation(api.todos.toggle);\n  const deleteTodo = useMutation(api.todos.deleteTodo);\n\n  const handleAddTodo = async (e: FormEvent<HTMLFormElement>) => {\n    e.preventDefault();\n    const text = newTodoText.trim();\n    if (!text) return;\n    await createTodo({ text });\n    setNewTodoText(\"\");\n  };\n\n  const handleToggleTodo = (id: Id<\"todos\">, currentCompleted: boolean) => {\n    toggleTodo({ id, completed: !currentCompleted });\n  };\n\n  const handleDeleteTodo = (id: Id<\"todos\">) => {\n    deleteTodo({ id });\n  };\n  {{else}}\n    {{#if (eq api \"orpc\")}}\n    const todos = useQuery(orpc.todo.getAll.queryOptions());\n    const createMutation = useMutation(\n      orpc.todo.create.mutationOptions({\n        onSuccess: () => {\n          todos.refetch();\n          setNewTodoText(\"\");\n        },\n      })\n    );\n    const toggleMutation = useMutation(\n      orpc.todo.toggle.mutationOptions({\n        onSuccess: () => { todos.refetch() },\n      })\n    );\n    const deleteMutation = useMutation(\n      orpc.todo.delete.mutationOptions({\n        onSuccess: () => { todos.refetch() },\n      })\n    );\n    {{/if}}\n    {{#if (eq api \"trpc\")}}\n    const todos = useQuery(trpc.todo.getAll.queryOptions());\n    const createMutation = useMutation(\n      trpc.todo.create.mutationOptions({\n        onSuccess: () => {\n          todos.refetch();\n          setNewTodoText(\"\");\n        },\n      })\n    );\n    const toggleMutation = useMutation(\n      trpc.todo.toggle.mutationOptions({\n        onSuccess: () => { todos.refetch() },\n      })\n    );\n    const deleteMutation = useMutation(\n      trpc.todo.delete.mutationOptions({\n        onSuccess: () => { todos.refetch() },\n      })\n    );\n    {{/if}}\n\n  const handleAddTodo = (e: FormEvent<HTMLFormElement>) => {\n    e.preventDefault();\n    if (newTodoText.trim()) {\n      createMutation.mutate({ text: newTodoText });\n    }\n  };\n\n  const handleToggleTodo = (id: number, completed: boolean) => {\n    toggleMutation.mutate({ id, completed: !completed });\n  };\n\n  const handleDeleteTodo = (id: number) => {\n    deleteMutation.mutate({ id });\n  };\n  {{/if}}\n\n  return (\n    <div className=\"w-full mx-auto max-w-md py-10\">\n      <Card>\n        <CardHeader>\n          <CardTitle>Todo List</CardTitle>\n          <CardDescription>Manage your tasks efficiently</CardDescription>\n        </CardHeader>\n        <CardContent>\n          <form\n            onSubmit={handleAddTodo}\n            className=\"mb-6 flex items-center space-x-2\"\n          >\n            <Input\n              value={newTodoText}\n              onChange={(e) => setNewTodoText(e.target.value)}\n              placeholder=\"Add a new task...\"\n              {{#if (eq backend \"convex\")}}\n              {{else}}\n              disabled={createMutation.isPending}\n              {{/if}}\n            />\n            <Button\n              type=\"submit\"\n              {{#if (eq backend \"convex\")}}\n              disabled={!newTodoText.trim()}\n              {{else}}\n              disabled={createMutation.isPending || !newTodoText.trim()}\n              {{/if}}\n            >\n              {{#if (eq backend \"convex\")}}\n              Add\n              {{else}}\n                {createMutation.isPending ? (\n                  <Loader2 className=\"h-4 w-4 animate-spin\" />\n                ) : (\n                  \"Add\"\n                )}\n              {{/if}}\n            </Button>\n          </form>\n\n          {{#if (eq backend \"convex\")}}\n            {todos === undefined ? (\n              <div className=\"flex justify-center py-4\">\n                <Loader2 className=\"h-6 w-6 animate-spin\" />\n              </div>\n            ) : todos.length === 0 ? (\n              <p className=\"py-4 text-center\">No todos yet. Add one above!</p>\n            ) : (\n              <ul className=\"space-y-2\">\n                {todos.map((todo) => (\n                  <li\n                    key={todo._id}\n                    className=\"flex items-center justify-between rounded-md border p-2\"\n                  >\n                    <div className=\"flex items-center space-x-2\">\n                      <Checkbox\n                        checked={todo.completed}\n                        onCheckedChange={() =>\n                          handleToggleTodo(todo._id, todo.completed)\n                        }\n                        id={`todo-${todo._id}`}\n                      />\n                      <label\n                        htmlFor={`todo-${todo._id}`}\n                        className={`${todo.completed ? \"line-through text-muted-foreground\" : \"\"}`}\n                      >\n                        {todo.text}\n                      </label>\n                    </div>\n                    <Button\n                      variant=\"ghost\"\n                      size=\"icon\"\n                      onClick={() => handleDeleteTodo(todo._id)}\n                      aria-label=\"Delete todo\"\n                    >\n                      <Trash2 className=\"h-4 w-4\" />\n                    </Button>\n                  </li>\n                ))}\n              </ul>\n            )}\n          {{else}}\n            {todos.isLoading ? (\n              <div className=\"flex justify-center py-4\">\n                <Loader2 className=\"h-6 w-6 animate-spin\" />\n              </div>\n            ) : todos.data?.length === 0 ? (\n              <p className=\"py-4 text-center\">\n                No todos yet. Add one above!\n              </p>\n            ) : (\n              <ul className=\"space-y-2\">\n                {todos.data?.map((todo) => (\n                  <li\n                    key={todo.id}\n                    className=\"flex items-center justify-between rounded-md border p-2\"\n                  >\n                    <div className=\"flex items-center space-x-2\">\n                      <Checkbox\n                        checked={todo.completed}\n                        onCheckedChange={() =>\n                          handleToggleTodo(todo.id, todo.completed)\n                        }\n                        id={`todo-${todo.id}`}\n                      />\n                      <label\n                        htmlFor={`todo-${todo.id}`}\n                        className={`${todo.completed ? \"line-through\" : \"\"}`}\n                      >\n                        {todo.text}\n                      </label>\n                    </div>\n                    <Button\n                      variant=\"ghost\"\n                      size=\"icon\"\n                      onClick={() => handleDeleteTodo(todo.id)}\n                      aria-label=\"Delete todo\"\n                    >\n                      <Trash2 className=\"h-4 w-4\" />\n                    </Button>\n                  </li>\n                ))}\n              </ul>\n            )}\n          {{/if}}\n        </CardContent>\n      </Card>\n    </div>\n  );\n}\n"
  },
  {
    "path": "packages/template-generator/templates/examples/todo/web/react/tanstack-router/src/routes/todos.tsx.hbs",
    "content": "import { Button } from \"@{{projectName}}/ui/components/button\";\nimport {\n  Card,\n  CardContent,\n  CardDescription,\n  CardHeader,\n  CardTitle,\n} from \"@{{projectName}}/ui/components/card\";\nimport { Checkbox } from \"@{{projectName}}/ui/components/checkbox\";\nimport { Input } from \"@{{projectName}}/ui/components/input\";\nimport { createFileRoute } from \"@tanstack/react-router\";\nimport { Loader2, Trash2 } from \"lucide-react\";\nimport { useState, type FormEvent } from \"react\";\n\n{{#if (eq backend \"convex\")}}\nimport { useMutation, useQuery } from \"convex/react\";\nimport { api } from \"@{{projectName}}/backend/convex/_generated/api\";\nimport type { Id } from \"@{{projectName}}/backend/convex/_generated/dataModel\";\n{{else}}\n  {{#if (eq api \"orpc\")}}\n  import { orpc } from \"@/utils/orpc\";\n  {{/if}}\n  {{#if (eq api \"trpc\")}}\n  import { trpc } from \"@/utils/trpc\";\n  {{/if}}\nimport { useMutation, useQuery } from \"@tanstack/react-query\";\n{{/if}}\n\nexport const Route = createFileRoute(\"/todos\")({\n  component: TodosRoute,\n});\n\nfunction TodosRoute() {\n  const [newTodoText, setNewTodoText] = useState(\"\");\n\n  {{#if (eq backend \"convex\")}}\n  const todos = useQuery(api.todos.getAll);\n  const createTodo = useMutation(api.todos.create);\n  const toggleTodo = useMutation(api.todos.toggle);\n  const deleteTodo = useMutation(api.todos.deleteTodo);\n\n  const handleAddTodo = async (e: FormEvent<HTMLFormElement>) => {\n    e.preventDefault();\n    const text = newTodoText.trim();\n    if (!text) return;\n    await createTodo({ text });\n    setNewTodoText(\"\");\n  };\n\n  const handleToggleTodo = (id: Id<\"todos\">, currentCompleted: boolean) => {\n    toggleTodo({ id, completed: !currentCompleted });\n  };\n\n  const handleDeleteTodo = (id: Id<\"todos\">) => {\n    deleteTodo({ id });\n  };\n  {{else}}\n    {{#if (eq api \"orpc\")}}\n    const todos = useQuery(orpc.todo.getAll.queryOptions());\n    const createMutation = useMutation(\n      orpc.todo.create.mutationOptions({\n        onSuccess: () => {\n          todos.refetch();\n          setNewTodoText(\"\");\n        },\n      }),\n    );\n    const toggleMutation = useMutation(\n      orpc.todo.toggle.mutationOptions({\n        onSuccess: () => { todos.refetch() },\n      }),\n    );\n    const deleteMutation = useMutation(\n      orpc.todo.delete.mutationOptions({\n        onSuccess: () => { todos.refetch() },\n      }),\n    );\n    {{/if}}\n    {{#if (eq api \"trpc\")}}\n    const todos = useQuery(trpc.todo.getAll.queryOptions());\n    const createMutation = useMutation(\n      trpc.todo.create.mutationOptions({\n        onSuccess: () => {\n          todos.refetch();\n          setNewTodoText(\"\");\n        },\n      }),\n    );\n    const toggleMutation = useMutation(\n      trpc.todo.toggle.mutationOptions({\n        onSuccess: () => { todos.refetch() },\n      }),\n    );\n    const deleteMutation = useMutation(\n      trpc.todo.delete.mutationOptions({\n        onSuccess: () => { todos.refetch() },\n      }),\n    );\n    {{/if}}\n\n  const handleAddTodo = (e: FormEvent<HTMLFormElement>) => {\n    e.preventDefault();\n    if (newTodoText.trim()) {\n      createMutation.mutate({ text: newTodoText });\n    }\n  };\n\n  const handleToggleTodo = (id: number, completed: boolean) => {\n    toggleMutation.mutate({ id, completed: !completed });\n  };\n\n  const handleDeleteTodo = (id: number) => {\n    deleteMutation.mutate({ id });\n  };\n  {{/if}}\n\n  return (\n    <div className=\"mx-auto w-full max-w-md py-10\">\n      <Card>\n        <CardHeader>\n          <CardTitle>Todo List</CardTitle>\n          <CardDescription>Manage your tasks efficiently</CardDescription>\n        </CardHeader>\n        <CardContent>\n          <form\n            onSubmit={handleAddTodo}\n            className=\"mb-6 flex items-center space-x-2\"\n          >\n            <Input\n              value={newTodoText}\n              onChange={(e) => setNewTodoText(e.target.value)}\n              placeholder=\"Add a new task...\"\n              {{#if (eq backend \"convex\")}}\n              {{else}}\n              disabled={createMutation.isPending}\n              {{/if}}\n            />\n            <Button\n              type=\"submit\"\n              {{#if (eq backend \"convex\")}}\n              disabled={!newTodoText.trim()}\n              {{else}}\n              disabled={createMutation.isPending || !newTodoText.trim()}\n              {{/if}}\n            >\n              {{#if (eq backend \"convex\")}}\n              Add\n              {{else}}\n                {createMutation.isPending ? (\n                  <Loader2 className=\"h-4 w-4 animate-spin\" />\n                ) : (\n                  \"Add\"\n                )}\n              {{/if}}\n            </Button>\n          </form>\n\n          {{#if (eq backend \"convex\")}}\n            {todos === undefined ? (\n              <div className=\"flex justify-center py-4\">\n                <Loader2 className=\"h-6 w-6 animate-spin\" />\n              </div>\n            ) : todos.length === 0 ? (\n              <p className=\"py-4 text-center\">No todos yet. Add one above!</p>\n            ) : (\n              <ul className=\"space-y-2\">\n                {todos.map((todo) => (\n                  <li\n                    key={todo._id}\n                    className=\"flex items-center justify-between rounded-md border p-2\"\n                  >\n                    <div className=\"flex items-center space-x-2\">\n                      <Checkbox\n                        checked={todo.completed}\n                        onCheckedChange={() =>\n                          handleToggleTodo(todo._id, todo.completed)\n                        }\n                        id={`todo-${todo._id}`}\n                      />\n                      <label\n                        htmlFor={`todo-${todo._id}`}\n                        className={`${todo.completed ? \"line-through text-muted-foreground\" : \"\"}`}\n                      >\n                        {todo.text}\n                      </label>\n                    </div>\n                    <Button\n                      variant=\"ghost\"\n                      size=\"icon\"\n                      onClick={() => handleDeleteTodo(todo._id)}\n                      aria-label=\"Delete todo\"\n                    >\n                      <Trash2 className=\"h-4 w-4\" />\n                    </Button>\n                  </li>\n                ))}\n              </ul>\n            )}\n          {{else}}\n            {todos.isLoading ? (\n              <div className=\"flex justify-center py-4\">\n                <Loader2 className=\"h-6 w-6 animate-spin\" />\n              </div>\n            ) : todos.data?.length === 0 ? (\n              <p className=\"py-4 text-center\">\n                No todos yet. Add one above!\n              </p>\n            ) : (\n              <ul className=\"space-y-2\">\n                {todos.data?.map((todo) => (\n                  <li\n                    key={todo.id}\n                    className=\"flex items-center justify-between rounded-md border p-2\"\n                  >\n                    <div className=\"flex items-center space-x-2\">\n                      <Checkbox\n                        checked={todo.completed}\n                        onCheckedChange={() =>\n                          handleToggleTodo(todo.id, todo.completed)\n                        }\n                        id={`todo-${todo.id}`}\n                      />\n                      <label\n                        htmlFor={`todo-${todo.id}`}\n                        className={`${todo.completed ? \"line-through\" : \"\"}`}\n                      >\n                        {todo.text}\n                      </label>\n                    </div>\n                    <Button\n                      variant=\"ghost\"\n                      size=\"icon\"\n                      onClick={() => handleDeleteTodo(todo.id)}\n                      aria-label=\"Delete todo\"\n                    >\n                      <Trash2 className=\"h-4 w-4\" />\n                    </Button>\n                  </li>\n                ))}\n              </ul>\n            )}\n          {{/if}}\n        </CardContent>\n      </Card>\n    </div>\n  );\n}\n"
  },
  {
    "path": "packages/template-generator/templates/examples/todo/web/react/tanstack-start/src/routes/todos.tsx.hbs",
    "content": "import { Button } from \"@{{projectName}}/ui/components/button\";\nimport {\n  Card,\n  CardContent,\n  CardDescription,\n  CardHeader,\n  CardTitle,\n} from \"@{{projectName}}/ui/components/card\";\nimport { Checkbox } from \"@{{projectName}}/ui/components/checkbox\";\nimport { Input } from \"@{{projectName}}/ui/components/input\";\nimport { createFileRoute } from \"@tanstack/react-router\";\n{{#if (eq backend \"convex\")}}\nimport { Trash2 } from \"lucide-react\";\n{{else}}\nimport { Loader2, Trash2 } from \"lucide-react\";\n{{/if}}\nimport { useState, type FormEvent } from \"react\";\n\n{{#if (eq backend \"convex\")}}\nimport { useSuspenseQuery } from \"@tanstack/react-query\";\nimport { convexQuery } from \"@convex-dev/react-query\";\nimport { useMutation } from \"convex/react\";\nimport { api } from \"@{{projectName}}/backend/convex/_generated/api\";\nimport type { Id } from \"@{{projectName}}/backend/convex/_generated/dataModel\";\n{{else}}\n{{#if (eq api \"trpc\")}}\nimport { useTRPC } from \"@/utils/trpc\";\n{{/if}}\n{{#if (eq api \"orpc\")}}\nimport { orpc } from \"@/utils/orpc\";\n{{/if}}\nimport { useMutation, useQuery } from \"@tanstack/react-query\";\n{{/if}}\n\nexport const Route = createFileRoute(\"/todos\")({\n  component: TodosRoute,\n});\n\nfunction TodosRoute() {\n  const [newTodoText, setNewTodoText] = useState(\"\");\n\n  {{#if (eq backend \"convex\")}}\n  const todosQuery = useSuspenseQuery(convexQuery(api.todos.getAll, {}));\n  const todos = todosQuery.data;\n\n  const createTodo = useMutation(api.todos.create);\n  const toggleTodo = useMutation(api.todos.toggle);\n  const removeTodo = useMutation(api.todos.deleteTodo);\n\n  const handleAddTodo = async (e: FormEvent<HTMLFormElement>) => {\n    e.preventDefault();\n    const text = newTodoText.trim();\n    if (text) {\n      setNewTodoText(\"\");\n      try {\n        await createTodo({ text });\n      } catch (error) {\n        console.error(\"Failed to add todo:\", error);\n        setNewTodoText(text);\n      }\n    }\n  };\n\n  const handleToggleTodo = async (id: Id<\"todos\">, completed: boolean) => {\n    try {\n      await toggleTodo({ id, completed: !completed });\n    } catch (error) {\n      console.error(\"Failed to toggle todo:\", error);\n    }\n  };\n\n  const handleDeleteTodo = async (id: Id<\"todos\">) => {\n    try {\n      await removeTodo({ id });\n    } catch (error) {\n      console.error(\"Failed to delete todo:\", error);\n    }\n  };\n  {{else}}\n    {{#if (eq api \"trpc\")}}\n  const trpc = useTRPC();\n    {{/if}}\n    {{#if (eq api \"orpc\")}}\n    {{/if}}\n\n    {{#if (eq api \"trpc\")}}\n  const todos = useQuery(trpc.todo.getAll.queryOptions());\n  const createMutation = useMutation(\n    trpc.todo.create.mutationOptions({\n      onSuccess: () => {\n        todos.refetch();\n        setNewTodoText(\"\");\n      },\n    }),\n  );\n  const toggleMutation = useMutation(\n    trpc.todo.toggle.mutationOptions({\n      onSuccess: () => { todos.refetch() },\n    }),\n  );\n  const deleteMutation = useMutation(\n    trpc.todo.delete.mutationOptions({\n      onSuccess: () => { todos.refetch() },\n    }),\n  );\n    {{/if}}\n    {{#if (eq api \"orpc\")}}\n  const todos = useQuery(orpc.todo.getAll.queryOptions());\n  const createMutation = useMutation(\n    orpc.todo.create.mutationOptions({\n      onSuccess: () => {\n        todos.refetch();\n        setNewTodoText(\"\");\n      },\n    }),\n  );\n  const toggleMutation = useMutation(\n    orpc.todo.toggle.mutationOptions({\n      onSuccess: () => { todos.refetch() },\n    }),\n  );\n  const deleteMutation = useMutation(\n    orpc.todo.delete.mutationOptions({\n      onSuccess: () => { todos.refetch() },\n    }),\n  );\n    {{/if}}\n\n  const handleAddTodo = (e: FormEvent<HTMLFormElement>) => {\n    e.preventDefault();\n    if (newTodoText.trim()) {\n      createMutation.mutate({ text: newTodoText });\n    }\n  };\n\n  const handleToggleTodo = (id: number, completed: boolean) => {\n    toggleMutation.mutate({ id, completed: !completed });\n  };\n\n  const handleDeleteTodo = (id: number) => {\n    deleteMutation.mutate({ id });\n  };\n  {{/if}}\n\n  return (\n    <div className=\"mx-auto w-full max-w-md py-10\">\n      <Card>\n        <CardHeader>\n          <CardTitle>Todo List{{#if (eq backend \"convex\")}} (Convex){{/if}}</CardTitle>\n          <CardDescription>Manage your tasks efficiently</CardDescription>\n        </CardHeader>\n        <CardContent>\n          <form\n            onSubmit={handleAddTodo}\n            className=\"mb-6 flex items-center space-x-2\"\n          >\n            <Input\n              value={newTodoText}\n              onChange={(e) => setNewTodoText(e.target.value)}\n              placeholder=\"Add a new task...\"\n              {{#unless (eq backend \"convex\")}}\n              disabled={createMutation.isPending}\n              {{/unless}}\n            />\n            <Button\n              type=\"submit\"\n              {{#unless (eq backend \"convex\")}}\n              disabled={createMutation.isPending || !newTodoText.trim()}\n              {{else}}\n              disabled={!newTodoText.trim()}\n              {{/unless}}\n            >\n              {{#unless (eq backend \"convex\")}}\n              {createMutation.isPending ? (\n                <Loader2 className=\"h-4 w-4 animate-spin\" />\n              ) : (\n                \"Add\"\n              )}\n              {{else}}\n              Add\n              {{/unless}}\n            </Button>\n          </form>\n\n          {{#if (eq backend \"convex\")}}\n          {todos?.length === 0 ? (\n            <p className=\"py-4 text-center\">No todos yet. Add one above!</p>\n          ) : (\n            <ul className=\"space-y-2\">\n              {todos?.map((todo) => (\n                <li\n                  key={todo._id}\n                  className=\"flex items-center justify-between rounded-md border p-2\"\n                >\n                  <div className=\"flex items-center space-x-2\">\n                    <Checkbox\n                      checked={todo.completed}\n                      onCheckedChange={() =>\n                        handleToggleTodo(todo._id, todo.completed)\n                      }\n                      id={`todo-${todo._id}`}\n                    />\n                    <label\n                      htmlFor={`todo-${todo._id}`}\n                      className={`${\n                        todo.completed\n                          ? \"text-muted-foreground line-through\"\n                          : \"\"\n                      }`}\n                    >\n                      {todo.text}\n                    </label>\n                  </div>\n                  <Button\n                    variant=\"ghost\"\n                    size=\"icon\"\n                    onClick={() => handleDeleteTodo(todo._id)}\n                    aria-label=\"Delete todo\"\n                  >\n                    <Trash2 className=\"h-4 w-4\" />\n                  </Button>\n                </li>\n              ))}\n            </ul>\n          )}\n          {{else}}\n          {todos.isLoading ? (\n            <div className=\"flex justify-center py-4\">\n              <Loader2 className=\"h-6 w-6 animate-spin\" />\n            </div>\n          ) : todos.data?.length === 0 ? (\n            <p className=\"py-4 text-center\">No todos yet. Add one above!</p>\n          ) : (\n            <ul className=\"space-y-2\">\n              {todos.data?.map((todo) => (\n                <li\n                  key={todo.id}\n                  className=\"flex items-center justify-between rounded-md border p-2\"\n                >\n                  <div className=\"flex items-center space-x-2\">\n                    <Checkbox\n                      checked={todo.completed}\n                      onCheckedChange={() =>\n                        handleToggleTodo(todo.id, todo.completed)\n                      }\n                      id={`todo-${todo.id}`}\n                    />\n                    <label\n                      htmlFor={`todo-${todo.id}`}\n                      className={`${todo.completed ? \"line-through\" : \"\"}`}\n                    >\n                      {todo.text}\n                    </label>\n                  </div>\n                  <Button\n                    variant=\"ghost\"\n                    size=\"icon\"\n                    onClick={() => handleDeleteTodo(todo.id)}\n                    aria-label=\"Delete todo\"\n                  >\n                    <Trash2 className=\"h-4 w-4\" />\n                  </Button>\n                </li>\n              ))}\n            </ul>\n          )}\n          {{/if}}\n        </CardContent>\n      </Card>\n    </div>\n  );\n}\n"
  },
  {
    "path": "packages/template-generator/templates/examples/todo/web/solid/src/routes/todos.tsx.hbs",
    "content": "import { createFileRoute } from \"@tanstack/solid-router\";\nimport { Loader2, Trash2 } from \"lucide-solid\";\nimport { createSignal, For, Show } from \"solid-js\";\nimport { orpc } from \"@/utils/orpc\";\nimport { useQuery, useMutation } from \"@tanstack/solid-query\";\n\nexport const Route = createFileRoute(\"/todos\")({\n  component: TodosRoute,\n});\n\nfunction TodosRoute() {\n  const [newTodoText, setNewTodoText] = createSignal(\"\");\n\n  const todos = useQuery(() => orpc.todo.getAll.queryOptions());\n\n  const createMutation = useMutation(() =>\n    orpc.todo.create.mutationOptions({\n      onSuccess: () => {\n        todos.refetch();\n        setNewTodoText(\"\");\n      },\n    }),\n  );\n\n  const toggleMutation = useMutation(() =>\n    orpc.todo.toggle.mutationOptions({\n      onSuccess: () => { todos.refetch() },\n    }),\n  );\n\n  const deleteMutation = useMutation(() =>\n    orpc.todo.delete.mutationOptions({\n      onSuccess: () => { todos.refetch() },\n    }),\n  );\n\n  const handleAddTodo = (e: Event) => {\n    e.preventDefault();\n    if (newTodoText().trim()) {\n      createMutation.mutate({ text: newTodoText() });\n    }\n  };\n\n  const handleToggleTodo = (id: number, completed: boolean) => {\n    toggleMutation.mutate({ id, completed: !completed });\n  };\n\n  const handleDeleteTodo = (id: number) => {\n    deleteMutation.mutate({ id });\n  };\n\n  return (\n    <div class=\"mx-auto w-full max-w-md py-10\">\n      <div class=\"rounded-lg border p-6 shadow-sm\">\n        <div class=\"mb-4\">\n          <h2 class=\"text-xl font-semibold\">Todo List</h2>\n          <p class=\"text-sm\">Manage your tasks efficiently</p>\n        </div>\n        <div>\n          <form\n            onSubmit={handleAddTodo}\n            class=\"mb-6 flex items-center space-x-2\"\n          >\n            <input\n              type=\"text\"\n              value={newTodoText()}\n              onInput={(e) => setNewTodoText(e.currentTarget.value)}\n              placeholder=\"Add a new task...\"\n              disabled={createMutation.isPending}\n              class=\"w-full rounded-md border p-2 text-sm\"\n            />\n            <button\n              type=\"submit\"\n              disabled={createMutation.isPending || !newTodoText().trim()}\n              class=\"rounded-md bg-blue-600 px-4 py-2 text-sm text-white disabled:opacity-50\"\n            >\n              <Show when={createMutation.isPending} fallback=\"Add\">\n                <Loader2 class=\"h-4 w-4 animate-spin\" />\n              </Show>\n            </button>\n          </form>\n\n          <Show when={todos.isLoading}>\n            <div class=\"flex justify-center py-4\">\n              <Loader2 class=\"h-6 w-6 animate-spin\" />\n            </div>\n          </Show>\n\n          <Show when={!todos.isLoading && todos.data?.length === 0}>\n            <p class=\"py-4 text-center\">No todos yet. Add one above!</p>\n          </Show>\n\n          <Show when={!todos.isLoading}>\n            <ul class=\"space-y-2\">\n              <For each={todos.data}>\n                {(todo) => (\n                  <li class=\"flex items-center justify-between rounded-md border p-2\">\n                    <div class=\"flex items-center space-x-2\">\n                      <input\n                        type=\"checkbox\"\n                        checked={todo.completed}\n                        onChange={() =>\n                          handleToggleTodo(todo.id, todo.completed)\n                        }\n                        id={`todo-${todo.id}`}\n                        class=\"h-4 w-4\"\n                      />\n                      <label\n                        for={`todo-${todo.id}`}\n                        class={todo.completed ? \"line-through\" : \"\"}\n                      >\n                        {todo.text}\n                      </label>\n                    </div>\n                    <button\n                      type=\"button\"\n                      onClick={() => handleDeleteTodo(todo.id)}\n                      aria-label=\"Delete todo\"\n                      class=\"ml-2 rounded-md p-1\"\n                    >\n                      <Trash2 class=\"h-4 w-4\" />\n                    </button>\n                  </li>\n                )}\n              </For>\n            </ul>\n          </Show>\n        </div>\n      </div>\n    </div>\n  );\n}\n"
  },
  {
    "path": "packages/template-generator/templates/examples/todo/web/svelte/src/routes/todos/+page.svelte.hbs",
    "content": "{{#if (eq backend \"convex\")}}\n<script lang=\"ts\">\n\timport { useQuery, useConvexClient } from 'convex-svelte';\n\timport { api } from '@{{projectName}}/backend/convex/_generated/api';\n\timport type { Id } from '@{{projectName}}/backend/convex/_generated/dataModel';\n\n\tlet newTodoText = $state('');\n\tlet isAdding = $state(false);\n\tlet addError = $state<Error | null>(null);\n\tlet togglingId = $state<Id<'todos'> | null>(null);\n\tlet toggleError = $state<Error | null>(null);\n\tlet deletingId = $state<Id<'todos'> | null>(null);\n\tlet deleteError = $state<Error | null>(null);\n\n\tconst client = useConvexClient();\n\n\tconst todosQuery = useQuery(api.todos.getAll, {});\n\n\tasync function handleAddTodo(event: SubmitEvent) {\n\t\tevent.preventDefault();\n\t\tconst text = newTodoText.trim();\n\t\tif (!text || isAdding) return;\n\n\t\tisAdding = true;\n\t\taddError = null;\n\t\ttry {\n\t\t\tawait client.mutation(api.todos.create, { text });\n\t\t\tnewTodoText = '';\n\t\t} catch (err) {\n\t\t\tconsole.error('Failed to add todo:', err);\n\t\t\taddError = err instanceof Error ? err : new Error(String(err));\n\t\t} finally {\n\t\t\tisAdding = false;\n\t\t}\n\t}\n\n\tasync function handleToggleTodo(id: Id<'todos'>, completed: boolean) {\n\t\tif (togglingId === id || deletingId === id) return;\n\n\t\ttogglingId = id;\n\t\ttoggleError = null;\n\t\ttry {\n\t\t\tawait client.mutation(api.todos.toggle, { id, completed: !completed });\n\t\t} catch (err) {\n\t\t\tconsole.error('Failed to toggle todo:', err);\n\t\t\ttoggleError = err instanceof Error ? err : new Error(String(err));\n\t\t} finally {\n\t\t\tif (togglingId === id) {\n\t\t\t\ttogglingId = null;\n\t\t\t}\n\t\t}\n\t}\n\n\tasync function handleDeleteTodo(id: Id<'todos'>) {\n\t\tif (togglingId === id || deletingId === id) return;\n\n\t\tdeletingId = id;\n\t\tdeleteError = null;\n\t\ttry {\n\t\t\tawait client.mutation(api.todos.deleteTodo, { id });\n\t\t} catch (err) {\n\t\t\tconsole.error('Failed to delete todo:', err);\n\t\t\tdeleteError = err instanceof Error ? err : new Error(String(err));\n\t\t} finally {\n\t\t\tif (deletingId === id) {\n\t\t\t\tdeletingId = null;\n\t\t\t}\n\t\t}\n\t}\n\n\tconst canAdd = $derived(!isAdding && newTodoText.trim().length > 0);\n\tconst isLoadingTodos = $derived(todosQuery.isLoading);\n\tconst todos = $derived(todosQuery.data ?? []);\n\tconst hasTodos = $derived(todos.length > 0);\n\n</script>\n\n<div class=\"p-4\">\n\t<h1 class=\"text-xl mb-4\">Todos (Convex)</h1>\n\n\t<form onsubmit={handleAddTodo} class=\"flex gap-2 mb-4\">\n\t\t<input\n\t\t\ttype=\"text\"\n\t\t\tbind:value={newTodoText}\n\t\t\tplaceholder=\"New task...\"\n\t\t\tdisabled={isAdding}\n\t\t\tclass=\"p-1 flex-grow\"\n\t\t/>\n\t\t<button\n\t\t\ttype=\"submit\"\n\t\t\tdisabled={!canAdd}\n\t\t\tclass=\"bg-blue-500 text-white px-3 py-1 rounded disabled:opacity-50\"\n\t\t>\n\t\t\t{#if isAdding}Adding...{:else}Add{/if}\n\t\t</button>\n\t</form>\n\n\t{#if isLoadingTodos}\n\t\t<p>Loading...</p>\n\t{:else if !hasTodos}\n\t\t<p>No todos yet.</p>\n\t{:else}\n\t\t<ul class=\"space-y-1\">\n\t\t\t{#each todos as todo (todo._id)}\n\t\t\t\t{@const isTogglingThis = togglingId === todo._id}\n\t\t\t\t{@const isDeletingThis = deletingId === todo._id}\n\t\t\t\t{@const isDisabled = isTogglingThis || isDeletingThis}\n\t\t\t\t<li\n\t\t\t\t\tclass=\"flex items-center justify-between p-2\"\n\t\t\t\t\tclass:opacity-50={isDisabled}\n\t\t\t\t>\n\t\t\t\t\t<div class=\"flex items-center gap-2\">\n\t\t\t\t\t\t<input\n\t\t\t\t\t\t\ttype=\"checkbox\"\n\t\t\t\t\t\t\tid={`todo-${todo._id}`}\n\t\t\t\t\t\t\tchecked={todo.completed}\n\t\t\t\t\t\t\tonchange={() => handleToggleTodo(todo._id, todo.completed)}\n\t\t\t\t\t\t\tdisabled={isDisabled}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<label\n\t\t\t\t\t\t\tfor={`todo-${todo._id}`}\n\t\t\t\t\t\t\tclass:line-through={todo.completed}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t{todo.text}\n\t\t\t\t\t\t</label>\n\t\t\t\t\t</div>\n\t\t\t\t\t<button\n\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\tonclick={() => handleDeleteTodo(todo._id)}\n\t\t\t\t\t\tdisabled={isDisabled}\n\t\t\t\t\t\taria-label=\"Delete todo\"\n\t\t\t\t\t\tclass=\"text-red-500 px-1 disabled:opacity-50\"\n\t\t\t\t\t>\n\t\t\t\t\t\t{#if isDeletingThis}Deleting...{:else}X{/if}\n\t\t\t\t\t</button>\n\t\t\t\t</li>\n\t\t\t{/each}\n\t\t</ul>\n\t{/if}\n\n\t{#if todosQuery.error}\n\t\t<p class=\"mt-4 text-red-500\">\n\t\t\tError loading: {todosQuery.error?.message ?? 'Unknown error'}\n\t\t</p>\n\t{/if}\n\t{#if addError}\n\t\t<p class=\"mt-4 text-red-500\">\n\t\t\tError adding: {addError.message ?? 'Unknown error'}\n\t\t</p>\n\t{/if}\n\t{#if toggleError}\n\t\t<p class=\"mt-4 text-red-500\">\n\t\t\tError updating: {toggleError.message ?? 'Unknown error'}\n\t\t</p>\n\t{/if}\n\t{#if deleteError}\n\t\t<p class=\"mt-4 text-red-500\">\n\t\t\tError deleting: {deleteError.message ?? 'Unknown error'}\n\t\t</p>\n\t{/if}\n</div>\n{{else}}\n<script lang=\"ts\">\n\t{{#if (eq api \"orpc\")}}\n\timport { orpc } from '$lib/orpc';\n\t{{/if}}\n\timport { createQuery, createMutation } from '@tanstack/svelte-query';\n\n\tlet newTodoText = $state('');\n\n\t{{#if (eq api \"orpc\")}}\n\tconst todosQuery = createQuery(orpc.todo.getAll.queryOptions());\n\n\tconst addMutation = createMutation(\n\t\torpc.todo.create.mutationOptions({\n\t\t\tonSuccess: () => {\n\t\t\t\t$todosQuery.refetch();\n\t\t\t\tnewTodoText = '';\n\t\t\t},\n\t\t\tonError: (error) => {\n\t\t\t\tconsole.error('Failed to create todo:', error?.message ?? error);\n\t\t\t},\n\t\t})\n\t);\n\n\tconst toggleMutation = createMutation(\n\t\torpc.todo.toggle.mutationOptions({\n\t\t\tonSuccess: () => {\n\t\t\t\t$todosQuery.refetch();\n\t\t\t},\n\t\t\tonError: (error) => {\n\t\t\t\tconsole.error('Failed to toggle todo:', error?.message ?? error);\n\t\t\t},\n\t\t})\n\t);\n\n\tconst deleteMutation = createMutation(\n\t\torpc.todo.delete.mutationOptions({\n\t\t\tonSuccess: () => {\n\t\t\t\t$todosQuery.refetch();\n\t\t\t},\n\t\t\tonError: (error) => {\n\t\t\t\tconsole.error('Failed to delete todo:', error?.message ?? error);\n\t\t\t},\n\t\t})\n\t);\n\t{{/if}}\n\n\tfunction handleAddTodo(event: SubmitEvent) {\n\t\tevent.preventDefault();\n\t\tconst text = newTodoText.trim();\n\t\tif (text) {\n\t\t\t$addMutation.mutate({ text });\n\t\t}\n\t}\n\n\tfunction handleToggleTodo(id: number, completed: boolean) {\n\t\t$toggleMutation.mutate({ id, completed: !completed });\n\t}\n\n\tfunction handleDeleteTodo(id: number) {\n\t\t$deleteMutation.mutate({ id });\n\t}\n\n\tconst isAdding = $derived($addMutation.isPending);\n\tconst canAdd = $derived(!isAdding && newTodoText.trim().length > 0);\n\tconst isLoadingTodos = $derived($todosQuery.isLoading);\n\tconst todos = $derived($todosQuery.data ?? []);\n\tconst hasTodos = $derived(todos.length > 0);\n\n</script>\n\n<div class=\"p-4\">\n\t<h1 class=\"text-xl mb-4\">Todos{{#if (eq api \"orpc\")}} (oRPC){{/if}}</h1>\n\n\t<form onsubmit={handleAddTodo} class=\"flex gap-2 mb-4\">\n\t\t<input\n\t\t\ttype=\"text\"\n\t\t\tbind:value={newTodoText}\n\t\t\tplaceholder=\"New task...\"\n\t\t\tdisabled={isAdding}\n\t\t\tclass=\" p-1 flex-grow\"\n\t\t/>\n\t\t<button\n\t\t\ttype=\"submit\"\n\t\t\tdisabled={!canAdd}\n\t\t\tclass=\"bg-blue-500 text-white px-3 py-1 rounded disabled:opacity-50\"\n\t\t>\n\t\t\t{#if isAdding}Adding...{:else}Add{/if}\n\t\t</button>\n\t</form>\n\n\t{#if isLoadingTodos}\n\t\t<p>Loading...</p>\n\t{:else if !hasTodos}\n\t\t<p>No todos yet.</p>\n\t{:else}\n\t\t<ul class=\"space-y-1\">\n\t\t\t{#each todos as todo (todo.id)}\n\t\t\t\t{@const isToggling = $toggleMutation.isPending && $toggleMutation.variables?.id === todo.id}\n\t\t\t\t{@const isDeleting = $deleteMutation.isPending && $deleteMutation.variables?.id === todo.id}\n\t\t\t\t{@const isDisabled = isToggling || isDeleting}\n\t\t\t\t<li\n\t\t\t\t\tclass=\"flex items-center justify-between p-2 \"\n\t\t\t\t\tclass:opacity-50={isDisabled}\n\t\t\t\t>\n\t\t\t\t\t<div class=\"flex items-center gap-2\">\n\t\t\t\t\t\t<input\n\t\t\t\t\t\t\ttype=\"checkbox\"\n\t\t\t\t\t\t\tid={`todo-${todo.id}`}\n\t\t\t\t\t\t\tchecked={todo.completed}\n\t\t\t\t\t\t\tonchange={() => handleToggleTodo(todo.id, todo.completed)}\n\t\t\t\t\t\t\tdisabled={isDisabled}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<label\n\t\t\t\t\t\t\tfor={`todo-${todo.id}`}\n\t\t\t\t\t\t\tclass:line-through={todo.completed}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t{todo.text}\n\t\t\t\t\t\t</label>\n\t\t\t\t\t</div>\n\t\t\t\t\t<button\n\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\tonclick={() => handleDeleteTodo(todo.id)}\n\t\t\t\t\t\tdisabled={isDisabled}\n\t\t\t\t\t\taria-label=\"Delete todo\"\n\t\t\t\t\t\tclass=\"text-red-500 px-1 disabled:opacity-50\"\n\t\t\t\t\t>\n\t\t\t\t\t\t{#if isDeleting}Deleting...{:else}X{/if}\n\t\t\t\t\t</button>\n\t\t\t\t</li>\n\t\t\t{/each}\n\t\t</ul>\n\t{/if}\n\n\t{#if $todosQuery.isError}\n\t\t<p class=\"mt-4 text-red-500\">\n\t\t\tError loading: {$todosQuery.error?.message ?? 'Unknown error'}\n\t\t</p>\n\t{/if}\n\t{#if $addMutation.isError}\n\t\t<p class=\"mt-4 text-red-500\">\n\t\t\tError adding: {$addMutation.error?.message ?? 'Unknown error'}\n\t\t</p>\n\t{/if}\n\t{#if $toggleMutation.isError}\n\t\t<p class=\"mt-4 text-red-500\">\n\t\t\tError updating: {$toggleMutation.error?.message ?? 'Unknown error'}\n\t\t</p>\n\t{/if}\n\t{#if $deleteMutation.isError}\n\t\t<p class=\"mt-4 text-red-500\">\n\t\t\tError deleting: {$deleteMutation.error?.message ?? 'Unknown error'}\n\t\t</p>\n\t{/if}\n</div>\n{{/if}}\n"
  },
  {
    "path": "packages/template-generator/templates/extras/_npmrc.hbs",
    "content": "node-linker=isolated\n{{#if (includes frontend \"nuxt\")}}\nshamefully-hoist=true\nstrict-peer-dependencies=false\n{{/if}}"
  },
  {
    "path": "packages/template-generator/templates/extras/bunfig.toml.hbs",
    "content": "[install]\n{{#if (or (includes frontend \"nuxt\"))}}\nlinker = \"hoisted\" # having issues with Nuxt when linker is isolated\n{{else}}\nlinker = \"isolated\"\n{{/if}}"
  },
  {
    "path": "packages/template-generator/templates/extras/env.d.ts.hbs",
    "content": "{{#if (eq serverDeploy \"cloudflare\")}}\nimport { type server } from \"@{{projectName}}/infra/alchemy.run\";\n{{else}}\nimport { type web as server } from \"@{{projectName}}/infra/alchemy.run\";\n{{/if}}\n\n// This file infers types for the cloudflare:workers environment from your Alchemy Worker.\n// @see https://alchemy.run/concepts/bindings/#type-safe-bindings\n\nexport type CloudflareEnv = typeof server.Env;\n\ndeclare global {\n  type Env = CloudflareEnv;\n}\n\ndeclare module \"cloudflare:workers\" {\n  namespace Cloudflare {\n    export interface Env extends CloudflareEnv {}\n  }\n}\n"
  },
  {
    "path": "packages/template-generator/templates/extras/pnpm-workspace.yaml",
    "content": "packages:\n  - \"apps/*\"\n  - \"packages/*\"\n"
  },
  {
    "path": "packages/template-generator/templates/frontend/astro/_gitignore",
    "content": "# build output\ndist/\n\n# generated types\n.astro/\n\n# dependencies\nnode_modules/\n\n# logs\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\npnpm-debug.log*\n\n# environment variables\n.env\n.env.production\n\n# macOS-specific files\n.DS_Store\n\n# jetbrains setting folder\n.idea/\n"
  },
  {
    "path": "packages/template-generator/templates/frontend/astro/astro.config.mjs.hbs",
    "content": "// @ts-check\nimport tailwindcss from \"@tailwindcss/vite\";\nimport { defineConfig, envField } from \"astro/config\";\nimport node from \"@astrojs/node\";\n\n// https://astro.build/config\nexport default defineConfig({\n  output: \"server\",\n  adapter: node({ mode: \"standalone\" }),\n{{#if (ne backend \"self\")}}\n  env: {\n    schema: {\n      PUBLIC_SERVER_URL: envField.string({\n        access: \"public\",\n        context: \"client\",\n        default: \"http://localhost:3000\",\n      }),\n    },\n  },\n{{/if}}\n  vite: {\n    plugins: [tailwindcss()],\n  },\n});\n"
  },
  {
    "path": "packages/template-generator/templates/frontend/astro/package.json.hbs",
    "content": "{\n  \"name\": \"web\",\n  \"type\": \"module\",\n  \"private\": true,\n  \"version\": \"0.0.1\",\n  \"scripts\": {\n    \"dev\": \"astro dev\",\n    \"build\": \"astro build\",\n    \"preview\": \"astro preview\",\n    \"astro\": \"astro\"\n  },\n  \"dependencies\": {\n    \"astro\": \"^6.0.1\"\n  },\n  \"devDependencies\": {\n    \"@tailwindcss/vite\": \"^4.1.18\",\n    \"tailwindcss\": \"^4.1.18\"\n  }\n}\n"
  },
  {
    "path": "packages/template-generator/templates/frontend/astro/src/components/Header.astro.hbs",
    "content": "---\nconst links = [\n  { to: \"/\", label: \"Home\" },\n  {{#if (eq auth \"better-auth\")}}\n  { to: \"/dashboard\", label: \"Dashboard\" },\n  {{/if}}\n  {{#if (includes examples \"todo\")}}\n  { to: \"/todos\", label: \"Todos\" },\n  {{/if}}\n  {{#if (includes examples \"ai\")}}\n  { to: \"/ai\", label: \"AI Chat\" },\n  {{/if}}\n];\n---\n\n<div>\n  <div class=\"flex flex-row items-center justify-between px-4 py-2 md:px-6\">\n    <nav class=\"flex gap-4 text-lg\">\n      {links.map((link) => (\n        <a href={link.to} class=\"text-white hover:text-neutral-400 transition-colors\">\n          {link.label}\n        </a>\n      ))}\n    </nav>\n    <div class=\"flex items-center gap-2\" id=\"user-menu-container\">\n      {{#if (eq auth \"better-auth\")}}\n      <a href=\"/login\" id=\"login-link\" class=\"rounded px-3 py-1.5 text-sm bg-indigo-600 hover:bg-indigo-700 text-white transition-colors\">\n        Sign In\n      </a>\n      <div id=\"user-menu\" class=\"hidden flex items-center gap-3\">\n        <span class=\"text-sm text-neutral-400 hidden sm:inline\" id=\"user-display\"></span>\n        <button\n          id=\"signout-button\"\n          class=\"rounded px-3 py-1.5 text-sm bg-red-600 hover:bg-red-700 text-white transition-colors\"\n        >\n          Sign Out\n        </button>\n      </div>\n      {{/if}}\n    </div>\n  </div>\n  <hr class=\"border-neutral-800\" />\n</div>\n\n{{#if (eq auth \"better-auth\")}}\n<script>\n  import { authClient } from \"../lib/auth-client\";\n\n  const loginLink = document.getElementById(\"login-link\");\n  const userMenu = document.getElementById(\"user-menu\");\n  const userDisplay = document.getElementById(\"user-display\");\n  const signOutButton = document.getElementById(\"signout-button\");\n\n  async function checkSession() {\n    try {\n      const { data: session } = await authClient.getSession();\n      if (session?.user) {\n        loginLink?.classList.add(\"hidden\");\n        userMenu?.classList.remove(\"hidden\");\n        if (userDisplay) {\n          userDisplay.textContent = session.user.name || session.user.email?.split(\"@\")[0] || \"User\";\n        }\n      }\n    } catch (e) {\n      // Not logged in\n    }\n  }\n\n  signOutButton?.addEventListener(\"click\", async () => {\n    await authClient.signOut({\n      fetchOptions: {\n        onSuccess: () => {\n          window.location.href = \"/\";\n        },\n      },\n    });\n  });\n\n  checkSession();\n</script>\n{{/if}}\n"
  },
  {
    "path": "packages/template-generator/templates/frontend/astro/src/layouts/Layout.astro.hbs",
    "content": "---\nimport \"../styles/global.css\";\nimport Header from \"../components/Header.astro\";\n\ninterface Props {\n  title?: string;\n}\n\nconst { title = \"{{projectName}}\" } = Astro.props;\n---\n\n<!doctype html>\n<html lang=\"en\" class=\"dark\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <link rel=\"icon\" type=\"image/svg+xml\" href=\"/favicon.svg\" />\n    <meta name=\"generator\" content={Astro.generator} />\n    <title>{title}</title>\n  </head>\n  <body class=\"min-h-screen bg-neutral-950 text-white\">\n    <Header />\n    <slot />\n  </body>\n</html>\n"
  },
  {
    "path": "packages/template-generator/templates/frontend/astro/src/pages/index.astro.hbs",
    "content": "---\nimport Layout from \"../layouts/Layout.astro\";\n{{#if (eq api \"orpc\")}}\nimport { orpc } from \"../lib/orpc\";\n{{/if}}\n\nconst TITLE_TEXT = `\n ██████╗ ███████╗████████╗████████╗███████╗██████╗\n ██╔══██╗██╔════╝╚══██╔══╝╚══██╔══╝██╔════╝██╔══██╗\n ██████╔╝█████╗     ██║      ██║   █████╗  ██████╔╝\n ██╔══██╗██╔══╝     ██║      ██║   ██╔══╝  ██╔══██╗\n ██████╔╝███████╗   ██║      ██║   ███████╗██║  ██║\n ╚═════╝ ╚══════╝   ╚═╝      ╚═╝   ╚══════╝╚═╝  ╚═╝\n\n ████████╗    ███████╗████████╗ █████╗  ██████╗██╗  ██╗\n ╚══██╔══╝    ██╔════╝╚══██╔══╝██╔══██╗██╔════╝██║ ██╔╝\n    ██║       ███████╗   ██║   ███████║██║     █████╔╝\n    ██║       ╚════██║   ██║   ██╔══██║██║     ██╔═██╗\n    ██║       ███████║   ██║   ██║  ██║╚██████╗██║  ██╗\n    ╚═╝       ╚══════╝   ╚═╝   ╚═╝  ╚═╝ ╚═════╝╚═╝  ╚═╝\n `;\n---\n\n<Layout title=\"{{projectName}}\">\n  <div class=\"container mx-auto max-w-3xl px-4 py-2\">\n    <pre class=\"overflow-x-auto font-mono text-sm text-white\">{TITLE_TEXT}</pre>\n    <div class=\"grid gap-6\">\n      {{#if (eq api \"orpc\")}}\n      <section class=\"rounded-lg border border-neutral-700 p-4\">\n        <h2 class=\"mb-2 font-medium text-white\">API Status</h2>\n        <div class=\"flex items-center gap-2\" id=\"api-status\">\n          <div class=\"h-2 w-2 rounded-full bg-orange-400\" id=\"status-dot\"></div>\n          <span class=\"text-sm text-neutral-400\" id=\"status-text\">Checking...</span>\n        </div>\n      </section>\n      {{/if}}\n    </div>\n  </div>\n</Layout>\n\n{{#if (eq api \"orpc\")}}\n<script>\n  import { orpc } from \"../lib/orpc\";\n\n  const statusDot = document.getElementById(\"status-dot\")!;\n  const statusText = document.getElementById(\"status-text\")!;\n\n  async function checkHealth() {\n    try {\n      const data = await orpc.healthCheck();\n      statusDot.className = \"h-2 w-2 rounded-full bg-green-500\";\n      statusText.textContent = \"Connected\";\n    } catch (error) {\n      statusDot.className = \"h-2 w-2 rounded-full bg-red-500\";\n      statusText.textContent = \"Disconnected\";\n    }\n  }\n\n  checkHealth();\n</script>\n{{/if}}\n"
  },
  {
    "path": "packages/template-generator/templates/frontend/astro/src/styles/global.css",
    "content": "@import \"tailwindcss\";\n\n/* Add your theme customizations here. */\n/* See the TailwindCSS v4 docs for more info: https://tailwindcss.com/docs/v4-beta */\n\n@theme {\n  --font-sans: \"Inter\", sans-serif;\n}\n"
  },
  {
    "path": "packages/template-generator/templates/frontend/astro/tsconfig.json.hbs",
    "content": "{\n  \"extends\": \"astro/tsconfigs/strict\",\n  \"include\": [\".astro/types.d.ts\", \"**/*\"],\n  \"exclude\": [\"dist\"]\n}\n"
  },
  {
    "path": "packages/template-generator/templates/frontend/native/bare/_gitignore",
    "content": "node_modules/\n.expo/\ndist/\nnpm-debug.*\n*.jks\n*.p8\n*.p12\n*.key\n*.mobileprovision\n*.orig.*\nweb-build/\n\n# macOS\n.DS_Store\n\n# Temporary files created by Metro to check the health of the file watcher\n.metro-health-check*\n\n"
  },
  {
    "path": "packages/template-generator/templates/frontend/native/bare/app/(drawer)/(tabs)/_layout.tsx.hbs",
    "content": "import { TabBarIcon } from \"@/components/tabbar-icon\";\nimport { useColorScheme } from \"@/lib/use-color-scheme\";\nimport { Tabs } from \"expo-router\";\nimport { NAV_THEME } from \"@/lib/constants\";\n\nexport default function TabLayout() {\n  const { isDarkColorScheme } = useColorScheme();\n  const theme = isDarkColorScheme ? NAV_THEME.dark : NAV_THEME.light;\n\n  return (\n    <Tabs\n      screenOptions=\\{{\n        headerShown: false,\n        tabBarActiveTintColor: theme.primary,\n        tabBarInactiveTintColor: theme.text,\n        tabBarStyle: {\n          backgroundColor: theme.background,\n          borderTopColor: theme.border,\n        },\n      }}\n    >\n      <Tabs.Screen\n        name=\"index\"\n        options=\\{{\n          title: \"Home\",\n          tabBarIcon: ({ color }) => <TabBarIcon name=\"home\" color={color} />,\n        }}\n      />\n      <Tabs.Screen\n        name=\"two\"\n        options=\\{{\n          title: \"Explore\",\n          tabBarIcon: ({ color }) => (\n            <TabBarIcon name=\"compass\" color={color} />\n          ),\n        }}\n      />\n    </Tabs>\n  );\n}\n\n"
  },
  {
    "path": "packages/template-generator/templates/frontend/native/bare/app/(drawer)/(tabs)/index.tsx.hbs",
    "content": "import { Container } from \"@/components/container\";\nimport { ScrollView, Text, View, StyleSheet } from \"react-native\";\nimport { useColorScheme } from \"@/lib/use-color-scheme\";\nimport { NAV_THEME } from \"@/lib/constants\";\n\nexport default function TabOne() {\n  const { colorScheme } = useColorScheme();\n  const theme = colorScheme === \"dark\" ? NAV_THEME.dark : NAV_THEME.light;\n\n  return (\n    <Container>\n      <ScrollView style={styles.scrollView}>\n        <View style={styles.content}>\n          <Text style={[styles.title, { color: theme.text }]}>\n            Tab One\n          </Text>\n          <Text style={[styles.subtitle, { color: theme.text, opacity: 0.7 }]}>\n            Explore the first section of your app\n          </Text>\n        </View>\n      </ScrollView>\n    </Container>\n  );\n}\n\nconst styles = StyleSheet.create({\n  scrollView: {\n    flex: 1,\n    padding: 16,\n  },\n  content: {\n    paddingVertical: 16,\n  },\n  title: {\n    fontSize: 24,\n    fontWeight: \"bold\",\n    marginBottom: 8,\n  },\n  subtitle: {\n    fontSize: 16,\n  },\n});\n\n"
  },
  {
    "path": "packages/template-generator/templates/frontend/native/bare/app/(drawer)/(tabs)/two.tsx.hbs",
    "content": "import { Container } from \"@/components/container\";\nimport { ScrollView, Text, View, StyleSheet } from \"react-native\";\nimport { useColorScheme } from \"@/lib/use-color-scheme\";\nimport { NAV_THEME } from \"@/lib/constants\";\n\nexport default function TabTwo() {\n  const { colorScheme } = useColorScheme();\n  const theme = colorScheme === \"dark\" ? NAV_THEME.dark : NAV_THEME.light;\n\n  return (\n    <Container>\n      <ScrollView style={styles.scrollView}>\n        <View style={styles.content}>\n          <Text style={[styles.title, { color: theme.text }]}>\n            Tab Two\n          </Text>\n          <Text style={[styles.subtitle, { color: theme.text, opacity: 0.7 }]}>\n            Discover more features and content\n          </Text>\n        </View>\n      </ScrollView>\n    </Container>\n  );\n}\n\nconst styles = StyleSheet.create({\n  scrollView: {\n    flex: 1,\n    padding: 16,\n  },\n  content: {\n    paddingVertical: 16,\n  },\n  title: {\n    fontSize: 24,\n    fontWeight: \"bold\",\n    marginBottom: 8,\n  },\n  subtitle: {\n    fontSize: 16,\n  },\n});\n\n"
  },
  {
    "path": "packages/template-generator/templates/frontend/native/bare/app/(drawer)/_layout.tsx.hbs",
    "content": "import { Ionicons, MaterialIcons } from \"@expo/vector-icons\";\nimport { Link } from \"expo-router\";\nimport { Drawer } from \"expo-router/drawer\";\nimport { useColorScheme } from \"@/lib/use-color-scheme\";\nimport { NAV_THEME } from \"@/lib/constants\";\n\nimport { HeaderButton } from \"@/components/header-button\";\n\nconst DrawerLayout = () => {\n  const { colorScheme } = useColorScheme();\n  const theme = colorScheme === \"dark\" ? NAV_THEME.dark : NAV_THEME.light;\n\n  return (\n    <Drawer\n      screenOptions=\\{{\n        headerStyle: {\n          backgroundColor: theme.background,\n        },\n        headerTitleStyle: {\n          color: theme.text,\n        },\n        headerTintColor: theme.text,\n        drawerStyle: {\n          backgroundColor: theme.background,\n        },\n        drawerLabelStyle: {\n          color: theme.text,\n        },\n        drawerInactiveTintColor: theme.text,\n      }}\n    >\n      <Drawer.Screen\n        name=\"index\"\n        options=\\{{\n          headerTitle: \"Home\",\n          drawerLabel: \"Home\",\n          drawerIcon: ({ size, color }) => (\n            <Ionicons name=\"home-outline\" size={size} color={color} />\n          ),\n        }}\n      />\n      <Drawer.Screen\n        name=\"(tabs)\"\n        options=\\{{\n          headerTitle: \"Tabs\",\n          drawerLabel: \"Tabs\",\n          drawerIcon: ({ size, color }) => (\n            <MaterialIcons name=\"border-bottom\" size={size} color={color} />\n          ),\n          headerRight: () => (\n            <Link href=\"/modal\" asChild>\n              <HeaderButton />\n            </Link>\n          ),\n        }}\n      />\n      {{#if (includes examples \"todo\")}}\n      <Drawer.Screen\n        name=\"todos\"\n        options=\\{{\n          headerTitle: \"Todos\",\n          drawerLabel: \"Todos\",\n          drawerIcon: ({ size, color }) => (\n            <Ionicons name=\"checkbox-outline\" size={size} color={color} />\n          ),\n        }}\n      />\n      {{/if}}\n      {{#if (includes examples \"ai\")}}\n      <Drawer.Screen\n        name=\"ai\"\n        options=\\{{\n          headerTitle: \"AI\",\n          drawerLabel: \"AI\",\n          drawerIcon: ({ size, color }) => (\n            <Ionicons\n              name=\"chatbubble-ellipses-outline\"\n              size={size}\n              color={color}\n            />\n          ),\n        }}\n      />\n      {{/if}}\n    </Drawer>\n  );\n};\n\nexport default DrawerLayout;\n\n"
  },
  {
    "path": "packages/template-generator/templates/frontend/native/bare/app/(drawer)/index.tsx.hbs",
    "content": "import { View, Text, ScrollView, TouchableOpacity, StyleSheet } from \"react-native\";\nimport { Container } from \"@/components/container\";\nimport { useColorScheme } from \"@/lib/use-color-scheme\";\nimport { NAV_THEME } from \"@/lib/constants\";\n{{#if (eq api \"orpc\")}}\nimport { useQuery } from \"@tanstack/react-query\";\nimport { orpc } from \"@/utils/orpc\";\n{{/if}}\n{{#if (eq api \"trpc\")}}\nimport { useQuery } from \"@tanstack/react-query\";\nimport { trpc } from \"@/utils/trpc\";\n{{/if}}\n{{#if (and (eq backend \"convex\") (eq auth \"clerk\"))}}\nimport { Link } from \"expo-router\";\nimport { Authenticated, AuthLoading, Unauthenticated, useQuery } from \"convex/react\";\nimport { api } from \"@{{ projectName }}/backend/convex/_generated/api\";\nimport { useUser } from \"@clerk/expo\";\nimport { SignOutButton } from \"@/components/sign-out-button\";\n{{else if (and (ne backend \"convex\") (eq auth \"clerk\"))}}\nimport { Link } from \"expo-router\";\nimport { useAuth, useUser } from \"@clerk/expo\";\nimport { SignOutButton } from \"@/components/sign-out-button\";\n{{else if (and (eq backend \"convex\") (eq auth \"better-auth\"))}}\nimport { useConvexAuth, useQuery } from \"convex/react\";\nimport { api } from \"@{{ projectName }}/backend/convex/_generated/api\";\nimport { authClient } from \"@/lib/auth-client\";\nimport { SignIn } from \"@/components/sign-in\";\nimport { SignUp } from \"@/components/sign-up\";\n{{else if (eq backend \"convex\")}}\nimport { useQuery } from \"convex/react\";\nimport { api } from \"@{{ projectName }}/backend/convex/_generated/api\";\n{{/if}}\n\nexport default function Home() {\nconst { colorScheme } = useColorScheme();\nconst theme = colorScheme === \"dark\" ? NAV_THEME.dark : NAV_THEME.light;\n{{#if (eq api \"orpc\")}}\nconst healthCheck = useQuery(orpc.healthCheck.queryOptions());\n{{/if}}\n{{#if (eq api \"trpc\")}}\nconst healthCheck = useQuery(trpc.healthCheck.queryOptions());\n{{/if}}\n{{#if (and (eq backend \"convex\") (eq auth \"clerk\"))}}\nconst { user } = useUser();\nconst healthCheck = useQuery(api.healthCheck.get);\nconst privateData = useQuery(api.privateData.get);\n{{else if (and (ne backend \"convex\") (eq auth \"clerk\"))}}\nconst { isLoaded, isSignedIn } = useAuth();\nconst { user } = useUser();\n{{else if (and (eq backend \"convex\") (eq auth \"better-auth\"))}}\nconst healthCheck = useQuery(api.healthCheck.get);\nconst { isAuthenticated } = useConvexAuth();\nconst user = useQuery(api.auth.getCurrentUser, isAuthenticated ? {} : \"skip\");\n{{else if (eq backend \"convex\")}}\nconst healthCheck = useQuery(api.healthCheck.get);\n{{/if}}\n\nreturn (\n<Container>\n  <ScrollView style={styles.scrollView}>\n    <View style={styles.content}>\n      <Text style={[styles.title, { color: theme.text }]}>\n        BETTER T STACK\n      </Text>\n\n      {{#unless (and (eq backend \"convex\") (eq auth \"better-auth\"))}}\n      <View style={[styles.card, { backgroundColor: theme.card, borderColor: theme.border }]}>\n        {{#if (eq backend \"convex\")}}\n        <View style={styles.statusRow}>\n          <View style={[styles.statusIndicator, { backgroundColor: healthCheck ? \"#10b981\" : \"#f59e0b\" }]} />\n          <View style={styles.statusContent}>\n            <Text style={[styles.statusTitle, { color: theme.text }]}>\n              Convex\n            </Text>\n            <Text style={[styles.statusText, { color: theme.text, opacity: 0.7 }]}>\n              {healthCheck === undefined\n              ? \"Checking...\"\n              : healthCheck === \"OK\"\n              ? \"Connected to API\"\n              : \"API Disconnected\"}\n            </Text>\n          </View>\n        </View>\n        {{else}}\n        {{#unless (eq api \"none\")}}\n        <View style={styles.statusRow}>\n          <View style={[styles.statusIndicator, { backgroundColor: healthCheck.data ? \"#10b981\" : \"#f59e0b\" }]} />\n          <View style={styles.statusContent}>\n            <Text style={[styles.statusTitle, { color: theme.text }]}>\n              {{#if (eq api \"orpc\")}}ORPC{{else}}TRPC{{/if}}\n            </Text>\n            <Text style={[styles.statusText, { color: theme.text, opacity: 0.7 }]}>\n              {healthCheck.isLoading\n              ? \"Checking connection...\"\n              : healthCheck.data\n              ? \"All systems operational\"\n              : \"Service unavailable\"}\n            </Text>\n          </View>\n        </View>\n        {{/unless}}\n        {{/if}}\n      </View>\n      {{/unless}}\n\n      {{#if (and (eq backend \"convex\") (eq auth \"clerk\"))}}\n      <Authenticated>\n        <Text style=\\{{ color: theme.text }}>Hello {user?.emailAddresses[0].emailAddress}</Text>\n        <Text style=\\{{ color: theme.text }}>Private Data: {privateData?.message}</Text>\n        <SignOutButton />\n      </Authenticated>\n      <Unauthenticated>\n        <Link href=\"/(auth)/sign-in\">\n        <Text style=\\{{ color: theme.primary }}>Sign in</Text>\n        </Link>\n        <Link href=\"/(auth)/sign-up\">\n        <Text style=\\{{ color: theme.primary }}>Sign up</Text>\n        </Link>\n      </Unauthenticated>\n      <AuthLoading>\n        <Text style=\\{{ color: theme.text }}>Loading...</Text>\n      </AuthLoading>\n      {{/if}}\n\n      {{#if (and (ne backend \"convex\") (eq auth \"clerk\"))}}\n      {!isLoaded ? (\n      <Text style=\\{{ color: theme.text }}>Loading...</Text>\n      ) : isSignedIn ? (\n      <View style={[styles.userCard, { backgroundColor: theme.card, borderColor: theme.border }]}>\n        <View style={styles.userHeader}>\n          <Text style={[styles.userText, { color: theme.text }]}>\n            Welcome, <Text style={styles.userName}>{user?.fullName ?? user?.firstName ?? \"there\"}</Text>\n          </Text>\n        </View>\n        <Text style={[styles.userEmail, { color: theme.text, opacity: 0.7 }]}>\n          {user?.emailAddresses[0]?.emailAddress}\n        </Text>\n        <SignOutButton />\n      </View>\n      ) : (\n      <View style={[styles.card, { backgroundColor: theme.card, borderColor: theme.border }]}>\n        <Link href=\"/(auth)/sign-in\">\n          <Text style=\\{{ color: theme.primary }}>Sign in</Text>\n        </Link>\n        <Link href=\"/(auth)/sign-up\">\n          <Text style=\\{{ color: theme.primary }}>Sign up</Text>\n        </Link>\n      </View>\n      )}\n      {{/if}}\n\n      {{#if (and (eq backend \"convex\") (eq auth \"better-auth\"))}}\n      {user ? (\n      <View style={[styles.userCard, { backgroundColor: theme.card, borderColor: theme.border }]}>\n        <View style={styles.userHeader}>\n          <Text style={[styles.userText, { color: theme.text }]}>\n            Welcome, <Text style={styles.userName}>{user.name}</Text>\n          </Text>\n        </View>\n        <Text style={[styles.userEmail, { color: theme.text, opacity: 0.7 }]}>\n          {user.email}\n        </Text>\n        <TouchableOpacity style={[styles.signOutButton, { backgroundColor: theme.notification }]} onPress={()=> {\n          authClient.signOut();\n          }}\n          >\n          <Text style={styles.signOutText}>Sign Out</Text>\n        </TouchableOpacity>\n      </View>\n      ) : null}\n      <View style={[styles.statusCard, { backgroundColor: theme.card, borderColor: theme.border }]}>\n        <Text style={[styles.statusCardTitle, { color: theme.text }]}>\n          API Status\n        </Text>\n        <View style={styles.statusRow}>\n          <View style={[styles.statusIndicator, { backgroundColor: healthCheck ? \"#10b981\" : \"#ef4444\" }]} />\n          <Text style={[styles.statusText, { color: theme.text, opacity: 0.7 }]}>\n            {healthCheck === undefined\n            ? \"Checking...\"\n            : healthCheck === \"OK\"\n            ? \"Connected to API\"\n            : \"API Disconnected\"}\n          </Text>\n        </View>\n      </View>\n      {!user && (\n      <>\n        <SignIn />\n        <SignUp />\n      </>\n      )}\n      {{/if}}\n    </View>\n  </ScrollView>\n</Container>\n);\n}\n\nconst styles = StyleSheet.create({\nscrollView: {\nflex: 1,\n},\ncontent: {\npadding: 16,\n},\ntitle: {\nfontSize: 24,\nfontWeight: \"bold\",\nmarginBottom: 16,\n},\ncard: {\npadding: 16,\nmarginBottom: 16,\nborderWidth: 1,\n},\nstatusRow: {\nflexDirection: \"row\",\nalignItems: \"center\",\ngap: 8,\n},\nstatusIndicator: {\nheight: 8,\nwidth: 8,\n},\nstatusContent: {\nflex: 1,\n},\nstatusTitle: {\nfontSize: 14,\nfontWeight: \"bold\",\n},\nstatusText: {\nfontSize: 12,\n},\nuserCard: {\nmarginBottom: 16,\npadding: 16,\nborderWidth: 1,\n},\nuserHeader: {\nmarginBottom: 8,\n},\nuserText: {\nfontSize: 16,\n},\nuserName: {\nfontWeight: \"bold\",\n},\nuserEmail: {\nfontSize: 14,\nmarginBottom: 12,\n},\nsignOutButton: {\npadding: 12,\n},\nsignOutText: {\ncolor: \"#ffffff\",\n},\nstatusCard: {\nmarginBottom: 16,\npadding: 16,\nborderWidth: 1,\n},\nstatusCardTitle: {\nmarginBottom: 8,\nfontWeight: \"bold\",\n},\n});\n"
  },
  {
    "path": "packages/template-generator/templates/frontend/native/bare/app/+not-found.tsx.hbs",
    "content": "import { Container } from \"@/components/container\";\nimport { Link, Stack } from \"expo-router\";\nimport { Text, View, StyleSheet } from \"react-native\";\nimport { useColorScheme } from \"@/lib/use-color-scheme\";\nimport { NAV_THEME } from \"@/lib/constants\";\n\nexport default function NotFoundScreen() {\n  const { colorScheme } = useColorScheme();\n  const theme = colorScheme === \"dark\" ? NAV_THEME.dark : NAV_THEME.light;\n\n  return (\n    <>\n      <Stack.Screen options=\\{{ title: \"Oops!\" }} />\n      <Container>\n        <View style={styles.container}>\n          <View style={styles.content}>\n            <Text style={styles.emoji}>🤔</Text>\n            <Text style={[styles.title, { color: theme.text }]}>\n              Page Not Found\n            </Text>\n            <Text style={[styles.subtitle, { color: theme.text, opacity: 0.7 }]}>\n              Sorry, the page you're looking for doesn't exist.\n            </Text>\n            <Link href=\"/\" asChild>\n              <Text style={[styles.link, { color: theme.primary, backgroundColor: `${theme.primary}1a` }]}>\n                Go to Home\n              </Text>\n            </Link>\n          </View>\n        </View>\n      </Container>\n    </>\n  );\n}\n\nconst styles = StyleSheet.create({\n  container: {\n    flex: 1,\n    justifyContent: \"center\",\n    alignItems: \"center\",\n    padding: 16,\n  },\n  content: {\n    alignItems: \"center\",\n  },\n  emoji: {\n    fontSize: 48,\n    marginBottom: 16,\n  },\n  title: {\n    fontSize: 20,\n    fontWeight: \"bold\",\n    marginBottom: 8,\n    textAlign: \"center\",\n  },\n  subtitle: {\n    fontSize: 14,\n    textAlign: \"center\",\n    marginBottom: 24,\n  },\n  link: {\n    padding: 12,\n  },\n});\n\n"
  },
  {
    "path": "packages/template-generator/templates/frontend/native/bare/app/_layout.tsx.hbs",
    "content": "{{#if (includes examples \"ai\")}}\nimport \"@/polyfills\";\n{{/if}}\n{{#if (and (eq auth \"clerk\") (ne api \"none\") (ne backend \"convex\"))}}\nimport { useEffect } from \"react\";\nimport { setClerkAuthTokenGetter } from \"@/utils/clerk-auth\";\n{{/if}}\n{{#if (and (ne backend \"convex\") (eq auth \"clerk\"))}}\nimport { ClerkProvider{{#unless (eq api \"none\")}}, useAuth{{/unless}} } from \"@clerk/expo\";\nimport { tokenCache } from \"@clerk/expo/token-cache\";\nimport { env } from \"@{{projectName}}/env/native\";\n{{/if}}\n\n{{#if (eq backend \"convex\")}}\n  {{#if (eq auth \"better-auth\")}}\n    import { ConvexReactClient } from \"convex/react\";\n    import { ConvexBetterAuthProvider } from \"@convex-dev/better-auth/react\";\n    import { authClient } from \"@/lib/auth-client\";\n    import { env } from \"@{{projectName}}/env/native\";\n  {{else}}\n    import { ConvexProvider, ConvexReactClient } from \"convex/react\";\n    import { env } from \"@{{projectName}}/env/native\";\n  {{/if}}\n  {{#if (eq auth \"clerk\")}}\n    import { ClerkProvider, useAuth } from \"@clerk/expo\";\n    import { ConvexProviderWithClerk } from \"convex/react-clerk\";\n    import { tokenCache } from \"@clerk/expo/token-cache\";\n  {{/if}}\n{{else}}\n  {{#unless (eq api \"none\")}}\n    import { QueryClientProvider } from \"@tanstack/react-query\";\n  {{/unless}}\n{{/if}}\n\nimport { Stack } from \"expo-router\";\nimport {\n  DarkTheme,\n  DefaultTheme,\n  type Theme,\n  ThemeProvider,\n} from \"@react-navigation/native\";\nimport { StatusBar } from \"expo-status-bar\";\nimport { GestureHandlerRootView } from \"react-native-gesture-handler\";\n{{#if (eq api \"trpc\")}}\nimport { queryClient } from \"@/utils/trpc\";\n{{/if}}\n{{#if (eq api \"orpc\")}}\nimport { queryClient } from \"@/utils/orpc\";\n{{/if}}\nimport { NAV_THEME } from \"@/lib/constants\";\nimport { useColorScheme } from \"@/lib/use-color-scheme\";\nimport { StyleSheet } from \"react-native\";\n\nconst LIGHT_THEME: Theme = {\n  ...DefaultTheme,\n  colors: NAV_THEME.light,\n};\nconst DARK_THEME: Theme = {\n  ...DarkTheme,\n  colors: NAV_THEME.dark,\n};\n\nexport const unstable_settings = {\n  initialRouteName: \"(drawer)\",\n};\n\n{{#if (eq backend \"convex\")}}\nconst convex = new ConvexReactClient(env.EXPO_PUBLIC_CONVEX_URL, {\n  {{#if (eq auth \"better-auth\")}}\n  expectAuth: true,\n  {{/if}}\n  unsavedChangesWarning: false,\n});\n{{/if}}\n\nconst styles = StyleSheet.create({\n  container: {\n    flex: 1,\n  },\n});\n\n{{#if (and (eq auth \"clerk\") (ne api \"none\") (ne backend \"convex\"))}}\nfunction ClerkApiAuthBridge() {\n  const { getToken } = useAuth();\n\n  useEffect(() => {\n    setClerkAuthTokenGetter(getToken);\n\n    return () => {\n      setClerkAuthTokenGetter(null);\n    };\n  }, [getToken]);\n\n  return null;\n}\n{{/if}}\n\nexport default function RootLayout() {\n  const { isDarkColorScheme } = useColorScheme();\n\n  return (\n    <>\n      {{#if (eq backend \"convex\")}}\n        {{#if (eq auth \"clerk\")}}\n          <ClerkProvider tokenCache={tokenCache} publishableKey={env.EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY}>\n            <ConvexProviderWithClerk client={convex} useAuth={useAuth}>\n              <ThemeProvider value={isDarkColorScheme ? DARK_THEME : LIGHT_THEME}>\n                <StatusBar style={isDarkColorScheme ? \"light\" : \"dark\"} />\n                <GestureHandlerRootView style={styles.container}>\n                  <Stack>\n                    <Stack.Screen name=\"(drawer)\" options=\\{{ headerShown: false }} />\n                    <Stack.Screen name=\"(auth)\" options=\\{{ headerShown: false }} />\n                    <Stack.Screen name=\"modal\" options=\\{{ title: \"Modal\", presentation: \"modal\" }} />\n                  </Stack>\n                </GestureHandlerRootView>\n              </ThemeProvider>\n            </ConvexProviderWithClerk>\n          </ClerkProvider>\n        {{else if (eq auth \"better-auth\")}}\n          <ConvexBetterAuthProvider client={convex} authClient={authClient}>\n            <ThemeProvider value={isDarkColorScheme ? DARK_THEME : LIGHT_THEME}>\n              <StatusBar style={isDarkColorScheme ? \"light\" : \"dark\"} />\n              <GestureHandlerRootView style={styles.container}>\n                <Stack>\n                  <Stack.Screen name=\"(drawer)\" options=\\{{ headerShown: false }} />\n                  <Stack.Screen name=\"modal\" options=\\{{ title: \"Modal\", presentation: \"modal\" }} />\n                </Stack>\n              </GestureHandlerRootView>\n            </ThemeProvider>\n          </ConvexBetterAuthProvider>\n        {{else}}\n          <ConvexProvider client={convex}>\n            <ThemeProvider value={isDarkColorScheme ? DARK_THEME : LIGHT_THEME}>\n              <StatusBar style={isDarkColorScheme ? \"light\" : \"dark\"} />\n              <GestureHandlerRootView style={styles.container}>\n                <Stack>\n                  <Stack.Screen name=\"(drawer)\" options=\\{{ headerShown: false }} />\n                  <Stack.Screen name=\"modal\" options=\\{{ title: \"Modal\", presentation: \"modal\" }} />\n                </Stack>\n              </GestureHandlerRootView>\n            </ThemeProvider>\n          </ConvexProvider>\n        {{/if}}\n      {{else}}\n        {{#if (eq auth \"clerk\")}}\n          <ClerkProvider tokenCache={tokenCache} publishableKey={env.EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY}>\n            {{#unless (eq api \"none\")}}\n              <ClerkApiAuthBridge />\n              <QueryClientProvider client={queryClient}>\n                <ThemeProvider value={isDarkColorScheme ? DARK_THEME : LIGHT_THEME}>\n                  <StatusBar style={isDarkColorScheme ? \"light\" : \"dark\"} />\n                  <GestureHandlerRootView style={styles.container}>\n                    <Stack>\n                      <Stack.Screen name=\"(drawer)\" options=\\{{ headerShown: false }} />\n                      <Stack.Screen name=\"(auth)\" options=\\{{ headerShown: false }} />\n                      <Stack.Screen name=\"modal\" options=\\{{ title: \"Modal\", presentation: \"modal\" }} />\n                    </Stack>\n                  </GestureHandlerRootView>\n                </ThemeProvider>\n              </QueryClientProvider>\n            {{else}}\n              <ThemeProvider value={isDarkColorScheme ? DARK_THEME : LIGHT_THEME}>\n                <StatusBar style={isDarkColorScheme ? \"light\" : \"dark\"} />\n                <GestureHandlerRootView style={styles.container}>\n                  <Stack>\n                    <Stack.Screen name=\"(drawer)\" options=\\{{ headerShown: false }} />\n                    <Stack.Screen name=\"(auth)\" options=\\{{ headerShown: false }} />\n                    <Stack.Screen name=\"modal\" options=\\{{ title: \"Modal\", presentation: \"modal\" }} />\n                  </Stack>\n                </GestureHandlerRootView>\n              </ThemeProvider>\n            {{/unless}}\n          </ClerkProvider>\n        {{else}}\n          {{#unless (eq api \"none\")}}\n            <QueryClientProvider client={queryClient}>\n              <ThemeProvider value={isDarkColorScheme ? DARK_THEME : LIGHT_THEME}>\n                <StatusBar style={isDarkColorScheme ? \"light\" : \"dark\"} />\n                <GestureHandlerRootView style={styles.container}>\n                  <Stack>\n                    <Stack.Screen name=\"(drawer)\" options=\\{{ headerShown: false }} />\n                    <Stack.Screen name=\"modal\" options=\\{{ title: \"Modal\", presentation: \"modal\" }} />\n                  </Stack>\n                </GestureHandlerRootView>\n              </ThemeProvider>\n            </QueryClientProvider>\n          {{else}}\n            <ThemeProvider value={isDarkColorScheme ? DARK_THEME : LIGHT_THEME}>\n              <StatusBar style={isDarkColorScheme ? \"light\" : \"dark\"} />\n              <GestureHandlerRootView style={styles.container}>\n                <Stack>\n                  <Stack.Screen name=\"(drawer)\" options=\\{{ headerShown: false }} />\n                  <Stack.Screen name=\"modal\" options=\\{{ title: \"Modal\", presentation: \"modal\" }} />\n                </Stack>\n              </GestureHandlerRootView>\n            </ThemeProvider>\n          {{/unless}}\n        {{/if}}\n      {{/if}}\n    </>\n  );\n}\n"
  },
  {
    "path": "packages/template-generator/templates/frontend/native/bare/app/modal.tsx.hbs",
    "content": "import { Container } from \"@/components/container\";\nimport { Text, View, StyleSheet } from \"react-native\";\nimport { useColorScheme } from \"@/lib/use-color-scheme\";\nimport { NAV_THEME } from \"@/lib/constants\";\n\nexport default function Modal() {\n  const { colorScheme } = useColorScheme();\n  const theme = colorScheme === \"dark\" ? NAV_THEME.dark : NAV_THEME.light;\n\n  return (\n    <Container>\n      <View style={styles.container}>\n        <View style={styles.header}>\n          <Text style={[styles.title, { color: theme.text }]}>Modal</Text>\n        </View>\n      </View>\n    </Container>\n  );\n}\n\nconst styles = StyleSheet.create({\n  container: {\n    flex: 1,\n    padding: 16,\n  },\n  header: {\n    marginBottom: 16,\n  },\n  title: {\n    fontSize: 20,\n    fontWeight: \"bold\",\n  },\n});\n\n"
  },
  {
    "path": "packages/template-generator/templates/frontend/native/bare/app.json.hbs",
    "content": "{\n\t\"expo\": {\n\t\t\"name\": \"{{projectName}}\",\n\t\t\"slug\": \"{{projectName}}\",\n\t\t\"version\": \"1.0.0\",\n\t\t\"orientation\": \"portrait\",\n\t\t\"icon\": \"./assets/images/icon.png\",\n\t\t\"scheme\": \"{{projectName}}\",\n\t\t\"userInterfaceStyle\": \"automatic\",\n\t\t\"ios\": {\n\t\t\t\"supportsTablet\": true\n\t\t},\n\t\t\"android\": {\n\t\t\t\"adaptiveIcon\": {\n\t\t\t\t\"backgroundColor\": \"#E6F4FE\",\n\t\t\t\t\"foregroundImage\": \"./assets/images/android-icon-foreground.png\",\n\t\t\t\t\"backgroundImage\": \"./assets/images/android-icon-background.png\",\n\t\t\t\t\"monochromeImage\": \"./assets/images/android-icon-monochrome.png\"\n\t\t\t},\n\t\t\t\"predictiveBackGestureEnabled\": false,\n\t\t\t\"package\": \"com.anonymous.mybettertapp\"\n\t\t},\n\t\t\"web\": {\n\t\t\t\"output\": \"static\",\n\t\t\t\"favicon\": \"./assets/images/favicon.png\"\n\t\t},\n\t\t\"plugins\": [\n\t\t\t\"expo-router\",\n\t\t\t[\n\t\t\t\t\"expo-splash-screen\",\n\t\t\t\t{\n\t\t\t\t\t\"image\": \"./assets/images/splash-icon.png\",\n\t\t\t\t\t\"imageWidth\": 200,\n\t\t\t\t\t\"resizeMode\": \"contain\",\n\t\t\t\t\t\"backgroundColor\": \"#ffffff\",\n\t\t\t\t\t\"dark\": {\n\t\t\t\t\t\t\"backgroundColor\": \"#000000\"\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t]\n\t\t],\n\t\t\"experiments\": {\n\t\t\t\"typedRoutes\": true,\n\t\t\t\"reactCompiler\": true\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "packages/template-generator/templates/frontend/native/bare/components/container.tsx.hbs",
    "content": "import React from \"react\";\nimport { SafeAreaView } from \"react-native-safe-area-context\";\nimport { useColorScheme } from \"@/lib/use-color-scheme\";\nimport { NAV_THEME } from \"@/lib/constants\";\nimport { StyleSheet } from \"react-native\";\n\nexport function Container({ children }: { children: React.ReactNode }) {\n  const { colorScheme } = useColorScheme();\n  const backgroundColor = colorScheme === \"dark\" \n    ? NAV_THEME.dark.background \n    : NAV_THEME.light.background;\n\n  return (\n    <SafeAreaView style={[styles.container, { backgroundColor }]}>\n      {children}\n    </SafeAreaView>\n  );\n}\n\nconst styles = StyleSheet.create({\n  container: {\n    flex: 1,\n  },\n});\n\n"
  },
  {
    "path": "packages/template-generator/templates/frontend/native/bare/components/header-button.tsx.hbs",
    "content": "import FontAwesome from \"@expo/vector-icons/FontAwesome\";\nimport { forwardRef } from \"react\";\nimport { Pressable, StyleSheet, View } from \"react-native\";\nimport { useColorScheme } from \"@/lib/use-color-scheme\";\nimport { NAV_THEME } from \"@/lib/constants\";\n\nexport const HeaderButton = forwardRef<\n  View,\n  { onPress?: () => void }\n>(({ onPress }, ref) => {\n  const { colorScheme } = useColorScheme();\n  const theme = colorScheme === \"dark\" ? NAV_THEME.dark : NAV_THEME.light;\n\n  return (\n    <Pressable\n      ref={ref}\n      onPress={onPress}\n      style={({ pressed }) => [\n        styles.button,\n        {\n          backgroundColor: pressed \n            ? theme.background \n            : theme.card,\n        },\n      ]}\n    >\n      {({ pressed }) => (\n        <FontAwesome\n          name=\"info-circle\"\n          size={20}\n          color={theme.text}\n          style=\\{{\n            opacity: pressed ? 0.7 : 1,\n          }}\n        />\n      )}\n    </Pressable>\n  );\n});\n\nconst styles = StyleSheet.create({\n  button: {\n    padding: 8,\n    marginRight: 8,\n  },\n});\n\n"
  },
  {
    "path": "packages/template-generator/templates/frontend/native/bare/components/tabbar-icon.tsx.hbs",
    "content": "import FontAwesome from \"@expo/vector-icons/FontAwesome\";\n\nexport const TabBarIcon = (props: {\n  name: React.ComponentProps<typeof FontAwesome>[\"name\"];\n  color: string;\n}) => {\n  return <FontAwesome size={24} style=\\{{ marginBottom: -3 }} {...props} />;\n};\n\n"
  },
  {
    "path": "packages/template-generator/templates/frontend/native/bare/lib/constants.ts.hbs",
    "content": "export const NAV_THEME = {\n  light: {\n    background: \"hsl(0 0% 100%)\",\n    border: \"hsl(220 13% 91%)\",\n    card: \"hsl(0 0% 100%)\",\n    notification: \"hsl(0 84.2% 60.2%)\",\n    primary: \"hsl(221.2 83.2% 53.3%)\",\n    text: \"hsl(222.2 84% 4.9%)\",\n  },\n  dark: {\n    background: \"hsl(222.2 84% 4.9%)\",\n    border: \"hsl(217.2 32.6% 17.5%)\",\n    card: \"hsl(222.2 84% 4.9%)\",\n    notification: \"hsl(0 72% 51%)\",\n    primary: \"hsl(217.2 91.2% 59.8%)\",\n    text: \"hsl(210 40% 98%)\",\n  },\n};\n\n"
  },
  {
    "path": "packages/template-generator/templates/frontend/native/bare/lib/use-color-scheme.ts.hbs",
    "content": "import { useColorScheme as useRNColorScheme } from \"react-native\";\n\nexport function useColorScheme() {\n  const systemColorScheme = useRNColorScheme();\n  const colorScheme = systemColorScheme ?? \"light\";\n  \n  return {\n    colorScheme: colorScheme as \"light\" | \"dark\",\n    isDarkColorScheme: colorScheme === \"dark\",\n    setColorScheme: () => {\n      // Color scheme is managed by the system in bare mode\n      console.warn(\"setColorScheme is not available in bare mode. Color scheme is managed by the system.\");\n    },\n    toggleColorScheme: () => {\n      // Color scheme is managed by the system in bare mode\n      console.warn(\"toggleColorScheme is not available in bare mode. Color scheme is managed by the system.\");\n    },\n  };\n}\n\n"
  },
  {
    "path": "packages/template-generator/templates/frontend/native/bare/metro.config.js.hbs",
    "content": "// Learn more https://docs.expo.io/guides/customizing-metro\nconst { getDefaultConfig } = require(\"expo/metro-config\");\n\nconst config = getDefaultConfig(__dirname);\n\nmodule.exports = config;\n"
  },
  {
    "path": "packages/template-generator/templates/frontend/native/bare/package.json.hbs",
    "content": "{\n  \"name\": \"native\",\n  \"version\": \"1.0.0\",\n  \"main\": \"expo-router/entry\",\n  \"scripts\": {\n    \"dev\": \"expo start --clear\",\n    \"android\": \"expo run:android\",\n    \"ios\": \"expo run:ios\",\n    \"prebuild\": \"expo prebuild\",\n    \"web\": \"expo start --web\"\n  },\n  \"dependencies\": {\n    \"@expo/vector-icons\": \"^15.1.1\",\n    \"@react-navigation/bottom-tabs\": \"^7.15.9\",\n    \"@react-navigation/drawer\": \"^7.9.4\",\n    \"@react-navigation/native\": \"^7.2.2\",\n    \"@tanstack/react-query\": \"^5.99.2\",\n    {{#if (includes examples \"ai\")}}\n    \"@stardazed/streams-text-encoding\": \"^1.0.2\",\n    \"@ungap/structured-clone\": \"^1.3.0\",\n    {{/if}}\n    \"expo\": \"^55.0.17\",\n    \"expo-constants\": \"~55.0.15\",\n    \"expo-crypto\": \"~55.0.14\",\n    \"expo-font\": \"~55.0.6\",\n    \"expo-linking\": \"~55.0.14\",\n    \"expo-network\": \"~55.0.13\",\n    \"expo-router\": \"~55.0.13\",\n    \"expo-secure-store\": \"~55.0.13\",\n    \"expo-splash-screen\": \"~55.0.19\",\n    \"expo-status-bar\": \"~55.0.5\",\n    \"expo-system-ui\": \"~55.0.16\",\n    \"expo-web-browser\": \"~55.0.14\",\n    \"react\": \"19.2.0\",\n    \"react-dom\": \"19.2.0\",\n    \"react-native\": \"0.83.6\",\n    \"react-native-gesture-handler\": \"~2.30.0\",\n    \"react-native-reanimated\": \"4.2.1\",\n    \"react-native-safe-area-context\": \"~5.6.2\",\n    \"react-native-screens\": \"~4.23.0\",\n    \"react-native-web\": \"~0.21.0\",\n    \"react-native-worklets\": \"0.7.4\"\n  },\n  \"devDependencies\": {\n    \"@babel/core\": \"^7.28.0\",\n    \"@types/react\": \"~19.2.10\",\n    \"typescript\": \"~5.9.2\"\n  },\n  \"private\": true\n}\n"
  },
  {
    "path": "packages/template-generator/templates/frontend/native/bare/tsconfig.json.hbs",
    "content": "{\n\t\"extends\": \"expo/tsconfig.base\",\n\t\"compilerOptions\": {\n\t\t\"strict\": true,\n\t\t\"paths\": {\n\t\t\t\"@/*\": [\"./*\"]\n\t\t}\n\t},\n\t\"include\": [\"**/*.ts\", \"**/*.tsx\", \".expo/types/**/*.ts\", \"expo-env.d.ts\"]\n}\n\n"
  },
  {
    "path": "packages/template-generator/templates/frontend/native/unistyles/_gitignore",
    "content": "node_modules/\n.expo/\ndist/\nnpm-debug.*\n*.jks\n*.p8\n*.p12\n*.key\n*.mobileprovision\n*.orig.*\nweb-build/\n# expo router\nexpo-env.d.ts\n\n.env\n\nios\nandroid\n\n# macOS\n.DS_Store\n\n# Temporary files created by Metro to check the health of the file watcher\n.metro-health-check*"
  },
  {
    "path": "packages/template-generator/templates/frontend/native/unistyles/app/(drawer)/(tabs)/_layout.tsx.hbs",
    "content": "import { Tabs } from \"expo-router\";\nimport { useUnistyles } from \"react-native-unistyles\";\n\nimport { TabBarIcon } from \"@/components/tabbar-icon\";\n\nexport default function TabLayout() {\n  const { theme } = useUnistyles();\n\n  return (\n    <Tabs\n      screenOptions=\\{{\n        headerShown: false,\n        tabBarActiveTintColor: theme.colors.primary,\n        tabBarInactiveTintColor: theme.colors.mutedForeground,\n        tabBarStyle: {\n          backgroundColor: theme.colors.background,\n          borderTopColor: theme.colors.border,\n        },\n      }}\n    >\n      <Tabs.Screen\n        name=\"index\"\n        options=\\{{\n          title: \"Home\",\n          tabBarIcon: ({ color }) => <TabBarIcon name=\"home\" color={color} />,\n        }}\n      />\n      <Tabs.Screen\n        name=\"two\"\n        options=\\{{\n          title: \"Explore\",\n          tabBarIcon: ({ color }) => (\n            <TabBarIcon name=\"compass\" color={color} />\n          ),\n        }}\n      />\n    </Tabs>\n  );\n}\n"
  },
  {
    "path": "packages/template-generator/templates/frontend/native/unistyles/app/(drawer)/(tabs)/index.tsx.hbs",
    "content": "import { Container } from \"@/components/container\";\nimport { ScrollView, Text, View } from \"react-native\";\nimport { StyleSheet } from \"react-native-unistyles\";\n\nexport default function Home() {\n  return (\n    <Container>\n      <ScrollView contentContainerStyle={styles.container}>\n        <View style={styles.headerSection}>\n          <Text style={styles.title}>Tab One</Text>\n          <Text style={styles.subtitle}>\n            Explore the first section of your app\n          </Text>\n        </View>\n      </ScrollView>\n    </Container>\n  );\n}\n\nconst styles = StyleSheet.create((theme) => ({\n  container: {\n    padding: theme.spacing.lg,\n  },\n  headerSection: {\n    paddingVertical: theme.spacing.xl,\n  },\n  title: {\n    fontSize: theme.fontSize[\"3xl\"],\n    fontWeight: \"bold\",\n    color: theme.colors.foreground,\n    marginBottom: theme.spacing.sm,\n  },\n  subtitle: {\n    fontSize: theme.fontSize.lg,\n    color: theme.colors.mutedForeground,\n  },\n}));\n"
  },
  {
    "path": "packages/template-generator/templates/frontend/native/unistyles/app/(drawer)/(tabs)/two.tsx.hbs",
    "content": "import { Container } from \"@/components/container\";\nimport { ScrollView, Text, View } from \"react-native\";\nimport { StyleSheet } from \"react-native-unistyles\";\n\nexport default function TabTwo() {\n  return (\n    <Container>\n      <ScrollView contentContainerStyle={styles.container}>\n        <View style={styles.headerSection}>\n          <Text style={styles.title}>Tab Two</Text>\n          <Text style={styles.subtitle}>\n            Discover more features and content\n          </Text>\n        </View>\n      </ScrollView>\n    </Container>\n  );\n}\n\nconst styles = StyleSheet.create((theme) => ({\n  container: {\n    padding: theme.spacing.lg,\n  },\n  headerSection: {\n    paddingVertical: theme.spacing.xl,\n  },\n  title: {\n    fontSize: theme.fontSize[\"3xl\"],\n    fontWeight: \"bold\",\n    color: theme.colors.foreground,\n    marginBottom: theme.spacing.sm,\n  },\n  subtitle: {\n    fontSize: theme.fontSize.lg,\n    color: theme.colors.mutedForeground,\n  },\n}));\n"
  },
  {
    "path": "packages/template-generator/templates/frontend/native/unistyles/app/(drawer)/_layout.tsx.hbs",
    "content": "import { Ionicons, MaterialIcons } from \"@expo/vector-icons\";\nimport { Link } from \"expo-router\";\nimport { Drawer } from \"expo-router/drawer\";\nimport { useUnistyles } from \"react-native-unistyles\";\n\nimport { HeaderButton } from \"../../components/header-button\";\n\nconst DrawerLayout = () => {\n  const { theme } = useUnistyles();\n\n  return (\n    <Drawer\n      screenOptions=\\{{\n        headerStyle: {\n          backgroundColor: theme.colors.background,\n        },\n        headerTitleStyle: {\n          color: theme.colors.foreground,\n        },\n        headerTintColor: theme.colors.foreground,\n        drawerStyle: {\n          backgroundColor: theme.colors.background,\n        },\n        drawerLabelStyle: {\n          color: theme.colors.foreground,\n        },\n        drawerInactiveTintColor: theme.colors.mutedForeground,\n      }}\n    >\n      <Drawer.Screen\n        name=\"index\"\n        options=\\{{\n          headerTitle: \"Home\",\n          drawerLabel: \"Home\",\n          drawerIcon: ({ size, color }) => (\n            <Ionicons name=\"home-outline\" size={size} color={color} />\n          ),\n        }}\n      />\n      <Drawer.Screen\n        name=\"(tabs)\"\n        options=\\{{\n          headerTitle: \"Tabs\",\n          drawerLabel: \"Tabs\",\n          drawerIcon: ({ size, color }) => (\n            <MaterialIcons name=\"border-bottom\" size={size} color={color} />\n          ),\n          headerRight: () => (\n            <Link href=\"/modal\" asChild>\n              <HeaderButton />\n            </Link>\n          ),\n        }}\n      />\n      {{#if (includes examples \"todo\")}}\n      <Drawer.Screen\n        name=\"todos\"\n        options=\\{{\n          headerTitle: \"Todos\",\n          drawerLabel: \"Todos\",\n          drawerIcon: ({ size, color }) => (\n            <Ionicons name=\"checkbox-outline\" size={size} color={color} />\n          ),\n        }}\n      />\n      {{/if}}\n      {{#if (includes examples \"ai\")}}\n      <Drawer.Screen\n        name=\"ai\"\n        options=\\{{\n          headerTitle: \"AI\",\n          drawerLabel: \"AI\",\n          drawerIcon: ({ size, color }) => (\n            <Ionicons\n              name=\"chatbubble-ellipses-outline\"\n              size={size}\n              color={color}\n            />\n          ),\n        }}\n      />\n      {{/if}}\n    </Drawer>\n  );\n};\n\nexport default DrawerLayout;\n"
  },
  {
    "path": "packages/template-generator/templates/frontend/native/unistyles/app/(drawer)/index.tsx.hbs",
    "content": "import { ScrollView, Text, View, TouchableOpacity } from \"react-native\";\nimport { StyleSheet } from \"react-native-unistyles\";\nimport { Container } from \"@/components/container\";\n\n{{#if (eq api \"orpc\")}}\nimport { useQuery } from \"@tanstack/react-query\";\nimport { orpc } from \"@/utils/orpc\";\n{{/if}}\n{{#if (eq api \"trpc\")}}\nimport { useQuery } from \"@tanstack/react-query\";\nimport { trpc } from \"@/utils/trpc\";\n{{/if}}\n{{#if (and (eq backend \"convex\") (eq auth \"clerk\"))}}\nimport { Link } from \"expo-router\";\nimport { Authenticated, AuthLoading, Unauthenticated, useQuery } from \"convex/react\";\nimport { api } from \"@{{ projectName }}/backend/convex/_generated/api\";\nimport { useUser } from \"@clerk/expo\";\nimport { SignOutButton } from \"@/components/sign-out-button\";\n{{else if (and (ne backend \"convex\") (eq auth \"clerk\"))}}\nimport { Link } from \"expo-router\";\nimport { useAuth, useUser } from \"@clerk/expo\";\nimport { SignOutButton } from \"@/components/sign-out-button\";\n{{else if (and (eq backend \"convex\") (eq auth \"better-auth\"))}}\nimport { useConvexAuth, useQuery } from \"convex/react\";\nimport { api } from \"@{{ projectName }}/backend/convex/_generated/api\";\nimport { authClient } from \"@/lib/auth-client\";\nimport { SignIn } from \"@/components/sign-in\";\nimport { SignUp } from \"@/components/sign-up\";\n{{else if (eq backend \"convex\")}}\nimport { useQuery } from \"convex/react\";\nimport { api } from \"@{{ projectName }}/backend/convex/_generated/api\";\n{{/if}}\n\nexport default function Home() {\n  {{#if (eq api \"orpc\")}}\n  const healthCheck = useQuery(orpc.healthCheck.queryOptions());\n  {{/if}}\n  {{#if (eq api \"trpc\")}}\n  const healthCheck = useQuery(trpc.healthCheck.queryOptions());\n  {{/if}}\n  {{#if (and (eq backend \"convex\") (eq auth \"clerk\"))}}\n  const { user } = useUser();\n  const healthCheck = useQuery(api.healthCheck.get);\n  const privateData = useQuery(api.privateData.get);\n  {{else if (and (ne backend \"convex\") (eq auth \"clerk\"))}}\n  const { isLoaded, isSignedIn } = useAuth();\n  const { user } = useUser();\n  {{else if (and (eq backend \"convex\") (eq auth \"better-auth\"))}}\n  const healthCheck = useQuery(api.healthCheck.get);\n  const { isAuthenticated } = useConvexAuth();\n  const user = useQuery(api.auth.getCurrentUser, isAuthenticated ? {} : \"skip\");\n  {{else if (eq backend \"convex\")}}\n  const healthCheck = useQuery(api.healthCheck.get);\n  {{/if}}\n\n  return (\n    <Container>\n      <ScrollView\n        contentContainerStyle={styles.container}\n        showsVerticalScrollIndicator={false}\n      >\n        <Text style={styles.heroTitle}>\n          BETTER T STACK\n        </Text>\n\n        {{#unless (and (eq backend \"convex\") (eq auth \"better-auth\"))}}\n        <View style={styles.statusCard}>\n          <View style={styles.statusHeader}>\n            <Text style={styles.statusTitle}>System Status</Text>\n            <View style={styles.statusBadge}>\n              <Text style={styles.statusBadgeText}>LIVE</Text>\n            </View>\n          </View>\n          {{#if (eq backend \"convex\")}}\n            {{#unless (eq auth \"better-auth\")}}\n            <View style={styles.statusRow}>\n              <View\n                style={[\n                  styles.statusDot,\n                  healthCheck === \"OK\"\n                    ? styles.statusDotSuccess\n                    : styles.statusDotWarning,\n                ]}\n              />\n              <View style={styles.statusContent}>\n                <Text style={styles.statusLabel}>Convex</Text>\n                <Text style={styles.statusDescription}>\n                  {healthCheck === undefined\n                    ? \"Checking connection...\"\n                    : healthCheck === \"OK\"\n                    ? \"Connected to API\"\n                    : \"API Disconnected\"}\n                </Text>\n              </View>\n            </View>\n            {{/unless}}\n          {{else}}\n            {{#unless (eq api \"none\")}}\n            <View style={styles.statusRow}>\n              <View\n                style={[\n                  styles.statusDot,\n                  healthCheck.data\n                    ? styles.statusDotSuccess\n                    : styles.statusDotWarning,\n                ]}\n              />\n              <View style={styles.statusContent}>\n                <Text style={styles.statusLabel}>\n                  {{#if (eq api \"orpc\")}}ORPC{{/if}}\n                  {{#if (eq api \"trpc\")}}TRPC{{/if}}\n                </Text>\n                <Text style={styles.statusDescription}>\n                  {healthCheck.isLoading\n                    ? \"Checking connection...\"\n                    : healthCheck.data\n                    ? \"Connected to API\"\n                    : \"API Disconnected\"}\n                </Text>\n              </View>\n            </View>\n            {{/unless}}\n          {{/if}}\n        </View>\n        {{/unless}}\n\n        {{#if (and (eq backend \"convex\") (eq auth \"clerk\"))}}\n        <Authenticated>\n          <Text>\n            Hello {user?.emailAddresses[0].emailAddress}\n          </Text>\n          <Text>\n            Private Data: {privateData?.message}\n          </Text>\n          <SignOutButton />\n        </Authenticated>\n        <Unauthenticated>\n          <Link href=\"/(auth)/sign-in\">\n            <Text>Sign in</Text>\n          </Link>\n          <Link href=\"/(auth)/sign-up\">\n            <Text>Sign up</Text>\n          </Link>\n        </Unauthenticated>\n        <AuthLoading>\n          <Text>Loading...</Text>\n        </AuthLoading>\n        {{/if}}\n\n        {{#if (and (ne backend \"convex\") (eq auth \"clerk\"))}}\n        {!isLoaded ? (\n          <Text style={styles.apiStatusText}>Loading...</Text>\n        ) : isSignedIn ? (\n          <View style={styles.userCard}>\n            <View style={styles.userHeader}>\n              <Text style={styles.userWelcome}>\n                Welcome,{\" \"}\n                <Text style={styles.userName}>{user?.fullName ?? user?.firstName ?? \"there\"}</Text>\n              </Text>\n            </View>\n            <Text style={styles.userEmail}>{user?.emailAddresses[0]?.emailAddress}</Text>\n            <SignOutButton />\n          </View>\n        ) : (\n          <>\n            <Link href=\"/(auth)/sign-in\">\n              <Text style={styles.apiStatusText}>Sign in</Text>\n            </Link>\n            <Link href=\"/(auth)/sign-up\">\n              <Text style={styles.apiStatusText}>Sign up</Text>\n            </Link>\n          </>\n        )}\n        {{/if}}\n\n        {{#if (and (eq backend \"convex\") (eq auth \"better-auth\"))}}\n        {user ? (\n          <View style={styles.userCard}>\n            <View style={styles.userHeader}>\n              <Text style={styles.userWelcome}>\n                Welcome,{\" \"}\n                <Text style={styles.userName}>{user.name}</Text>\n              </Text>\n            </View>\n            <Text style={styles.userEmail}>{user.email}</Text>\n            <TouchableOpacity\n              style={styles.signOutButton}\n              onPress={() => {\n                authClient.signOut();\n              }}\n            >\n              <Text style={styles.signOutText}>Sign Out</Text>\n            </TouchableOpacity>\n          </View>\n        ) : null}\n        <View style={styles.apiStatusCard}>\n          <Text style={styles.apiStatusTitle}>API Status</Text>\n          <View style={styles.apiStatusRow}>\n            <View\n              style={[\n                styles.statusDot,\n                healthCheck === \"OK\"\n                  ? styles.statusDotSuccess\n                  : styles.statusDotWarning,\n              ]}\n            />\n            <Text style={styles.apiStatusText}>\n              {healthCheck === undefined\n                ? \"Checking...\"\n                : healthCheck === \"OK\"\n                ? \"Connected to API\"\n                : \"API Disconnected\"}\n            </Text>\n          </View>\n        </View>\n        {!user && (\n          <>\n            <SignIn />\n            <SignUp />\n          </>\n        )}\n        {{/if}}\n      </ScrollView>\n    </Container>\n  );\n}\n\nconst styles = StyleSheet.create((theme) => ({\n  container: {\n    paddingHorizontal: theme.spacing.md,\n  },\n  heroSection: {\n    paddingVertical: theme.spacing.xl,\n  },\n  heroTitle: {\n    fontSize: theme.fontSize[\"4xl\"],\n    fontWeight: \"bold\",\n    color: theme.colors.foreground,\n    marginBottom: theme.spacing.sm,\n  },\n  heroSubtitle: {\n    fontSize: theme.fontSize.lg,\n    color: theme.colors.mutedForeground,\n    lineHeight: 28,\n  },\n  statusCard: {\n    backgroundColor: theme.colors.card,\n    borderWidth: 1,\n    borderColor: theme.colors.border,\n    borderRadius: theme.borderRadius.xl,\n    padding: theme.spacing.lg,\n    marginBottom: theme.spacing.lg,\n    shadowColor: \"#000\",\n    shadowOffset: { width: 0, height: 1 },\n    shadowOpacity: 0.05,\n    shadowRadius: 3,\n    elevation: 2,\n  },\n  statusHeader: {\n    flexDirection: \"row\",\n    alignItems: \"center\",\n    justifyContent: \"space-between\",\n    marginBottom: theme.spacing.md,\n  },\n  statusTitle: {\n    fontSize: theme.fontSize.lg,\n    fontWeight: \"600\",\n    color: theme.colors.cardForeground,\n  },\n  statusBadge: {\n    backgroundColor: theme.colors.secondary,\n    paddingHorizontal: theme.spacing.sm + 4,\n    paddingVertical: theme.spacing.xs,\n    borderRadius: 9999,\n  },\n  statusBadgeText: {\n    fontSize: theme.fontSize.xs,\n    fontWeight: \"500\",\n    color: theme.colors.secondaryForeground,\n  },\n  statusRow: {\n    flexDirection: \"row\",\n    alignItems: \"center\",\n    gap: theme.spacing.sm + 4,\n  },\n  statusDot: {\n    height: 12,\n    width: 12,\n    borderRadius: 6,\n  },\n  statusDotSuccess: {\n    backgroundColor: theme.colors.success,\n  },\n  statusDotWarning: {\n    backgroundColor: \"#F59E0B\",\n  },\n  statusContent: {\n    flex: 1,\n  },\n  statusLabel: {\n    fontSize: theme.fontSize.sm,\n    fontWeight: \"500\",\n    color: theme.colors.cardForeground,\n  },\n  statusDescription: {\n    fontSize: theme.fontSize.xs,\n    color: theme.colors.mutedForeground,\n  },\n  userCard: {\n    backgroundColor: theme.colors.card,\n    borderWidth: 1,\n    borderColor: theme.colors.border,\n    borderRadius: theme.borderRadius.lg,\n    padding: theme.spacing.md,\n    marginBottom: theme.spacing.md,\n  },\n  userHeader: {\n    flexDirection: \"row\",\n    justifyContent: \"space-between\",\n    alignItems: \"center\",\n    marginBottom: theme.spacing.xs,\n  },\n  userWelcome: {\n    fontSize: theme.fontSize.base,\n    color: theme.colors.foreground,\n  },\n  userName: {\n    fontWeight: \"500\",\n  },\n  userEmail: {\n    fontSize: theme.fontSize.sm,\n    color: theme.colors.mutedForeground,\n    marginBottom: theme.spacing.md,\n  },\n  signOutButton: {\n    backgroundColor: theme.colors.destructive,\n    paddingVertical: theme.spacing.sm,\n    paddingHorizontal: theme.spacing.md,\n    borderRadius: theme.borderRadius.md,\n    alignSelf: \"flex-start\",\n  },\n  signOutText: {\n    color: theme.colors.destructiveForeground,\n    fontWeight: \"500\",\n  },\n  apiStatusCard: {\n    marginBottom: theme.spacing.md,\n    borderRadius: theme.borderRadius.lg,\n    borderWidth: 1,\n    borderColor: theme.colors.border,\n    padding: theme.spacing.md,\n  },\n  apiStatusTitle: {\n    marginBottom: theme.spacing.sm,\n    fontWeight: \"500\",\n    color: theme.colors.foreground,\n  },\n  apiStatusRow: {\n    flexDirection: \"row\",\n    alignItems: \"center\",\n    gap: theme.spacing.xs,\n  },\n  apiStatusText: {\n    color: theme.colors.mutedForeground,\n  },\n}));\n"
  },
  {
    "path": "packages/template-generator/templates/frontend/native/unistyles/app/+html.tsx.hbs",
    "content": "import { ScrollViewStyleReset } from \"expo-router/html\";\nimport { type PropsWithChildren } from \"react\";\n\nimport \"../unistyles\";\n\n// This file is web-only and used to configure the root HTML for every\n// web page during static rendering.\n// The contents of this function only run in Node.js environments and\n// do not have access to the DOM or browser APIs.\nexport default function Root({ children }: PropsWithChildren) {\n  return (\n    <html lang=\"en\">\n      <head>\n        <meta charSet=\"utf-8\" />\n        <meta httpEquiv=\"X-UA-Compatible\" content=\"IE=edge\" />\n        <meta\n          name=\"viewport\"\n          content=\"width=device-width, initial-scale=1, shrink-to-fit=no\"\n        />\n\n        {/*\n          Disable body scrolling on web. This makes ScrollView components work closer to how they do on native.\n          However, body scrolling is often nice to have for mobile web. If you want to enable it, remove this line.\n        */}\n        <ScrollViewStyleReset />\n\n        {/* Add any additional <head> elements that you want globally available on web... */}\n      </head>\n      <body>{children}</body>\n    </html>\n  );\n}\n"
  },
  {
    "path": "packages/template-generator/templates/frontend/native/unistyles/app/+not-found.tsx.hbs",
    "content": "import { Link, Stack } from \"expo-router\";\nimport { Text, View } from \"react-native\";\nimport { StyleSheet } from \"react-native-unistyles\";\nimport { Container } from \"@/components/container\";\n\nexport default function NotFoundScreen() {\n  return (\n    <>\n      <Stack.Screen options=\\{{ title: \"Oops!\" }} />\n      <Container>\n        <View style={styles.container}>\n          <View style={styles.content}>\n            <Text style={styles.emoji}>🤔</Text>\n            <Text style={styles.title}>Page Not Found</Text>\n            <Text style={styles.description}>\n              Sorry, the page you're looking for doesn't exist.\n            </Text>\n            <Link href=\"/\" style={styles.button}>\n              <Text style={styles.buttonText}>Go to Home</Text>\n            </Link>\n          </View>\n        </View>\n      </Container>\n    </>\n  );\n}\n\nconst styles = StyleSheet.create((theme) => ({\n  container: {\n    flex: 1,\n    justifyContent: \"center\",\n    alignItems: \"center\",\n    padding: theme.spacing.lg,\n  },\n  content: {\n    alignItems: \"center\",\n  },\n  emoji: {\n    fontSize: 64,\n    marginBottom: theme.spacing.md,\n  },\n  title: {\n    fontSize: theme.fontSize[\"2xl\"],\n    fontWeight: \"bold\",\n    color: theme.colors.foreground,\n    marginBottom: theme.spacing.sm,\n    textAlign: \"center\",\n  },\n  description: {\n    color: theme.colors.mutedForeground,\n    textAlign: \"center\",\n    marginBottom: theme.spacing.xl,\n    maxWidth: 280,\n  },\n  button: {\n    backgroundColor: `${theme.colors.primary}1A`, // 10% opacity\n    paddingHorizontal: theme.spacing.lg,\n    paddingVertical: theme.spacing.sm + 4,\n    borderRadius: theme.borderRadius.lg,\n  },\n  buttonText: {\n    color: theme.colors.primary,\n    fontWeight: \"500\",\n  },\n}));\n"
  },
  {
    "path": "packages/template-generator/templates/frontend/native/unistyles/app/_layout.tsx.hbs",
    "content": "{{#if (includes examples \"ai\")}}\nimport \"@/polyfills\";\n{{/if}}\n{{#if (and (eq auth \"clerk\") (ne api \"none\") (ne backend \"convex\"))}}\nimport { useEffect } from \"react\";\nimport { setClerkAuthTokenGetter } from \"@/utils/clerk-auth\";\n{{/if}}\n{{#if (and (ne backend \"convex\") (eq auth \"clerk\"))}}\nimport { ClerkProvider{{#unless (eq api \"none\")}}, useAuth{{/unless}} } from \"@clerk/expo\";\nimport { tokenCache } from \"@clerk/expo/token-cache\";\nimport { env } from \"@{{projectName}}/env/native\";\n{{/if}}\n{{#if (eq api \"trpc\")}}\nimport { queryClient } from \"@/utils/trpc\";\n{{/if}}\n{{#if (eq api \"orpc\")}}\nimport { queryClient } from \"@/utils/orpc\";\n{{/if}}\n{{#if (eq backend \"convex\")}}\n{{#if (eq auth \"better-auth\")}}\nimport { ConvexReactClient } from \"convex/react\";\nimport { ConvexBetterAuthProvider } from \"@convex-dev/better-auth/react\";\nimport { authClient } from \"@/lib/auth-client\";\nimport { env } from \"@{{projectName}}/env/native\";\n{{else}}\nimport { ConvexProvider, ConvexReactClient } from \"convex/react\";\nimport { env } from \"@{{projectName}}/env/native\";\n{{/if}}\n{{#if (eq auth \"clerk\")}}\nimport { ClerkProvider, useAuth } from \"@clerk/expo\";\nimport { ConvexProviderWithClerk } from \"convex/react-clerk\";\nimport { tokenCache } from \"@clerk/expo/token-cache\";\n{{/if}}\n{{else}}\n  {{#unless (eq api \"none\")}}\nimport { QueryClientProvider } from \"@tanstack/react-query\";\n  {{/unless}}\n{{/if}}\nimport { Stack } from \"expo-router\";\nimport { GestureHandlerRootView } from \"react-native-gesture-handler\";\nimport { useUnistyles } from \"react-native-unistyles\";\nimport { StatusBar } from \"expo-status-bar\";\n\nexport const unstable_settings = {\n  initialRouteName: \"(drawer)\",\n};\n\n{{#if (eq backend \"convex\")}}\nconst convex = new ConvexReactClient(env.EXPO_PUBLIC_CONVEX_URL, {\n  {{#if (eq auth \"better-auth\")}}\n  expectAuth: true,\n  {{/if}}\n  unsavedChangesWarning: false,\n});\n{{/if}}\n\n{{#if (and (eq auth \"clerk\") (ne api \"none\") (ne backend \"convex\"))}}\nfunction ClerkApiAuthBridge() {\n  const { getToken } = useAuth();\n\n  useEffect(() => {\n    setClerkAuthTokenGetter(getToken);\n\n    return () => {\n      setClerkAuthTokenGetter(null);\n    };\n  }, [getToken]);\n\n  return null;\n}\n{{/if}}\n\nexport default function RootLayout() {\n  const { theme } = useUnistyles();\n\n  return (\n    {{#if (eq backend \"convex\")}}\n    {{#if (eq auth \"clerk\")}}\n    <ClerkProvider\n      tokenCache={tokenCache}\n      publishableKey={env.EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY}\n    >\n      <ConvexProviderWithClerk client={convex} useAuth={useAuth}>\n        <GestureHandlerRootView style=\\{{ flex: 1 }}>\n          <Stack\n            screenOptions=\\{{\n              headerStyle: {\n                backgroundColor: theme.colors.background,\n              },\n              headerTitleStyle: {\n                color: theme.colors.foreground,\n              },\n              headerTintColor: theme.colors.foreground,\n            }}\n          >\n            <Stack.Screen name=\"(drawer)\" options=\\{{ headerShown: false }} />\n            <Stack.Screen name=\"(auth)\" options=\\{{ headerShown: false }} />\n            <Stack.Screen\n              name=\"modal\"\n              options=\\{{ title: \"Modal\", presentation: \"modal\" }}\n            />\n          </Stack>\n        </GestureHandlerRootView>\n      </ConvexProviderWithClerk>\n    </ClerkProvider>\n    {{else if (eq auth \"better-auth\")}}\n    <ConvexBetterAuthProvider client={convex} authClient={authClient}>\n      <GestureHandlerRootView style=\\{{ flex: 1 }}>\n        <Stack\n          screenOptions=\\{{\n            headerStyle: {\n              backgroundColor: theme.colors.background,\n            },\n            headerTitleStyle: {\n              color: theme.colors.foreground,\n            },\n            headerTintColor: theme.colors.foreground,\n          }}\n        >\n          <Stack.Screen name=\"(drawer)\" options=\\{{ headerShown: false }} />\n          <Stack.Screen\n            name=\"modal\"\n            options=\\{{ title: \"Modal\", presentation: \"modal\" }}\n          />\n        </Stack>\n      </GestureHandlerRootView>\n    </ConvexBetterAuthProvider>\n    {{else}}\n    <ConvexProvider client={convex}>\n      <GestureHandlerRootView style=\\{{ flex: 1 }}>\n        <Stack\n          screenOptions=\\{{\n            headerStyle: {\n              backgroundColor: theme.colors.background,\n            },\n            headerTitleStyle: {\n              color: theme.colors.foreground,\n            },\n            headerTintColor: theme.colors.foreground,\n          }}\n        >\n          <Stack.Screen name=\"(drawer)\" options=\\{{ headerShown: false }} />\n          <Stack.Screen\n            name=\"modal\"\n            options=\\{{ title: \"Modal\", presentation: \"modal\" }}\n          />\n        </Stack>\n      </GestureHandlerRootView>\n    </ConvexProvider>\n    {{/if}}\n    {{else}}\n      {{#if (eq auth \"clerk\")}}\n      <ClerkProvider\n        tokenCache={tokenCache}\n        publishableKey={env.EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY}\n      >\n        {{#unless (eq api \"none\")}}\n        <ClerkApiAuthBridge />\n        <QueryClientProvider client={queryClient}>\n          <GestureHandlerRootView style=\\{{ flex: 1 }}>\n            <Stack\n              screenOptions=\\{{\n                headerStyle: {\n                  backgroundColor: theme.colors.background,\n                },\n                headerTitleStyle: {\n                  color: theme.colors.foreground,\n                },\n                headerTintColor: theme.colors.foreground,\n              }}\n            >\n              <Stack.Screen name=\"(drawer)\" options=\\{{ headerShown: false }} />\n              <Stack.Screen name=\"(auth)\" options=\\{{ headerShown: false }} />\n              <Stack.Screen\n                name=\"modal\"\n                options=\\{{ title: \"Modal\", presentation: \"modal\" }}\n              />\n            </Stack>\n          </GestureHandlerRootView>\n        </QueryClientProvider>\n        {{else}}\n        <GestureHandlerRootView style=\\{{ flex: 1 }}>\n          <Stack\n            screenOptions=\\{{\n              headerStyle: {\n                backgroundColor: theme.colors.background,\n              },\n              headerTitleStyle: {\n                color: theme.colors.foreground,\n              },\n              headerTintColor: theme.colors.foreground,\n            }}\n          >\n            <Stack.Screen name=\"(drawer)\" options=\\{{ headerShown: false }} />\n            <Stack.Screen name=\"(auth)\" options=\\{{ headerShown: false }} />\n            <Stack.Screen\n              name=\"modal\"\n              options=\\{{ title: \"Modal\", presentation: \"modal\" }}\n            />\n          </Stack>\n        </GestureHandlerRootView>\n        {{/unless}}\n      </ClerkProvider>\n      {{else}}\n        {{#unless (eq api \"none\")}}\n      <QueryClientProvider client={queryClient}>\n        <GestureHandlerRootView style=\\{{ flex: 1 }}>\n          <Stack\n            screenOptions=\\{{\n              headerStyle: {\n                backgroundColor: theme.colors.background,\n              },\n              headerTitleStyle: {\n                color: theme.colors.foreground,\n              },\n              headerTintColor: theme.colors.foreground,\n            }}\n          >\n            <Stack.Screen name=\"(drawer)\" options=\\{{ headerShown: false }} />\n            <Stack.Screen\n              name=\"modal\"\n              options=\\{{ title: \"Modal\", presentation: \"modal\" }}\n            />\n          </Stack>\n        </GestureHandlerRootView>\n      </QueryClientProvider>\n        {{else}}\n        <GestureHandlerRootView style=\\{{ flex: 1 }}>\n          <Stack\n            screenOptions=\\{{\n              headerStyle: {\n                backgroundColor: theme.colors.background,\n              },\n              headerTitleStyle: {\n                color: theme.colors.foreground,\n              },\n              headerTintColor: theme.colors.foreground,\n            }}\n          >\n            <Stack.Screen name=\"(drawer)\" options=\\{{ headerShown: false }} />\n            <Stack.Screen\n              name=\"modal\"\n              options=\\{{ title: \"Modal\", presentation: \"modal\" }}\n            />\n          </Stack>\n        </GestureHandlerRootView>\n        {{/unless}}\n      {{/if}}\n    {{/if}}\n  );\n}\n"
  },
  {
    "path": "packages/template-generator/templates/frontend/native/unistyles/app/modal.tsx.hbs",
    "content": "import { Container } from \"@/components/container\";\nimport { Text, View } from \"react-native\";\nimport { StyleSheet } from \"react-native-unistyles\";\n\nexport default function Modal() {\n  return (\n    <Container>\n      <View style={styles.container}>\n        <View style={styles.header}>\n          <Text style={styles.title}>Modal</Text>\n        </View>\n      </View>\n    </Container>\n  );\n}\n\nconst styles = StyleSheet.create((theme) => ({\n  container: {\n    flex: 1,\n    padding: theme.spacing.lg,\n  },\n  header: {\n    flexDirection: \"row\",\n    alignItems: \"center\",\n    justifyContent: \"space-between\",\n    marginBottom: theme.spacing.xl,\n  },\n  title: {\n    fontSize: theme.fontSize[\"2xl\"],\n    fontWeight: \"bold\",\n    color: theme.colors.foreground,\n  },\n}));\n"
  },
  {
    "path": "packages/template-generator/templates/frontend/native/unistyles/app.json.hbs",
    "content": "{\n  \"expo\": {\n    \"name\": \"{{projectName}}\",\n    \"slug\": \"{{projectName}}\",\n    \"version\": \"1.0.0\",\n    \"orientation\": \"portrait\",\n    \"icon\": \"./assets/images/icon.png\",\n    \"scheme\": \"{{projectName}}\",\n    \"userInterfaceStyle\": \"automatic\",\n    \"ios\": {\n      \"supportsTablet\": true\n    },\n    \"android\": {\n      \"adaptiveIcon\": {\n        \"backgroundColor\": \"#E6F4FE\",\n        \"foregroundImage\": \"./assets/images/android-icon-foreground.png\",\n        \"backgroundImage\": \"./assets/images/android-icon-background.png\",\n        \"monochromeImage\": \"./assets/images/android-icon-monochrome.png\"\n      },\n      \"predictiveBackGestureEnabled\": false,\n      \"package\": \"com.anonymous.mybettertapp\"\n    },\n    \"web\": {\n      \"output\": \"static\",\n      \"favicon\": \"./assets/images/favicon.png\"\n    },\n    \"plugins\": [\n      \"expo-router\",\n      [\n        \"expo-splash-screen\",\n        {\n          \"image\": \"./assets/images/splash-icon.png\",\n          \"imageWidth\": 200,\n          \"resizeMode\": \"contain\",\n          \"backgroundColor\": \"#ffffff\",\n          \"dark\": {\n            \"backgroundColor\": \"#000000\"\n          }\n        }\n      ]\n    ],\n    \"experiments\": {\n      \"typedRoutes\": true,\n      \"reactCompiler\": true\n    }\n  }\n}\n"
  },
  {
    "path": "packages/template-generator/templates/frontend/native/unistyles/babel.config.js.hbs",
    "content": "module.exports = (api) => {\n\tapi.cache(true);\n\tconst plugins = [];\n\n\tplugins.push([\n\t\t\"react-native-unistyles/plugin\",\n\t\t{\n\t\t\troot: \"src\",\n\t\t\tautoProcessRoot: \"app\",\n\t\t\tautoProcessImports: [\"@/components\"],\n\t\t},\n\t]);\n\n\tplugins.push(\"react-native-worklets/plugin\");\n\n\treturn {\n\t\tpresets: [\"babel-preset-expo\"],\n\n\t\tplugins,\n\t};\n};\n"
  },
  {
    "path": "packages/template-generator/templates/frontend/native/unistyles/breakpoints.ts.hbs",
    "content": "export const breakpoints = {\n  xs: 0,\n  sm: 576,\n  md: 768,\n  lg: 992,\n  xl: 1200,\n  superLarge: 2000,\n  tvLike: 4000,\n} as const;\n"
  },
  {
    "path": "packages/template-generator/templates/frontend/native/unistyles/components/container.tsx.hbs",
    "content": "import React from \"react\";\nimport { SafeAreaView } from \"react-native-safe-area-context\";\nimport { StyleSheet } from \"react-native-unistyles\";\n\nexport const Container = ({ children }: { children: React.ReactNode }) => {\n  return <SafeAreaView style={styles.container}>{children}</SafeAreaView>;\n};\n\nconst styles = StyleSheet.create((theme, rt) => ({\n  container: {\n    flex: 1,\n    backgroundColor: theme.colors.background,\n    paddingBottom: rt.insets.bottom,\n  },\n}));\n"
  },
  {
    "path": "packages/template-generator/templates/frontend/native/unistyles/components/header-button.tsx.hbs",
    "content": "import FontAwesome from \"@expo/vector-icons/FontAwesome\";\nimport { forwardRef } from \"react\";\nimport { Pressable } from \"react-native\";\nimport { StyleSheet } from \"react-native-unistyles\";\n\nexport const HeaderButton = forwardRef<\n  typeof Pressable,\n  { onPress?: () => void }\n>(({ onPress }, ref) => {\n  return (\n    <Pressable onPress={onPress} style={styles.button}>\n      {({ pressed }) => (\n        <FontAwesome\n          name=\"info-circle\"\n          size={20}\n          color={styles.icon.color}\n          style=\\{{\n            opacity: pressed ? 0.7 : 1,\n          }}\n        />\n      )}\n    </Pressable>\n  );\n});\n\nconst styles = StyleSheet.create((theme) => ({\n  button: {\n    padding: theme.spacing.sm,\n    marginRight: theme.spacing.sm,\n    borderRadius: theme.borderRadius.lg,\n    backgroundColor: `${theme.colors.secondary}80`, // 50% opacity\n  },\n  icon: {\n    color: theme.colors.secondaryForeground,\n  },\n}));\n"
  },
  {
    "path": "packages/template-generator/templates/frontend/native/unistyles/components/tabbar-icon.tsx.hbs",
    "content": "import FontAwesome from \"@expo/vector-icons/FontAwesome\";\n\nexport const TabBarIcon = (props: {\n  name: React.ComponentProps<typeof FontAwesome>[\"name\"];\n  color: string;\n}) => {\n  return <FontAwesome size={24} style=\\{{ marginBottom: -3 }} {...props} />;\n};\n"
  },
  {
    "path": "packages/template-generator/templates/frontend/native/unistyles/index.js.hbs",
    "content": "import 'expo-router/entry';\nimport './unistyles';\n"
  },
  {
    "path": "packages/template-generator/templates/frontend/native/unistyles/metro.config.js.hbs",
    "content": "const { getDefaultConfig } = require(\"expo/metro-config\");\n\nconst config = getDefaultConfig(__dirname);\n\nmodule.exports = config;\n"
  },
  {
    "path": "packages/template-generator/templates/frontend/native/unistyles/package.json.hbs",
    "content": "{\n  \"name\": \"native\",\n  \"version\": \"1.0.0\",\n  \"private\": true,\n  \"main\": \"index.js\",\n  \"scripts\": {\n    \"dev\": \"expo start --clear\",\n    \"android\": \"expo run:android\",\n    \"ios\": \"expo run:ios\",\n    \"web\": \"expo start --web\"\n  },\n  \"dependencies\": {\n    \"@expo/vector-icons\": \"^15.1.1\",\n    \"@react-navigation/bottom-tabs\": \"^7.15.9\",\n    \"@react-navigation/drawer\": \"^7.9.4\",\n    \"@react-navigation/native\": \"^7.2.2\",\n    {{#if (includes examples \"ai\")}}\n    \"@stardazed/streams-text-encoding\": \"^1.0.2\",\n    \"@ungap/structured-clone\": \"^1.3.0\",\n    {{/if}}\n    \"babel-preset-expo\": \"~55.0.18\",\n    \"expo\": \"^55.0.17\",\n    \"expo-constants\": \"~55.0.15\",\n    \"expo-crypto\": \"~55.0.14\",\n    \"expo-dev-client\": \"~55.0.28\",\n    \"expo-font\": \"~55.0.6\",\n    \"expo-linking\": \"~55.0.14\",\n    \"expo-network\": \"~55.0.13\",\n    \"expo-router\": \"~55.0.13\",\n    \"expo-secure-store\": \"~55.0.13\",\n    \"expo-splash-screen\": \"~55.0.19\",\n    \"expo-status-bar\": \"~55.0.5\",\n    \"expo-system-ui\": \"~55.0.16\",\n    \"expo-web-browser\": \"~55.0.14\",\n    \"react\": \"19.2.0\",\n    \"react-dom\": \"19.2.0\",\n    \"react-native\": \"0.83.6\",\n    \"react-native-edge-to-edge\": \"^1.8.1\",\n    \"react-native-gesture-handler\": \"~2.30.0\",\n    \"react-native-nitro-modules\": \"^0.35.4\",\n    \"react-native-reanimated\": \"4.2.1\",\n    \"react-native-safe-area-context\": \"~5.6.2\",\n    \"react-native-screens\": \"~4.23.0\",\n    \"react-native-unistyles\": \"^3.2.3\",\n    \"react-native-web\": \"~0.21.0\",\n    \"react-native-worklets\": \"0.7.4\"\n  },\n  \"devDependencies\": {\n    \"ajv\": \"^8.17.1\",\n    \"@babel/core\": \"^7.28.0\",\n    \"@types/react\": \"~19.2.10\",\n    \"typescript\": \"~5.9.2\"\n  }\n}\n"
  },
  {
    "path": "packages/template-generator/templates/frontend/native/unistyles/theme.ts.hbs",
    "content": "const sharedColors = {\n  success: \"#22C55E\",\n  destructive: \"#EF4444\",\n  destructiveForeground: \"#FFFFFF\",\n  warning: \"#F59E0B\",\n  info: \"#3B82F6\",\n} as const;\n\nexport const lightTheme = {\n  colors: {\n    ...sharedColors,\n    typography: \"hsl(0 0% 0%)\",\n    background: \"hsl(0 0% 100%)\",\n    foreground: \"hsl(0 0% 0%)\",\n    card: \"hsl(0 0% 98%)\",\n    cardForeground: \"hsl(0 0% 0%)\",\n    primary: \"hsl(0 0% 10%)\",\n    primaryForeground: \"hsl(0 0% 100%)\",\n    secondary: \"hsl(0 0% 95%)\",\n    secondaryForeground: \"hsl(0 0% 0%)\",\n    muted: \"hsl(0 0% 96%)\",\n    mutedForeground: \"hsl(0 0% 45%)\",\n    accent: \"hsl(0 0% 96%)\",\n    accentForeground: \"hsl(0 0% 0%)\",\n    border: \"hsl(0 0% 90%)\",\n    input: \"hsl(0 0% 90%)\",\n    ring: \"hsl(0 0% 20%)\",\n  },\n  spacing: {\n    xs: 4,\n    sm: 8,\n    md: 16,\n    lg: 24,\n    xl: 32,\n    xxl: 48,\n  },\n  borderRadius: {\n    sm: 6,\n    md: 8,\n    lg: 12,\n    xl: 16,\n  },\n  fontSize: {\n    xs: 12,\n    sm: 14,\n    base: 16,\n    lg: 18,\n    xl: 20,\n    \"2xl\": 24,\n    \"3xl\": 30,\n    \"4xl\": 36,\n  },\n} as const;\n\nexport const darkTheme = {\n  colors: {\n    ...sharedColors,\n    typography: \"hsl(0 0% 100%)\",\n    background: \"hsl(0 0% 0%)\",\n    foreground: \"hsl(0 0% 100%)\",\n    card: \"hsl(0 0% 2%)\",\n    cardForeground: \"hsl(0 0% 100%)\",\n    primary: \"hsl(0 0% 90%)\",\n    primaryForeground: \"hsl(0 0% 0%)\",\n    secondary: \"hsl(0 0% 10%)\",\n    secondaryForeground: \"hsl(0 0% 100%)\",\n    muted: \"hsl(0 0% 8%)\",\n    mutedForeground: \"hsl(0 0% 65%)\",\n    accent: \"hsl(0 0% 8%)\",\n    accentForeground: \"hsl(0 0% 100%)\",\n    border: \"hsl(0 0% 15%)\",\n    input: \"hsl(0 0% 15%)\",\n    ring: \"hsl(0 0% 80%)\",\n  },\n  spacing: {\n    xs: 4,\n    sm: 8,\n    md: 16,\n    lg: 24,\n    xl: 32,\n    xxl: 48,\n  },\n  borderRadius: {\n    sm: 6,\n    md: 8,\n    lg: 12,\n    xl: 16,\n  },\n  fontSize: {\n    xs: 12,\n    sm: 14,\n    base: 16,\n    lg: 18,\n    xl: 20,\n    \"2xl\": 24,\n    \"3xl\": 30,\n    \"4xl\": 36,\n  },\n} as const;\n"
  },
  {
    "path": "packages/template-generator/templates/frontend/native/unistyles/tsconfig.json.hbs",
    "content": "{\n  \"extends\": \"expo/tsconfig.base\",\n  \"compilerOptions\": {\n    \"strict\": true,\n    \"jsx\": \"react-jsx\",\n    \"paths\": {\n      \"@/*\": [\"*\"]\n    }\n  },\n  \"include\": [\"**/*.ts\", \"**/*.tsx\", \".expo/types/**/*.ts\", \"expo-env.d.ts\"]\n}\n"
  },
  {
    "path": "packages/template-generator/templates/frontend/native/unistyles/unistyles.ts.hbs",
    "content": "import { StyleSheet } from \"react-native-unistyles\";\n\nimport { breakpoints } from \"./breakpoints\";\nimport { darkTheme, lightTheme } from \"./theme\";\n\ntype AppBreakpoints = typeof breakpoints;\n\ntype AppThemes = {\n  light: typeof lightTheme;\n  dark: typeof darkTheme;\n};\n\ndeclare module \"react-native-unistyles\" {\n  export interface UnistylesBreakpoints extends AppBreakpoints {}\n  export interface UnistylesThemes extends AppThemes {}\n}\n\nStyleSheet.configure({\n  breakpoints,\n  themes: {\n    light: lightTheme,\n    dark: darkTheme,\n  },\n  settings: {\n    adaptiveThemes: true,\n  },\n});\n"
  },
  {
    "path": "packages/template-generator/templates/frontend/native/uniwind/_gitignore",
    "content": "node_modules/\n.expo/\ndist/\nnpm-debug.*\n*.jks\n*.p8\n*.p12\n*.key\n*.mobileprovision\n*.orig.*\nweb-build/\n\n# macOS\n.DS_Store\n\n# Temporary files created by Metro to check the health of the file watcher\n.metro-health-check*\n\n# UniWind generated types\nuniwind-types.d.ts\n\n"
  },
  {
    "path": "packages/template-generator/templates/frontend/native/uniwind/app/(drawer)/(tabs)/_layout.tsx.hbs",
    "content": "import { Tabs } from \"expo-router\";\nimport { Ionicons } from \"@expo/vector-icons\";\nimport { useThemeColor } from \"heroui-native\";\n\nexport default function TabLayout() {\n\tconst themeColorForeground = useThemeColor(\"foreground\");\n\tconst themeColorBackground = useThemeColor(\"background\");\n\n\treturn (\n\t\t<Tabs\n\t\t\tscreenOptions=\\{{\n\t\t\t\theaderShown: false,\n\t\t\t\theaderStyle: {\n\t\t\t\t\tbackgroundColor: themeColorBackground,\n\t\t\t\t},\n\t\t\t\theaderTintColor: themeColorForeground,\n\t\t\t\theaderTitleStyle: {\n\t\t\t\t\tcolor: themeColorForeground,\n\t\t\t\t\tfontWeight: \"600\",\n\t\t\t\t},\n\t\t\t\ttabBarStyle: {\n\t\t\t\t\tbackgroundColor: themeColorBackground,\n\t\t\t\t},\n\t\t\t}}\n\t\t>\n\t\t\t<Tabs.Screen\n\t\t\t\tname=\"index\"\n\t\t\t\toptions=\\{{\n\t\t\t\t\ttitle: \"Home\",\n\t\t\t\t\ttabBarIcon: ({ color, size }: { color: string; size: number }) => (\n\t\t\t\t\t\t<Ionicons name=\"home\" size={size} color={color} />\n\t\t\t\t\t),\n\t\t\t\t}}\n\t\t\t/>\n\t\t\t<Tabs.Screen\n\t\t\t\tname=\"two\"\n\t\t\t\toptions=\\{{\n\t\t\t\t\ttitle: \"Explore\",\n\t\t\t\t\ttabBarIcon: ({ color, size }: { color: string; size: number }) => (\n\t\t\t\t\t\t<Ionicons name=\"compass\" size={size} color={color} />\n\t\t\t\t\t),\n\t\t\t\t}}\n\t\t\t/>\n\t\t</Tabs>\n\t);\n}\n"
  },
  {
    "path": "packages/template-generator/templates/frontend/native/uniwind/app/(drawer)/(tabs)/index.tsx.hbs",
    "content": "import { Container } from \"@/components/container\";\nimport { Text, View } from \"react-native\";\nimport { Card } from \"heroui-native\";\n\nexport default function Home() {\n\treturn (\n\t\t<Container className=\"p-6\">\n\t\t\t<View className=\"flex-1 justify-center items-center\">\n\t\t\t\t<Card variant=\"secondary\" className=\"p-8 items-center\">\n\t\t\t\t\t<Card.Title className=\"text-3xl mb-2\">Tab One</Card.Title>\n\t\t\t\t</Card>\n\t\t\t</View>\n\t\t</Container>\n\t);\n}\n"
  },
  {
    "path": "packages/template-generator/templates/frontend/native/uniwind/app/(drawer)/(tabs)/two.tsx.hbs",
    "content": "import { Container } from \"@/components/container\";\nimport { Text, View } from \"react-native\";\nimport { Card } from \"heroui-native\";\n\nexport default function TabTwo() {\n\treturn (\n\t\t<Container className=\"p-6\">\n\t\t\t<View className=\"flex-1 justify-center items-center\">\n\t\t\t\t<Card variant=\"secondary\" className=\"p-8 items-center\">\n\t\t\t\t\t<Card.Title className=\"text-3xl mb-2\">TabTwo</Card.Title>\n\t\t\t\t</Card>\n\t\t\t</View>\n\t\t</Container>\n\t);\n}\n"
  },
  {
    "path": "packages/template-generator/templates/frontend/native/uniwind/app/(drawer)/_layout.tsx.hbs",
    "content": "import React, { useCallback } from \"react\";\nimport { Ionicons, MaterialIcons } from \"@expo/vector-icons\";\nimport { Link } from \"expo-router\";\nimport { Drawer } from \"expo-router/drawer\";\nimport { useThemeColor } from \"heroui-native\";\nimport { Pressable, Text } from \"react-native\";\nimport { ThemeToggle } from \"@/components/theme-toggle\";\n\nfunction DrawerLayout() {\n  const themeColorForeground = useThemeColor(\"foreground\");\n  const themeColorBackground = useThemeColor(\"background\");\n\n  const renderThemeToggle = useCallback(() => <ThemeToggle />, []);\n\n  return (\n    <Drawer\n      screenOptions=\\{{\n        headerTintColor: themeColorForeground,\n        headerStyle: { backgroundColor: themeColorBackground },\n        headerTitleStyle: {\n          fontWeight: \"600\",\n          color: themeColorForeground,\n        },\n        headerRight: renderThemeToggle,\n        drawerStyle: { backgroundColor: themeColorBackground },\n      }}\n    >\n      <Drawer.Screen\n        name=\"index\"\n        options=\\{{\n          headerTitle: \"Home\",\n          drawerLabel: ({ color, focused }) => (\n            <Text style=\\{{ color: focused ? color : themeColorForeground }}>Home</Text>\n          ),\n          drawerIcon: ({ size, color, focused }) => (\n            <Ionicons name=\"home-outline\" size={size} color={focused ? color : themeColorForeground} />\n          ),\n        }}\n      />\n      <Drawer.Screen\n        name=\"(tabs)\"\n        options=\\{{\n          headerTitle: \"Tabs\",\n          drawerLabel: ({ color, focused }) => (\n            <Text style=\\{{ color: focused ? color : themeColorForeground }}>Tabs</Text>\n          ),\n          drawerIcon: ({ size, color, focused }) => (\n            <MaterialIcons name=\"border-bottom\" size={size} color={focused ? color : themeColorForeground} />\n          ),\n          headerRight: () => (\n            <Link href=\"/modal\" asChild>\n              <Pressable className=\"mr-4\">\n                <Ionicons name=\"add-outline\" size={24} color={themeColorForeground} />\n              </Pressable>\n            </Link>\n          ),\n        }}\n      />\n      {{#if (includes examples \"todo\")}}\n      <Drawer.Screen\n        name=\"todos\"\n        options=\\{{\n          headerTitle: \"Todos\",\n          drawerLabel: ({ color, focused }) => (\n            <Text style=\\{{ color: focused ? color : themeColorForeground }}>Todos</Text>\n          ),\n          drawerIcon: ({ size, color, focused }) => (\n            <Ionicons name=\"checkbox-outline\" size={size} color={focused ? color : themeColorForeground} />\n          ),\n        }}\n      />\n      {{/if}}\n      {{#if (includes examples \"ai\")}}\n      <Drawer.Screen\n        name=\"ai\"\n        options=\\{{\n          headerTitle: \"AI\",\n          drawerLabel: ({ color, focused }) => (\n            <Text style=\\{{ color: focused ? color : themeColorForeground }}>AI</Text>\n          ),\n          drawerIcon: ({ size, color, focused }) => (\n            <Ionicons name=\"chatbubble-ellipses-outline\" size={size} color={focused ? color : themeColorForeground} />\n          ),\n        }}\n      />\n      {{/if}}\n    </Drawer>\n  );\n}\n\nexport default DrawerLayout;"
  },
  {
    "path": "packages/template-generator/templates/frontend/native/uniwind/app/(drawer)/index.tsx.hbs",
    "content": "import { Text, View } from \"react-native\";\nimport { Container } from \"@/components/container\";\n{{#if (eq api \"orpc\")}}\nimport { useQuery } from \"@tanstack/react-query\";\nimport { orpc } from \"@/utils/orpc\";\n{{/if}}\n{{#if (eq api \"trpc\")}}\nimport { useQuery } from \"@tanstack/react-query\";\nimport { trpc } from \"@/utils/trpc\";\n{{/if}}\n{{#if (and (eq backend \"convex\") (eq auth \"clerk\"))}}\nimport { Link } from \"expo-router\";\nimport { Authenticated, AuthLoading, Unauthenticated, useQuery } from \"convex/react\";\nimport { api } from \"@{{projectName}}/backend/convex/_generated/api\";\nimport { useUser } from \"@clerk/expo\";\nimport { SignOutButton } from \"@/components/sign-out-button\";\n{{else if (and (ne backend \"convex\") (eq auth \"clerk\"))}}\nimport { Link } from \"expo-router\";\nimport { useAuth, useUser } from \"@clerk/expo\";\nimport { SignOutButton } from \"@/components/sign-out-button\";\n{{else if (and (eq backend \"convex\") (eq auth \"better-auth\"))}}\nimport { useConvexAuth, useQuery } from \"convex/react\";\nimport { api } from \"@{{projectName}}/backend/convex/_generated/api\";\nimport { authClient } from \"@/lib/auth-client\";\nimport { SignIn } from \"@/components/sign-in\";\nimport { SignUp } from \"@/components/sign-up\";\n{{else if (eq backend \"convex\")}}\nimport { useQuery } from \"convex/react\";\nimport { api } from \"@{{projectName}}/backend/convex/_generated/api\";\n{{/if}}\n{{#unless (or (eq backend \"none\") (and (eq backend \"convex\") (eq auth \"better-auth\")))}}\nimport { Ionicons } from \"@expo/vector-icons\";\n{{/unless}}\nimport { Button, Chip, Separator, Spinner, Surface, useThemeColor } from \"heroui-native\";\n\nexport default function Home() {\n{{#if (eq api \"orpc\")}}\nconst healthCheck = useQuery(orpc.healthCheck.queryOptions());\n{{/if}}\n{{#if (eq api \"trpc\")}}\nconst healthCheck = useQuery(trpc.healthCheck.queryOptions());\n{{/if}}\n{{#if (and (eq backend \"convex\") (eq auth \"clerk\"))}}\nconst { user } = useUser();\nconst healthCheck = useQuery(api.healthCheck.get);\nconst privateData = useQuery(api.privateData.get);\n{{else if (and (ne backend \"convex\") (eq auth \"clerk\"))}}\nconst { isLoaded, isSignedIn } = useAuth();\nconst { user } = useUser();\n{{else if (and (eq backend \"convex\") (eq auth \"better-auth\"))}}\nconst healthCheck = useQuery(api.healthCheck.get);\nconst { isAuthenticated } = useConvexAuth();\nconst user = useQuery(api.auth.getCurrentUser, isAuthenticated ? {} : \"skip\");\n{{else if (eq backend \"convex\")}}\nconst healthCheck = useQuery(api.healthCheck.get);\n{{/if}}\n{{#unless (eq backend \"none\")}}\nconst successColor = useThemeColor(\"success\");\nconst dangerColor = useThemeColor(\"danger\");\n\n{{#if (eq backend \"convex\")}}\nconst isConnected = healthCheck === \"OK\";\nconst isLoading = healthCheck === undefined;\n{{else}}\n{{#unless (eq api \"none\")}}\nconst isConnected = healthCheck?.data === \"OK\";\nconst isLoading = healthCheck?.isLoading;\n{{/unless}}\n{{/if}}\n{{/unless}}\n\nreturn (\n<Container className=\"px-4 pb-4\">\n  <View className=\"py-6 mb-5\">\n    <Text className=\"text-3xl font-semibold text-foreground tracking-tight\">\n      Better T Stack\n    </Text>\n    <Text className=\"text-muted text-sm mt-1\">Full-stack TypeScript starter</Text>\n  </View>\n\n  {{#unless (or (eq backend \"none\") (and (eq backend \"convex\") (eq auth \"better-auth\")))}}\n  <Surface variant=\"secondary\" className=\"p-4 rounded-xl\">\n    <View className=\"flex-row items-center justify-between mb-3\">\n      <Text className=\"text-foreground font-medium\">System Status</Text>\n      <Chip variant=\"secondary\" color={isConnected ? \"success\" : \"danger\" } size=\"sm\">\n        <Chip.Label>\n          {isConnected ? \"LIVE\" : \"OFFLINE\"}\n        </Chip.Label>\n      </Chip>\n    </View>\n\n    <Separator className=\"mb-3\" />\n\n    <Surface variant=\"tertiary\" className=\"p-3 rounded-lg\">\n      <View className=\"flex-row items-center\">\n        <View className={`w-2 h-2 rounded-full mr-3 ${ isConnected ? \"bg-success\" : \"bg-muted\" }`} />\n        <View className=\"flex-1\">\n          <Text className=\"text-foreground text-sm font-medium\">\n            {{#if (eq backend \"convex\")}}\n            Convex Backend\n            {{else}}\n            {{#unless (eq api \"none\")}}\n            {{#if (eq api \"orpc\")}}ORPC{{else}}TRPC{{/if}} Backend\n            {{/unless}}\n            {{/if}}\n          </Text>\n          <Text className=\"text-muted text-xs mt-0.5\">\n            {isLoading\n            ? \"Checking connection...\"\n            : isConnected\n            ? \"Connected to API\"\n            : \"API Disconnected\"}\n          </Text>\n        </View>\n        {isLoading && <Spinner size=\"sm\" />}\n        {!isLoading && isConnected && (\n        <Ionicons name=\"checkmark-circle\" size={18} color={successColor} />\n        )}\n        {!isLoading && !isConnected && (\n        <Ionicons name=\"close-circle\" size={18} color={dangerColor} />\n        )}\n      </View>\n    </Surface>\n  </Surface>\n  {{/unless}}\n\n  {{#if (and (eq backend \"convex\") (eq auth \"clerk\"))}}\n  <Authenticated>\n    <Surface variant=\"secondary\" className=\"mt-5 p-4 rounded-xl\">\n      <View className=\"flex-row items-center justify-between\">\n        <View className=\"flex-1\">\n          <Text className=\"text-foreground font-medium\">{user?.emailAddresses[0].emailAddress}</Text>\n          <Text className=\"text-muted text-xs mt-0.5\">Private: {privateData?.message}</Text>\n        </View>\n        <SignOutButton />\n      </View>\n    </Surface>\n  </Authenticated>\n  <Unauthenticated>\n    <View className=\"mt-4 gap-3\">\n      <Link href=\"/(auth)/sign-in\" asChild>\n        <Button variant=\"secondary\"><Button.Label>Sign In</Button.Label></Button>\n      </Link>\n      <Link href=\"/(auth)/sign-up\" asChild>\n        <Button variant=\"ghost\"><Button.Label>Sign Up</Button.Label></Button>\n      </Link>\n    </View>\n  </Unauthenticated>\n  <AuthLoading>\n    <View className=\"mt-4 items-center\">\n      <Spinner size=\"sm\" />\n    </View>\n  </AuthLoading>\n  {{/if}}\n\n  {{#if (and (ne backend \"convex\") (eq auth \"clerk\"))}}\n  {!isLoaded ? (\n  <View className=\"mt-4 items-center\">\n    <Spinner size=\"sm\" />\n  </View>\n  ) : isSignedIn ? (\n  <Surface variant=\"secondary\" className=\"mt-5 p-4 rounded-xl\">\n    <View className=\"flex-row items-center justify-between\">\n      <View className=\"flex-1\">\n        <Text className=\"text-foreground font-medium\">\n          {user?.fullName ?? user?.firstName ?? \"Welcome\"}\n        </Text>\n        <Text className=\"text-muted text-xs mt-0.5\">\n          {user?.emailAddresses[0]?.emailAddress}\n        </Text>\n      </View>\n      <SignOutButton />\n    </View>\n  </Surface>\n  ) : (\n  <View className=\"mt-4 gap-3\">\n    <Link href=\"/(auth)/sign-in\" asChild>\n      <Button variant=\"secondary\"><Button.Label>Sign In</Button.Label></Button>\n    </Link>\n    <Link href=\"/(auth)/sign-up\" asChild>\n      <Button variant=\"ghost\"><Button.Label>Sign Up</Button.Label></Button>\n    </Link>\n  </View>\n  )}\n  {{/if}}\n\n  {{#if (and (eq backend \"convex\") (eq auth \"better-auth\"))}}\n  {user ? (\n  <Surface variant=\"secondary\" className=\"mb-4 p-4 rounded-xl\">\n    <View className=\"flex-row items-center justify-between\">\n      <View className=\"flex-1\">\n        <Text className=\"text-foreground font-medium\">{user.name}</Text>\n        <Text className=\"text-muted text-xs mt-0.5\">{user.email}</Text>\n      </View>\n      <Button\n        variant=\"danger\"\n        size=\"sm\"\n        onPress={() => {\n          authClient.signOut();\n        }}\n      >\n        Sign Out\n      </Button>\n    </View>\n  </Surface>\n  ) : null}\n  <Surface variant=\"secondary\" className=\"p-4 rounded-xl\">\n    <Text className=\"text-foreground font-medium mb-2\">API Status</Text>\n    <View className=\"flex-row items-center gap-2\">\n      <View className={`w-2 h-2 rounded-full ${healthCheck===\"OK\" ? \"bg-success\" : \"bg-danger\" }`} />\n      <Text className=\"text-muted text-xs\">\n        {healthCheck === undefined\n        ? \"Checking...\"\n        : healthCheck === \"OK\"\n        ? \"Connected to API\"\n        : \"API Disconnected\"}\n      </Text>\n    </View>\n  </Surface>\n  {!user && (\n  <View className=\"mt-5 gap-4\">\n    <SignIn />\n    <SignUp />\n  </View>\n  )}\n  {{/if}}\n</Container>\n);\n}\n"
  },
  {
    "path": "packages/template-generator/templates/frontend/native/uniwind/app/+not-found.tsx.hbs",
    "content": "import { Link, Stack } from \"expo-router\";\nimport { Button, Surface } from \"heroui-native\";\nimport { Text, View } from \"react-native\";\n\nimport { Container } from \"@/components/container\";\n\nexport default function NotFoundScreen() {\n\treturn (\n\t\t<>\n\t\t\t<Stack.Screen options=\\{{ title: \"Not Found\" }} />\n\t\t\t<Container>\n\t\t\t\t<View className=\"flex-1 justify-center items-center p-4\">\n\t\t\t\t\t<Surface variant=\"secondary\" className=\"items-center p-6 max-w-sm rounded-lg\">\n\t\t\t\t\t\t<Text className=\"text-4xl mb-3\">🤔</Text>\n\t\t\t\t\t\t<Text className=\"text-foreground font-medium text-lg mb-1\">Page Not Found</Text>\n\t\t\t\t\t\t<Text className=\"text-muted text-sm text-center mb-4\">\n\t\t\t\t\t\t\tThe page you're looking for doesn't exist.\n\t\t\t\t\t\t</Text>\n\t\t\t\t\t\t<Link href=\"/\" asChild>\n\t\t\t\t\t\t\t<Button size=\"sm\">Go Home</Button>\n\t\t\t\t\t\t</Link>\n\t\t\t\t\t</Surface>\n\t\t\t\t</View>\n\t\t\t</Container>\n\t\t</>\n\t);\n}\n"
  },
  {
    "path": "packages/template-generator/templates/frontend/native/uniwind/app/_layout.tsx.hbs",
    "content": "{{#if (includes examples \"ai\")}}\nimport \"@/polyfills\";\n{{/if}}\n{{#if (and (eq auth \"clerk\") (ne api \"none\") (ne backend \"convex\"))}}\nimport { useEffect } from \"react\";\nimport { setClerkAuthTokenGetter } from \"@/utils/clerk-auth\";\n{{/if}}\n\nimport \"@/global.css\";\n{{#if (and (ne backend \"convex\") (eq auth \"clerk\"))}}\nimport { ClerkProvider{{#unless (eq api \"none\")}}, useAuth{{/unless}} } from \"@clerk/expo\";\nimport { tokenCache } from \"@clerk/expo/token-cache\";\nimport { env } from \"@{{projectName}}/env/native\";\n{{/if}}\n\n{{#if (eq backend \"convex\")}}\n  {{#if (eq auth \"better-auth\")}}\n    import { ConvexReactClient } from \"convex/react\";\n    import { ConvexBetterAuthProvider } from \"@convex-dev/better-auth/react\";\n    import { authClient } from \"@/lib/auth-client\";\n    import { env } from \"@{{projectName}}/env/native\";\n  {{else}}\n    import { ConvexProvider, ConvexReactClient } from \"convex/react\";\n    import { env } from \"@{{projectName}}/env/native\";\n  {{/if}}\n\n  {{#if (eq auth \"clerk\")}}\n    import { ClerkProvider, useAuth } from \"@clerk/expo\";\n    import { ConvexProviderWithClerk } from \"convex/react-clerk\";\n    import { tokenCache } from \"@clerk/expo/token-cache\";\n  {{/if}}\n{{else}}\n  {{#unless (eq api \"none\")}}\n    import { QueryClientProvider } from \"@tanstack/react-query\";\n  {{/unless}}\n{{/if}}\n\nimport { Stack } from \"expo-router\";\nimport { HeroUINativeProvider } from \"heroui-native\";\nimport { GestureHandlerRootView } from \"react-native-gesture-handler\";\nimport { KeyboardProvider } from \"react-native-keyboard-controller\";\nimport { AppThemeProvider } from \"@/contexts/app-theme-context\";\n\n{{#if (eq api \"trpc\")}}\n  import { queryClient } from \"@/utils/trpc\";\n{{/if}}\n{{#if (eq api \"orpc\")}}\n  import { queryClient } from \"@/utils/orpc\";\n{{/if}}\n\nexport const unstable_settings = {\n  initialRouteName: \"(drawer)\",\n};\n\n{{#if (eq backend \"convex\")}}\n  const convex = new ConvexReactClient(env.EXPO_PUBLIC_CONVEX_URL, {\n    {{#if (eq auth \"better-auth\")}}\n    expectAuth: true,\n    {{/if}}\n    unsavedChangesWarning: false,\n  });\n{{/if}}\n\n{{#if (and (eq auth \"clerk\") (ne api \"none\") (ne backend \"convex\"))}}\nfunction ClerkApiAuthBridge() {\n  const { getToken } = useAuth();\n\n  useEffect(() => {\n    setClerkAuthTokenGetter(getToken);\n\n    return () => {\n      setClerkAuthTokenGetter(null);\n    };\n  }, [getToken]);\n\n  return null;\n}\n{{/if}}\n\nfunction StackLayout() {\n  return (\n    <Stack screenOptions=\\{{}}>\n      <Stack.Screen name=\"(drawer)\" options=\\{{ headerShown: false }} />\n      {{#if (eq auth \"clerk\")}}\n        <Stack.Screen name=\"(auth)\" options=\\{{ headerShown: false }} />\n      {{/if}}\n      <Stack.Screen name=\"modal\" options=\\{{ title: \"Modal\", presentation: \"modal\" }} />\n    </Stack>\n  );\n}\n\nexport default function Layout() {\n  return (\n    {{#if (eq backend \"convex\")}}\n      {{#if (eq auth \"clerk\")}}\n        <ClerkProvider tokenCache={tokenCache} publishableKey={env.EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY}>\n          <ConvexProviderWithClerk client={convex} useAuth={useAuth}>\n            <GestureHandlerRootView style=\\{{ flex: 1 }}>\n              <KeyboardProvider>\n                <AppThemeProvider>\n                  <HeroUINativeProvider>\n                    <StackLayout />\n                  </HeroUINativeProvider>\n                </AppThemeProvider>\n              </KeyboardProvider>\n            </GestureHandlerRootView>\n          </ConvexProviderWithClerk>\n        </ClerkProvider>\n      {{else if (eq auth \"better-auth\")}}\n        <ConvexBetterAuthProvider client={convex} authClient={authClient}>\n          <GestureHandlerRootView style=\\{{ flex: 1 }}>\n            <KeyboardProvider>\n              <AppThemeProvider>\n                <HeroUINativeProvider>\n                  <StackLayout />\n                </HeroUINativeProvider>\n              </AppThemeProvider>\n            </KeyboardProvider>\n          </GestureHandlerRootView>\n        </ConvexBetterAuthProvider>\n      {{else}}\n        <ConvexProvider client={convex}>\n          <GestureHandlerRootView style=\\{{ flex: 1 }}>\n            <KeyboardProvider>\n              <AppThemeProvider>\n                <HeroUINativeProvider>\n                  <StackLayout />\n                </HeroUINativeProvider>\n              </AppThemeProvider>\n            </KeyboardProvider>\n          </GestureHandlerRootView>\n        </ConvexProvider>\n      {{/if}}\n    {{else}}\n      {{#if (eq auth \"clerk\")}}\n        <ClerkProvider tokenCache={tokenCache} publishableKey={env.EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY}>\n          {{#unless (eq api \"none\")}}\n            <ClerkApiAuthBridge />\n            <QueryClientProvider client={queryClient}>\n              <GestureHandlerRootView style=\\{{ flex: 1 }}>\n                <KeyboardProvider>\n                  <AppThemeProvider>\n                    <HeroUINativeProvider>\n                      <StackLayout />\n                    </HeroUINativeProvider>\n                  </AppThemeProvider>\n                </KeyboardProvider>\n              </GestureHandlerRootView>\n            </QueryClientProvider>\n          {{else}}\n            <GestureHandlerRootView style=\\{{ flex: 1 }}>\n              <KeyboardProvider>\n                <AppThemeProvider>\n                  <HeroUINativeProvider>\n                    <StackLayout />\n                  </HeroUINativeProvider>\n                </AppThemeProvider>\n              </KeyboardProvider>\n            </GestureHandlerRootView>\n          {{/unless}}\n        </ClerkProvider>\n      {{else}}\n        {{#unless (eq api \"none\")}}\n          <QueryClientProvider client={queryClient}>\n            <GestureHandlerRootView style=\\{{ flex: 1 }}>\n              <KeyboardProvider>\n                <AppThemeProvider>\n                  <HeroUINativeProvider>\n                    <StackLayout />\n                  </HeroUINativeProvider>\n                </AppThemeProvider>\n              </KeyboardProvider>\n            </GestureHandlerRootView>\n          </QueryClientProvider>\n        {{else}}\n          <GestureHandlerRootView style=\\{{ flex: 1 }}>\n            <KeyboardProvider>\n              <AppThemeProvider>\n                <HeroUINativeProvider>\n                  <StackLayout />\n                </HeroUINativeProvider>\n              </AppThemeProvider>\n            </KeyboardProvider>\n          </GestureHandlerRootView>\n        {{/unless}}\n      {{/if}}\n    {{/if}}\n  );\n}\n"
  },
  {
    "path": "packages/template-generator/templates/frontend/native/uniwind/app/modal.tsx.hbs",
    "content": "import { Ionicons } from \"@expo/vector-icons\";\nimport { router } from \"expo-router\";\nimport { Button, Surface, useThemeColor } from \"heroui-native\";\nimport { Text, View } from \"react-native\";\n\nimport { Container } from \"@/components/container\";\n\nfunction Modal() {\n\tconst accentForegroundColor = useThemeColor(\"accent-foreground\");\n\n\tfunction handleClose() {\n\t\trouter.back();\n\t}\n\n\treturn (\n\t\t<Container>\n\t\t\t<View className=\"flex-1 justify-center items-center p-4\">\n\t\t\t\t<Surface variant=\"secondary\" className=\"p-5 w-full max-w-sm rounded-lg\">\n\t\t\t\t\t<View className=\"items-center\">\n\t\t\t\t\t\t<View className=\"w-12 h-12 bg-accent rounded-lg items-center justify-center mb-3\">\n\t\t\t\t\t\t\t<Ionicons name=\"checkmark\" size={24} color={accentForegroundColor} />\n\t\t\t\t\t\t</View>\n\t\t\t\t\t\t<Text className=\"text-foreground font-medium text-lg mb-1\">Modal Screen</Text>\n\t\t\t\t\t\t<Text className=\"text-muted text-sm text-center mb-4\">\n\t\t\t\t\t\t\tThis is an example modal screen for dialogs and confirmations.\n\t\t\t\t\t\t</Text>\n\t\t\t\t\t</View>\n\t\t\t\t\t<Button onPress={handleClose} className=\"w-full\" size=\"sm\">\n\t\t\t\t\t\t<Button.Label>Close</Button.Label>\n\t\t\t\t\t</Button>\n\t\t\t\t</Surface>\n\t\t\t</View>\n\t\t</Container>\n\t);\n}\n\nexport default Modal;\n"
  },
  {
    "path": "packages/template-generator/templates/frontend/native/uniwind/app.json.hbs",
    "content": "{\n  \"expo\": {\n    \"scheme\": \"{{projectName}}\",\n    \"userInterfaceStyle\": \"automatic\",\n    \"orientation\": \"default\",\n    \"web\": {\n      \"bundler\": \"metro\"\n    },\n    \"name\": \"{{projectName}}\",\n    \"slug\": \"{{projectName}}\",\n    \"plugins\": [\n      \"expo-font\"\n    ],\n    \"experiments\": {\n      \"typedRoutes\": true,\n      \"reactCompiler\": true\n    }\n  }\n}\n"
  },
  {
    "path": "packages/template-generator/templates/frontend/native/uniwind/components/container.tsx.hbs",
    "content": "import { cn } from \"heroui-native\";\nimport { type PropsWithChildren } from \"react\";\nimport { ScrollView, View, type ScrollViewProps, type ViewProps } from \"react-native\";\nimport Animated, { type AnimatedProps } from \"react-native-reanimated\";\nimport { useSafeAreaInsets } from \"react-native-safe-area-context\";\n\nconst AnimatedView = Animated.createAnimatedComponent(View);\n\ntype Props = AnimatedProps<ViewProps> & {\n  className?: string;\n  isScrollable?: boolean;\n  scrollViewProps?: Omit<ScrollViewProps, \"contentContainerStyle\">;\n};\n\nexport function Container({\n  children,\n  className,\n  isScrollable = true,\n  scrollViewProps,\n  ...props\n}: PropsWithChildren<Props>) {\n  const insets = useSafeAreaInsets();\n\n  return (\n    <AnimatedView\n      className={cn(\"flex-1 bg-background\", className)}\n      style=\\{{\n        paddingBottom: insets.bottom,\n      }}\n      {...props}\n    >\n      {isScrollable ? (\n        <ScrollView\n          contentContainerStyle=\\{{ flexGrow: 1 }}\n          keyboardShouldPersistTaps=\"handled\"\n          contentInsetAdjustmentBehavior=\"automatic\"\n          {...scrollViewProps}\n        >\n          {children}\n        </ScrollView>\n      ) : (\n        <View className=\"flex-1\">{children}</View>\n      )}\n    </AnimatedView>\n  );\n}\n"
  },
  {
    "path": "packages/template-generator/templates/frontend/native/uniwind/components/theme-toggle.tsx.hbs",
    "content": "import { Ionicons } from '@expo/vector-icons';\nimport * as Haptics from 'expo-haptics';\nimport { Platform, Pressable } from 'react-native';\nimport Animated, { FadeOut, ZoomIn } from 'react-native-reanimated';\nimport { withUniwind } from 'uniwind';\nimport { useAppTheme } from '@/contexts/app-theme-context';\n\nconst StyledIonicons = withUniwind(Ionicons);\n\nexport function ThemeToggle() {\n\tconst { toggleTheme, isLight } = useAppTheme();\n\n\treturn (\n\t\t<Pressable\n\t\t\tonPress={() => {\n\t\t\t\tif (Platform.OS === 'ios') {\n\t\t\t\t\tHaptics.impactAsync(Haptics.ImpactFeedbackStyle.Light);\n\t\t\t\t}\n\t\t\t\ttoggleTheme();\n\t\t\t}}\n\t\t\tclassName=\"px-2.5\"\n\t\t>\n\t\t\t{isLight ? (\n\t\t\t\t<Animated.View key=\"moon\" entering={ZoomIn} exiting={FadeOut}>\n\t\t\t\t\t<StyledIonicons name=\"moon\" size={20} className=\"text-foreground\" />\n\t\t\t\t</Animated.View>\n\t\t\t) : (\n\t\t\t\t<Animated.View key=\"sun\" entering={ZoomIn} exiting={FadeOut}>\n\t\t\t\t\t<StyledIonicons name=\"sunny\" size={20} className=\"text-foreground\" />\n\t\t\t\t</Animated.View>\n\t\t\t)}\n\t\t</Pressable>\n\t);\n}\n\n"
  },
  {
    "path": "packages/template-generator/templates/frontend/native/uniwind/contexts/app-theme-context.tsx.hbs",
    "content": "import React, { createContext, useCallback, useContext, useMemo } from 'react';\nimport { Uniwind, useUniwind } from 'uniwind';\n\ntype ThemeName = 'light' | 'dark';\n\ntype AppThemeContextType = {\n    currentTheme: string;\n    isLight: boolean;\n    isDark: boolean;\n    setTheme: (theme: ThemeName) => void;\n    toggleTheme: () => void;\n}\n\nconst AppThemeContext = createContext<AppThemeContextType | undefined>(\n    undefined\n);\n\nexport const AppThemeProvider = ({ children }: { children: React.ReactNode }) => {\n    const { theme } = useUniwind();\n\n    const isLight = useMemo(() => {\n        return theme === 'light';\n    }, [theme]);\n\n    const isDark = useMemo(() => {\n        return theme === 'dark';\n    }, [theme]);\n\n    const setTheme = useCallback((newTheme: ThemeName) => {\n        Uniwind.setTheme(newTheme);\n    }, []);\n\n    const toggleTheme = useCallback(() => {\n        Uniwind.setTheme(theme === 'light' ? 'dark' : 'light');\n    }, [theme]);\n\n    const value = useMemo(\n        () => ({\n            currentTheme: theme,\n            isLight,\n            isDark,\n            setTheme,\n            toggleTheme,\n        }),\n        [theme, isLight, isDark, setTheme, toggleTheme]\n    );\n\n    return (\n        <AppThemeContext.Provider value={value}>\n            {children}\n        </AppThemeContext.Provider>\n    );\n};\n\nexport function useAppTheme() {\n    const context = useContext(AppThemeContext);\n    if (!context) {\n        throw new Error('useAppTheme must be used within AppThemeProvider');\n    }\n    return context;\n}\n\n"
  },
  {
    "path": "packages/template-generator/templates/frontend/native/uniwind/global.css",
    "content": "@import \"tailwindcss\";\n@import \"uniwind\";\n@import \"heroui-native/styles\";\n\n@source './node_modules/heroui-native/lib';\n"
  },
  {
    "path": "packages/template-generator/templates/frontend/native/uniwind/metro.config.js.hbs",
    "content": "const { getDefaultConfig } = require(\"expo/metro-config\");\nconst { withUniwindConfig } = require(\"uniwind/metro\");\nconst { wrapWithReanimatedMetroConfig } = require(\"react-native-reanimated/metro-config\");\n\n/** @type {import('expo/metro-config').MetroConfig} */\nconst config = getDefaultConfig(__dirname);\n\nconst uniwindConfig = withUniwindConfig(wrapWithReanimatedMetroConfig(config), {\n  cssEntryFile: \"./global.css\",\n  dtsFile: \"./uniwind-types.d.ts\",\n});\n\nmodule.exports = uniwindConfig;\n"
  },
  {
    "path": "packages/template-generator/templates/frontend/native/uniwind/package.json.hbs",
    "content": "{\n  \"name\": \"native\",\n  \"version\": \"1.0.0\",\n  \"private\": true,\n  \"main\": \"expo-router/entry\",\n  \"scripts\": {\n    \"start\": \"expo start\",\n    \"dev\": \"expo start --clear\",\n    \"android\": \"expo run:android\",\n    \"ios\": \"expo run:ios\",\n    \"prebuild\": \"expo prebuild\",\n    \"web\": \"expo start --web\"\n  },\n  \"dependencies\": {\n    \"@expo/metro-runtime\": \"~55.0.10\",\n    \"@expo/vector-icons\": \"^15.1.1\",\n    \"@gorhom/bottom-sheet\": \"^5.2.10\",\n    \"@react-navigation/drawer\": \"^7.9.4\",\n    \"@react-navigation/elements\": \"^2.9.14\",\n    {{#if (includes examples \"ai\")}}\n    \"@stardazed/streams-text-encoding\": \"^1.0.2\",\n    \"@ungap/structured-clone\": \"^1.3.0\",\n    {{/if}}\n    \"expo\": \"^55.0.17\",\n    \"expo-constants\": \"~55.0.15\",\n    \"expo-font\": \"~55.0.6\",\n    \"expo-haptics\": \"~55.0.14\",\n    \"expo-linking\": \"~55.0.14\",\n    \"expo-network\": \"~55.0.13\",\n    \"expo-router\": \"~55.0.13\",\n    \"expo-secure-store\": \"~55.0.13\",\n    \"expo-status-bar\": \"~55.0.5\",\n    \"expo-web-browser\": \"~55.0.14\",\n    \"heroui-native\": \"^1.0.2\",\n    \"react\": \"19.2.0\",\n    \"react-dom\": \"19.2.0\",\n    \"react-native\": \"0.83.6\",\n    \"react-native-gesture-handler\": \"~2.30.0\",\n    \"react-native-keyboard-controller\": \"1.20.7\",\n    \"react-native-reanimated\": \"4.2.1\",\n    \"react-native-safe-area-context\": \"~5.6.2\",\n    \"react-native-screens\": \"~4.23.0\",\n    \"react-native-svg\": \"15.15.3\",\n    \"react-native-web\": \"~0.21.0\",\n    \"react-native-worklets\": \"0.7.4\",\n    \"tailwind-merge\": \"^3.5.0\",\n    \"tailwind-variants\": \"^3.2.2\",\n    \"tailwindcss\": \"^4.2.4\",\n    \"uniwind\": \"^1.6.3\"\n  },\n  \"devDependencies\": {\n    \"@types/node\": \"^24.10.0\",\n    \"@types/react\": \"~19.2.10\",\n    \"typescript\": \"~5.9.2\"\n  }\n}\n"
  },
  {
    "path": "packages/template-generator/templates/frontend/native/uniwind/tsconfig.json.hbs",
    "content": "{\n  \"extends\": \"expo/tsconfig.base\",\n  \"compilerOptions\": {\n    \"strict\": true,\n    \"paths\": {\n      \"@/*\": [\"./*\"]\n    }\n  },\n  \"include\": [\n    \"**/*.ts\",\n    \"**/*.tsx\",\n    \".expo/types/**/*.ts\",\n    \"expo-env.d.ts\"\n  ]\n}"
  },
  {
    "path": "packages/template-generator/templates/frontend/native/uniwind/uniwind-env.d.ts",
    "content": "/// <reference types=\"uniwind/types\" />\n"
  },
  {
    "path": "packages/template-generator/templates/frontend/nuxt/_gitignore",
    "content": "# Nuxt dev/build outputs\n.output\n.data\n.nuxt\n.nitro\n.cache\ndist\n.wrangler\n.alchemy\n\n# Node dependencies\nnode_modules\n\n# Logs\nlogs\n*.log\n\n# Misc\n.DS_Store\n.fleet\n.idea\n\n# Local env files\n.env\n.env.*\n!.env.example\n\n"
  },
  {
    "path": "packages/template-generator/templates/frontend/nuxt/app/app.config.ts.hbs",
    "content": "export default defineAppConfig({\n  ui: {\n    colors: {\n      primary: 'emerald',\n      neutral: 'neutral',\n    },\n  }\n})\n"
  },
  {
    "path": "packages/template-generator/templates/frontend/nuxt/app/app.vue.hbs",
    "content": "<script setup lang=\"ts\">\n{{#if (eq api \"orpc\")}}\nimport { VueQueryDevtools } from '@tanstack/vue-query-devtools'\n{{/if}}\n</script>\n\n<template>\n    <NuxtAnnouncer />\n    <NuxtRouteAnnouncer />\n    <NuxtLoadingIndicator />\n    <UApp>\n        <NuxtLayout>\n            <NuxtPage />\n        </NuxtLayout>\n    </UApp>\n    {{#if (eq api \"orpc\")}}\n    <VueQueryDevtools />\n    {{/if}}\n</template>\n"
  },
  {
    "path": "packages/template-generator/templates/frontend/nuxt/app/assets/css/main.css",
    "content": "@import \"tailwindcss\";\n@import \"@nuxt/ui\";\n"
  },
  {
    "path": "packages/template-generator/templates/frontend/nuxt/app/components/Header.vue.hbs",
    "content": "<script setup lang=\"ts\">\nimport type { NavigationMenuItem } from '@nuxt/ui'\n{{#if (eq auth \"better-auth\")}}\nimport UserMenu from './UserMenu.vue'\n{{/if}}\n\nconst route = useRoute()\n\nconst items = computed<NavigationMenuItem[]>(() => [\n    { label: \"Home\", to: \"/\", active: route.path === \"/\" },\n    {{#if (or (eq auth \"better-auth\") (eq auth \"clerk\"))}}\n    { label: \"Dashboard\", to: \"/dashboard\", active: route.path.startsWith(\"/dashboard\") },\n    {{/if}}\n    {{#if (includes examples \"todo\")}}\n    { label: \"Todos\", to: \"/todos\", active: route.path.startsWith(\"/todos\") },\n    {{/if}}\n    {{#if (includes examples \"ai\")}}\n    { label: \"AI Chat\", to: \"/ai\", active: route.path.startsWith(\"/ai\") },\n    {{/if}}\n])\n</script>\n\n<template>\n  <UHeader>\n    <template #left>\n      <UNavigationMenu :items=\"items\" />\n    </template>\n\n    <template #right>\n      <UColorModeButton />\n      {{#if (eq auth \"better-auth\")}}\n      <UserMenu />\n      {{/if}}\n    </template>\n\n    <template #body>\n      <UNavigationMenu :items=\"items\" orientation=\"vertical\" class=\"-mx-2.5\" />\n    </template>\n  </UHeader>\n</template>\n"
  },
  {
    "path": "packages/template-generator/templates/frontend/nuxt/app/layouts/default.vue.hbs",
    "content": "<script setup></script>\n\n<template>\n  <div>\n    <Header />\n    <UMain>\n      <slot />\n    </UMain>\n  </div>\n</template>\n"
  },
  {
    "path": "packages/template-generator/templates/frontend/nuxt/app/pages/index.vue.hbs",
    "content": "<script setup lang=\"ts\">\n{{#if (eq backend \"convex\")}}\nimport { api } from \"@{{ projectName }}/backend/convex/_generated/api\";\nimport { useConvexQuery } from \"convex-vue\";\n{{else}}\n  {{#unless (eq api \"none\")}}\nconst { $orpc } = useNuxtApp()\nimport { useQuery } from '@tanstack/vue-query'\n  {{/unless}}\n{{/if}}\n\nconst TITLE_TEXT = `\n ██████╗ ███████╗████████╗████████╗███████╗██████╗\n ██╔══██╗██╔════╝╚══██╔══╝╚══██╔══╝██╔════╝██╔══██╗\n ██████╔╝█████╗     ██║      ██║   █████╗  ██████╔╝\n ██╔══██╗██╔══╝     ██║      ██║   ██╔══╝  ██╔══██╗\n ██████╔╝███████╗   ██║      ██║   ███████╗██║  ██║\n ╚═════╝ ╚══════╝   ╚═╝      ╚═╝   ╚══════╝╚═╝  ╚═╝\n\n ████████╗    ███████╗████████╗ █████╗  ██████╗██╗  ██╗\n ╚══██╔══╝    ██╔════╝╚══██╔══╝██╔══██╗██╔════╝██║ ██╔╝\n    ██║       ███████╗   ██║   ███████║██║     █████╔╝\n    ██║       ╚════██║   ██║   ██╔══██║██║     ██╔═██╗\n    ██║       ███████║   ██║   ██║  ██║╚██████╗██║  ██╗\n    ╚═╝       ╚══════╝   ╚═╝   ╚═╝  ╚═╝ ╚═════╝╚═╝  ╚═╝\n `;\n\n{{#if (eq backend \"convex\")}}\nconst healthCheck = useConvexQuery(api.healthCheck.get, {});\n{{else}}\n  {{#unless (eq api \"none\")}}\nconst healthCheck = useQuery($orpc.healthCheck.queryOptions())\n\nonServerPrefetch(async () => {\n  try {\n    await healthCheck.suspense()\n  } catch {}\n})\n  {{/unless}}\n{{/if}}\n</script>\n\n<template>\n  <UContainer class=\"py-8\">\n    <pre class=\"overflow-x-auto font-mono text-sm whitespace-pre-wrap\">\\{{ TITLE_TEXT }}</pre>\n\n    <div class=\"grid gap-6 mt-6\">\n      <UCard>\n        <template #header>\n          <div class=\"font-medium\">API Status</div>\n        </template>\n\n        {{#if (eq backend \"convex\")}}\n        <div class=\"flex items-center gap-2\">\n          <UIcon\n            :name=\"healthCheck === undefined ? 'i-lucide-loader-2' : healthCheck.data.value === 'OK' ? 'i-lucide-check-circle' : 'i-lucide-x-circle'\"\n            :class=\"[\n              healthCheck === undefined ? 'animate-spin text-muted' : '',\n              healthCheck?.data.value === 'OK' ? 'text-success' : 'text-error'\n            ]\"\n          />\n          <span class=\"text-sm\">\n            \\{{\n              healthCheck === undefined\n                ? \"Checking...\"\n                : healthCheck.data.value === \"OK\"\n                  ? \"Connected\"\n                  : \"Error\"\n            }}\n          </span>\n        </div>\n        {{else}}\n        {{#unless (eq api \"none\")}}\n        <div class=\"flex items-center gap-2\">\n          <UIcon\n            :name=\"healthCheck.isLoading.value ? 'i-lucide-loader-2' : healthCheck.isSuccess.value ? 'i-lucide-check-circle' : 'i-lucide-x-circle'\"\n            :class=\"[\n              healthCheck.isLoading.value ? 'animate-spin text-muted' : '',\n              healthCheck.isSuccess.value ? 'text-success' : '',\n              healthCheck.isError.value ? 'text-error' : ''\n            ]\"\n          />\n          <span class=\"text-sm\">\n            <template v-if=\"healthCheck.isLoading.value\">\n              Checking...\n            </template>\n            <template v-else-if=\"healthCheck.isSuccess.value\">\n              Connected (\\{{ healthCheck.data.value }})\n            </template>\n            <template v-else-if=\"healthCheck.isError.value\">\n              Error: \\{{ healthCheck.error.value?.message || 'Failed to connect' }}\n            </template>\n            <template v-else>\n              Idle\n            </template>\n          </span>\n        </div>\n        {{/unless}}\n        {{/if}}\n      </UCard>\n    </div>\n  </UContainer>\n</template>\n"
  },
  {
    "path": "packages/template-generator/templates/frontend/nuxt/nuxt.config.ts.hbs",
    "content": "import \"@{{projectName}}/env/web\";\n\n// https://nuxt.com/docs/api/configuration/nuxt-config\nexport default defineNuxtConfig({\n  compatibilityDate: 'latest',\n  devtools: { enabled: true },\n  experimental: {\n    payloadExtraction: 'client',\n  },\n  modules: [\n    '@nuxt/ui'\n    {{#if (eq backend \"convex\")}},\n    'convex-nuxt'\n    {{/if}}\n  ],\n  css: ['~/assets/css/main.css'],\n  devServer: {\n    port: 3001\n  },\n  {{#if (eq backend \"convex\")}}\n  convex: {\n    url: process.env.NUXT_PUBLIC_CONVEX_URL,\n  },\n  {{else if (and (ne backend \"self\") (ne backend \"none\"))}}\n  runtimeConfig: {\n    public: {\n      serverUrl: process.env.NUXT_PUBLIC_SERVER_URL ?? \"\",\n    }\n  },\n  {{/if}}\n})\n"
  },
  {
    "path": "packages/template-generator/templates/frontend/nuxt/package.json.hbs",
    "content": "{\n  \"name\": \"web\",\n  \"private\": true,\n  \"type\": \"module\",\n  \"scripts\": {\n    \"build\": \"nuxt build\",\n    \"dev\": \"nuxt dev\",\n    \"generate\": \"nuxt generate\",\n    \"preview\": \"nuxt preview\",\n    \"postinstall\": \"nuxt prepare\"\n  },\n  \"dependencies\": {\n    \"@nuxt/ui\": \"^4.5.1\",\n    \"nuxt\": \"^4.4.4\"\n  },\n  \"devDependencies\": {\n    \"tailwindcss\": \"^4.2.1\",\n    \"@iconify-json/lucide\": \"^1.2.96\"\n  }\n}\n"
  },
  {
    "path": "packages/template-generator/templates/frontend/nuxt/public/robots.txt",
    "content": "User-Agent: *\nDisallow:\n"
  },
  {
    "path": "packages/template-generator/templates/frontend/nuxt/server/tsconfig.json",
    "content": "{\n  \"extends\": \"../.nuxt/tsconfig.server.json\"\n}\n"
  },
  {
    "path": "packages/template-generator/templates/frontend/nuxt/tsconfig.json.hbs",
    "content": "{\n  // https://nuxt.com/docs/guide/concepts/typescript\n  \"files\": [],\n  \"references\": [\n    {\n      \"path\": \"./.nuxt/tsconfig.app.json\"\n    },\n    {\n      \"path\": \"./.nuxt/tsconfig.server.json\"\n    },\n    {\n      \"path\": \"./.nuxt/tsconfig.shared.json\"\n    },\n    {\n      \"path\": \"./.nuxt/tsconfig.node.json\"\n    }\n  ]\n}\n"
  },
  {
    "path": "packages/template-generator/templates/frontend/react/next/next-env.d.ts.hbs",
    "content": "/// <reference types=\"next\" />\n/// <reference types=\"next/image-types/global\" />\n\n// NOTE: This file should not be edited\n// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.\n"
  },
  {
    "path": "packages/template-generator/templates/frontend/react/next/next.config.ts.hbs",
    "content": "import \"@{{projectName}}/env/web\";\n{{#if (eq webDeploy \"cloudflare\")}}\nimport { initOpenNextCloudflareForDev } from \"@opennextjs/cloudflare\";\n{{/if}}\nimport type { NextConfig } from \"next\";\n\nconst nextConfig: NextConfig = {\n\ttypedRoutes: true,\n\treactCompiler: true,\n\t{{#if (includes examples \"ai\")}}\n\ttranspilePackages: [\"shiki\"],\n\t{{/if}}\n\t{{#if (and (eq backend \"self\") (eq dbSetup \"turso\"))}}\n\tserverExternalPackages: [\"libsql\", \"@libsql/client\"],\n\t{{/if}}\n};\n\nexport default nextConfig;\n\n{{#if (eq webDeploy \"cloudflare\")}}\ninitOpenNextCloudflareForDev();\n{{/if}}\n"
  },
  {
    "path": "packages/template-generator/templates/frontend/react/next/package.json.hbs",
    "content": "{\n  \"name\": \"web\",\n  \"version\": \"0.1.0\",\n  \"private\": true,\n  \"scripts\": {\n    \"dev\": \"next dev --port 3001\",\n    \"build\": \"next build\",\n    \"start\": \"next start\"\n  },\n  \"dependencies\": {\n    \"@{{projectName}}/ui\": \"{{#if (eq packageManager \"npm\")}}*{{else}}workspace:*{{/if}}\",\n    \"lucide-react\": \"^0.546.0\",\n    \"next\": \"^16.2.0\",\n    \"next-themes\": \"^0.4.6\",\n    \"react\": \"^19.2.3\",\n    \"react-dom\": \"^19.2.3\",\n    \"sonner\": \"^2.0.5\",\n    \"babel-plugin-react-compiler\": \"^1.0.0\"\n  },\n  \"devDependencies\": {\n    \"@tailwindcss/postcss\": \"^4.1.18\",\n    \"@types/node\": \"^20\",\n    \"@types/react\": \"^19.2.10\",\n    \"@types/react-dom\": \"^19.2.3\",\n    \"tailwindcss\": \"^4.1.18\"\n  }\n}\n"
  },
  {
    "path": "packages/template-generator/templates/frontend/react/next/postcss.config.mjs.hbs",
    "content": "const config = {\n  plugins: [\"@tailwindcss/postcss\"],\n};\n\nexport default config;\n"
  },
  {
    "path": "packages/template-generator/templates/frontend/react/next/src/app/layout.tsx.hbs",
    "content": "import type { Metadata } from \"next\";\nimport { Geist, Geist_Mono } from \"next/font/google\";\nimport \"../index.css\";\n{{#if (eq auth \"clerk\")}}import { ClerkProvider } from \"@clerk/nextjs\";\n{{/if}}{{#if (and (eq backend \"convex\") (eq auth \"better-auth\"))}}\nimport { getToken } from \"@/lib/auth-server\";\n{{/if}}\nimport Providers from \"@/components/providers\";\nimport Header from \"@/components/header\";\n\nconst geistSans = Geist({\n  variable: \"--font-geist-sans\",\n  subsets: [\"latin\"],\n});\n\nconst geistMono = Geist_Mono({\n  variable: \"--font-geist-mono\",\n  subsets: [\"latin\"],\n});\n\nexport const metadata: Metadata = {\n  title: \"{{projectName}}\",\n  description: \"{{projectName}}\",\n};\n\n{{#if (and (eq backend \"convex\") (eq auth \"better-auth\"))}}\nexport default async function RootLayout({\n  children,\n}: Readonly<{\n  children: React.ReactNode;\n}>) {\n  const token = await getToken();\n  return (\n    <html lang=\"en\" suppressHydrationWarning>\n      <body\n        className={`${geistSans.variable} ${geistMono.variable} antialiased`}\n      >\n        <Providers initialToken={token}>\n          <div className=\"grid grid-rows-[auto_1fr] h-svh\">\n            <Header />\n            {children}\n          </div>\n        </Providers>\n      </body>\n    </html>\n  );\n}\n{{else}}\nexport default function RootLayout({\n  children,\n}: Readonly<{\n  children: React.ReactNode;\n}>) {\n\treturn (\n\t\t<html lang=\"en\" suppressHydrationWarning>\n\t\t\t<body\n\t\t\t\tclassName={`${geistSans.variable} ${geistMono.variable} antialiased`}\n\t\t\t>\n\t\t\t\t{{#if (eq auth \"clerk\")}}<ClerkProvider>\n\t\t\t\t\t<Providers>\n\t\t\t\t\t\t<div className=\"grid grid-rows-[auto_1fr] h-svh\">\n\t\t\t\t\t\t\t<Header />\n\t\t\t\t\t\t\t{children}\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</Providers>\n\t\t\t\t</ClerkProvider>{{else}}<Providers>\n\t\t\t\t\t<div className=\"grid grid-rows-[auto_1fr] h-svh\">\n\t\t\t\t\t\t<Header />\n\t\t\t\t\t\t{children}\n\t\t\t\t\t</div>\n\t\t\t\t</Providers>{{/if}}\n\t\t\t</body>\n\t\t</html>\n\t);\n}\n{{/if}}\n"
  },
  {
    "path": "packages/template-generator/templates/frontend/react/next/src/app/page.tsx.hbs",
    "content": "\"use client\"\n{{#if (eq backend \"convex\")}}\nimport { useQuery } from \"convex/react\";\nimport { api } from \"@{{projectName}}/backend/convex/_generated/api\";\n{{else if (or (eq api \"orpc\") (eq api \"trpc\"))}}\nimport { useQuery } from \"@tanstack/react-query\";\n  {{#if (eq api \"orpc\")}}\nimport { orpc } from \"@/utils/orpc\";\n  {{/if}}\n  {{#if (eq api \"trpc\")}}\nimport { trpc } from \"@/utils/trpc\";\n  {{/if}}\n{{/if}}\n\nconst TITLE_TEXT = `\n ██████╗ ███████╗████████╗████████╗███████╗██████╗\n ██╔══██╗██╔════╝╚══██╔══╝╚══██╔══╝██╔════╝██╔══██╗\n ██████╔╝█████╗     ██║      ██║   █████╗  ██████╔╝\n ██╔══██╗██╔══╝     ██║      ██║   ██╔══╝  ██╔══██╗\n ██████╔╝███████╗   ██║      ██║   ███████╗██║  ██║\n ╚═════╝ ╚══════╝   ╚═╝      ╚═╝   ╚══════╝╚═╝  ╚═╝\n\n ████████╗    ███████╗████████╗ █████╗  ██████╗██╗  ██╗\n ╚══██╔══╝    ██╔════╝╚══██╔══╝██╔══██╗██╔════╝██║ ██╔╝\n    ██║       ███████╗   ██║   ███████║██║     █████╔╝\n    ██║       ╚════██║   ██║   ██╔══██║██║     ██╔═██╗\n    ██║       ███████║   ██║   ██║  ██║╚██████╗██║  ██╗\n    ╚═╝       ╚══════╝   ╚═╝   ╚═╝  ╚═╝ ╚═════╝╚═╝  ╚═╝\n `;\n\nexport default function Home() {\n  {{#if (eq backend \"convex\")}}\n  const healthCheck = useQuery(api.healthCheck.get);\n  {{else if (eq api \"orpc\")}}\n  const healthCheck = useQuery(orpc.healthCheck.queryOptions());\n  {{else if (eq api \"trpc\")}}\n  const healthCheck = useQuery(trpc.healthCheck.queryOptions());\n  {{/if}}\n\n  return (\n    <div className=\"container mx-auto max-w-3xl px-4 py-2\">\n      <pre className=\"overflow-x-auto font-mono text-sm\">{TITLE_TEXT}</pre>\n      <div className=\"grid gap-6\">\n        <section className=\"rounded-lg border p-4\">\n          <h2 className=\"mb-2 font-medium\">API Status</h2>\n          {{#if (eq backend \"convex\")}}\n          <div className=\"flex items-center gap-2\">\n            <div\n              className={`h-2 w-2 rounded-full ${healthCheck === \"OK\" ? \"bg-green-500\" : healthCheck === undefined ? \"bg-orange-400\" : \"bg-red-500\"}`}\n            />\n            <span className=\"text-sm text-muted-foreground\">\n              {healthCheck === undefined\n                ? \"Checking...\"\n                : healthCheck === \"OK\"\n                  ? \"Connected\"\n                  : \"Error\"}\n            </span>\n          </div>\n          {{else}}\n            {{#unless (eq api \"none\")}}\n            <div className=\"flex items-center gap-2\">\n              <div\n                className={`h-2 w-2 rounded-full ${healthCheck.data ? \"bg-green-500\" : \"bg-red-500\"}`}\n              />\n              <span className=\"text-sm text-muted-foreground\">\n                {healthCheck.isLoading\n                  ? \"Checking...\"\n                  : healthCheck.data\n                    ? \"Connected\"\n                    : \"Disconnected\"}\n              </span>\n            </div>\n            {{/unless}}\n          {{/if}}\n        </section>\n      </div>\n    </div>\n  );\n}\n"
  },
  {
    "path": "packages/template-generator/templates/frontend/react/next/src/components/mode-toggle.tsx.hbs",
    "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { Moon, Sun } from \"lucide-react\"\nimport { useTheme } from \"next-themes\"\nimport { Button } from \"@{{projectName}}/ui/components/button\"\nimport {\n  DropdownMenu,\n  DropdownMenuContent,\n  DropdownMenuItem,\n  DropdownMenuTrigger,\n} from \"@{{projectName}}/ui/components/dropdown-menu\"\n\nexport function ModeToggle() {\n  const { setTheme } = useTheme()\n\n  return (\n    <DropdownMenu>\n      <DropdownMenuTrigger render={<Button variant=\"outline\" size=\"icon\" />}>\n        <Sun className=\"h-[1.2rem] w-[1.2rem] rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0\" />\n        <Moon className=\"absolute h-[1.2rem] w-[1.2rem] rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100\" />\n        <span className=\"sr-only\">Toggle theme</span>\n      </DropdownMenuTrigger>\n      <DropdownMenuContent align=\"end\">\n        <DropdownMenuItem onClick={() => setTheme(\"light\")}>\n          Light\n        </DropdownMenuItem>\n        <DropdownMenuItem onClick={() => setTheme(\"dark\")}>\n          Dark\n        </DropdownMenuItem>\n        <DropdownMenuItem onClick={() => setTheme(\"system\")}>\n          System\n        </DropdownMenuItem>\n      </DropdownMenuContent>\n    </DropdownMenu>\n  )\n}\n"
  },
  {
    "path": "packages/template-generator/templates/frontend/react/next/src/components/providers.tsx.hbs",
    "content": "\"use client\";\n\n{{#if (and (eq auth \"clerk\") (ne api \"none\"))}}\nimport { useEffect } from \"react\";\nimport { setClerkAuthTokenGetter } from \"@/utils/clerk-auth\";\n{{/if}}\n{{#if (and (eq auth \"clerk\") (or (eq backend \"convex\") (ne api \"none\")))}}\nimport { useAuth } from \"@clerk/nextjs\";\n{{/if}}\n{{#if (eq backend \"convex\")}}\n{{#if (eq auth \"clerk\")}}\nimport { ConvexReactClient } from \"convex/react\";\nimport { ConvexProviderWithClerk } from \"convex/react-clerk\";\nimport { env } from \"@{{projectName}}/env/web\";\n{{else if (eq auth \"better-auth\")}}\nimport { ConvexReactClient } from \"convex/react\";\nimport { ConvexBetterAuthProvider } from \"@convex-dev/better-auth/react\";\nimport { authClient } from \"@/lib/auth-client\";\nimport { env } from \"@{{projectName}}/env/web\";\n{{else}}\nimport { ConvexProvider, ConvexReactClient } from \"convex/react\";\nimport { env } from \"@{{projectName}}/env/web\";\n{{/if}}\n{{else}}\n{{#unless (eq api \"none\")}}\nimport { QueryClientProvider } from \"@tanstack/react-query\";\nimport { ReactQueryDevtools } from \"@tanstack/react-query-devtools\";\n{{#if (eq api \"orpc\")}}\nimport { queryClient } from \"@/utils/orpc\";\n{{/if}}\n{{#if (eq api \"trpc\")}}\nimport { queryClient } from \"@/utils/trpc\";\n{{/if}}\n{{/unless}}\n{{/if}}\nimport { ThemeProvider } from \"./theme-provider\";\nimport { Toaster } from \"@{{projectName}}/ui/components/sonner\";\n\n{{#if (eq backend \"convex\")}}\nconst convex = new ConvexReactClient(env.NEXT_PUBLIC_CONVEX_URL);\n{{/if}}\n\n{{#if (and (eq auth \"clerk\") (ne backend \"convex\") (ne api \"none\"))}}\nfunction ClerkApiAuthBridge() {\n  const { getToken } = useAuth();\n\n  useEffect(() => {\n    setClerkAuthTokenGetter(getToken);\n\n    return () => {\n      setClerkAuthTokenGetter(null);\n    };\n  }, [getToken]);\n\n  return null;\n}\n{{/if}}\n\nexport default function Providers({\n  children,\n{{#if (and (eq backend \"convex\") (eq auth \"better-auth\"))}}\n  initialToken,\n{{/if}}\n}: {\n  children: React.ReactNode;\n{{#if (and (eq backend \"convex\") (eq auth \"better-auth\"))}}\n  initialToken?: string | null;\n{{/if}}\n}) {\n  return (\n    <ThemeProvider\n      attribute=\"class\"\n      defaultTheme=\"system\"\n      enableSystem\n      disableTransitionOnChange\n    >\n      {{#if (eq backend \"convex\")}}\n      {{#if (eq auth \"clerk\")}}\n      <ConvexProviderWithClerk client={convex} useAuth={useAuth}>\n        {children}\n      </ConvexProviderWithClerk>\n      {{else if (eq auth \"better-auth\")}}\n      <ConvexBetterAuthProvider\n        client={convex}\n        authClient={authClient}\n        initialToken={initialToken}\n      >\n        {children}\n      </ConvexBetterAuthProvider>\n      {{else}}\n      <ConvexProvider client={convex}>{children}</ConvexProvider>\n      {{/if}}\n      {{else}}\n      {{#unless (eq api \"none\")}}\n      <QueryClientProvider client={queryClient}>\n        {{#if (eq auth \"clerk\")}}\n        <ClerkApiAuthBridge />\n        {{/if}}\n        {{#if (eq api \"orpc\")}}\n        {children}\n        {{/if}}\n        {{#if (eq api \"trpc\")}}\n        {children}\n        {{/if}}\n        <ReactQueryDevtools />\n      </QueryClientProvider>\n      {{else}}\n      {children}\n      {{/unless}}\n      {{/if}}\n      <Toaster richColors />\n    </ThemeProvider>\n  );\n}\n"
  },
  {
    "path": "packages/template-generator/templates/frontend/react/next/src/components/theme-provider.tsx.hbs",
    "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { ThemeProvider as NextThemesProvider } from \"next-themes\"\n\nexport function ThemeProvider({\n  children,\n  ...props\n}: React.ComponentProps<typeof NextThemesProvider>) {\n  return <NextThemesProvider {...props}>{children}</NextThemesProvider>\n}\n"
  },
  {
    "path": "packages/template-generator/templates/frontend/react/next/tsconfig.json.hbs",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ES2017\",\n    \"lib\": [\"dom\", \"dom.iterable\", \"esnext\"],\n    \"allowJs\": true,\n    \"skipLibCheck\": true,\n    \"strict\": true,\n    \"noEmit\": true,\n    \"esModuleInterop\": true,\n    \"module\": \"esnext\",\n    \"moduleResolution\": \"bundler\",\n    \"resolveJsonModule\": true,\n    \"isolatedModules\": true,\n    \"verbatimModuleSyntax\": true,\n    \"jsx\": \"react-jsx\",\n    \"incremental\": true,\n    \"plugins\": [\n      {\n        \"name\": \"next\"\n      }\n    ],\n    \"paths\": {\n      \"@/*\": [\"./src/*\"],\n      \"@{{projectName}}/ui/*\": [\"../../packages/ui/src/*\"]\n    }{{#if (or (eq serverDeploy \"cloudflare\") (eq webDeploy \"cloudflare\"))}},\n    \"types\": [\n      \"@cloudflare/workers-types\"\n    ]{{/if}}\n  },\n  \"include\": [\n    {{#if (eq serverDeploy \"cloudflare\")}}\n    \"../server/env.d.ts\",\n    {{/if}}\n    \"next-env.d.ts\",\n    \"**/*.ts\",\n    \"**/*.tsx\",\n    \".next/types/**/*.ts\",\n    \".next/dev/types/**/*.ts\"\n  ],\n  \"exclude\": [\n    \"./node_modules\"\n  ]\n}\n"
  },
  {
    "path": "packages/template-generator/templates/frontend/react/react-router/package.json.hbs",
    "content": "{\n  \"name\": \"web\",\n  \"private\": true,\n  \"type\": \"module\",\n  \"scripts\": {\n    \"build\": \"react-router build\",\n    \"dev\": \"react-router dev\",\n    \"start\": \"react-router-serve ./build/server/index.js\",\n    \"typecheck\": \"react-router typegen && tsc\"\n  },\n  \"dependencies\": {\n    \"@{{projectName}}/ui\": \"{{#if (eq packageManager \"npm\")}}*{{else}}workspace:*{{/if}}\",\n    \"@react-router/fs-routes\": \"^7.14.1\",\n    \"@react-router/node\": \"^7.14.1\",\n    \"@react-router/serve\": \"^7.14.1\",\n    \"isbot\": \"^5.1.39\",\n    \"lucide-react\": \"^1.8.0\",\n    \"next-themes\": \"^0.4.6\",\n    \"react\": \"^19.2.5\",\n    \"react-dom\": \"^19.2.5\",\n    \"react-router\": \"^7.14.1\",\n    \"sonner\": \"^2.0.7\"\n  },\n  \"devDependencies\": {\n    \"@react-router/dev\": \"^7.14.1\",\n    \"@tailwindcss/vite\": \"^4.2.2\",\n    \"@types/node\": \"^20\",\n    \"@types/react\": \"^19.2.14\",\n    \"@types/react-dom\": \"^19.2.3\",\n    \"react-router-devtools\": \"^1.1.0\",\n    \"tailwindcss\": \"^4.2.2\",\n    \"vite\": \"^8.0.8\",\n    \"vite-tsconfig-paths\": \"^6.1.1\"\n  }\n}\n"
  },
  {
    "path": "packages/template-generator/templates/frontend/react/react-router/react-router.config.ts",
    "content": "import type { Config } from \"@react-router/dev/config\";\n\nexport default {\n  ssr: false,\n  appDirectory: \"src\",\n  future: {\n    v8_middleware: true,\n  },\n} satisfies Config;\n"
  },
  {
    "path": "packages/template-generator/templates/frontend/react/react-router/src/components/mode-toggle.tsx.hbs",
    "content": "import { Moon, Sun } from \"lucide-react\";\n\nimport { Button } from \"@{{projectName}}/ui/components/button\";\nimport {\n  DropdownMenu,\n  DropdownMenuContent,\n  DropdownMenuItem,\n  DropdownMenuTrigger,\n} from \"@{{projectName}}/ui/components/dropdown-menu\";\nimport { useTheme } from \"@/components/theme-provider\";\n\nexport function ModeToggle() {\n  const { setTheme } = useTheme();\n\n  return (\n    <DropdownMenu>\n      <DropdownMenuTrigger render={<Button variant=\"outline\" size=\"icon\" />}>\n        <Sun className=\"h-[1.2rem] w-[1.2rem] scale-100 rotate-0 transition-all dark:scale-0 dark:-rotate-90\" />\n        <Moon className=\"absolute h-[1.2rem] w-[1.2rem] scale-0 rotate-90 transition-all dark:scale-100 dark:rotate-0\" />\n        <span className=\"sr-only\">Toggle theme</span>\n      </DropdownMenuTrigger>\n      <DropdownMenuContent align=\"end\">\n        <DropdownMenuItem onClick={() => setTheme(\"light\")}>Light</DropdownMenuItem>\n        <DropdownMenuItem onClick={() => setTheme(\"dark\")}>Dark</DropdownMenuItem>\n        <DropdownMenuItem onClick={() => setTheme(\"system\")}>System</DropdownMenuItem>\n      </DropdownMenuContent>\n    </DropdownMenu>\n  );\n}\n"
  },
  {
    "path": "packages/template-generator/templates/frontend/react/react-router/src/components/theme-provider.tsx.hbs",
    "content": "import * as React from \"react\";\nimport { ThemeProvider as NextThemesProvider } from \"next-themes\";\n\nexport function ThemeProvider({\n  children,\n  ...props\n}: React.ComponentProps<typeof NextThemesProvider>) {\n  return <NextThemesProvider {...props}>{children}</NextThemesProvider>;\n}\n\nexport { useTheme } from \"next-themes\";\n"
  },
  {
    "path": "packages/template-generator/templates/frontend/react/react-router/src/root.tsx.hbs",
    "content": "import {\n  isRouteErrorResponse,\n  Links,\n  Meta,\n  Outlet,\n  Scripts,\n  ScrollRestoration,\n} from \"react-router\";\nimport type { Route } from \"./+types/root\";\nimport \"./index.css\";\nimport Header from \"./components/header\";\nimport { ThemeProvider } from \"./components/theme-provider\";\nimport { Toaster } from \"@{{projectName}}/ui/components/sonner\";\n{{#if (eq auth \"clerk\")}}\nimport { ClerkProvider{{#if (or (eq backend \"convex\") (ne api \"none\"))}}, useAuth{{/if}} } from \"@clerk/react-router\";\nimport { clerkMiddleware, rootAuthLoader } from \"@clerk/react-router/server\";\n{{/if}}\n{{#if (and (eq auth \"clerk\") (ne backend \"convex\") (ne api \"none\"))}}\nimport { useEffect } from \"react\";\nimport { setClerkAuthTokenGetter } from \"@/utils/clerk-auth\";\n{{/if}}\n\n{{#if (eq backend \"convex\")}}\nimport { ConvexReactClient } from \"convex/react\";\nimport { env } from \"@{{projectName}}/env/web\";\n  {{#if (eq auth \"clerk\")}}\nimport { ConvexProviderWithClerk } from \"convex/react-clerk\";\n  {{else if (eq auth \"better-auth\")}}\nimport { ConvexBetterAuthProvider } from \"@convex-dev/better-auth/react\";\nimport { authClient } from \"@/lib/auth-client\";\n  {{else}}\nimport { ConvexProvider } from \"convex/react\";\n  {{/if}}\n{{else}}\n  {{#unless (eq api \"none\")}}\nimport { QueryClientProvider } from \"@tanstack/react-query\";\nimport { ReactQueryDevtools } from \"@tanstack/react-query-devtools\";\n    {{#if (eq api \"orpc\")}}\nimport { queryClient } from \"./utils/orpc\";\n    {{/if}}\n    {{#if (eq api \"trpc\")}}\nimport { queryClient } from \"./utils/trpc\";\n    {{/if}}\n  {{/unless}}\n{{/if}}\n\n{{#if (eq auth \"clerk\")}}\nexport const middleware: Route.MiddlewareFunction[] = [clerkMiddleware()];\n\nexport const loader = (args: Route.LoaderArgs) => rootAuthLoader(args);\n{{/if}}\n\n{{#if (and (eq auth \"clerk\") (ne backend \"convex\") (ne api \"none\"))}}\nfunction ClerkApiAuthBridge() {\n  const { getToken } = useAuth();\n\n  useEffect(() => {\n    setClerkAuthTokenGetter(getToken);\n\n    return () => {\n      setClerkAuthTokenGetter(null);\n    };\n  }, [getToken]);\n\n  return null;\n}\n{{/if}}\n\nexport const links: Route.LinksFunction = () => [\n  { rel: \"preconnect\", href: \"https://fonts.googleapis.com\" },\n  { rel: \"preconnect\", href: \"https://fonts.gstatic.com\", crossOrigin: \"anonymous\" },\n  {\n    rel: \"stylesheet\",\n    href:\n      \"https://fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&display=swap\",\n  },\n];\n\nexport function Layout({ children }: { children: React.ReactNode }) {\n  return (\n    <html lang=\"en\">\n      <head>\n        <meta charSet=\"utf-8\" />\n        <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n        <Meta />\n        <Links />\n      </head>\n      <body>\n        {children}\n        <ScrollRestoration />\n        <Scripts />\n      </body>\n    </html>\n  );\n}\n\n{{#if (eq backend \"convex\")}}\n{{#if (eq auth \"clerk\")}}\nexport default function App({ loaderData }: Route.ComponentProps) {\n{{else if (eq auth \"better-auth\")}}\nexport default function App() {\n{{else}}\nexport default function App() {\n{{/if}}\n  {{#if (eq auth \"better-auth\")}}\n  const convex = new ConvexReactClient(env.VITE_CONVEX_URL, {\n    expectAuth: true,\n  });\n  {{else}}\n  const convex = new ConvexReactClient(env.VITE_CONVEX_URL);\n  {{/if}}\n  {{#if (eq auth \"clerk\")}}\n  return (\n    <ClerkProvider loaderData={loaderData}>\n      <ConvexProviderWithClerk client={convex} useAuth={useAuth}>\n        <ThemeProvider\n          attribute=\"class\"\n          defaultTheme=\"dark\"\n          disableTransitionOnChange\n          storageKey=\"vite-ui-theme\"\n        >\n          <div className=\"grid grid-rows-[auto_1fr] h-svh\">\n            <Header />\n            <Outlet />\n          </div>\n          <Toaster richColors />\n        </ThemeProvider>\n      </ConvexProviderWithClerk>\n    </ClerkProvider>\n  );\n  {{else if (eq auth \"better-auth\")}}\n  return (\n    <ConvexBetterAuthProvider client={convex} authClient={authClient}>\n      <ThemeProvider\n        attribute=\"class\"\n        defaultTheme=\"dark\"\n        disableTransitionOnChange\n        storageKey=\"vite-ui-theme\"\n      >\n        <div className=\"grid grid-rows-[auto_1fr] h-svh\">\n          <Header />\n          <Outlet />\n        </div>\n        <Toaster richColors />\n      </ThemeProvider>\n    </ConvexBetterAuthProvider>\n  );\n  {{else}}\n  return (\n    <ConvexProvider client={convex}>\n      <ThemeProvider\n        attribute=\"class\"\n        defaultTheme=\"dark\"\n        disableTransitionOnChange\n        storageKey=\"vite-ui-theme\"\n      >\n        <div className=\"grid grid-rows-[auto_1fr] h-svh\">\n          <Header />\n          <Outlet />\n        </div>\n        <Toaster richColors />\n      </ThemeProvider>\n    </ConvexProvider>\n  );\n  {{/if}}\n}\n{{else if (eq auth \"clerk\")}}\nexport default function App({ loaderData }: Route.ComponentProps) {\n  return (\n    <ClerkProvider loaderData={loaderData}>\n      {{#unless (eq api \"none\")}}\n      <ClerkApiAuthBridge />\n      {{/unless}}\n      {{#if (eq api \"orpc\")}}\n      <QueryClientProvider client={queryClient}>\n        <ThemeProvider\n          attribute=\"class\"\n          defaultTheme=\"dark\"\n          disableTransitionOnChange\n          storageKey=\"vite-ui-theme\"\n        >\n          <div className=\"grid grid-rows-[auto_1fr] h-svh\">\n            <Header />\n            <Outlet />\n          </div>\n          <Toaster richColors />\n        </ThemeProvider>\n        <ReactQueryDevtools position=\"bottom\" buttonPosition=\"bottom-right\" />\n      </QueryClientProvider>\n      {{else if (eq api \"trpc\")}}\n      <QueryClientProvider client={queryClient}>\n        <ThemeProvider\n          attribute=\"class\"\n          defaultTheme=\"dark\"\n          disableTransitionOnChange\n          storageKey=\"vite-ui-theme\"\n        >\n          <div className=\"grid grid-rows-[auto_1fr] h-svh\">\n            <Header />\n            <Outlet />\n          </div>\n          <Toaster richColors />\n        </ThemeProvider>\n        <ReactQueryDevtools position=\"bottom\" buttonPosition=\"bottom-right\" />\n      </QueryClientProvider>\n      {{else}}\n      <ThemeProvider\n        attribute=\"class\"\n        defaultTheme=\"dark\"\n        disableTransitionOnChange\n        storageKey=\"vite-ui-theme\"\n      >\n        <div className=\"grid grid-rows-[auto_1fr] h-svh\">\n          <Header />\n          <Outlet />\n        </div>\n        <Toaster richColors />\n      </ThemeProvider>\n      {{/if}}\n    </ClerkProvider>\n  );\n}\n{{else if (eq api \"orpc\")}}\nexport default function App() {\n  return (\n    <QueryClientProvider client={queryClient}>\n      <ThemeProvider\n        attribute=\"class\"\n        defaultTheme=\"dark\"\n        disableTransitionOnChange\n        storageKey=\"vite-ui-theme\"\n      >\n        <div className=\"grid grid-rows-[auto_1fr] h-svh\">\n          <Header />\n          <Outlet />\n        </div>\n        <Toaster richColors />\n      </ThemeProvider>\n      <ReactQueryDevtools position=\"bottom\" buttonPosition=\"bottom-right\" />\n    </QueryClientProvider>\n  );\n}\n{{else if (eq api \"trpc\")}}\nexport default function App() {\n  return (\n    <QueryClientProvider client={queryClient}>\n      <ThemeProvider\n        attribute=\"class\"\n        defaultTheme=\"dark\"\n        disableTransitionOnChange\n        storageKey=\"vite-ui-theme\"\n      >\n        <div className=\"grid grid-rows-[auto_1fr] h-svh\">\n          <Header />\n          <Outlet />\n        </div>\n        <Toaster richColors />\n      </ThemeProvider>\n      <ReactQueryDevtools position=\"bottom\" buttonPosition=\"bottom-right\" />\n    </QueryClientProvider>\n  );\n}\n{{else}}\nexport default function App() {\n  return (\n    <ThemeProvider\n      attribute=\"class\"\n      defaultTheme=\"dark\"\n      disableTransitionOnChange\n      storageKey=\"vite-ui-theme\"\n    >\n      <div className=\"grid grid-rows-[auto_1fr] h-svh\">\n        <Header />\n        <Outlet />\n      </div>\n      <Toaster richColors />\n    </ThemeProvider>\n  );\n}\n{{/if}}\n\nexport function ErrorBoundary({ error }: Route.ErrorBoundaryProps) {\n  let message = \"Oops!\";\n  let details = \"An unexpected error occurred.\";\n  let stack: string | undefined;\n  if (isRouteErrorResponse(error)) {\n    message = error.status === 404 ? \"404\" : \"Error\";\n    details =\n      error.status === 404\n        ? \"The requested page could not be found.\"\n        : error.statusText || details;\n  } else if (import.meta.env.DEV && error && error instanceof Error) {\n    details = error.message;\n    stack = error.stack;\n  }\n  return (\n    <main className=\"pt-16 p-4 container mx-auto\">\n      <h1>{message}</h1>\n      <p>{details}</p>\n      {stack && (\n        <pre className=\"w-full p-4 overflow-x-auto\">\n          <code>{stack}</code>\n        </pre>\n      )}\n    </main>\n  );\n}\n"
  },
  {
    "path": "packages/template-generator/templates/frontend/react/react-router/src/routes/_index.tsx.hbs",
    "content": "import type { Route } from \"./+types/_index\";\n{{#if (eq backend \"convex\")}}\nimport { useQuery } from \"convex/react\";\nimport { api } from \"@{{projectName}}/backend/convex/_generated/api\";\n{{else if (or (eq api \"orpc\") (eq api \"trpc\"))}}\nimport { useQuery } from \"@tanstack/react-query\";\n  {{#if (eq api \"orpc\")}}\n  import { orpc } from \"@/utils/orpc\";\n  {{/if}}\n  {{#if (eq api \"trpc\")}}\n  import { trpc } from \"@/utils/trpc\";\n  {{/if}}\n{{/if}}\n\nconst TITLE_TEXT = `\n ██████╗ ███████╗████████╗████████╗███████╗██████╗\n ██╔══██╗██╔════╝╚══██╔══╝╚══██╔══╝██╔════╝██╔══██╗\n ██████╔╝█████╗     ██║      ██║   █████╗  ██████╔╝\n ██╔══██╗██╔══╝     ██║      ██║   ██╔══╝  ██╔══██╗\n ██████╔╝███████╗   ██║      ██║   ███████╗██║  ██║\n ╚═════╝ ╚══════╝   ╚═╝      ╚═╝   ╚══════╝╚═╝  ╚═╝\n\n ████████╗    ███████╗████████╗ █████╗  ██████╗██╗  ██╗\n ╚══██╔══╝    ██╔════╝╚══██╔══╝██╔══██╗██╔════╝██║ ██╔╝\n    ██║       ███████╗   ██║   ███████║██║     █████╔╝\n    ██║       ╚════██║   ██║   ██╔══██║██║     ██╔═██╗\n    ██║       ███████║   ██║   ██║  ██║╚██████╗██║  ██╗\n    ╚═╝       ╚══════╝   ╚═╝   ╚═╝  ╚═╝ ╚═════╝╚═╝  ╚═╝\n `;\n\nexport function meta({}: Route.MetaArgs) {\n  return [{ title: \"{{projectName}}\" }, { name: \"description\", content: \"{{projectName}} is a web application\" }];\n}\n\nexport default function Home() {\n  {{#if (eq backend \"convex\")}}\n  const healthCheck = useQuery(api.healthCheck.get);\n  {{else if (eq api \"orpc\")}}\n  const healthCheck = useQuery(orpc.healthCheck.queryOptions());\n  {{else if (eq api \"trpc\")}}\n  const healthCheck = useQuery(trpc.healthCheck.queryOptions());\n  {{/if}}\n\n  return (\n    <div className=\"container mx-auto max-w-3xl px-4 py-2\">\n      <pre className=\"overflow-x-auto font-mono text-sm\">{TITLE_TEXT}</pre>\n      <div className=\"grid gap-6\">\n        <section className=\"rounded-lg border p-4\">\n          <h2 className=\"mb-2 font-medium\">API Status</h2>\n          {{#if (eq backend \"convex\")}}\n          <div className=\"flex items-center gap-2\">\n            <div\n              className={`h-2 w-2 rounded-full ${healthCheck === \"OK\" ? \"bg-green-500\" : healthCheck === undefined ? \"bg-orange-400\" : \"bg-red-500\"}`}\n            />\n            <span className=\"text-sm text-muted-foreground\">\n              {healthCheck === undefined\n                ? \"Checking...\"\n                : healthCheck === \"OK\"\n                  ? \"Connected\"\n                  : \"Error\"}\n            </span>\n          </div>\n          {{else}}\n            {{#unless (eq api \"none\")}}\n            <div className=\"flex items-center gap-2\">\n              <div\n                className={`h-2 w-2 rounded-full ${\n                  healthCheck.data ? \"bg-green-500\" : \"bg-red-500\"\n                }`}\n              />\n              <span className=\"text-sm text-muted-foreground\">\n                {healthCheck.isLoading\n                  ? \"Checking...\"\n                  : healthCheck.data\n                  ? \"Connected\"\n                  : \"Disconnected\"}\n              </span>\n            </div>\n            {{/unless}}\n          {{/if}}\n        </section>\n      </div>\n    </div>\n  );\n}\n"
  },
  {
    "path": "packages/template-generator/templates/frontend/react/react-router/src/routes.ts",
    "content": "import { type RouteConfig } from \"@react-router/dev/routes\";\nimport { flatRoutes } from \"@react-router/fs-routes\";\n\nexport default flatRoutes() satisfies RouteConfig;\n"
  },
  {
    "path": "packages/template-generator/templates/frontend/react/react-router/tsconfig.json.hbs",
    "content": "{\n  \"include\": [\n    \"**/*\",\n    \"**/.server/**/*\",\n    \"**/.client/**/*\",\n    \".react-router/types/**/*\"\n  ],\n  \"compilerOptions\": {\n    \"lib\": [\"DOM\", \"DOM.Iterable\", \"ES2022\"],\n    \"types\": [\"node\", \"vite/client\"],\n    \"target\": \"ES2022\",\n    \"module\": \"ES2022\",\n    \"moduleResolution\": \"bundler\",\n    \"jsx\": \"react-jsx\",\n    \"rootDirs\": [\".\", \"./.react-router/types\"],\n    \"paths\": {\n      \"@/*\": [\"./src/*\"],\n      \"@{{projectName}}/ui/*\": [\"../../packages/ui/src/*\"]\n    },\n    \"esModuleInterop\": true,\n    \"verbatimModuleSyntax\": true,\n    \"noEmit\": true,\n    \"resolveJsonModule\": true,\n    \"skipLibCheck\": true,\n    \"strict\": true\n  }\n}\n"
  },
  {
    "path": "packages/template-generator/templates/frontend/react/react-router/vite.config.ts.hbs",
    "content": "import { reactRouter } from \"@react-router/dev/vite\";\nimport tailwindcss from \"@tailwindcss/vite\";\nimport { defineConfig } from \"vite\";\nimport tsconfigPaths from \"vite-tsconfig-paths\";\n\nexport default defineConfig({\n  plugins: [\n    tailwindcss(),\n    reactRouter(),\n    tsconfigPaths(),\n  ],\n});"
  },
  {
    "path": "packages/template-generator/templates/frontend/react/tanstack-router/index.html.hbs",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <title>{{projectName}}</title>\n  </head>\n\n  <body>\n    <div id=\"app\"></div>\n    <script type=\"module\" src=\"/src/main.tsx\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "packages/template-generator/templates/frontend/react/tanstack-router/package.json.hbs",
    "content": "{\n\t\"name\": \"web\",\n\t\"version\": \"0.0.0\",\n\t\"private\": true,\n\t\"type\": \"module\",\n\t\"scripts\": {\n\t\t\"dev\": \"vite dev\",\n\t\t\"build\": \"vite build\",\n\t\t\"serve\": \"vite preview\",\n\t\t\"start\": \"vite\",\n\t\t\"check-types\": \"vite build && tsc --noEmit\"\n\t},\n\t\"dependencies\": {\n        \"@hookform/resolvers\": \"^5.2.2\",\n        \"@{{projectName}}/ui\": \"{{#if (eq packageManager \"npm\")}}*{{else}}workspace:*{{/if}}\",\n\t\t\"@tailwindcss/vite\": \"^4.2.2\",\n\t\t\"@tanstack/react-router\": \"^1.168.22\",\n\t\t\"lucide-react\": \"^1.8.0\",\n        \"next-themes\": \"^0.4.6\",\n\t\t\"react\": \"^19.2.5\",\n\t\t\"react-dom\": \"^19.2.5\",\n        \"sonner\": \"^2.0.7\"\n\t},\n\t\"devDependencies\": {\n\t\t\"@tanstack/react-router-devtools\": \"^1.166.13\",\n\t\t\"@tanstack/router-plugin\": \"^1.167.22\",\n\t\t\"@types/node\": \"^22.13.14\",\n\t\t\"@types/react\": \"^19.2.14\",\n\t\t\"@types/react-dom\": \"^19.2.3\",\n\t\t\"@vitejs/plugin-react\": \"^6.0.1\",\n\t\t\"postcss\": \"^8.5.10\",\n\t\t\"tailwindcss\": \"^4.2.2\",\n\t\t\"vite\": \"^8.0.8\"\n\t}\n}\n"
  },
  {
    "path": "packages/template-generator/templates/frontend/react/tanstack-router/src/components/mode-toggle.tsx.hbs",
    "content": "import { Moon, Sun } from \"lucide-react\";\n\nimport { Button } from \"@{{projectName}}/ui/components/button\";\nimport {\n  DropdownMenu,\n  DropdownMenuContent,\n  DropdownMenuItem,\n  DropdownMenuTrigger,\n} from \"@{{projectName}}/ui/components/dropdown-menu\";\nimport { useTheme } from \"@/components/theme-provider\";\n\nexport function ModeToggle() {\n  const { setTheme } = useTheme();\n\n  return (\n    <DropdownMenu>\n      <DropdownMenuTrigger render={<Button variant=\"outline\" size=\"icon\" />}>\n        <Sun className=\"h-[1.2rem] w-[1.2rem] scale-100 rotate-0 transition-all dark:scale-0 dark:-rotate-90\" />\n        <Moon className=\"absolute h-[1.2rem] w-[1.2rem] scale-0 rotate-90 transition-all dark:scale-100 dark:rotate-0\" />\n        <span className=\"sr-only\">Toggle theme</span>\n      </DropdownMenuTrigger>\n      <DropdownMenuContent align=\"end\">\n        <DropdownMenuItem onClick={() => setTheme(\"light\")}>Light</DropdownMenuItem>\n        <DropdownMenuItem onClick={() => setTheme(\"dark\")}>Dark</DropdownMenuItem>\n        <DropdownMenuItem onClick={() => setTheme(\"system\")}>System</DropdownMenuItem>\n      </DropdownMenuContent>\n    </DropdownMenu>\n  );\n}\n"
  },
  {
    "path": "packages/template-generator/templates/frontend/react/tanstack-router/src/components/theme-provider.tsx.hbs",
    "content": "import * as React from \"react\";\nimport { ThemeProvider as NextThemesProvider } from \"next-themes\";\n\nexport function ThemeProvider({\n  children,\n  ...props\n}: React.ComponentProps<typeof NextThemesProvider>) {\n  return <NextThemesProvider {...props}>{children}</NextThemesProvider>;\n}\n\nexport { useTheme } from \"next-themes\";\n"
  },
  {
    "path": "packages/template-generator/templates/frontend/react/tanstack-router/src/main.tsx.hbs",
    "content": "import { RouterProvider, createRouter } from \"@tanstack/react-router\";\nimport ReactDOM from \"react-dom/client\";\n{{#if (and (eq auth \"clerk\") (ne backend \"convex\") (ne api \"none\"))}}\nimport { useEffect } from \"react\";\nimport { setClerkAuthTokenGetter } from \"@/utils/clerk-auth\";\n{{/if}}\nimport Loader from \"./components/loader\";\nimport { routeTree } from \"./routeTree.gen\";\n\n{{#if (eq api \"orpc\")}}\n  import { QueryClientProvider } from \"@tanstack/react-query\";\n  import { orpc, queryClient } from \"./utils/orpc\";\n{{/if}}\n{{#if (eq api \"trpc\")}}\n  import { QueryClientProvider } from \"@tanstack/react-query\";\n  import { queryClient, trpc } from \"./utils/trpc\";\n{{/if}}\n{{#if (or (eq backend \"convex\") (eq auth \"clerk\"))}}\n  import { env } from \"@{{projectName}}/env/web\";\n{{/if}}\n{{#if (eq auth \"clerk\")}}\n  import { ClerkProvider{{#if (or (eq backend \"convex\") (ne api \"none\"))}}, useAuth{{/if}} } from \"@clerk/react\";\n{{/if}}\n{{#if (eq backend \"convex\")}}\n  import { ConvexReactClient } from \"convex/react\";\n  {{#if (eq auth \"clerk\")}}\n  import { ConvexProviderWithClerk } from \"convex/react-clerk\";\n  {{else if (eq auth \"better-auth\")}}\n  import { ConvexBetterAuthProvider } from \"@convex-dev/better-auth/react\";\n  import { authClient } from \"@/lib/auth-client\";\n  {{else}}\n  import { ConvexProvider } from \"convex/react\";\n  {{/if}}\n  {{#if (eq auth \"better-auth\")}}\n  const convex = new ConvexReactClient(env.VITE_CONVEX_URL, {\n    expectAuth: true,\n  });\n  {{else}}\n  const convex = new ConvexReactClient(env.VITE_CONVEX_URL);\n  {{/if}}\n{{/if}}\n\n{{#if (and (eq auth \"clerk\") (ne backend \"convex\") (ne api \"none\"))}}\nfunction ClerkApiAuthBridge() {\n  const { getToken } = useAuth();\n\n  useEffect(() => {\n    setClerkAuthTokenGetter(getToken);\n\n    return () => {\n      setClerkAuthTokenGetter(null);\n    };\n  }, [getToken]);\n\n  return null;\n}\n{{/if}}\n\nconst router = createRouter({\n  routeTree,\n  defaultPreload: \"intent\",\n  scrollRestoration: true,\n  defaultPendingComponent: () => <Loader />,\n  {{#if (eq api \"orpc\")}}\n  context: { orpc, queryClient },\n  Wrap: function WrapComponent({ children }: { children: React.ReactNode }) {\n    return (\n      {{#if (eq auth \"clerk\")}}\n      <ClerkProvider publishableKey={env.VITE_CLERK_PUBLISHABLE_KEY}>\n        <ClerkApiAuthBridge />\n        <QueryClientProvider client={queryClient}>\n          {children}\n        </QueryClientProvider>\n      </ClerkProvider>\n      {{else}}\n      <QueryClientProvider client={queryClient}>\n        {children}\n      </QueryClientProvider>\n      {{/if}}\n    );\n  },\n  {{else if (eq api \"trpc\")}}\n  context: { trpc, queryClient },\n  Wrap: function WrapComponent({ children }: { children: React.ReactNode }) {\n    return (\n      {{#if (eq auth \"clerk\")}}\n      <ClerkProvider publishableKey={env.VITE_CLERK_PUBLISHABLE_KEY}>\n        <ClerkApiAuthBridge />\n        <QueryClientProvider client={queryClient}>\n          {children}\n        </QueryClientProvider>\n      </ClerkProvider>\n      {{else}}\n      <QueryClientProvider client={queryClient}>\n        {children}\n      </QueryClientProvider>\n      {{/if}}\n    );\n  },\n  {{else if (eq backend \"convex\")}}\n  context: {},\n  Wrap: function WrapComponent({ children }: { children: React.ReactNode }) {\n    {{#if (eq auth \"clerk\")}}\n    return (\n      <ClerkProvider\n        publishableKey={env.VITE_CLERK_PUBLISHABLE_KEY}\n      >\n        <ConvexProviderWithClerk client={convex} useAuth={useAuth}>\n          {children}\n        </ConvexProviderWithClerk>\n      </ClerkProvider>\n    );\n    {{else if (eq auth \"better-auth\")}}\n    return <ConvexBetterAuthProvider client={convex} authClient={authClient}>{children}</ConvexBetterAuthProvider>;\n    {{else}}\n    return <ConvexProvider client={convex}>{children}</ConvexProvider>;\n    {{/if}}\n  },\n  {{else if (eq auth \"clerk\")}}\n  context: {},\n  Wrap: function WrapComponent({ children }: { children: React.ReactNode }) {\n    return <ClerkProvider publishableKey={env.VITE_CLERK_PUBLISHABLE_KEY}>{children}</ClerkProvider>;\n  },\n  {{else}}\n  context: {},\n  {{/if}}\n});\n\ndeclare module \"@tanstack/react-router\" {\n  interface Register {\n    router: typeof router;\n  }\n}\n\nconst rootElement = document.getElementById(\"app\");\n\nif (!rootElement) {\n  throw new Error(\"Root element not found\");\n}\n\nif (!rootElement.innerHTML) {\n  const root = ReactDOM.createRoot(rootElement);\n  root.render(<RouterProvider router={router} />);\n}\n"
  },
  {
    "path": "packages/template-generator/templates/frontend/react/tanstack-router/src/routes/__root.tsx.hbs",
    "content": "import Header from \"@/components/header\";\nimport { ThemeProvider } from \"@/components/theme-provider\";\nimport { Toaster } from \"@{{projectName}}/ui/components/sonner\";\n{{#if (eq api \"orpc\")}}\nimport { link, orpc } from \"@/utils/orpc\";\nimport type { QueryClient } from \"@tanstack/react-query\";\nimport { ReactQueryDevtools } from \"@tanstack/react-query-devtools\";\nimport { useState } from \"react\";\nimport { createTanstackQueryUtils } from \"@orpc/tanstack-query\";\nimport type { AppRouterClient } from \"@{{projectName}}/api/routers/index\";\nimport { createORPCClient } from \"@orpc/client\";\n{{/if}}\n{{#if (eq api \"trpc\")}}\nimport type { trpc } from \"@/utils/trpc\";\nimport type { QueryClient } from \"@tanstack/react-query\";\nimport { ReactQueryDevtools } from \"@tanstack/react-query-devtools\";\n{{/if}}\nimport {\n  HeadContent,\n  Outlet,\n  createRootRouteWithContext,\n} from \"@tanstack/react-router\";\nimport { TanStackRouterDevtools } from \"@tanstack/react-router-devtools\";\nimport \"../index.css\";\n\n{{#if (eq api \"orpc\")}}\nexport interface RouterAppContext {\n  orpc: typeof orpc;\n  queryClient: QueryClient;\n}\n{{else if (eq api \"trpc\")}}\nexport interface RouterAppContext {\n  trpc: typeof trpc;\n  queryClient: QueryClient;\n}\n{{else}}\nexport interface RouterAppContext {}\n{{/if}}\n\nexport const Route = createRootRouteWithContext<RouterAppContext>()({\n  component: RootComponent,\n  head: () => ({\n    meta: [\n      {\n        title: \"{{projectName}}\",\n      },\n      {\n        name: \"description\",\n        content: \"{{projectName}} is a web application\",\n      },\n    ],\n    links: [\n      {\n        rel: \"icon\",\n        href: \"/favicon.ico\",\n      },\n    ],\n  }),\n});\n\nfunction RootComponent() {\n  {{#if (eq api \"orpc\")}}\n  const [client] = useState<AppRouterClient>(() => createORPCClient(link));\n  const [orpcUtils] = useState(() => createTanstackQueryUtils(client));\n  {{/if}}\n\n  return (\n    <>\n      <HeadContent />\n      {{#if (eq api \"orpc\")}}\n        <ThemeProvider\n          attribute=\"class\"\n          defaultTheme=\"dark\"\n          disableTransitionOnChange\n          storageKey=\"vite-ui-theme\"\n        >\n          <div className=\"grid grid-rows-[auto_1fr] h-svh\">\n            <Header />\n            <Outlet />\n          </div>\n          <Toaster richColors />\n        </ThemeProvider>\n      {{else}}\n      <ThemeProvider\n        attribute=\"class\"\n        defaultTheme=\"dark\"\n        disableTransitionOnChange\n        storageKey=\"vite-ui-theme\"\n      >\n        <div className=\"grid grid-rows-[auto_1fr] h-svh\">\n          <Header />\n          <Outlet />\n        </div>\n        <Toaster richColors />\n      </ThemeProvider>\n      {{/if}}\n      <TanStackRouterDevtools position=\"bottom-left\" />\n      {{#if (or (eq api \"orpc\") (eq api \"trpc\"))}}\n      <ReactQueryDevtools position=\"bottom\" buttonPosition=\"bottom-right\" />\n      {{/if}}\n    </>\n  );\n}\n"
  },
  {
    "path": "packages/template-generator/templates/frontend/react/tanstack-router/src/routes/index.tsx.hbs",
    "content": "import { createFileRoute } from \"@tanstack/react-router\";\n{{#if (eq api \"orpc\")}}\nimport { orpc } from \"@/utils/orpc\";\nimport { useQuery } from \"@tanstack/react-query\";\n{{/if}}\n{{#if (eq api \"trpc\")}}\nimport { trpc } from \"@/utils/trpc\";\nimport { useQuery } from \"@tanstack/react-query\";\n{{/if}}\n{{#if (eq backend \"convex\")}}\nimport { useQuery } from \"convex/react\";\nimport { api } from \"@{{ projectName }}/backend/convex/_generated/api\";\n{{/if}}\n\nexport const Route = createFileRoute(\"/\")({\n  component: HomeComponent,\n});\n\nconst TITLE_TEXT = `\n ██████╗ ███████╗████████╗████████╗███████╗██████╗\n ██╔══██╗██╔════╝╚══██╔══╝╚══██╔══╝██╔════╝██╔══██╗\n ██████╔╝█████╗     ██║      ██║   █████╗  ██████╔╝\n ██╔══██╗██╔══╝     ██║      ██║   ██╔══╝  ██╔══██╗\n ██████╔╝███████╗   ██║      ██║   ███████╗██║  ██║\n ╚═════╝ ╚══════╝   ╚═╝      ╚═╝   ╚══════╝╚═╝  ╚═╝\n\n ████████╗    ███████╗████████╗ █████╗  ██████╗██╗  ██╗\n ╚══██╔══╝    ██╔════╝╚══██╔══╝██╔══██╗██╔════╝██║ ██╔╝\n    ██║       ███████╗   ██║   ███████║██║     █████╔╝\n    ██║       ╚════██║   ██║   ██╔══██║██║     ██╔═██╗\n    ██║       ███████║   ██║   ██║  ██║╚██████╗██║  ██╗\n    ╚═╝       ╚══════╝   ╚═╝   ╚═╝  ╚═╝ ╚═════╝╚═╝  ╚═╝\n `;\n\nfunction HomeComponent() {\n  {{#if (eq api \"orpc\")}}\n  const healthCheck = useQuery(orpc.healthCheck.queryOptions());\n  {{/if}}\n  {{#if (eq api \"trpc\")}}\n  const healthCheck = useQuery(trpc.healthCheck.queryOptions());\n  {{/if}}\n  {{#if (eq backend \"convex\")}}\n  const healthCheck = useQuery(api.healthCheck.get);\n  {{/if}}\n\n  return (\n    <div className=\"container mx-auto max-w-3xl px-4 py-2\">\n      <pre className=\"overflow-x-auto font-mono text-sm\">{TITLE_TEXT}</pre>\n      <div className=\"grid gap-6\">\n        <section className=\"rounded-lg border p-4\">\n          <h2 className=\"mb-2 font-medium\">API Status</h2>\n          {{#if (eq backend \"convex\")}}\n          <div className=\"flex items-center gap-2\">\n            <div\n              className={`h-2 w-2 rounded-full ${healthCheck === \"OK\" ? \"bg-green-500\" : healthCheck === undefined ? \"bg-orange-400\" : \"bg-red-500\"}`}\n            />\n            <span className=\"text-sm text-muted-foreground\">\n              {healthCheck === undefined\n                ? \"Checking...\"\n                : healthCheck === \"OK\"\n                  ? \"Connected\"\n                  : \"Error\"}\n            </span>\n          </div>\n          {{else}}\n            {{#unless (eq api \"none\")}}\n            <div className=\"flex items-center gap-2\">\n              <div\n                className={`h-2 w-2 rounded-full ${healthCheck.data ? \"bg-green-500\" : \"bg-red-500\"}`}\n              />\n              <span className=\"text-sm text-muted-foreground\">\n                {healthCheck.isLoading\n                  ? \"Checking...\"\n                  : healthCheck.data\n                    ? \"Connected\"\n                    : \"Disconnected\"}\n              </span>\n            </div>\n            {{/unless}}\n          {{/if}}\n        </section>\n      </div>\n    </div>\n  );\n}\n"
  },
  {
    "path": "packages/template-generator/templates/frontend/react/tanstack-router/tsconfig.json.hbs",
    "content": "{\n  \"compilerOptions\": {\n    \"strict\": true,\n    \"esModuleInterop\": true,\n    \"jsx\": \"react-jsx\",\n    \"target\": \"ESNext\",\n    \"module\": \"ESNext\",\n    \"moduleResolution\": \"Bundler\",\n    \"skipLibCheck\": true,\n    \"types\": [\"vite/client\"],\n    \"rootDirs\": [\".\"],\n    \"paths\": {\n      \"@/*\": [\"./src/*\"],\n      \"@{{projectName}}/ui/*\": [\"../../packages/ui/src/*\"]\n    }\n  }\n}\n"
  },
  {
    "path": "packages/template-generator/templates/frontend/react/tanstack-router/vite.config.ts.hbs",
    "content": "import tailwindcss from \"@tailwindcss/vite\";\nimport { tanstackRouter } from \"@tanstack/router-plugin/vite\";\nimport react from \"@vitejs/plugin-react\";\nimport { defineConfig } from \"vite\";\n\nexport default defineConfig({\n  server: {\n    port: 3001,\n  },\n  resolve: {\n    tsconfigPaths: true,\n  },\n  plugins: [\n    tailwindcss(),\n    tanstackRouter({\n      target: \"react\",\n      autoCodeSplitting: true,\n    }),\n    react(),\n  ],\n});\n"
  },
  {
    "path": "packages/template-generator/templates/frontend/react/tanstack-start/package.json.hbs",
    "content": "{\n  \"name\": \"web\",\n  \"private\": true,\n  \"type\": \"module\",\n  \"scripts\": {\n    \"build\": \"vite build\",\n    \"serve\": \"vite preview\",\n    \"dev\": \"vite dev\"\n  },\n  \"dependencies\": {\n    \"@{{projectName}}/ui\": \"{{#if (eq packageManager \"npm\")}}*{{else}}workspace:*{{/if}}\",\n    \"@tailwindcss/vite\": \"^4.2.2\",\n    \"@tanstack/react-query\": \"^5.99.0\",\n    \"@tanstack/react-router\": \"^1.168.22\",\n    \"@tanstack/react-start\": \"^1.167.41\",\n    \"lucide-react\": \"^1.8.0\",\n    \"next-themes\": \"^0.4.6\",\n    \"react\": \"^19.2.5\",\n    \"react-dom\": \"^19.2.5\",\n    \"sonner\": \"^2.0.7\",\n    \"tailwindcss\": \"^4.2.2\"\n  },\n  \"devDependencies\": {\n    \"@tanstack/react-router-devtools\": \"^1.166.13\",\n    \"@testing-library/dom\": \"^10.4.1\",\n    \"@testing-library/react\": \"^16.3.2\",\n    \"@types/react\": \"^19.2.14\",\n    \"@types/react-dom\": \"^19.2.3\",\n    \"@vitejs/plugin-react\": \"^6.0.1\",\n    \"jsdom\": \"^29.0.2\",\n    \"vite\": \"^8.0.8\",\n    \"web-vitals\": \"^5.2.0\"\n  }\n}\n"
  },
  {
    "path": "packages/template-generator/templates/frontend/react/tanstack-start/public/robots.txt",
    "content": "# https://www.robotstxt.org/robotstxt.html\nUser-agent: *\nDisallow:\n"
  },
  {
    "path": "packages/template-generator/templates/frontend/react/tanstack-start/src/router.tsx.hbs",
    "content": "{{#if (eq backend \"convex\")}}\nimport { createRouter as createTanStackRouter } from \"@tanstack/react-router\";\nimport { QueryClient } from \"@tanstack/react-query\";\nimport { setupRouterSsrQueryIntegration } from \"@tanstack/react-router-ssr-query\";\nimport { ConvexQueryClient } from \"@convex-dev/react-query\";\nimport { routeTree } from \"./routeTree.gen\";\nimport Loader from \"./components/loader\";\nimport \"./index.css\";\nimport { env } from \"@{{projectName}}/env/web\";\n{{else}}\nimport { createRouter as createTanStackRouter } from \"@tanstack/react-router\";\nimport Loader from \"./components/loader\";\nimport \"./index.css\";\nimport { routeTree } from \"./routeTree.gen\";\n{{#if (eq api \"trpc\")}}\nimport { QueryCache, QueryClient } from \"@tanstack/react-query\";\nimport { setupRouterSsrQueryIntegration } from \"@tanstack/react-router-ssr-query\";\nimport { createTRPCClient, httpBatchLink } from \"@trpc/client\";\nimport { createTRPCOptionsProxy } from \"@trpc/tanstack-react-query\";\nimport { toast } from \"sonner\";\nimport type { AppRouter } from \"@{{projectName}}/api/routers/index\";\nimport { TRPCProvider } from \"./utils/trpc\";\n{{#unless (eq backend \"self\")}}\nimport { env } from \"@{{projectName}}/env/web\";\n{{/unless}}\n{{#if (eq auth \"clerk\")}}\nimport { getClerkAuthToken } from \"@/utils/clerk-auth\";\n{{/if}}\n{{else if (eq api \"orpc\")}}\nimport { setupRouterSsrQueryIntegration } from \"@tanstack/react-router-ssr-query\";\nimport { orpc, queryClient } from \"./utils/orpc\";\n{{/if}}\n{{/if}}\n\n{{#if (eq backend \"convex\")}}\nexport function getRouter() {\n\tconst convexUrl = env.VITE_CONVEX_URL;\n\tif (!convexUrl) {\n\t\tthrow new Error(\"VITE_CONVEX_URL is not set\");\n\t}\n\n\tconst convexQueryClient = new ConvexQueryClient(convexUrl);\n\n\tconst queryClient: QueryClient = new QueryClient({\n\t\tdefaultOptions: {\n\t\t\tqueries: {\n\t\t\t\tqueryKeyHashFn: convexQueryClient.hashFn(),\n\t\t\t\tqueryFn: convexQueryClient.queryFn(),\n\t\t\t},\n\t\t},\n\t});\n\tconvexQueryClient.connect(queryClient);\n\n\tconst router = createTanStackRouter({\n\t\trouteTree,\n\t\tdefaultPreload: \"intent\",\n\t\tdefaultPendingComponent: () => <Loader />,\n\t\tdefaultNotFoundComponent: () => <div>Not Found</div>,\n\t\tcontext: { queryClient, convexQueryClient },\n\t});\n\n\tsetupRouterSsrQueryIntegration({\n\t\trouter,\n\t\tqueryClient,\n\t});\n\n\treturn router;\n}\n{{else}}\n{{#if (eq api \"trpc\")}}\nexport const queryClient = new QueryClient({\n\tqueryCache: new QueryCache({\n\t\tonError: (error, query) => {\n\t\t\ttoast.error(error.message, {\n\t\t\t\taction: {\n\t\t\t\t\tlabel: \"retry\",\n\t\t\t\t\tonClick: query.invalidate,\n\t\t\t\t},\n\t\t\t});\n\t\t},\n\t}),\n\tdefaultOptions: { queries: { staleTime: 60 * 1000 } },\n});\n\nconst trpcClient = createTRPCClient<AppRouter>({\n\tlinks: [\n\t\thttpBatchLink({\n\t\t\turl: {{#if (eq backend \"self\")}}\"/api/trpc\"{{else}}`${env.VITE_SERVER_URL}/trpc`{{/if}},\n{{#if (eq auth \"clerk\")}}\n\t\t\theaders: async () => {\n\t\t\t\tconst token = await getClerkAuthToken();\n\t\t\t\treturn token ? { Authorization: `Bearer ${token}` } : {};\n\t\t\t},\n{{/if}}\n{{#if (eq auth \"better-auth\")}}\n\t\t\tfetch(url, options) {\n\t\t\t\treturn fetch(url, {\n\t\t\t\t\t...options,\n\t\t\t\t\tcredentials: \"include\",\n\t\t\t\t});\n\t\t\t},\n{{/if}}\n\t\t}),\n\t],\n});\n\nconst trpc = createTRPCOptionsProxy({\n\tclient: trpcClient,\n\tqueryClient: queryClient,\n});\n{{else if (eq api \"orpc\")}}\n{{/if}}\n\nexport const getRouter = () => {\n\tconst router = createTanStackRouter({\n\t\trouteTree,\n\t\tscrollRestoration: true,\n\t\tdefaultPreloadStaleTime: 0,\n{{#if (eq api \"trpc\")}}\n\t\tcontext: { trpc, queryClient },\n{{else if (eq api \"orpc\")}}\n\t\tcontext: { orpc, queryClient },\n{{else}}\n\t\tcontext: {},\n{{/if}}\n\t\tdefaultPendingComponent: () => <Loader />,\n\t\tdefaultNotFoundComponent: () => <div>Not Found</div>,\n{{#if (eq api \"trpc\")}}\n\t\tWrap: ({ children }) => (\n\t\t\t<TRPCProvider trpcClient={trpcClient} queryClient={queryClient}>\n\t\t\t\t{children}\n\t\t\t</TRPCProvider>\n\t\t),\n{{/if}}\n\t});\n{{#if (or (eq api \"trpc\") (eq api \"orpc\"))}}\n\n\tsetupRouterSsrQueryIntegration({\n\t\trouter,\n\t\tqueryClient,\n\t});\n{{/if}}\n\n\treturn router;\n};\n{{/if}}\n\ndeclare module \"@tanstack/react-router\" {\n\tinterface Register {\n\t\trouter: ReturnType<typeof getRouter>;\n\t}\n}\n"
  },
  {
    "path": "packages/template-generator/templates/frontend/react/tanstack-start/src/routes/__root.tsx.hbs",
    "content": "import { Toaster } from \"@{{projectName}}/ui/components/sonner\";\n{{#unless (eq backend \"convex\")}} {{#unless (eq api \"none\")}}\nimport { ReactQueryDevtools } from \"@tanstack/react-query-devtools\";\n{{/unless}} {{/unless}}\nimport {\n  HeadContent,\n  Outlet,\n  Scripts,\n  createRootRouteWithContext,\n{{#if (and (eq backend \"convex\") (or (eq auth \"clerk\") (eq auth \"better-auth\")))}}\n  useRouteContext,\n{{/if}}\n} from \"@tanstack/react-router\";\nimport { TanStackRouterDevtools } from \"@tanstack/react-router-devtools\";\nimport Header from \"../components/header\";\nimport appCss from \"../index.css?url\";\n{{#if (eq backend \"convex\")}}\nimport type { QueryClient } from \"@tanstack/react-query\";\nimport type { ConvexQueryClient } from \"@convex-dev/react-query\";\n{{else}}\n{{#if (or (eq api \"trpc\") (eq api \"orpc\"))}}\nimport type { QueryClient } from \"@tanstack/react-query\";\n{{/if}}\n{{/if}}\n\n{{#if (eq auth \"clerk\")}}\nimport { ClerkProvider{{#if (or (eq backend \"convex\") (ne api \"none\"))}}, useAuth{{/if}} } from \"@clerk/tanstack-react-start\";\n{{/if}}\n{{#if (and (eq auth \"clerk\") (ne backend \"convex\") (ne api \"none\"))}}\nimport { useEffect } from \"react\";\nimport { setClerkAuthTokenGetter } from \"@/utils/clerk-auth\";\n{{/if}}\n{{#if (and (eq backend \"convex\") (eq auth \"clerk\"))}}\nimport { auth } from \"@clerk/tanstack-react-start/server\";\nimport { createServerFn } from \"@tanstack/react-start\";\nimport { ConvexProviderWithClerk } from \"convex/react-clerk\";\n\nconst fetchClerkAuth = createServerFn({ method: \"GET\" }).handler(async () => {\n  const clerkAuth = await auth();\n  const token = await clerkAuth.getToken({ template: \"convex\" });\n  return { userId: clerkAuth.userId, token };\n});\n{{else if (and (eq backend \"convex\") (eq auth \"better-auth\"))}}\nimport { createServerFn } from \"@tanstack/react-start\";\nimport { ConvexBetterAuthProvider } from \"@convex-dev/better-auth/react\";\nimport { authClient } from \"@/lib/auth-client\";\nimport { getToken } from \"@/lib/auth-server\";\n\nconst getAuth = createServerFn({ method: \"GET\" }).handler(async () => {\n  return await getToken();\n});\n{{else if (eq backend \"convex\")}}\nimport { ConvexProvider } from \"convex/react\";\n{{/if}}\n\n{{#if (and (eq auth \"clerk\") (ne backend \"convex\") (ne api \"none\"))}}\nfunction ClerkApiAuthBridge() {\n  const { getToken } = useAuth();\n\n  useEffect(() => {\n    setClerkAuthTokenGetter(getToken);\n\n    return () => {\n      setClerkAuthTokenGetter(null);\n    };\n  }, [getToken]);\n\n  return null;\n}\n{{/if}}\n\n{{#if (eq backend \"convex\")}}\nexport interface RouterAppContext {\n  queryClient: QueryClient;\n  convexQueryClient: ConvexQueryClient;\n}\n{{else}}\n  {{#if (eq api \"trpc\")}}\nimport type { TRPCOptionsProxy } from \"@trpc/tanstack-react-query\";\nimport type { AppRouter } from \"@{{projectName}}/api/routers/index\";\nexport interface RouterAppContext {\n  trpc: TRPCOptionsProxy<AppRouter>;\n  queryClient: QueryClient;\n}\n  {{else if (eq api \"orpc\")}}\nimport type { orpc } from \"@/utils/orpc\";\nexport interface RouterAppContext {\n  orpc: typeof orpc;\n  queryClient: QueryClient;\n}\n  {{else}}\nexport interface RouterAppContext {\n}\n  {{/if}}\n{{/if}}\n\nexport const Route = createRootRouteWithContext<RouterAppContext>()({\n  head: () => ({\n    meta: [\n      {\n        charSet: \"utf-8\",\n      },\n      {\n        name: \"viewport\",\n        content: \"width=device-width, initial-scale=1\",\n      },\n      {\n        title: \"My App\",\n      },\n    ],\n    links: [\n      {\n        rel: \"stylesheet\",\n        href: appCss,\n      },\n    ],\n  }),\n\n  component: RootDocument,\n  {{#if (and (eq backend \"convex\") (eq auth \"clerk\"))}}\n  beforeLoad: async (ctx) => {\n    const { userId, token } = await fetchClerkAuth();\n    if (token) {\n      ctx.context.convexQueryClient.serverHttpClient?.setAuth(token);\n    }\n    return { userId, token };\n  },\n  {{else if (and (eq backend \"convex\") (eq auth \"better-auth\"))}}\n  beforeLoad: async (ctx) => {\n    const token = await getAuth();\n    if (token) {\n      ctx.context.convexQueryClient.serverHttpClient?.setAuth(token);\n    }\n    return {\n      isAuthenticated: !!token,\n      token,\n    };\n  },\n  {{/if}}\n});\n\nfunction RootDocument() {\n  {{#if (and (eq backend \"convex\") (eq auth \"clerk\"))}}\n  const context = useRouteContext({ from: Route.id });\n  return (\n    <ClerkProvider>\n      <ConvexProviderWithClerk client={context.convexQueryClient.convexClient} useAuth={useAuth}>\n        <html lang=\"en\" className=\"dark\">\n          <head>\n            <HeadContent />\n          </head>\n          <body>\n            <div className=\"grid h-svh grid-rows-[auto_1fr]\">\n              <Header />\n              <Outlet />\n            </div>\n            <Toaster richColors />\n            <TanStackRouterDevtools position=\"bottom-left\" />\n            <Scripts />\n          </body>\n        </html>\n      </ConvexProviderWithClerk>\n    </ClerkProvider>\n  );\n  {{else if (and (eq backend \"convex\") (eq auth \"better-auth\"))}}\n  const context = useRouteContext({ from: Route.id });\n  return (\n    <ConvexBetterAuthProvider\n      client={context.convexQueryClient.convexClient}\n      authClient={authClient}\n      initialToken={context.token}\n    >\n      <html lang=\"en\" className=\"dark\">\n        <head>\n          <HeadContent />\n        </head>\n        <body>\n          <div className=\"grid h-svh grid-rows-[auto_1fr]\">\n            <Header />\n            <Outlet />\n          </div>\n          <Toaster richColors />\n          <TanStackRouterDevtools position=\"bottom-left\" />\n          <Scripts />\n        </body>\n      </html>\n    </ConvexBetterAuthProvider>\n  );\n  {{else if (eq auth \"clerk\")}}\n  return (\n    <ClerkProvider>\n      {{#unless (eq api \"none\")}}\n      <ClerkApiAuthBridge />\n      {{/unless}}\n      <html lang=\"en\" className=\"dark\">\n        <head>\n          <HeadContent />\n        </head>\n        <body>\n          <div className=\"grid h-svh grid-rows-[auto_1fr]\">\n            <Header />\n            <Outlet />\n          </div>\n          <Toaster richColors />\n          <TanStackRouterDevtools position=\"bottom-left\" />\n          {{#unless (eq api \"none\")}}\n          <ReactQueryDevtools position=\"bottom\" buttonPosition=\"bottom-right\" />\n          {{/unless}}\n          <Scripts />\n        </body>\n      </html>\n    </ClerkProvider>\n  );\n  {{else if (eq backend \"convex\")}}\n  const { convexQueryClient } = Route.useRouteContext();\n  return (\n    <ConvexProvider client={convexQueryClient.convexClient}>\n      <html lang=\"en\" className=\"dark\">\n        <head>\n          <HeadContent />\n        </head>\n        <body>\n          <div className=\"grid h-svh grid-rows-[auto_1fr]\">\n            <Header />\n            <Outlet />\n          </div>\n          <Toaster richColors />\n          <TanStackRouterDevtools position=\"bottom-left\" />\n          <Scripts />\n        </body>\n      </html>\n    </ConvexProvider>\n  );\n  {{else}}\n  return (\n    <html lang=\"en\" className=\"dark\">\n      <head>\n        <HeadContent />\n      </head>\n      <body>\n        <div className=\"grid h-svh grid-rows-[auto_1fr]\">\n          <Header />\n          <Outlet />\n        </div>\n        <Toaster richColors />\n        <TanStackRouterDevtools position=\"bottom-left\" />\n        {{#unless (eq api \"none\")}}\n        <ReactQueryDevtools position=\"bottom\" buttonPosition=\"bottom-right\" />\n        {{/unless}}\n        <Scripts />\n      </body>\n    </html>\n  );\n  {{/if}}\n}\n"
  },
  {
    "path": "packages/template-generator/templates/frontend/react/tanstack-start/src/routes/index.tsx.hbs",
    "content": "import { createFileRoute } from \"@tanstack/react-router\";\n{{#if (eq backend \"convex\")}}\nimport { convexQuery } from \"@convex-dev/react-query\";\nimport { useQuery } from \"@tanstack/react-query\";\nimport { api } from \"@{{projectName}}/backend/convex/_generated/api\";\n{{else if (or (eq api \"trpc\") (eq api \"orpc\"))}}\nimport { useQuery } from \"@tanstack/react-query\";\n  {{#if (eq api \"trpc\")}}\nimport { useTRPC } from \"@/utils/trpc\";\n  {{/if}}\n  {{#if (eq api \"orpc\")}}\nimport { orpc } from \"@/utils/orpc\";\n  {{/if}}\n{{/if}}\n\nexport const Route = createFileRoute(\"/\")({\n  component: HomeComponent,\n});\n\nconst TITLE_TEXT = `\n ██████╗ ███████╗████████╗████████╗███████╗██████╗\n ██╔══██╗██╔════╝╚══██╔══╝╚══██╔══╝██╔════╝██╔══██╗\n ██████╔╝█████╗     ██║      ██║   █████╗  ██████╔╝\n ██╔══██╗██╔══╝     ██║      ██║   ██╔══╝  ██╔══██╗\n ██████╔╝███████╗   ██║      ██║   ███████╗██║  ██║\n ╚═════╝ ╚══════╝   ╚═╝      ╚═╝   ╚══════╝╚═╝  ╚═╝\n\n ████████╗    ███████╗████████╗ █████╗  ██████╗██╗  ██╗\n ╚══██╔══╝    ██╔════╝╚══██╔══╝██╔══██╗██╔════╝██║ ██╔╝\n    ██║       ███████╗   ██║   ███████║██║     █████╔╝\n    ██║       ╚════██║   ██║   ██╔══██║██║     ██╔═██╗\n    ██║       ███████║   ██║   ██║  ██║╚██████╗██║  ██╗\n    ╚═╝       ╚══════╝   ╚═╝   ╚═╝  ╚═╝ ╚═════╝╚═╝  ╚═╝\n `;\n\nfunction HomeComponent() {\n  {{#if (eq backend \"convex\")}}\n  const healthCheck = useQuery(convexQuery(api.healthCheck.get, {}));\n  {{else if (eq api \"trpc\")}}\n  const trpc = useTRPC();\n  const healthCheck = useQuery(trpc.healthCheck.queryOptions());\n  {{else if (eq api \"orpc\")}}\n  const healthCheck = useQuery(orpc.healthCheck.queryOptions());\n  {{/if}}\n\n  return (\n    <div className=\"container mx-auto max-w-3xl px-4 py-2\">\n      <pre className=\"overflow-x-auto font-mono text-sm\">{TITLE_TEXT}</pre>\n      <div className=\"grid gap-6\">\n        <section className=\"rounded-lg border p-4\">\n          <h2 className=\"mb-2 font-medium\">API Status</h2>\n          {{#if (eq backend \"convex\")}}\n          <div className=\"flex items-center gap-2\">\n            <div\n              className={`h-2 w-2 rounded-full ${healthCheck.data === \"OK\" ? \"bg-green-500\" : healthCheck.isLoading ? \"bg-orange-400\" : \"bg-red-500\"}`}\n            />\n            <span className=\"text-muted-foreground text-sm\">\n              {healthCheck.isLoading\n                ? \"Checking...\"\n                : healthCheck.data === \"OK\"\n                  ? \"Connected\"\n                  : \"Error\"}\n            </span>\n          </div>\n          {{else}}\n            {{#unless (eq api \"none\")}}\n            <div className=\"flex items-center gap-2\">\n              <div\n                className={`h-2 w-2 rounded-full ${healthCheck.data ? \"bg-green-500\" : \"bg-red-500\"}`}\n              />\n              <span className=\"text-muted-foreground text-sm\">\n                {healthCheck.isLoading\n                  ? \"Checking...\"\n                  : healthCheck.data\n                    ? \"Connected\"\n                    : \"Disconnected\"}\n              </span>\n            </div>\n            {{/unless}}\n          {{/if}}\n        </section>\n      </div>\n    </div>\n  );\n}\n"
  },
  {
    "path": "packages/template-generator/templates/frontend/react/tanstack-start/tsconfig.json.hbs",
    "content": "{\n  \"include\": [\"**/*.ts\", \"**/*.tsx\"],\n  \"compilerOptions\": {\n    \"target\": \"ES2022\",\n    \"jsx\": \"react-jsx\",\n    \"module\": \"ESNext\",\n    \"lib\": [\"ES2022\", \"DOM\", \"DOM.Iterable\"],\n    \"types\": [\"vite/client\"],\n\n    /* Bundler mode */\n    \"moduleResolution\": \"bundler\",\n    \"allowImportingTsExtensions\": true,\n    \"noEmit\": true,\n\n    /* Linting */\n    \"skipLibCheck\": true,\n    \"strict\": true,\n    \"noUnusedLocals\": true,\n    \"noUnusedParameters\": true,\n    \"noFallthroughCasesInSwitch\": true,\n    \"noUncheckedSideEffectImports\": true,\n    \"paths\": {\n      \"@/*\": [\"./src/*\"],\n      \"@{{projectName}}/ui/*\": [\"../../packages/ui/src/*\"]\n    }\n  }\n}\n"
  },
  {
    "path": "packages/template-generator/templates/frontend/react/tanstack-start/vite.config.ts.hbs",
    "content": "import { defineConfig } from \"vite\";\nimport { tanstackStart } from \"@tanstack/react-start/plugin/vite\";\nimport tailwindcss from \"@tailwindcss/vite\";\nimport viteReact from \"@vitejs/plugin-react\";\n\nexport default defineConfig({\n  server: {\n    port: 3001,\n  },\n  resolve: {\n    tsconfigPaths: true,\n  },\n  plugins: [\n    tailwindcss(),\n    tanstackStart(),\n    viteReact(),\n  ],\n{{#if (and (eq backend \"convex\") (eq auth \"better-auth\"))}}\n  ssr: {\n    noExternal: [\"@convex-dev/better-auth\"],\n  },\n{{/if}}\n});\n"
  },
  {
    "path": "packages/template-generator/templates/frontend/react/web-base/_gitignore",
    "content": "# Dependencies\n/node_modules\n/.pnp\n.pnp.*\n.yarn/*\n!.yarn/patches\n!.yarn/plugins\n!.yarn/releases\n!.yarn/versions\n\n# Testing\n/coverage\n\n# Build outputs\n/.next/\n/out/\n/build/\n/dist/\n.vinxi\n.output\n.react-router/\n.tanstack/\n.nitro/\n\n# Deployment\n.vercel\n.netlify\n.wrangler\n.alchemy\n\n# Environment & local files\n.env*\n!.env.example\n.DS_Store\n*.pem\n*.local\n\n# Logs\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\n.pnpm-debug.log*\n*.log*\n\n# TypeScript\n*.tsbuildinfo\nnext-env.d.ts\n\n# IDE\n.vscode/*\n!.vscode/extensions.json\n.idea\n\n# Other\ndev-dist\n\n.wrangler\n.dev.vars*\n\n.open-next\n"
  },
  {
    "path": "packages/template-generator/templates/frontend/react/web-base/components.json.hbs",
    "content": "{\n  \"$schema\": \"https://ui.shadcn.com/schema.json\",\n  \"style\": \"base-lyra\",\n  \"rsc\": {{#if (includes frontend \"next\")}}true{{else}}false{{/if}},\n  \"tsx\": true,\n  \"tailwind\": {\n    \"config\": \"\",\n    \"css\": \"../../packages/ui/src/styles/globals.css\",\n    \"baseColor\": \"neutral\",\n    \"cssVariables\": true,\n    \"prefix\": \"\"\n  },\n  \"iconLibrary\": \"lucide\",\n  \"aliases\": {\n    \"components\": \"@/components\",\n    \"utils\": \"@{{projectName}}/ui/lib/utils\",\n    \"ui\": \"@{{projectName}}/ui/components\",\n    \"lib\": \"@/lib\",\n    \"hooks\": \"@/hooks\"\n  },\n  \"menuColor\": \"default\",\n  \"menuAccent\": \"subtle\",\n  \"registries\": {}\n}\n"
  },
  {
    "path": "packages/template-generator/templates/frontend/react/web-base/src/components/header.tsx.hbs",
    "content": "{{#if (includes frontend \"next\")}}\n\"use client\";\nimport Link from \"next/link\";\n{{else if (includes frontend \"react-router\")}}\nimport { NavLink } from \"react-router\";\n{{else if (or (includes frontend \"tanstack-router\") (includes frontend \"tanstack-start\"))}}\nimport { Link } from \"@tanstack/react-router\";\n{{/if}}\n{{#unless (includes frontend \"tanstack-start\")}}\nimport { ModeToggle } from \"./mode-toggle\";\n{{/unless}}\n{{#if (and (eq auth \"better-auth\") (ne backend \"convex\"))}}\nimport UserMenu from \"./user-menu\";\n{{/if}}\n\nexport default function Header() {\n  const links = [\n    { to: \"/\", label: \"Home\" },\n    {{#if (or (eq auth \"better-auth\") (eq auth \"clerk\"))}}\n      { to: \"/dashboard\", label: \"Dashboard\" },\n    {{/if}}\n    {{#if (includes examples \"todo\")}}\n    { to: \"/todos\", label: \"Todos\" },\n    {{/if}}\n    {{#if (includes examples \"ai\")}}\n    { to: \"/ai\", label: \"AI Chat\" },\n    {{/if}}\n  ] as const;\n\n  return (\n    <div>\n      <div className=\"flex flex-row items-center justify-between px-2 py-1\">\n        <nav className=\"flex gap-4 text-lg\">\n          {links.map(({ to, label }) => {\n            {{#if (includes frontend \"next\")}}\n            return (\n              <Link key={to} href={to}>\n                {label}\n              </Link>\n            );\n            {{else if (includes frontend \"react-router\")}}\n            return (\n              <NavLink\n                key={to}\n                to={to}\n                className={({ isActive }) => isActive ? \"font-bold\" : \"\"}\n                end\n              >\n                {label}\n              </NavLink>\n            );\n            {{else if (or (includes frontend \"tanstack-router\") (includes frontend \"tanstack-start\"))}}\n            return (\n              <Link\n                key={to}\n                to={to}\n              >\n                {label}\n              </Link>\n            );\n            {{else}}\n            return null;\n            {{/if}}\n          })}\n        </nav>\n        <div className=\"flex items-center gap-2\">\n          {{#unless (includes frontend \"tanstack-start\")}}\n          <ModeToggle />\n          {{/unless}}\n          {{#if (and (eq auth \"better-auth\") (ne backend \"convex\"))}}\n          <UserMenu />\n          {{/if}}\n        </div>\n      </div>\n      <hr />\n    </div>\n  );\n}\n"
  },
  {
    "path": "packages/template-generator/templates/frontend/react/web-base/src/components/loader.tsx.hbs",
    "content": "import { Loader2 } from \"lucide-react\";\n\nexport default function Loader() {\n  return (\n    <div className=\"flex h-full items-center justify-center pt-8\">\n      <Loader2 className=\"animate-spin\" />\n    </div>\n  );\n}\n"
  },
  {
    "path": "packages/template-generator/templates/frontend/react/web-base/src/index.css.hbs",
    "content": "@import '@{{projectName}}/ui/globals.css';\n{{#if (includes examples \"ai\")}}\n@source \"../node_modules/streamdown/dist/*.js\";\n{{/if}}\n"
  },
  {
    "path": "packages/template-generator/templates/frontend/solid/_gitignore",
    "content": "node_modules\n.DS_Store\ndist\ndist-ssr\n*.local\n.env\n.env.*\n\n.wrangler\n.alchemy\n.dev.vars*"
  },
  {
    "path": "packages/template-generator/templates/frontend/solid/index.html",
    "content": "<!doctype html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <link rel=\"icon\" href=\"/favicon.ico\" />\n    <meta name=\"theme-color\" content=\"#000000\" />\n  </head>\n  <body>\n    <div id=\"app\"></div>\n    <script type=\"module\" src=\"/src/main.tsx\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "packages/template-generator/templates/frontend/solid/package.json.hbs",
    "content": "{\n  \"name\": \"web\",\n  \"private\": true,\n  \"type\": \"module\",\n  \"scripts\": {\n    \"dev\": \"vite dev\",\n    \"build\": \"vite build\",\n    \"serve\": \"vite preview\",\n    \"test\": \"vitest run\"\n  },\n  \"dependencies\": {\n    \"@tailwindcss/vite\": \"^4.2.2\",\n    \"@tanstack/router-plugin\": \"^1.167.22\",\n    \"@tanstack/solid-router\": \"^1.168.20\",\n    \"lucide-solid\": \"^1.8.0\",\n    \"solid-js\": \"^1.9.12\",\n    \"tailwindcss\": \"^4.2.2\"\n  },\n  \"devDependencies\": {\n    \"vite\": \"^8.0.8\",\n    \"vite-plugin-solid\": \"^2.11.12\"\n  }\n}\n"
  },
  {
    "path": "packages/template-generator/templates/frontend/solid/public/robots.txt",
    "content": "# https://www.robotstxt.org/robotstxt.html\nUser-agent: *\nDisallow:\n"
  },
  {
    "path": "packages/template-generator/templates/frontend/solid/src/components/header.tsx.hbs",
    "content": "import { Link } from \"@tanstack/solid-router\";\n{{#if (eq auth \"better-auth\")}}\nimport UserMenu from \"./user-menu\";\n{{/if}}\nimport { For } from \"solid-js\";\n\nexport default function Header() {\n  const links = [\n    { to: \"/\", label: \"Home\" },\n    {{#if (eq auth \"better-auth\")}}\n    { to: \"/dashboard\", label: \"Dashboard\" },\n    {{/if}}\n    {{#if (includes examples \"todo\")}}\n    { to: \"/todos\", label: \"Todos\" },\n    {{/if}}\n    {{#if (includes examples \"ai\")}}\n    { to: \"/ai\", label: \"AI Chat\" },\n    {{/if}}\n  ];\n\n  return (\n    <div>\n      <div class=\"flex flex-row items-center justify-between px-2 py-1\">\n        <nav class=\"flex gap-4 text-lg\">\n          <For each={links}>\n            {(link) => <Link to={link.to}>{link.label}</Link>}\n          </For>\n        </nav>\n        <div class=\"flex items-center gap-2\">\n          {{#if (eq auth \"better-auth\")}}\n          <UserMenu />\n          {{/if}}\n        </div>\n      </div>\n      <hr />\n    </div>\n  );\n}\n"
  },
  {
    "path": "packages/template-generator/templates/frontend/solid/src/components/loader.tsx",
    "content": "import { Loader2 } from \"lucide-solid\";\n\nexport default function Loader() {\n  return (\n    <div class=\"flex h-full items-center justify-center pt-8\">\n      <Loader2 class=\"animate-spin\" />\n    </div>\n  );\n}\n"
  },
  {
    "path": "packages/template-generator/templates/frontend/solid/src/main.tsx.hbs",
    "content": "import { RouterProvider, createRouter } from \"@tanstack/solid-router\";\nimport { render } from \"solid-js/web\";\nimport { routeTree } from \"./routeTree.gen\";\nimport \"./styles.css\";\n{{#if (eq api \"orpc\")}}\nimport { QueryClientProvider } from \"@tanstack/solid-query\";\nimport { orpc, queryClient } from \"./utils/orpc\";\n{{/if}}\n\nconst router = createRouter({\n  routeTree,\n  defaultPreload: \"intent\",\n  scrollRestoration: true,\n  defaultPreloadStaleTime: 0,\n  {{#if (eq api \"orpc\")}}\n  context: { orpc, queryClient },\n  {{/if}}\n});\n\ndeclare module \"@tanstack/solid-router\" {\n  interface Register {\n    router: typeof router;\n  }\n}\n\nfunction App() {\n  return (\n    {{#if (eq api \"orpc\")}}\n    <QueryClientProvider client={queryClient}>\n    {{/if}}\n      <RouterProvider router={router} />\n    {{#if (eq api \"orpc\")}}\n    </QueryClientProvider>\n    {{/if}}\n  );\n}\n\nconst rootElement = document.getElementById(\"app\");\nif (rootElement) {\n  render(() => <App />, rootElement);\n}\n"
  },
  {
    "path": "packages/template-generator/templates/frontend/solid/src/routes/__root.tsx.hbs",
    "content": "import Header from \"@/components/header\";\nimport { Outlet, createRootRouteWithContext } from \"@tanstack/solid-router\";\nimport { TanStackRouterDevtools } from \"@tanstack/solid-router-devtools\";\n{{#if (eq api \"orpc\")}}\nimport { SolidQueryDevtools } from \"@tanstack/solid-query-devtools\";\nimport type { QueryClient } from \"@tanstack/solid-query\";\nimport type { orpc } from \"../utils/orpc\";\n\nexport interface RouterContext {\n  orpc: typeof orpc;\n  queryClient: QueryClient;\n}\n{{else}}\nexport interface RouterContext {}\n{{/if}}\n\nexport const Route = createRootRouteWithContext<RouterContext>()({\n  component: RootComponent,\n});\n\nfunction RootComponent() {\n  return (\n    <>\n      <div class=\"grid grid-rows-[auto_1fr] h-svh\">\n        <Header />\n        <Outlet />\n      </div>\n      {{#if (eq api \"orpc\")}}\n      <SolidQueryDevtools />\n      {{/if}}\n      <TanStackRouterDevtools />\n    </>\n  );\n}\n"
  },
  {
    "path": "packages/template-generator/templates/frontend/solid/src/routes/index.tsx.hbs",
    "content": "import { createFileRoute } from \"@tanstack/solid-router\";\n{{#if (eq api \"orpc\")}}\nimport { useQuery } from \"@tanstack/solid-query\";\nimport { orpc } from \"../utils/orpc\";\nimport { Match, Switch } from \"solid-js\";\n{{else}}\n{{/if}}\n\nexport const Route = createFileRoute(\"/\")({\n  component: App,\n});\n\nconst TITLE_TEXT = `\n ██████╗ ███████╗████████╗████████╗███████╗██████╗\n ██╔══██╗██╔════╝╚══██╔══╝╚══██╔══╝██╔════╝██╔══██╗\n ██████╔╝█████╗     ██║      ██║   █████╗  ██████╔╝\n ██╔══██╗██╔══╝     ██║      ██║   ██╔══╝  ██╔══██╗\n ██████╔╝███████╗   ██║      ██║   ███████╗██║  ██║\n ╚═════╝ ╚══════╝   ╚═╝      ╚═╝   ╚══════╝╚═╝  ╚═╝\n\n ████████╗    ███████╗████████╗ █████╗  ██████╗██╗  ██╗\n ╚══██╔══╝    ██╔════╝╚══██╔══╝██╔══██╗██╔════╝██║ ██╔╝\n    ██║       ███████╗   ██║   ███████║██║     █████╔╝\n    ██║       ╚════██║   ██║   ██╔══██║██║     ██╔═██╗\n    ██║       ███████║   ██║   ██║  ██║╚██████╗██║  ██╗\n    ╚═╝       ╚══════╝   ╚═╝   ╚═╝  ╚═╝ ╚═════╝╚═╝  ╚═╝\n `;\n\nfunction App() {\n  {{#if (eq api \"orpc\")}}\n  const healthCheck = useQuery(() => orpc.healthCheck.queryOptions());\n  {{/if}}\n\n  return (\n    <div class=\"container mx-auto max-w-3xl px-4 py-2\">\n      <pre class=\"overflow-x-auto font-mono text-sm\">{TITLE_TEXT}</pre>\n      <div class=\"grid gap-6\">\n        {{#if (eq api \"orpc\")}}\n        <section class=\"rounded-lg border p-4\">\n          <h2 class=\"mb-2 font-medium\">API Status</h2>\n          <Switch>\n            <Match when={healthCheck.isPending}>\n              <div class=\"flex items-center gap-2\">\n                <div class=\"h-2 w-2 rounded-full bg-gray-500 animate-pulse\" />{\" \"}\n                <span class=\"text-sm text-muted-foreground\">Checking...</span>\n              </div>\n            </Match>\n            <Match when={healthCheck.isError}>\n              <div class=\"flex items-center gap-2\">\n                <div class=\"h-2 w-2 rounded-full bg-red-500\" />\n                <span class=\"text-sm text-muted-foreground\">Disconnected</span>\n              </div>\n            </Match>\n            <Match when={healthCheck.isSuccess}>\n              <div class=\"flex items-center gap-2\">\n                <div\n                  class={`h-2 w-2 rounded-full ${healthCheck.data ? \"bg-green-500\" : \"bg-red-500\"}`}\n                />\n                <span class=\"text-sm text-muted-foreground\">\n                  {healthCheck.data\n                    ? \"Connected\"\n                    : \"Disconnected\"}\n                </span>\n              </div>\n            </Match>\n          </Switch>\n        </section>\n        {{/if}}\n      </div>\n    </div>\n  );\n}\n"
  },
  {
    "path": "packages/template-generator/templates/frontend/solid/src/styles.css",
    "content": "@import \"tailwindcss\";\n\nbody {\n  @apply bg-neutral-950 text-neutral-100;\n}\n"
  },
  {
    "path": "packages/template-generator/templates/frontend/solid/tsconfig.json.hbs",
    "content": "{\n  \"include\": [\"**/*.ts\", \"**/*.tsx\"],\n  \"compilerOptions\": {\n    \"target\": \"ES2022\",\n    \"jsx\": \"preserve\",\n    \"jsxImportSource\": \"solid-js\",\n    \"module\": \"ESNext\",\n    \"lib\": [\"ES2022\", \"DOM\", \"DOM.Iterable\"],\n    \"types\": [\"vite/client\"],\n\n    \"moduleResolution\": \"bundler\",\n    \"allowImportingTsExtensions\": true,\n    \"verbatimModuleSyntax\": true,\n    \"noEmit\": true,\n\n    \"skipLibCheck\": true,\n    \"strict\": true,\n    \"noUnusedLocals\": true,\n    \"noUnusedParameters\": true,\n    \"noFallthroughCasesInSwitch\": true,\n    \"noUncheckedSideEffectImports\": true,\n\n    \"rootDirs\": [\".\"],\n    \"paths\": {\n      \"@/*\": [\"./src/*\"]\n    }\n  }\n}\n"
  },
  {
    "path": "packages/template-generator/templates/frontend/solid/vite.config.ts.hbs",
    "content": "import { defineConfig } from \"vite\";\nimport { tanstackRouter } from \"@tanstack/router-plugin/vite\";\nimport solidPlugin from \"vite-plugin-solid\";\nimport tailwindcss from \"@tailwindcss/vite\";\nimport path from \"node:path\";\n\nexport default defineConfig({\n  plugins: [\n    tanstackRouter({ target: \"solid\", autoCodeSplitting: true }),\n    solidPlugin(),\n    tailwindcss(),\n  ],\n  resolve: {\n    alias: {\n      \"@\": path.resolve(__dirname, \"./src\"),\n    },\n  },\n  server: {\n    port: 3001,\n  },\n});"
  },
  {
    "path": "packages/template-generator/templates/frontend/svelte/_gitignore",
    "content": "node_modules\n\n# Output\n.output\n.vercel\n.netlify\n.wrangler\n.alchemy\n/.svelte-kit\n/build\n\n# OS\n.DS_Store\nThumbs.db\n\n# Env\n.env\n.env.*\n!.env.example\n!.env.test\n\n# Vite\nvite.config.js.timestamp-*\nvite.config.ts.timestamp-*\n"
  },
  {
    "path": "packages/template-generator/templates/frontend/svelte/_npmrc",
    "content": "engine-strict=true\n"
  },
  {
    "path": "packages/template-generator/templates/frontend/svelte/package.json.hbs",
    "content": "{\n\t\"name\": \"web\",\n\t\"private\": true,\n\t\"version\": \"0.0.1\",\n\t\"type\": \"module\",\n\t\"scripts\": {\n\t\t\"dev\": \"vite dev\",\n\t\t\"build\": \"vite build\",\n\t\t\"preview\": \"vite preview\",\n\t\t\"prepare\": \"svelte-kit sync || echo ''\",\n\t\t\"check\": \"svelte-kit sync && svelte-check --tsconfig ./tsconfig.json\",\n\t\t\"check:watch\": \"svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch\"\n\t},\n\t\"devDependencies\": {\n\t\t\"@sveltejs/adapter-auto\": \"^7.0.1\",\n\t\t\"@sveltejs/kit\": \"^2.58.0\",\n\t\t\"@sveltejs/vite-plugin-svelte\": \"^7.0.0\",\n\t\t\"@tailwindcss/vite\": \"^4.2.4\",\n\t\t\"svelte\": \"^5.55.5\",\n\t\t\"svelte-check\": \"^4.4.6\",\n\t\t\"tailwindcss\": \"^4.2.4\",\n\t\t\"vite\": \"^8.0.10\"\n\t},\n\t\"dependencies\": {}\n}\n"
  },
  {
    "path": "packages/template-generator/templates/frontend/svelte/src/app.css",
    "content": "@import \"tailwindcss\";\n\nbody {\n  @apply bg-neutral-950 text-neutral-100;\n}\n"
  },
  {
    "path": "packages/template-generator/templates/frontend/svelte/src/app.d.ts.hbs",
    "content": "{{#if (eq webDeploy \"cloudflare\")}}\n/// <reference path=\"../../../packages/env/env.d.ts\" />\n{{/if}}\n{{#if (and (eq backend \"self\") (eq api \"orpc\"))}}\nimport type { AppRouterClient } from \"@{{projectName}}/api/routers/index\";\n\n{{/if}}\n// See https://svelte.dev/docs/kit/types#app.d.ts\n// for information about these interfaces\ndeclare global {\n{{#if (and (eq backend \"self\") (eq api \"orpc\"))}}\n\tvar $client: AppRouterClient | undefined;\n\n{{/if}}\n\tnamespace App {\n\t\t// interface Error {}\n\t\t// interface Locals {}\n\t\t// interface PageData {}\n\t\t// interface PageState {}\n{{#if (eq webDeploy \"cloudflare\")}}\n\t\tinterface Platform {\n\t\t\tenv: Env;\n\t\t\tctx: ExecutionContext;\n\t\t\tcaches: CacheStorage;\n\t\t\tcf: IncomingRequestCfProperties;\n\t\t}\n{{else}}\n\t\t// interface Platform {}\n{{/if}}\n\t}\n}\n\nexport {};\n"
  },
  {
    "path": "packages/template-generator/templates/frontend/svelte/src/app.html",
    "content": "<!doctype html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"utf-8\" />\n    <link rel=\"icon\" href=\"%sveltekit.assets%/favicon.png\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n    %sveltekit.head%\n  </head>\n  <body data-sveltekit-preload-data=\"hover\">\n    <div style=\"display: contents\">%sveltekit.body%</div>\n  </body>\n</html>\n"
  },
  {
    "path": "packages/template-generator/templates/frontend/svelte/src/components/Header.svelte.hbs",
    "content": "<script lang=\"ts\">\n\n    {{#if (eq auth \"better-auth\")}}\n\timport UserMenu from './UserMenu.svelte';\n    {{/if}}\n\n</script>\n\n<div>\n\t<div class=\"flex flex-row items-center justify-between px-4 py-2 md:px-6\">\n\t\t<nav class=\"flex gap-4 text-lg\">\n\t\t\t<a href=\"/\" class=\"hover:text-neutral-400 transition-colors\">Home</a>\n\t\t    {{#if (eq auth \"better-auth\")}}\n\t\t\t<a href=\"/dashboard\" class=\"hover:text-neutral-400 transition-colors\">Dashboard</a>\n\t\t    {{/if}}\n\t\t    {{#if (includes examples \"todo\")}}\n\t\t\t<a href=\"/todos\" class=\"hover:text-neutral-400 transition-colors\">Todos</a>\n\t\t    {{/if}}\n\t\t    {{#if (includes examples \"ai\")}}\n\t\t\t<a href=\"/ai\" class=\"hover:text-neutral-400 transition-colors\">AI Chat</a>\n\t\t    {{/if}}\n\t\t</nav>\n\t\t<div class=\"flex items-center gap-2\">\n\t\t    {{#if (eq auth \"better-auth\")}}\n            <UserMenu />\n             {{/if}}\n\t\t</div>\n\t</div>\n\t<hr class=\"border-neutral-800\" />\n</div>\n"
  },
  {
    "path": "packages/template-generator/templates/frontend/svelte/src/lib/index.ts",
    "content": "// place files you want to import through the `$lib` alias in this folder.\nexport {};\n"
  },
  {
    "path": "packages/template-generator/templates/frontend/svelte/src/routes/+layout.svelte.hbs",
    "content": "{{#if (eq backend \"convex\")}}\n<script lang=\"ts\">\n\timport '../app.css';\n    import Header from '../components/Header.svelte';\n    import { PUBLIC_CONVEX_URL } from '$env/static/public';\n\timport { setupConvex } from 'convex-svelte';\n\n\tconst { children } = $props();\n\tsetupConvex(PUBLIC_CONVEX_URL);\n</script>\n\n<div class=\"grid h-svh grid-rows-[auto_1fr]\">\n\t<Header />\n\t<main class=\"overflow-y-auto\">\n\t\t{@render children()}\n\t</main>\n</div>\n{{else}}\n  {{#if (eq api \"orpc\")}}\n<script lang=\"ts\">\n    import { QueryClientProvider } from '@tanstack/svelte-query';\n    import { SvelteQueryDevtools } from '@tanstack/svelte-query-devtools'\n\timport '../app.css';\n    import { queryClient } from '$lib/orpc';\n    import Header from '../components/Header.svelte';\n\n\tconst { children } = $props();\n</script>\n\n<QueryClientProvider client={queryClient}>\n    <div class=\"grid h-svh grid-rows-[auto_1fr]\">\n\t\t<Header />\n\t\t<main class=\"overflow-y-auto\">\n\t\t\t{@render children()}\n\t\t</main>\n    </div>\n    <SvelteQueryDevtools />\n</QueryClientProvider>\n  {{else}}\n<script lang=\"ts\">\n\timport '../app.css';\n    import Header from '../components/Header.svelte';\n\n\tconst { children } = $props();\n</script>\n\n<div class=\"grid h-svh grid-rows-[auto_1fr]\">\n\t<Header />\n\t<main class=\"overflow-y-auto\">\n\t\t{@render children()}\n\t</main>\n</div>\n  {{/if}}\n{{/if}}\n"
  },
  {
    "path": "packages/template-generator/templates/frontend/svelte/src/routes/+page.svelte.hbs",
    "content": "{{#if (eq backend \"convex\")}}\n<script lang=\"ts\">\nimport { useQuery } from 'convex-svelte';\nimport { api } from \"@{{projectName}}/backend/convex/_generated/api\";\n\nconst healthCheck = useQuery(api.healthCheck.get, {});\n\nconst TITLE_TEXT = `\n   ██████╗ ███████╗████████╗████████╗███████╗██████╗\n   ██╔══██╗██╔════╝╚══██╔══╝╚══██╔══╝██╔════╝██╔══██╗\n   ██████╔╝█████╗     ██║      ██║   █████╗  ██████╔╝\n   ██╔══██╗██╔══╝     ██║      ██║   ██╔══╝  ██╔══██╗\n   ██████╔╝███████╗   ██║      ██║   ███████╗██║  ██║\n   ╚═════╝ ╚══════╝   ╚═╝      ╚═╝   ╚══════╝╚═╝  ╚═╝\n\n   ████████╗    ███████╗████████╗ █████╗  ██████╗██╗  ██╗\n   ╚══██╔══╝    ██╔════╝╚══██╔══╝██╔══██╗██╔════╝██║ ██╔╝\n      ██║       ███████╗   ██║   ███████║██║     █████╔╝\n      ██║       ╚════██║   ██║   ██╔══██║██║     ██╔═██╗\n      ██║       ███████║   ██║   ██║  ██║╚██████╗██║  ██╗\n      ╚═╝       ╚══════╝   ╚═╝   ╚═╝  ╚═╝ ╚═════╝╚═╝  ╚═╝\n   `;\n</script>\n\n<div class=\"container mx-auto max-w-3xl px-4 py-2\">\n\t<pre class=\"overflow-x-auto font-mono text-sm\">{TITLE_TEXT}</pre>\n\t<div class=\"grid gap-6\">\n\t\t<section class=\"rounded-lg border p-4\">\n\t\t\t<h2 class=\"mb-2 font-medium\">API Status</h2>\n\t\t\t<div class=\"flex items-center gap-2\">\n\t\t\t\t<div\n\t\t\t\t\tclass={`h-2 w-2 rounded-full ${healthCheck.data ? \"bg-green-500\" : \"bg-red-500\"}`}\n\t\t\t\t></div>\n\t\t\t\t<span class=\"text-muted-foreground text-sm\">\n\t\t\t\t\t{healthCheck.isLoading\n\t\t\t\t\t\t? \"Checking...\"\n\t\t\t\t\t\t: healthCheck.data\n\t\t\t\t\t\t\t? \"Connected\"\n\t\t\t\t\t\t\t: \"Disconnected\"}\n\t\t\t\t</span>\n\t\t\t</div>\n\t\t</section>\n\t</div>\n</div>\n{{else}}\n<script lang=\"ts\">\n{{#if (eq api \"orpc\")}}\nimport { orpc } from \"$lib/orpc\";\nimport { createQuery } from \"@tanstack/svelte-query\";\nconst healthCheck = createQuery(orpc.healthCheck.queryOptions());\n{{/if}}\n\nconst TITLE_TEXT = `\n   ██████╗ ███████╗████████╗████████╗███████╗██████╗\n   ██╔══██╗██╔════╝╚══██╔══╝╚══██╔══╝██╔════╝██╔══██╗\n   ██████╔╝█████╗     ██║      ██║   █████╗  ██████╔╝\n   ██╔══██╗██╔══╝     ██║      ██║   ██╔══╝  ██╔══██╗\n   ██████╔╝███████╗   ██║      ██║   ███████╗██║  ██║\n   ╚═════╝ ╚══════╝   ╚═╝      ╚═╝   ╚══════╝╚═╝  ╚═╝\n\n   ████████╗    ███████╗████████╗ █████╗  ██████╗██╗  ██╗\n   ╚══██╔══╝    ██╔════╝╚══██╔══╝██╔══██╗██╔════╝██║ ██╔╝\n      ██║       ███████╗   ██║   ███████║██║     █████╔╝\n      ██║       ╚════██║   ██║   ██╔══██║██║     ██╔═██╗\n      ██║       ███████║   ██║   ██║  ██║╚██████╗██║  ██╗\n      ╚═╝       ╚══════╝   ╚═╝   ╚═╝  ╚═╝ ╚═════╝╚═╝  ╚═╝\n   `;\n</script>\n\n<div class=\"container mx-auto max-w-3xl px-4 py-2\">\n\t<pre class=\"overflow-x-auto font-mono text-sm\">{TITLE_TEXT}</pre>\n\t<div class=\"grid gap-6\">\n\t    {{#if (eq api \"orpc\")}}\n\t\t<section class=\"rounded-lg border p-4\">\n\t\t\t<h2 class=\"mb-2 font-medium\">API Status</h2>\n\t\t\t<div class=\"flex items-center gap-2\">\n\t\t\t\t<div\n\t\t\t\t\tclass={`h-2 w-2 rounded-full ${$healthCheck.data ? \"bg-green-500\" : \"bg-red-500\"}`}\n\t\t\t\t></div>\n\t\t\t\t<span class=\"text-muted-foreground text-sm\">\n\t\t\t\t\t{$healthCheck.isLoading\n\t\t\t\t\t\t? \"Checking...\"\n\t\t\t\t\t\t: $healthCheck.data\n\t\t\t\t\t\t\t? \"Connected\"\n\t\t\t\t\t\t\t: \"Disconnected\"}\n\t\t\t\t</span>\n\t\t\t</div>\n\t\t</section>\n\t    {{/if}}\n\t</div>\n</div>\n{{/if}}\n"
  },
  {
    "path": "packages/template-generator/templates/frontend/svelte/svelte.config.js.hbs",
    "content": "{{#if (eq webDeploy \"cloudflare\")}}\nimport alchemy from 'alchemy/cloudflare/sveltekit';\n{{else}}\nimport adapter from '@sveltejs/adapter-auto';\n{{/if}}\nimport { vitePreprocess } from '@sveltejs/vite-plugin-svelte';\n\n/** @type {import('@sveltejs/kit').Config} */\nconst config = {\n\t// Consult https://svelte.dev/docs/kit/integrations\n\t// for more information about preprocessors\n\tpreprocess: vitePreprocess(),\n\n\tkit: {\n{{#if (eq webDeploy \"cloudflare\")}}\n\t\t// Alchemy's adapter wraps SvelteKit's Cloudflare adapter for local platform.env and Worker builds.\n\t\tadapter: alchemy()\n{{else}}\n\t\t// adapter-auto only supports some environments, see https://svelte.dev/docs/kit/adapter-auto for a list.\n\t\t// If your environment is not supported, or you settled on a specific environment, switch out the adapter.\n\t\t// See https://svelte.dev/docs/kit/adapters for more information about adapters.\n\t\tadapter: adapter()\n{{/if}}\n\t}\n};\n\nexport default config;\n"
  },
  {
    "path": "packages/template-generator/templates/frontend/svelte/tsconfig.json.hbs",
    "content": "{\n\t\"extends\": \"./.svelte-kit/tsconfig.json\",\n\t\"compilerOptions\": {\n\t\t\"allowJs\": true,\n\t\t\"checkJs\": true,\n\t\t\"esModuleInterop\": true,\n\t\t\"forceConsistentCasingInFileNames\": true,\n\t\t\"resolveJsonModule\": true,\n\t\t\"skipLibCheck\": true,\n\t\t\"sourceMap\": true,\n\t\t\"strict\": true,\n\t\t\"moduleResolution\": \"bundler\"{{#if (eq webDeploy \"cloudflare\")}},\n\t\t\t\"types\": [\"@cloudflare/workers-types\"]{{/if}}\n\t}\n\t// Path aliases are handled by https://svelte.dev/docs/kit/configuration#alias\n\t// except $lib which is handled by https://svelte.dev/docs/kit/configuration#files\n\t//\n\t// If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes\n\t// from the referenced tsconfig.json - TypeScript does not merge them in\n}\n"
  },
  {
    "path": "packages/template-generator/templates/frontend/svelte/vite.config.ts.hbs",
    "content": "import tailwindcss from \"@tailwindcss/vite\";\nimport { sveltekit } from \"@sveltejs/kit/vite\";\nimport { defineConfig } from \"vite\";\n\nexport default defineConfig({\n  plugins: [tailwindcss(), sveltekit()],\n});\n"
  },
  {
    "path": "packages/template-generator/templates/packages/config/package.json.hbs",
    "content": "{\n  \"name\": \"@{{projectName}}/config\",\n  \"version\": \"0.0.0\",\n  \"private\": true\n}\n"
  },
  {
    "path": "packages/template-generator/templates/packages/config/tsconfig.base.json.hbs",
    "content": "{\n  \"$schema\": \"https://json.schemastore.org/tsconfig\",\n  \"compilerOptions\": {\n    \"target\": \"ESNext\",\n    \"module\": \"ESNext\",\n    \"moduleResolution\": \"bundler\",\n    \"lib\": [\"ESNext\"],\n    \"verbatimModuleSyntax\": true,\n    \"strict\": true,\n    \"skipLibCheck\": true,\n    \"resolveJsonModule\": true,\n    \"allowSyntheticDefaultImports\": true,\n    \"esModuleInterop\": true,\n    \"forceConsistentCasingInFileNames\": true,\n    \"isolatedModules\": true,\n    \"noUncheckedIndexedAccess\": true,\n    \"noUnusedLocals\": true,\n    \"noUnusedParameters\": true,\n    \"noFallthroughCasesInSwitch\": true,\n    \"types\": [\n      {{#if (eq runtime \"node\")}}\n        \"node\"\n      {{else if (eq runtime \"bun\")}}\n        \"bun\"\n      {{else if (eq runtime \"workers\")}}\n        \"node\"\n      {{else}}\n        \"node\"\n      {{/if}}{{#if (or (eq serverDeploy \"cloudflare\") (eq webDeploy \"cloudflare\"))}},\n      \"@cloudflare/workers-types\"{{/if}}\n    ]\n  }\n}"
  },
  {
    "path": "packages/template-generator/templates/packages/env/package.json.hbs",
    "content": "{\n\t\"name\": \"@{{projectName}}/env\",\n\t\"version\": \"0.0.0\",\n\t\"private\": true,\n\t\"type\": \"module\",\n\t\"exports\": {}\n}"
  },
  {
    "path": "packages/template-generator/templates/packages/env/src/cloudflare-local.ts.hbs",
    "content": "import { config } from \"dotenv\";\nimport { fileURLToPath } from \"node:url\";\n\nconfig({ path: fileURLToPath(new URL(\"../../../.env\", import.meta.url)) });\nconfig();\n\nconst runtimeEnv = typeof process === \"undefined\" ? {} : process.env;\n\nexport const env = new Proxy({} as Env, {\n\tget(_target, prop) {\n\t\tif (typeof prop !== \"string\") {\n\t\t\treturn undefined;\n\t\t}\n\n\t\treturn runtimeEnv[prop];\n\t},\n});\n"
  },
  {
    "path": "packages/template-generator/templates/packages/env/src/native.ts.hbs",
    "content": "import { createEnv } from \"@t3-oss/env-core\";\nimport { z } from \"zod\";\n\nexport const env = createEnv({\n\tclientPrefix: \"EXPO_PUBLIC_\",\n\tclient: {\n{{#if (eq backend \"convex\")}}\n\t\tEXPO_PUBLIC_CONVEX_URL: z.url(),\n{{#if (eq auth \"better-auth\")}}\n\t\tEXPO_PUBLIC_CONVEX_SITE_URL: z.url(),\n{{/if}}\n{{else}}\n\t\tEXPO_PUBLIC_SERVER_URL: z.url(),\n{{/if}}\n{{#if (eq auth \"clerk\")}}\n\t\tEXPO_PUBLIC_CLERK_PUBLISHABLE_KEY: z.string().min(1),\n{{/if}}\n\t},\n\truntimeEnv: process.env,\n\temptyStringAsUndefined: true,\n});\n"
  },
  {
    "path": "packages/template-generator/templates/packages/env/src/server.ts.hbs",
    "content": "{{#if (and (eq serverDeploy \"cloudflare\") (or (ne backend \"self\") (ne webDeploy \"cloudflare\")))}}\n/// <reference types=\"@cloudflare/workers-types\" />\n/// <reference path=\"../env.d.ts\" />\n// For Cloudflare Workers, env is accessed via cloudflare:workers module\n// Types are defined in env.d.ts based on your alchemy.run.ts bindings\nexport { env } from \"cloudflare:workers\";\n{{else if (and (eq backend \"self\") (eq webDeploy \"cloudflare\") (includes frontend \"next\"))}}\n/// <reference path=\"../env.d.ts\" />\nimport { getCloudflareContext } from \"@opennextjs/cloudflare\";\n\nfunction getNodeEnvValue(key: string) {\n\tif (key === \"DB\") {\n\t\treturn undefined;\n\t}\n\n\treturn process.env[key];\n}\n\nfunction getCloudflareEnvSync() {\n\ttry {\n\t\treturn getCloudflareContext().env as Env;\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\ntype EnvValue = Env[keyof Env];\n\nfunction createEnvProxy(getValue: (key: keyof Env & string) => EnvValue | undefined) {\n\treturn new Proxy({} as Env, {\n\t\tget(_target, prop) {\n\t\t\tif (typeof prop !== \"string\") {\n\t\t\t\treturn undefined;\n\t\t\t}\n\n\t\t\treturn getValue(prop as keyof Env & string);\n\t\t},\n\t});\n}\n\nfunction resolveEnvValue(key: keyof Env & string): EnvValue | undefined {\n\tconst nodeValue = getNodeEnvValue(key);\n\tif (nodeValue !== undefined) {\n\t\treturn nodeValue as EnvValue;\n\t}\n\n\treturn getCloudflareEnvSync()?.[key as keyof Env];\n}\n\n// Next.js local dev runs in Node.js, where env vars are exposed on process.env.\n// In the Cloudflare runtime, fall back to OpenNext's Cloudflare context bindings.\n// For static routes (ISR/SSG), use getEnvAsync() so OpenNext can resolve bindings\n// with the async Cloudflare context API.\nexport async function getEnvAsync() {\n\tconst cloudflareEnv = (await getCloudflareContext({ async: true })).env as Env;\n\n\treturn createEnvProxy((key) => {\n\t\tconst nodeValue = getNodeEnvValue(key);\n\t\tif (nodeValue !== undefined) {\n\t\t\treturn nodeValue;\n\t\t}\n\n\t\treturn cloudflareEnv[key as keyof Env];\n\t});\n}\n\nexport const env = createEnvProxy(resolveEnvValue);\n{{else if (and (eq backend \"self\") (eq webDeploy \"cloudflare\") (includes frontend \"svelte\"))}}\n/// <reference path=\"../env.d.ts\" />\nimport { config } from \"dotenv\";\nimport { fileURLToPath } from \"node:url\";\n\nconfig({ path: fileURLToPath(new URL(\"../../../.env\", import.meta.url)) });\nconfig();\n\nconst runtimeEnv = typeof process === \"undefined\" ? {} : process.env;\n\nexport const env = new Proxy({} as Env, {\n\tget(_target, prop) {\n\t\tif (typeof prop !== \"string\") {\n\t\t\treturn undefined;\n\t\t}\n\n\t\treturn runtimeEnv[prop];\n\t},\n});\n{{else if (and (eq backend \"self\") (eq webDeploy \"cloudflare\"))}}\n/// <reference types=\"@cloudflare/workers-types\" />\n/// <reference path=\"../env.d.ts\" />\n// For Cloudflare Workers, env is accessed via cloudflare:workers module\n// Types are defined in env.d.ts based on your alchemy.run.ts bindings\nexport { env } from \"cloudflare:workers\";\n{{else}}\nimport \"dotenv/config\";\nimport { createEnv } from \"@t3-oss/env-core\";\nimport { z } from \"zod\";\n\nexport const env = createEnv({\n\tserver: {\n{{#if (ne database \"none\")}}\n{{#if (eq dbSetup \"planetscale\")}}\n\t\tDATABASE_HOST: z.string().min(1),\n\t\tDATABASE_USERNAME: z.string().min(1),\n\t\tDATABASE_PASSWORD: z.string().min(1),\n{{else}}\n\t\tDATABASE_URL: z.string().min(1),\n{{#if (eq dbSetup \"turso\")}}\n\t\tDATABASE_AUTH_TOKEN: z.string().min(1),\n{{/if}}\n{{/if}}\n{{/if}}\n{{#if (eq auth \"better-auth\")}}\n\t\tBETTER_AUTH_SECRET: z.string().min(32),\n\t\tBETTER_AUTH_URL: z.url(),\n{{/if}}\n{{#if (eq auth \"clerk\")}}\n\t\tCLERK_SECRET_KEY: z.string().min(1),\n{{#if (or (eq backend \"express\") (eq backend \"fastify\") (and (ne api \"none\") (or (eq backend \"self\") (eq backend \"hono\") (eq backend \"elysia\"))))}}\n\t\tCLERK_PUBLISHABLE_KEY: z.string().min(1),\n{{/if}}\n{{/if}}\n{{#if (eq payments \"polar\")}}\n\t\tPOLAR_ACCESS_TOKEN: z.string().min(1),\n\t\tPOLAR_SUCCESS_URL: z.url(),\n{{/if}}\n\t\tCORS_ORIGIN: z.url(),\n\t\tNODE_ENV: z.enum([\"development\", \"production\", \"test\"]).default(\"development\"),\n\t},\n\truntimeEnv: process.env,\n\temptyStringAsUndefined: true,\n});\n{{/if}}\n"
  },
  {
    "path": "packages/template-generator/templates/packages/env/src/web.ts.hbs",
    "content": "{{#if (includes frontend \"next\")}}\nimport { createEnv } from \"@t3-oss/env-nextjs\";\n{{else if (includes frontend \"nuxt\")}}\nimport { createEnv } from \"@t3-oss/env-nuxt\";\n{{else if (or (includes frontend \"svelte\") (includes frontend \"astro\"))}}\nimport { createEnv } from \"@t3-oss/env-core\";\n{{else}}\nimport { createEnv } from \"@t3-oss/env-core\";\n{{/if}}\nimport { z } from \"zod\";\n\n{{#if (includes frontend \"nuxt\")}}\n/**\n * Nuxt env validation - validates at build time when imported in nuxt.config.ts\n * For runtime access in components/plugins, use useRuntimeConfig() instead:\n *   const config = useRuntimeConfig()\n *   config.public.serverUrl (NUXT_PUBLIC_SERVER_URL maps to serverUrl)\n */\n{{/if}}\nexport const env = createEnv({\n{{#if (eq backend \"convex\")}}\n{{#if (includes frontend \"next\")}}\n\tclient: {\n\t\tNEXT_PUBLIC_CONVEX_URL: z.url(),\n{{#if (eq auth \"better-auth\")}}\n\t\tNEXT_PUBLIC_CONVEX_SITE_URL: z.url(),\n{{/if}}\n{{#if (eq auth \"clerk\")}}\n\t\tNEXT_PUBLIC_CLERK_PUBLISHABLE_KEY: z.string().min(1),\n{{/if}}\n\t},\n\truntimeEnv: {\n\t\tNEXT_PUBLIC_CONVEX_URL: process.env.NEXT_PUBLIC_CONVEX_URL,\n{{#if (eq auth \"better-auth\")}}\n\t\tNEXT_PUBLIC_CONVEX_SITE_URL: process.env.NEXT_PUBLIC_CONVEX_SITE_URL,\n{{/if}}\n{{#if (eq auth \"clerk\")}}\n\t\tNEXT_PUBLIC_CLERK_PUBLISHABLE_KEY: process.env.NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY,\n{{/if}}\n\t},\n{{else if (includes frontend \"nuxt\")}}\n\tclient: {\n\t\tNUXT_PUBLIC_CONVEX_URL: z.url(),\n\t},\n{{else if (or (includes frontend \"svelte\") (includes frontend \"astro\"))}}\n\tclientPrefix: \"PUBLIC_\",\n\tclient: {\n\t\tPUBLIC_CONVEX_URL: z.url(),\n\t},\n\truntimeEnv: (import.meta as any).env,\n{{else}}\n\tclientPrefix: \"VITE_\",\n\tclient: {\n\t\tVITE_CONVEX_URL: z.url(),\n{{#if (eq auth \"better-auth\")}}\n\t\tVITE_CONVEX_SITE_URL: z.url(),\n{{/if}}\n{{#if (eq auth \"clerk\")}}\n\t\tVITE_CLERK_PUBLISHABLE_KEY: z.string().min(1),\n{{/if}}\n\t},\n\truntimeEnv: (import.meta as any).env,\n{{/if}}\n{{else if (eq backend \"self\")}}\n{{#if (includes frontend \"next\")}}\n\tclient: {\n{{#if (eq auth \"clerk\")}}\n\t\tNEXT_PUBLIC_CLERK_PUBLISHABLE_KEY: z.string().min(1),\n{{/if}}\n\t},\n\truntimeEnv: {\n{{#if (eq auth \"clerk\")}}\n\t\tNEXT_PUBLIC_CLERK_PUBLISHABLE_KEY: process.env.NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY,\n{{/if}}\n\t},\n{{else if (includes frontend \"nuxt\")}}\n\tclient: {},\n{{else}}\n\tclientPrefix: \"VITE_\",\n\tclient: {\n{{#if (eq auth \"clerk\")}}\n\t\tVITE_CLERK_PUBLISHABLE_KEY: z.string().min(1),\n{{/if}}\n\t},\n\truntimeEnv: (import.meta as any).env,\n{{/if}}\n{{else if (ne backend \"none\")}}\n{{#if (includes frontend \"next\")}}\n\tclient: {\n\t\tNEXT_PUBLIC_SERVER_URL: z.url(),\n{{#if (eq auth \"clerk\")}}\n\t\tNEXT_PUBLIC_CLERK_PUBLISHABLE_KEY: z.string().min(1),\n{{/if}}\n\t},\n\truntimeEnv: {\n\t\tNEXT_PUBLIC_SERVER_URL: process.env.NEXT_PUBLIC_SERVER_URL,\n{{#if (eq auth \"clerk\")}}\n\t\tNEXT_PUBLIC_CLERK_PUBLISHABLE_KEY: process.env.NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY,\n{{/if}}\n\t},\n{{else if (includes frontend \"nuxt\")}}\n\tclient: {\n\t\tNUXT_PUBLIC_SERVER_URL: z.url(),\n\t},\n{{else if (or (includes frontend \"svelte\") (includes frontend \"astro\"))}}\n\tclientPrefix: \"PUBLIC_\",\n\tclient: {\n\t\tPUBLIC_SERVER_URL: z.url(),\n\t},\n\truntimeEnv: (import.meta as any).env,\n{{else}}\n\tclientPrefix: \"VITE_\",\n\tclient: {\n\t\tVITE_SERVER_URL: z.url(),\n{{#if (eq auth \"clerk\")}}\n\t\tVITE_CLERK_PUBLISHABLE_KEY: z.string().min(1),\n{{/if}}\n\t},\n\truntimeEnv: (import.meta as any).env,\n{{/if}}\n{{/if}}\n\temptyStringAsUndefined: true,\n});\n"
  },
  {
    "path": "packages/template-generator/templates/packages/env/tsconfig.json.hbs",
    "content": "{\n  \"extends\": \"@{{projectName}}/config/tsconfig.base.json\",\n}\n"
  },
  {
    "path": "packages/template-generator/templates/packages/infra/alchemy.run.ts.hbs",
    "content": "import alchemy from \"alchemy\";\n{{#if (eq webDeploy \"cloudflare\")}}\n{{#if (includes frontend \"next\")}}\nimport { Nextjs } from \"alchemy/cloudflare\";\n{{else if (includes frontend \"nuxt\")}}\nimport { Nuxt } from \"alchemy/cloudflare\";\n{{else if (includes frontend \"svelte\")}}\nimport { SvelteKit } from \"alchemy/cloudflare\";\n{{else if (includes frontend \"tanstack-start\")}}\nimport { TanStackStart } from \"alchemy/cloudflare\";\n{{else if (includes frontend \"tanstack-router\")}}\nimport { Vite } from \"alchemy/cloudflare\";\n{{else if (includes frontend \"react-router\")}}\nimport { ReactRouter } from \"alchemy/cloudflare\";\n{{else if (includes frontend \"solid\")}}\nimport { Vite } from \"alchemy/cloudflare\";\n{{else if (includes frontend \"astro\")}}\nimport { Astro } from \"alchemy/cloudflare\";\n{{/if}}\n{{/if}}\n{{#if (eq serverDeploy \"cloudflare\")}}\nimport { Worker } from \"alchemy/cloudflare\";\n{{/if}}\n{{#if (and (or (eq serverDeploy \"cloudflare\") (and (eq webDeploy \"cloudflare\") (eq backend \"self\"))) (eq dbSetup \"d1\"))}}\nimport { D1Database } from \"alchemy/cloudflare\";\n{{/if}}\nimport { config } from \"dotenv\";\n\n{{#if (and (eq webDeploy \"cloudflare\") (eq serverDeploy \"cloudflare\"))}}\nconfig({ path: \"./.env\" });\nconfig({ path: \"../../apps/web/.env\" });\nconfig({ path: \"../../apps/server/.env\" });\n{{else if (eq webDeploy \"cloudflare\")}}\nconfig({ path: \"./.env\" });\nconfig({ path: \"../../apps/web/.env\" });\n{{else if (eq serverDeploy \"cloudflare\")}}\nconfig({ path: \"./.env\" });\nconfig({ path: \"../../apps/server/.env\" });\n{{/if}}\n\nconst app = await alchemy(\"{{projectName}}\");\n\n{{#if (and (or (eq serverDeploy \"cloudflare\") (and (eq webDeploy \"cloudflare\") (eq backend \"self\"))) (eq dbSetup \"d1\"))}}\nconst db = await D1Database(\"database\", {\n\t{{#if (eq orm \"prisma\")}}\n\tmigrationsDir: \"../../packages/db/prisma/migrations\",\n\t{{else if (eq orm \"drizzle\")}}\n\tmigrationsDir: \"../../packages/db/src/migrations\",\n\t{{/if}}\n});\n{{/if}}\n\n{{#if (eq webDeploy \"cloudflare\")}}\n{{#if (includes frontend \"next\")}}\nexport const web = await Nextjs(\"web\", {\n  cwd: \"../../apps/web\",\n  bindings: {\n    {{#if (eq backend \"convex\")}}\n    NEXT_PUBLIC_CONVEX_URL: alchemy.env.NEXT_PUBLIC_CONVEX_URL!,\n    {{#if (eq auth \"better-auth\")}}\n    NEXT_PUBLIC_CONVEX_SITE_URL: alchemy.env.NEXT_PUBLIC_CONVEX_SITE_URL!,\n    {{/if}}\n    {{else if (ne backend \"self\")}}\n    NEXT_PUBLIC_SERVER_URL: alchemy.env.NEXT_PUBLIC_SERVER_URL!,\n    {{/if}}\n    {{#if (eq dbSetup \"d1\")}}\n    DB: db,\n    {{else if (ne database \"none\")}}\n    DATABASE_URL: alchemy.secret.env.DATABASE_URL!,\n    {{/if}}\n    {{#if (ne backend \"convex\")}}\n    CORS_ORIGIN: alchemy.env.CORS_ORIGIN!,\n    {{#if (eq auth \"better-auth\")}}\n    BETTER_AUTH_SECRET: alchemy.secret.env.BETTER_AUTH_SECRET!,\n    BETTER_AUTH_URL: alchemy.env.BETTER_AUTH_URL!,\n    {{/if}}\n    {{/if}}\n    {{#if (eq auth \"clerk\")}}\n    CLERK_SECRET_KEY: alchemy.secret.env.CLERK_SECRET_KEY!,\n    {{#if (and (ne api \"none\") (or (eq backend \"self\") (eq backend \"hono\") (eq backend \"elysia\")))}}\n    CLERK_PUBLISHABLE_KEY: alchemy.env.CLERK_PUBLISHABLE_KEY!,\n    {{/if}}\n    {{/if}}\n    {{#if (and (includes examples \"ai\") (ne backend \"convex\"))}}\n    GOOGLE_GENERATIVE_AI_API_KEY: alchemy.secret.env.GOOGLE_GENERATIVE_AI_API_KEY!,\n    {{/if}}\n    {{#if (eq payments \"polar\")}}\n    POLAR_ACCESS_TOKEN: alchemy.secret.env.POLAR_ACCESS_TOKEN!,\n    POLAR_SUCCESS_URL: alchemy.env.POLAR_SUCCESS_URL!,\n    {{/if}}\n    {{#if (eq dbSetup \"turso\")}}\n    DATABASE_AUTH_TOKEN: alchemy.secret.env.DATABASE_AUTH_TOKEN!,\n    {{/if}}\n    {{#if (eq database \"mysql\")}}\n    {{#if (eq orm \"drizzle\")}}\n    DATABASE_HOST: alchemy.env.DATABASE_HOST!,\n    DATABASE_USERNAME: alchemy.env.DATABASE_USERNAME!,\n    DATABASE_PASSWORD: alchemy.secret.env.DATABASE_PASSWORD!,\n    {{/if}}\n    {{/if}}\n  },\n  dev: {\n    env: {\n      PORT: \"3001\",\n    },\n  },\n});\n{{else if (includes frontend \"nuxt\")}}\nexport const web = await Nuxt(\"web\", {\n  cwd: \"../../apps/web\",\n  bindings: {\n    {{#if (eq backend \"convex\")}}\n    NUXT_PUBLIC_CONVEX_URL: alchemy.env.NUXT_PUBLIC_CONVEX_URL!,\n    {{#if (eq auth \"better-auth\")}}\n    NUXT_PUBLIC_CONVEX_SITE_URL: alchemy.env.NUXT_PUBLIC_CONVEX_SITE_URL!,\n    {{/if}}\n    {{else if (ne backend \"self\")}}\n    NUXT_PUBLIC_SERVER_URL: alchemy.env.NUXT_PUBLIC_SERVER_URL!,\n    {{/if}}\n    {{#if (eq backend \"self\")}}\n    {{#if (eq dbSetup \"d1\")}}\n    DB: db,\n    {{else if (ne database \"none\")}}\n    DATABASE_URL: alchemy.secret.env.DATABASE_URL!,\n    {{/if}}\n    {{#if (ne backend \"convex\")}}\n    CORS_ORIGIN: alchemy.env.CORS_ORIGIN!,\n    {{#if (eq auth \"better-auth\")}}\n    BETTER_AUTH_SECRET: alchemy.secret.env.BETTER_AUTH_SECRET!,\n    BETTER_AUTH_URL: alchemy.env.BETTER_AUTH_URL!,\n    {{/if}}\n    {{/if}}\n    {{#if (eq auth \"clerk\")}}\n    CLERK_SECRET_KEY: alchemy.secret.env.CLERK_SECRET_KEY!,\n    {{#if (and (ne api \"none\") (or (eq backend \"self\") (eq backend \"hono\") (eq backend \"elysia\")))}}\n    CLERK_PUBLISHABLE_KEY: alchemy.env.CLERK_PUBLISHABLE_KEY!,\n    {{/if}}\n    {{/if}}\n    {{#if (and (includes examples \"ai\") (ne backend \"convex\"))}}\n    GOOGLE_GENERATIVE_AI_API_KEY: alchemy.secret.env.GOOGLE_GENERATIVE_AI_API_KEY!,\n    {{/if}}\n    {{#if (eq payments \"polar\")}}\n    POLAR_ACCESS_TOKEN: alchemy.secret.env.POLAR_ACCESS_TOKEN!,\n    POLAR_SUCCESS_URL: alchemy.env.POLAR_SUCCESS_URL!,\n    {{/if}}\n    {{#if (eq dbSetup \"turso\")}}\n    DATABASE_AUTH_TOKEN: alchemy.secret.env.DATABASE_AUTH_TOKEN!,\n    {{/if}}\n    {{#if (eq database \"mysql\")}}\n    {{#if (eq orm \"drizzle\")}}\n    DATABASE_HOST: alchemy.env.DATABASE_HOST!,\n    DATABASE_USERNAME: alchemy.env.DATABASE_USERNAME!,\n    DATABASE_PASSWORD: alchemy.secret.env.DATABASE_PASSWORD!,\n    {{/if}}\n    {{/if}}\n    {{/if}}\n  }\n});\n{{else if (includes frontend \"svelte\")}}\nexport const web = await SvelteKit(\"web\", {\n  cwd: \"../../apps/web\",\n  bindings: {\n    {{#if (eq backend \"convex\")}}\n    PUBLIC_CONVEX_URL: alchemy.env.PUBLIC_CONVEX_URL!,\n    {{#if (eq auth \"better-auth\")}}\n    PUBLIC_CONVEX_SITE_URL: alchemy.env.PUBLIC_CONVEX_SITE_URL!,\n    {{/if}}\n    {{else if (ne backend \"self\")}}\n    PUBLIC_SERVER_URL: alchemy.env.PUBLIC_SERVER_URL!,\n    {{/if}}\n    {{#if (eq backend \"self\")}}\n    {{#if (eq dbSetup \"d1\")}}\n    DB: db,\n    {{else if (ne database \"none\")}}\n    DATABASE_URL: alchemy.secret.env.DATABASE_URL!,\n    {{/if}}\n    CORS_ORIGIN: alchemy.env.CORS_ORIGIN!,\n    {{#if (eq auth \"better-auth\")}}\n    BETTER_AUTH_SECRET: alchemy.secret.env.BETTER_AUTH_SECRET!,\n    BETTER_AUTH_URL: alchemy.env.BETTER_AUTH_URL!,\n    {{/if}}\n    {{#if (and (includes examples \"ai\") (ne backend \"convex\"))}}\n    GOOGLE_GENERATIVE_AI_API_KEY: alchemy.secret.env.GOOGLE_GENERATIVE_AI_API_KEY!,\n    {{/if}}\n    {{#if (eq payments \"polar\")}}\n    POLAR_ACCESS_TOKEN: alchemy.secret.env.POLAR_ACCESS_TOKEN!,\n    POLAR_SUCCESS_URL: alchemy.env.POLAR_SUCCESS_URL!,\n    {{/if}}\n    {{#if (eq dbSetup \"turso\")}}\n    DATABASE_AUTH_TOKEN: alchemy.secret.env.DATABASE_AUTH_TOKEN!,\n    {{/if}}\n    {{#if (eq database \"mysql\")}}\n    {{#if (eq orm \"drizzle\")}}\n    DATABASE_HOST: alchemy.env.DATABASE_HOST!,\n    DATABASE_USERNAME: alchemy.env.DATABASE_USERNAME!,\n    DATABASE_PASSWORD: alchemy.secret.env.DATABASE_PASSWORD!,\n    {{/if}}\n    {{/if}}\n    {{/if}}\n  },\n  dev: {\n    domain: \"localhost:5173\",\n  },\n});\n{{else if (includes frontend \"tanstack-start\")}}\nexport const web = await TanStackStart(\"web\", {\n  cwd: \"../../apps/web\",\n  bindings: {\n    {{#if (eq backend \"convex\")}}\n    VITE_CONVEX_URL: alchemy.env.VITE_CONVEX_URL!,\n    {{#if (eq auth \"better-auth\")}}\n    VITE_CONVEX_SITE_URL: alchemy.env.VITE_CONVEX_SITE_URL!,\n    {{/if}}\n    {{else if (ne backend \"self\")}}\n    VITE_SERVER_URL: alchemy.env.VITE_SERVER_URL!,\n    {{/if}}\n    {{#if (eq dbSetup \"d1\")}}\n    DB: db,\n    {{else if (ne database \"none\")}}\n    DATABASE_URL: alchemy.secret.env.DATABASE_URL!,\n    {{/if}}\n    {{#if (ne backend \"convex\")}}\n    CORS_ORIGIN: alchemy.env.CORS_ORIGIN!,\n    {{#if (eq auth \"better-auth\")}}\n    BETTER_AUTH_SECRET: alchemy.secret.env.BETTER_AUTH_SECRET!,\n    BETTER_AUTH_URL: alchemy.env.BETTER_AUTH_URL!,\n    {{/if}}\n    {{/if}}\n    {{#if (eq auth \"clerk\")}}\n    CLERK_SECRET_KEY: alchemy.secret.env.CLERK_SECRET_KEY!,\n    {{#if (and (ne api \"none\") (or (eq backend \"self\") (eq backend \"hono\") (eq backend \"elysia\")))}}\n    CLERK_PUBLISHABLE_KEY: alchemy.env.CLERK_PUBLISHABLE_KEY!,\n    {{/if}}\n    {{/if}}\n    {{#if (and (includes examples \"ai\") (ne backend \"convex\"))}}\n    GOOGLE_GENERATIVE_AI_API_KEY: alchemy.secret.env.GOOGLE_GENERATIVE_AI_API_KEY!,\n    {{/if}}\n    {{#if (eq payments \"polar\")}}\n    POLAR_ACCESS_TOKEN: alchemy.secret.env.POLAR_ACCESS_TOKEN!,\n    POLAR_SUCCESS_URL: alchemy.env.POLAR_SUCCESS_URL!,\n    {{/if}}\n    {{#if (eq dbSetup \"turso\")}}\n    DATABASE_AUTH_TOKEN: alchemy.secret.env.DATABASE_AUTH_TOKEN!,\n    {{/if}}\n    {{#if (eq database \"mysql\")}}\n    {{#if (eq orm \"drizzle\")}}\n    DATABASE_HOST: alchemy.env.DATABASE_HOST!,\n    DATABASE_USERNAME: alchemy.env.DATABASE_USERNAME!,\n    DATABASE_PASSWORD: alchemy.secret.env.DATABASE_PASSWORD!,\n    {{/if}}\n    {{/if}}\n  }\n});\n{{else if (includes frontend \"tanstack-router\")}}\nexport const web = await Vite(\"web\", {\n  cwd: \"../../apps/web\",\n  assets: \"dist\",\n  bindings: {\n    {{#if (eq backend \"convex\")}}\n    VITE_CONVEX_URL: alchemy.env.VITE_CONVEX_URL!,\n    {{#if (eq auth \"better-auth\")}}\n    VITE_CONVEX_SITE_URL: alchemy.env.VITE_CONVEX_SITE_URL!,\n    {{/if}}\n    {{else if (ne backend \"self\")}}\n    VITE_SERVER_URL: alchemy.env.VITE_SERVER_URL!,\n    {{/if}}\n  }\n});\n{{else if (includes frontend \"react-router\")}}\nexport const web = await ReactRouter(\"web\", {\n  cwd: \"../../apps/web\",\n  bindings: {\n    {{#if (eq backend \"convex\")}}\n    VITE_CONVEX_URL: alchemy.env.VITE_CONVEX_URL!,\n    {{#if (eq auth \"better-auth\")}}\n    VITE_CONVEX_SITE_URL: alchemy.env.VITE_CONVEX_SITE_URL!,\n    {{/if}}\n    {{else if (ne backend \"self\")}}\n    VITE_SERVER_URL: alchemy.env.VITE_SERVER_URL!,\n    {{/if}}\n  }\n});\n{{else if (includes frontend \"solid\")}}\nexport const web = await Vite(\"web\", {\n  cwd: \"../../apps/web\",\n  assets: \"dist\",\n  bindings: {\n    {{#if (eq backend \"convex\")}}\n    VITE_CONVEX_URL: alchemy.env.VITE_CONVEX_URL!,\n    {{#if (eq auth \"better-auth\")}}\n    VITE_CONVEX_SITE_URL: alchemy.env.VITE_CONVEX_SITE_URL!,\n    {{/if}}\n    {{else if (ne backend \"self\")}}\n    VITE_SERVER_URL: alchemy.env.VITE_SERVER_URL!,\n    {{/if}}\n  }\n});\n{{else if (includes frontend \"astro\")}}\nexport const web = await Astro(\"web\", {\n  cwd: \"../../apps/web\",\n  entrypoint: \"dist/server/entry.mjs\",\n  assets: \"dist/client\",\n  {{#if (eq backend \"self\")}}\n  compatibility: \"node\",\n  {{/if}}\n  bindings: {\n    {{#if (ne backend \"self\")}}\n    PUBLIC_SERVER_URL: alchemy.env.PUBLIC_SERVER_URL!,\n    {{/if}}\n    {{#if (eq backend \"self\")}}\n    {{#if (eq dbSetup \"d1\")}}\n    DB: db,\n    {{else if (ne database \"none\")}}\n    DATABASE_URL: alchemy.secret.env.DATABASE_URL!,\n    {{/if}}\n    CORS_ORIGIN: alchemy.env.CORS_ORIGIN!,\n    {{#if (eq auth \"better-auth\")}}\n    BETTER_AUTH_SECRET: alchemy.secret.env.BETTER_AUTH_SECRET!,\n    BETTER_AUTH_URL: alchemy.env.BETTER_AUTH_URL!,\n    {{/if}}\n    {{#if (eq payments \"polar\")}}\n    POLAR_ACCESS_TOKEN: alchemy.secret.env.POLAR_ACCESS_TOKEN!,\n    POLAR_SUCCESS_URL: alchemy.env.POLAR_SUCCESS_URL!,\n    {{/if}}\n    {{#if (eq dbSetup \"turso\")}}\n    DATABASE_AUTH_TOKEN: alchemy.secret.env.DATABASE_AUTH_TOKEN!,\n    {{/if}}\n    {{#if (eq database \"mysql\")}}\n    {{#if (eq orm \"drizzle\")}}\n    DATABASE_HOST: alchemy.env.DATABASE_HOST!,\n    DATABASE_USERNAME: alchemy.env.DATABASE_USERNAME!,\n    DATABASE_PASSWORD: alchemy.secret.env.DATABASE_PASSWORD!,\n    {{/if}}\n    {{/if}}\n    {{/if}}\n  }\n});\n{{/if}}\n{{/if}}\n\n{{#if (eq serverDeploy \"cloudflare\")}}\nexport const server = await Worker(\"server\", {\n  cwd: \"../../apps/server\",\n  entrypoint: \"src/index.ts\",\n  compatibility: \"node\",\n  bindings: {\n    {{#if (eq dbSetup \"d1\")}}\n    DB: db,\n    {{else if (ne database \"none\")}}\n    DATABASE_URL: alchemy.secret.env.DATABASE_URL!,\n    {{/if}}\n    CORS_ORIGIN: alchemy.env.CORS_ORIGIN!,\n    {{#if (eq auth \"better-auth\")}}\n    BETTER_AUTH_SECRET: alchemy.secret.env.BETTER_AUTH_SECRET!,\n    BETTER_AUTH_URL: alchemy.env.BETTER_AUTH_URL!,\n    {{/if}}\n    {{#if (eq auth \"clerk\")}}\n    CLERK_SECRET_KEY: alchemy.secret.env.CLERK_SECRET_KEY!,\n    {{#if (and (ne api \"none\") (or (eq backend \"self\") (eq backend \"hono\") (eq backend \"elysia\")))}}\n    CLERK_PUBLISHABLE_KEY: alchemy.env.CLERK_PUBLISHABLE_KEY!,\n    {{/if}}\n    {{/if}}\n    {{#if (includes examples \"ai\")}}\n    GOOGLE_GENERATIVE_AI_API_KEY: alchemy.secret.env.GOOGLE_GENERATIVE_AI_API_KEY!,\n    {{/if}}\n    {{#if (eq payments \"polar\")}}\n    POLAR_ACCESS_TOKEN: alchemy.secret.env.POLAR_ACCESS_TOKEN!,\n    POLAR_SUCCESS_URL: alchemy.env.POLAR_SUCCESS_URL!,\n    {{/if}}\n    {{#if (eq dbSetup \"turso\")}}\n    DATABASE_AUTH_TOKEN: alchemy.secret.env.DATABASE_AUTH_TOKEN!,\n    {{/if}}\n    {{#if (eq database \"mysql\")}}\n    {{#if (eq orm \"drizzle\")}}\n    DATABASE_HOST: alchemy.env.DATABASE_HOST!,\n    DATABASE_USERNAME: alchemy.env.DATABASE_USERNAME!,\n    DATABASE_PASSWORD: alchemy.secret.env.DATABASE_PASSWORD!,\n    {{/if}}\n    {{/if}}\n  },\n  dev: {\n\t\tport: 3000,\n\t},\n});\n{{/if}}\n\n{{#if (and (eq webDeploy \"cloudflare\") (eq serverDeploy \"cloudflare\"))}}\nconsole.log(`Web    -> ${web.url}`);\nconsole.log(`Server -> ${server.url}`);\n{{else if (eq webDeploy \"cloudflare\")}}\nconsole.log(`Web    -> ${web.url}`);\n{{else if (eq serverDeploy \"cloudflare\")}}\nconsole.log(`Server -> ${server.url}`);\n{{/if}}\n\nawait app.finalize();\n"
  },
  {
    "path": "packages/template-generator/templates/packages/infra/package.json.hbs",
    "content": "{\n  \"name\": \"@{{projectName}}/infra\",\n  \"private\": true,\n  \"type\": \"module\",\n  \"scripts\": {\n    \"dev\": \"alchemy dev\",\n    \"deploy\": \"alchemy deploy\",\n    \"destroy\": \"alchemy destroy\"\n  }\n}\n"
  },
  {
    "path": "packages/template-generator/templates/packages/ui/components.json.hbs",
    "content": "{\n  \"$schema\": \"https://ui.shadcn.com/schema.json\",\n  \"style\": \"base-lyra\",\n  \"rsc\": {{#if (includes frontend \"next\")}}true{{else}}false{{/if}},\n  \"tsx\": true,\n  \"tailwind\": {\n    \"config\": \"\",\n    \"css\": \"src/styles/globals.css\",\n    \"baseColor\": \"neutral\",\n    \"cssVariables\": true,\n    \"prefix\": \"\"\n  },\n  \"iconLibrary\": \"lucide\",\n  \"aliases\": {\n    \"components\": \"@{{projectName}}/ui/components\",\n    \"utils\": \"@{{projectName}}/ui/lib/utils\",\n    \"hooks\": \"@{{projectName}}/ui/hooks\",\n    \"lib\": \"@{{projectName}}/ui/lib\",\n    \"ui\": \"@{{projectName}}/ui/components\"\n  },\n  \"menuColor\": \"default\",\n  \"menuAccent\": \"subtle\",\n  \"registries\": {}\n}\n"
  },
  {
    "path": "packages/template-generator/templates/packages/ui/package.json.hbs",
    "content": "{\n  \"name\": \"@{{projectName}}/ui\",\n  \"version\": \"0.0.0\",\n  \"private\": true,\n  \"type\": \"module\",\n  \"exports\": {\n    \"./globals.css\": \"./src/styles/globals.css\",\n    \"./lib/*\": \"./src/lib/*.ts\",\n    \"./components/*\": \"./src/components/*.tsx\",\n    \"./hooks/*\": \"./src/hooks/*.ts\",\n    \"./postcss.config\": \"./postcss.config.mjs\"\n  },\n  \"dependencies\": {\n    \"@base-ui/react\": \"^1.0.0\",\n    \"shadcn\": \"^3.6.2\",\n    \"class-variance-authority\": \"^0.7.1\",\n    \"clsx\": \"^2.1.1\",\n    \"lucide-react\": \"^0.546.0\",\n    \"next-themes\": \"^0.4.6\",\n    \"react\": \"^19.2.3\",\n    \"react-dom\": \"^19.2.3\",\n    \"sonner\": \"^2.0.5\",\n    \"tailwind-merge\": \"^3.3.1\",\n    \"tw-animate-css\": \"^1.3.4\"\n  },\n  \"devDependencies\": {\n    \"@types/react\": \"^19.2.10\",\n    \"@types/react-dom\": \"^19.2.3\",\n    \"tailwindcss\": \"^4.1.18\"\n  },\n  \"scripts\": {\n    \"check-types\": \"tsc --noEmit\"\n  }\n}\n"
  },
  {
    "path": "packages/template-generator/templates/packages/ui/postcss.config.mjs.hbs",
    "content": "export default {\n  plugins: {\n    \"@tailwindcss/postcss\": {},\n  },\n};\n"
  },
  {
    "path": "packages/template-generator/templates/packages/ui/src/components/button.tsx.hbs",
    "content": "import { Button as ButtonPrimitive } from \"@base-ui/react/button\"\nimport { cva, type VariantProps } from \"class-variance-authority\"\n\nimport { cn } from \"@{{projectName}}/ui/lib/utils\"\n\nconst buttonVariants = cva(\n  \"group/button inline-flex shrink-0 items-center justify-center rounded-none border border-transparent bg-clip-padding text-xs font-medium whitespace-nowrap transition-all outline-none select-none focus-visible:border-ring focus-visible:ring-1 focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-1 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4\",\n  {\n    variants: {\n      variant: {\n        default: \"bg-primary text-primary-foreground [a]:hover:bg-primary/80\",\n        outline:\n          \"border-border bg-background hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50\",\n        secondary:\n          \"bg-secondary text-secondary-foreground hover:bg-secondary/80 aria-expanded:bg-secondary aria-expanded:text-secondary-foreground\",\n        ghost:\n          \"hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:hover:bg-muted/50\",\n        destructive:\n          \"bg-destructive/10 text-destructive hover:bg-destructive/20 focus-visible:border-destructive/40 focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:hover:bg-destructive/30 dark:focus-visible:ring-destructive/40\",\n        link: \"text-primary underline-offset-4 hover:underline\",\n      },\n      size: {\n        default:\n          \"h-8 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2\",\n        xs: \"h-6 gap-1 rounded-none px-2 text-xs has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3\",\n        sm: \"h-7 gap-1 rounded-none px-2.5 has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3.5\",\n        lg: \"h-9 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-3 has-data-[icon=inline-start]:pl-3\",\n        icon: \"size-8\",\n        \"icon-xs\": \"size-6 rounded-none [&_svg:not([class*='size-'])]:size-3\",\n        \"icon-sm\": \"size-7 rounded-none\",\n        \"icon-lg\": \"size-9\",\n      },\n    },\n    defaultVariants: {\n      variant: \"default\",\n      size: \"default\",\n    },\n  }\n)\n\nfunction Button({\n  className,\n  variant = \"default\",\n  size = \"default\",\n  ...props\n}: ButtonPrimitive.Props & VariantProps<typeof buttonVariants>) {\n  return (\n    <ButtonPrimitive\n      data-slot=\"button\"\n      className={cn(buttonVariants({ variant, size, className }))}\n      {...props}\n    />\n  )\n}\n\nexport { Button, buttonVariants }\n"
  },
  {
    "path": "packages/template-generator/templates/packages/ui/src/components/card.tsx.hbs",
    "content": "import * as React from \"react\"\n\nimport { cn } from \"@{{projectName}}/ui/lib/utils\"\n\nfunction Card({\n  className,\n  size = \"default\",\n  ...props\n}: React.ComponentProps<\"div\"> & { size?: \"default\" | \"sm\" }) {\n  return (\n    <div\n      data-slot=\"card\"\n      data-size={size}\n      className={cn(\n        \"group/card flex flex-col gap-4 overflow-hidden rounded-none bg-card py-4 text-xs/relaxed text-card-foreground ring-1 ring-foreground/10 has-data-[slot=card-footer]:pb-0 has-[>img:first-child]:pt-0 data-[size=sm]:gap-2 data-[size=sm]:py-3 data-[size=sm]:has-data-[slot=card-footer]:pb-0 *:[img:first-child]:rounded-none *:[img:last-child]:rounded-none\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction CardHeader({ className, ...props }: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"card-header\"\n      className={cn(\n        \"group/card-header @container/card-header grid auto-rows-min items-start gap-1 rounded-none px-4 group-data-[size=sm]/card:px-3 has-data-[slot=card-action]:grid-cols-[1fr_auto] has-data-[slot=card-description]:grid-rows-[auto_auto] [.border-b]:pb-4 group-data-[size=sm]/card:[.border-b]:pb-3\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction CardTitle({ className, ...props }: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"card-title\"\n      className={cn(\n        \"text-sm font-medium group-data-[size=sm]/card:text-sm\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction CardDescription({ className, ...props }: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"card-description\"\n      className={cn(\"text-xs/relaxed text-muted-foreground\", className)}\n      {...props}\n    />\n  )\n}\n\nfunction CardAction({ className, ...props }: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"card-action\"\n      className={cn(\n        \"col-start-2 row-span-2 row-start-1 self-start justify-self-end\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction CardContent({ className, ...props }: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"card-content\"\n      className={cn(\"px-4 group-data-[size=sm]/card:px-3\", className)}\n      {...props}\n    />\n  )\n}\n\nfunction CardFooter({ className, ...props }: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"card-footer\"\n      className={cn(\n        \"flex items-center rounded-none border-t p-4 group-data-[size=sm]/card:p-3\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nexport {\n  Card,\n  CardHeader,\n  CardFooter,\n  CardTitle,\n  CardAction,\n  CardDescription,\n  CardContent,\n}\n"
  },
  {
    "path": "packages/template-generator/templates/packages/ui/src/components/checkbox.tsx.hbs",
    "content": "\"use client\"\n\nimport { Checkbox as CheckboxPrimitive } from \"@base-ui/react/checkbox\"\n\nimport { cn } from \"@{{projectName}}/ui/lib/utils\"\nimport { CheckIcon } from \"lucide-react\"\n\nfunction Checkbox({ className, ...props }: CheckboxPrimitive.Root.Props) {\n  return (\n    <CheckboxPrimitive.Root\n      data-slot=\"checkbox\"\n      className={cn(\n        \"peer relative flex size-4 shrink-0 items-center justify-center rounded-none border border-input transition-colors outline-none group-has-disabled/field:opacity-50 after:absolute after:-inset-x-3 after:-inset-y-2 focus-visible:border-ring focus-visible:ring-1 focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-1 aria-invalid:ring-destructive/20 aria-invalid:aria-checked:border-primary dark:bg-input/30 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 data-checked:border-primary data-checked:bg-primary data-checked:text-primary-foreground dark:data-checked:bg-primary\",\n        className\n      )}\n      {...props}\n    >\n      <CheckboxPrimitive.Indicator\n        data-slot=\"checkbox-indicator\"\n        className=\"grid place-content-center text-current transition-none [&>svg]:size-3.5\"\n      >\n        <CheckIcon />\n      </CheckboxPrimitive.Indicator>\n    </CheckboxPrimitive.Root>\n  )\n}\n\nexport { Checkbox }\n"
  },
  {
    "path": "packages/template-generator/templates/packages/ui/src/components/dropdown-menu.tsx.hbs",
    "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { Menu as MenuPrimitive } from \"@base-ui/react/menu\"\n\nimport { cn } from \"@{{projectName}}/ui/lib/utils\"\nimport { ChevronRightIcon, CheckIcon } from \"lucide-react\"\n\nfunction DropdownMenu({ ...props }: MenuPrimitive.Root.Props) {\n  return <MenuPrimitive.Root data-slot=\"dropdown-menu\" {...props} />\n}\n\nfunction DropdownMenuPortal({ ...props }: MenuPrimitive.Portal.Props) {\n  return <MenuPrimitive.Portal data-slot=\"dropdown-menu-portal\" {...props} />\n}\n\nfunction DropdownMenuTrigger({ ...props }: MenuPrimitive.Trigger.Props) {\n  return <MenuPrimitive.Trigger data-slot=\"dropdown-menu-trigger\" {...props} />\n}\n\nfunction DropdownMenuContent({\n  align = \"start\",\n  alignOffset = 0,\n  side = \"bottom\",\n  sideOffset = 4,\n  className,\n  ...props\n}: MenuPrimitive.Popup.Props &\n  Pick<\n    MenuPrimitive.Positioner.Props,\n    \"align\" | \"alignOffset\" | \"side\" | \"sideOffset\"\n  >) {\n  return (\n    <MenuPrimitive.Portal>\n      <MenuPrimitive.Positioner\n        className=\"isolate z-50 outline-none\"\n        align={align}\n        alignOffset={alignOffset}\n        side={side}\n        sideOffset={sideOffset}\n      >\n        <MenuPrimitive.Popup\n          data-slot=\"dropdown-menu-content\"\n          className={cn(\n            \"z-50 max-h-(--available-height) w-(--anchor-width) min-w-32 origin-(--transform-origin) overflow-x-hidden overflow-y-auto rounded-none bg-popover text-popover-foreground shadow-md ring-1 ring-foreground/10 duration-100 outline-none data-[side=bottom]:slide-in-from-top-2 data-[side=inline-end]:slide-in-from-left-2 data-[side=inline-start]:slide-in-from-right-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:overflow-hidden data-closed:fade-out-0 data-closed:zoom-out-95\",\n            className\n          )}\n          {...props}\n        />\n      </MenuPrimitive.Positioner>\n    </MenuPrimitive.Portal>\n  )\n}\n\nfunction DropdownMenuGroup({ ...props }: MenuPrimitive.Group.Props) {\n  return <MenuPrimitive.Group data-slot=\"dropdown-menu-group\" {...props} />\n}\n\nfunction DropdownMenuLabel({\n  className,\n  inset,\n  ...props\n}: MenuPrimitive.GroupLabel.Props & {\n  inset?: boolean\n}) {\n  return (\n    <MenuPrimitive.GroupLabel\n      data-slot=\"dropdown-menu-label\"\n      data-inset={inset}\n      className={cn(\n        \"px-2 py-2 text-xs text-muted-foreground data-inset:pl-7\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction DropdownMenuItem({\n  className,\n  inset,\n  variant = \"default\",\n  ...props\n}: MenuPrimitive.Item.Props & {\n  inset?: boolean\n  variant?: \"default\" | \"destructive\"\n}) {\n  return (\n    <MenuPrimitive.Item\n      data-slot=\"dropdown-menu-item\"\n      data-inset={inset}\n      data-variant={variant}\n      className={cn(\n        \"group/dropdown-menu-item relative flex cursor-default items-center gap-2 rounded-none px-2 py-2 text-xs outline-hidden select-none focus:bg-accent focus:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground data-inset:pl-7 data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 data-[variant=destructive]:focus:text-destructive dark:data-[variant=destructive]:focus:bg-destructive/20 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 data-[variant=destructive]:*:[svg]:text-destructive\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction DropdownMenuSub({ ...props }: MenuPrimitive.SubmenuRoot.Props) {\n  return <MenuPrimitive.SubmenuRoot data-slot=\"dropdown-menu-sub\" {...props} />\n}\n\nfunction DropdownMenuSubTrigger({\n  className,\n  inset,\n  children,\n  ...props\n}: MenuPrimitive.SubmenuTrigger.Props & {\n  inset?: boolean\n}) {\n  return (\n    <MenuPrimitive.SubmenuTrigger\n      data-slot=\"dropdown-menu-sub-trigger\"\n      data-inset={inset}\n      className={cn(\n        \"flex cursor-default items-center gap-2 rounded-none px-2 py-2 text-xs outline-hidden select-none focus:bg-accent focus:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground data-inset:pl-7 data-popup-open:bg-accent data-popup-open:text-accent-foreground data-open:bg-accent data-open:text-accent-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4\",\n        className\n      )}\n      {...props}\n    >\n      {children}\n      <ChevronRightIcon className=\"ml-auto\" />\n    </MenuPrimitive.SubmenuTrigger>\n  )\n}\n\nfunction DropdownMenuSubContent({\n  align = \"start\",\n  alignOffset = -3,\n  side = \"right\",\n  sideOffset = 0,\n  className,\n  ...props\n}: React.ComponentProps<typeof DropdownMenuContent>) {\n  return (\n    <DropdownMenuContent\n      data-slot=\"dropdown-menu-sub-content\"\n      className={cn(\n        \"w-auto min-w-[96px] rounded-none bg-popover text-popover-foreground shadow-lg ring-1 ring-foreground/10 duration-100 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95\",\n        className\n      )}\n      align={align}\n      alignOffset={alignOffset}\n      side={side}\n      sideOffset={sideOffset}\n      {...props}\n    />\n  )\n}\n\nfunction DropdownMenuCheckboxItem({\n  className,\n  children,\n  checked,\n  inset,\n  ...props\n}: MenuPrimitive.CheckboxItem.Props & {\n  inset?: boolean\n}) {\n  return (\n    <MenuPrimitive.CheckboxItem\n      data-slot=\"dropdown-menu-checkbox-item\"\n      data-inset={inset}\n      className={cn(\n        \"relative flex cursor-default items-center gap-2 rounded-none py-2 pr-8 pl-2 text-xs outline-hidden select-none focus:bg-accent focus:text-accent-foreground focus:**:text-accent-foreground data-inset:pl-7 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4\",\n        className\n      )}\n      checked={checked}\n      {...props}\n    >\n      <span\n        className=\"pointer-events-none absolute right-2 flex items-center justify-center\"\n        data-slot=\"dropdown-menu-checkbox-item-indicator\"\n      >\n        <MenuPrimitive.CheckboxItemIndicator>\n          <CheckIcon />\n        </MenuPrimitive.CheckboxItemIndicator>\n      </span>\n      {children}\n    </MenuPrimitive.CheckboxItem>\n  )\n}\n\nfunction DropdownMenuRadioGroup({ ...props }: MenuPrimitive.RadioGroup.Props) {\n  return (\n    <MenuPrimitive.RadioGroup\n      data-slot=\"dropdown-menu-radio-group\"\n      {...props}\n    />\n  )\n}\n\nfunction DropdownMenuRadioItem({\n  className,\n  children,\n  inset,\n  ...props\n}: MenuPrimitive.RadioItem.Props & {\n  inset?: boolean\n}) {\n  return (\n    <MenuPrimitive.RadioItem\n      data-slot=\"dropdown-menu-radio-item\"\n      data-inset={inset}\n      className={cn(\n        \"relative flex cursor-default items-center gap-2 rounded-none py-2 pr-8 pl-2 text-xs outline-hidden select-none focus:bg-accent focus:text-accent-foreground focus:**:text-accent-foreground data-inset:pl-7 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4\",\n        className\n      )}\n      {...props}\n    >\n      <span\n        className=\"pointer-events-none absolute right-2 flex items-center justify-center\"\n        data-slot=\"dropdown-menu-radio-item-indicator\"\n      >\n        <MenuPrimitive.RadioItemIndicator>\n          <CheckIcon />\n        </MenuPrimitive.RadioItemIndicator>\n      </span>\n      {children}\n    </MenuPrimitive.RadioItem>\n  )\n}\n\nfunction DropdownMenuSeparator({\n  className,\n  ...props\n}: MenuPrimitive.Separator.Props) {\n  return (\n    <MenuPrimitive.Separator\n      data-slot=\"dropdown-menu-separator\"\n      className={cn(\"-mx-1 h-px bg-border\", className)}\n      {...props}\n    />\n  )\n}\n\nfunction DropdownMenuShortcut({\n  className,\n  ...props\n}: React.ComponentProps<\"span\">) {\n  return (\n    <span\n      data-slot=\"dropdown-menu-shortcut\"\n      className={cn(\n        \"ml-auto text-xs tracking-widest text-muted-foreground group-focus/dropdown-menu-item:text-accent-foreground\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nexport {\n  DropdownMenu,\n  DropdownMenuPortal,\n  DropdownMenuTrigger,\n  DropdownMenuContent,\n  DropdownMenuGroup,\n  DropdownMenuLabel,\n  DropdownMenuItem,\n  DropdownMenuCheckboxItem,\n  DropdownMenuRadioGroup,\n  DropdownMenuRadioItem,\n  DropdownMenuSeparator,\n  DropdownMenuShortcut,\n  DropdownMenuSub,\n  DropdownMenuSubTrigger,\n  DropdownMenuSubContent,\n}\n"
  },
  {
    "path": "packages/template-generator/templates/packages/ui/src/components/input.tsx.hbs",
    "content": "import * as React from \"react\"\nimport { Input as InputPrimitive } from \"@base-ui/react/input\"\n\nimport { cn } from \"@{{projectName}}/ui/lib/utils\"\n\nfunction Input({ className, type, ...props }: React.ComponentProps<\"input\">) {\n  return (\n    <InputPrimitive\n      type={type}\n      data-slot=\"input\"\n      className={cn(\n        \"h-8 w-full min-w-0 rounded-none border border-input bg-transparent px-2.5 py-1 text-xs transition-colors outline-none file:inline-flex file:h-6 file:border-0 file:bg-transparent file:text-xs file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-1 focus-visible:ring-ring/50 disabled:pointer-events-none disabled:cursor-not-allowed disabled:bg-input/50 disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-1 aria-invalid:ring-destructive/20 md:text-xs dark:bg-input/30 dark:disabled:bg-input/80 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nexport { Input }\n"
  },
  {
    "path": "packages/template-generator/templates/packages/ui/src/components/label.tsx.hbs",
    "content": "import * as React from \"react\"\n\nimport { cn } from \"@{{projectName}}/ui/lib/utils\"\n\nfunction Label({ className, ...props }: React.ComponentProps<\"label\">) {\n  return (\n    <label\n      data-slot=\"label\"\n      className={cn(\n        \"flex items-center gap-2 text-xs leading-none select-none group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50 peer-disabled:cursor-not-allowed peer-disabled:opacity-50\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nexport { Label }\n"
  },
  {
    "path": "packages/template-generator/templates/packages/ui/src/components/skeleton.tsx.hbs",
    "content": "import { cn } from \"@{{projectName}}/ui/lib/utils\"\n\nfunction Skeleton({ className, ...props }: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"skeleton\"\n      className={cn(\"animate-pulse rounded-none bg-muted\", className)}\n      {...props}\n    />\n  )\n}\n\nexport { Skeleton }\n"
  },
  {
    "path": "packages/template-generator/templates/packages/ui/src/components/sonner.tsx.hbs",
    "content": "\"use client\"\n\nimport { useTheme } from \"next-themes\"\nimport { Toaster as Sonner, type ToasterProps } from \"sonner\"\nimport { CircleCheckIcon, InfoIcon, TriangleAlertIcon, OctagonXIcon, Loader2Icon } from \"lucide-react\"\n\nconst Toaster = ({ ...props }: ToasterProps) => {\n  const { theme = \"system\" } = useTheme()\n\n  return (\n    <Sonner\n      theme={theme as ToasterProps[\"theme\"]}\n      className=\"toaster group\"\n      icons=\\{{\n        success: (\n          <CircleCheckIcon className=\"size-4\" />\n        ),\n        info: (\n          <InfoIcon className=\"size-4\" />\n        ),\n        warning: (\n          <TriangleAlertIcon className=\"size-4\" />\n        ),\n        error: (\n          <OctagonXIcon className=\"size-4\" />\n        ),\n        loading: (\n          <Loader2Icon className=\"size-4 animate-spin\" />\n        ),\n      }}\n      style={\n        {\n          \"--normal-bg\": \"var(--popover)\",\n          \"--normal-text\": \"var(--popover-foreground)\",\n          \"--normal-border\": \"var(--border)\",\n          \"--border-radius\": \"var(--radius)\",\n        } as React.CSSProperties\n      }\n      toastOptions=\\{{\n        classNames: {\n          toast: \"cn-toast\",\n        },\n      }}\n      {...props}\n    />\n  )\n}\n\nexport { Toaster }\n"
  },
  {
    "path": "packages/template-generator/templates/packages/ui/src/hooks/.gitkeep",
    "content": ""
  },
  {
    "path": "packages/template-generator/templates/packages/ui/src/lib/utils.ts.hbs",
    "content": "import { clsx, type ClassValue } from \"clsx\";\nimport { twMerge } from \"tailwind-merge\";\n\nexport function cn(...inputs: ClassValue[]) {\n  return twMerge(clsx(inputs));\n}\n"
  },
  {
    "path": "packages/template-generator/templates/packages/ui/src/styles/globals.css.hbs",
    "content": "@import 'tailwindcss';\n@import 'tw-animate-css';\n@import 'shadcn/tailwind.css';\n@source \"../../../apps/**/*.{ts,tsx}\";\n@source \"../**/*.{ts,tsx}\";\n\n@custom-variant dark (&:is(.dark *));\n\n:root {\n  --background: oklch(1 0 0);\n  --foreground: oklch(0.145 0 0);\n  --card: oklch(1 0 0);\n  --card-foreground: oklch(0.145 0 0);\n  --popover: oklch(1 0 0);\n  --popover-foreground: oklch(0.145 0 0);\n  --primary: oklch(0.205 0 0);\n  --primary-foreground: oklch(0.985 0 0);\n  --secondary: oklch(0.97 0 0);\n  --secondary-foreground: oklch(0.205 0 0);\n  --muted: oklch(0.97 0 0);\n  --muted-foreground: oklch(0.556 0 0);\n  --accent: oklch(0.97 0 0);\n  --accent-foreground: oklch(0.205 0 0);\n  --destructive: oklch(0.58 0.22 27);\n  --border: oklch(0.922 0 0);\n  --input: oklch(0.922 0 0);\n  --ring: oklch(0.708 0 0);\n  --chart-1: oklch(0.809 0.105 251.813);\n  --chart-2: oklch(0.623 0.214 259.815);\n  --chart-3: oklch(0.546 0.245 262.881);\n  --chart-4: oklch(0.488 0.243 264.376);\n  --chart-5: oklch(0.424 0.199 265.638);\n  --radius: 0.625rem;\n  --sidebar: oklch(0.985 0 0);\n  --sidebar-foreground: oklch(0.145 0 0);\n  --sidebar-primary: oklch(0.205 0 0);\n  --sidebar-primary-foreground: oklch(0.985 0 0);\n  --sidebar-accent: oklch(0.97 0 0);\n  --sidebar-accent-foreground: oklch(0.205 0 0);\n  --sidebar-border: oklch(0.922 0 0);\n  --sidebar-ring: oklch(0.708 0 0);\n}\n\n.dark {\n  --background: oklch(0.145 0 0);\n  --foreground: oklch(0.985 0 0);\n  --card: oklch(0.205 0 0);\n  --card-foreground: oklch(0.985 0 0);\n  --popover: oklch(0.205 0 0);\n  --popover-foreground: oklch(0.985 0 0);\n  --primary: oklch(0.87 0 0);\n  --primary-foreground: oklch(0.205 0 0);\n  --secondary: oklch(0.269 0 0);\n  --secondary-foreground: oklch(0.985 0 0);\n  --muted: oklch(0.269 0 0);\n  --muted-foreground: oklch(0.708 0 0);\n  --accent: oklch(0.371 0 0);\n  --accent-foreground: oklch(0.985 0 0);\n  --destructive: oklch(0.704 0.191 22.216);\n  --border: oklch(1 0 0 / 10%);\n  --input: oklch(1 0 0 / 15%);\n  --ring: oklch(0.556 0 0);\n  --chart-1: oklch(0.809 0.105 251.813);\n  --chart-2: oklch(0.623 0.214 259.815);\n  --chart-3: oklch(0.546 0.245 262.881);\n  --chart-4: oklch(0.488 0.243 264.376);\n  --chart-5: oklch(0.424 0.199 265.638);\n  --sidebar: oklch(0.205 0 0);\n  --sidebar-foreground: oklch(0.985 0 0);\n  --sidebar-primary: oklch(0.488 0.243 264.376);\n  --sidebar-primary-foreground: oklch(0.985 0 0);\n  --sidebar-accent: oklch(0.269 0 0);\n  --sidebar-accent-foreground: oklch(0.985 0 0);\n  --sidebar-border: oklch(1 0 0 / 10%);\n  --sidebar-ring: oklch(0.556 0 0);\n}\n\n@theme inline {\n  --font-sans: 'Inter Variable', sans-serif;\n  --color-sidebar-ring: var(--sidebar-ring);\n  --color-sidebar-border: var(--sidebar-border);\n  --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);\n  --color-sidebar-accent: var(--sidebar-accent);\n  --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);\n  --color-sidebar-primary: var(--sidebar-primary);\n  --color-sidebar-foreground: var(--sidebar-foreground);\n  --color-sidebar: var(--sidebar);\n  --color-chart-5: var(--chart-5);\n  --color-chart-4: var(--chart-4);\n  --color-chart-3: var(--chart-3);\n  --color-chart-2: var(--chart-2);\n  --color-chart-1: var(--chart-1);\n  --color-ring: var(--ring);\n  --color-input: var(--input);\n  --color-border: var(--border);\n  --color-destructive: var(--destructive);\n  --color-accent-foreground: var(--accent-foreground);\n  --color-accent: var(--accent);\n  --color-muted-foreground: var(--muted-foreground);\n  --color-muted: var(--muted);\n  --color-secondary-foreground: var(--secondary-foreground);\n  --color-secondary: var(--secondary);\n  --color-primary-foreground: var(--primary-foreground);\n  --color-primary: var(--primary);\n  --color-popover-foreground: var(--popover-foreground);\n  --color-popover: var(--popover);\n  --color-card-foreground: var(--card-foreground);\n  --color-card: var(--card);\n  --color-foreground: var(--foreground);\n  --color-background: var(--background);\n  --radius-sm: calc(var(--radius) - 4px);\n  --radius-md: calc(var(--radius) - 2px);\n  --radius-lg: var(--radius);\n  --radius-xl: calc(var(--radius) + 4px);\n  --radius-2xl: calc(var(--radius) + 8px);\n  --radius-3xl: calc(var(--radius) + 12px);\n  --radius-4xl: calc(var(--radius) + 16px);\n}\n\n@layer base {\n  * {\n    @apply border-border outline-ring/50;\n  }\n  body {\n    @apply font-sans bg-background text-foreground;\n  }\n  html {\n    @apply font-sans;\n  }\n}\n"
  },
  {
    "path": "packages/template-generator/templates/packages/ui/tsconfig.json.hbs",
    "content": "{\n  \"extends\": \"@{{projectName}}/config/tsconfig.base.json\",\n  \"compilerOptions\": {\n    \"jsx\": \"react-jsx\",\n    \"lib\": [\"ESNext\", \"DOM\", \"DOM.Iterable\"],\n    \"paths\": {\n      \"@{{projectName}}/ui/*\": [\"./src/*\"]\n    }\n  },\n  \"include\": [\"src/**/*.ts\", \"src/**/*.tsx\"],\n  \"exclude\": [\"node_modules\"]\n}\n"
  },
  {
    "path": "packages/template-generator/templates/payments/polar/server/base/src/lib/payments.ts.hbs",
    "content": "import { Polar } from \"@polar-sh/sdk\";\n{{#if (and (eq backend \"self\") (eq webDeploy \"cloudflare\") (includes frontend \"svelte\"))}}\nimport type {} from \"@{{projectName}}/env/server\";\n{{else}}\nimport { env } from \"@{{projectName}}/env/server\";\n{{/if}}\n\n{{#if (and (eq backend \"self\") (eq webDeploy \"cloudflare\") (includes frontend \"svelte\"))}}\nexport function createPolarClient({{#if (and (eq backend \"self\") (eq webDeploy \"cloudflare\") (includes frontend \"svelte\"))}}env: Env{{/if}}) {\n\treturn new Polar({\n\t\taccessToken: env.POLAR_ACCESS_TOKEN,\n\t\tserver: \"sandbox\",\n\t});\n}\n{{else}}\nexport const polarClient = new Polar({\n\taccessToken: env.POLAR_ACCESS_TOKEN,\n\tserver: \"sandbox\",\n});\n{{/if}}\n"
  },
  {
    "path": "packages/template-generator/templates/payments/polar/web/nuxt/app/pages/success.vue.hbs",
    "content": "<script setup lang=\"ts\">\nconst route = useRoute()\nconst checkout_id = route.query.checkout_id as string\n</script>\n\n<template>\n  <div class=\"container mx-auto px-4 py-8\">\n    <h1 class=\"text-2xl font-bold mb-4\">Payment Successful!</h1>\n    <p v-if=\"checkout_id\">Checkout ID: \\{{ checkout_id }}</p>\n  </div>\n</template>\n"
  },
  {
    "path": "packages/template-generator/templates/payments/polar/web/react/next/src/app/success/page.tsx.hbs",
    "content": "export default async function SuccessPage({\n    searchParams,\n}: {\n    searchParams: Promise<{ checkout_id: string }>\n}) {\n    const params = await searchParams;\n    const checkout_id = params.checkout_id;\n\n    return (\n        <div className=\"px-4 py-8\">\n            <h1>Payment Successful!</h1>\n            {checkout_id && <p>Checkout ID: {checkout_id}</p>}\n        </div>\n    );\n}\n"
  },
  {
    "path": "packages/template-generator/templates/payments/polar/web/react/react-router/src/routes/success.tsx.hbs",
    "content": "import { useSearchParams } from \"react-router\";\n\nexport default function SuccessPage() {\n    const [searchParams] = useSearchParams();\n    const checkout_id = searchParams.get(\"checkout_id\");\n\n    return (\n        <div className=\"container mx-auto px-4 py-8\">\n            <h1>Payment Successful!</h1>\n            {checkout_id && <p>Checkout ID: {checkout_id}</p>}\n        </div>\n    );\n}\n"
  },
  {
    "path": "packages/template-generator/templates/payments/polar/web/react/tanstack-router/src/routes/success.tsx.hbs",
    "content": "import { createFileRoute, useSearch } from \"@tanstack/react-router\";\n\nexport const Route = createFileRoute(\"/success\")({\n\tcomponent: SuccessPage,\n\tvalidateSearch: (search) => ({\n\t\tcheckout_id: search.checkout_id as string,\n\t}),\n});\n\nfunction SuccessPage() {\n\tconst { checkout_id } = useSearch({ from: \"/success\" });\n\n\treturn (\n\t\t<div className=\"container mx-auto px-4 py-8\">\n\t\t\t<h1>Payment Successful!</h1>\n\t\t\t{checkout_id && <p>Checkout ID: {checkout_id}</p>}\n\t\t</div>\n\t);\n}\n"
  },
  {
    "path": "packages/template-generator/templates/payments/polar/web/react/tanstack-start/src/functions/get-payment.ts.hbs",
    "content": "import { authClient } from \"@/lib/auth-client\";\nimport { authMiddleware } from \"@/middleware/auth\";\nimport { createServerFn } from \"@tanstack/react-start\";\nimport { getRequestHeaders } from \"@tanstack/react-start/server\";\n\nexport const getPayment = createServerFn({ method: \"GET\" })\n    .middleware([authMiddleware])\n    .handler(async () => {\n        const { data: customerState } = await authClient.customer.state({\n            fetchOptions: {\n                headers: getRequestHeaders()\n            }\n        });\n        return customerState;\n    });\n"
  },
  {
    "path": "packages/template-generator/templates/payments/polar/web/react/tanstack-start/src/routes/success.tsx.hbs",
    "content": "import { createFileRoute, useSearch } from \"@tanstack/react-router\";\n\nexport const Route = createFileRoute(\"/success\")({\n\tcomponent: SuccessPage,\n\tvalidateSearch: (search) => ({\n\t\tcheckout_id: search.checkout_id as string,\n\t}),\n});\n\nfunction SuccessPage() {\n\tconst { checkout_id } = useSearch({ from: \"/success\" });\n\n\treturn (\n\t\t<div className=\"container mx-auto px-4 py-8\">\n\t\t\t<h1>Payment Successful!</h1>\n\t\t\t{checkout_id && <p>Checkout ID: {checkout_id}</p>}\n\t\t</div>\n\t);\n}\n"
  },
  {
    "path": "packages/template-generator/templates/payments/polar/web/solid/src/routes/success.tsx.hbs",
    "content": "import { createFileRoute } from \"@tanstack/solid-router\";\nimport { Show } from \"solid-js\";\n\nexport const Route = createFileRoute(\"/success\")({\n\tcomponent: SuccessPage,\n\tvalidateSearch: (search) => ({\n\t\tcheckout_id: search.checkout_id as string,\n\t}),\n});\n\nfunction SuccessPage() {\n\tconst searchParams = Route.useSearch();\n\tconst checkout_id = searchParams().checkout_id;\n\n\treturn (\n\t\t<div class=\"container mx-auto px-4 py-8\">\n\t\t\t<h1>Payment Successful!</h1>\n\t\t\t<Show when={checkout_id}>\n\t\t\t\t<p>Checkout ID: {checkout_id}</p>\n\t\t\t</Show>\n\t\t</div>\n\t);\n}\n"
  },
  {
    "path": "packages/template-generator/templates/payments/polar/web/svelte/src/routes/success/+page.svelte.hbs",
    "content": "<script lang=\"ts\">\n\timport { page } from '$app/state';\n\t\n\tconst checkout_id = $derived(page.url.searchParams.get('checkout_id'));\n</script>\n\n<div class=\"container mx-auto px-4 py-8\">\n\t<h1>Payment Successful!</h1>\n\t{#if checkout_id}\n\t\t<p>Checkout ID: {checkout_id}</p>\n\t{/if}\n</div>\n"
  },
  {
    "path": "packages/template-generator/tsconfig.json",
    "content": "{\n  \"extends\": \"../../tsconfig.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"./dist\",\n    \"rootDir\": \"./src\",\n    \"declaration\": true,\n    \"declarationMap\": true,\n    \"module\": \"ESNext\",\n    \"moduleResolution\": \"bundler\",\n    \"target\": \"ES2022\",\n    \"lib\": [\"ES2022\"],\n    \"strict\": true,\n    \"esModuleInterop\": true,\n    \"skipLibCheck\": true,\n    \"forceConsistentCasingInFileNames\": true,\n    \"resolveJsonModule\": true,\n    \"isolatedModules\": true\n  },\n  \"include\": [\"src/**/*\"],\n  \"exclude\": [\"node_modules\", \"dist\"]\n}\n"
  },
  {
    "path": "packages/template-generator/tsdown.config.ts",
    "content": "import { defineConfig } from \"tsdown\";\n\nexport default defineConfig({\n  entry: [\"src/index.ts\", \"src/fs-writer.ts\", \"src/core/template-reader.ts\"],\n  format: [\"esm\"],\n  clean: true,\n  shims: true,\n  outDir: \"dist\",\n  dts: true,\n});\n"
  },
  {
    "path": "packages/types/package.json",
    "content": "{\n  \"name\": \"@better-t-stack/types\",\n  \"version\": \"3.28.0\",\n  \"description\": \"TypeScript types and schemas for create-better-t-stack CLI\",\n  \"keywords\": [\n    \"better-t-stack\",\n    \"cli\",\n    \"schemas\",\n    \"types\",\n    \"typescript\"\n  ],\n  \"homepage\": \"https://better-t-stack.dev/\",\n  \"license\": \"MIT\",\n  \"author\": \"Aman Varshney\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/AmanVarshney01/create-better-t-stack.git\",\n    \"directory\": \"packages/types\"\n  },\n  \"files\": [\n    \"dist\"\n  ],\n  \"type\": \"module\",\n  \"exports\": {\n    \".\": {\n      \"types\": \"./dist/index.d.mts\",\n      \"default\": \"./dist/index.mjs\"\n    },\n    \"./schemas\": {\n      \"types\": \"./dist/schemas.d.mts\",\n      \"default\": \"./dist/schemas.mjs\"\n    },\n    \"./json-schema\": {\n      \"types\": \"./dist/json-schema.d.mts\",\n      \"default\": \"./dist/json-schema.mjs\"\n    }\n  },\n  \"publishConfig\": {\n    \"access\": \"public\"\n  },\n  \"scripts\": {\n    \"build\": \"tsdown\",\n    \"prepublishOnly\": \"bun run build\"\n  },\n  \"dependencies\": {\n    \"zod\": \"^4.3.6\"\n  },\n  \"devDependencies\": {\n    \"tsdown\": \"^0.21.9\",\n    \"typescript\": \"^6.0.3\"\n  }\n}\n"
  },
  {
    "path": "packages/types/src/constants.ts",
    "content": "import type { DesktopWebFrontend } from \"./types\";\n\nexport const desktopWebFrontends = [\n  \"tanstack-router\",\n  \"react-router\",\n  \"tanstack-start\",\n  \"next\",\n  \"nuxt\",\n  \"svelte\",\n  \"solid\",\n  \"astro\",\n] as const satisfies readonly DesktopWebFrontend[];\n"
  },
  {
    "path": "packages/types/src/index.ts",
    "content": "// Re-export all schemas and types\nexport * from \"./constants\";\nexport * from \"./schemas\";\nexport * from \"./types\";\n"
  },
  {
    "path": "packages/types/src/json-schema.ts",
    "content": "import { z } from \"zod\";\n\nimport {\n  DatabaseSchema,\n  ORMSchema,\n  BackendSchema,\n  RuntimeSchema,\n  FrontendSchema,\n  AddonsSchema,\n  ExamplesSchema,\n  PackageManagerSchema,\n  DatabaseSetupSchema,\n  APISchema,\n  AuthSchema,\n  PaymentsSchema,\n  WebDeploySchema,\n  ServerDeploySchema,\n  DirectoryConflictSchema,\n  TemplateSchema,\n  AddonOptionsSchema,\n  DbSetupOptionsSchema,\n  CreateInputSchema,\n  AddInputSchema,\n  ProjectConfigSchema,\n  BetterTStackConfigSchema,\n  InitResultSchema,\n} from \"./schemas\";\n\n// Generate JSON schemas for each type\nexport function getDatabaseJsonSchema() {\n  return z.toJSONSchema(DatabaseSchema);\n}\n\nexport function getORMJsonSchema() {\n  return z.toJSONSchema(ORMSchema);\n}\n\nexport function getBackendJsonSchema() {\n  return z.toJSONSchema(BackendSchema);\n}\n\nexport function getRuntimeJsonSchema() {\n  return z.toJSONSchema(RuntimeSchema);\n}\n\nexport function getFrontendJsonSchema() {\n  return z.toJSONSchema(FrontendSchema);\n}\n\nexport function getAddonsJsonSchema() {\n  return z.toJSONSchema(AddonsSchema);\n}\n\nexport function getExamplesJsonSchema() {\n  return z.toJSONSchema(ExamplesSchema);\n}\n\nexport function getPackageManagerJsonSchema() {\n  return z.toJSONSchema(PackageManagerSchema);\n}\n\nexport function getDatabaseSetupJsonSchema() {\n  return z.toJSONSchema(DatabaseSetupSchema);\n}\n\nexport function getAPIJsonSchema() {\n  return z.toJSONSchema(APISchema);\n}\n\nexport function getAuthJsonSchema() {\n  return z.toJSONSchema(AuthSchema);\n}\n\nexport function getPaymentsJsonSchema() {\n  return z.toJSONSchema(PaymentsSchema);\n}\n\nexport function getWebDeployJsonSchema() {\n  return z.toJSONSchema(WebDeploySchema);\n}\n\nexport function getServerDeployJsonSchema() {\n  return z.toJSONSchema(ServerDeploySchema);\n}\n\nexport function getDirectoryConflictJsonSchema() {\n  return z.toJSONSchema(DirectoryConflictSchema);\n}\n\nexport function getTemplateJsonSchema() {\n  return z.toJSONSchema(TemplateSchema);\n}\n\nexport function getAddonOptionsJsonSchema() {\n  return z.toJSONSchema(AddonOptionsSchema);\n}\n\nexport function getDbSetupOptionsJsonSchema() {\n  return z.toJSONSchema(DbSetupOptionsSchema);\n}\n\nexport function getCreateInputJsonSchema() {\n  return z.toJSONSchema(CreateInputSchema);\n}\n\nexport function getAddInputJsonSchema() {\n  return z.toJSONSchema(AddInputSchema);\n}\n\nexport function getProjectConfigJsonSchema() {\n  return z.toJSONSchema(ProjectConfigSchema);\n}\n\nexport function getBetterTStackConfigJsonSchema() {\n  return z.toJSONSchema(BetterTStackConfigSchema);\n}\n\nexport function getInitResultJsonSchema() {\n  return z.toJSONSchema(InitResultSchema);\n}\n\n// Get all JSON schemas as a single object\nexport function getAllJsonSchemas() {\n  return {\n    database: getDatabaseJsonSchema(),\n    orm: getORMJsonSchema(),\n    backend: getBackendJsonSchema(),\n    runtime: getRuntimeJsonSchema(),\n    frontend: getFrontendJsonSchema(),\n    addons: getAddonsJsonSchema(),\n    examples: getExamplesJsonSchema(),\n    packageManager: getPackageManagerJsonSchema(),\n    databaseSetup: getDatabaseSetupJsonSchema(),\n    api: getAPIJsonSchema(),\n    auth: getAuthJsonSchema(),\n    payments: getPaymentsJsonSchema(),\n    webDeploy: getWebDeployJsonSchema(),\n    serverDeploy: getServerDeployJsonSchema(),\n    directoryConflict: getDirectoryConflictJsonSchema(),\n    template: getTemplateJsonSchema(),\n    addonOptions: getAddonOptionsJsonSchema(),\n    dbSetupOptions: getDbSetupOptionsJsonSchema(),\n    createInput: getCreateInputJsonSchema(),\n    addInput: getAddInputJsonSchema(),\n    projectConfig: getProjectConfigJsonSchema(),\n    betterTStackConfig: getBetterTStackConfigJsonSchema(),\n    initResult: getInitResultJsonSchema(),\n  };\n}\n"
  },
  {
    "path": "packages/types/src/schemas.ts",
    "content": "import { z } from \"zod\";\n\nexport const DatabaseSchema = z\n  .enum([\"none\", \"sqlite\", \"postgres\", \"mysql\", \"mongodb\"])\n  .describe(\"Database type\");\n\nexport const ORMSchema = z.enum([\"drizzle\", \"prisma\", \"mongoose\", \"none\"]).describe(\"ORM type\");\n\nexport const BackendSchema = z\n  .enum([\"hono\", \"express\", \"fastify\", \"elysia\", \"convex\", \"self\", \"none\"])\n  .describe(\"Backend framework\");\n\nexport const RuntimeSchema = z\n  .enum([\"bun\", \"node\", \"workers\", \"none\"])\n  .describe(\"Runtime environment\");\n\nexport const FrontendSchema = z\n  .enum([\n    \"tanstack-router\",\n    \"react-router\",\n    \"tanstack-start\",\n    \"next\",\n    \"nuxt\",\n    \"native-bare\",\n    \"native-uniwind\",\n    \"native-unistyles\",\n    \"svelte\",\n    \"solid\",\n    \"astro\",\n    \"none\",\n  ])\n  .describe(\"Frontend framework\");\n\nexport const AddonsSchema = z\n  .enum([\n    \"pwa\",\n    \"tauri\",\n    \"electrobun\",\n    \"starlight\",\n    \"biome\",\n    \"lefthook\",\n    \"husky\",\n    \"mcp\",\n    \"turborepo\",\n    \"nx\",\n    \"fumadocs\",\n    \"ultracite\",\n    \"oxlint\",\n    \"opentui\",\n    \"wxt\",\n    \"skills\",\n    \"evlog\",\n    \"none\",\n  ])\n  .describe(\"Additional addons\");\n\nconst AddonsListSchema = z.array(AddonsSchema).superRefine((addons, ctx) => {\n  if (addons.includes(\"nx\") && addons.includes(\"turborepo\")) {\n    ctx.addIssue({\n      code: z.ZodIssueCode.custom,\n      message: \"`nx` and `turborepo` cannot be used together\",\n    });\n  }\n});\n\nexport const ExamplesSchema = z\n  .enum([\"todo\", \"ai\", \"none\"])\n  .describe(\"Example templates to include\");\n\nexport const PackageManagerSchema = z.enum([\"npm\", \"pnpm\", \"bun\"]).describe(\"Package manager\");\n\nexport const DatabaseSetupSchema = z\n  .enum([\n    \"turso\",\n    \"neon\",\n    \"prisma-postgres\",\n    \"planetscale\",\n    \"mongodb-atlas\",\n    \"supabase\",\n    \"d1\",\n    \"docker\",\n    \"none\",\n  ])\n  .describe(\"Database hosting setup\");\n\nexport const APISchema = z.enum([\"trpc\", \"orpc\", \"none\"]).describe(\"API type\");\n\nexport const AuthSchema = z\n  .enum([\"better-auth\", \"clerk\", \"none\"])\n  .describe(\"Authentication provider\");\n\nexport const PaymentsSchema = z.enum([\"polar\", \"none\"]).describe(\"Payments provider\");\n\nexport const WebDeploySchema = z.enum([\"cloudflare\", \"none\"]).describe(\"Web deployment\");\n\nexport const ServerDeploySchema = z.enum([\"cloudflare\", \"none\"]).describe(\"Server deployment\");\n\nexport const DirectoryConflictSchema = z\n  .enum([\"merge\", \"overwrite\", \"increment\", \"error\"])\n  .describe(\"How to handle existing directory conflicts\");\n\nexport const TemplateSchema = z\n  .enum([\"mern\", \"pern\", \"t3\", \"uniwind\", \"none\"])\n  .describe(\"Predefined project template\");\n\nexport const WxtTemplateSchema = z\n  .enum([\"vanilla\", \"vue\", \"react\", \"solid\", \"svelte\"])\n  .describe(\"WXT template\");\n\nexport const TuiTemplateSchema = z.enum([\"core\", \"react\", \"solid\"]).describe(\"OpenTUI template\");\n\nexport const FumadocsTemplateSchema = z\n  .enum([\n    \"next-mdx\",\n    \"next-mdx-static\",\n    \"waku\",\n    \"react-router\",\n    \"react-router-spa\",\n    \"tanstack-start\",\n    \"tanstack-start-spa\",\n  ])\n  .describe(\"Fumadocs template\");\n\nexport const FumadocsSearchSchema = z\n  .enum([\"orama\", \"orama-cloud\"])\n  .describe(\"Fumadocs search solution\");\n\nexport const FumadocsOgImageSchema = z\n  .enum([\"next-og\", \"takumi\"])\n  .describe(\"Fumadocs OG image generator\");\n\nexport const FumadocsAiChatSchema = z\n  .enum([\"openrouter\", \"inkeep\"])\n  .describe(\"Fumadocs AI chat provider\");\n\nexport const InstallScopeSchema = z.enum([\"project\", \"global\"]).describe(\"Installation scope\");\n\nexport const McpServerSchema = z\n  .enum([\n    \"better-t-stack\",\n    \"context7\",\n    \"nx\",\n    \"cloudflare-docs\",\n    \"convex\",\n    \"shadcn\",\n    \"next-devtools\",\n    \"nuxt-docs\",\n    \"nuxt-ui-docs\",\n    \"svelte-docs\",\n    \"astro-docs\",\n    \"planetscale\",\n    \"neon\",\n    \"supabase\",\n    \"better-auth\",\n    \"clerk\",\n    \"expo\",\n    \"polar\",\n  ])\n  .describe(\"MCP server to install\");\n\nexport const McpAgentSchema = z\n  .enum([\n    \"antigravity\",\n    \"cline\",\n    \"cline-cli\",\n    \"cursor\",\n    \"claude-code\",\n    \"codex\",\n    \"opencode\",\n    \"gemini-cli\",\n    \"github-copilot-cli\",\n    \"mcporter\",\n    \"vscode\",\n    \"zed\",\n    \"claude-desktop\",\n    \"goose\",\n  ])\n  .describe(\"Agent target for MCP installation\");\n\nexport const SkillsSourceSchema = z\n  .enum([\n    \"vercel-labs/agent-skills\",\n    \"vercel/ai\",\n    \"vercel/turborepo\",\n    \"yusukebe/hono-skill\",\n    \"vercel-labs/next-skills\",\n    \"nuxt/ui\",\n    \"heroui-inc/heroui\",\n    \"shadcn/ui\",\n    \"better-auth/skills\",\n    \"clerk/skills\",\n    \"neondatabase/agent-skills\",\n    \"supabase/agent-skills\",\n    \"planetscale/database-skills\",\n    \"expo/skills\",\n    \"prisma/skills\",\n    \"elysiajs/skills\",\n    \"waynesutton/convexskills\",\n    \"msmps/opentui-skill\",\n    \"haydenbleasel/ultracite\",\n    \"https://www.evlog.dev\",\n  ])\n  .describe(\"Skill source repository\");\n\nexport const SkillsAgentSchema = z\n  .enum([\n    \"cursor\",\n    \"claude-code\",\n    \"cline\",\n    \"github-copilot\",\n    \"codex\",\n    \"opencode\",\n    \"windsurf\",\n    \"goose\",\n    \"roo\",\n    \"kilo\",\n    \"gemini-cli\",\n    \"antigravity\",\n    \"openhands\",\n    \"trae\",\n    \"amp\",\n    \"pi\",\n    \"qoder\",\n    \"qwen-code\",\n    \"kiro-cli\",\n    \"droid\",\n    \"command-code\",\n    \"clawdbot\",\n    \"zencoder\",\n    \"neovate\",\n    \"mcpjam\",\n  ])\n  .describe(\"Agent target for skill installation\");\n\nexport const SkillSelectionSchema = z.strictObject({\n  source: SkillsSourceSchema.describe(\"Skill source to install from\"),\n  skills: z.array(z.string()).describe(\"Curated skill names to install from this source\"),\n});\n\nexport const UltraciteLinterSchema = z\n  .enum([\"biome\", \"eslint\", \"oxlint\"])\n  .describe(\"Ultracite linter\");\n\nexport const UltraciteEditorSchema = z\n  .enum([\n    \"vscode\",\n    \"cursor\",\n    \"windsurf\",\n    \"codebuddy\",\n    \"antigravity\",\n    \"bob\",\n    \"kiro\",\n    \"trae\",\n    \"void\",\n    \"zed\",\n  ])\n  .describe(\"Ultracite editor integration\");\n\nexport const UltraciteAgentSchema = z\n  .enum([\n    \"universal\",\n    \"claude\",\n    \"codex\",\n    \"jules\",\n    \"replit\",\n    \"devin\",\n    \"lovable\",\n    \"zencoder\",\n    \"ona\",\n    \"openclaw\",\n    \"continue\",\n    \"snowflake-cortex\",\n    \"deepagents\",\n    \"qoder\",\n    \"kimi-cli\",\n    \"mcpjam\",\n    \"mux\",\n    \"pi\",\n    \"adal\",\n    \"copilot\",\n    \"cline\",\n    \"amp\",\n    \"aider\",\n    \"firebase-studio\",\n    \"open-hands\",\n    \"gemini\",\n    \"junie\",\n    \"augmentcode\",\n    \"bob\",\n    \"kilo-code\",\n    \"goose\",\n    \"roo-code\",\n    \"warp\",\n    \"droid\",\n    \"opencode\",\n    \"crush\",\n    \"qwen\",\n    \"amazon-q-cli\",\n    \"firebender\",\n    \"cursor-cli\",\n    \"mistral-vibe\",\n    \"vercel\",\n  ])\n  .describe(\"Ultracite agent integration\");\n\nexport const UltraciteHookSchema = z\n  .enum([\"cursor\", \"windsurf\", \"codebuddy\", \"claude\", \"copilot\"])\n  .describe(\"Ultracite hook integration\");\n\nexport const DbSetupModeSchema = z.enum([\"manual\", \"auto\"]).describe(\"Database setup mode\");\n\nexport const NeonSetupMethodSchema = z\n  .enum([\"neondb\", \"neonctl\"])\n  .describe(\"Neon database provisioning method\");\n\nexport const AddonOptionsSchema = z\n  .strictObject({\n    wxt: z\n      .strictObject({\n        template: WxtTemplateSchema,\n        devPort: z.number().int().min(1).max(65535).optional().describe(\"WXT dev server port\"),\n      })\n      .optional()\n      .describe(\"Options for the WXT addon\"),\n    fumadocs: z\n      .strictObject({\n        template: FumadocsTemplateSchema,\n        devPort: z.number().int().min(1).max(65535).optional().describe(\"Fumadocs dev server port\"),\n        search: FumadocsSearchSchema.optional().describe(\"Fumadocs search solution\"),\n        ogImage: FumadocsOgImageSchema.optional().describe(\"Fumadocs OG image generator\"),\n        aiChat: FumadocsAiChatSchema.optional().describe(\"Fumadocs AI chat provider\"),\n      })\n      .optional()\n      .describe(\"Options for the Fumadocs addon\"),\n    opentui: z\n      .strictObject({\n        template: TuiTemplateSchema,\n      })\n      .optional()\n      .describe(\"Options for the OpenTUI addon\"),\n    mcp: z\n      .strictObject({\n        scope: InstallScopeSchema.optional(),\n        servers: z.array(McpServerSchema).optional().describe(\"MCP servers to install\"),\n        agents: z.array(McpAgentSchema).optional().describe(\"Agents to wire MCP servers into\"),\n      })\n      .optional()\n      .describe(\"Options for the MCP addon\"),\n    skills: z\n      .strictObject({\n        scope: InstallScopeSchema.optional(),\n        agents: z.array(SkillsAgentSchema).optional().describe(\"Agents to install skills into\"),\n        selections: z.array(SkillSelectionSchema).optional().describe(\"Skills grouped by source\"),\n      })\n      .optional()\n      .describe(\"Options for the Skills addon\"),\n    ultracite: z\n      .strictObject({\n        linter: UltraciteLinterSchema.optional(),\n        editors: z.array(UltraciteEditorSchema).optional(),\n        agents: z.array(UltraciteAgentSchema).optional(),\n        hooks: z.array(UltraciteHookSchema).optional(),\n      })\n      .optional()\n      .describe(\"Options for the Ultracite addon\"),\n  })\n  .describe(\"Addon-specific configuration\");\n\nexport const DbSetupOptionsSchema = z\n  .strictObject({\n    mode: DbSetupModeSchema.optional().describe(\"How database setup should be executed\"),\n    neon: z\n      .strictObject({\n        method: NeonSetupMethodSchema.optional(),\n        projectName: z.string().min(1).optional().describe(\"Neon project name\"),\n        regionId: z.string().min(1).optional().describe(\"Neon region identifier\"),\n      })\n      .optional()\n      .describe(\"Options for Neon setup\"),\n    prismaPostgres: z\n      .strictObject({\n        regionId: z.string().min(1).optional().describe(\"Prisma Postgres region identifier\"),\n      })\n      .optional()\n      .describe(\"Options for Prisma Postgres setup\"),\n    turso: z\n      .strictObject({\n        databaseName: z.string().min(1).optional().describe(\"Turso database name\"),\n        groupName: z.string().min(1).optional().describe(\"Turso database group name\"),\n        installCli: z\n          .boolean()\n          .optional()\n          .describe(\"Whether the CLI may install the Turso CLI automatically\"),\n      })\n      .optional()\n      .describe(\"Options for Turso setup\"),\n  })\n  .describe(\"Database setup configuration\");\n\nexport const ProjectNameSchema = z\n  .string()\n  .min(1, \"Project name cannot be empty\")\n  .max(255, \"Project name must be less than 255 characters\")\n  .refine(\n    (name) => name === \".\" || !name.startsWith(\".\"),\n    \"Project name cannot start with a dot (except for '.')\",\n  )\n  .refine((name) => name === \".\" || !name.startsWith(\"-\"), \"Project name cannot start with a dash\")\n  .refine((name) => {\n    const invalidChars = [\"<\", \">\", \":\", '\"', \"|\", \"?\", \"*\"];\n    return !invalidChars.some((char) => name.includes(char));\n  }, \"Project name contains invalid characters\")\n  .refine((name) => name.toLowerCase() !== \"node_modules\", \"Project name is reserved\")\n  .describe(\"Project name or path\");\n\nexport const CreateInputSchema = z\n  .object({\n    projectName: z.string().optional(),\n    template: TemplateSchema.optional(),\n    yes: z.boolean().optional(),\n    yolo: z.boolean().optional(),\n    dryRun: z.boolean().optional(),\n    verbose: z.boolean().optional(),\n    addonOptions: AddonOptionsSchema.optional(),\n    dbSetupOptions: DbSetupOptionsSchema.optional(),\n    database: DatabaseSchema.optional(),\n    orm: ORMSchema.optional(),\n    auth: AuthSchema.optional(),\n    payments: PaymentsSchema.optional(),\n    frontend: z.array(FrontendSchema).optional(),\n    addons: AddonsListSchema.optional(),\n    examples: z.array(ExamplesSchema).optional(),\n    git: z.boolean().optional(),\n    packageManager: PackageManagerSchema.optional(),\n    install: z.boolean().optional(),\n    dbSetup: DatabaseSetupSchema.optional(),\n    backend: BackendSchema.optional(),\n    runtime: RuntimeSchema.optional(),\n    api: APISchema.optional(),\n    webDeploy: WebDeploySchema.optional(),\n    serverDeploy: ServerDeploySchema.optional(),\n    directoryConflict: DirectoryConflictSchema.optional(),\n    renderTitle: z.boolean().optional(),\n    disableAnalytics: z.boolean().optional(),\n    manualDb: z.boolean().optional(),\n  })\n  .strict()\n  .refine((input) => !(input.manualDb !== undefined && input.dbSetupOptions?.mode !== undefined), {\n    message: \"`manualDb` and `dbSetupOptions.mode` are mutually exclusive\",\n    path: [\"dbSetupOptions\", \"mode\"],\n  });\n\nexport const AddInputSchema = z\n  .object({\n    addons: AddonsListSchema.optional(),\n    addonOptions: AddonOptionsSchema.optional(),\n    webDeploy: WebDeploySchema.optional(),\n    serverDeploy: ServerDeploySchema.optional(),\n    projectDir: z.string().optional(),\n    install: z.boolean().optional(),\n    packageManager: PackageManagerSchema.optional(),\n    dryRun: z.boolean().optional(),\n  })\n  .strict();\n\nexport const CLIInputSchema = CreateInputSchema.safeExtend({\n  projectDirectory: z.string().optional(),\n}).strict();\n\nexport const ProjectConfigSchema = z.object({\n  projectName: z.string(),\n  projectDir: z.string(),\n  relativePath: z.string(),\n  addonOptions: AddonOptionsSchema.optional(),\n  dbSetupOptions: DbSetupOptionsSchema.optional(),\n  database: DatabaseSchema,\n  orm: ORMSchema,\n  backend: BackendSchema,\n  runtime: RuntimeSchema,\n  frontend: z.array(FrontendSchema),\n  addons: AddonsListSchema,\n  examples: z.array(ExamplesSchema),\n  auth: AuthSchema,\n  payments: PaymentsSchema,\n  git: z.boolean(),\n  packageManager: PackageManagerSchema,\n  install: z.boolean(),\n  dbSetup: DatabaseSetupSchema,\n  api: APISchema,\n  webDeploy: WebDeploySchema,\n  serverDeploy: ServerDeploySchema,\n});\n\nexport const BetterTStackConfigSchema = z.object({\n  version: z.string().describe(\"CLI version used to create this project\"),\n  createdAt: z.string().describe(\"Timestamp when the project was created\"),\n  reproducibleCommand: z.string().optional().describe(\"Command to reproduce this project setup\"),\n  addonOptions: AddonOptionsSchema.optional(),\n  dbSetupOptions: DbSetupOptionsSchema.optional(),\n  database: DatabaseSchema,\n  orm: ORMSchema,\n  backend: BackendSchema,\n  runtime: RuntimeSchema,\n  frontend: z.array(FrontendSchema),\n  addons: AddonsListSchema,\n  examples: z.array(ExamplesSchema),\n  auth: AuthSchema,\n  payments: PaymentsSchema,\n  packageManager: PackageManagerSchema,\n  dbSetup: DatabaseSetupSchema,\n  api: APISchema,\n  webDeploy: WebDeploySchema,\n  serverDeploy: ServerDeploySchema,\n});\n\nexport const BetterTStackConfigFileSchema = z\n  .object({\n    $schema: z.string().optional().describe(\"JSON Schema reference for validation\"),\n  })\n  .extend(BetterTStackConfigSchema.shape)\n  .strict()\n  .meta({\n    id: \"https://r2.better-t-stack.dev/schema.json\",\n    title: \"Better-T-Stack Configuration\",\n    description: \"Configuration file for Better-T-Stack projects\",\n  });\n\nexport const InitResultSchema = z.object({\n  success: z.boolean(),\n  projectConfig: ProjectConfigSchema,\n  reproducibleCommand: z.string(),\n  timeScaffolded: z.string(),\n  elapsedTimeMs: z.number(),\n  projectDirectory: z.string(),\n  relativePath: z.string(),\n  error: z.string().optional(),\n});\n\nexport const DATABASE_VALUES = DatabaseSchema.options;\nexport const ORM_VALUES = ORMSchema.options;\nexport const BACKEND_VALUES = BackendSchema.options;\nexport const RUNTIME_VALUES = RuntimeSchema.options;\nexport const FRONTEND_VALUES = FrontendSchema.options;\nexport const ADDONS_VALUES = AddonsSchema.options;\nexport const EXAMPLES_VALUES = ExamplesSchema.options;\nexport const PACKAGE_MANAGER_VALUES = PackageManagerSchema.options;\nexport const DATABASE_SETUP_VALUES = DatabaseSetupSchema.options;\nexport const API_VALUES = APISchema.options;\nexport const AUTH_VALUES = AuthSchema.options;\nexport const PAYMENTS_VALUES = PaymentsSchema.options;\nexport const WEB_DEPLOY_VALUES = WebDeploySchema.options;\nexport const SERVER_DEPLOY_VALUES = ServerDeploySchema.options;\nexport const DIRECTORY_CONFLICT_VALUES = DirectoryConflictSchema.options;\nexport const TEMPLATE_VALUES = TemplateSchema.options;\n"
  },
  {
    "path": "packages/types/src/types.ts",
    "content": "import type { z } from \"zod\";\n\nimport type {\n  DatabaseSchema,\n  ORMSchema,\n  BackendSchema,\n  RuntimeSchema,\n  FrontendSchema,\n  AddonsSchema,\n  ExamplesSchema,\n  PackageManagerSchema,\n  DatabaseSetupSchema,\n  APISchema,\n  AuthSchema,\n  PaymentsSchema,\n  WebDeploySchema,\n  ServerDeploySchema,\n  DirectoryConflictSchema,\n  TemplateSchema,\n  AddonOptionsSchema,\n  DbSetupOptionsSchema,\n  ProjectNameSchema,\n  CreateInputSchema,\n  AddInputSchema,\n  CLIInputSchema,\n  ProjectConfigSchema,\n  BetterTStackConfigSchema,\n  InitResultSchema,\n} from \"./schemas\";\n\n// Inferred types from Zod schemas\nexport type Database = z.infer<typeof DatabaseSchema>;\nexport type ORM = z.infer<typeof ORMSchema>;\nexport type Backend = z.infer<typeof BackendSchema>;\nexport type Runtime = z.infer<typeof RuntimeSchema>;\nexport type Frontend = z.infer<typeof FrontendSchema>;\nexport type Addons = z.infer<typeof AddonsSchema>;\nexport type Examples = z.infer<typeof ExamplesSchema>;\nexport type PackageManager = z.infer<typeof PackageManagerSchema>;\nexport type DatabaseSetup = z.infer<typeof DatabaseSetupSchema>;\nexport type API = z.infer<typeof APISchema>;\nexport type Auth = z.infer<typeof AuthSchema>;\nexport type Payments = z.infer<typeof PaymentsSchema>;\nexport type WebDeploy = z.infer<typeof WebDeploySchema>;\nexport type ServerDeploy = z.infer<typeof ServerDeploySchema>;\nexport type DirectoryConflict = z.infer<typeof DirectoryConflictSchema>;\nexport type Template = z.infer<typeof TemplateSchema>;\nexport type AddonOptions = z.infer<typeof AddonOptionsSchema>;\nexport type DbSetupOptions = z.infer<typeof DbSetupOptionsSchema>;\nexport type ProjectName = z.infer<typeof ProjectNameSchema>;\n\nexport type CreateInput = z.infer<typeof CreateInputSchema>;\nexport type AddInput = z.infer<typeof AddInputSchema>;\nexport type CLIInput = z.infer<typeof CLIInputSchema>;\nexport type ProjectConfig = z.infer<typeof ProjectConfigSchema>;\nexport type BetterTStackConfig = z.infer<typeof BetterTStackConfigSchema>;\nexport type InitResult = z.infer<typeof InitResultSchema>;\n\nexport type WebFrontend = Extract<\n  Frontend,\n  | \"tanstack-router\"\n  | \"react-router\"\n  | \"tanstack-start\"\n  | \"next\"\n  | \"nuxt\"\n  | \"svelte\"\n  | \"solid\"\n  | \"astro\"\n  | \"none\"\n>;\n\nexport type DesktopWebFrontend = Exclude<WebFrontend, \"none\">;\n\nexport type NativeFrontend = Extract<\n  Frontend,\n  \"native-bare\" | \"native-uniwind\" | \"native-unistyles\" | \"none\"\n>;\n"
  },
  {
    "path": "packages/types/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ESNext\",\n    \"module\": \"ESNext\",\n    \"moduleResolution\": \"bundler\",\n    \"strict\": true,\n    \"esModuleInterop\": true,\n    \"skipLibCheck\": true,\n    \"declaration\": true,\n    \"declarationMap\": true,\n    \"outDir\": \"./dist\"\n  },\n  \"include\": [\"src/**/*\"]\n}\n"
  },
  {
    "path": "packages/types/tsdown.config.ts",
    "content": "import { defineConfig } from \"tsdown\";\n\nexport default defineConfig({\n  entry: [\"src/index.ts\", \"src/schemas.ts\", \"src/json-schema.ts\"],\n  format: [\"esm\"],\n  clean: true,\n  shims: true,\n  outDir: \"dist\",\n  dts: true,\n});\n"
  },
  {
    "path": "scripts/bump-version.ts",
    "content": "import { readFile, writeFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\n\nimport { confirm, select, text } from \"@clack/prompts\";\nimport { $ } from \"bun\";\n\nconst CLI_PACKAGE_JSON_PATH = join(process.cwd(), \"apps/cli/package.json\");\nconst ALIAS_PACKAGE_JSON_PATH = join(process.cwd(), \"packages/create-bts/package.json\");\nconst TYPES_PACKAGE_JSON_PATH = join(process.cwd(), \"packages/types/package.json\");\nconst TEMPLATE_GENERATOR_PACKAGE_JSON_PATH = join(\n  process.cwd(),\n  \"packages/template-generator/package.json\",\n);\n\nasync function main(): Promise<void> {\n  const args = process.argv.slice(2);\n  const isDryRun = args.includes(\"--dry-run\");\n  let versionInput = args.find((arg) => !arg.startsWith(\"--\"));\n\n  if (!versionInput) {\n    const bumpType = await select({\n      message: \"What type of release do you want to create?\",\n      options: [\n        { value: \"patch\", label: \"Patch (bug fixes) - 2.33.9 → 2.33.10\" },\n        { value: \"minor\", label: \"Minor (new features) - 2.33.9 → 2.34.0\" },\n        { value: \"major\", label: \"Major (breaking changes) - 2.33.9 → 3.0.0\" },\n        { value: \"custom\", label: \"Custom version\" },\n      ],\n    });\n\n    if (bumpType === \"custom\") {\n      const customVersion = await text({\n        message: \"Enter the version (e.g., 2.34.0):\",\n        placeholder: \"2.34.0\",\n      });\n      versionInput = typeof customVersion === \"string\" ? customVersion : undefined;\n    } else if (typeof bumpType === \"string\") {\n      versionInput = bumpType;\n    }\n\n    if (!versionInput) {\n      console.log(\"❌ No version selected\");\n      process.exit(1);\n    }\n  }\n\n  const packageJson = JSON.parse(await readFile(CLI_PACKAGE_JSON_PATH, \"utf-8\"));\n  const currentVersion = packageJson.version;\n  console.log(`Current version: ${currentVersion}`);\n\n  let newVersion = \"\";\n\n  if ([\"major\", \"minor\", \"patch\"].includes(versionInput)) {\n    const [major, minor, patch] = currentVersion.split(\".\").map(Number);\n\n    switch (versionInput) {\n      case \"major\":\n        newVersion = `${major + 1}.0.0`;\n        break;\n      case \"minor\":\n        newVersion = `${major}.${minor + 1}.0`;\n        break;\n      case \"patch\":\n        newVersion = `${major}.${minor}.${patch + 1}`;\n        break;\n    }\n\n    console.log(`Bumping ${versionInput}: ${currentVersion} → ${newVersion}`);\n  } else {\n    if (!/^\\d+\\.\\d+\\.\\d+$/.test(versionInput)) {\n      console.error(\"Version must be x.y.z format\");\n      process.exit(1);\n    }\n    newVersion = versionInput;\n  }\n\n  if (isDryRun) {\n    console.log(`✅ Would release v${newVersion} (dry run)`);\n    return;\n  }\n\n  // Check for uncommitted changes\n  const statusResult = await $`git status --porcelain`.text();\n  if (statusResult.trim()) {\n    console.error(\"❌ You have uncommitted changes. Please commit or stash them first.\");\n    process.exit(1);\n  }\n\n  // Create release branch\n  const branchName = `release/v${newVersion}`;\n  console.log(`\\n📦 Creating release branch: ${branchName}`);\n\n  // Make sure we're on main and up to date\n  await $`git checkout main`;\n  await $`git pull origin main`;\n\n  // Create and checkout the release branch\n  await $`git checkout -b ${branchName}`;\n\n  // Update package versions\n  packageJson.version = newVersion;\n  await writeFile(CLI_PACKAGE_JSON_PATH, `${JSON.stringify(packageJson, null, 2)}\\n`);\n\n  // Update alias package version\n  const aliasPackageJson = JSON.parse(await readFile(ALIAS_PACKAGE_JSON_PATH, \"utf-8\"));\n  aliasPackageJson.version = newVersion;\n  aliasPackageJson.dependencies[\"create-better-t-stack\"] = `^${newVersion}`;\n  await writeFile(ALIAS_PACKAGE_JSON_PATH, `${JSON.stringify(aliasPackageJson, null, 2)}\\n`);\n\n  // Update types package version\n  const typesPackageJson = JSON.parse(await readFile(TYPES_PACKAGE_JSON_PATH, \"utf-8\"));\n  typesPackageJson.version = newVersion;\n  await writeFile(TYPES_PACKAGE_JSON_PATH, `${JSON.stringify(typesPackageJson, null, 2)}\\n`);\n\n  // Update template-generator package version and types dependency\n  const templateGeneratorPackageJson = JSON.parse(\n    await readFile(TEMPLATE_GENERATOR_PACKAGE_JSON_PATH, \"utf-8\"),\n  );\n  templateGeneratorPackageJson.version = newVersion;\n  templateGeneratorPackageJson.dependencies[\"@better-t-stack/types\"] = `^${newVersion}`;\n  await writeFile(\n    TEMPLATE_GENERATOR_PACKAGE_JSON_PATH,\n    `${JSON.stringify(templateGeneratorPackageJson, null, 2)}\\n`,\n  );\n\n  await $`bun install`;\n  await $`bun run build:cli`;\n  await $`git add apps/cli/package.json packages/create-bts/package.json packages/types/package.json packages/template-generator/package.json bun.lock`;\n  await $`git commit -m \"chore(release): ${newVersion}\"`;\n\n  // Push the release branch\n  console.log(`\\n🚀 Pushing release branch...`);\n  await $`git push -u origin ${branchName}`;\n\n  // Create PR using GitHub CLI\n  console.log(`\\n📝 Creating pull request...`);\n  const prTitle = `chore(release): ${newVersion}`;\n  const prBody = `## Release v${newVersion}\n\nThis PR bumps the version to \\`${newVersion}\\`.\n\n### Changes\n- Updated \\`create-better-t-stack\\` to v${newVersion}\n- Updated \\`create-bts\\` to v${newVersion}\n- Updated \\`@better-t-stack/types\\` to v${newVersion}\n- Updated \\`@better-t-stack/template-generator\\` to v${newVersion}\n\n---\n*This PR was automatically created by \\`bun run bump\\`*`;\n\n  await $`gh pr create --title ${prTitle} --body ${prBody} --base main --head ${branchName}`;\n\n  // Ask if user wants to enable auto-merge\n  const shouldAutoMerge = await confirm({\n    message: \"Enable auto-merge? (PR will merge automatically when tests pass)\",\n    initialValue: true,\n  });\n\n  if (shouldAutoMerge) {\n    console.log(`\\n🔄 Enabling auto-merge...`);\n    await $`gh pr merge ${branchName} --auto --squash --delete-branch`;\n    console.log(`✅ Auto-merge enabled. PR will merge automatically when tests pass.`);\n  }\n\n  console.log(`\\n✅ Release PR created for v${newVersion}`);\n  console.log(`\\n📋 Next steps:`);\n  console.log(`   1. Wait for the \"Test Suite\" check to pass`);\n  if (!shouldAutoMerge) {\n    console.log(`   2. Merge the PR manually`);\n  }\n  console.log(\n    `   ${shouldAutoMerge ? \"2\" : \"3\"}. The release workflow will automatically publish to NPM`,\n  );\n\n  // Switch back to main\n  await $`git checkout main`;\n}\n\nmain().catch(console.error);\n"
  },
  {
    "path": "scripts/canary-release.ts",
    "content": "import { readFile, writeFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\n\nimport { confirm, isCancel, multiselect, spinner } from \"@clack/prompts\";\nimport { $ } from \"bun\";\n\nconst CLI_PACKAGE_JSON_PATH = join(process.cwd(), \"apps/cli/package.json\");\nconst ALIAS_PACKAGE_JSON_PATH = join(process.cwd(), \"packages/create-bts/package.json\");\nconst TYPES_PACKAGE_JSON_PATH = join(process.cwd(), \"packages/types/package.json\");\nconst TEMPLATE_GENERATOR_PACKAGE_JSON_PATH = join(\n  process.cwd(),\n  \"packages/template-generator/package.json\",\n);\n\nasync function main(): Promise<void> {\n  const args = process.argv.slice(2);\n  const isDryRun = args.includes(\"--dry-run\");\n  const deprecateOld = args.includes(\"--deprecate-old\") || args.includes(\"--prune-old\");\n  const autoYes = args.includes(\"--yes\");\n\n  const packageJson = JSON.parse(await readFile(CLI_PACKAGE_JSON_PATH, \"utf-8\"));\n  const currentVersion = packageJson.version;\n  const packageName: string = packageJson.name || \"create-better-t-stack\";\n  const strictSemver = /^\\d+\\.\\d+\\.\\d+$/;\n  let baseVersion = currentVersion;\n  if (strictSemver.test(currentVersion)) {\n    baseVersion = currentVersion;\n  } else {\n    const m = currentVersion.match(/^(\\d+)\\.(\\d+)\\.(\\d+)/);\n    baseVersion = m ? m[0] : currentVersion;\n  }\n  console.log(`Current version: ${currentVersion}`);\n  if (baseVersion !== currentVersion) {\n    console.log(`Sanitized base version: ${baseVersion}`);\n  }\n\n  const commitHash = (await $`git rev-parse --short HEAD`.text()).trim();\n  const canaryVersion = `${baseVersion}-canary.${commitHash}`;\n\n  console.log(`Canary version: ${canaryVersion}`);\n  console.log(`Commit: ${commitHash}`);\n\n  if (isDryRun) {\n    console.log(`✅ Would release canary v${canaryVersion} (dry run)`);\n    return;\n  }\n\n  if (deprecateOld) {\n    try {\n      const versionsJson = await $`npm view ${packageName} versions --json`.text();\n      const versions = JSON.parse(versionsJson) as string[];\n      const isCanary = (v: string) => v.includes(\"-canary.\") || v.includes(\"+canary.\");\n      const canaryVersions = (Array.isArray(versions) ? versions : []).filter(isCanary);\n\n      if (!canaryVersions.length) {\n        console.log(\"ℹ️ No canary versions found to deprecate.\");\n        return;\n      }\n\n      const nonDeprecated: string[] = [];\n      for (const v of canaryVersions) {\n        try {\n          const deprecatedJson =\n            await $`npm view ${`${packageName}@${v}`} deprecated --json`.text();\n          const deprecatedMsg = deprecatedJson ? JSON.parse(deprecatedJson) : null;\n          if (!deprecatedMsg || (typeof deprecatedMsg === \"string\" && deprecatedMsg.length === 0)) {\n            nonDeprecated.push(v);\n          }\n        } catch {\n          nonDeprecated.push(v);\n        }\n      }\n\n      if (autoYes) {\n        const depSpin = spinner();\n        depSpin.start(`Deprecating ${nonDeprecated.length} canary version(s)...`);\n        let count = 0;\n        for (const v of nonDeprecated) {\n          try {\n            await $`npm deprecate -f ${`${packageName}@${v}`} \"Deprecated canary; use ${packageName}@canary (currently ${canaryVersion})\"`;\n            count++;\n          } catch {}\n        }\n        depSpin.stop(`Deprecated ${count} version(s).`);\n        return;\n      }\n\n      const selected = (await multiselect({\n        message: \"Select canary versions to deprecate:\",\n        options: nonDeprecated\n          .sort()\n          .reverse()\n          .map((v) => ({ value: v, label: v })),\n      })) as unknown as string[] | symbol;\n\n      if (isCancel(selected) || !Array.isArray(selected) || selected.length === 0) {\n        console.log(\"❌ No selections made. Aborting.\");\n        return;\n      }\n\n      const depSpin = spinner();\n      depSpin.start(`Deprecating ${selected.length} canary version(s)...`);\n      let count = 0;\n      for (const v of selected) {\n        try {\n          await $`npm deprecate -f ${`${packageName}@${v}`} \"Deprecated canary; use ${packageName}@canary (currently ${canaryVersion})\"`;\n          count++;\n        } catch {}\n      }\n      depSpin.stop(`Deprecated ${count} version(s).`);\n      return;\n    } catch (err) {\n      console.error(\"❌ Failed to fetch versions from npm:\", err);\n      return;\n    }\n  }\n\n  try {\n    const versionsJson = await $`npm view ${packageName} versions --json`.text();\n    const versions = JSON.parse(versionsJson) as string[];\n    if (Array.isArray(versions) && versions.includes(canaryVersion)) {\n      if (deprecateOld) {\n        const depSpin = spinner();\n        depSpin.start(\"Deprecating older canary versions (no publish)...\");\n        try {\n          const isCanary = (v: string) => v.includes(\"-canary.\") || v.includes(\"+canary.\");\n          let count = 0;\n          for (const v of versions) {\n            if (!isCanary(v) || v === canaryVersion) continue;\n            await $`npm deprecate -f ${`${packageName}@${v}`} \"Deprecated canary; use ${packageName}@canary (currently ${canaryVersion})\"`;\n            count++;\n          }\n          depSpin.stop(`Deprecated ${count} older canary versions`);\n        } catch (err) {\n          depSpin.stop(\"Failed to deprecate older canaries\");\n          console.warn(\"⚠️ Failed to deprecate older canaries:\", err);\n        }\n        console.error(\n          `❌ ${packageName}@${canaryVersion} is already published on npm. Skipped publish after deprecating older canaries.`,\n        );\n        return;\n      }\n      console.error(\n        `❌ ${packageName}@${canaryVersion} is already published on npm. Make a new commit (or clean your workspace) and try again.`,\n      );\n      return;\n    }\n  } catch {}\n\n  if (!autoYes) {\n    const proceed = await confirm({\n      message: `Publish ${packageName}@${canaryVersion} with dist-tag \"canary\"${deprecateOld ? \", then deprecate older canaries\" : \"\"}?`,\n    });\n    if (isCancel(proceed) || proceed === false) {\n      console.log(\"❌ Canceled by user.\");\n      return;\n    }\n  }\n\n  const originalPackageJsonString = await readFile(CLI_PACKAGE_JSON_PATH, \"utf-8\");\n  const aliasPackageJson = JSON.parse(await readFile(ALIAS_PACKAGE_JSON_PATH, \"utf-8\"));\n  const originalAliasPackageJsonString = await readFile(ALIAS_PACKAGE_JSON_PATH, \"utf-8\");\n  const typesPackageJson = JSON.parse(await readFile(TYPES_PACKAGE_JSON_PATH, \"utf-8\"));\n  const originalTypesPackageJsonString = await readFile(TYPES_PACKAGE_JSON_PATH, \"utf-8\");\n  const templateGeneratorPackageJson = JSON.parse(\n    await readFile(TEMPLATE_GENERATOR_PACKAGE_JSON_PATH, \"utf-8\"),\n  );\n  const originalTemplateGeneratorPackageJsonString = await readFile(\n    TEMPLATE_GENERATOR_PACKAGE_JSON_PATH,\n    \"utf-8\",\n  );\n  let restored = false;\n\n  try {\n    // Update types package version, build, and publish first\n    typesPackageJson.version = canaryVersion;\n    await writeFile(TYPES_PACKAGE_JSON_PATH, `${JSON.stringify(typesPackageJson, null, 2)}\\n`);\n\n    const typesBuildSpin = spinner();\n    typesBuildSpin.start(\"Building types package...\");\n    try {\n      await $`cd packages/types && bun run build`;\n      typesBuildSpin.stop(\"Types build complete\");\n    } catch (err) {\n      typesBuildSpin.stop(\"Types build failed\");\n      throw err;\n    }\n\n    const typesPubSpin = spinner();\n    typesPubSpin.start(`Publishing @better-t-stack/types@${canaryVersion} (canary)...`);\n    try {\n      await $`cd packages/types && bun publish --access public --tag canary`;\n      typesPubSpin.stop(\"Types package published\");\n    } catch (err) {\n      typesPubSpin.stop(\"Types publish failed\");\n      throw err;\n    }\n\n    // Update template-generator package version and types dependency, build, and publish\n    templateGeneratorPackageJson.version = canaryVersion;\n    templateGeneratorPackageJson.dependencies[\"@better-t-stack/types\"] = canaryVersion;\n    await writeFile(\n      TEMPLATE_GENERATOR_PACKAGE_JSON_PATH,\n      `${JSON.stringify(templateGeneratorPackageJson, null, 2)}\\n`,\n    );\n\n    const templateGeneratorBuildSpin = spinner();\n    templateGeneratorBuildSpin.start(\"Building template-generator package...\");\n    try {\n      await $`cd packages/template-generator && bun run build`;\n      templateGeneratorBuildSpin.stop(\"Template-generator build complete\");\n    } catch (err) {\n      templateGeneratorBuildSpin.stop(\"Template-generator build failed\");\n      throw err;\n    }\n\n    const templateGeneratorPubSpin = spinner();\n    templateGeneratorPubSpin.start(\n      `Publishing @better-t-stack/template-generator@${canaryVersion} (canary)...`,\n    );\n    try {\n      await $`cd packages/template-generator && bun publish --access public --tag canary`;\n      templateGeneratorPubSpin.stop(\"Template-generator package published\");\n    } catch (err) {\n      templateGeneratorPubSpin.stop(\"Template-generator publish failed\");\n      throw err;\n    }\n\n    // Update CLI package version and dependencies\n    packageJson.version = canaryVersion;\n    packageJson.dependencies[\"@better-t-stack/types\"] = canaryVersion;\n    packageJson.dependencies[\"@better-t-stack/template-generator\"] = canaryVersion;\n    await writeFile(CLI_PACKAGE_JSON_PATH, `${JSON.stringify(packageJson, null, 2)}\\n`);\n\n    // Update alias package version\n    aliasPackageJson.version = canaryVersion;\n    aliasPackageJson.dependencies[\"create-better-t-stack\"] = canaryVersion;\n    await writeFile(ALIAS_PACKAGE_JSON_PATH, `${JSON.stringify(aliasPackageJson, null, 2)}\\n`);\n\n    const buildSpin = spinner();\n    buildSpin.start(\"Building CLI...\");\n    try {\n      await $`bun run build:cli`;\n      buildSpin.stop(\"Build complete\");\n    } catch (err) {\n      buildSpin.stop(\"Build failed\");\n      throw err;\n    }\n\n    const pubSpin = spinner();\n    pubSpin.start(\n      `Publishing ${packageName}@${canaryVersion} and create-bts@${canaryVersion} (canary)...`,\n    );\n    try {\n      await $`cd apps/cli && bun publish --access public --tag canary`;\n      await $`cd packages/create-bts && bun publish --access public --tag canary`;\n      pubSpin.stop(\"Publish complete for all packages\");\n    } catch (err) {\n      pubSpin.stop(\"Publish failed\");\n      throw err;\n    }\n\n    if (deprecateOld) {\n      console.log(\"🔎 Cleaning up older canary versions (deprecating)...\");\n      try {\n        const versionsJson = await $`npm view ${packageName} versions --json`.text();\n        const versions = JSON.parse(versionsJson) as string[];\n        const isCanary = (v: string) => v.includes(\"-canary.\") || v.includes(\"+canary.\");\n        for (const v of versions) {\n          if (!isCanary(v) || v === canaryVersion) continue;\n          console.log(`➡️ Deprecating ${packageName}@${v}`);\n          await $`npm deprecate -f ${`${packageName}@${v}`} \"Deprecated canary; use ${packageName}@canary (currently ${canaryVersion})\"`;\n        }\n        console.log(\"🧹 Older canaries deprecated.\");\n      } catch (err) {\n        console.warn(\"⚠️ Failed to deprecate older canaries:\", err);\n      }\n    }\n\n    await writeFile(CLI_PACKAGE_JSON_PATH, originalPackageJsonString);\n    await writeFile(ALIAS_PACKAGE_JSON_PATH, originalAliasPackageJsonString);\n    await writeFile(TYPES_PACKAGE_JSON_PATH, originalTypesPackageJsonString);\n    await writeFile(\n      TEMPLATE_GENERATOR_PACKAGE_JSON_PATH,\n      originalTemplateGeneratorPackageJsonString,\n    );\n    restored = true;\n\n    console.log(`✅ Published canary v${canaryVersion} for all packages`);\n    console.log(`📦 NPM: https://www.npmjs.com/package/${packageName}/v/${canaryVersion}`);\n    console.log(`📦 NPM: https://www.npmjs.com/package/create-bts/v/${canaryVersion}`);\n    console.log(`📦 NPM: https://www.npmjs.com/package/@better-t-stack/types/v/${canaryVersion}`);\n    console.log(\n      `📦 NPM: https://www.npmjs.com/package/@better-t-stack/template-generator/v/${canaryVersion}`,\n    );\n  } finally {\n    if (!restored) {\n      await writeFile(CLI_PACKAGE_JSON_PATH, originalPackageJsonString);\n      await writeFile(ALIAS_PACKAGE_JSON_PATH, originalAliasPackageJsonString);\n      await writeFile(TYPES_PACKAGE_JSON_PATH, originalTypesPackageJsonString);\n      await writeFile(\n        TEMPLATE_GENERATOR_PACKAGE_JSON_PATH,\n        originalTemplateGeneratorPackageJsonString,\n      );\n    }\n  }\n}\n\nmain().catch(console.error);\n"
  },
  {
    "path": "scripts/cleanup-previews.ts",
    "content": "import { confirm, isCancel, multiselect, spinner } from \"@clack/prompts\";\nimport { $ } from \"bun\";\n\nconst PACKAGES = [\n  \"create-better-t-stack\",\n  \"create-bts\",\n  \"@better-t-stack/types\",\n  \"@better-t-stack/template-generator\",\n] as const;\n\ninterface DistTags {\n  [tag: string]: string;\n}\n\nasync function main(): Promise<void> {\n  const args = process.argv.slice(2);\n  const isDryRun = args.includes(\"--dry-run\");\n  const autoYes = args.includes(\"--yes\");\n  const prNumber = args.find((arg) => arg.startsWith(\"--pr=\"))?.split(\"=\")[1];\n\n  console.log(\"PR Preview Cleanup Script\");\n  console.log(\"=========================\\n\");\n\n  if (isDryRun) {\n    console.log(\"[DRY RUN MODE - No changes will be made]\\n\");\n  }\n\n  // If specific PR number provided, clean up just that PR\n  if (prNumber) {\n    await cleanupPR(prNumber, isDryRun);\n    return;\n  }\n\n  // Otherwise, show all PR tags and let user select\n  const allTags = new Map<string, Map<string, string>>();\n\n  const fetchSpin = spinner();\n  fetchSpin.start(\"Fetching PR preview tags from NPM...\");\n\n  for (const pkg of PACKAGES) {\n    try {\n      const tagsJson = await $`npm view ${pkg} dist-tags --json`.text();\n      const tags = JSON.parse(tagsJson) as DistTags;\n\n      for (const [tag, version] of Object.entries(tags)) {\n        const match = tag.match(/^pr(\\d+)$/);\n        if (!match) continue;\n\n        if (!allTags.has(tag)) {\n          allTags.set(tag, new Map());\n        }\n        allTags.get(tag)?.set(pkg, version);\n      }\n    } catch {\n      console.warn(`Failed to fetch tags for ${pkg}`);\n    }\n  }\n\n  fetchSpin.stop(\"Fetched PR preview tags\");\n\n  if (allTags.size === 0) {\n    console.log(\"\\nNo PR preview tags found. Nothing to clean up.\");\n    return;\n  }\n\n  console.log(`\\nFound ${allTags.size} PR preview tag(s):\\n`);\n\n  const tagOptions = Array.from(allTags.entries())\n    .sort((a, b) => {\n      const prA = Number.parseInt(a[0].replace(\"pr\", \"\"), 10);\n      const prB = Number.parseInt(b[0].replace(\"pr\", \"\"), 10);\n      return prB - prA;\n    })\n    .map(([tag, packages]) => {\n      const versions = Array.from(packages.entries())\n        .map(([pkg, ver]) => `${pkg}@${ver}`)\n        .join(\", \");\n      return {\n        value: tag,\n        label: `${tag} (${versions})`,\n      };\n    });\n\n  if (autoYes) {\n    // Clean up all tags\n    console.log(\"Auto-cleaning all PR preview tags...\\n\");\n    for (const { value: tag } of tagOptions) {\n      const prNum = tag.replace(\"pr\", \"\");\n      await cleanupPR(prNum, isDryRun);\n    }\n    return;\n  }\n\n  const selected = (await multiselect({\n    message: \"Select PR tags to clean up:\",\n    options: tagOptions,\n  })) as unknown as string[] | symbol;\n\n  if (isCancel(selected) || !Array.isArray(selected) || selected.length === 0) {\n    console.log(\"\\nNo selections made. Aborting.\");\n    return;\n  }\n\n  const proceed = await confirm({\n    message: `Clean up ${selected.length} PR preview tag(s)?`,\n  });\n\n  if (isCancel(proceed) || proceed === false) {\n    console.log(\"\\nCanceled by user.\");\n    return;\n  }\n\n  for (const tag of selected) {\n    const prNum = tag.replace(\"pr\", \"\");\n    await cleanupPR(prNum, isDryRun);\n  }\n\n  console.log(\"\\nCleanup complete!\");\n}\n\nasync function cleanupPR(prNumber: string, isDryRun: boolean): Promise<void> {\n  const tag = `pr${prNumber}`;\n  console.log(`\\nCleaning up PR #${prNumber}...`);\n\n  for (const pkg of PACKAGES) {\n    try {\n      const versionText = await $`npm view ${pkg}@${tag} version 2>/dev/null`.text();\n      const version = versionText.trim();\n\n      if (!version) {\n        console.log(`  ${pkg}: No version found for tag ${tag}`);\n        continue;\n      }\n\n      console.log(`  ${pkg}@${version} (tag: ${tag})`);\n\n      if (isDryRun) {\n        console.log(`    [DRY RUN] Would remove tag and deprecate`);\n        continue;\n      }\n\n      // Remove the dist-tag\n      try {\n        await $`npm dist-tag rm ${pkg} ${tag}`.quiet();\n        console.log(`    Removed tag ${tag}`);\n      } catch {\n        console.log(`    Tag ${tag} already removed or doesn't exist`);\n      }\n\n      // Deprecate the version\n      try {\n        await $`npm deprecate ${pkg}@${version} \"PR preview for closed PR #${prNumber}\"`.quiet();\n        console.log(`    Deprecated version ${version}`);\n      } catch {\n        console.log(`    Failed to deprecate ${version}`);\n      }\n    } catch {\n      console.log(`  ${pkg}: No preview found`);\n    }\n  }\n}\n\nmain().catch(console.error);\n"
  },
  {
    "path": "scripts/publish-smoke.ts",
    "content": "#!/usr/bin/env bun\n// Verify create-better-t-stack installs and runs under npm, pnpm, and bun.\n// Catches any regression that breaks the published artifact for consumers —\n// unresolved protocol refs, missing files, broken bin entry, import failures\n// from missing transitive deps, etc.\n//\n// Packs each publishable workspace with `npm pack` (matching the release\n// workflow, which uses `npm publish`), installs the CLI tarball in a temp\n// dir using overrides to redirect sibling workspace deps at their local\n// tarballs, then runs `create-better-t-stack --version` to prove the binary\n// actually executes.\n\nimport { mkdirSync, readFileSync, rmSync, writeFileSync } from \"node:fs\";\nimport { tmpdir } from \"node:os\";\nimport { join, resolve } from \"node:path\";\n\nimport { $ } from \"bun\";\n\nconst ROOT = resolve(import.meta.dir, \"..\");\n\ntype Publishable = {\n  name: string;\n  dir: string;\n  // Release workflow rewrites workspace:* → ^<version> via jq before publish.\n  // Mirror that here so the tarball matches what actually ships.\n  rewriteWorkspaceDeps?: string[];\n};\n\nconst PUBLISHABLES: Publishable[] = [\n  { name: \"@better-t-stack/types\", dir: \"packages/types\" },\n  { name: \"@better-t-stack/template-generator\", dir: \"packages/template-generator\" },\n  {\n    name: \"create-better-t-stack\",\n    dir: \"apps/cli\",\n    rewriteWorkspaceDeps: [\"@better-t-stack/types\", \"@better-t-stack/template-generator\"],\n  },\n];\n\nconst green = (s: string) => `\\x1b[32m${s}\\x1b[0m`;\nconst red = (s: string) => `\\x1b[31m${s}\\x1b[0m`;\nconst dim = (s: string) => `\\x1b[2m${s}\\x1b[0m`;\n\nasync function pack(pkg: Publishable, outDir: string): Promise<string> {\n  const pkgJsonPath = join(ROOT, pkg.dir, \"package.json\");\n  const original = readFileSync(pkgJsonPath, \"utf-8\");\n\n  if (pkg.rewriteWorkspaceDeps) {\n    const parsed = JSON.parse(original);\n    for (const bucket of [\n      \"dependencies\",\n      \"devDependencies\",\n      \"peerDependencies\",\n      \"optionalDependencies\",\n    ] as const) {\n      const deps = parsed[bucket];\n      if (!deps) continue;\n      for (const d of pkg.rewriteWorkspaceDeps) {\n        if (deps[d]?.startsWith(\"workspace:\")) {\n          deps[d] = `^${parsed.version}`;\n        }\n      }\n    }\n    writeFileSync(pkgJsonPath, `${JSON.stringify(parsed, null, 2)}\\n`);\n  }\n\n  try {\n    const r = await $`npm pack --pack-destination=${outDir} --json`\n      .cwd(join(ROOT, pkg.dir))\n      .quiet();\n    const [entry] = JSON.parse(r.stdout.toString()) as Array<{ filename: string }>;\n    return join(outDir, entry.filename);\n  } finally {\n    if (pkg.rewriteWorkspaceDeps) writeFileSync(pkgJsonPath, original);\n  }\n}\n\nasync function installAndRun(\n  pm: \"npm\" | \"pnpm\" | \"bun\",\n  tarballs: Record<string, string>,\n  smokeRoot: string,\n) {\n  const dir = join(smokeRoot, `install-${pm}`);\n  rmSync(dir, { recursive: true, force: true });\n  mkdirSync(dir, { recursive: true });\n\n  const overrides = {\n    \"@better-t-stack/types\": `file:${tarballs[\"@better-t-stack/types\"]}`,\n    \"@better-t-stack/template-generator\": `file:${tarballs[\"@better-t-stack/template-generator\"]}`,\n  };\n  const fixture: Record<string, unknown> = {\n    name: `smoke-${pm}`,\n    private: true,\n    version: \"0.0.0\",\n    dependencies: { \"create-better-t-stack\": `file:${tarballs[\"create-better-t-stack\"]}` },\n  };\n  if (pm === \"pnpm\") fixture.pnpm = { overrides };\n  else fixture.overrides = overrides;\n\n  writeFileSync(join(dir, \"package.json\"), JSON.stringify(fixture, null, 2));\n\n  const install = await $`${pm} install --ignore-scripts`.cwd(dir).quiet().nothrow();\n  if (install.exitCode !== 0) {\n    console.error(red(`✗ ${pm} install failed`));\n    console.error(dim(install.stderr.toString()));\n    process.exit(1);\n  }\n\n  // Execute the CLI via its installed bin path to prove it actually works —\n  // not just that the file got linked into node_modules/.bin.\n  const bin = join(dir, \"node_modules\", \".bin\", \"create-better-t-stack\");\n  const run = await $`${bin} --version`.cwd(dir).quiet().nothrow();\n  if (run.exitCode !== 0) {\n    console.error(red(`✗ ${pm}: create-better-t-stack --version failed (exit ${run.exitCode})`));\n    console.error(dim(run.stderr.toString() + run.stdout.toString()));\n    process.exit(1);\n  }\n\n  console.log(green(`✓ ${pm}`) + dim(`  v${run.stdout.toString().trim()}`));\n}\n\nasync function hasPackageManager(pm: string): Promise<boolean> {\n  return (await $`which ${pm}`.quiet().nothrow()).exitCode === 0;\n}\n\nconst smokeRoot = join(tmpdir(), `bts-publish-smoke-${Date.now()}`);\nconst tarballDir = join(smokeRoot, \"tarballs\");\nmkdirSync(tarballDir, { recursive: true });\n\nconsole.log(\"Packing...\");\nconst tarballs: Record<string, string> = {};\nfor (const pkg of PUBLISHABLES) {\n  tarballs[pkg.name] = await pack(pkg, tarballDir);\n  console.log(dim(`  ${pkg.name}`));\n}\n\nconsole.log(\"\\nInstalling and running create-better-t-stack under each package manager...\");\nfor (const pm of [\"npm\", \"pnpm\", \"bun\"] as const) {\n  if (!(await hasPackageManager(pm))) {\n    console.log(dim(`  - ${pm} not available, skipping`));\n    continue;\n  }\n  await installAndRun(pm, tarballs, smokeRoot);\n}\n\nrmSync(smokeRoot, { recursive: true, force: true });\nconsole.log(green(\"\\n✓ publish smoke test passed\"));\n"
  },
  {
    "path": "scripts/release.ts",
    "content": "import { readFile, writeFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\n\nimport { $ } from \"bun\";\nimport { generate } from \"changelogithub\";\n\nimport config from \"../changelogithub.config\";\n\nasync function main(): Promise<void> {\n  const tag = process.env.GITHUB_REF?.replace(\"refs/tags/\", \"\");\n  if (!tag) {\n    console.error(\"No git tag found\");\n    process.exit(1);\n  }\n\n  console.log(`Generating changelog for ${tag}`);\n\n  const changelog = await generate({\n    to: tag,\n    ...config,\n  });\n\n  const changelogPath = join(process.cwd(), \"CHANGELOG.md\");\n  let existingContent = \"\";\n\n  try {\n    existingContent = await readFile(changelogPath, \"utf-8\");\n  } catch {}\n\n  const newChangelog = `## ${tag}\\n\\n${changelog.md}\\n\\n---\\n\\n${existingContent}`;\n  await writeFile(changelogPath, newChangelog);\n\n  await $`git add CHANGELOG.md`;\n  await $`git commit -m \"chore: update changelog for ${tag}\"`;\n  await $`git push`;\n\n  console.log(`✅ Generated changelog for ${tag}`);\n}\n\nmain().catch(console.error);\n"
  },
  {
    "path": "tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    // Environment setup & latest features\n    \"lib\": [\"ESNext\"],\n    \"target\": \"ESNext\",\n    \"module\": \"Preserve\",\n    \"moduleDetection\": \"force\",\n    \"jsx\": \"react-jsx\",\n    \"allowJs\": true,\n\n    // Bundler mode\n    \"moduleResolution\": \"bundler\",\n    \"allowImportingTsExtensions\": true,\n    \"verbatimModuleSyntax\": true,\n    \"noEmit\": true,\n\n    // Best practices\n    \"strict\": true,\n    \"skipLibCheck\": true,\n    \"noFallthroughCasesInSwitch\": true,\n    \"noUncheckedIndexedAccess\": true,\n    \"noImplicitOverride\": true,\n\n    // Some stricter flags (disabled by default)\n    \"noUnusedLocals\": false,\n    \"noUnusedParameters\": false,\n    \"noPropertyAccessFromIndexSignature\": false\n  }\n}\n"
  },
  {
    "path": "turbo.json",
    "content": "{\n  \"$schema\": \"https://turbo.build/schema.json\",\n  \"ui\": \"tui\",\n  \"tasks\": {\n    \"transit\": {\n      \"dependsOn\": [\"^transit\"]\n    },\n    \"build\": {\n      \"dependsOn\": [\"^build\"],\n      \"inputs\": [\"$TURBO_DEFAULT$\"],\n      \"outputs\": [\".next/**\", \"!.next/cache/**\", \"dist/**\", \"templates-binary/**\"]\n    },\n    \"lint\": {\n      \"dependsOn\": [\"transit\"]\n    },\n    \"check\": {\n      \"dependsOn\": [\"transit\"]\n    },\n    \"generate-schema\": {\n      \"cache\": false\n    },\n    \"dev\": {\n      \"cache\": false,\n      \"persistent\": true\n    },\n    \"deploy\": {\n      \"cache\": false\n    },\n    \"deploy:convex\": {\n      \"cache\": false\n    }\n  }\n}\n"
  }
]