Skip to content

btst init: @btst/codegen CLI tool #91

@olliethedev

Description

@olliethedev

Overview

Add a btst init command to the @btst/codegen package that automatically scaffolds all the boilerplate required to integrate BTST into an existing Next.js (App Router), React Router v7, or TanStack Start project.

The install docs currently require ~8 steps of manual file creation and surgical edits to existing files. This command automates the entire flow end-to-end — detecting the framework, picking the right adapter, installing dependencies, generating files, and patching the root layout and CSS.

Think shadcn/ui's npx shadcn init — a single command that gets a project from zero to working without consulting documentation.

npx @btst/codegen init
# or if @btst/codegen is already installed:
codegen init

Core Features

Framework Detection & Selection

  • Auto-detect framework from package.json deps (next, react-router, @tanstack/react-router)
  • Prompt to confirm or override when multiple frameworks are found
  • Detect path alias convention from tsconfig.json paths (@/, ~/, etc.)
  • Detect package manager from lockfile (pnpm-lock.yaml, yarn.lock, package-lock.json)

Adapter Selection

  • Interactive prompt to choose adapter: memory, prisma, drizzle, kysely, mongodb
  • Install the corresponding @btst/adapter-* package automatically

Dependency Installation

  • Install @btst/stack and @tanstack/react-query via detected package manager
  • Install selected @btst/adapter-* package

Plugin Selection & Basic Config

  • Prompt users to select one or more plugins to install during init (e.g. blog, comments, etc.)
  • Install selected plugin package(s) automatically
  • Scaffold basic backend + client plugin registration in lib/stack.ts and lib/stack-client.tsx
  • Add required plugin CSS imports to global stylesheet
  • Generate minimal override/config stubs in the framework layout for each selected plugin

File Generation

  • lib/query-client.ts — shared QueryClient utility (identical across frameworks)
  • lib/stack.ts — backend instance with selected adapter boilerplate
  • lib/stack-client.tsx — client instance stub (createStackClient)
  • Framework-specific API route handler:
    • Next.js: app/api/data/[[...all]]/route.ts
    • React Router: app/routes/api/data/route.ts
    • TanStack: src/routes/api/data/$.ts
  • Framework-specific page handler (catch-all):
    • Next.js: app/pages/[[...all]]/page.tsx
    • React Router: app/routes/pages/index.tsx
    • TanStack: src/routes/pages/$.tsx
  • Framework-specific StackProvider layout:
    • Next.js: app/pages/[[...all]]/layout.tsx
    • React Router: app/routes/pages/_layout.tsx
    • TanStack: src/routes/pages/route.tsx

Surgical File Patching

  • Inject @import \"@btst/stack/...\" into the project's global CSS file (auto-detected or prompted)
  • Wrap root layout / app entry with <QueryClientProvider> — attempted via AST transform (ts-morph), with printed fallback instructions if the file is non-standard
  • Conflict handling for every generated file: skip / overwrite / show diff

Prerequisite Validation

  • Warn (non-blocking) if components.json (shadcn) is absent
  • Warn if Tailwind v4 is not detected in dependencies
  • Warn if <Toaster /> (Sonner) is not present

Package Structure

packages/cli/
├── src/
│   ├── index.ts                      # commander entry — registers init + generate commands
│   ├── commands/
│   │   ├── init.ts                   # btst init — full project bootstrapping
│   │   └── generate.ts               # btst generate — existing DB schema codegen
│   ├── templates/
│   │   ├── shared/
│   │   │   └── lib/query-client.ts   # no templating needed; identical across frameworks
│   │   ├── nextjs/
│   │   │   ├── lib/stack.ts.hbs      # Handlebars template, one variant per adapter
│   │   │   ├── lib/stack-client.tsx.hbs
│   │   │   ├── app/api/data/[[...all]]/route.ts.hbs
│   │   │   ├── app/pages/[[...all]]/page.tsx.hbs
│   │   │   └── app/pages/[[...all]]/layout.tsx.hbs
│   │   ├── react-router/
│   │   │   ├── lib/stack.ts.hbs
│   │   │   ├── lib/stack-client.tsx.hbs
│   │   │   ├── app/routes/api/data/route.ts.hbs
│   │   │   ├── app/routes/pages/index.tsx.hbs
│   │   │   └── app/routes/pages/_layout.tsx.hbs
│   │   └── tanstack/
│   │       ├── lib/stack.ts.hbs
│   │       ├── lib/stack-client.tsx.hbs
│   │       ├── src/routes/api/data/$.ts.hbs
│   │       ├── src/routes/pages/$.tsx.hbs
│   │       └── src/routes/pages/route.tsx.hbs
│   └── utils/
│       ├── detect-framework.ts        # sniff package.json + folder layout
│       ├── detect-package-manager.ts  # lockfile detection
│       ├── detect-alias.ts            # read tsconfig.json paths
│       ├── detect-css-file.ts         # find globals.css / app.css
│       ├── file-writer.ts             # write + skip/overwrite/diff conflict UX
│       ├── package-installer.ts       # run pnpm/npm/yarn add
│       ├── css-patcher.ts             # inject @import line safely
│       └── layout-patcher.ts          # ts-morph AST wrap for QueryClientProvider
├── package.json
├── tsconfig.json
└── build.config.ts

