SsuperslateDocs

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:

ToolRequired versionRepository authority
Nodev24 (LTS line).node-version
Bun1.3.14.bun-version
pnpm11.18.0package.json
Vite+0.2.7package.json
PostgreSQL18docker-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 version

Create 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 login

Then 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-monitor

bunx 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-monitor

Replace 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-install is 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 dev

In a second terminal:

vp run --filter @app/web dev

The separate documentation application is optional during product development. Run it in a third terminal when editing buyer documentation:

pnpm --filter @app/docs dev

Open 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 postgres

Use 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 verified

Then set both values in apps/web/.env:

VITE_DEV_LOGIN_EMAIL=demo@example.com
VITE_DEV_LOGIN_PASSWORD=demopassword123

The 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 build

vp 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/server

See 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

SymptomCheck
vp install changes the lockfileUse pnpm 11.18.0 and --frozen-lockfile; do not accept the diff.
Server cannot connect to PostgresCompare DATABASE_URL with Compose and any POSTGRES_PORT override.
/health works but /ready returns 503Database initialization, migration output, and Postgres health.
Server exits before bindingMissing migrations/templates, invalid environment, or failed DB.
No email arrives locallyRead structured server logs; local delivery does not call Resend.
Google, billing, upload, Slack, or Sentry offExpected when its complete optional configuration is absent.
Browser requests return 401API origin, CORS origin, Better Auth cookie, and credentials policy.
Generated code keeps source scope or brandRe-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 /ready returns 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.

On this page