> ## Documentation Index
> Fetch the complete documentation index at: https://docs.vibestrap.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Dev tooling

> The opinionated stack — Biome, Knip, Drizzle, Vitest, next-safe-action, server-only, t3-env — chosen so the verification loop stays fast.

Vibestrap is opinionated about dev tooling because indie hackers don't have
time to babysit a build. Everything is either fast (Biome over ESLint+Prettier)
or load-bearing (`server-only` keeps secrets out of the client bundle). This
page covers what's installed, what each tool does, and the mistakes that bite.

## Prerequisites

* Node.js 20 or newer (see `engines` in `package.json`).
* pnpm 10 (`corepack enable && corepack prepare pnpm@10.26.1 --activate`).
* A Postgres database for `db:push` / `db:migrate`.

## The stack

### Biome 2 — linter + formatter in one binary

Replaces ESLint and Prettier with a single Rust binary that's roughly 10x
faster. Config lives in `biome.json`.

```bash theme={null}
pnpm lint        # biome check .
pnpm lint:fix    # biome check --write .
pnpm format      # biome format --write .
```

Style is single quotes, ES5 trailing commas, 100-column width. Toggle rules
in `biome.json` under `linter.rules`.

### Knip 6 — unused-export detection

Finds dead exports, unused dependencies, and orphaned files. Run before
shipping a refactor.

```bash theme={null}
pnpm knip
```

Config lives in `knip.config.ts`. Add false-positives to its `ignore` /
`ignoreDependencies` lists.

### Drizzle ORM 0.45 — type-safe SQL

Schema split across `src/db/{auth,app,affiliate,ai,license}.schema.ts`. IDs
are `text` with nanoid prefixes (e.g. `lic_xxx`), never `serial`.

```bash theme={null}
pnpm db:push        # dev — sync schema to DB without a migration file
pnpm db:generate    # prod — generate a SQL migration from schema diff
pnpm db:migrate     # prod — apply pending migrations
pnpm db:studio      # visual DB explorer at localhost:4983
```

### Vitest 4 — unit tests

Tests live in `tests/unit/**` and `src/**/*.{test,spec}.ts`. Node environment,
globals enabled, `@/` alias resolved.

```bash theme={null}
pnpm test           # one-shot run
pnpm test:watch     # watch mode
```

### next-safe-action 3-tier — typed server actions

Every mutation goes through one of three clients in `src/lib/safe-action.ts`:

```ts theme={null}
actionClient        // public, no auth
userActionClient    // requires logged-in user (provides ctx.user)
adminActionClient   // requires user.role === 'admin'
```

Each action declares an input schema with Zod, returns a typed result.

### server-only — runtime guard against client leaks

Modules that touch the DB or secrets `import 'server-only'` at the top. If
they're accidentally imported into a client component, the build fails with
a clear error instead of shipping your `DATABASE_URL` to the browser.

Already protected: `src/db/index.ts`, `src/lib/auth.ts`,
`src/payment/provider/*`, `src/credits/server.ts`, `src/github/invite.ts`.

### @t3-oss/env-nextjs — typed env vars

`src/env.ts` declares every required and optional env var with Zod. The app
refuses to boot when a required var is missing, so misconfiguration fails at
build time instead of in production at 3 AM.

Always import from `@/env` (not `process.env.X`) so you get types and
validation.

## Verify it works

Before every commit:

```bash theme={null}
pnpm typecheck && pnpm lint && pnpm build
```

If any of the three fails, the error message tells you what to fix. Don't
push code that fails any of them — the production CI runs the same chain.

## Common pitfalls

1. **Forgetting `import 'server-only'`** in a new module that touches secrets.
   Add it as the first line of any file that reads `env.STRIPE_SECRET_KEY`,
   queries the DB, or hits an API key. The build will catch the leak.
2. **`process.env.X` instead of `env.X`.** You skip Zod validation, lose
   types, and break in production when the var is misspelled. There's no
   ESLint rule against it — use code review or grep for `process\.env\.`.
3. **Calling a `userActionClient` action from an unauthenticated route.** It
   throws `UNAUTHORIZED` and returns 401. Wrap the call in a session check or
   use `actionClient` if the action is genuinely public.
4. **`pnpm db:push` against production.** It diffs the schema and applies
   destructive changes (drops, renames) without a migration file. Use
   `db:generate` + `db:migrate` for prod, and pass `--strict` if you want a
   confirmation prompt before destructive ops.
5. **Knip false positives.** Skill registries and dynamically-imported
   providers look unused to static analysis. Add them to `knip.config.ts`'s
   `ignore` list — don't delete the file.

## Official docs

* Biome: [biomejs.dev](https://biomejs.dev/)
* Knip: [knip.dev](https://knip.dev/)
* Drizzle ORM: [orm.drizzle.team](https://orm.drizzle.team/)
* Vitest: [vitest.dev](https://vitest.dev/)
* next-safe-action: [next-safe-action.dev](https://next-safe-action.dev/)
* t3-env: [env.t3.gg](https://env.t3.gg/)