Command Interface

btst init [options]

Options:
  --framework <name>    Skip detection prompt (nextjs | react-router | tanstack)
  --adapter <name>      Skip adapter prompt (memory | prisma | drizzle | kysely | mongodb)
  --skip-install        Skip dependency installation
  --cwd <path>          Run in a different directory (default: process.cwd())
  --yes                 Accept all defaults / overwrite all conflicts without prompting
  -h, --help            Show help

Interactive Prompt Flow

◆ Which framework are you using?
  ● Next.js (App Router)
  ○ React Router v7
  ○ TanStack Start

◆ Which database adapter?
  ● Memory (development / testing)
  ○ Prisma
  ○ Drizzle
  ○ Kysely
  ○ MongoDB

◆ Where is your global CSS file?
  app/globals.css

Installing dependencies with pnpm...
  + @btst/stack
  + @tanstack/react-query
  + @btst/adapter-memory

Writing files...
  ✔ lib/query-client.ts
  ✔ lib/stack.ts
  ✔ lib/stack-client.tsx
  ✔ app/api/data/[[...all]]/route.ts
  ✔ app/pages/[[...all]]/page.tsx
  ✔ app/pages/[[...all]]/layout.tsx

Patching files...
  ✔ app/globals.css       added @import \"@btst/stack/style.css\"
  ✔ app/layout.tsx        wrapped children with QueryClientProvider

◆ All done!

Next steps:
  → Add plugins to lib/stack.ts and lib/stack-client.tsx
  → Generate database schema: npx @btst/codegen generate --config=lib/stack.ts --orm=memory
  → Visit /pages to see plugin routes

Generated File Examples

lib/stack.ts (memory adapter, Next.js)
import { stack } from \"@btst/stack\"
import { createMemoryAdapter } from \"@btst/adapter-memory\"

let _stack: ReturnType<typeof stack> | undefined

function getStack() {
  if (!_stack) {
    _stack = stack({
      basePath: \"/api/data\",
      plugins: {
        // Add your backend plugins here
      },
      adapter: (db) => createMemoryAdapter(db)({}),
    })
  }
  return _stack
}

export const myStack = {
  get handler() { return getStack().handler },
  get api()     { return getStack().api },
  get adapter() { return getStack().adapter },
}
lib/stack-client.tsx
import { createStackClient } from \"@btst/stack/client\"
import type { QueryClient } from \"@tanstack/react-query\"

export const getStackClient = (queryClient: QueryClient) =>
  createStackClient({
    plugins: {
      // Add your client plugins here
    },
  })

Detection Heuristics

Signal What it determines
package.json dep: next Framework = Next.js
package.json dep: react-router Framework = React Router
package.json dep: @tanstack/react-router Framework = TanStack
pnpm-lock.yaml present Package manager = pnpm
yarn.lock present Package manager = yarn
tsconfig.json paths @/* Alias = @/
tsconfig.json paths ~/* Alias = ~/
components.json absent Warn: shadcn not installed

Layout Patching Strategy

The root layout patch (QueryClientProvider wrap) uses ts-morph to parse the TSX AST and find the JSX return value. If patching succeeds silently, great. If the file is non-standard (class component, unusual structure), the patcher prints a clear diff block with manual instructions rather than failing:

⚠ Could not automatically patch app/layout.tsx.
  Add the following manually:

  import { QueryClientProvider } from \"@tanstack/react-query\"
  import { getOrCreateQueryClient } from \"@/lib/query-client\"

  // Wrap your root {children} with:
  <QueryClientProvider client={getOrCreateQueryClient()}>
    {children}
  </QueryClientProvider>

Non-Goals (v1)

  • Scaffolding a brand-new framework project (wrapping create-next-app etc.) — init targets existing projects only
  • Multi-monorepo-workspace awareness
  • Adding plugins via btst init (that is a separate btst add command concern)
  • Windows-specific path handling beyond basic normalisation
  • Non-TypeScript projects

Implementation Notes

  • Use @clack/prompts for the interactive prompt UX (same as shadcn CLI)
  • Use commander for argument parsing
  • Use ts-morph for AST-based layout patching
  • Use handlebars for file templates (adapter × framework matrix)
  • Use execa for running package manager commands
  • Build with tsup or unbuild; ship as CJS + ESM with a bin entry

Package Configuration

{
  \"name\": \"@btst/codegen\",
  \"bin\": { \"btst\": \"./dist/index.cjs\" },
  \"scripts\": {
    \"build\": \"unbuild --clean\",
    \"dev\": \"tsx src/index.ts\"
  }
}

Documentation

Add docs/content/docs/cli.mdx (or extend the existing CLI page) covering:

  • @btst/codegen init — full walkthrough
  • Supported frameworks — Next.js, React Router, TanStack Start
  • Supported adapters — all five, with links to adapter docs
  • --yes flag — CI/CD usage
  • Manual patching fallback — what to do when auto-patch fails

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions