Setup
Create a product repository and run the complete stack locally.
Setup
This is the supported path from delivered source to a custom local product. It requires Postgres but no third-party account or API key.
Pinned prerequisites
Use the repository-pinned versions, not whatever happens to be globally current:
| Tool | Required version | Repository authority |
|---|---|---|
| Node | v24 (LTS line) | .node-version |
| Bun | 1.3.14 | .bun-version |
| pnpm | 11.18.0 | package.json |
| Vite+ | 0.2.7 | package.json |
| PostgreSQL | 18 | docker-compose.yml |
Docker Desktop or a compatible Docker engine is required for the supported local database. dbmate is installed in the server workspace and does not need a separate global install.
Verify the tools before scaffolding:
node --version
bun --version
pnpm --version
vp --version
docker versionCreate a product repository
Released buyer path
After accepting the private GitHub repository invitation for your purchase, install GitHub CLI and authenticate the entitled account once:
gh auth loginThen run the public bootstrapper. It contains no paid source and never asks you to paste a GitHub token:
cd /path/to/parent
pnpm dlx superslate@latest paid-monitor
cd paid-monitorbunx superslate@latest paid-monitor is equivalent. The CLI version selects the same immutable
source tag, downloads its release archive, checksum, and manifest through the authenticated GitHub
CLI session, verifies them, applies the chosen scope and brand, installs the frozen dependency
graph, and optionally initializes Git. Use an exact version instead of latest when reproducing a
specific source release.
Pre-release and maintainer path
Keep the delivered source directory unchanged so it remains a clean upgrade/reference checkout. Run the local scaffold from its parent directory; the generated product must be a sibling, not a child of the template. This remains the supported path until the first commercial source tag and matching npm bootstrapper are published:
cd /path/to/parent
bun ./foundation-source/packages/create-app/src/cli.ts paid-monitor \
--from ./foundation-source \
--brand "Paid Monitor" \
--git
cd paid-monitorReplace the example directory and brand with the intended product values. The scaffold derives the
internal workspace namespace from the brand (Paid Monitor becomes @paid-monitor); use the
advanced --scope option only when an automation contract requires a different value. The namespace
groups private packages and does not need to exist on npm. The scaffold:
- copies only buyer-owned product files;
- excludes the commercial landing app, private plans, historical Phase 2 notes, evaluation runs, build output, dependency directories, and source-control history;
- rewrites the root package name and workspace scope;
- replaces the source brand only on the counted brand manifest;
- creates ignored root, server, web, and docs environment files;
- generates a unique 32-byte Better Auth secret;
- optionally creates an initial Git repository and commit;
- automatically installs dependencies unless
--no-installis selected. - offers to start Postgres and every development server immediately after creation.
It refuses a non-empty target. Do not point it at an existing project or run --from . while the
target would be inside the source directory.
Install and start
Accepting the CLI's Start local development now? prompt runs Compose and Vite+ from the generated
directory automatically. For non-interactive automation, pass --start; use --no-start when the
command must finish after generation. --yes does not start a long-running development process
unless --start is also present. Ctrl+C stops the application development servers, while the
detached Postgres container remains available for the next run. Stop it with docker compose down
from the generated directory.
From the generated repository, skip the install command when the CLI already completed it:
vp install --frozen-lockfile
docker compose up -d postgres
vp run --filter @app/server devIn a second terminal:
vp run --filter @app/web devThe separate documentation application is optional during product development. Run it in a third terminal when editing buyer documentation:
pnpm --filter @app/docs devOpen http://localhost:3002/docs. See Operate the documentation site before
publishing it.
Compose scopes the database container to the generated project. The scaffold selects the first
available port starting at 5432, records it in the ignored root .env, and writes the same port
into apps/server/.env. The normal command therefore remains:
docker compose up -d postgresUse the namespace derived during scaffolding. Open http://localhost:5173. The API listens on
http://localhost:8000; the CLI completion summary reports the selected Postgres port.
The server must connect to Postgres, apply pending dbmate migrations, register event handlers, and preload all rendered email templates before it binds the port. Verify both operational endpoints:
curl --fail http://localhost:8000/health
curl --fail http://localhost:8000/ready/health proves the process can answer. /ready proves the initialized database can answer. A
successful health response with failed readiness is not a successful setup.
Zero-key behavior
With the generated environment files unchanged:
- password signup, email verification, password reset, and magic-link sign-in work;
- email bodies and action links are written to the local server log;
- Google sign-in is disabled;
- billing checkout and customer portal actions are disabled;
- object-storage upload endpoints return a stable unavailable response;
- Slack alerts and Sentry delivery are disabled.
Never use the email-log fallback in production. Production environment validation requires
RESEND_API_KEY.
Optional integrations are atomic. Select one billing adapter and configure all of its required values, or leave billing unset. Configure every selected storage provider value together. Partial billing or storage configuration fails at startup.
Demo sign-in during local development
Repeated sign-in during development is avoidable. Seed a verified local account and enable a
one-click button on /auth/login:
pnpm dev:server # the seed script calls the running API
pnpm seed:demo # creates demo@example.com and marks it verifiedThen set both values in apps/web/.env:
VITE_DEV_LOGIN_EMAIL=demo@example.com
VITE_DEV_LOGIN_PASSWORD=demopassword123The button signs in through the normal Better Auth password route; it adds no server endpoint and no
authentication bypass. It renders only when import.meta.env.DEV is true, so vp build removes the
control and both values from the bundle even when they are present in the build environment.
pnpm seed:demo refuses any API origin outside localhost. Override the defaults with
DEMO_EMAIL, DEMO_PASSWORD, or DEMO_API_URL.
First verification
Run the same deterministic path used by CI:
vp check
vp run -r typecheck
vp run -r test
vp run -r buildvp run -r test intentionally skips real Postgres integration unless both opt-in variables are set.
Run the database suite against a disposable migrated database:
RUN_DB_INTEGRATION_TESTS=1 \
DATABASE_URL=postgresql://app:app@localhost:5432/app?sslmode=disable \
bun test --cwd apps/serverSee Testing for isolation rules and Deployment for the compiled binary, Docker, Railway or Dokploy, Vercel, production email, and rollback proof.
Pinned-toolchain risk and escape hatches
Vite+ 0.x and TypeScript 7 are intentional early-adopter choices. They provide one fast workspace
command surface and current language/tooling behavior, but their APIs, plugin compatibility, and
diagnostics can change faster than stable major releases.
Operational rules:
- keep
pnpm-lock.yaml, Node, Bun, pnpm, and Vite+ pinned together; - do not accept automated major or beta upgrades without a fresh scaffold, CI, build, PWA, Sentry, and deployment proof;
- reproduce a Vite+ failure with the owning package command before changing architecture;
- retain package-native commands as the recovery path:
bun test --cwd apps/server
pnpm --filter @app/server exec tsc -b --noEmit
pnpm --filter @app/web exec tsc -b --noEmit
pnpm --filter @app/web exec vitest run
pnpm --filter @app/server run build
(cd apps/web && pnpm dlx vite@8.1.5 build --mode production)The final command is an online diagnostic escape hatch: it changes into the web workspace and runs
the exact underlying official Vite version without changing the lockfile. Running it from the
monorepo root is invalid because the root has no index.html. It has been verified against the
current web config, but it does not replace the Vite+ release command or prove Vite+ orchestration.
If it passes while vp build fails, restore the last green pinned Vite+ version/lockfile and
diagnose the orchestrator instead of rewriting the application. Changing Vite+, Vite, TypeScript,
Node, or Bun is a release-engineering change, not routine dependency maintenance.
Failure guide
| Symptom | Check |
|---|---|
vp install changes the lockfile | Use pnpm 11.18.0 and --frozen-lockfile; do not accept the diff. |
| Server cannot connect to Postgres | Compare DATABASE_URL with Compose and any POSTGRES_PORT override. |
/health works but /ready returns 503 | Database initialization, migration output, and Postgres health. |
| Server exits before binding | Missing migrations/templates, invalid environment, or failed DB. |
| No email arrives locally | Read structured server logs; local delivery does not call Resend. |
| Google, billing, upload, Slack, or Sentry off | Expected when its complete optional configuration is absent. |
| Browser requests return 401 | API origin, CORS origin, Better Auth cookie, and credentials policy. |
| Generated code keeps source scope or brand | Re-run scaffold tests; do not perform an unbounded text replacement. |
Setup acceptance
Setup is complete only when:
- the product is a generated sibling repository with its own unique auth secret and clean Git history;
- frozen install, checks, tests, and builds pass without editing generated or lock files;
- Postgres starts from the included Compose contract and
/readyreturns 200; - a password signup can be verified from the local email log;
- a magic link works once and replay fails;
- disabled integrations fail clearly without blocking basic local use.