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:
| Task | Description | Cached |
|---|
build | Build distributable workspace artifacts | Yes (dist/**) |
build-storybook | Build Storybook static assets | Yes (storybook-static/**) |
typecheck | Type-check each workspace | Yes (no outputs) |
lint | Run standard workspace lint checks | Yes (no outputs) |
lint:custom | Run backend-only custom lint rules | Yes (no outputs) |
fmt:check | Verify formatting without modifying files | Yes (no outputs) |
knip | Detect unused files and exports | Yes (no outputs) |
test | Run workspace test suites | Yes (no outputs) |
dev | Run long-lived local development servers | No (persistent) |
storybook | Run Storybook development servers | No (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:
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:
Linting
Backend: oxlint
Frontend: ESLint
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
The ui/ workspace uses ESLint. It is invoked through Turborepo’s lint task so bun run lint covers both workspaces.
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.
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"]
}
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:
| Hook | Command |
|---|
fmt-check | bun run fmt:check |
oxlint | bun run lint |
lintcn | bun run lint:custom |
typecheck | bun run typecheck |
knip | bun run knip |
Pre-push — runs on every git push:
| Hook | Command |
|---|
vitest | bun run test |
Install or reinstall the hooks with:
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.