Skip to main content
The Effect Coffee Shop repo assembles a modern TypeScript toolchain where each tool has a focused responsibility. Turborepo coordinates tasks across workspaces and caches their outputs; Bun drives the runtime and package management; and a collection of fast, focused tools handle typechecking, linting, formatting, dead-code detection, and Git-hook enforcement.

Turborepo

Turborepo is the canonical root task runner. It defines tasks in turbo.json, tracks their input hashes, and caches outputs in .turbo/ so unchanged workspaces are never reprocessed. The following tasks are declared:
TaskDescriptionCached
buildBuild distributable workspace artifactsYes (dist/**)
build-storybookBuild Storybook static assetsYes (storybook-static/**)
typecheckType-check each workspaceYes (no outputs)
lintRun standard workspace lint checksYes (no outputs)
lint:customRun backend-only custom lint rulesYes (no outputs)
fmt:checkVerify formatting without modifying filesYes (no outputs)
knipDetect unused files and exportsYes (no outputs)
testRun workspace test suitesYes (no outputs)
devRun long-lived local development serversNo (persistent)
storybookRun Storybook development serversNo (persistent)
build and test declare "dependsOn": ["typecheck"] so typechecking always completes before artifact generation or test execution.

Affected-Only Runs

Turborepo can scope any task to only the workspaces touched by the current branch:
bun run check:affected
bun run build:affected
bun run test:affected
To inspect which packages are considered affected:
bun run affected:packages

Bun Workspaces

The root package.json declares two workspaces:
{
  "workspaces": ["backend", "ui"],
  "packageManager": "[email protected]"
}
A single bun install at the repo root resolves and deduplicates dependencies for both workspaces. Workspace packages reference each other by name, and scripts in the root package.json use bun run --cwd <workspace> <script> to delegate to workspace-specific commands.

TypeScript with tsgo

The backend uses @typescript/native-preview — the native TypeScript compiler (codename tsgo) — for typechecking. It replaces the standard tsc --noEmit invocation with a significantly faster native binary:
# backend/package.json
"typecheck": "tsgo --noEmit"
Run typechecking across all workspaces:
bun run typecheck

Linting

The backend uses oxlint — a Rust-based linter — configured in backend/.oxlintrc.json. It enables the oxc, typescript, unicorn, and node plugin groups and runs with type-aware analysis:
{
  "plugins": ["oxc", "typescript", "unicorn", "node"],
  "options": {
    "typeAware": true,
    "typeCheck": true,
    "maxWarnings": 0
  }
}
Source files under src/ are subject to additional structural rules including complexity limits, line-count caps, and a no-restricted-imports rule that enforces onion architecture boundaries by blocking direct imports of platform-specific or runtime modules.
bun run lint        # check
bun run lint:fix    # auto-fix

Custom Linting: lintcn

The backend ships lintcn for custom architectural lint rules that go beyond what standard linters provide. It reads the project’s tsconfig.json and evaluates rules specific to the onion architecture structure.
bun run lint:custom          # run custom lint rules
bun run lint:custom:build    # rebuild the custom rule bundle
bun run lint:custom:list     # list available custom rules
These map to lintcn lint, lintcn build, and lintcn list in backend/package.json.

Formatting: oxfmt

oxfmt is the formatter for backend TypeScript files:
bun run fmt            # format files in-place
bun run fmt:check      # verify formatting without modifying files
fmt:check runs through Turborepo so it covers all workspaces, while fmt writes directly to the backend workspace only.
The pre-commit hook runs fmt:check (not fmt) so it will fail on unformatted files rather than silently modifying them. Run bun run fmt manually before staging changes.

Dead Code Detection: knip

knip scans the project for unused files, exports, and dependencies. Its configuration in backend/knip.jsonc targets the four application entrypoints plus all test files:
{
  "entry": [
    "src/presentation/http/main.ts!",
    "src/presentation/cli/main.ts!",
    "src/presentation/mcp/stdio-main.ts!",
    "src/presentation/mcp/http-main.ts!",
    "src/presentation/mcp/miniflare.worker.ts",
    "src/**/*.test.ts",
    "test/**/*.test.ts"
  ],
  "project": ["src/**/*.ts!", "test/**/*.ts"]
}
bun run knip
The pre-commit hook runs knip automatically on every commit.

Git Hooks: prek

prek manages Git hooks declared in prek.toml. Two hook stages are installed: Pre-commit — runs on every git commit:
HookCommand
fmt-checkbun run fmt:check
oxlintbun run lint
lintcnbun run lint:custom
typecheckbun run typecheck
knipbun run knip
Pre-push — runs on every git push:
HookCommand
vitestbun run test
Install or reinstall the hooks with:
bun run hooks:install
Dry-run any hook stage without committing or pushing:
bun run hooks:run:pre-commit
bun run hooks:run:pre-push
Running bun run hooks:run:pre-commit before staging files is useful for checking that formatting, linting, typechecking, and dead-code analysis all pass before you write a commit message.

Build docs developers (and LLMs) love