DarkFactory is a strict-TypeScript monorepo template for starting a web product with the boring parts already wired. It ships Next App Router pages on Vite (vinext) deployed to Cloudflare Workers, PostgreSQL through Drizzle, contract-first oRPC with generated OpenAPI, Better Auth, and Tailwind + shadcn UI.
Every external service (email, analytics, AI, telemetry) sits behind a small port with an adapter, so you swap a provider by replacing an adapter, not by rewriting features. The database host is a config switch: plain Postgres, PlanetScale or Cloudflare Hyperdrive. Gates are strict: 100% coverage, four required CI lanes, and generated contracts and docs that must match the code. An optional operator plane runs a planned, approval-gated agent SDLC on top.
Every workspace package declares a brick role in its package.json. Arrows mean "uses"; bun run docs:check enforces their direction:
flowchart LR
app["🌐 <b>app</b><br/>apps/web<br/><i>turns bricks<br/>into pages</i>"]
sdlc["🧭 <b>agent-sdlc</b><br/>operator, jobs<br/><i>optional AI crew,<br/>safe to delete</i>"]
product["🧱 <b>product</b><br/>api · auth · db<br/>config · ui · state<br/>observability<br/><i>your rules and data</i>"]
capability["🔌 <b>capability</b><br/>email · ai<br/>analytics<br/><i>swap vendors, not features</i>"]
tooling["🧪 <b>tooling</b><br/>testkit<br/><i>throwaway test databases</i>"]
app --> product & capability
sdlc --> product & capability
product --> capability
tooling -.->|tests| product
classDef appRole fill:#1f6feb,stroke:#0b3d91,color:#ffffff
classDef productRole fill:#2ea44f,stroke:#1a7f37,color:#ffffff
classDef capabilityRole fill:#bf8700,stroke:#7d4e00,color:#ffffff
classDef sdlcRole fill:#8250df,stroke:#512a97,color:#ffffff
classDef toolingRole fill:#6e7781,stroke:#424a53,color:#ffffff
class app appRole
class product productRole
class capability capabilityRole
class sdlc sdlcRole
class tooling toolingRole
Nothing depends on an app, capability bricks depend on nothing but tooling, and product bricks never reach into the agent plane. The per-package graph, with every workspace edge and export, is generated from the package.json files: docs/generated/package-graph.md.
ai is the reference capability brick: a port, a Groq adapter, a recording fake and a browser guard, with no app wired to it yet. Copy its shape when you add a capability (docs/capabilities.md).
You need mise, which installs the toolchain pinned in mise.toml, and Docker for local Postgres (or your own PostgreSQL 17 with the roles in infra/docker/postgres.compose.yml).
Create your repository from the template with the GitHub CLI. It starts from one fresh commit, so none of the template's Git history comes with it. Rename it with init before you run setup:
gh repo create acme/acme-labs --template jeffscottward/darkfactory --private --clone && cd acme-labs
mise install
bun run init -- --name "Acme Labs" --slug acme-labs --scope @acme --domain acme.dev --repo acme/acme-labs
git commit -m "chore: initialize project" && git pushInit rewrites the package scope, slug, domain, repository URLs, env prefixes and database names in one pass. It removes instance-only history (the changelog, archives and the OpenSSF record) and then deletes itself. Add --without-operator to drop the opt-in agent-SDLC plane, --dry-run to preview, or --help to list every option. Then protect main.
To only try the template, git clone https://github.com/jeffscottward/darkfactory.git instead. That clone carries the template's full history, so never push it; bun run init -- … --fresh-history turns it into a project with a single root commit.
Run it from a checkout:
mise install
bun run setup
bun run devOpen https://darkfactory.localhost and sign in as admin@domain.test with the development password Development123!. Details: docs/getting-started.md.
| Command | What it does |
|---|---|
bun run setup |
Checks the toolchain, installs dependencies, writes .env with generated secrets, starts Postgres with Docker Compose, migrates, seeds dev accounts and writes the Worker .dev.vars. Safe to re-run. |
bun run dev |
Refreshes .dev.vars and starts the web app over portless HTTPS at https://darkfactory.localhost. |
bun run check |
Format check, lint (Biome and Markdown) and typecheck. |
bun run test |
Every suite: unit, contract, operations, integration, then a build and the E2E and accessibility lane. Database suites run against the test database (TEST_DATABASE_URL), never the app database in .env. |
bun run verify:prepush |
The pre-push hook: check, generated-artifact checks, and unit, contract and operations tests. |
bun run verify |
Runs the same checks as the four CI lanes, locally. |
bun run docs:generate / docs:check |
Regenerates or checks docs/generated/package-graph.md. |
bun run generate:feature <name> |
Generates a feature slice: table, migration, repository, contract, service, portal page, docs and tests. Add --dry-run to preview. |
bun run db:migrate |
Applies migrations. db:seed and db:reset also need -- --confirm-environment=development. |
bun run deploy:web:check / deploy:web |
Validates the production database config and dry-runs the Worker deploy, or deploys it (docs/deploy.md). |
bun run doctor |
Checks the toolchain, required env keys and the services declared in capabilities.yaml. |
bun run graph:build |
Optional. Builds a local Graphify code graph for navigation. It is never committed. |
Versions live in package.json, the catalog in pnpm-workspace.yaml and mise.toml.
| Concern | Default | Swap point |
|---|---|---|
| Pages and routing | vinext (Next App Router API on Vite) | apps/web; see ADR-001 in ARCHITECTURE.md |
| Hosting | Cloudflare Workers via @vinext/cloudflare |
apps/web/wrangler.jsonc, scripts/deployment/ |
| Database host | Postgres, PlanetScale or Hyperdrive | DATABASE_PROVIDER profiles in packages/config/src/database.ts |
| ORM and migrations | Drizzle | Core: packages/db (not a swap point) |
| API | oRPC contracts + generated OpenAPI | Core: packages/api/src/contracts/ (not a swap point) |
| Auth | Better Auth | packages/auth |
| Resend (preview files locally) | EmailPort in packages/email/src/index.ts; adapters in packages/email/src/server/provider.ts and contact.ts |
|
| Product analytics | PostHog | AnalyticsPort in packages/analytics/src/index.ts; adapter in packages/analytics/src/server/posthog.ts |
| AI | Groq | AiPort in packages/ai/src/index.ts; adapter in packages/ai/src/server/groq.ts |
| Traces and metrics | OpenTelemetry | TelemetryPort in packages/observability/src/port.ts; adapter in packages/observability/src/server/otel.ts |
| Structured events | evlog | StructuredEventSink in packages/observability/src/port.ts; adapter in packages/observability/src/server/evlog.ts |
| UI | Tailwind + shadcn | packages/ui |
| State | XState (lifecycles), Zustand (UI-only) | packages/state |
| Tests | Vitest (100% gate), Playwright | vitest.config.ts, playwright.config.ts |
| Lint and format | Biome (ultracite/core), markdownlint |
biome.jsonc, .markdownlint-cli2.jsonc |
| Local HTTPS | portless | bun run dev |
| Workspace | pnpm + Turborepo, Bun for scripts | pnpm-workspace.yaml, turbo.json, mise.toml |
- AGENTS.md: rules, workflow and agent roles, for agents and humans
- ARCHITECTURE.md: bricks, request flow, SDLC graph, CI, decisions
- CONVENTIONS.md: TypeScript, errors, logging and tests
- Package graph (generated)
- Getting started, Testing, Capabilities: add or swap a provider, Deploy, Debugging, Operator plane, Security model
- Security policy, Contributing, Support, Changelog, MIT license
