Installation
Start with the local-app lane: Next.js development, a local SQLite file, development/password sign-in, local content and the free personal workspace. This baseline needs no organization membership or Cloudflare, OAuth, SMTP, payment, analytics, CMS or DevReview account. Choose the core composition to stop new sales and organization enrollment. Demo/mock checkout is unavailable for real purchases and never grants paid access. Dependency installation still needs access to trusted npm packages.
| Lane | Default URL | Purpose |
|---|---|---|
local-app | http://localhost:3101 | Fast Next.js development and the canonical Playwright lane |
local-worker | http://localhost:8787 | Optional local Wrangler runtime checks |
remote-cloudflare | Your own preview/production domain | Explicit remote deployment |
Install your template copy
This is a commercial, closed-source developer template. Start from the source copy supplied to you; the public preview does not provide source-repository access. Use Node.js 24 LTS, the declared npm 10.9.2, Git, and Bash on macOS or Linux. Check node --version and npm --version before installing; Node’s bundled npm may differ from the declared version. From the root of your own source copy, install its locked dependencies:
npm ciThe default dependency graph does not require the private DevReview packages. Preserve lockfile integrity; a checksum failure is a failed install, not a reason to replace the expected checksum with a downloaded artifact’s checksum.
Before creating local environment files, you can verify the installation with the maintained baseline:
npm run test:baseline -- static
npm run test:baseline -- adoptionThis generates Velite content, runs lint and type checks, and builds Next.js plus the Pagefind search index. It uses synthetic configuration without a database or provider credentials. The runner requires Node 24 and refuses auto-loaded local environment files; use a separate checkout if you already have local configuration. A successful static lane does not verify login, database initialization, external providers, or deployment. Run npm run test:baseline -- --help for the separate test lanes.
The bounded adoption lane checks product selections, retained navigation and disabled operations, runtime/diagnostic environment agreement, safe diagnostic output, the reading-list SQLite example and Decision Log transitions/0038 upgrades on disposable libSQL and local D1. Local D1 requires workerd and loopback access. The lane includes invalid Usage limits and production-secret rules, uses synthetic inputs, and does not contact external providers or run the full application suite. The existing CI runs the same explicit test list. Authentication guide examples are typechecked by static; fresh Auth.js identity and revocation behavior remain in domain.
Configure local-app
Copy the public example after running any configuration-free baseline checks:
cp .env.local.example .env.localGenerate a local Auth.js secret without changing an env file automatically:
node -e "console.log(require('node:crypto').randomBytes(32).toString('base64'))"Set that value in .env.local and keep these local settings:
APP_URL="http://localhost:3101"
AUTH_SECRET="your-generated-local-secret"
AUTH_TRUST_HOST="1"
DATABASE_URL="file:./dev.db"
PAYMENT_PROVIDER="mock"
DEV_AUTH_EMAIL="member@example.test"
DEV_AUTH_PASSWORD="dev-password"
DEV_AUTH_NAME="Local Member"Use .env.example as the optional-variable reference. Local database commands read only .env and .env.local, then apply shell overrides: default < .env < .env.local < shell. An explicit empty DATABASE_URL is invalid. The default when it is absent is file:./dev.db.
The resolved file must stay inside this project directory. Remote URLs, escaping paths, symlink aliases and any nonempty CLOUDFLARE_ACCOUNT_ID, CLOUDFLARE_D1_DATABASE_ID or CLOUDFLARE_D1_TOKEN cause rejection before database writes. Keep remote credentials in their own lane files; inspect the named input and its source when a conflict is reported. Local commands never silently remove conflicting credentials.
Inspect configuration without side effects
Before initializing the database, select your application composition in src/config/product.ts, then run the read-only adoption diagnostic from the project root. A core deployment uses ...productCompositions.core; the shipped live purchase selection reports blocked until its required configuration is supplied:
npm run adoption:check -- --lane local-appIt reads the chosen lane’s local configuration and the single product declaration. It does not create a database, inspect its schema, seed records, start the app, load provider SDKs, contact a service, or deploy. Existing database files remain unchanged. A missing database is still SCHEMA_UNVERIFIED, with initialization as the next step.
| Situation | Command | Result |
|---|---|---|
| Learn the interface without configuration | npm run adoption:check -- --help | Help and exit 0; no project configuration import or read |
| Inspect local-app inputs | npm run adoption:check -- --lane local-app | Text checks with safe reason codes and key names |
| Inspect local-worker inputs | npm run adoption:check -- --lane local-worker | Local file checks; actual Worker bindings remain unverified |
| Inspect preview inputs as JSON | TEMPLATE_ADOPTION_LANE=preview npm run --silent adoption:check -- --format json | One JSON document on stdout; no remote connection |
| Override an inherited lane for one run | TEMPLATE_ADOPTION_LANE=preview npm run adoption:check -- --lane production | Inspect the production lane’s local input file only |
--lane accepts local-app, local-worker, preview or production and defaults to unset. --format accepts text or json, defaulting to text. Explicit CLI options override TEMPLATE_ADOPTION_LANE and TEMPLATE_ADOPTION_FORMAT in the shell, including invalid lower-priority values. These selectors are not loaded from env files. No arguments and no selector variables show help; running with a format but no lane, unknown options, positionals, or invalid/empty selector values returns exit 2. --help takes precedence over all other inputs.
| Lane | Input precedence, lowest to highest |
|---|---|
local-app | Domain defaults → .env → .env.local → shell |
local-worker | Domain defaults → .dev.vars.dev → shell |
preview | Domain defaults → .env.cloudflare.preview → shell |
production | Domain defaults → .env.cloudflare → shell |
Private feedback uploads require the Assets private-store policy, a separate private bucket and Images binding. Public R2/S3 credentials never establish that readiness. With attachments selected, a plain Node diagnostic reports missing private binding inputs; Worker lanes without request context remain unknown. The operator observation can describe present private binding conditions, but does not probe remote storage or establish permission. Disabling new uploads does not revoke access to historical attachments.
Absent lane files are optional when the shell supplies the required inputs; unreadable files produce TOOL_FAILURE. Adoption requires APP_URL and a nonempty AUTH_SECRET. The diagnostic and runtime share the pure src/env/contract.ts declarations, including Usage and the inspected NODE_ENV’s production-secret rule; diagnostics also apply guarded local-app target rules. Invalid optional settings remain errors even when their capability is disabled or SKIP_ENV_VALIDATION is set. Live checkout requires supported Stripe configuration; demo is currently unavailable and grants no paid capability. See Configuration for the selected composition and existing-customer obligations.
The non-local lanes inspect these files, not the effective environment of a running Worker. They do not read private Wrangler binding configuration or infer a remote database/bucket from credentials. A selected upload capability with unavailable Worker context remains unknown; public S3 inputs cannot make private uploads available. DevReview artifact trust, installed identity and adapter compatibility remain unverified; use its separate preflight only after choosing that integration.
Interpret checks and recover
| Exit | Meaning |
|---|---|
0 | No known required configuration blocker; unverified checks can remain |
1 | A required input, local target or selected capability is blocked |
2 | Usage is invalid or the tool cannot complete inspection |
Exit 0 is not installation, schema, login, payment, application or deployment success. A pass check covers only its named declaration/input. Capability state and observation are separate: disabled describes an unselected declaration, blocked a known missing/invalid input, unknown insufficient runtime context, and available sufficient configuration for that operation. All diagnostic observations are unverified; even Mock’s mode=demo does not prove payment or entitlement. These states describe selected configuration, not user permissions or a guarantee that every legacy consumer enforces a switch.
JSON contains schemaVersion: 1, lane, the public product revision, checks and safe operator capabilities. Automated consumers must reject unsupported schema versions. Values, credentials, provider URLs, absolute local paths and raw exceptions are omitted; failures include only controlled codes, key names and source categories. Use npm’s --silent option when piping JSON, because npm otherwise prints its own command banner. The CLI sends tool/usage diagnostics to stderr; valid inspections, including exit 1 results, leave stderr empty. Usage errors have no JSON result; a tool failure after valid selectors emits a versioned failure result.
nextStep and action are identifiers, never commands with interpolated secrets:
| Next-step ID | Action |
|---|---|
none | No change indicated for this check |
configure-environment, inspect-input-files | Correct the named input in the selected lane or its shell override; check file permissions without publishing values |
review-local-target | Resolve the named conflict using the local-app target rules above |
verify-installation | Verify the trusted lock and installation using the public-baseline steps |
initialize-local-schema | Follow the guarded local database initialization steps below |
verify-runtime-schema, verify-runtime | Follow the chosen lane’s explicit configuration/deployment and runtime verification steps |
configure-capability, select-checkout-mode | Reconcile the explicit selection in src/config/product.ts with its domain inputs |
verify-checkout | Verify the configured supported purchase flow and original-attempt recovery, then verify Stripe independently before selling |
devreview-preflight | Follow the optional DevReview prerequisites and integration:doctor -- --preflight below |
Initialize the local database
npm run db:push
npm run db:seed -- --admin-action noneBoth commands print the same normalized local target. db:push applies the ordered drizzle/ migrations with the installed Drizzle migrator, including data backfills and custom constraints. It accepts an empty file or a matching Drizzle migration history. A database created by older schema-only push is refused: preserve it and rehearse a data transfer into a fresh migrated file instead of inventing migration history. Stop app processes before migrating and restart them afterward.
Seed creates the four missing demo plans (free, pro, pro-yearly, enterprise). Existing plan prices, text, active/deleted flags and provider IDs remain unchanged. Seed does not create schema.
npm run db:init runs migrations followed by seed with its default admin intent. If seed preflight fails, the earlier migrations may already be committed. --force and schema-only push are unsupported; both would bypass the complete migration contract.
Create an administrator explicitly
Choose an email different from the ordinary development account and add it to .env.local:
ADMIN_EMAIL="admin@example.test"
ADMIN_PASSWORD="choose-a-local-admin-password"Then run:
npm run db:seed -- --admin-action create--admin-action accepts none or create and overrides TEMPLATE_ADMIN_ACTION. Its default is none. A legacy ADMIN_EMAIL with no explicit action fails with ADMIN_INTENT_REQUIRED before seed writes; use create deliberately or explicit none to ignore the admin inputs. There is no default admin password.
An existing active, non-deleted admin returns unchanged and PASSWORD_NOT_UPDATED. The password you just supplied is not installed or verified. Sign in using the account’s existing password. An ordinary, disabled or deleted account with the same email returns ADMIN_CONFLICT before plan writes. Seed never promotes users, reactivates accounts, resets passwords, or changes verification/profile fields. Use the account-management workflow for those actions.
Read results and recover
| Result | Meaning and next action |
|---|---|
succeeded / exit 0 | Every requested step completed or was already present; inspect created, unchanged and skipped steps. |
failed / exit 1 | Preflight or the first operation failed. SCHEMA_REQUIRED means run the schema step for the same target; DB_BUSY is retryable after the other local writer finishes. |
partial / exit 1 | Earlier steps completed but a later step failed. Those writes remain; fix the reported problem and rerun to fill missing records. |
unknown / exit 1, or interrupted process with no final result | A write may have committed. Rerun against the same target so unique keys reconcile existing records; do not assume failure or reset the database. |
Plan and admin uniqueness conflicts never overwrite the winning record. A competing ordinary/admin-disabled account can cause an admin conflict after plans completed, reported as partial. Local db:seed and HTTP bootstrap now share these create-only rules. HTTP uses the explicit operator command below; it never resets passwords or promotes an existing user.
Initialize an already running app through HTTP
After migrating the target database and starting the authorized application, configure APP_URL and its exact XADMIN_SECRET in the selected private helper environment. db:seed:d1:local reads .dev.vars.dev, :preview reads .env.cloudflare.preview, and db:seed:d1 reads .env.cloudflare; shell values override those files. The local-worker command requires a localhost HTTP(S) target.
# Explicit plans-only initialization:
npm run db:seed:d1:local -- initialize --admin-action none
# Explicit new administrator, using private ADMIN_EMAIL / ADMIN_PASSWORD inputs:
npm run db:seed:d1:preview -- initialize --admin-action createNo command or --help shows help without reading environment files or calling the app. There is no default administrator password; HTTP creation accepts passwords of 8–128 characters. An existing active admin stays unchanged, including its original password. Other existing accounts conflict before any plan writes.
The helper prints safe step results: exit 0 means succeeded, exit 2 retains failed/partial/unknown, and exit 1 means configuration, authentication or invalid/lost transport. Initialization is sequential: earlier successful inserts remain if a later step fails. Inspect the steps before a deliberate later invocation; the helper never retries automatically. HTTP always disables caching.
Old direct clients must send { "adminAction": "none" } or { "adminAction": "create", "adminEmail": "...", "adminPassword": "..." }. Missing/malformed bodies, undeclared fields and invalid credentials are refused before writes. The old default-password and overwrite behaviors are removed.
Run and verify local-app
Generate local content, then start the app on the chosen port:
npm exec velite
npm run dev:fast -- --port 3101If you edit local blog content, rerun Velite or start npm exec velite -- --watch in another terminal. Keep APP_URL and the actual port aligned. If the port is occupied, choose an unused port and update both; do not stop an unrelated service.
- Open the home page and /docs. Open /pricing when new purchases are selected; the core composition returns 404 there.
- Sign in on /login with
member@example.testand the configured development password. This creates an ordinary user when absent, only in development. Verify /admin is denied. - Sign out or use a new browser session. Sign in with the separate explicitly seeded admin and its existing password; verify /admin is accessible. A stale session does not prove the current stored role.
- As the ordinary user, open /workspace, create a bookmark and reload it. This verifies a real free resource without organization membership or payment configuration.
- With new purchases selected, open /pricing; core returns 404 for that route. Demo or unavailable checkout does not create a paid entitlement; a pricing fallback card alone does not prove database initialization. Real selling requires configured catalog offers, an enabled supported provider and independently verified confirmation.
The development credentials provider is not a production/test-mode login promise. Registration/password recovery with real email and real payments require their own configured integrations and verification.
Disposable browser checks
The default Playwright preparation recreates e2e.db, installs Chromium, pushes schema, explicitly creates dev@example.com as its admin fixture, and removes .next, playwright-report, and test-results. Run it when no other process owns those build/report directories:
npm run test:e2ePreparation validates the target before any mkdir, removal, or browser installation. Only file:./e2e.db or a named .db file directly under .cache/e2e/ is accepted; dev.db, external files and symlink targets are rejected. Use a distinct email for ordinary-user tests. For an intentionally isolated database:
PW_E2E_DATABASE_URL="file:./.cache/e2e/adoption.db" npm run test:e2eThis file is disposable and will be deleted on preparation. Remote runtime smoke checks and PW_E2E_REUSE_SERVER=1 use different setup responsibilities; do not treat them as initialization proof.
Optional: DevReview
The public baseline keeps publicProductConfig.capabilities.devReview false and runs without private packages, keys, or an adapter. Opt-in is unavailable until you obtain trusted, independently verified, versioned artifacts. After verifying their provenance and checksums, install them into your own copy:
npm install --save-dev --save-exact /absolute/trusted/devreview-shared-VERSION.tgz /absolute/trusted/devreview-vite-plugin-VERSION.tgzReplace the paths/version with the artifacts you reviewed, retain the generated SHA-512 lock entries, and use npm ci for subsequent installations. Do not reuse removed original-project download URLs, invent checksums, or use a downloaded checksum as proof that an unknown artifact is trusted.
Provide .devreview/next.cjs exporting composeDevReview(config, target) according to the DevReviewNextAdapter contract in scripts/devreview-next.ts. Implement it against your reviewed SDK’s actual API, honoring the supplied projectId, serverUrl and applyInProduction. This repository supplies no vendor adapter because no trusted private SDK is available in the baseline.
Set your own DEVREVIEW_PROJECT_ID and DEVREVIEW_SERVER_URL (an HTTPS origin, or loopback HTTP), then explicitly set publicProductConfig.capabilities.devReview to true in src/config/product.ts. Production instrumentation defaults to false; opt in with DEVREVIEW_APPLY_IN_PRODUCTION=true only when intended. DEVREVIEW_API_TOKEN is only for explicit integration CLI operations and is not passed to the build adapter.
| Command | Effect |
|---|---|
npm run integration:doctor -- --preflight | Check local target, pinned artifacts and adapter prerequisites. |
npm run integration:pack | Preflight, build, then inspect manifests. |
npm run integration:doctor -- --remote | Additionally probe your bootstrap endpoint after local checks. |
npm run integration:bootstrap | Fetch your chosen target’s bootstrap data. |
npm run manifest:upload -- [MANIFEST] [VERSION] | Publish the selected manifest to your chosen integration target. |
These commands provide --help without reading project env and reject missing target/artifact prerequisites before network I/O. Baseline installation does not verify private SDK compatibility or remote connectivity. Any remote smoke test also needs an explicitly supplied PW_BASE_URL for your own app.
Optional: local-worker
cp .dev.vars.example .dev.vars.dev
npm run cf:previewKeep .dev.vars.dev on APP_URL=http://localhost:8787; keep .env.local on the local-app lane. Set the local-worker Auth.js/admin/cron/trigger secrets in its own file. Tracked custom-worker owns the Worker runtime and scheduled behavior. Follow the configuration guide for D1 bindings and the separate local-worker schema/seed workflow.
npm run test:e2e:worker checks an already running Worker on 8787; npm run test:e2e:worker:headed opens a visible browser. Neither command changes the default local-app lane into a Worker lane.
Optional: remote Cloudflare
cp wrangler.remote.example.toml wrangler.remote.toml
cp .env.cloudflare.example .env.cloudflare
cp .env.cloudflare.example .env.cloudflare.previewPut your private account/routes/bindings in wrangler.remote.toml, remote helper secrets in .env.cloudflare*, and follow the deployment guide. Tracked wrangler.toml remains template-safe. Remote release keeps root drizzle.config.ts; db:migrate, db:studio, remote release and db:seed:d1* are not covered by the guarded local-app guarantee.
Troubleshooting
- Target conflict: check the named D1 input in the shell,
.env.localand.env; keep credentials in the intended lane. Error messages omit credential values. - Missing schema: compare the printed push/seed target and run
npm run db:pushfor that same file. Do not delete an existing database to diagnose connection errors. - Existing admin:
PASSWORD_NOT_UPDATEDpreserves the old password; supplying another password to seed does not reset it. - Dependency failure: resolve the reported trusted-package/integrity problem and rerun
npm ci. Optional DevReview failure does not require enabling that integration for the baseline. - Usage:
npm run db:push -- --helpandnpm run db:seed -- --helpwork without environment files, database connections, or provider credentials.