You are about to get both apps running against your own accounts. This page covers your machine. The following pages explain each provider integration before deployment.
Everything here is reported rather than enforced. Skip Stripe and you still reach a running app, with the checkout route answering 501.
What you need
| Tool | Version | Why |
|---|---|---|
| Node | 22.12.0 or newer | engines in the root package.json |
| pnpm | 11.25.0 | pinned by packageManager |
| git | any |
Do not run this repo through corepack. pnpm refuses to self-switch under it and the packageManager check then fails inside nested turbo tasks. Install pnpm directly and invoke pnpm directly.
The short version
-
Clone and install
git clone git@github.com:<owner>/<repo>.git cd <repo> pnpm installOne lockfile covers all four packages.
@ffd/sharedhas no build step, so there is nothing to compile before the apps can import it. -
Fill in the two env files
apps/api/.envandapps/web/.env.developmentare committed encrypted by dotenvx (docs/adr/0050-env-files-are-committed-encrypted.md). If you have the repository's.env.keys, put it at the repository root and skip the copy below:pnpm devdecrypts both files. Otherwise start from the examples:cp apps/api/.env.example apps/api/.env cp apps/web/.env.example apps/web/.env.developmentBoth example files ship with local defaults. Run
pnpm env:encryptafter you fill them in. It encrypts every plaintext value and creates.env.keyson the first run. Keep that file out of git and share it only with people who run the app.SETUP.mddirects an agent to obtain the account-backed values through each provider's supported tools and write them directly without exposing credentials in chat or terminal output. The steps only you can take, such as choosing an account or copying a dashboard-only key, run in your terminal throughpnpm setup:wizard, which opens each page, takes secrets through hidden input, and checks them against the provider. The provider pages explain those values and the human handoffs. The full list, with which app reads each one, is Environment variables. -
Create the database and run the migrations
pnpm db:migrateThis creates the target database if it is not there, then applies every migration under
packages/shared/drizzle/. It resolves to the development database unless you say otherwise, so migrating production isNODE_ENV=production pnpm db:migrate, typed on purpose.Nothing migrates at boot, so this is a step you run rather than something a deploy does for you.
-
Run both apps
pnpm devTurbo runs
vite devfor the web app on port 3000 andtsx watchfor the api on port 5001. Both reload on save, including changes insidepackages/shared. -
Check that the api agrees with you
curl http://localhost:5001/health{ "status": "ok", "service": "api", "uptimeSeconds": 12, "database": "ffd_dev", "stripeEnv": "test" }databaseis the resolved database name, never the URL, which carries a password.stripeEnvisnullwhen no Stripe key is configured, which is a supported state. Those two fields are the two environment axes, and this endpoint is where you find out they disagree.
Let an agent do it
SETUP.md at the repository root is the agent's end-to-end guide. It covers repository work and external provisioning, using CLIs and APIs first and authenticated browser control where needed. It pauses only for account control, consent, approval, or an ambiguous resource choice, then continues the same sequence.
It is a guide, not a gate. Nothing in it is required, and a buyer who skips a section still reaches a running app.
The commands that gate a change
pnpm check # Biome, formatting and lint
pnpm typecheck # tsc --noEmit across every package
pnpm test # Vitest, the api's webhook and Entitlement tests
Run pnpm test as well. The api's Vitest suite checks webhook signatures and the Entitlements each payment state grants. The Entitlement tests need Postgres and are skipped unless TEST_DATABASE_URL names a bootstrap database; the suite creates and migrates its own ffd_test database there. A change that has not passed all three commands is not finished.
.github/workflows/ci.yml runs the same gates on every pull request against a throwaway Postgres, together with the web build and pnpm audit --prod --audit-level high. .github/dependabot.yml opens weekly update pull requests for versions at least 14 days old.
pnpm check:fix writes the formatting fixes rather than reporting them.
What runs where
| Command | What it does |
|---|---|
pnpm dev |
both apps, watching |
pnpm build |
vite build for web, nothing for the api |
pnpm db:generate |
writes the next migration from the schema diff |
pnpm db:migrate |
creates the database if needed, then migrates |
pnpm check |
Biome |
pnpm typecheck |
tsc --noEmit |
The api has no build step. It runs under tsx in production too, started by pnpm --filter @ffd/api start.
Related
docs/adr/0009-drizzle-v1-on-neon-postgres.md, why nothing migrates at bootdocs/adr/0010-typescript-7-and-biome.md, the toolchain
