Skip to content

Start here — setup, run & debug

New to the codebase and picking up a ticket? This page gets you from a fresh clone to a running store you can change and debug. (To understand the system, read the book from Chapter 1; this is the practical companion.)

Prerequisites

  • Docker (for Postgres + MinIO), Bun (package manager / task runner), Node 22 (the test + dev runner).
  • The repo's own README.md at the root is the canonical setup note — start there if anything here drifts.

Run the whole stack

bash
bun install                # install workspace deps
bun run dev                # dev/all.ts — the one command that boots everything

bun run dev (dev/all.ts) does it all: docker compose up (Postgres :5433, MinIO :9000) → migrate → ensure the bucket → then three processes, all with RATIO_LOCAL=true:

ServicePortWhat
storefront:8080 / :9090edge Worker (:8080) + origin (:9090)
admin-api:8787the control plane
admin-web:5173the merchant/admin SPA (Vite)

Ctrl-C stops the three services; the DB container stays up (bun run db:down to drop it). If Postgres was created by an older major version, dev/all.ts recreates the volume for you.

Create a store and view it

There are no seeded stores — a store comes from onboarding:

  1. Open the SPA at http://localhost:5173 and sign in (dev Clerk).
  2. Create a store via the onboarding wizard (/stores/new) — it publishes a live store on the default theme. (Scriptable alternative: scripts/onboard.ts — see Ops scripts.)
  3. View its storefront on the edge (:8080). Local multi-tenancy is keyed by host, so use either the store's <id>.localhost:8080 alias or the localhost-only override http://localhost:8080/?store=<tenantId> (the ?store= override is gated to localhost — see Edge internals).
  4. Make a trivial theme edit in the code editor, publish, and refresh the storefront.
  5. bun run reset:local truncates app tables for a clean re-onboard; bun run recover re-heals the base library after a DB/object-store wipe.

Validate before you push

PRs get no CI — you are the pre-merge gate. Run the same four checks CI runs:

bash
bun run typecheck && bun run lint && bun run format:check && bun run test

Husky runs lint-staged (eslint --fix + prettier) on commit. See Testing & CI for the full picture, including the two test runners.

Two traps CI won't save you from

  • PRs run zero CI (push-to-main only) — a broken change passes review if you didn't run the checks locally.
  • CI has no MinIO, so ~8 object-store tests skip silently (green ≠ they ran). Run them locally with MinIO up and BUNDLE_S3_ENDPOINT set. And CI Postgres is :5432, local Docker is :5433 — read DATABASE_URL, never hardcode a port.

Run one test (not the whole suite)

Backend/package tests use node:test (not Bun), so a single file is:

bash
DATABASE_URL=postgres://poc:poc@localhost:5433/s2poc_test \
node --import tsx --import ./tests/bootstrap.ts --test packages/builder-render/src/__tests__/liquid-render.test.ts

bootstrap.ts must be --import'd (it configures the DB pool + sets EDGE_SECRET; without it a test fails closed on DATABASE_URL/the edge secret). Object-store tests also need BUNDLE_S3_ENDPOINT=http://localhost:9000 (+ BUNDLE_S3_BUCKET/KEY/SECRET). The SPA suite is bun run --cwd apps/admin-web test (Vitest).

Debug — is this page cached or rendered?

Storefront pages are cacheable, so "it didn't change" is usually a cache question:

  • x-ratio-stale: 1 on a response = the edge served a stale copy (origin was down/slow). Its absence + a fresh body = a real render or a fresh cache hit.
  • /__stats on the origin (:9090) is a render counter; the edge (:8080) has /__admin/stats (cache hits). Hit a page 5× and watch: a cacheable page should show 1 render + 4 hits (this is what bun run prove:s1 asserts).
  • A page that forgot s-maxage is silently never cached — every request hits the origin (a cost/perf regression, no error). See Rendering & themes · the render cycle.

Reproduce a per-shopper flow (cart / account / checkout)

These paths are no-store and per-shopper, so they bypass the cache and the prove harnesses — reproduce them by sending the right cookie to the origin/edge:

  • /account is login-gated: without a KwikPass cookie it 302s to /. To hit the signed-in path, send the access cookie rt_kp_at=<token> (its value is the GoKwik access token). See Commerce internals · account.
  • /cart reads the httpOnly server cart cookie rt_cart; add-to-cart is POST /cart/add.

Logs

Every service logs one-line JSON ({ lvl, svc, time, reqId, … }); the edge emits one access log per request (tenant, status, stale, ms, pathname). To trace a failing local request, grab its reqId from the response's x-request-id and grep the service output for that id. Full model: Observability.