Tooling
Biome linter, Lefthook git hooks, and Turborepo build system.
Raypx uses a modern toolchain: Biome for linting and formatting, Lefthook for git hooks, and Turborepo for monorepo build orchestration. This page covers each tool and how to customize it.
Biome
Biome replaces ESLint, Prettier, and some import-sorting tools with a single fast binary. Configuration lives in biome.json.
Formatter
{
"formatter": {
"enabled": true,
"indentStyle": "space",
"indentWidth": 2,
"lineWidth": 100,
"lineEnding": "lf"
}
}- Indent: 2 spaces.
- Line width: 100 characters.
- Line ending: LF (Unix-style).
JavaScript files use semicolons in "asNeeded" mode and multiline attribute positioning:
{
"javascript": {
"formatter": {
"enabled": true,
"attributePosition": "multiline",
"semicolons": "asNeeded"
}
}
}Linter Rules
The linter enables the recommended ruleset plus custom overrides:
{
"linter": {
"enabled": true,
"rules": {
"recommended": true,
"style": {
"useImportType": "error",
"useSelfClosingElements": "error",
"noUselessElse": "error"
},
"suspicious": {
"noExplicitAny": "warn",
"noConsole": {
"level": "warn",
"options": {
"allow": ["warn", "error", "debug"]
}
}
},
"security": {
"noDangerouslySetInnerHtml": "warn"
}
}
}
}Notable rules:
| Rule | Level | Description |
|---|---|---|
useImportType | error | Enforces import type for type-only imports. |
useSelfClosingElements | error | Requires <br /> instead of <br>. |
noExplicitAny | warn | Warns on explicit any usage. |
noConsole | warn | Warns on console.log, but allows console.warn and console.error. |
noDangerouslySetInnerHtml | warn | Warns on dangerouslySetInnerHTML usage. |
useSortedClasses | warn | Auto-sorts Tailwind utility classes in cn(), clsx(), and cva() calls. |
File Includes
{
"files": {
"includes": [
"**/*.{tsx,ts,js,jsx,json,jsonc,css,mdx}",
"!**/dist",
"!**/node_modules",
"!**/.output",
"!**/routeTree.gen.ts"
]
}
}Overrides
Specific file patterns have custom rules:
| Pattern | Override |
|---|---|
**/routeTree.gen.ts | All checks disabled (auto-generated). |
**/*.css | Linter disabled. |
**/*.mdx | Linter and assist disabled; formatter enabled. |
packages/ui/src/components/**/*.tsx | a11y rules disabled (components are primitives). |
packages/**/src/logger-browser.ts | noConsole disabled (logging library). |
Running Biome
# Check and auto-fix all files
pnpm check
# Format only
pnpm formatTailwind CSS Support
Biome is configured to parse Tailwind directives in CSS files:
{
"css": {
"parser": {
"tailwindDirectives": true
}
}
}Lefthook
Lefthook manages git hooks. Configuration is in lefthook.yml.
Pre-Commit
Runs in parallel on every commit:
pre-commit:
parallel: true
jobs:
- name: biome
glob: "*.{js,ts,cjs,mjs,d.cts,d.mts,jsx,tsx,json,jsonc}"
run: pnpm biome check --write --no-errors-on-unmatched --files-ignore-unknown=true {staged_files}
stage_fixed: true
- name: typecheck
run: pnpm typecheck- biome: Runs Biome on staged files only. Auto-fixes issues and re-stages the fixed files.
- typecheck: Runs
tsc --noEmitto catch type errors.
Both jobs run in parallel to minimize wait time.
Pre-Push
pre-push:
jobs:
- name: test
run: pnpm testRuns the full Vitest suite before pushing to a remote. If any test fails, the push is blocked.
Turborepo
Turborepo orchestrates builds across the monorepo. Configuration is in turbo.json.
Task Pipeline
{
"tasks": {
"build": {
"dependsOn": ["^build"],
"inputs": ["$TURBO_DEFAULT$", ".env*"],
"outputs": ["dist/**"]
},
"dev": {
"cache": false,
"persistent": true
},
"db:push": { "cache": false },
"db:generate": { "cache": false },
"db:migrate": { "cache": false, "persistent": true },
"db:studio": { "cache": false, "persistent": true }
}
}Key behaviors:
build: Depends on^build(builds dependencies first). Caches based on source files and.env*files. Outputsdist/**.dev: Never cached, runs persistently (long-running process).- Database tasks: Never cached because they modify external state.
db:migrateanddb:studioare persistent.
Running Tasks
# Build all packages
pnpm build
# Build a specific package
turbo -F @raypx/database build
# Run a database command
pnpm db:push
pnpm db:studioTypeScript
The TypeScript configuration extends a shared base from @raypx/tsconfig:
{
"extends": "@raypx/tsconfig/tsconfig.base.json",
"compilerOptions": {
"target": "ES2022",
"jsx": "react-jsx",
"module": "ESNext",
"moduleResolution": "bundler",
"verbatimModuleSyntax": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"paths": {
"@/*": ["./src/*"],
"@raypx/ui/*": ["./packages/ui/src/*"],
"collections/*": [".source/*"]
}
}
}Path Aliases
| Alias | Resolves To | Usage |
|---|---|---|
@/* | ./src/* | Application source files. |
@raypx/ui/* | ./packages/ui/src/* | UI package source. |
collections/* | ./.source/* | Fumadocs content collections. |
Path aliases are resolved by Vite via resolve.tsconfigPaths: true in vite.config.ts. This means import { foo } from "@/lib/foo" works in both the Vite build and TypeScript without any additional configuration.