set up eslint and prettier
This commit is contained in:
914
.agents/skills/turborepo/SKILL.md
Normal file
914
.agents/skills/turborepo/SKILL.md
Normal file
@@ -0,0 +1,914 @@
|
|||||||
|
---
|
||||||
|
name: turborepo
|
||||||
|
description: |
|
||||||
|
Turborepo monorepo build system guidance. Triggers on: turbo.json, task pipelines,
|
||||||
|
dependsOn, caching, remote cache, the "turbo" CLI, --filter, --affected, CI optimization, environment
|
||||||
|
variables, internal packages, monorepo structure/best practices, and boundaries.
|
||||||
|
|
||||||
|
Use when user: configures tasks/workflows/pipelines, creates packages, sets up
|
||||||
|
monorepo, shares code between apps, runs changed/affected packages, debugs cache,
|
||||||
|
or has apps/packages directories.
|
||||||
|
metadata:
|
||||||
|
version: 2.8.7-canary.2
|
||||||
|
---
|
||||||
|
|
||||||
|
# Turborepo Skill
|
||||||
|
|
||||||
|
Build system for JavaScript/TypeScript monorepos. Turborepo caches task outputs and runs tasks in parallel based on dependency graph.
|
||||||
|
|
||||||
|
## IMPORTANT: Package Tasks, Not Root Tasks
|
||||||
|
|
||||||
|
**DO NOT create Root Tasks. ALWAYS create package tasks.**
|
||||||
|
|
||||||
|
When creating tasks/scripts/pipelines, you MUST:
|
||||||
|
|
||||||
|
1. Add the script to each relevant package's `package.json`
|
||||||
|
2. Register the task in root `turbo.json`
|
||||||
|
3. Root `package.json` only delegates via `turbo run <task>`
|
||||||
|
|
||||||
|
**DO NOT** put task logic in root `package.json`. This defeats Turborepo's parallelization.
|
||||||
|
|
||||||
|
```json
|
||||||
|
// DO THIS: Scripts in each package
|
||||||
|
// apps/web/package.json
|
||||||
|
{ "scripts": { "build": "next build", "lint": "eslint .", "test": "vitest" } }
|
||||||
|
|
||||||
|
// apps/api/package.json
|
||||||
|
{ "scripts": { "build": "tsc", "lint": "eslint .", "test": "vitest" } }
|
||||||
|
|
||||||
|
// packages/ui/package.json
|
||||||
|
{ "scripts": { "build": "tsc", "lint": "eslint .", "test": "vitest" } }
|
||||||
|
```
|
||||||
|
|
||||||
|
```json
|
||||||
|
// turbo.json - register tasks
|
||||||
|
{
|
||||||
|
"tasks": {
|
||||||
|
"build": { "dependsOn": ["^build"], "outputs": ["dist/**"] },
|
||||||
|
"lint": {},
|
||||||
|
"test": { "dependsOn": ["build"] }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```json
|
||||||
|
// Root package.json - ONLY delegates, no task logic
|
||||||
|
{
|
||||||
|
"scripts": {
|
||||||
|
"build": "turbo run build",
|
||||||
|
"lint": "turbo run lint",
|
||||||
|
"test": "turbo run test"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```json
|
||||||
|
// DO NOT DO THIS - defeats parallelization
|
||||||
|
// Root package.json
|
||||||
|
{
|
||||||
|
"scripts": {
|
||||||
|
"build": "cd apps/web && next build && cd ../api && tsc",
|
||||||
|
"lint": "eslint apps/ packages/",
|
||||||
|
"test": "vitest"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Root Tasks (`//#taskname`) are ONLY for tasks that truly cannot exist in packages (rare).
|
||||||
|
|
||||||
|
## Secondary Rule: `turbo run` vs `turbo`
|
||||||
|
|
||||||
|
**Always use `turbo run` when the command is written into code:**
|
||||||
|
|
||||||
|
```json
|
||||||
|
// package.json - ALWAYS "turbo run"
|
||||||
|
{
|
||||||
|
"scripts": {
|
||||||
|
"build": "turbo run build"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# CI workflows - ALWAYS "turbo run"
|
||||||
|
- run: turbo run build --affected
|
||||||
|
```
|
||||||
|
|
||||||
|
**The shorthand `turbo <tasks>` is ONLY for one-off terminal commands** typed directly by humans or agents. Never write `turbo build` into package.json, CI, or scripts.
|
||||||
|
|
||||||
|
## Quick Decision Trees
|
||||||
|
|
||||||
|
### "I need to configure a task"
|
||||||
|
|
||||||
|
```
|
||||||
|
Configure a task?
|
||||||
|
├─ Define task dependencies → references/configuration/tasks.md
|
||||||
|
├─ Lint/check-types (parallel + caching) → Use Transit Nodes pattern (see below)
|
||||||
|
├─ Specify build outputs → references/configuration/tasks.md#outputs
|
||||||
|
├─ Handle environment variables → references/environment/RULE.md
|
||||||
|
├─ Set up dev/watch tasks → references/configuration/tasks.md#persistent
|
||||||
|
├─ Package-specific config → references/configuration/RULE.md#package-configurations
|
||||||
|
└─ Global settings (cacheDir, daemon) → references/configuration/global-options.md
|
||||||
|
```
|
||||||
|
|
||||||
|
### "My cache isn't working"
|
||||||
|
|
||||||
|
```
|
||||||
|
Cache problems?
|
||||||
|
├─ Tasks run but outputs not restored → Missing `outputs` key
|
||||||
|
├─ Cache misses unexpectedly → references/caching/gotchas.md
|
||||||
|
├─ Need to debug hash inputs → Use --summarize or --dry
|
||||||
|
├─ Want to skip cache entirely → Use --force or cache: false
|
||||||
|
├─ Remote cache not working → references/caching/remote-cache.md
|
||||||
|
└─ Environment causing misses → references/environment/gotchas.md
|
||||||
|
```
|
||||||
|
|
||||||
|
### "I want to run only changed packages"
|
||||||
|
|
||||||
|
```
|
||||||
|
Run only what changed?
|
||||||
|
├─ Changed packages + dependents (RECOMMENDED) → turbo run build --affected
|
||||||
|
├─ Custom base branch → --affected --affected-base=origin/develop
|
||||||
|
├─ Manual git comparison → --filter=...[origin/main]
|
||||||
|
└─ See all filter options → references/filtering/RULE.md
|
||||||
|
```
|
||||||
|
|
||||||
|
**`--affected` is the primary way to run only changed packages.** It automatically compares against the default branch and includes dependents.
|
||||||
|
|
||||||
|
### "I want to filter packages"
|
||||||
|
|
||||||
|
```
|
||||||
|
Filter packages?
|
||||||
|
├─ Only changed packages → --affected (see above)
|
||||||
|
├─ By package name → --filter=web
|
||||||
|
├─ By directory → --filter=./apps/*
|
||||||
|
├─ Package + dependencies → --filter=web...
|
||||||
|
├─ Package + dependents → --filter=...web
|
||||||
|
└─ Complex combinations → references/filtering/patterns.md
|
||||||
|
```
|
||||||
|
|
||||||
|
### "Environment variables aren't working"
|
||||||
|
|
||||||
|
```
|
||||||
|
Environment issues?
|
||||||
|
├─ Vars not available at runtime → Strict mode filtering (default)
|
||||||
|
├─ Cache hits with wrong env → Var not in `env` key
|
||||||
|
├─ .env changes not causing rebuilds → .env not in `inputs`
|
||||||
|
├─ CI variables missing → references/environment/gotchas.md
|
||||||
|
└─ Framework vars (NEXT_PUBLIC_*) → Auto-included via inference
|
||||||
|
```
|
||||||
|
|
||||||
|
### "I need to set up CI"
|
||||||
|
|
||||||
|
```
|
||||||
|
CI setup?
|
||||||
|
├─ GitHub Actions → references/ci/github-actions.md
|
||||||
|
├─ Vercel deployment → references/ci/vercel.md
|
||||||
|
├─ Remote cache in CI → references/caching/remote-cache.md
|
||||||
|
├─ Only build changed packages → --affected flag
|
||||||
|
├─ Skip unnecessary builds → turbo-ignore (references/cli/commands.md)
|
||||||
|
└─ Skip container setup when no changes → turbo-ignore
|
||||||
|
```
|
||||||
|
|
||||||
|
### "I want to watch for changes during development"
|
||||||
|
|
||||||
|
```
|
||||||
|
Watch mode?
|
||||||
|
├─ Re-run tasks on change → turbo watch (references/watch/RULE.md)
|
||||||
|
├─ Dev servers with dependencies → Use `with` key (references/configuration/tasks.md#with)
|
||||||
|
├─ Restart dev server on dep change → Use `interruptible: true`
|
||||||
|
└─ Persistent dev tasks → Use `persistent: true`
|
||||||
|
```
|
||||||
|
|
||||||
|
### "I need to create/structure a package"
|
||||||
|
|
||||||
|
```
|
||||||
|
Package creation/structure?
|
||||||
|
├─ Create an internal package → references/best-practices/packages.md
|
||||||
|
├─ Repository structure → references/best-practices/structure.md
|
||||||
|
├─ Dependency management → references/best-practices/dependencies.md
|
||||||
|
├─ Best practices overview → references/best-practices/RULE.md
|
||||||
|
├─ JIT vs Compiled packages → references/best-practices/packages.md#compilation-strategies
|
||||||
|
└─ Sharing code between apps → references/best-practices/RULE.md#package-types
|
||||||
|
```
|
||||||
|
|
||||||
|
### "How should I structure my monorepo?"
|
||||||
|
|
||||||
|
```
|
||||||
|
Monorepo structure?
|
||||||
|
├─ Standard layout (apps/, packages/) → references/best-practices/RULE.md
|
||||||
|
├─ Package types (apps vs libraries) → references/best-practices/RULE.md#package-types
|
||||||
|
├─ Creating internal packages → references/best-practices/packages.md
|
||||||
|
├─ TypeScript configuration → references/best-practices/structure.md#typescript-configuration
|
||||||
|
├─ ESLint configuration → references/best-practices/structure.md#eslint-configuration
|
||||||
|
├─ Dependency management → references/best-practices/dependencies.md
|
||||||
|
└─ Enforce package boundaries → references/boundaries/RULE.md
|
||||||
|
```
|
||||||
|
|
||||||
|
### "I want to enforce architectural boundaries"
|
||||||
|
|
||||||
|
```
|
||||||
|
Enforce boundaries?
|
||||||
|
├─ Check for violations → turbo boundaries
|
||||||
|
├─ Tag packages → references/boundaries/RULE.md#tags
|
||||||
|
├─ Restrict which packages can import others → references/boundaries/RULE.md#rule-types
|
||||||
|
└─ Prevent cross-package file imports → references/boundaries/RULE.md
|
||||||
|
```
|
||||||
|
|
||||||
|
## Critical Anti-Patterns
|
||||||
|
|
||||||
|
### Using `turbo` Shorthand in Code
|
||||||
|
|
||||||
|
**`turbo run` is recommended in package.json scripts and CI pipelines.** The shorthand `turbo <task>` is intended for interactive terminal use.
|
||||||
|
|
||||||
|
```json
|
||||||
|
// WRONG - using shorthand in package.json
|
||||||
|
{
|
||||||
|
"scripts": {
|
||||||
|
"build": "turbo build",
|
||||||
|
"dev": "turbo dev"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CORRECT
|
||||||
|
{
|
||||||
|
"scripts": {
|
||||||
|
"build": "turbo run build",
|
||||||
|
"dev": "turbo run dev"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# WRONG - using shorthand in CI
|
||||||
|
- run: turbo build --affected
|
||||||
|
|
||||||
|
# CORRECT
|
||||||
|
- run: turbo run build --affected
|
||||||
|
```
|
||||||
|
|
||||||
|
### Root Scripts Bypassing Turbo
|
||||||
|
|
||||||
|
Root `package.json` scripts MUST delegate to `turbo run`, not run tasks directly.
|
||||||
|
|
||||||
|
```json
|
||||||
|
// WRONG - bypasses turbo entirely
|
||||||
|
{
|
||||||
|
"scripts": {
|
||||||
|
"build": "bun build",
|
||||||
|
"dev": "bun dev"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CORRECT - delegates to turbo
|
||||||
|
{
|
||||||
|
"scripts": {
|
||||||
|
"build": "turbo run build",
|
||||||
|
"dev": "turbo run dev"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Using `&&` to Chain Turbo Tasks
|
||||||
|
|
||||||
|
Don't chain turbo tasks with `&&`. Let turbo orchestrate.
|
||||||
|
|
||||||
|
```json
|
||||||
|
// WRONG - turbo task not using turbo run
|
||||||
|
{
|
||||||
|
"scripts": {
|
||||||
|
"changeset:publish": "bun build && changeset publish"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CORRECT
|
||||||
|
{
|
||||||
|
"scripts": {
|
||||||
|
"changeset:publish": "turbo run build && changeset publish"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### `prebuild` Scripts That Manually Build Dependencies
|
||||||
|
|
||||||
|
Scripts like `prebuild` that manually build other packages bypass Turborepo's dependency graph.
|
||||||
|
|
||||||
|
```json
|
||||||
|
// WRONG - manually building dependencies
|
||||||
|
{
|
||||||
|
"scripts": {
|
||||||
|
"prebuild": "cd ../../packages/types && bun run build && cd ../utils && bun run build",
|
||||||
|
"build": "next build"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**However, the fix depends on whether workspace dependencies are declared:**
|
||||||
|
|
||||||
|
1. **If dependencies ARE declared** (e.g., `"@repo/types": "workspace:*"` in package.json), remove the `prebuild` script. Turbo's `dependsOn: ["^build"]` handles this automatically.
|
||||||
|
|
||||||
|
2. **If dependencies are NOT declared**, the `prebuild` exists because `^build` won't trigger without a dependency relationship. The fix is to:
|
||||||
|
- Add the dependency to package.json: `"@repo/types": "workspace:*"`
|
||||||
|
- Then remove the `prebuild` script
|
||||||
|
|
||||||
|
```json
|
||||||
|
// CORRECT - declare dependency, let turbo handle build order
|
||||||
|
// package.json
|
||||||
|
{
|
||||||
|
"dependencies": {
|
||||||
|
"@repo/types": "workspace:*",
|
||||||
|
"@repo/utils": "workspace:*"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"build": "next build"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// turbo.json
|
||||||
|
{
|
||||||
|
"tasks": {
|
||||||
|
"build": {
|
||||||
|
"dependsOn": ["^build"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Key insight:** `^build` only runs build in packages listed as dependencies. No dependency declaration = no automatic build ordering.
|
||||||
|
|
||||||
|
### Overly Broad `globalDependencies`
|
||||||
|
|
||||||
|
`globalDependencies` affects ALL tasks in ALL packages. Be specific.
|
||||||
|
|
||||||
|
```json
|
||||||
|
// WRONG - heavy hammer, affects all hashes
|
||||||
|
{
|
||||||
|
"globalDependencies": ["**/.env.*local"]
|
||||||
|
}
|
||||||
|
|
||||||
|
// BETTER - move to task-level inputs
|
||||||
|
{
|
||||||
|
"globalDependencies": [".env"],
|
||||||
|
"tasks": {
|
||||||
|
"build": {
|
||||||
|
"inputs": ["$TURBO_DEFAULT$", ".env*"],
|
||||||
|
"outputs": ["dist/**"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Repetitive Task Configuration
|
||||||
|
|
||||||
|
Look for repeated configuration across tasks that can be collapsed. Turborepo supports shared configuration patterns.
|
||||||
|
|
||||||
|
```json
|
||||||
|
// WRONG - repetitive env and inputs across tasks
|
||||||
|
{
|
||||||
|
"tasks": {
|
||||||
|
"build": {
|
||||||
|
"env": ["API_URL", "DATABASE_URL"],
|
||||||
|
"inputs": ["$TURBO_DEFAULT$", ".env*"]
|
||||||
|
},
|
||||||
|
"test": {
|
||||||
|
"env": ["API_URL", "DATABASE_URL"],
|
||||||
|
"inputs": ["$TURBO_DEFAULT$", ".env*"]
|
||||||
|
},
|
||||||
|
"dev": {
|
||||||
|
"env": ["API_URL", "DATABASE_URL"],
|
||||||
|
"inputs": ["$TURBO_DEFAULT$", ".env*"],
|
||||||
|
"cache": false,
|
||||||
|
"persistent": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// BETTER - use globalEnv and globalDependencies for shared config
|
||||||
|
{
|
||||||
|
"globalEnv": ["API_URL", "DATABASE_URL"],
|
||||||
|
"globalDependencies": [".env*"],
|
||||||
|
"tasks": {
|
||||||
|
"build": {},
|
||||||
|
"test": {},
|
||||||
|
"dev": {
|
||||||
|
"cache": false,
|
||||||
|
"persistent": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**When to use global vs task-level:**
|
||||||
|
|
||||||
|
- `globalEnv` / `globalDependencies` - affects ALL tasks, use for truly shared config
|
||||||
|
- Task-level `env` / `inputs` - use when only specific tasks need it
|
||||||
|
|
||||||
|
### NOT an Anti-Pattern: Large `env` Arrays
|
||||||
|
|
||||||
|
A large `env` array (even 50+ variables) is **not** a problem. It usually means the user was thorough about declaring their build's environment dependencies. Do not flag this as an issue.
|
||||||
|
|
||||||
|
### Using `--parallel` Flag
|
||||||
|
|
||||||
|
The `--parallel` flag bypasses Turborepo's dependency graph. If tasks need parallel execution, configure `dependsOn` correctly instead.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# WRONG - bypasses dependency graph
|
||||||
|
turbo run lint --parallel
|
||||||
|
|
||||||
|
# CORRECT - configure tasks to allow parallel execution
|
||||||
|
# In turbo.json, set dependsOn appropriately (or use transit nodes)
|
||||||
|
turbo run lint
|
||||||
|
```
|
||||||
|
|
||||||
|
### Package-Specific Task Overrides in Root turbo.json
|
||||||
|
|
||||||
|
When multiple packages need different task configurations, use **Package Configurations** (`turbo.json` in each package) instead of cluttering root `turbo.json` with `package#task` overrides.
|
||||||
|
|
||||||
|
```json
|
||||||
|
// WRONG - root turbo.json with many package-specific overrides
|
||||||
|
{
|
||||||
|
"tasks": {
|
||||||
|
"test": { "dependsOn": ["build"] },
|
||||||
|
"@repo/web#test": { "outputs": ["coverage/**"] },
|
||||||
|
"@repo/api#test": { "outputs": ["coverage/**"] },
|
||||||
|
"@repo/utils#test": { "outputs": [] },
|
||||||
|
"@repo/cli#test": { "outputs": [] },
|
||||||
|
"@repo/core#test": { "outputs": [] }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CORRECT - use Package Configurations
|
||||||
|
// Root turbo.json - base config only
|
||||||
|
{
|
||||||
|
"tasks": {
|
||||||
|
"test": { "dependsOn": ["build"] }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// packages/web/turbo.json - package-specific override
|
||||||
|
{
|
||||||
|
"extends": ["//"],
|
||||||
|
"tasks": {
|
||||||
|
"test": { "outputs": ["coverage/**"] }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// packages/api/turbo.json
|
||||||
|
{
|
||||||
|
"extends": ["//"],
|
||||||
|
"tasks": {
|
||||||
|
"test": { "outputs": ["coverage/**"] }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Benefits of Package Configurations:**
|
||||||
|
|
||||||
|
- Keeps configuration close to the code it affects
|
||||||
|
- Root turbo.json stays clean and focused on base patterns
|
||||||
|
- Easier to understand what's special about each package
|
||||||
|
- Works with `$TURBO_EXTENDS$` to inherit + extend arrays
|
||||||
|
|
||||||
|
**When to use `package#task` in root:**
|
||||||
|
|
||||||
|
- Single package needs a unique dependency (e.g., `"deploy": { "dependsOn": ["web#build"] }`)
|
||||||
|
- Temporary override while migrating
|
||||||
|
|
||||||
|
See `references/configuration/RULE.md#package-configurations` for full details.
|
||||||
|
|
||||||
|
### Using `../` to Traverse Out of Package in `inputs`
|
||||||
|
|
||||||
|
Don't use relative paths like `../` to reference files outside the package. Use `$TURBO_ROOT$` instead.
|
||||||
|
|
||||||
|
```json
|
||||||
|
// WRONG - traversing out of package
|
||||||
|
{
|
||||||
|
"tasks": {
|
||||||
|
"build": {
|
||||||
|
"inputs": ["$TURBO_DEFAULT$", "../shared-config.json"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CORRECT - use $TURBO_ROOT$ for repo root
|
||||||
|
{
|
||||||
|
"tasks": {
|
||||||
|
"build": {
|
||||||
|
"inputs": ["$TURBO_DEFAULT$", "$TURBO_ROOT$/shared-config.json"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Missing `outputs` for File-Producing Tasks
|
||||||
|
|
||||||
|
**Before flagging missing `outputs`, check what the task actually produces:**
|
||||||
|
|
||||||
|
1. Read the package's script (e.g., `"build": "tsc"`, `"test": "vitest"`)
|
||||||
|
2. Determine if it writes files to disk or only outputs to stdout
|
||||||
|
3. Only flag if the task produces files that should be cached
|
||||||
|
|
||||||
|
```json
|
||||||
|
// WRONG: build produces files but they're not cached
|
||||||
|
{
|
||||||
|
"tasks": {
|
||||||
|
"build": {
|
||||||
|
"dependsOn": ["^build"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CORRECT: build outputs are cached
|
||||||
|
{
|
||||||
|
"tasks": {
|
||||||
|
"build": {
|
||||||
|
"dependsOn": ["^build"],
|
||||||
|
"outputs": ["dist/**"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Common outputs by framework:
|
||||||
|
|
||||||
|
- Next.js: `[".next/**", "!.next/cache/**"]`
|
||||||
|
- Vite/Rollup: `["dist/**"]`
|
||||||
|
- tsc: `["dist/**"]` or custom `outDir`
|
||||||
|
|
||||||
|
**TypeScript `--noEmit` can still produce cache files:**
|
||||||
|
|
||||||
|
When `incremental: true` in tsconfig.json, `tsc --noEmit` writes `.tsbuildinfo` files even without emitting JS. Check the tsconfig before assuming no outputs:
|
||||||
|
|
||||||
|
```json
|
||||||
|
// If tsconfig has incremental: true, tsc --noEmit produces cache files
|
||||||
|
{
|
||||||
|
"tasks": {
|
||||||
|
"typecheck": {
|
||||||
|
"outputs": ["node_modules/.cache/tsbuildinfo.json"] // or wherever tsBuildInfoFile points
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
To determine correct outputs for TypeScript tasks:
|
||||||
|
|
||||||
|
1. Check if `incremental` or `composite` is enabled in tsconfig
|
||||||
|
2. Check `tsBuildInfoFile` for custom cache location (default: alongside `outDir` or in project root)
|
||||||
|
3. If no incremental mode, `tsc --noEmit` produces no files
|
||||||
|
|
||||||
|
### `^build` vs `build` Confusion
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"tasks": {
|
||||||
|
// ^build = run build in DEPENDENCIES first (other packages this one imports)
|
||||||
|
"build": {
|
||||||
|
"dependsOn": ["^build"]
|
||||||
|
},
|
||||||
|
// build (no ^) = run build in SAME PACKAGE first
|
||||||
|
"test": {
|
||||||
|
"dependsOn": ["build"]
|
||||||
|
},
|
||||||
|
// pkg#task = specific package's task
|
||||||
|
"deploy": {
|
||||||
|
"dependsOn": ["web#build"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Environment Variables Not Hashed
|
||||||
|
|
||||||
|
```json
|
||||||
|
// WRONG: API_URL changes won't cause rebuilds
|
||||||
|
{
|
||||||
|
"tasks": {
|
||||||
|
"build": {
|
||||||
|
"outputs": ["dist/**"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CORRECT: API_URL changes invalidate cache
|
||||||
|
{
|
||||||
|
"tasks": {
|
||||||
|
"build": {
|
||||||
|
"outputs": ["dist/**"],
|
||||||
|
"env": ["API_URL", "API_KEY"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### `.env` Files Not in Inputs
|
||||||
|
|
||||||
|
Turbo does NOT load `.env` files - your framework does. But Turbo needs to know about changes:
|
||||||
|
|
||||||
|
```json
|
||||||
|
// WRONG: .env changes don't invalidate cache
|
||||||
|
{
|
||||||
|
"tasks": {
|
||||||
|
"build": {
|
||||||
|
"env": ["API_URL"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CORRECT: .env file changes invalidate cache
|
||||||
|
{
|
||||||
|
"tasks": {
|
||||||
|
"build": {
|
||||||
|
"env": ["API_URL"],
|
||||||
|
"inputs": ["$TURBO_DEFAULT$", ".env", ".env.*"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Root `.env` File in Monorepo
|
||||||
|
|
||||||
|
A `.env` file at the repo root is an anti-pattern — even for small monorepos or starter templates. It creates implicit coupling between packages and makes it unclear which packages depend on which variables.
|
||||||
|
|
||||||
|
```
|
||||||
|
// WRONG - root .env affects all packages implicitly
|
||||||
|
my-monorepo/
|
||||||
|
├── .env # Which packages use this?
|
||||||
|
├── apps/
|
||||||
|
│ ├── web/
|
||||||
|
│ └── api/
|
||||||
|
└── packages/
|
||||||
|
|
||||||
|
// CORRECT - .env files in packages that need them
|
||||||
|
my-monorepo/
|
||||||
|
├── apps/
|
||||||
|
│ ├── web/
|
||||||
|
│ │ └── .env # Clear: web needs DATABASE_URL
|
||||||
|
│ └── api/
|
||||||
|
│ └── .env # Clear: api needs API_KEY
|
||||||
|
└── packages/
|
||||||
|
```
|
||||||
|
|
||||||
|
**Problems with root `.env`:**
|
||||||
|
|
||||||
|
- Unclear which packages consume which variables
|
||||||
|
- All packages get all variables (even ones they don't need)
|
||||||
|
- Cache invalidation is coarse-grained (root .env change invalidates everything)
|
||||||
|
- Security risk: packages may accidentally access sensitive vars meant for others
|
||||||
|
- Bad habits start small — starter templates should model correct patterns
|
||||||
|
|
||||||
|
**If you must share variables**, use `globalEnv` to be explicit about what's shared, and document why.
|
||||||
|
|
||||||
|
### Strict Mode Filtering CI Variables
|
||||||
|
|
||||||
|
By default, Turborepo filters environment variables to only those in `env`/`globalEnv`. CI variables may be missing:
|
||||||
|
|
||||||
|
```json
|
||||||
|
// If CI scripts need GITHUB_TOKEN but it's not in env:
|
||||||
|
{
|
||||||
|
"globalPassThroughEnv": ["GITHUB_TOKEN", "CI"],
|
||||||
|
"tasks": { ... }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Or use `--env-mode=loose` (not recommended for production).
|
||||||
|
|
||||||
|
### Shared Code in Apps (Should Be a Package)
|
||||||
|
|
||||||
|
```
|
||||||
|
// WRONG: Shared code inside an app
|
||||||
|
apps/
|
||||||
|
web/
|
||||||
|
shared/ # This breaks monorepo principles!
|
||||||
|
utils.ts
|
||||||
|
|
||||||
|
// CORRECT: Extract to a package
|
||||||
|
packages/
|
||||||
|
utils/
|
||||||
|
src/utils.ts
|
||||||
|
```
|
||||||
|
|
||||||
|
### Accessing Files Across Package Boundaries
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// WRONG: Reaching into another package's internals
|
||||||
|
import { Button } from "../../packages/ui/src/button";
|
||||||
|
|
||||||
|
// CORRECT: Install and import properly
|
||||||
|
import { Button } from "@repo/ui/button";
|
||||||
|
```
|
||||||
|
|
||||||
|
### Too Many Root Dependencies
|
||||||
|
|
||||||
|
```json
|
||||||
|
// WRONG: App dependencies in root
|
||||||
|
{
|
||||||
|
"dependencies": {
|
||||||
|
"react": "^18",
|
||||||
|
"next": "^14"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CORRECT: Only repo tools in root
|
||||||
|
{
|
||||||
|
"devDependencies": {
|
||||||
|
"turbo": "latest"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Common Task Configurations
|
||||||
|
|
||||||
|
### Standard Build Pipeline
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"$schema": "https://turborepo.dev/schema.v2.json",
|
||||||
|
"tasks": {
|
||||||
|
"build": {
|
||||||
|
"dependsOn": ["^build"],
|
||||||
|
"outputs": ["dist/**", ".next/**", "!.next/cache/**"]
|
||||||
|
},
|
||||||
|
"dev": {
|
||||||
|
"cache": false,
|
||||||
|
"persistent": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Add a `transit` task if you have tasks that need parallel execution with cache invalidation (see below).
|
||||||
|
|
||||||
|
### Dev Task with `^dev` Pattern (for `turbo watch`)
|
||||||
|
|
||||||
|
A `dev` task with `dependsOn: ["^dev"]` and `persistent: false` in root turbo.json may look unusual but is **correct for `turbo watch` workflows**:
|
||||||
|
|
||||||
|
```json
|
||||||
|
// Root turbo.json
|
||||||
|
{
|
||||||
|
"tasks": {
|
||||||
|
"dev": {
|
||||||
|
"dependsOn": ["^dev"],
|
||||||
|
"cache": false,
|
||||||
|
"persistent": false // Packages have one-shot dev scripts
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Package turbo.json (apps/web/turbo.json)
|
||||||
|
{
|
||||||
|
"extends": ["//"],
|
||||||
|
"tasks": {
|
||||||
|
"dev": {
|
||||||
|
"persistent": true // Apps run long-running dev servers
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Why this works:**
|
||||||
|
|
||||||
|
- **Packages** (e.g., `@acme/db`, `@acme/validators`) have `"dev": "tsc"` — one-shot type generation that completes quickly
|
||||||
|
- **Apps** override with `persistent: true` for actual dev servers (Next.js, etc.)
|
||||||
|
- **`turbo watch`** re-runs the one-shot package `dev` scripts when source files change, keeping types in sync
|
||||||
|
|
||||||
|
**Intended usage:** Run `turbo watch dev` (not `turbo run dev`). Watch mode re-executes one-shot tasks on file changes while keeping persistent tasks running.
|
||||||
|
|
||||||
|
**Alternative pattern:** Use a separate task name like `prepare` or `generate` for one-shot dependency builds to make the intent clearer:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"tasks": {
|
||||||
|
"prepare": {
|
||||||
|
"dependsOn": ["^prepare"],
|
||||||
|
"outputs": ["dist/**"]
|
||||||
|
},
|
||||||
|
"dev": {
|
||||||
|
"dependsOn": ["prepare"],
|
||||||
|
"cache": false,
|
||||||
|
"persistent": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Transit Nodes for Parallel Tasks with Cache Invalidation
|
||||||
|
|
||||||
|
Some tasks can run in parallel (don't need built output from dependencies) but must invalidate cache when dependency source code changes.
|
||||||
|
|
||||||
|
**The problem with `dependsOn: ["^taskname"]`:**
|
||||||
|
|
||||||
|
- Forces sequential execution (slow)
|
||||||
|
|
||||||
|
**The problem with `dependsOn: []` (no dependencies):**
|
||||||
|
|
||||||
|
- Allows parallel execution (fast)
|
||||||
|
- But cache is INCORRECT - changing dependency source won't invalidate cache
|
||||||
|
|
||||||
|
**Transit Nodes solve both:**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"tasks": {
|
||||||
|
"transit": { "dependsOn": ["^transit"] },
|
||||||
|
"my-task": { "dependsOn": ["transit"] }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The `transit` task creates dependency relationships without matching any actual script, so tasks run in parallel with correct cache invalidation.
|
||||||
|
|
||||||
|
**How to identify tasks that need this pattern:** Look for tasks that read source files from dependencies but don't need their build outputs.
|
||||||
|
|
||||||
|
### With Environment Variables
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"globalEnv": ["NODE_ENV"],
|
||||||
|
"globalDependencies": [".env"],
|
||||||
|
"tasks": {
|
||||||
|
"build": {
|
||||||
|
"dependsOn": ["^build"],
|
||||||
|
"outputs": ["dist/**"],
|
||||||
|
"env": ["API_URL", "DATABASE_URL"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Reference Index
|
||||||
|
|
||||||
|
### Configuration
|
||||||
|
|
||||||
|
| File | Purpose |
|
||||||
|
| --- | --- |
|
||||||
|
| [configuration/RULE.md](./references/configuration/RULE.md) | turbo.json overview, Package Configurations |
|
||||||
|
| [configuration/tasks.md](./references/configuration/tasks.md) | dependsOn, outputs, inputs, env, cache, persistent |
|
||||||
|
| [configuration/global-options.md](./references/configuration/global-options.md) | globalEnv, globalDependencies, cacheDir, daemon, envMode |
|
||||||
|
| [configuration/gotchas.md](./references/configuration/gotchas.md) | Common configuration mistakes |
|
||||||
|
|
||||||
|
### Caching
|
||||||
|
|
||||||
|
| File | Purpose |
|
||||||
|
| --- | --- |
|
||||||
|
| [caching/RULE.md](./references/caching/RULE.md) | How caching works, hash inputs |
|
||||||
|
| [caching/remote-cache.md](./references/caching/remote-cache.md) | Vercel Remote Cache, self-hosted, login/link |
|
||||||
|
| [caching/gotchas.md](./references/caching/gotchas.md) | Debugging cache misses, --summarize, --dry |
|
||||||
|
|
||||||
|
### Environment Variables
|
||||||
|
|
||||||
|
| File | Purpose |
|
||||||
|
| --- | --- |
|
||||||
|
| [environment/RULE.md](./references/environment/RULE.md) | env, globalEnv, passThroughEnv |
|
||||||
|
| [environment/modes.md](./references/environment/modes.md) | Strict vs Loose mode, framework inference |
|
||||||
|
| [environment/gotchas.md](./references/environment/gotchas.md) | .env files, CI issues |
|
||||||
|
|
||||||
|
### Filtering
|
||||||
|
|
||||||
|
| File | Purpose |
|
||||||
|
| --- | --- |
|
||||||
|
| [filtering/RULE.md](./references/filtering/RULE.md) | --filter syntax overview |
|
||||||
|
| [filtering/patterns.md](./references/filtering/patterns.md) | Common filter patterns |
|
||||||
|
|
||||||
|
### CI/CD
|
||||||
|
|
||||||
|
| File | Purpose |
|
||||||
|
| --- | --- |
|
||||||
|
| [ci/RULE.md](./references/ci/RULE.md) | General CI principles |
|
||||||
|
| [ci/github-actions.md](./references/ci/github-actions.md) | Complete GitHub Actions setup |
|
||||||
|
| [ci/vercel.md](./references/ci/vercel.md) | Vercel deployment, turbo-ignore |
|
||||||
|
| [ci/patterns.md](./references/ci/patterns.md) | --affected, caching strategies |
|
||||||
|
|
||||||
|
### CLI
|
||||||
|
|
||||||
|
| File | Purpose |
|
||||||
|
| --- | --- |
|
||||||
|
| [cli/RULE.md](./references/cli/RULE.md) | turbo run basics |
|
||||||
|
| [cli/commands.md](./references/cli/commands.md) | turbo run flags, turbo-ignore, other commands |
|
||||||
|
|
||||||
|
### Best Practices
|
||||||
|
|
||||||
|
| File | Purpose |
|
||||||
|
| --- | --- |
|
||||||
|
| [best-practices/RULE.md](./references/best-practices/RULE.md) | Monorepo best practices overview |
|
||||||
|
| [best-practices/structure.md](./references/best-practices/structure.md) | Repository structure, workspace config, TypeScript/ESLint setup |
|
||||||
|
| [best-practices/packages.md](./references/best-practices/packages.md) | Creating internal packages, JIT vs Compiled, exports |
|
||||||
|
| [best-practices/dependencies.md](./references/best-practices/dependencies.md) | Dependency management, installing, version sync |
|
||||||
|
|
||||||
|
### Watch Mode
|
||||||
|
|
||||||
|
| File | Purpose |
|
||||||
|
| --- | --- |
|
||||||
|
| [watch/RULE.md](./references/watch/RULE.md) | turbo watch, interruptible tasks, dev workflows |
|
||||||
|
|
||||||
|
### Boundaries (Experimental)
|
||||||
|
|
||||||
|
| File | Purpose |
|
||||||
|
| --- | --- |
|
||||||
|
| [boundaries/RULE.md](./references/boundaries/RULE.md) | Enforce package isolation, tag-based dependency rules |
|
||||||
|
|
||||||
|
## Source Documentation
|
||||||
|
|
||||||
|
This skill is based on the official Turborepo documentation at:
|
||||||
|
|
||||||
|
- Source: `apps/docs/content/docs/` in the Turborepo repository
|
||||||
|
- Live: https://turborepo.dev/docs
|
||||||
70
.agents/skills/turborepo/command/turborepo.md
Normal file
70
.agents/skills/turborepo/command/turborepo.md
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
---
|
||||||
|
description: Load Turborepo skill for creating workflows, tasks, and pipelines in monorepos. Use when users ask to "create a workflow", "make a task", "generate a pipeline", or set up build orchestration.
|
||||||
|
---
|
||||||
|
|
||||||
|
Load the Turborepo skill and help with monorepo task orchestration: creating workflows, configuring tasks, setting up pipelines, and optimizing builds.
|
||||||
|
|
||||||
|
## Workflow
|
||||||
|
|
||||||
|
### Step 1: Load turborepo skill
|
||||||
|
|
||||||
|
```
|
||||||
|
skill({ name: 'turborepo' })
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 2: Identify task type from user request
|
||||||
|
|
||||||
|
Analyze $ARGUMENTS to determine:
|
||||||
|
|
||||||
|
- **Topic**: configuration, caching, filtering, environment, CI, or CLI
|
||||||
|
- **Task type**: new setup, debugging, optimization, or implementation
|
||||||
|
|
||||||
|
Use decision trees in SKILL.md to select the relevant reference files.
|
||||||
|
|
||||||
|
### Step 3: Read relevant reference files
|
||||||
|
|
||||||
|
Based on task type, read from `references/<topic>/`:
|
||||||
|
|
||||||
|
| Task | Files to Read |
|
||||||
|
| --- | --- |
|
||||||
|
| Configure turbo.json | `configuration/RULE.md` + `configuration/tasks.md` |
|
||||||
|
| Debug cache issues | `caching/gotchas.md` |
|
||||||
|
| Set up remote cache | `caching/remote-cache.md` |
|
||||||
|
| Filter packages | `filtering/RULE.md` + `filtering/patterns.md` |
|
||||||
|
| Environment problems | `environment/gotchas.md` + `environment/modes.md` |
|
||||||
|
| Set up CI | `ci/RULE.md` + `ci/github-actions.md` or `ci/vercel.md` |
|
||||||
|
| CLI usage | `cli/commands.md` |
|
||||||
|
|
||||||
|
### Step 4: Execute task
|
||||||
|
|
||||||
|
Apply Turborepo-specific patterns from references to complete the user's request.
|
||||||
|
|
||||||
|
**CRITICAL - When creating tasks/scripts/pipelines:**
|
||||||
|
|
||||||
|
1. **DO NOT create Root Tasks** - Always create package tasks
|
||||||
|
2. Add scripts to each relevant package's `package.json` (e.g., `apps/web/package.json`, `packages/ui/package.json`)
|
||||||
|
3. Register the task in root `turbo.json`
|
||||||
|
4. Root `package.json` only contains `turbo run <task>` - never actual task logic
|
||||||
|
|
||||||
|
**Other things to verify:**
|
||||||
|
|
||||||
|
- `outputs` defined for cacheable tasks
|
||||||
|
- `dependsOn` uses correct syntax (`^task` vs `task`)
|
||||||
|
- Environment variables in `env` key
|
||||||
|
- `.env` files in `inputs` if used
|
||||||
|
- Use `turbo run` (not `turbo`) in package.json and CI
|
||||||
|
|
||||||
|
### Step 5: Summarize
|
||||||
|
|
||||||
|
```
|
||||||
|
=== Turborepo Task Complete ===
|
||||||
|
|
||||||
|
Topic: <configuration|caching|filtering|environment|ci|cli>
|
||||||
|
Files referenced: <reference files consulted>
|
||||||
|
|
||||||
|
<brief summary of what was done>
|
||||||
|
```
|
||||||
|
|
||||||
|
<user-request>
|
||||||
|
$ARGUMENTS
|
||||||
|
</user-request>
|
||||||
239
.agents/skills/turborepo/references/best-practices/RULE.md
Normal file
239
.agents/skills/turborepo/references/best-practices/RULE.md
Normal file
@@ -0,0 +1,239 @@
|
|||||||
|
# Monorepo Best Practices
|
||||||
|
|
||||||
|
Essential patterns for structuring and maintaining a healthy Turborepo monorepo.
|
||||||
|
|
||||||
|
## Repository Structure
|
||||||
|
|
||||||
|
### Standard Layout
|
||||||
|
|
||||||
|
```
|
||||||
|
my-monorepo/
|
||||||
|
├── apps/ # Application packages (deployable)
|
||||||
|
│ ├── web/
|
||||||
|
│ ├── docs/
|
||||||
|
│ └── api/
|
||||||
|
├── packages/ # Library packages (shared code)
|
||||||
|
│ ├── ui/
|
||||||
|
│ ├── utils/
|
||||||
|
│ └── config-*/ # Shared configs (eslint, typescript, etc.)
|
||||||
|
├── package.json # Root package.json (minimal deps)
|
||||||
|
├── turbo.json # Turborepo configuration
|
||||||
|
├── pnpm-workspace.yaml # (pnpm) or workspaces in package.json
|
||||||
|
└── pnpm-lock.yaml # Lockfile (required)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Key Principles
|
||||||
|
|
||||||
|
1. **`apps/` for deployables**: Next.js sites, APIs, CLIs - things that get deployed
|
||||||
|
2. **`packages/` for libraries**: Shared code consumed by apps or other packages
|
||||||
|
3. **One purpose per package**: Each package should do one thing well
|
||||||
|
4. **No nested packages**: Don't put packages inside packages
|
||||||
|
|
||||||
|
## Package Types
|
||||||
|
|
||||||
|
### Application Packages (`apps/`)
|
||||||
|
|
||||||
|
- **Deployable**: These are the "endpoints" of your package graph
|
||||||
|
- **Not installed by other packages**: Apps shouldn't be dependencies of other packages
|
||||||
|
- **No shared code**: If code needs sharing, extract to `packages/`
|
||||||
|
|
||||||
|
```json
|
||||||
|
// apps/web/package.json
|
||||||
|
{
|
||||||
|
"name": "web",
|
||||||
|
"private": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@repo/ui": "workspace:*",
|
||||||
|
"next": "latest"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Library Packages (`packages/`)
|
||||||
|
|
||||||
|
- **Shared code**: Utilities, components, configs
|
||||||
|
- **Namespaced names**: Use `@repo/` or `@yourorg/` prefix
|
||||||
|
- **Clear exports**: Define what the package exposes
|
||||||
|
|
||||||
|
```json
|
||||||
|
// packages/ui/package.json
|
||||||
|
{
|
||||||
|
"name": "@repo/ui",
|
||||||
|
"exports": {
|
||||||
|
"./button": "./src/button.tsx",
|
||||||
|
"./card": "./src/card.tsx"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Package Compilation Strategies
|
||||||
|
|
||||||
|
### Just-in-Time (Simplest)
|
||||||
|
|
||||||
|
Export TypeScript directly; let the app's bundler compile it.
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"name": "@repo/ui",
|
||||||
|
"exports": {
|
||||||
|
"./button": "./src/button.tsx"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Pros**: Zero build config, instant changes **Cons**: Can't cache builds, requires app bundler support
|
||||||
|
|
||||||
|
### Compiled (Recommended for Libraries)
|
||||||
|
|
||||||
|
Package compiles itself with `tsc` or bundler.
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"name": "@repo/ui",
|
||||||
|
"exports": {
|
||||||
|
"./button": {
|
||||||
|
"types": "./src/button.tsx",
|
||||||
|
"default": "./dist/button.js"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"build": "tsc"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Pros**: Cacheable by Turborepo, works everywhere **Cons**: More configuration
|
||||||
|
|
||||||
|
## Dependency Management
|
||||||
|
|
||||||
|
### Install Where Used
|
||||||
|
|
||||||
|
Install dependencies in the package that uses them, not the root.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Good: Install in the package that needs it
|
||||||
|
pnpm add lodash --filter=@repo/utils
|
||||||
|
|
||||||
|
# Avoid: Installing everything at root
|
||||||
|
pnpm add lodash -w # Only for repo-level tools
|
||||||
|
```
|
||||||
|
|
||||||
|
### Root Dependencies
|
||||||
|
|
||||||
|
Only these belong in root `package.json`:
|
||||||
|
|
||||||
|
- `turbo` - The build system
|
||||||
|
- `husky`, `lint-staged` - Git hooks
|
||||||
|
- Repository-level tooling
|
||||||
|
|
||||||
|
### Internal Dependencies
|
||||||
|
|
||||||
|
Use workspace protocol for internal packages:
|
||||||
|
|
||||||
|
```json
|
||||||
|
// pnpm/bun
|
||||||
|
{ "@repo/ui": "workspace:*" }
|
||||||
|
|
||||||
|
// npm/yarn
|
||||||
|
{ "@repo/ui": "*" }
|
||||||
|
```
|
||||||
|
|
||||||
|
## Exports Best Practices
|
||||||
|
|
||||||
|
### Use `exports` Field (Not `main`)
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"exports": {
|
||||||
|
".": "./src/index.ts",
|
||||||
|
"./button": "./src/button.tsx",
|
||||||
|
"./utils": "./src/utils.ts"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Avoid Barrel Files
|
||||||
|
|
||||||
|
Don't create `index.ts` files that re-export everything:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// BAD: packages/ui/src/index.ts
|
||||||
|
export * from './button';
|
||||||
|
export * from './card';
|
||||||
|
export * from './modal';
|
||||||
|
// ... imports everything even if you need one thing
|
||||||
|
|
||||||
|
// GOOD: Direct exports in package.json
|
||||||
|
{
|
||||||
|
"exports": {
|
||||||
|
"./button": "./src/button.tsx",
|
||||||
|
"./card": "./src/card.tsx"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Namespace Your Packages
|
||||||
|
|
||||||
|
```json
|
||||||
|
// Good
|
||||||
|
{ "name": "@repo/ui" }
|
||||||
|
{ "name": "@acme/utils" }
|
||||||
|
|
||||||
|
// Avoid (conflicts with npm registry)
|
||||||
|
{ "name": "ui" }
|
||||||
|
{ "name": "utils" }
|
||||||
|
```
|
||||||
|
|
||||||
|
## Common Anti-Patterns
|
||||||
|
|
||||||
|
### Accessing Files Across Package Boundaries
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// BAD: Reaching into another package
|
||||||
|
import { Button } from "../../packages/ui/src/button";
|
||||||
|
|
||||||
|
// GOOD: Install and import properly
|
||||||
|
import { Button } from "@repo/ui/button";
|
||||||
|
```
|
||||||
|
|
||||||
|
### Shared Code in Apps
|
||||||
|
|
||||||
|
```
|
||||||
|
// BAD
|
||||||
|
apps/
|
||||||
|
web/
|
||||||
|
shared/ # This should be a package!
|
||||||
|
utils.ts
|
||||||
|
|
||||||
|
// GOOD
|
||||||
|
packages/
|
||||||
|
utils/ # Proper shared package
|
||||||
|
src/utils.ts
|
||||||
|
```
|
||||||
|
|
||||||
|
### Too Many Root Dependencies
|
||||||
|
|
||||||
|
```json
|
||||||
|
// BAD: Root has app dependencies
|
||||||
|
{
|
||||||
|
"dependencies": {
|
||||||
|
"react": "^18",
|
||||||
|
"next": "^14",
|
||||||
|
"lodash": "^4"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GOOD: Root only has repo tools
|
||||||
|
{
|
||||||
|
"devDependencies": {
|
||||||
|
"turbo": "latest",
|
||||||
|
"husky": "latest"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## See Also
|
||||||
|
|
||||||
|
- [structure.md](./structure.md) - Detailed repository structure patterns
|
||||||
|
- [packages.md](./packages.md) - Creating and managing internal packages
|
||||||
|
- [dependencies.md](./dependencies.md) - Dependency management strategies
|
||||||
@@ -0,0 +1,245 @@
|
|||||||
|
# Dependency Management
|
||||||
|
|
||||||
|
Best practices for managing dependencies in a Turborepo monorepo.
|
||||||
|
|
||||||
|
## Core Principle: Install Where Used
|
||||||
|
|
||||||
|
Dependencies belong in the package that uses them, not the root.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Good: Install in specific package
|
||||||
|
pnpm add react --filter=@repo/ui
|
||||||
|
pnpm add next --filter=web
|
||||||
|
|
||||||
|
# Avoid: Installing in root
|
||||||
|
pnpm add react -w # Only for repo-level tools!
|
||||||
|
```
|
||||||
|
|
||||||
|
## Benefits of Local Installation
|
||||||
|
|
||||||
|
### 1. Clarity
|
||||||
|
|
||||||
|
Each package's `package.json` lists exactly what it needs:
|
||||||
|
|
||||||
|
```json
|
||||||
|
// packages/ui/package.json
|
||||||
|
{
|
||||||
|
"dependencies": {
|
||||||
|
"react": "^18.0.0",
|
||||||
|
"class-variance-authority": "^0.7.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Flexibility
|
||||||
|
|
||||||
|
Different packages can use different versions when needed:
|
||||||
|
|
||||||
|
```json
|
||||||
|
// packages/legacy-ui/package.json
|
||||||
|
{ "dependencies": { "react": "^17.0.0" } }
|
||||||
|
|
||||||
|
// packages/ui/package.json
|
||||||
|
{ "dependencies": { "react": "^18.0.0" } }
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Better Caching
|
||||||
|
|
||||||
|
Installing in root changes workspace lockfile, invalidating all caches.
|
||||||
|
|
||||||
|
### 4. Pruning Support
|
||||||
|
|
||||||
|
`turbo prune` can remove unused dependencies for Docker images.
|
||||||
|
|
||||||
|
## What Belongs in Root
|
||||||
|
|
||||||
|
Only repository-level tools:
|
||||||
|
|
||||||
|
```json
|
||||||
|
// Root package.json
|
||||||
|
{
|
||||||
|
"devDependencies": {
|
||||||
|
"turbo": "latest",
|
||||||
|
"husky": "^8.0.0",
|
||||||
|
"lint-staged": "^15.0.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**NOT** application dependencies:
|
||||||
|
|
||||||
|
- react, next, express
|
||||||
|
- lodash, axios, zod
|
||||||
|
- Testing libraries (unless truly repo-wide)
|
||||||
|
|
||||||
|
## Installing Dependencies
|
||||||
|
|
||||||
|
### Single Package
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# pnpm
|
||||||
|
pnpm add lodash --filter=@repo/utils
|
||||||
|
|
||||||
|
# npm
|
||||||
|
npm install lodash --workspace=@repo/utils
|
||||||
|
|
||||||
|
# yarn
|
||||||
|
yarn workspace @repo/utils add lodash
|
||||||
|
|
||||||
|
# bun
|
||||||
|
cd packages/utils && bun add lodash
|
||||||
|
```
|
||||||
|
|
||||||
|
### Multiple Packages
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# pnpm
|
||||||
|
pnpm add jest --save-dev --filter=web --filter=@repo/ui
|
||||||
|
|
||||||
|
# npm
|
||||||
|
npm install jest --save-dev --workspace=web --workspace=@repo/ui
|
||||||
|
|
||||||
|
# yarn (v2+)
|
||||||
|
yarn workspaces foreach -R --from '{web,@repo/ui}' add jest --dev
|
||||||
|
```
|
||||||
|
|
||||||
|
### Internal Packages
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# pnpm
|
||||||
|
pnpm add @repo/ui --filter=web
|
||||||
|
|
||||||
|
# This updates package.json:
|
||||||
|
{
|
||||||
|
"dependencies": {
|
||||||
|
"@repo/ui": "workspace:*"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Keeping Versions in Sync
|
||||||
|
|
||||||
|
### Option 1: Tooling
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# syncpack - Check and fix version mismatches
|
||||||
|
npx syncpack list-mismatches
|
||||||
|
npx syncpack fix-mismatches
|
||||||
|
|
||||||
|
# manypkg - Similar functionality
|
||||||
|
npx @manypkg/cli check
|
||||||
|
npx @manypkg/cli fix
|
||||||
|
|
||||||
|
# sherif - Rust-based, very fast
|
||||||
|
npx sherif
|
||||||
|
```
|
||||||
|
|
||||||
|
### Option 2: Package Manager Commands
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# pnpm - Update everywhere
|
||||||
|
pnpm up --recursive typescript@latest
|
||||||
|
|
||||||
|
# npm - Update in all workspaces
|
||||||
|
npm install typescript@latest --workspaces
|
||||||
|
```
|
||||||
|
|
||||||
|
### Option 3: pnpm Catalogs (pnpm 9.5+)
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# pnpm-workspace.yaml
|
||||||
|
packages:
|
||||||
|
- "apps/*"
|
||||||
|
- "packages/*"
|
||||||
|
|
||||||
|
catalog:
|
||||||
|
react: ^18.2.0
|
||||||
|
typescript: ^5.3.0
|
||||||
|
```
|
||||||
|
|
||||||
|
```json
|
||||||
|
// Any package.json
|
||||||
|
{
|
||||||
|
"dependencies": {
|
||||||
|
"react": "catalog:" // Uses version from catalog
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Internal vs External Dependencies
|
||||||
|
|
||||||
|
### Internal (Workspace)
|
||||||
|
|
||||||
|
```json
|
||||||
|
// pnpm/bun
|
||||||
|
{ "@repo/ui": "workspace:*" }
|
||||||
|
|
||||||
|
// npm/yarn
|
||||||
|
{ "@repo/ui": "*" }
|
||||||
|
```
|
||||||
|
|
||||||
|
Turborepo understands these relationships and orders builds accordingly.
|
||||||
|
|
||||||
|
### External (npm Registry)
|
||||||
|
|
||||||
|
```json
|
||||||
|
{ "lodash": "^4.17.21" }
|
||||||
|
```
|
||||||
|
|
||||||
|
Standard semver versioning from npm.
|
||||||
|
|
||||||
|
## Peer Dependencies
|
||||||
|
|
||||||
|
For library packages that expect the consumer to provide dependencies:
|
||||||
|
|
||||||
|
```json
|
||||||
|
// packages/ui/package.json
|
||||||
|
{
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^18.0.0",
|
||||||
|
"react-dom": "^18.0.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"react": "^18.0.0", // For development/testing
|
||||||
|
"react-dom": "^18.0.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Common Issues
|
||||||
|
|
||||||
|
### "Module not found"
|
||||||
|
|
||||||
|
1. Check the dependency is installed in the right package
|
||||||
|
2. Run `pnpm install` / `npm install` to update lockfile
|
||||||
|
3. Check exports are defined in the package
|
||||||
|
|
||||||
|
### Version Conflicts
|
||||||
|
|
||||||
|
Packages can use different versions - this is a feature, not a bug. But if you need consistency:
|
||||||
|
|
||||||
|
1. Use tooling (syncpack, manypkg)
|
||||||
|
2. Use pnpm catalogs
|
||||||
|
3. Create a lint rule
|
||||||
|
|
||||||
|
### Hoisting Issues
|
||||||
|
|
||||||
|
Some tools expect dependencies in specific locations. Use package manager config:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# .npmrc (pnpm)
|
||||||
|
public-hoist-pattern[]=*eslint* public-hoist-pattern[]=*prettier*
|
||||||
|
```
|
||||||
|
|
||||||
|
## Lockfile
|
||||||
|
|
||||||
|
**Required** for:
|
||||||
|
|
||||||
|
- Reproducible builds
|
||||||
|
- Turborepo dependency analysis
|
||||||
|
- Cache correctness
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Commit your lockfile!
|
||||||
|
git add pnpm-lock.yaml # or package-lock.json, yarn.lock
|
||||||
|
```
|
||||||
335
.agents/skills/turborepo/references/best-practices/packages.md
Normal file
335
.agents/skills/turborepo/references/best-practices/packages.md
Normal file
@@ -0,0 +1,335 @@
|
|||||||
|
# Creating Internal Packages
|
||||||
|
|
||||||
|
How to create and structure internal packages in your monorepo.
|
||||||
|
|
||||||
|
## Package Creation Checklist
|
||||||
|
|
||||||
|
1. Create directory in `packages/`
|
||||||
|
2. Add `package.json` with name and exports
|
||||||
|
3. Add source code in `src/`
|
||||||
|
4. Add `tsconfig.json` if using TypeScript
|
||||||
|
5. Install as dependency in consuming packages
|
||||||
|
6. Run package manager install to update lockfile
|
||||||
|
|
||||||
|
## Package Compilation Strategies
|
||||||
|
|
||||||
|
### Just-in-Time (JIT)
|
||||||
|
|
||||||
|
Export TypeScript directly. The consuming app's bundler compiles it.
|
||||||
|
|
||||||
|
```json
|
||||||
|
// packages/ui/package.json
|
||||||
|
{
|
||||||
|
"name": "@repo/ui",
|
||||||
|
"exports": {
|
||||||
|
"./button": "./src/button.tsx",
|
||||||
|
"./card": "./src/card.tsx"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"lint": "eslint .",
|
||||||
|
"check-types": "tsc --noEmit"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**When to use:**
|
||||||
|
|
||||||
|
- Apps use modern bundlers (Turbopack, webpack, Vite)
|
||||||
|
- You want minimal configuration
|
||||||
|
- Build times are acceptable without caching
|
||||||
|
|
||||||
|
**Limitations:**
|
||||||
|
|
||||||
|
- No Turborepo cache for the package itself
|
||||||
|
- Consumer must support TypeScript compilation
|
||||||
|
- Can't use TypeScript `paths` (use Node.js subpath imports instead)
|
||||||
|
|
||||||
|
### Compiled
|
||||||
|
|
||||||
|
Package handles its own compilation.
|
||||||
|
|
||||||
|
```json
|
||||||
|
// packages/ui/package.json
|
||||||
|
{
|
||||||
|
"name": "@repo/ui",
|
||||||
|
"exports": {
|
||||||
|
"./button": {
|
||||||
|
"types": "./src/button.tsx",
|
||||||
|
"default": "./dist/button.js"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"build": "tsc",
|
||||||
|
"dev": "tsc --watch"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```json
|
||||||
|
// packages/ui/tsconfig.json
|
||||||
|
{
|
||||||
|
"extends": "@repo/typescript-config/library.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"outDir": "dist",
|
||||||
|
"rootDir": "src"
|
||||||
|
},
|
||||||
|
"include": ["src"],
|
||||||
|
"exclude": ["node_modules", "dist"]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**When to use:**
|
||||||
|
|
||||||
|
- You want Turborepo to cache builds
|
||||||
|
- Package will be used by non-bundler tools
|
||||||
|
- You need maximum compatibility
|
||||||
|
|
||||||
|
**Remember:** Add `dist/**` to turbo.json outputs!
|
||||||
|
|
||||||
|
## Defining Exports
|
||||||
|
|
||||||
|
### Multiple Entrypoints
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"exports": {
|
||||||
|
".": "./src/index.ts", // @repo/ui
|
||||||
|
"./button": "./src/button.tsx", // @repo/ui/button
|
||||||
|
"./card": "./src/card.tsx", // @repo/ui/card
|
||||||
|
"./hooks": "./src/hooks/index.ts" // @repo/ui/hooks
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Conditional Exports (Compiled)
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"exports": {
|
||||||
|
"./button": {
|
||||||
|
"types": "./src/button.tsx",
|
||||||
|
"import": "./dist/button.mjs",
|
||||||
|
"require": "./dist/button.cjs",
|
||||||
|
"default": "./dist/button.js"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Installing Internal Packages
|
||||||
|
|
||||||
|
### Add to Consuming Package
|
||||||
|
|
||||||
|
```json
|
||||||
|
// apps/web/package.json
|
||||||
|
{
|
||||||
|
"dependencies": {
|
||||||
|
"@repo/ui": "workspace:*" // pnpm/bun
|
||||||
|
// "@repo/ui": "*" // npm/yarn
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Run Install
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm install # Updates lockfile with new dependency
|
||||||
|
```
|
||||||
|
|
||||||
|
### Import and Use
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// apps/web/src/page.tsx
|
||||||
|
import { Button } from '@repo/ui/button';
|
||||||
|
|
||||||
|
export default function Page() {
|
||||||
|
return <Button>Click me</Button>;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## One Purpose Per Package
|
||||||
|
|
||||||
|
### Good Examples
|
||||||
|
|
||||||
|
```
|
||||||
|
packages/
|
||||||
|
├── ui/ # Shared UI components
|
||||||
|
├── utils/ # General utilities
|
||||||
|
├── auth/ # Authentication logic
|
||||||
|
├── database/ # Database client/schemas
|
||||||
|
├── eslint-config/ # ESLint configuration
|
||||||
|
├── typescript-config/ # TypeScript configuration
|
||||||
|
└── api-client/ # Generated API client
|
||||||
|
```
|
||||||
|
|
||||||
|
### Avoid Mega-Packages
|
||||||
|
|
||||||
|
```
|
||||||
|
// BAD: One package for everything
|
||||||
|
packages/
|
||||||
|
└── shared/
|
||||||
|
├── components/
|
||||||
|
├── utils/
|
||||||
|
├── hooks/
|
||||||
|
├── types/
|
||||||
|
└── api/
|
||||||
|
|
||||||
|
// GOOD: Separate by purpose
|
||||||
|
packages/
|
||||||
|
├── ui/ # Components
|
||||||
|
├── utils/ # Utilities
|
||||||
|
├── hooks/ # React hooks
|
||||||
|
├── types/ # Shared TypeScript types
|
||||||
|
└── api-client/ # API utilities
|
||||||
|
```
|
||||||
|
|
||||||
|
## Config Packages
|
||||||
|
|
||||||
|
### TypeScript Config
|
||||||
|
|
||||||
|
```json
|
||||||
|
// packages/typescript-config/package.json
|
||||||
|
{
|
||||||
|
"name": "@repo/typescript-config",
|
||||||
|
"exports": {
|
||||||
|
"./base.json": "./base.json",
|
||||||
|
"./nextjs.json": "./nextjs.json",
|
||||||
|
"./library.json": "./library.json"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### ESLint Config
|
||||||
|
|
||||||
|
```json
|
||||||
|
// packages/eslint-config/package.json
|
||||||
|
{
|
||||||
|
"name": "@repo/eslint-config",
|
||||||
|
"exports": {
|
||||||
|
"./base": "./base.js",
|
||||||
|
"./next": "./next.js"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"eslint": "^8.0.0",
|
||||||
|
"eslint-config-next": "latest"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Common Mistakes
|
||||||
|
|
||||||
|
### Forgetting to Export
|
||||||
|
|
||||||
|
```json
|
||||||
|
// BAD: No exports defined
|
||||||
|
{
|
||||||
|
"name": "@repo/ui"
|
||||||
|
}
|
||||||
|
|
||||||
|
// GOOD: Clear exports
|
||||||
|
{
|
||||||
|
"name": "@repo/ui",
|
||||||
|
"exports": {
|
||||||
|
"./button": "./src/button.tsx"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Wrong Workspace Syntax
|
||||||
|
|
||||||
|
```json
|
||||||
|
// pnpm/bun
|
||||||
|
{ "@repo/ui": "workspace:*" } // Correct
|
||||||
|
|
||||||
|
// npm/yarn
|
||||||
|
{ "@repo/ui": "*" } // Correct
|
||||||
|
{ "@repo/ui": "workspace:*" } // Wrong for npm/yarn!
|
||||||
|
```
|
||||||
|
|
||||||
|
### Missing from turbo.json Outputs
|
||||||
|
|
||||||
|
```json
|
||||||
|
// Package builds to dist/, but turbo.json doesn't know
|
||||||
|
{
|
||||||
|
"tasks": {
|
||||||
|
"build": {
|
||||||
|
"outputs": [".next/**"] // Missing dist/**!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Correct
|
||||||
|
{
|
||||||
|
"tasks": {
|
||||||
|
"build": {
|
||||||
|
"outputs": [".next/**", "dist/**"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## TypeScript Best Practices
|
||||||
|
|
||||||
|
### Use Node.js Subpath Imports (Not `paths`)
|
||||||
|
|
||||||
|
TypeScript `compilerOptions.paths` breaks with JIT packages. Use Node.js subpath imports instead (TypeScript 5.4+).
|
||||||
|
|
||||||
|
**JIT Package:**
|
||||||
|
|
||||||
|
```json
|
||||||
|
// packages/ui/package.json
|
||||||
|
{
|
||||||
|
"imports": {
|
||||||
|
"#*": "./src/*"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// packages/ui/button.tsx
|
||||||
|
import { MY_STRING } from "#utils.ts"; // Uses .ts extension
|
||||||
|
```
|
||||||
|
|
||||||
|
**Compiled Package:**
|
||||||
|
|
||||||
|
```json
|
||||||
|
// packages/ui/package.json
|
||||||
|
{
|
||||||
|
"imports": {
|
||||||
|
"#*": "./dist/*"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// packages/ui/button.tsx
|
||||||
|
import { MY_STRING } from "#utils.js"; // Uses .js extension
|
||||||
|
```
|
||||||
|
|
||||||
|
### Use `tsc` for Internal Packages
|
||||||
|
|
||||||
|
For internal packages, prefer `tsc` over bundlers. Bundlers can mangle code before it reaches your app's bundler, causing hard-to-debug issues.
|
||||||
|
|
||||||
|
### Enable Go-to-Definition
|
||||||
|
|
||||||
|
For Compiled Packages, enable declaration maps:
|
||||||
|
|
||||||
|
```json
|
||||||
|
// tsconfig.json
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"declaration": true,
|
||||||
|
"declarationMap": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
This creates `.d.ts` and `.d.ts.map` files for IDE navigation.
|
||||||
|
|
||||||
|
### No Root tsconfig.json Needed
|
||||||
|
|
||||||
|
Each package should have its own `tsconfig.json`. A root one causes all tasks to miss cache when changed. Only use root `tsconfig.json` for non-package scripts.
|
||||||
|
|
||||||
|
### Avoid TypeScript Project References
|
||||||
|
|
||||||
|
They add complexity and another caching layer. Turborepo handles dependencies better.
|
||||||
270
.agents/skills/turborepo/references/best-practices/structure.md
Normal file
270
.agents/skills/turborepo/references/best-practices/structure.md
Normal file
@@ -0,0 +1,270 @@
|
|||||||
|
# Repository Structure
|
||||||
|
|
||||||
|
Detailed guidance on structuring a Turborepo monorepo.
|
||||||
|
|
||||||
|
## Workspace Configuration
|
||||||
|
|
||||||
|
### pnpm (Recommended)
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# pnpm-workspace.yaml
|
||||||
|
packages:
|
||||||
|
- "apps/*"
|
||||||
|
- "packages/*"
|
||||||
|
```
|
||||||
|
|
||||||
|
### npm/yarn/bun
|
||||||
|
|
||||||
|
```json
|
||||||
|
// package.json
|
||||||
|
{
|
||||||
|
"workspaces": ["apps/*", "packages/*"]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Root package.json
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"name": "my-monorepo",
|
||||||
|
"private": true,
|
||||||
|
"packageManager": "pnpm@9.0.0",
|
||||||
|
"scripts": {
|
||||||
|
"build": "turbo run build",
|
||||||
|
"dev": "turbo run dev",
|
||||||
|
"lint": "turbo run lint",
|
||||||
|
"test": "turbo run test"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"turbo": "latest"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Key points:
|
||||||
|
|
||||||
|
- `private: true` - Prevents accidental publishing
|
||||||
|
- `packageManager` - Enforces consistent package manager version
|
||||||
|
- **Scripts only delegate to `turbo run`** - No actual build logic here!
|
||||||
|
- Minimal devDependencies (just turbo and repo tools)
|
||||||
|
|
||||||
|
## Always Prefer Package Tasks
|
||||||
|
|
||||||
|
**Always use package tasks. Only use Root Tasks if you cannot succeed with package tasks.**
|
||||||
|
|
||||||
|
```json
|
||||||
|
// packages/web/package.json
|
||||||
|
{
|
||||||
|
"scripts": {
|
||||||
|
"build": "next build",
|
||||||
|
"lint": "eslint .",
|
||||||
|
"test": "vitest",
|
||||||
|
"typecheck": "tsc --noEmit"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// packages/api/package.json
|
||||||
|
{
|
||||||
|
"scripts": {
|
||||||
|
"build": "tsc",
|
||||||
|
"lint": "eslint .",
|
||||||
|
"test": "vitest",
|
||||||
|
"typecheck": "tsc --noEmit"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Package tasks enable Turborepo to:
|
||||||
|
|
||||||
|
1. **Parallelize** - Run `web#lint` and `api#lint` simultaneously
|
||||||
|
2. **Cache individually** - Each package's task output is cached separately
|
||||||
|
3. **Filter precisely** - Run `turbo run test --filter=web` for just one package
|
||||||
|
|
||||||
|
**Root Tasks are a fallback** for tasks that truly cannot run per-package:
|
||||||
|
|
||||||
|
```json
|
||||||
|
// AVOID unless necessary - sequential, not parallelized, can't filter
|
||||||
|
{
|
||||||
|
"scripts": {
|
||||||
|
"lint": "eslint apps/web && eslint apps/api && eslint packages/ui"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Root turbo.json
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"$schema": "https://turborepo.dev/schema.v2.json",
|
||||||
|
"tasks": {
|
||||||
|
"build": {
|
||||||
|
"dependsOn": ["^build"],
|
||||||
|
"outputs": ["dist/**", ".next/**", "!.next/cache/**"]
|
||||||
|
},
|
||||||
|
"lint": {},
|
||||||
|
"test": {
|
||||||
|
"dependsOn": ["build"]
|
||||||
|
},
|
||||||
|
"dev": {
|
||||||
|
"cache": false,
|
||||||
|
"persistent": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Directory Organization
|
||||||
|
|
||||||
|
### Grouping Packages
|
||||||
|
|
||||||
|
You can group packages by adding more workspace paths:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# pnpm-workspace.yaml
|
||||||
|
packages:
|
||||||
|
- "apps/*"
|
||||||
|
- "packages/*"
|
||||||
|
- "packages/config/*" # Grouped configs
|
||||||
|
- "packages/features/*" # Feature packages
|
||||||
|
```
|
||||||
|
|
||||||
|
This allows:
|
||||||
|
|
||||||
|
```
|
||||||
|
packages/
|
||||||
|
├── ui/
|
||||||
|
├── utils/
|
||||||
|
├── config/
|
||||||
|
│ ├── eslint/
|
||||||
|
│ ├── typescript/
|
||||||
|
│ └── tailwind/
|
||||||
|
└── features/
|
||||||
|
├── auth/
|
||||||
|
└── payments/
|
||||||
|
```
|
||||||
|
|
||||||
|
### What NOT to Do
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# BAD: Nested wildcards cause ambiguous behavior
|
||||||
|
packages:
|
||||||
|
- "packages/**" # Don't do this!
|
||||||
|
```
|
||||||
|
|
||||||
|
## Package Anatomy
|
||||||
|
|
||||||
|
### Minimum Required Files
|
||||||
|
|
||||||
|
```
|
||||||
|
packages/ui/
|
||||||
|
├── package.json # Required: Makes it a package
|
||||||
|
├── src/ # Source code
|
||||||
|
│ └── button.tsx
|
||||||
|
└── tsconfig.json # TypeScript config (if using TS)
|
||||||
|
```
|
||||||
|
|
||||||
|
### package.json Requirements
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"name": "@repo/ui", // Unique, namespaced name
|
||||||
|
"version": "0.0.0", // Version (can be 0.0.0 for internal)
|
||||||
|
"private": true, // Prevents accidental publishing
|
||||||
|
"exports": {
|
||||||
|
// Entry points
|
||||||
|
"./button": "./src/button.tsx"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## TypeScript Configuration
|
||||||
|
|
||||||
|
### Shared Base Config
|
||||||
|
|
||||||
|
Create a shared TypeScript config package:
|
||||||
|
|
||||||
|
```
|
||||||
|
packages/
|
||||||
|
└── typescript-config/
|
||||||
|
├── package.json
|
||||||
|
├── base.json
|
||||||
|
├── nextjs.json
|
||||||
|
└── library.json
|
||||||
|
```
|
||||||
|
|
||||||
|
```json
|
||||||
|
// packages/typescript-config/base.json
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"strict": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"module": "ESNext",
|
||||||
|
"target": "ES2022"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Extending in Packages
|
||||||
|
|
||||||
|
```json
|
||||||
|
// packages/ui/tsconfig.json
|
||||||
|
{
|
||||||
|
"extends": "@repo/typescript-config/library.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"outDir": "dist",
|
||||||
|
"rootDir": "src"
|
||||||
|
},
|
||||||
|
"include": ["src"],
|
||||||
|
"exclude": ["node_modules", "dist"]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### No Root tsconfig.json
|
||||||
|
|
||||||
|
You likely don't need a `tsconfig.json` in the workspace root. Each package should have its own config extending from the shared config package.
|
||||||
|
|
||||||
|
## ESLint Configuration
|
||||||
|
|
||||||
|
### Shared Config Package
|
||||||
|
|
||||||
|
```
|
||||||
|
packages/
|
||||||
|
└── eslint-config/
|
||||||
|
├── package.json
|
||||||
|
├── base.js
|
||||||
|
├── next.js
|
||||||
|
└── library.js
|
||||||
|
```
|
||||||
|
|
||||||
|
```json
|
||||||
|
// packages/eslint-config/package.json
|
||||||
|
{
|
||||||
|
"name": "@repo/eslint-config",
|
||||||
|
"exports": {
|
||||||
|
"./base": "./base.js",
|
||||||
|
"./next": "./next.js",
|
||||||
|
"./library": "./library.js"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Using in Packages
|
||||||
|
|
||||||
|
```js
|
||||||
|
// apps/web/.eslintrc.js
|
||||||
|
module.exports = {
|
||||||
|
extends: ["@repo/eslint-config/next"],
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
## Lockfile
|
||||||
|
|
||||||
|
A lockfile is **required** for:
|
||||||
|
|
||||||
|
- Reproducible builds
|
||||||
|
- Turborepo to understand package dependencies
|
||||||
|
- Cache correctness
|
||||||
|
|
||||||
|
Without a lockfile, you'll see unpredictable behavior.
|
||||||
126
.agents/skills/turborepo/references/boundaries/RULE.md
Normal file
126
.agents/skills/turborepo/references/boundaries/RULE.md
Normal file
@@ -0,0 +1,126 @@
|
|||||||
|
# Boundaries
|
||||||
|
|
||||||
|
**Experimental feature** - See [RFC](https://github.com/vercel/turborepo/discussions/9435)
|
||||||
|
|
||||||
|
Full docs: https://turborepo.dev/docs/reference/boundaries
|
||||||
|
|
||||||
|
Boundaries enforce package isolation by detecting:
|
||||||
|
|
||||||
|
1. Imports of files outside the package's directory
|
||||||
|
2. Imports of packages not declared in `package.json` dependencies
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```bash
|
||||||
|
turbo boundaries
|
||||||
|
```
|
||||||
|
|
||||||
|
Run this to check for workspace violations across your monorepo.
|
||||||
|
|
||||||
|
## Tags
|
||||||
|
|
||||||
|
Tags allow you to create rules for which packages can depend on each other.
|
||||||
|
|
||||||
|
### Adding Tags to a Package
|
||||||
|
|
||||||
|
```json
|
||||||
|
// packages/ui/turbo.json
|
||||||
|
{
|
||||||
|
"tags": ["internal"]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Configuring Tag Rules
|
||||||
|
|
||||||
|
Rules go in root `turbo.json`:
|
||||||
|
|
||||||
|
```json
|
||||||
|
// turbo.json
|
||||||
|
{
|
||||||
|
"boundaries": {
|
||||||
|
"tags": {
|
||||||
|
"public": {
|
||||||
|
"dependencies": {
|
||||||
|
"deny": ["internal"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
This prevents `public`-tagged packages from importing `internal`-tagged packages.
|
||||||
|
|
||||||
|
### Rule Types
|
||||||
|
|
||||||
|
**Allow-list approach** (only allow specific tags):
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"boundaries": {
|
||||||
|
"tags": {
|
||||||
|
"public": {
|
||||||
|
"dependencies": {
|
||||||
|
"allow": ["public"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Deny-list approach** (block specific tags):
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"boundaries": {
|
||||||
|
"tags": {
|
||||||
|
"public": {
|
||||||
|
"dependencies": {
|
||||||
|
"deny": ["internal"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Restrict dependents** (who can import this package):
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"boundaries": {
|
||||||
|
"tags": {
|
||||||
|
"private": {
|
||||||
|
"dependents": {
|
||||||
|
"deny": ["public"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Using Package Names
|
||||||
|
|
||||||
|
Package names work in place of tags:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"boundaries": {
|
||||||
|
"tags": {
|
||||||
|
"private": {
|
||||||
|
"dependents": {
|
||||||
|
"deny": ["@repo/my-pkg"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Key Points
|
||||||
|
|
||||||
|
- Rules apply transitively (dependencies of dependencies)
|
||||||
|
- Helps enforce architectural boundaries at scale
|
||||||
|
- Catches violations before runtime/build errors
|
||||||
107
.agents/skills/turborepo/references/caching/RULE.md
Normal file
107
.agents/skills/turborepo/references/caching/RULE.md
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
# How Turborepo Caching Works
|
||||||
|
|
||||||
|
Turborepo's core principle: **never do the same work twice**.
|
||||||
|
|
||||||
|
## The Cache Equation
|
||||||
|
|
||||||
|
```
|
||||||
|
fingerprint(inputs) → stored outputs
|
||||||
|
```
|
||||||
|
|
||||||
|
If inputs haven't changed, restore outputs from cache instead of re-running the task.
|
||||||
|
|
||||||
|
## What Determines the Cache Key
|
||||||
|
|
||||||
|
### Global Hash Inputs
|
||||||
|
|
||||||
|
These affect ALL tasks in the repo:
|
||||||
|
|
||||||
|
- `package-lock.json` / `yarn.lock` / `pnpm-lock.yaml`
|
||||||
|
- Files listed in `globalDependencies`
|
||||||
|
- Environment variables in `globalEnv`
|
||||||
|
- `turbo.json` configuration
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"globalDependencies": [".env", "tsconfig.base.json"],
|
||||||
|
"globalEnv": ["CI", "NODE_ENV"]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Task Hash Inputs
|
||||||
|
|
||||||
|
These affect specific tasks:
|
||||||
|
|
||||||
|
- All files in the package (unless filtered by `inputs`)
|
||||||
|
- `package.json` contents
|
||||||
|
- Environment variables in task's `env` key
|
||||||
|
- Task configuration (command, outputs, dependencies)
|
||||||
|
- Hashes of dependent tasks (`dependsOn`)
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"tasks": {
|
||||||
|
"build": {
|
||||||
|
"dependsOn": ["^build"],
|
||||||
|
"inputs": ["src/**", "package.json", "tsconfig.json"],
|
||||||
|
"env": ["API_URL"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## What Gets Cached
|
||||||
|
|
||||||
|
1. **File outputs** - files/directories specified in `outputs`
|
||||||
|
2. **Task logs** - stdout/stderr for replay on cache hit
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"tasks": {
|
||||||
|
"build": {
|
||||||
|
"outputs": ["dist/**", ".next/**"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Local Cache Location
|
||||||
|
|
||||||
|
```
|
||||||
|
.turbo/cache/
|
||||||
|
├── <hash1>.tar.zst # compressed outputs
|
||||||
|
├── <hash2>.tar.zst
|
||||||
|
└── ...
|
||||||
|
```
|
||||||
|
|
||||||
|
Add `.turbo` to `.gitignore`.
|
||||||
|
|
||||||
|
## Cache Restoration
|
||||||
|
|
||||||
|
On cache hit, Turborepo:
|
||||||
|
|
||||||
|
1. Extracts archived outputs to their original locations
|
||||||
|
2. Replays the logged stdout/stderr
|
||||||
|
3. Reports the task as cached (shows `FULL TURBO` in output)
|
||||||
|
|
||||||
|
## Example Flow
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# First run - executes build, caches result
|
||||||
|
turbo build
|
||||||
|
# → packages/ui: cache miss, executing...
|
||||||
|
# → packages/web: cache miss, executing...
|
||||||
|
|
||||||
|
# Second run - same inputs, restores from cache
|
||||||
|
turbo build
|
||||||
|
# → packages/ui: cache hit, replaying output
|
||||||
|
# → packages/web: cache hit, replaying output
|
||||||
|
# → FULL TURBO
|
||||||
|
```
|
||||||
|
|
||||||
|
## Key Points
|
||||||
|
|
||||||
|
- Cache is content-addressed (based on input hash, not timestamps)
|
||||||
|
- Empty `outputs` array means task runs but nothing is cached
|
||||||
|
- Tasks without `outputs` key cache nothing (use `"outputs": []` to be explicit)
|
||||||
|
- Cache is invalidated when ANY input changes
|
||||||
169
.agents/skills/turborepo/references/caching/gotchas.md
Normal file
169
.agents/skills/turborepo/references/caching/gotchas.md
Normal file
@@ -0,0 +1,169 @@
|
|||||||
|
# Debugging Cache Issues
|
||||||
|
|
||||||
|
## Diagnostic Tools
|
||||||
|
|
||||||
|
### `--summarize`
|
||||||
|
|
||||||
|
Generates a JSON file with all hash inputs. Compare two runs to find differences.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
turbo build --summarize
|
||||||
|
# Creates .turbo/runs/<run-id>.json
|
||||||
|
```
|
||||||
|
|
||||||
|
The summary includes:
|
||||||
|
|
||||||
|
- Global hash and its inputs
|
||||||
|
- Per-task hashes and their inputs
|
||||||
|
- Environment variables that affected the hash
|
||||||
|
|
||||||
|
**Comparing runs:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Run twice, compare the summaries
|
||||||
|
diff .turbo/runs/<first-run>.json .turbo/runs/<second-run>.json
|
||||||
|
```
|
||||||
|
|
||||||
|
### `--dry` / `--dry=json`
|
||||||
|
|
||||||
|
See what would run without executing anything:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
turbo build --dry
|
||||||
|
turbo build --dry=json # machine-readable output
|
||||||
|
```
|
||||||
|
|
||||||
|
Shows cache status for each task without running them.
|
||||||
|
|
||||||
|
### `--force`
|
||||||
|
|
||||||
|
Skip reading cache, re-execute all tasks:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
turbo build --force
|
||||||
|
```
|
||||||
|
|
||||||
|
Useful to verify tasks actually work (not just cached results).
|
||||||
|
|
||||||
|
## Unexpected Cache Misses
|
||||||
|
|
||||||
|
**Symptom:** Task runs when you expected a cache hit.
|
||||||
|
|
||||||
|
### Environment Variable Changed
|
||||||
|
|
||||||
|
Check if an env var in the `env` key changed:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"tasks": {
|
||||||
|
"build": {
|
||||||
|
"env": ["API_URL", "NODE_ENV"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Different `API_URL` between runs = cache miss.
|
||||||
|
|
||||||
|
### .env File Changed
|
||||||
|
|
||||||
|
`.env` files aren't tracked by default. Add to `inputs`:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"tasks": {
|
||||||
|
"build": {
|
||||||
|
"inputs": ["$TURBO_DEFAULT$", ".env", ".env.local"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Or use `globalDependencies` for repo-wide env files:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"globalDependencies": [".env"]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Lockfile Changed
|
||||||
|
|
||||||
|
Installing/updating packages changes the global hash.
|
||||||
|
|
||||||
|
### Source Files Changed
|
||||||
|
|
||||||
|
Any file in the package (or in `inputs`) triggers a miss.
|
||||||
|
|
||||||
|
### turbo.json Changed
|
||||||
|
|
||||||
|
Config changes invalidate the global hash.
|
||||||
|
|
||||||
|
## Incorrect Cache Hits
|
||||||
|
|
||||||
|
**Symptom:** Cached output is stale/wrong.
|
||||||
|
|
||||||
|
### Missing Environment Variable
|
||||||
|
|
||||||
|
Task uses an env var not listed in `env`:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// build.js
|
||||||
|
const apiUrl = process.env.API_URL; // not tracked!
|
||||||
|
```
|
||||||
|
|
||||||
|
Fix: add to task config:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"tasks": {
|
||||||
|
"build": {
|
||||||
|
"env": ["API_URL"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Missing File in Inputs
|
||||||
|
|
||||||
|
Task reads a file outside default inputs:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"tasks": {
|
||||||
|
"build": {
|
||||||
|
"inputs": [
|
||||||
|
"$TURBO_DEFAULT$",
|
||||||
|
"../../shared-config.json" // file outside package
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Useful Flags
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Only show output for cache misses
|
||||||
|
turbo build --output-logs=new-only
|
||||||
|
|
||||||
|
# Show output for everything (debugging)
|
||||||
|
turbo build --output-logs=full
|
||||||
|
|
||||||
|
# See why tasks are running
|
||||||
|
turbo build --verbosity=2
|
||||||
|
```
|
||||||
|
|
||||||
|
## Quick Checklist
|
||||||
|
|
||||||
|
Cache miss when expected hit:
|
||||||
|
|
||||||
|
1. Run with `--summarize`, compare with previous run
|
||||||
|
2. Check env vars with `--dry=json`
|
||||||
|
3. Look for lockfile/config changes in git
|
||||||
|
|
||||||
|
Cache hit when expected miss:
|
||||||
|
|
||||||
|
1. Verify env var is in `env` array
|
||||||
|
2. Verify file is in `inputs` array
|
||||||
|
3. Check if file is outside package directory
|
||||||
127
.agents/skills/turborepo/references/caching/remote-cache.md
Normal file
127
.agents/skills/turborepo/references/caching/remote-cache.md
Normal file
@@ -0,0 +1,127 @@
|
|||||||
|
# Remote Caching
|
||||||
|
|
||||||
|
Share cache artifacts across your team and CI pipelines.
|
||||||
|
|
||||||
|
## Benefits
|
||||||
|
|
||||||
|
- Team members get cache hits from each other's work
|
||||||
|
- CI gets cache hits from local development (and vice versa)
|
||||||
|
- Dramatically faster CI runs after first build
|
||||||
|
- No more "works on my machine" rebuilds
|
||||||
|
|
||||||
|
## Vercel Remote Cache
|
||||||
|
|
||||||
|
Free, zero-config when deploying on Vercel. For local dev and other CI:
|
||||||
|
|
||||||
|
### Local Development Setup
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Authenticate with Vercel
|
||||||
|
npx turbo login
|
||||||
|
|
||||||
|
# Link repo to your Vercel team
|
||||||
|
npx turbo link
|
||||||
|
```
|
||||||
|
|
||||||
|
This creates `.turbo/config.json` with your team info (gitignored by default).
|
||||||
|
|
||||||
|
### CI Setup
|
||||||
|
|
||||||
|
Set these environment variables:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
TURBO_TOKEN=<your-token>
|
||||||
|
TURBO_TEAM=<your-team-slug>
|
||||||
|
```
|
||||||
|
|
||||||
|
Get your token from Vercel dashboard → Settings → Tokens.
|
||||||
|
|
||||||
|
**GitHub Actions example:**
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
- name: Build
|
||||||
|
run: npx turbo build
|
||||||
|
env:
|
||||||
|
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
|
||||||
|
TURBO_TEAM: ${{ vars.TURBO_TEAM }}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Configuration in turbo.json
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"remoteCache": {
|
||||||
|
"enabled": true,
|
||||||
|
"signature": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Options:
|
||||||
|
|
||||||
|
- `enabled`: toggle remote cache (default: true when authenticated)
|
||||||
|
- `signature`: require artifact signing (default: false)
|
||||||
|
|
||||||
|
## Artifact Signing
|
||||||
|
|
||||||
|
Verify cache artifacts haven't been tampered with:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Set a secret key (use same key across all environments)
|
||||||
|
export TURBO_REMOTE_CACHE_SIGNATURE_KEY="your-secret-key"
|
||||||
|
```
|
||||||
|
|
||||||
|
Enable in config:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"remoteCache": {
|
||||||
|
"signature": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Signed artifacts can only be restored if the signature matches.
|
||||||
|
|
||||||
|
## Self-Hosted Options
|
||||||
|
|
||||||
|
Community implementations for running your own cache server:
|
||||||
|
|
||||||
|
- **turbo-remote-cache** (Node.js) - supports S3, GCS, Azure
|
||||||
|
- **turborepo-remote-cache** (Go) - lightweight, S3-compatible
|
||||||
|
- **ducktape** (Rust) - high-performance option
|
||||||
|
|
||||||
|
Configure with environment variables:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
TURBO_API=https://your-cache-server.com
|
||||||
|
TURBO_TOKEN=your-auth-token
|
||||||
|
TURBO_TEAM=your-team
|
||||||
|
```
|
||||||
|
|
||||||
|
## Cache Behavior Control
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Disable remote cache for a run
|
||||||
|
turbo build --remote-cache-read-only # read but don't write
|
||||||
|
turbo build --no-cache # skip cache entirely
|
||||||
|
|
||||||
|
# Environment variable alternative
|
||||||
|
TURBO_REMOTE_ONLY=true # only use remote, skip local
|
||||||
|
```
|
||||||
|
|
||||||
|
## Debugging Remote Cache
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Verbose output shows cache operations
|
||||||
|
turbo build --verbosity=2
|
||||||
|
|
||||||
|
# Check if remote cache is configured
|
||||||
|
turbo config
|
||||||
|
```
|
||||||
|
|
||||||
|
Look for:
|
||||||
|
|
||||||
|
- "Remote caching enabled" in output
|
||||||
|
- Upload/download messages during runs
|
||||||
|
- "cache hit, replaying output" with remote cache indicator
|
||||||
79
.agents/skills/turborepo/references/ci/RULE.md
Normal file
79
.agents/skills/turborepo/references/ci/RULE.md
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
# CI/CD with Turborepo
|
||||||
|
|
||||||
|
General principles for running Turborepo in continuous integration environments.
|
||||||
|
|
||||||
|
## Core Principles
|
||||||
|
|
||||||
|
### Always Use `turbo run` in CI
|
||||||
|
|
||||||
|
**Never use the `turbo <tasks>` shorthand in CI or scripts.** Always use `turbo run`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# CORRECT - Always use in CI, package.json, scripts
|
||||||
|
turbo run build test lint
|
||||||
|
|
||||||
|
# WRONG - Shorthand is only for one-off terminal commands
|
||||||
|
turbo build test lint
|
||||||
|
```
|
||||||
|
|
||||||
|
The shorthand `turbo <tasks>` is only for one-off invocations typed directly in terminal by humans or agents. Anywhere the command is written into code (CI, package.json, scripts), use `turbo run`.
|
||||||
|
|
||||||
|
### Enable Remote Caching
|
||||||
|
|
||||||
|
Remote caching dramatically speeds up CI by sharing cached artifacts across runs.
|
||||||
|
|
||||||
|
Required environment variables:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
TURBO_TOKEN=your_vercel_token
|
||||||
|
TURBO_TEAM=your_team_slug
|
||||||
|
```
|
||||||
|
|
||||||
|
### Use --affected for PR Builds
|
||||||
|
|
||||||
|
The `--affected` flag only runs tasks for packages changed since the base branch:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
turbo run build test --affected
|
||||||
|
```
|
||||||
|
|
||||||
|
This requires Git history to compute what changed.
|
||||||
|
|
||||||
|
## Git History Requirements
|
||||||
|
|
||||||
|
### Fetch Depth
|
||||||
|
|
||||||
|
`--affected` needs access to the merge base. Shallow clones break this.
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# GitHub Actions
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 2 # Minimum for --affected
|
||||||
|
# Use 0 for full history if merge base is far
|
||||||
|
```
|
||||||
|
|
||||||
|
### Why Shallow Clones Break --affected
|
||||||
|
|
||||||
|
Turborepo compares the current HEAD to the merge base with `main`. If that commit isn't fetched, `--affected` falls back to running everything.
|
||||||
|
|
||||||
|
For PRs with many commits, consider:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
fetch-depth: 0 # Full history
|
||||||
|
```
|
||||||
|
|
||||||
|
## Environment Variables Reference
|
||||||
|
|
||||||
|
| Variable | Purpose |
|
||||||
|
| ------------------- | ------------------------------------ |
|
||||||
|
| `TURBO_TOKEN` | Vercel access token for remote cache |
|
||||||
|
| `TURBO_TEAM` | Your Vercel team slug |
|
||||||
|
| `TURBO_REMOTE_ONLY` | Skip local cache, use remote only |
|
||||||
|
| `TURBO_LOG_ORDER` | Set to `grouped` for cleaner CI logs |
|
||||||
|
|
||||||
|
## See Also
|
||||||
|
|
||||||
|
- [github-actions.md](./github-actions.md) - GitHub Actions setup
|
||||||
|
- [vercel.md](./vercel.md) - Vercel deployment
|
||||||
|
- [patterns.md](./patterns.md) - CI optimization patterns
|
||||||
162
.agents/skills/turborepo/references/ci/github-actions.md
Normal file
162
.agents/skills/turborepo/references/ci/github-actions.md
Normal file
@@ -0,0 +1,162 @@
|
|||||||
|
# GitHub Actions
|
||||||
|
|
||||||
|
Complete setup guide for Turborepo with GitHub Actions.
|
||||||
|
|
||||||
|
## Basic Workflow Structure
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
name: CI
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [main]
|
||||||
|
pull_request:
|
||||||
|
branches: [main]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 2
|
||||||
|
|
||||||
|
- uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: 20
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: npm ci
|
||||||
|
|
||||||
|
- name: Build and Test
|
||||||
|
run: turbo run build test lint
|
||||||
|
```
|
||||||
|
|
||||||
|
## Package Manager Setup
|
||||||
|
|
||||||
|
### pnpm
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
- uses: pnpm/action-setup@v3
|
||||||
|
with:
|
||||||
|
version: 9
|
||||||
|
|
||||||
|
- uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: 20
|
||||||
|
cache: "pnpm"
|
||||||
|
|
||||||
|
- run: pnpm install --frozen-lockfile
|
||||||
|
```
|
||||||
|
|
||||||
|
### Yarn
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
- uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: 20
|
||||||
|
cache: "yarn"
|
||||||
|
|
||||||
|
- run: yarn install --frozen-lockfile
|
||||||
|
```
|
||||||
|
|
||||||
|
### Bun
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
- uses: oven-sh/setup-bun@v1
|
||||||
|
with:
|
||||||
|
bun-version: latest
|
||||||
|
|
||||||
|
- run: bun install --frozen-lockfile
|
||||||
|
```
|
||||||
|
|
||||||
|
## Remote Cache Setup
|
||||||
|
|
||||||
|
### 1. Create Vercel Access Token
|
||||||
|
|
||||||
|
1. Go to [Vercel Dashboard](https://vercel.com/account/tokens)
|
||||||
|
2. Create a new token with appropriate scope
|
||||||
|
3. Copy the token value
|
||||||
|
|
||||||
|
### 2. Add Secrets and Variables
|
||||||
|
|
||||||
|
In your GitHub repository settings:
|
||||||
|
|
||||||
|
**Secrets** (Settings > Secrets and variables > Actions > Secrets):
|
||||||
|
|
||||||
|
- `TURBO_TOKEN`: Your Vercel access token
|
||||||
|
|
||||||
|
**Variables** (Settings > Secrets and variables > Actions > Variables):
|
||||||
|
|
||||||
|
- `TURBO_TEAM`: Your Vercel team slug
|
||||||
|
|
||||||
|
### 3. Add to Workflow
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
env:
|
||||||
|
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
|
||||||
|
TURBO_TEAM: ${{ vars.TURBO_TEAM }}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Alternative: actions/cache
|
||||||
|
|
||||||
|
If you can't use remote cache, cache Turborepo's local cache directory:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
- uses: actions/cache@v4
|
||||||
|
with:
|
||||||
|
path: .turbo
|
||||||
|
key: turbo-${{ runner.os }}-${{ hashFiles('**/turbo.json', '**/package-lock.json') }}
|
||||||
|
restore-keys: |
|
||||||
|
turbo-${{ runner.os }}-
|
||||||
|
```
|
||||||
|
|
||||||
|
Note: This is less effective than remote cache since it's per-branch.
|
||||||
|
|
||||||
|
## Complete Example
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
name: CI
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [main]
|
||||||
|
pull_request:
|
||||||
|
branches: [main]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
env:
|
||||||
|
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
|
||||||
|
TURBO_TEAM: ${{ vars.TURBO_TEAM }}
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 2
|
||||||
|
|
||||||
|
- uses: pnpm/action-setup@v3
|
||||||
|
with:
|
||||||
|
version: 9
|
||||||
|
|
||||||
|
- uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: 20
|
||||||
|
cache: "pnpm"
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: pnpm install --frozen-lockfile
|
||||||
|
|
||||||
|
- name: Build
|
||||||
|
run: turbo run build --affected
|
||||||
|
|
||||||
|
- name: Test
|
||||||
|
run: turbo run test --affected
|
||||||
|
|
||||||
|
- name: Lint
|
||||||
|
run: turbo run lint --affected
|
||||||
|
```
|
||||||
145
.agents/skills/turborepo/references/ci/patterns.md
Normal file
145
.agents/skills/turborepo/references/ci/patterns.md
Normal file
@@ -0,0 +1,145 @@
|
|||||||
|
# CI Optimization Patterns
|
||||||
|
|
||||||
|
Strategies for efficient CI/CD with Turborepo.
|
||||||
|
|
||||||
|
## PR vs Main Branch Builds
|
||||||
|
|
||||||
|
### PR Builds: Only Affected
|
||||||
|
|
||||||
|
Test only what changed in the PR:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
- name: Test (PR)
|
||||||
|
if: github.event_name == 'pull_request'
|
||||||
|
run: turbo run build test --affected
|
||||||
|
```
|
||||||
|
|
||||||
|
### Main Branch: Full Build
|
||||||
|
|
||||||
|
Ensure complete validation on merge:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
- name: Test (Main)
|
||||||
|
if: github.ref == 'refs/heads/main'
|
||||||
|
run: turbo run build test
|
||||||
|
```
|
||||||
|
|
||||||
|
## Custom Git Ranges with --filter
|
||||||
|
|
||||||
|
For advanced scenarios, use `--filter` with git refs:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Changes since specific commit
|
||||||
|
turbo run test --filter="...[abc123]"
|
||||||
|
|
||||||
|
# Changes between refs
|
||||||
|
turbo run test --filter="...[main...HEAD]"
|
||||||
|
|
||||||
|
# Changes in last 3 commits
|
||||||
|
turbo run test --filter="...[HEAD~3]"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Caching Strategies
|
||||||
|
|
||||||
|
### Remote Cache (Recommended)
|
||||||
|
|
||||||
|
Best performance - shared across all CI runs and developers:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
env:
|
||||||
|
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
|
||||||
|
TURBO_TEAM: ${{ vars.TURBO_TEAM }}
|
||||||
|
```
|
||||||
|
|
||||||
|
### actions/cache Fallback
|
||||||
|
|
||||||
|
When remote cache isn't available:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
- uses: actions/cache@v4
|
||||||
|
with:
|
||||||
|
path: .turbo
|
||||||
|
key: turbo-${{ runner.os }}-${{ github.sha }}
|
||||||
|
restore-keys: |
|
||||||
|
turbo-${{ runner.os }}-${{ github.ref }}-
|
||||||
|
turbo-${{ runner.os }}-
|
||||||
|
```
|
||||||
|
|
||||||
|
Limitations:
|
||||||
|
|
||||||
|
- Cache is branch-scoped
|
||||||
|
- PRs restore from base branch cache
|
||||||
|
- Less efficient than remote cache
|
||||||
|
|
||||||
|
## Matrix Builds
|
||||||
|
|
||||||
|
Test across Node versions:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
node: [18, 20, 22]
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: ${{ matrix.node }}
|
||||||
|
|
||||||
|
- run: turbo run test
|
||||||
|
```
|
||||||
|
|
||||||
|
## Parallelizing Across Jobs
|
||||||
|
|
||||||
|
Split tasks into separate jobs:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
jobs:
|
||||||
|
lint:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- run: turbo run lint --affected
|
||||||
|
|
||||||
|
test:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- run: turbo run test --affected
|
||||||
|
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: [lint, test]
|
||||||
|
steps:
|
||||||
|
- run: turbo run build
|
||||||
|
```
|
||||||
|
|
||||||
|
### Cache Considerations
|
||||||
|
|
||||||
|
When parallelizing:
|
||||||
|
|
||||||
|
- Each job has separate cache writes
|
||||||
|
- Remote cache handles this automatically
|
||||||
|
- With actions/cache, use unique keys per job to avoid conflicts
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
- uses: actions/cache@v4
|
||||||
|
with:
|
||||||
|
path: .turbo
|
||||||
|
key: turbo-${{ runner.os }}-${{ github.job }}-${{ github.sha }}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Conditional Tasks
|
||||||
|
|
||||||
|
Skip expensive tasks on draft PRs:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
- name: E2E Tests
|
||||||
|
if: github.event.pull_request.draft == false
|
||||||
|
run: turbo run test:e2e --affected
|
||||||
|
```
|
||||||
|
|
||||||
|
Or require label for full test:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
- name: Full Test Suite
|
||||||
|
if: contains(github.event.pull_request.labels.*.name, 'full-test')
|
||||||
|
run: turbo run test
|
||||||
|
```
|
||||||
103
.agents/skills/turborepo/references/ci/vercel.md
Normal file
103
.agents/skills/turborepo/references/ci/vercel.md
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
# Vercel Deployment
|
||||||
|
|
||||||
|
Turborepo integrates seamlessly with Vercel for monorepo deployments.
|
||||||
|
|
||||||
|
## Remote Cache
|
||||||
|
|
||||||
|
Remote caching is **automatically enabled** when deploying to Vercel. No configuration needed - Vercel detects Turborepo and enables caching.
|
||||||
|
|
||||||
|
This means:
|
||||||
|
|
||||||
|
- No `TURBO_TOKEN` or `TURBO_TEAM` setup required on Vercel
|
||||||
|
- Cache is shared across all deployments
|
||||||
|
- Preview and production builds benefit from cache
|
||||||
|
|
||||||
|
## turbo-ignore
|
||||||
|
|
||||||
|
Skip unnecessary builds when a package hasn't changed using `turbo-ignore`.
|
||||||
|
|
||||||
|
### Installation
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npx turbo-ignore
|
||||||
|
```
|
||||||
|
|
||||||
|
Or install globally in your project:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm add -D turbo-ignore
|
||||||
|
```
|
||||||
|
|
||||||
|
### Setup in Vercel
|
||||||
|
|
||||||
|
1. Go to your project in Vercel Dashboard
|
||||||
|
2. Navigate to Settings > Git > Ignored Build Step
|
||||||
|
3. Select "Custom" and enter:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npx turbo-ignore
|
||||||
|
```
|
||||||
|
|
||||||
|
### How It Works
|
||||||
|
|
||||||
|
`turbo-ignore` checks if the current package (or its dependencies) changed since the last successful deployment:
|
||||||
|
|
||||||
|
1. Compares current commit to last deployed commit
|
||||||
|
2. Uses Turborepo's dependency graph
|
||||||
|
3. Returns exit code 0 (skip) if no changes
|
||||||
|
4. Returns exit code 1 (build) if changes detected
|
||||||
|
|
||||||
|
### Options
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Check specific package
|
||||||
|
npx turbo-ignore web
|
||||||
|
|
||||||
|
# Use specific comparison ref
|
||||||
|
npx turbo-ignore --fallback=HEAD~1
|
||||||
|
|
||||||
|
# Verbose output
|
||||||
|
npx turbo-ignore --verbose
|
||||||
|
```
|
||||||
|
|
||||||
|
## Environment Variables
|
||||||
|
|
||||||
|
Set environment variables in Vercel Dashboard:
|
||||||
|
|
||||||
|
1. Go to Project Settings > Environment Variables
|
||||||
|
2. Add variables for each environment (Production, Preview, Development)
|
||||||
|
|
||||||
|
Common variables:
|
||||||
|
|
||||||
|
- `DATABASE_URL`
|
||||||
|
- `API_KEY`
|
||||||
|
- Package-specific config
|
||||||
|
|
||||||
|
## Monorepo Root Directory
|
||||||
|
|
||||||
|
For monorepos, set the root directory in Vercel:
|
||||||
|
|
||||||
|
1. Project Settings > General > Root Directory
|
||||||
|
2. Set to the package path (e.g., `apps/web`)
|
||||||
|
|
||||||
|
Vercel automatically:
|
||||||
|
|
||||||
|
- Installs dependencies from monorepo root
|
||||||
|
- Runs build from the package directory
|
||||||
|
- Detects framework settings
|
||||||
|
|
||||||
|
## Build Command
|
||||||
|
|
||||||
|
Vercel auto-detects `turbo run build` when `turbo.json` exists at root.
|
||||||
|
|
||||||
|
Override if needed:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
turbo run build --filter=web
|
||||||
|
```
|
||||||
|
|
||||||
|
Or for production-only optimizations:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
turbo run build --filter=web --env-mode=strict
|
||||||
|
```
|
||||||
100
.agents/skills/turborepo/references/cli/RULE.md
Normal file
100
.agents/skills/turborepo/references/cli/RULE.md
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
# turbo run
|
||||||
|
|
||||||
|
The primary command for executing tasks across your monorepo.
|
||||||
|
|
||||||
|
## Basic Usage
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Full form (use in CI, package.json, scripts)
|
||||||
|
turbo run <tasks>
|
||||||
|
|
||||||
|
# Shorthand (only for one-off terminal invocations)
|
||||||
|
turbo <tasks>
|
||||||
|
```
|
||||||
|
|
||||||
|
## When to Use `turbo run` vs `turbo`
|
||||||
|
|
||||||
|
**Always use `turbo run` when the command is written into code:**
|
||||||
|
|
||||||
|
- `package.json` scripts
|
||||||
|
- CI/CD workflows (GitHub Actions, etc.)
|
||||||
|
- Shell scripts
|
||||||
|
- Documentation
|
||||||
|
- Any static/committed configuration
|
||||||
|
|
||||||
|
**Only use `turbo` (shorthand) for:**
|
||||||
|
|
||||||
|
- One-off commands typed directly in terminal
|
||||||
|
- Ad-hoc invocations by humans or agents
|
||||||
|
|
||||||
|
```json
|
||||||
|
// package.json - ALWAYS use "turbo run"
|
||||||
|
{
|
||||||
|
"scripts": {
|
||||||
|
"build": "turbo run build",
|
||||||
|
"dev": "turbo run dev",
|
||||||
|
"lint": "turbo run lint",
|
||||||
|
"test": "turbo run test"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# CI workflow - ALWAYS use "turbo run"
|
||||||
|
- run: turbo run build --affected
|
||||||
|
- run: turbo run test --affected
|
||||||
|
```
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Terminal one-off - shorthand OK
|
||||||
|
turbo build --filter=web
|
||||||
|
```
|
||||||
|
|
||||||
|
## Running Tasks
|
||||||
|
|
||||||
|
Tasks must be defined in `turbo.json` before running.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Single task
|
||||||
|
turbo build
|
||||||
|
|
||||||
|
# Multiple tasks
|
||||||
|
turbo run build lint test
|
||||||
|
|
||||||
|
# See available tasks (run without arguments)
|
||||||
|
turbo run
|
||||||
|
```
|
||||||
|
|
||||||
|
## Passing Arguments to Scripts
|
||||||
|
|
||||||
|
Use `--` to pass arguments through to the underlying package scripts:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
turbo run build -- --sourcemap
|
||||||
|
turbo test -- --watch
|
||||||
|
turbo lint -- --fix
|
||||||
|
```
|
||||||
|
|
||||||
|
Everything after `--` goes directly to the task's script.
|
||||||
|
|
||||||
|
## Package Selection
|
||||||
|
|
||||||
|
By default, turbo runs tasks in all packages. Use `--filter` to narrow scope:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
turbo build --filter=web
|
||||||
|
turbo test --filter=./apps/*
|
||||||
|
```
|
||||||
|
|
||||||
|
See `filtering/` for complete filter syntax.
|
||||||
|
|
||||||
|
## Quick Reference
|
||||||
|
|
||||||
|
| Goal | Command |
|
||||||
|
| ------------------- | -------------------------- |
|
||||||
|
| Build everything | `turbo build` |
|
||||||
|
| Build one package | `turbo build --filter=web` |
|
||||||
|
| Multiple tasks | `turbo build lint test` |
|
||||||
|
| Pass args to script | `turbo build -- --arg` |
|
||||||
|
| Preview run | `turbo build --dry` |
|
||||||
|
| Force rebuild | `turbo build --force` |
|
||||||
297
.agents/skills/turborepo/references/cli/commands.md
Normal file
297
.agents/skills/turborepo/references/cli/commands.md
Normal file
@@ -0,0 +1,297 @@
|
|||||||
|
# turbo run Flags Reference
|
||||||
|
|
||||||
|
Full docs: https://turborepo.dev/docs/reference/run
|
||||||
|
|
||||||
|
## Package Selection
|
||||||
|
|
||||||
|
### `--filter` / `-F`
|
||||||
|
|
||||||
|
Select specific packages to run tasks in.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
turbo build --filter=web
|
||||||
|
turbo build -F=@repo/ui -F=@repo/utils
|
||||||
|
turbo test --filter=./apps/*
|
||||||
|
```
|
||||||
|
|
||||||
|
See `filtering/` for complete syntax (globs, dependencies, git ranges).
|
||||||
|
|
||||||
|
### Task Identifier Syntax (v2.2.4+)
|
||||||
|
|
||||||
|
Run specific package tasks directly:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
turbo run web#build # Build web package
|
||||||
|
turbo run web#build docs#lint # Multiple specific tasks
|
||||||
|
```
|
||||||
|
|
||||||
|
### `--affected`
|
||||||
|
|
||||||
|
Run only in packages changed since the base branch.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
turbo build --affected
|
||||||
|
turbo test --affected --filter=./apps/* # combine with filter
|
||||||
|
```
|
||||||
|
|
||||||
|
**How it works:**
|
||||||
|
|
||||||
|
- Default: compares `main...HEAD`
|
||||||
|
- In GitHub Actions: auto-detects `GITHUB_BASE_REF`
|
||||||
|
- Override base: `TURBO_SCM_BASE=development turbo build --affected`
|
||||||
|
- Override head: `TURBO_SCM_HEAD=your-branch turbo build --affected`
|
||||||
|
|
||||||
|
**Requires git history** - shallow clones may fall back to running all tasks.
|
||||||
|
|
||||||
|
## Execution Control
|
||||||
|
|
||||||
|
### `--dry` / `--dry=json`
|
||||||
|
|
||||||
|
Preview what would run without executing.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
turbo build --dry # human-readable
|
||||||
|
turbo build --dry=json # machine-readable
|
||||||
|
```
|
||||||
|
|
||||||
|
### `--force`
|
||||||
|
|
||||||
|
Ignore all cached artifacts, re-run everything.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
turbo build --force
|
||||||
|
```
|
||||||
|
|
||||||
|
### `--concurrency`
|
||||||
|
|
||||||
|
Limit parallel task execution.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
turbo build --concurrency=4 # max 4 tasks
|
||||||
|
turbo build --concurrency=50% # 50% of CPU cores
|
||||||
|
```
|
||||||
|
|
||||||
|
### `--continue`
|
||||||
|
|
||||||
|
Keep running other tasks when one fails.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
turbo build test --continue
|
||||||
|
```
|
||||||
|
|
||||||
|
### `--only`
|
||||||
|
|
||||||
|
Run only the specified task, skip its dependencies.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
turbo build --only # skip running dependsOn tasks
|
||||||
|
```
|
||||||
|
|
||||||
|
### `--parallel` (Discouraged)
|
||||||
|
|
||||||
|
Ignores task graph dependencies, runs all tasks simultaneously. **Avoid using this flag**—if tasks need to run in parallel, configure `dependsOn` correctly instead. Using `--parallel` bypasses Turborepo's dependency graph, which can cause race conditions and incorrect builds.
|
||||||
|
|
||||||
|
## Cache Control
|
||||||
|
|
||||||
|
### `--cache`
|
||||||
|
|
||||||
|
Fine-grained cache behavior control.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Default: read/write both local and remote
|
||||||
|
turbo build --cache=local:rw,remote:rw
|
||||||
|
|
||||||
|
# Read-only local, no remote
|
||||||
|
turbo build --cache=local:r,remote:
|
||||||
|
|
||||||
|
# Disable local, read-only remote
|
||||||
|
turbo build --cache=local:,remote:r
|
||||||
|
|
||||||
|
# Disable all caching
|
||||||
|
turbo build --cache=local:,remote:
|
||||||
|
```
|
||||||
|
|
||||||
|
## Output & Debugging
|
||||||
|
|
||||||
|
### `--graph`
|
||||||
|
|
||||||
|
Generate task graph visualization.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
turbo build --graph # opens in browser
|
||||||
|
turbo build --graph=graph.svg # SVG file
|
||||||
|
turbo build --graph=graph.png # PNG file
|
||||||
|
turbo build --graph=graph.json # JSON data
|
||||||
|
turbo build --graph=graph.mermaid # Mermaid diagram
|
||||||
|
```
|
||||||
|
|
||||||
|
### `--summarize`
|
||||||
|
|
||||||
|
Generate JSON run summary for debugging.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
turbo build --summarize
|
||||||
|
# creates .turbo/runs/<run-id>.json
|
||||||
|
```
|
||||||
|
|
||||||
|
### `--output-logs`
|
||||||
|
|
||||||
|
Control log output verbosity.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
turbo build --output-logs=full # all logs (default)
|
||||||
|
turbo build --output-logs=new-only # only cache misses
|
||||||
|
turbo build --output-logs=errors-only # only failures
|
||||||
|
turbo build --output-logs=none # silent
|
||||||
|
```
|
||||||
|
|
||||||
|
### `--profile`
|
||||||
|
|
||||||
|
Generate Chrome tracing profile for performance analysis.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
turbo build --profile=profile.json
|
||||||
|
# open chrome://tracing and load the file
|
||||||
|
```
|
||||||
|
|
||||||
|
### `--verbosity` / `-v`
|
||||||
|
|
||||||
|
Control turbo's own log level.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
turbo build -v # verbose
|
||||||
|
turbo build -vv # more verbose
|
||||||
|
turbo build -vvv # maximum verbosity
|
||||||
|
```
|
||||||
|
|
||||||
|
## Environment
|
||||||
|
|
||||||
|
### `--env-mode`
|
||||||
|
|
||||||
|
Control environment variable handling.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
turbo build --env-mode=strict # only declared env vars (default)
|
||||||
|
turbo build --env-mode=loose # include all env vars in hash
|
||||||
|
```
|
||||||
|
|
||||||
|
## UI
|
||||||
|
|
||||||
|
### `--ui`
|
||||||
|
|
||||||
|
Select output interface.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
turbo build --ui=tui # interactive terminal UI (default in TTY)
|
||||||
|
turbo build --ui=stream # streaming logs (default in CI)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# turbo-ignore
|
||||||
|
|
||||||
|
Full docs: https://turborepo.dev/docs/reference/turbo-ignore
|
||||||
|
|
||||||
|
Skip CI work when nothing relevant changed. Useful for skipping container setup.
|
||||||
|
|
||||||
|
## Basic Usage
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Check if build is needed for current package (uses Automatic Package Scoping)
|
||||||
|
npx turbo-ignore
|
||||||
|
|
||||||
|
# Check specific package
|
||||||
|
npx turbo-ignore web
|
||||||
|
|
||||||
|
# Check specific task
|
||||||
|
npx turbo-ignore --task=test
|
||||||
|
```
|
||||||
|
|
||||||
|
## Exit Codes
|
||||||
|
|
||||||
|
- `0`: No changes detected - skip CI work
|
||||||
|
- `1`: Changes detected - proceed with CI
|
||||||
|
|
||||||
|
## CI Integration Example
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# GitHub Actions
|
||||||
|
- name: Check for changes
|
||||||
|
id: turbo-ignore
|
||||||
|
run: npx turbo-ignore web
|
||||||
|
continue-on-error: true
|
||||||
|
|
||||||
|
- name: Build
|
||||||
|
if: steps.turbo-ignore.outcome == 'failure' # changes detected
|
||||||
|
run: pnpm build
|
||||||
|
```
|
||||||
|
|
||||||
|
## Comparison Depth
|
||||||
|
|
||||||
|
Default: compares to parent commit (`HEAD^1`).
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Compare to specific commit
|
||||||
|
npx turbo-ignore --fallback=abc123
|
||||||
|
|
||||||
|
# Compare to branch
|
||||||
|
npx turbo-ignore --fallback=main
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Other Commands
|
||||||
|
|
||||||
|
## turbo boundaries
|
||||||
|
|
||||||
|
Check workspace violations (experimental).
|
||||||
|
|
||||||
|
```bash
|
||||||
|
turbo boundaries
|
||||||
|
```
|
||||||
|
|
||||||
|
See `references/boundaries/` for configuration.
|
||||||
|
|
||||||
|
## turbo watch
|
||||||
|
|
||||||
|
Re-run tasks on file changes.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
turbo watch build test
|
||||||
|
```
|
||||||
|
|
||||||
|
See `references/watch/` for details.
|
||||||
|
|
||||||
|
## turbo prune
|
||||||
|
|
||||||
|
Create sparse checkout for Docker.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
turbo prune web --docker
|
||||||
|
```
|
||||||
|
|
||||||
|
## turbo link / unlink
|
||||||
|
|
||||||
|
Connect/disconnect Remote Cache.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
turbo link # connect to Vercel Remote Cache
|
||||||
|
turbo unlink # disconnect
|
||||||
|
```
|
||||||
|
|
||||||
|
## turbo login / logout
|
||||||
|
|
||||||
|
Authenticate with Remote Cache provider.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
turbo login # authenticate
|
||||||
|
turbo logout # log out
|
||||||
|
```
|
||||||
|
|
||||||
|
## turbo generate
|
||||||
|
|
||||||
|
Scaffold new packages.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
turbo generate
|
||||||
|
```
|
||||||
211
.agents/skills/turborepo/references/configuration/RULE.md
Normal file
211
.agents/skills/turborepo/references/configuration/RULE.md
Normal file
@@ -0,0 +1,211 @@
|
|||||||
|
# turbo.json Configuration Overview
|
||||||
|
|
||||||
|
Configuration reference for Turborepo. Full docs: https://turborepo.dev/docs/reference/configuration
|
||||||
|
|
||||||
|
## File Location
|
||||||
|
|
||||||
|
Root `turbo.json` lives at repo root, sibling to root `package.json`:
|
||||||
|
|
||||||
|
```
|
||||||
|
my-monorepo/
|
||||||
|
├── turbo.json # Root configuration
|
||||||
|
├── package.json
|
||||||
|
└── packages/
|
||||||
|
└── web/
|
||||||
|
├── turbo.json # Package Configuration (optional)
|
||||||
|
└── package.json
|
||||||
|
```
|
||||||
|
|
||||||
|
## Always Prefer Package Tasks Over Root Tasks
|
||||||
|
|
||||||
|
**Always use package tasks. Only use Root Tasks if you cannot succeed with package tasks.**
|
||||||
|
|
||||||
|
Package tasks enable parallelization, individual caching, and filtering. Define scripts in each package's `package.json`:
|
||||||
|
|
||||||
|
```json
|
||||||
|
// packages/web/package.json
|
||||||
|
{
|
||||||
|
"scripts": {
|
||||||
|
"build": "next build",
|
||||||
|
"lint": "eslint .",
|
||||||
|
"test": "vitest",
|
||||||
|
"typecheck": "tsc --noEmit"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// packages/api/package.json
|
||||||
|
{
|
||||||
|
"scripts": {
|
||||||
|
"build": "tsc",
|
||||||
|
"lint": "eslint .",
|
||||||
|
"test": "vitest",
|
||||||
|
"typecheck": "tsc --noEmit"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```json
|
||||||
|
// Root package.json - delegates to turbo
|
||||||
|
{
|
||||||
|
"scripts": {
|
||||||
|
"build": "turbo run build",
|
||||||
|
"lint": "turbo run lint",
|
||||||
|
"test": "turbo run test",
|
||||||
|
"typecheck": "turbo run typecheck"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
When you run `turbo run lint`, Turborepo finds all packages with a `lint` script and runs them **in parallel**.
|
||||||
|
|
||||||
|
**Root Tasks are a fallback**, not the default. Only use them for tasks that truly cannot run per-package (e.g., repo-level CI scripts, workspace-wide config generation).
|
||||||
|
|
||||||
|
```json
|
||||||
|
// AVOID: Task logic in root defeats parallelization
|
||||||
|
{
|
||||||
|
"scripts": {
|
||||||
|
"lint": "eslint apps/web && eslint apps/api && eslint packages/ui"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Basic Structure
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"$schema": "https://turborepo.dev/schema.v2.json",
|
||||||
|
"globalEnv": ["CI"],
|
||||||
|
"globalDependencies": ["tsconfig.json"],
|
||||||
|
"tasks": {
|
||||||
|
"build": {
|
||||||
|
"dependsOn": ["^build"],
|
||||||
|
"outputs": ["dist/**"]
|
||||||
|
},
|
||||||
|
"dev": {
|
||||||
|
"cache": false,
|
||||||
|
"persistent": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The `$schema` key enables IDE autocompletion and validation.
|
||||||
|
|
||||||
|
## Configuration Sections
|
||||||
|
|
||||||
|
**Global options** - Settings affecting all tasks:
|
||||||
|
|
||||||
|
- `globalEnv`, `globalDependencies`, `globalPassThroughEnv`
|
||||||
|
- `cacheDir`, `daemon`, `envMode`, `ui`, `remoteCache`
|
||||||
|
|
||||||
|
**Task definitions** - Per-task settings in `tasks` object:
|
||||||
|
|
||||||
|
- `dependsOn`, `outputs`, `inputs`, `env`
|
||||||
|
- `cache`, `persistent`, `interactive`, `outputLogs`
|
||||||
|
|
||||||
|
## Package Configurations
|
||||||
|
|
||||||
|
Use `turbo.json` in individual packages to override root settings:
|
||||||
|
|
||||||
|
```json
|
||||||
|
// packages/web/turbo.json
|
||||||
|
{
|
||||||
|
"extends": ["//"],
|
||||||
|
"tasks": {
|
||||||
|
"build": {
|
||||||
|
"outputs": [".next/**", "!.next/cache/**"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The `"extends": ["//"]` is required - it references the root configuration.
|
||||||
|
|
||||||
|
**When to use Package Configurations:**
|
||||||
|
|
||||||
|
- Framework-specific outputs (Next.js, Vite, etc.)
|
||||||
|
- Package-specific env vars
|
||||||
|
- Different caching rules for specific packages
|
||||||
|
- Keeping framework config close to the framework code
|
||||||
|
|
||||||
|
### Extending from Other Packages
|
||||||
|
|
||||||
|
You can extend from config packages instead of just root:
|
||||||
|
|
||||||
|
```json
|
||||||
|
// packages/web/turbo.json
|
||||||
|
{
|
||||||
|
"extends": ["//", "@repo/turbo-config"]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Adding to Inherited Arrays with `$TURBO_EXTENDS$`
|
||||||
|
|
||||||
|
By default, array fields in Package Configurations **replace** root values. Use `$TURBO_EXTENDS$` to **append** instead:
|
||||||
|
|
||||||
|
```json
|
||||||
|
// Root turbo.json
|
||||||
|
{
|
||||||
|
"tasks": {
|
||||||
|
"build": {
|
||||||
|
"outputs": ["dist/**"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```json
|
||||||
|
// packages/web/turbo.json
|
||||||
|
{
|
||||||
|
"extends": ["//"],
|
||||||
|
"tasks": {
|
||||||
|
"build": {
|
||||||
|
// Inherits "dist/**" from root, adds ".next/**"
|
||||||
|
"outputs": ["$TURBO_EXTENDS$", ".next/**", "!.next/cache/**"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Without `$TURBO_EXTENDS$`, outputs would only be `[".next/**", "!.next/cache/**"]`.
|
||||||
|
|
||||||
|
**Works with:**
|
||||||
|
|
||||||
|
- `dependsOn`
|
||||||
|
- `env`
|
||||||
|
- `inputs`
|
||||||
|
- `outputs`
|
||||||
|
- `passThroughEnv`
|
||||||
|
- `with`
|
||||||
|
|
||||||
|
### Excluding Tasks from Packages
|
||||||
|
|
||||||
|
Use `extends: false` to exclude a task from a package:
|
||||||
|
|
||||||
|
```json
|
||||||
|
// packages/ui/turbo.json
|
||||||
|
{
|
||||||
|
"extends": ["//"],
|
||||||
|
"tasks": {
|
||||||
|
"e2e": {
|
||||||
|
"extends": false // UI package doesn't have e2e tests
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## `turbo.jsonc` for Comments
|
||||||
|
|
||||||
|
Use `turbo.jsonc` extension to add comments with IDE support:
|
||||||
|
|
||||||
|
```jsonc
|
||||||
|
// turbo.jsonc
|
||||||
|
{
|
||||||
|
"tasks": {
|
||||||
|
"build": {
|
||||||
|
// Next.js outputs
|
||||||
|
"outputs": [".next/**", "!.next/cache/**"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
```
|
||||||
@@ -0,0 +1,191 @@
|
|||||||
|
# Global Options Reference
|
||||||
|
|
||||||
|
Options that affect all tasks. Full docs: https://turborepo.dev/docs/reference/configuration
|
||||||
|
|
||||||
|
## globalEnv
|
||||||
|
|
||||||
|
Environment variables affecting all task hashes.
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"globalEnv": ["CI", "NODE_ENV", "VERCEL_*"]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Use for variables that should invalidate all caches when changed.
|
||||||
|
|
||||||
|
## globalDependencies
|
||||||
|
|
||||||
|
Files that affect all task hashes.
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"globalDependencies": ["tsconfig.json", ".env", "pnpm-lock.yaml"]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Lockfile is included by default. Add shared configs here.
|
||||||
|
|
||||||
|
## globalPassThroughEnv
|
||||||
|
|
||||||
|
Variables available to tasks but not included in hash.
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"globalPassThroughEnv": ["AWS_SECRET_KEY", "GITHUB_TOKEN"]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Use for credentials that shouldn't affect cache keys.
|
||||||
|
|
||||||
|
## cacheDir
|
||||||
|
|
||||||
|
Custom cache location. Default: `node_modules/.cache/turbo`.
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"cacheDir": ".turbo/cache"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## daemon
|
||||||
|
|
||||||
|
Background process for faster subsequent runs. Default: `true`.
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"daemon": false
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Disable in CI or when debugging.
|
||||||
|
|
||||||
|
## envMode
|
||||||
|
|
||||||
|
How unspecified env vars are handled. Default: `"strict"`.
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"envMode": "strict" // Only specified vars available
|
||||||
|
// or
|
||||||
|
"envMode": "loose" // All vars pass through
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Strict mode catches missing env declarations.
|
||||||
|
|
||||||
|
## ui
|
||||||
|
|
||||||
|
Terminal UI mode. Default: `"stream"`.
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"ui": "tui" // Interactive terminal UI
|
||||||
|
// or
|
||||||
|
"ui": "stream" // Traditional streaming logs
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
TUI provides better UX for parallel tasks.
|
||||||
|
|
||||||
|
## remoteCache
|
||||||
|
|
||||||
|
Configure remote caching.
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"remoteCache": {
|
||||||
|
"enabled": true,
|
||||||
|
"signature": true,
|
||||||
|
"timeout": 30,
|
||||||
|
"uploadTimeout": 60
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
| Option | Default | Description |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `enabled` | `true` | Enable/disable remote caching |
|
||||||
|
| `signature` | `false` | Sign artifacts with `TURBO_REMOTE_CACHE_SIGNATURE_KEY` |
|
||||||
|
| `preflight` | `false` | Send OPTIONS request before cache requests |
|
||||||
|
| `timeout` | `30` | Timeout in seconds for cache operations |
|
||||||
|
| `uploadTimeout` | `60` | Timeout in seconds for uploads |
|
||||||
|
| `apiUrl` | `"https://vercel.com"` | Remote cache API endpoint |
|
||||||
|
| `loginUrl` | `"https://vercel.com"` | Login endpoint |
|
||||||
|
| `teamId` | - | Team ID (must start with `team_`) |
|
||||||
|
| `teamSlug` | - | Team slug for querystring |
|
||||||
|
|
||||||
|
See https://turborepo.dev/docs/core-concepts/remote-caching for setup.
|
||||||
|
|
||||||
|
## concurrency
|
||||||
|
|
||||||
|
Default: `"10"`
|
||||||
|
|
||||||
|
Limit parallel task execution.
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"concurrency": "4" // Max 4 tasks at once
|
||||||
|
// or
|
||||||
|
"concurrency": "50%" // 50% of available CPUs
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## futureFlags
|
||||||
|
|
||||||
|
Enable experimental features that will become default in future versions.
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"futureFlags": {
|
||||||
|
"errorsOnlyShowHash": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### `errorsOnlyShowHash`
|
||||||
|
|
||||||
|
When using `outputLogs: "errors-only"`, show task hashes on start/completion:
|
||||||
|
|
||||||
|
- Cache miss: `cache miss, executing <hash> (only logging errors)`
|
||||||
|
- Cache hit: `cache hit, replaying logs (no errors) <hash>`
|
||||||
|
|
||||||
|
## noUpdateNotifier
|
||||||
|
|
||||||
|
Disable update notifications when new turbo versions are available.
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"noUpdateNotifier": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## dangerouslyDisablePackageManagerCheck
|
||||||
|
|
||||||
|
Bypass the `packageManager` field requirement. Use for incremental migration.
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"dangerouslyDisablePackageManagerCheck": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Warning**: Unstable lockfiles can cause unpredictable behavior.
|
||||||
|
|
||||||
|
## Git Worktree Cache Sharing
|
||||||
|
|
||||||
|
When working in Git worktrees, Turborepo automatically shares local cache between the main worktree and linked worktrees.
|
||||||
|
|
||||||
|
**How it works:**
|
||||||
|
|
||||||
|
- Detects worktree configuration
|
||||||
|
- Redirects cache to main worktree's `.turbo/cache`
|
||||||
|
- Works alongside Remote Cache
|
||||||
|
|
||||||
|
**Benefits:**
|
||||||
|
|
||||||
|
- Cache hits across branches
|
||||||
|
- Reduced disk usage
|
||||||
|
- Faster branch switching
|
||||||
|
|
||||||
|
**Disabled by**: Setting explicit `cacheDir` in turbo.json.
|
||||||
348
.agents/skills/turborepo/references/configuration/gotchas.md
Normal file
348
.agents/skills/turborepo/references/configuration/gotchas.md
Normal file
@@ -0,0 +1,348 @@
|
|||||||
|
# Configuration Gotchas
|
||||||
|
|
||||||
|
Common mistakes and how to fix them.
|
||||||
|
|
||||||
|
## #1 Root Scripts Not Using `turbo run`
|
||||||
|
|
||||||
|
Root `package.json` scripts for turbo tasks MUST use `turbo run`, not direct commands.
|
||||||
|
|
||||||
|
```json
|
||||||
|
// WRONG - bypasses turbo, no parallelization or caching
|
||||||
|
{
|
||||||
|
"scripts": {
|
||||||
|
"build": "bun build",
|
||||||
|
"dev": "bun dev"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CORRECT - delegates to turbo
|
||||||
|
{
|
||||||
|
"scripts": {
|
||||||
|
"build": "turbo run build",
|
||||||
|
"dev": "turbo run dev"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Why this matters:** Running `bun build` or `npm run build` at root bypasses Turborepo entirely - no parallelization, no caching, no dependency graph awareness.
|
||||||
|
|
||||||
|
## #2 Using `&&` to Chain Turbo Tasks
|
||||||
|
|
||||||
|
Don't use `&&` to chain tasks that turbo should orchestrate.
|
||||||
|
|
||||||
|
```json
|
||||||
|
// WRONG - changeset:publish chains turbo task with non-turbo command
|
||||||
|
{
|
||||||
|
"scripts": {
|
||||||
|
"changeset:publish": "bun build && changeset publish"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CORRECT - use turbo run, let turbo handle dependencies
|
||||||
|
{
|
||||||
|
"scripts": {
|
||||||
|
"changeset:publish": "turbo run build && changeset publish"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
If the second command (`changeset publish`) depends on build outputs, the turbo task should run through turbo to get caching and parallelization benefits.
|
||||||
|
|
||||||
|
## #3 Overly Broad globalDependencies
|
||||||
|
|
||||||
|
`globalDependencies` affects hash for ALL tasks in ALL packages. Be specific.
|
||||||
|
|
||||||
|
```json
|
||||||
|
// WRONG - affects all hashes
|
||||||
|
{
|
||||||
|
"globalDependencies": ["**/.env.*local"]
|
||||||
|
}
|
||||||
|
|
||||||
|
// CORRECT - move to specific tasks that need it
|
||||||
|
{
|
||||||
|
"globalDependencies": [".env"],
|
||||||
|
"tasks": {
|
||||||
|
"build": {
|
||||||
|
"inputs": ["$TURBO_DEFAULT$", ".env*"],
|
||||||
|
"outputs": ["dist/**"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Why this matters:** `**/.env.*local` matches .env files in ALL packages, causing unnecessary cache invalidation. Instead:
|
||||||
|
|
||||||
|
- Use `globalDependencies` only for truly global files (root `.env`)
|
||||||
|
- Use task-level `inputs` for package-specific .env files with `$TURBO_DEFAULT$` to preserve default behavior
|
||||||
|
|
||||||
|
## #4 Repetitive Task Configuration
|
||||||
|
|
||||||
|
Look for repeated configuration across tasks that can be collapsed.
|
||||||
|
|
||||||
|
```json
|
||||||
|
// WRONG - repetitive env and inputs across tasks
|
||||||
|
{
|
||||||
|
"tasks": {
|
||||||
|
"build": {
|
||||||
|
"env": ["API_URL", "DATABASE_URL"],
|
||||||
|
"inputs": ["$TURBO_DEFAULT$", ".env*"]
|
||||||
|
},
|
||||||
|
"test": {
|
||||||
|
"env": ["API_URL", "DATABASE_URL"],
|
||||||
|
"inputs": ["$TURBO_DEFAULT$", ".env*"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// BETTER - use globalEnv and globalDependencies
|
||||||
|
{
|
||||||
|
"globalEnv": ["API_URL", "DATABASE_URL"],
|
||||||
|
"globalDependencies": [".env*"],
|
||||||
|
"tasks": {
|
||||||
|
"build": {},
|
||||||
|
"test": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**When to use global vs task-level:**
|
||||||
|
|
||||||
|
- `globalEnv` / `globalDependencies` - affects ALL tasks, use for truly shared config
|
||||||
|
- Task-level `env` / `inputs` - use when only specific tasks need it
|
||||||
|
|
||||||
|
## #5 Using `../` to Traverse Out of Package in `inputs`
|
||||||
|
|
||||||
|
Don't use relative paths like `../` to reference files outside the package. Use `$TURBO_ROOT$` instead.
|
||||||
|
|
||||||
|
```json
|
||||||
|
// WRONG - traversing out of package
|
||||||
|
{
|
||||||
|
"tasks": {
|
||||||
|
"build": {
|
||||||
|
"inputs": ["$TURBO_DEFAULT$", "../shared-config.json"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CORRECT - use $TURBO_ROOT$ for repo root
|
||||||
|
{
|
||||||
|
"tasks": {
|
||||||
|
"build": {
|
||||||
|
"inputs": ["$TURBO_DEFAULT$", "$TURBO_ROOT$/shared-config.json"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## #6 MOST COMMON MISTAKE: Creating Root Tasks
|
||||||
|
|
||||||
|
**DO NOT create Root Tasks. ALWAYS create package tasks.**
|
||||||
|
|
||||||
|
When you need to create a task (build, lint, test, typecheck, etc.):
|
||||||
|
|
||||||
|
1. Add the script to **each relevant package's** `package.json`
|
||||||
|
2. Register the task in root `turbo.json`
|
||||||
|
3. Root `package.json` only contains `turbo run <task>`
|
||||||
|
|
||||||
|
```json
|
||||||
|
// WRONG - DO NOT DO THIS
|
||||||
|
// Root package.json with task logic
|
||||||
|
{
|
||||||
|
"scripts": {
|
||||||
|
"build": "cd apps/web && next build && cd ../api && tsc",
|
||||||
|
"lint": "eslint apps/ packages/",
|
||||||
|
"test": "vitest"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CORRECT - DO THIS
|
||||||
|
// apps/web/package.json
|
||||||
|
{ "scripts": { "build": "next build", "lint": "eslint .", "test": "vitest" } }
|
||||||
|
|
||||||
|
// apps/api/package.json
|
||||||
|
{ "scripts": { "build": "tsc", "lint": "eslint .", "test": "vitest" } }
|
||||||
|
|
||||||
|
// packages/ui/package.json
|
||||||
|
{ "scripts": { "build": "tsc", "lint": "eslint .", "test": "vitest" } }
|
||||||
|
|
||||||
|
// Root package.json - ONLY delegates
|
||||||
|
{ "scripts": { "build": "turbo run build", "lint": "turbo run lint", "test": "turbo run test" } }
|
||||||
|
|
||||||
|
// turbo.json - register tasks
|
||||||
|
{
|
||||||
|
"tasks": {
|
||||||
|
"build": { "dependsOn": ["^build"], "outputs": ["dist/**"] },
|
||||||
|
"lint": {},
|
||||||
|
"test": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Why this matters:**
|
||||||
|
|
||||||
|
- Package tasks run in **parallel** across all packages
|
||||||
|
- Each package's output is cached **individually**
|
||||||
|
- You can **filter** to specific packages: `turbo run test --filter=web`
|
||||||
|
|
||||||
|
Root Tasks (`//#taskname`) defeat all these benefits. Only use them for tasks that truly cannot exist in any package (extremely rare).
|
||||||
|
|
||||||
|
## #7 Tasks That Need Parallel Execution + Cache Invalidation
|
||||||
|
|
||||||
|
Some tasks can run in parallel (don't need built output from dependencies) but must still invalidate cache when dependency source code changes. Using `dependsOn: ["^taskname"]` forces sequential execution. Using no dependencies breaks cache invalidation.
|
||||||
|
|
||||||
|
**Use Transit Nodes for these tasks:**
|
||||||
|
|
||||||
|
```json
|
||||||
|
// WRONG - forces sequential execution (SLOW)
|
||||||
|
"my-task": {
|
||||||
|
"dependsOn": ["^my-task"]
|
||||||
|
}
|
||||||
|
|
||||||
|
// ALSO WRONG - no dependency awareness (INCORRECT CACHING)
|
||||||
|
"my-task": {}
|
||||||
|
|
||||||
|
// CORRECT - use Transit Nodes for parallel + correct caching
|
||||||
|
{
|
||||||
|
"tasks": {
|
||||||
|
"transit": { "dependsOn": ["^transit"] },
|
||||||
|
"my-task": { "dependsOn": ["transit"] }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Why Transit Nodes work:**
|
||||||
|
|
||||||
|
- `transit` creates dependency relationships without matching any actual script
|
||||||
|
- Tasks that depend on `transit` gain dependency awareness
|
||||||
|
- Since `transit` completes instantly (no script), tasks run in parallel
|
||||||
|
- Cache correctly invalidates when dependency source code changes
|
||||||
|
|
||||||
|
**How to identify tasks that need this pattern:** Look for tasks that read source files from dependencies but don't need their build outputs.
|
||||||
|
|
||||||
|
## Missing outputs for File-Producing Tasks
|
||||||
|
|
||||||
|
**Before flagging missing `outputs`, check what the task actually produces:**
|
||||||
|
|
||||||
|
1. Read the package's script (e.g., `"build": "tsc"`, `"test": "vitest"`)
|
||||||
|
2. Determine if it writes files to disk or only outputs to stdout
|
||||||
|
3. Only flag if the task produces files that should be cached
|
||||||
|
|
||||||
|
```json
|
||||||
|
// WRONG - build produces files but they're not cached
|
||||||
|
"build": {
|
||||||
|
"dependsOn": ["^build"]
|
||||||
|
}
|
||||||
|
|
||||||
|
// CORRECT - outputs are cached
|
||||||
|
"build": {
|
||||||
|
"dependsOn": ["^build"],
|
||||||
|
"outputs": ["dist/**"]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
No `outputs` key is fine for stdout-only tasks. For file-producing tasks, missing `outputs` means Turbo has nothing to cache.
|
||||||
|
|
||||||
|
## Forgetting ^ in dependsOn
|
||||||
|
|
||||||
|
```json
|
||||||
|
// WRONG - looks for "build" in SAME package (infinite loop or missing)
|
||||||
|
"build": {
|
||||||
|
"dependsOn": ["build"]
|
||||||
|
}
|
||||||
|
|
||||||
|
// CORRECT - runs dependencies' build first
|
||||||
|
"build": {
|
||||||
|
"dependsOn": ["^build"]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The `^` means "in dependency packages", not "in this package".
|
||||||
|
|
||||||
|
## Missing persistent on Dev Tasks
|
||||||
|
|
||||||
|
```json
|
||||||
|
// WRONG - dependent tasks hang waiting for dev to "finish"
|
||||||
|
"dev": {
|
||||||
|
"cache": false
|
||||||
|
}
|
||||||
|
|
||||||
|
// CORRECT
|
||||||
|
"dev": {
|
||||||
|
"cache": false,
|
||||||
|
"persistent": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Package Config Missing extends
|
||||||
|
|
||||||
|
```json
|
||||||
|
// WRONG - packages/web/turbo.json
|
||||||
|
{
|
||||||
|
"tasks": {
|
||||||
|
"build": { "outputs": [".next/**"] }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CORRECT
|
||||||
|
{
|
||||||
|
"extends": ["//"],
|
||||||
|
"tasks": {
|
||||||
|
"build": { "outputs": [".next/**"] }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Without `"extends": ["//"]`, Package Configurations are invalid.
|
||||||
|
|
||||||
|
## Root Tasks Need Special Syntax
|
||||||
|
|
||||||
|
To run a task defined only in root `package.json`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# WRONG
|
||||||
|
turbo run format
|
||||||
|
|
||||||
|
# CORRECT
|
||||||
|
turbo run //#format
|
||||||
|
```
|
||||||
|
|
||||||
|
And in dependsOn:
|
||||||
|
|
||||||
|
```json
|
||||||
|
"build": {
|
||||||
|
"dependsOn": ["//#codegen"] // Root package's codegen
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Overwriting Default Inputs
|
||||||
|
|
||||||
|
```json
|
||||||
|
// WRONG - only watches test files, ignores source changes
|
||||||
|
"test": {
|
||||||
|
"inputs": ["tests/**"]
|
||||||
|
}
|
||||||
|
|
||||||
|
// CORRECT - extends defaults, adds test files
|
||||||
|
"test": {
|
||||||
|
"inputs": ["$TURBO_DEFAULT$", "tests/**"]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Without `$TURBO_DEFAULT$`, you replace all default file watching.
|
||||||
|
|
||||||
|
## Caching Tasks with Side Effects
|
||||||
|
|
||||||
|
```json
|
||||||
|
// WRONG - deploy might be skipped on cache hit
|
||||||
|
"deploy": {
|
||||||
|
"dependsOn": ["build"]
|
||||||
|
}
|
||||||
|
|
||||||
|
// CORRECT
|
||||||
|
"deploy": {
|
||||||
|
"dependsOn": ["build"],
|
||||||
|
"cache": false
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Always disable cache for deploy, publish, or mutation tasks.
|
||||||
284
.agents/skills/turborepo/references/configuration/tasks.md
Normal file
284
.agents/skills/turborepo/references/configuration/tasks.md
Normal file
@@ -0,0 +1,284 @@
|
|||||||
|
# Task Configuration Reference
|
||||||
|
|
||||||
|
Full docs: https://turborepo.dev/docs/reference/configuration#tasks
|
||||||
|
|
||||||
|
## dependsOn
|
||||||
|
|
||||||
|
Controls task execution order.
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"tasks": {
|
||||||
|
"build": {
|
||||||
|
"dependsOn": [
|
||||||
|
"^build", // Dependencies' build tasks first
|
||||||
|
"codegen", // Same package's codegen task first
|
||||||
|
"shared#build" // Specific package's build task
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
| Syntax | Meaning |
|
||||||
|
| ---------- | ------------------------------------ |
|
||||||
|
| `^task` | Run `task` in all dependencies first |
|
||||||
|
| `task` | Run `task` in same package first |
|
||||||
|
| `pkg#task` | Run specific package's task first |
|
||||||
|
|
||||||
|
The `^` prefix is crucial - without it, you're referencing the same package.
|
||||||
|
|
||||||
|
### Transit Nodes for Parallel Tasks
|
||||||
|
|
||||||
|
For tasks like `lint` and `check-types` that can run in parallel but need dependency-aware caching:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"tasks": {
|
||||||
|
"transit": { "dependsOn": ["^transit"] },
|
||||||
|
"lint": { "dependsOn": ["transit"] },
|
||||||
|
"check-types": { "dependsOn": ["transit"] }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**DO NOT use `dependsOn: ["^lint"]`** - this forces sequential execution. **DO NOT use `dependsOn: []`** - this breaks cache invalidation.
|
||||||
|
|
||||||
|
The `transit` task creates dependency relationships without running anything (no matching script), so tasks run in parallel with correct caching.
|
||||||
|
|
||||||
|
## outputs
|
||||||
|
|
||||||
|
Glob patterns for files to cache. **If omitted, nothing is cached.**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"tasks": {
|
||||||
|
"build": {
|
||||||
|
"outputs": ["dist/**", "build/**"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Framework examples:**
|
||||||
|
|
||||||
|
```json
|
||||||
|
// Next.js
|
||||||
|
"outputs": [".next/**", "!.next/cache/**"]
|
||||||
|
|
||||||
|
// Vite
|
||||||
|
"outputs": ["dist/**"]
|
||||||
|
|
||||||
|
// TypeScript (tsc)
|
||||||
|
"outputs": ["dist/**", "*.tsbuildinfo"]
|
||||||
|
|
||||||
|
// No file outputs (lint, typecheck)
|
||||||
|
"outputs": []
|
||||||
|
```
|
||||||
|
|
||||||
|
Use `!` prefix to exclude patterns from caching.
|
||||||
|
|
||||||
|
## inputs
|
||||||
|
|
||||||
|
Files considered when calculating task hash. Defaults to all tracked files in package.
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"tasks": {
|
||||||
|
"test": {
|
||||||
|
"inputs": ["src/**", "tests/**", "vitest.config.ts"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Special values:**
|
||||||
|
|
||||||
|
| Value | Meaning |
|
||||||
|
| --------------------- | --------------------------------------- |
|
||||||
|
| `$TURBO_DEFAULT$` | Include default inputs, then add/remove |
|
||||||
|
| `$TURBO_ROOT$/<path>` | Reference files from repo root |
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"tasks": {
|
||||||
|
"build": {
|
||||||
|
"inputs": [
|
||||||
|
"$TURBO_DEFAULT$",
|
||||||
|
"!README.md",
|
||||||
|
"$TURBO_ROOT$/tsconfig.base.json"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## env
|
||||||
|
|
||||||
|
Environment variables to include in task hash.
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"tasks": {
|
||||||
|
"build": {
|
||||||
|
"env": [
|
||||||
|
"API_URL",
|
||||||
|
"NEXT_PUBLIC_*", // Wildcard matching
|
||||||
|
"!DEBUG" // Exclude from hash
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Variables listed here affect cache hits - changing the value invalidates cache.
|
||||||
|
|
||||||
|
## cache
|
||||||
|
|
||||||
|
Enable/disable caching for a task. Default: `true`.
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"tasks": {
|
||||||
|
"dev": { "cache": false },
|
||||||
|
"deploy": { "cache": false }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Disable for: dev servers, deploy commands, tasks with side effects.
|
||||||
|
|
||||||
|
## persistent
|
||||||
|
|
||||||
|
Mark long-running tasks that don't exit. Default: `false`.
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"tasks": {
|
||||||
|
"dev": {
|
||||||
|
"cache": false,
|
||||||
|
"persistent": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Required for dev servers - without it, dependent tasks wait forever.
|
||||||
|
|
||||||
|
## interactive
|
||||||
|
|
||||||
|
Allow task to receive stdin input. Default: `false`.
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"tasks": {
|
||||||
|
"login": {
|
||||||
|
"cache": false,
|
||||||
|
"interactive": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## outputLogs
|
||||||
|
|
||||||
|
Control when logs are shown. Options: `full`, `hash-only`, `new-only`, `errors-only`, `none`.
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"tasks": {
|
||||||
|
"build": {
|
||||||
|
"outputLogs": "new-only" // Only show logs on cache miss
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## with
|
||||||
|
|
||||||
|
Run tasks alongside this task. For long-running tasks that need runtime dependencies.
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"tasks": {
|
||||||
|
"dev": {
|
||||||
|
"with": ["api#dev"],
|
||||||
|
"persistent": true,
|
||||||
|
"cache": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Unlike `dependsOn`, `with` runs tasks concurrently (not sequentially). Use for dev servers that need other services running.
|
||||||
|
|
||||||
|
## interruptible
|
||||||
|
|
||||||
|
Allow `turbo watch` to restart the task on changes. Default: `false`.
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"tasks": {
|
||||||
|
"dev": {
|
||||||
|
"persistent": true,
|
||||||
|
"interruptible": true,
|
||||||
|
"cache": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Use for dev servers that don't automatically detect dependency changes.
|
||||||
|
|
||||||
|
## description
|
||||||
|
|
||||||
|
Human-readable description of the task.
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"tasks": {
|
||||||
|
"build": {
|
||||||
|
"description": "Compiles the application for production deployment"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
For documentation only - doesn't affect execution or caching.
|
||||||
|
|
||||||
|
## passThroughEnv
|
||||||
|
|
||||||
|
Environment variables available at runtime but NOT included in cache hash.
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"tasks": {
|
||||||
|
"build": {
|
||||||
|
"passThroughEnv": ["AWS_SECRET_KEY", "GITHUB_TOKEN"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Warning**: Changes to these vars won't cause cache misses. Use `env` if changes should invalidate cache.
|
||||||
|
|
||||||
|
## extends (Package Configuration only)
|
||||||
|
|
||||||
|
Control task inheritance in Package Configurations.
|
||||||
|
|
||||||
|
```json
|
||||||
|
// packages/ui/turbo.json
|
||||||
|
{
|
||||||
|
"extends": ["//"],
|
||||||
|
"tasks": {
|
||||||
|
"lint": {
|
||||||
|
"extends": false // Exclude from this package
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
| Value | Behavior |
|
||||||
|
| --- | --- |
|
||||||
|
| `true` (default) | Inherit from root turbo.json |
|
||||||
|
| `false` | Exclude task from package, or define fresh without inheritance |
|
||||||
96
.agents/skills/turborepo/references/environment/RULE.md
Normal file
96
.agents/skills/turborepo/references/environment/RULE.md
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
# Environment Variables in Turborepo
|
||||||
|
|
||||||
|
Turborepo provides fine-grained control over which environment variables affect task hashing and runtime availability.
|
||||||
|
|
||||||
|
## Configuration Keys
|
||||||
|
|
||||||
|
### `env` - Task-Specific Variables
|
||||||
|
|
||||||
|
Variables that affect a specific task's hash. When these change, only that task rebuilds.
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"tasks": {
|
||||||
|
"build": {
|
||||||
|
"env": ["DATABASE_URL", "API_KEY"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### `globalEnv` - Variables Affecting All Tasks
|
||||||
|
|
||||||
|
Variables that affect EVERY task's hash. When these change, all tasks rebuild.
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"globalEnv": ["CI", "NODE_ENV"]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### `passThroughEnv` - Runtime-Only Variables (Not Hashed)
|
||||||
|
|
||||||
|
Variables available at runtime but NOT included in hash. **Use with caution** - changes won't trigger rebuilds.
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"tasks": {
|
||||||
|
"deploy": {
|
||||||
|
"passThroughEnv": ["AWS_ACCESS_KEY_ID", "AWS_SECRET_ACCESS_KEY"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### `globalPassThroughEnv` - Global Runtime Variables
|
||||||
|
|
||||||
|
Same as `passThroughEnv` but for all tasks.
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"globalPassThroughEnv": ["GITHUB_TOKEN"]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Wildcards and Negation
|
||||||
|
|
||||||
|
### Wildcards
|
||||||
|
|
||||||
|
Match multiple variables with `*`:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"env": ["MY_API_*", "FEATURE_FLAG_*"]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
This matches `MY_API_URL`, `MY_API_KEY`, `FEATURE_FLAG_DARK_MODE`, etc.
|
||||||
|
|
||||||
|
### Negation
|
||||||
|
|
||||||
|
Exclude variables (useful with framework inference):
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"env": ["!NEXT_PUBLIC_ANALYTICS_ID"]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Complete Example
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"$schema": "https://turborepo.dev/schema.v2.json",
|
||||||
|
"globalEnv": ["CI", "NODE_ENV"],
|
||||||
|
"globalPassThroughEnv": ["GITHUB_TOKEN", "NPM_TOKEN"],
|
||||||
|
"tasks": {
|
||||||
|
"build": {
|
||||||
|
"env": ["DATABASE_URL", "API_*"],
|
||||||
|
"passThroughEnv": ["SENTRY_AUTH_TOKEN"]
|
||||||
|
},
|
||||||
|
"test": {
|
||||||
|
"env": ["TEST_DATABASE_URL"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
141
.agents/skills/turborepo/references/environment/gotchas.md
Normal file
141
.agents/skills/turborepo/references/environment/gotchas.md
Normal file
@@ -0,0 +1,141 @@
|
|||||||
|
# Environment Variable Gotchas
|
||||||
|
|
||||||
|
Common mistakes and how to fix them.
|
||||||
|
|
||||||
|
## .env Files Must Be in `inputs`
|
||||||
|
|
||||||
|
Turbo does NOT read `.env` files. Your framework (Next.js, Vite, etc.) or `dotenv` loads them. But Turbo needs to know when they change.
|
||||||
|
|
||||||
|
**Wrong:**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"tasks": {
|
||||||
|
"build": {
|
||||||
|
"env": ["DATABASE_URL"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Right:**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"tasks": {
|
||||||
|
"build": {
|
||||||
|
"env": ["DATABASE_URL"],
|
||||||
|
"inputs": ["$TURBO_DEFAULT$", ".env", ".env.local", ".env.production"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Strict Mode Filters CI Variables
|
||||||
|
|
||||||
|
In strict mode, CI provider variables (GITHUB_TOKEN, GITLAB_CI, etc.) are filtered unless explicitly listed.
|
||||||
|
|
||||||
|
**Symptom:** Task fails with "authentication required" or "permission denied" in CI.
|
||||||
|
|
||||||
|
**Solution:**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"globalPassThroughEnv": ["GITHUB_TOKEN", "GITLAB_CI", "CI"]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## passThroughEnv Doesn't Affect Hash
|
||||||
|
|
||||||
|
Variables in `passThroughEnv` are available at runtime but changes WON'T trigger rebuilds.
|
||||||
|
|
||||||
|
**Dangerous example:**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"tasks": {
|
||||||
|
"build": {
|
||||||
|
"passThroughEnv": ["API_URL"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
If `API_URL` changes from staging to production, Turbo may serve a cached build pointing to the wrong API.
|
||||||
|
|
||||||
|
**Use passThroughEnv only for:**
|
||||||
|
|
||||||
|
- Auth tokens that don't affect output (SENTRY_AUTH_TOKEN)
|
||||||
|
- CI metadata (GITHUB_RUN_ID)
|
||||||
|
- Variables consumed after build (deploy credentials)
|
||||||
|
|
||||||
|
## Runtime-Created Variables Are Invisible
|
||||||
|
|
||||||
|
Turbo captures env vars at startup. Variables created during execution aren't seen.
|
||||||
|
|
||||||
|
**Won't work:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# In package.json scripts
|
||||||
|
"build": "export API_URL=$COMPUTED_VALUE && next build"
|
||||||
|
```
|
||||||
|
|
||||||
|
**Solution:** Set vars before invoking turbo:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
API_URL=$COMPUTED_VALUE turbo run build
|
||||||
|
```
|
||||||
|
|
||||||
|
## Different .env Files for Different Environments
|
||||||
|
|
||||||
|
If you use `.env.development` and `.env.production`, both should be in inputs.
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"tasks": {
|
||||||
|
"build": {
|
||||||
|
"inputs": [
|
||||||
|
"$TURBO_DEFAULT$",
|
||||||
|
".env",
|
||||||
|
".env.local",
|
||||||
|
".env.development",
|
||||||
|
".env.development.local",
|
||||||
|
".env.production",
|
||||||
|
".env.production.local"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Complete Next.js Example
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"$schema": "https://turborepo.dev/schema.v2.json",
|
||||||
|
"globalEnv": ["CI", "NODE_ENV", "VERCEL"],
|
||||||
|
"globalPassThroughEnv": ["GITHUB_TOKEN", "VERCEL_URL"],
|
||||||
|
"tasks": {
|
||||||
|
"build": {
|
||||||
|
"dependsOn": ["^build"],
|
||||||
|
"env": ["DATABASE_URL", "NEXT_PUBLIC_*", "!NEXT_PUBLIC_ANALYTICS_ID"],
|
||||||
|
"passThroughEnv": ["SENTRY_AUTH_TOKEN"],
|
||||||
|
"inputs": [
|
||||||
|
"$TURBO_DEFAULT$",
|
||||||
|
".env",
|
||||||
|
".env.local",
|
||||||
|
".env.production",
|
||||||
|
".env.production.local"
|
||||||
|
],
|
||||||
|
"outputs": [".next/**", "!.next/cache/**"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
This config:
|
||||||
|
|
||||||
|
- Hashes DATABASE*URL and NEXT_PUBLIC*\* vars (except analytics)
|
||||||
|
- Passes through SENTRY_AUTH_TOKEN without hashing
|
||||||
|
- Includes all .env file variants in the hash
|
||||||
|
- Makes CI tokens available globally
|
||||||
101
.agents/skills/turborepo/references/environment/modes.md
Normal file
101
.agents/skills/turborepo/references/environment/modes.md
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
# Environment Modes
|
||||||
|
|
||||||
|
Turborepo supports different modes for handling environment variables during task execution.
|
||||||
|
|
||||||
|
## Strict Mode (Default)
|
||||||
|
|
||||||
|
Only explicitly configured variables are available to tasks.
|
||||||
|
|
||||||
|
**Behavior:**
|
||||||
|
|
||||||
|
- Tasks only see vars listed in `env`, `globalEnv`, `passThroughEnv`, or `globalPassThroughEnv`
|
||||||
|
- Unlisted vars are filtered out
|
||||||
|
- Tasks fail if they require unlisted variables
|
||||||
|
|
||||||
|
**Benefits:**
|
||||||
|
|
||||||
|
- Guarantees cache correctness
|
||||||
|
- Prevents accidental dependencies on system vars
|
||||||
|
- Reproducible builds across machines
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Explicit (though it's the default)
|
||||||
|
turbo run build --env-mode=strict
|
||||||
|
```
|
||||||
|
|
||||||
|
## Loose Mode
|
||||||
|
|
||||||
|
All system environment variables are available to tasks.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
turbo run build --env-mode=loose
|
||||||
|
```
|
||||||
|
|
||||||
|
**Behavior:**
|
||||||
|
|
||||||
|
- Every system env var is passed through
|
||||||
|
- Only vars in `env`/`globalEnv` affect the hash
|
||||||
|
- Other vars are available but NOT hashed
|
||||||
|
|
||||||
|
**Risks:**
|
||||||
|
|
||||||
|
- Cache may restore incorrect results if unhashed vars changed
|
||||||
|
- "Works on my machine" bugs
|
||||||
|
- CI vs local environment mismatches
|
||||||
|
|
||||||
|
**Use case:** Migrating legacy projects or debugging strict mode issues.
|
||||||
|
|
||||||
|
## Framework Inference (Automatic)
|
||||||
|
|
||||||
|
Turborepo automatically detects frameworks and includes their conventional env vars.
|
||||||
|
|
||||||
|
### Inferred Variables by Framework
|
||||||
|
|
||||||
|
| Framework | Pattern |
|
||||||
|
| ---------------- | ------------------- |
|
||||||
|
| Next.js | `NEXT_PUBLIC_*` |
|
||||||
|
| Vite | `VITE_*` |
|
||||||
|
| Create React App | `REACT_APP_*` |
|
||||||
|
| Gatsby | `GATSBY_*` |
|
||||||
|
| Nuxt | `NUXT_*`, `NITRO_*` |
|
||||||
|
| Expo | `EXPO_PUBLIC_*` |
|
||||||
|
| Astro | `PUBLIC_*` |
|
||||||
|
| SvelteKit | `PUBLIC_*` |
|
||||||
|
| Remix | `REMIX_*` |
|
||||||
|
| Redwood | `REDWOOD_ENV_*` |
|
||||||
|
| Sanity | `SANITY_STUDIO_*` |
|
||||||
|
| Solid | `VITE_*` |
|
||||||
|
|
||||||
|
### Disabling Framework Inference
|
||||||
|
|
||||||
|
Globally via CLI:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
turbo run build --framework-inference=false
|
||||||
|
```
|
||||||
|
|
||||||
|
Or exclude specific patterns in config:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"tasks": {
|
||||||
|
"build": {
|
||||||
|
"env": ["!NEXT_PUBLIC_*"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Why Disable?
|
||||||
|
|
||||||
|
- You want explicit control over all env vars
|
||||||
|
- Framework vars shouldn't bust the cache (e.g., analytics IDs)
|
||||||
|
- Debugging unexpected cache misses
|
||||||
|
|
||||||
|
## Checking Environment Mode
|
||||||
|
|
||||||
|
Use `--dry` to see which vars affect each task:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
turbo run build --dry=json | jq '.tasks[].environmentVariables'
|
||||||
|
```
|
||||||
148
.agents/skills/turborepo/references/filtering/RULE.md
Normal file
148
.agents/skills/turborepo/references/filtering/RULE.md
Normal file
@@ -0,0 +1,148 @@
|
|||||||
|
# Turborepo Filter Syntax Reference
|
||||||
|
|
||||||
|
## Running Only Changed Packages: `--affected`
|
||||||
|
|
||||||
|
**The primary way to run only changed packages is `--affected`:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Run build/test/lint only in changed packages and their dependents
|
||||||
|
turbo run build test lint --affected
|
||||||
|
```
|
||||||
|
|
||||||
|
This compares your current branch to the default branch (usually `main` or `master`) and runs tasks in:
|
||||||
|
|
||||||
|
1. Packages with file changes
|
||||||
|
2. Packages that depend on changed packages (dependents)
|
||||||
|
|
||||||
|
### Why Include Dependents?
|
||||||
|
|
||||||
|
If you change `@repo/ui`, packages that import `@repo/ui` (like `apps/web`) need to re-run their tasks to verify they still work with the changes.
|
||||||
|
|
||||||
|
### Customizing --affected
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Use a different base branch
|
||||||
|
turbo run build --affected --affected-base=origin/develop
|
||||||
|
|
||||||
|
# Use a different head (current state)
|
||||||
|
turbo run build --affected --affected-head=HEAD~5
|
||||||
|
```
|
||||||
|
|
||||||
|
### Common CI Pattern
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# .github/workflows/ci.yml
|
||||||
|
- run: turbo run build test lint --affected
|
||||||
|
```
|
||||||
|
|
||||||
|
This is the most efficient CI setup - only run tasks for what actually changed.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Manual Git Comparison with --filter
|
||||||
|
|
||||||
|
For more control, use `--filter` with git comparison syntax:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Changed packages + dependents (same as --affected)
|
||||||
|
turbo run build --filter=...[origin/main]
|
||||||
|
|
||||||
|
# Only changed packages (no dependents)
|
||||||
|
turbo run build --filter=[origin/main]
|
||||||
|
|
||||||
|
# Changed packages + dependencies (packages they import)
|
||||||
|
turbo run build --filter=[origin/main]...
|
||||||
|
|
||||||
|
# Changed since last commit
|
||||||
|
turbo run build --filter=...[HEAD^1]
|
||||||
|
|
||||||
|
# Changed between two commits
|
||||||
|
turbo run build --filter=[a1b2c3d...e4f5g6h]
|
||||||
|
```
|
||||||
|
|
||||||
|
### Comparison Syntax
|
||||||
|
|
||||||
|
| Syntax | Meaning |
|
||||||
|
| ------------- | ------------------------------------- |
|
||||||
|
| `[ref]` | Packages changed since `ref` |
|
||||||
|
| `...[ref]` | Changed packages + their dependents |
|
||||||
|
| `[ref]...` | Changed packages + their dependencies |
|
||||||
|
| `...[ref]...` | Dependencies, changed, AND dependents |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Other Filter Types
|
||||||
|
|
||||||
|
Filters select which packages to include in a `turbo run` invocation.
|
||||||
|
|
||||||
|
### Basic Syntax
|
||||||
|
|
||||||
|
```bash
|
||||||
|
turbo run build --filter=<package-name>
|
||||||
|
turbo run build -F <package-name>
|
||||||
|
```
|
||||||
|
|
||||||
|
Multiple filters combine as a union (packages matching ANY filter run).
|
||||||
|
|
||||||
|
### By Package Name
|
||||||
|
|
||||||
|
```bash
|
||||||
|
--filter=web # exact match
|
||||||
|
--filter=@acme/* # scope glob
|
||||||
|
--filter=*-app # name glob
|
||||||
|
```
|
||||||
|
|
||||||
|
### By Directory
|
||||||
|
|
||||||
|
```bash
|
||||||
|
--filter=./apps/* # all packages in apps/
|
||||||
|
--filter=./packages/ui # specific directory
|
||||||
|
```
|
||||||
|
|
||||||
|
### By Dependencies/Dependents
|
||||||
|
|
||||||
|
| Syntax | Meaning |
|
||||||
|
| ----------- | -------------------------------------- |
|
||||||
|
| `pkg...` | Package AND all its dependencies |
|
||||||
|
| `...pkg` | Package AND all its dependents |
|
||||||
|
| `...pkg...` | Dependencies, package, AND dependents |
|
||||||
|
| `^pkg...` | Only dependencies (exclude pkg itself) |
|
||||||
|
| `...^pkg` | Only dependents (exclude pkg itself) |
|
||||||
|
|
||||||
|
### Negation
|
||||||
|
|
||||||
|
Exclude packages with `!`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
--filter=!web # exclude web
|
||||||
|
--filter=./apps/* --filter=!admin # apps except admin
|
||||||
|
```
|
||||||
|
|
||||||
|
### Task Identifiers
|
||||||
|
|
||||||
|
Run a specific task in a specific package:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
turbo run web#build # only web's build task
|
||||||
|
turbo run web#build api#test # web build + api test
|
||||||
|
```
|
||||||
|
|
||||||
|
### Combining Filters
|
||||||
|
|
||||||
|
Multiple `--filter` flags create a union:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
turbo run build --filter=web --filter=api # runs in both
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Quick Reference: Changed Packages
|
||||||
|
|
||||||
|
| Goal | Command |
|
||||||
|
| --- | --- |
|
||||||
|
| Changed + dependents (recommended) | `turbo run build --affected` |
|
||||||
|
| Custom base branch | `turbo run build --affected --affected-base=origin/develop` |
|
||||||
|
| Only changed (no dependents) | `turbo run build --filter=[origin/main]` |
|
||||||
|
| Changed + dependencies | `turbo run build --filter=[origin/main]...` |
|
||||||
|
| Since last commit | `turbo run build --filter=...[HEAD^1]` |
|
||||||
152
.agents/skills/turborepo/references/filtering/patterns.md
Normal file
152
.agents/skills/turborepo/references/filtering/patterns.md
Normal file
@@ -0,0 +1,152 @@
|
|||||||
|
# Common Filter Patterns
|
||||||
|
|
||||||
|
Practical examples for typical monorepo scenarios.
|
||||||
|
|
||||||
|
## Single Package
|
||||||
|
|
||||||
|
Run task in one package:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
turbo run build --filter=web
|
||||||
|
turbo run test --filter=@acme/api
|
||||||
|
```
|
||||||
|
|
||||||
|
## Package with Dependencies
|
||||||
|
|
||||||
|
Build a package and everything it depends on:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
turbo run build --filter=web...
|
||||||
|
```
|
||||||
|
|
||||||
|
Useful for: ensuring all dependencies are built before the target.
|
||||||
|
|
||||||
|
## Package Dependents
|
||||||
|
|
||||||
|
Run in all packages that depend on a library:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
turbo run test --filter=...ui
|
||||||
|
```
|
||||||
|
|
||||||
|
Useful for: testing consumers after changing a shared package.
|
||||||
|
|
||||||
|
## Dependents Only (Exclude Target)
|
||||||
|
|
||||||
|
Test packages that depend on ui, but not ui itself:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
turbo run test --filter=...^ui
|
||||||
|
```
|
||||||
|
|
||||||
|
## Changed Packages
|
||||||
|
|
||||||
|
Run only in packages with file changes since last commit:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
turbo run lint --filter=[HEAD^1]
|
||||||
|
```
|
||||||
|
|
||||||
|
Since a specific branch point:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
turbo run lint --filter=[main...HEAD]
|
||||||
|
```
|
||||||
|
|
||||||
|
## Changed + Dependents (PR Builds)
|
||||||
|
|
||||||
|
Run in changed packages AND packages that depend on them:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
turbo run build test --filter=...[HEAD^1]
|
||||||
|
```
|
||||||
|
|
||||||
|
Or use the shortcut:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
turbo run build test --affected
|
||||||
|
```
|
||||||
|
|
||||||
|
## Directory-Based
|
||||||
|
|
||||||
|
Run in all apps:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
turbo run build --filter=./apps/*
|
||||||
|
```
|
||||||
|
|
||||||
|
Run in specific directories:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
turbo run build --filter=./apps/web --filter=./apps/api
|
||||||
|
```
|
||||||
|
|
||||||
|
## Scope-Based
|
||||||
|
|
||||||
|
Run in all packages under a scope:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
turbo run build --filter=@acme/*
|
||||||
|
```
|
||||||
|
|
||||||
|
## Exclusions
|
||||||
|
|
||||||
|
Run in all apps except admin:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
turbo run build --filter=./apps/* --filter=!admin
|
||||||
|
```
|
||||||
|
|
||||||
|
Run everywhere except specific packages:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
turbo run lint --filter=!legacy-app --filter=!deprecated-pkg
|
||||||
|
```
|
||||||
|
|
||||||
|
## Complex Combinations
|
||||||
|
|
||||||
|
Apps that changed, plus their dependents:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
turbo run build --filter=...[HEAD^1] --filter=./apps/*
|
||||||
|
```
|
||||||
|
|
||||||
|
All packages except docs, but only if changed:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
turbo run build --filter=[main...HEAD] --filter=!docs
|
||||||
|
```
|
||||||
|
|
||||||
|
## Debugging Filters
|
||||||
|
|
||||||
|
Use `--dry` to see what would run without executing:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
turbo run build --filter=web... --dry
|
||||||
|
```
|
||||||
|
|
||||||
|
Use `--dry=json` for machine-readable output:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
turbo run build --filter=...[HEAD^1] --dry=json
|
||||||
|
```
|
||||||
|
|
||||||
|
## CI/CD Patterns
|
||||||
|
|
||||||
|
PR validation (most common):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
turbo run build test lint --affected
|
||||||
|
```
|
||||||
|
|
||||||
|
Deploy only changed apps:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
turbo run deploy --filter=./apps/* --filter=[main...HEAD]
|
||||||
|
```
|
||||||
|
|
||||||
|
Full rebuild of specific app and deps:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
turbo run build --filter=production-app...
|
||||||
|
```
|
||||||
99
.agents/skills/turborepo/references/watch/RULE.md
Normal file
99
.agents/skills/turborepo/references/watch/RULE.md
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
# turbo watch
|
||||||
|
|
||||||
|
Full docs: https://turborepo.dev/docs/reference/watch
|
||||||
|
|
||||||
|
Re-run tasks automatically when code changes. Dependency-aware.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
turbo watch [tasks]
|
||||||
|
```
|
||||||
|
|
||||||
|
## Basic Usage
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Watch and re-run build task when code changes
|
||||||
|
turbo watch build
|
||||||
|
|
||||||
|
# Watch multiple tasks
|
||||||
|
turbo watch build test lint
|
||||||
|
```
|
||||||
|
|
||||||
|
Tasks re-run in order configured in `turbo.json` when source files change.
|
||||||
|
|
||||||
|
## With Persistent Tasks
|
||||||
|
|
||||||
|
Persistent tasks (`"persistent": true`) won't exit, so they can't be depended on. They work the same in `turbo watch` as `turbo run`.
|
||||||
|
|
||||||
|
### Dependency-Aware Persistent Tasks
|
||||||
|
|
||||||
|
If your tool has built-in watching (like `next dev`), use its watcher:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"tasks": {
|
||||||
|
"dev": {
|
||||||
|
"persistent": true,
|
||||||
|
"cache": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Non-Dependency-Aware Tools
|
||||||
|
|
||||||
|
For tools that don't detect dependency changes, use `interruptible`:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"tasks": {
|
||||||
|
"dev": {
|
||||||
|
"persistent": true,
|
||||||
|
"interruptible": true,
|
||||||
|
"cache": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
`turbo watch` will restart interruptible tasks when dependencies change.
|
||||||
|
|
||||||
|
## Limitations
|
||||||
|
|
||||||
|
### Caching
|
||||||
|
|
||||||
|
Caching is experimental with watch mode:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
turbo watch your-tasks --experimental-write-cache
|
||||||
|
```
|
||||||
|
|
||||||
|
### Task Outputs in Source Control
|
||||||
|
|
||||||
|
If tasks write files tracked by git, watch mode may loop infinitely. Watch mode uses file hashes to prevent this but it's not foolproof.
|
||||||
|
|
||||||
|
**Recommendation**: Remove task outputs from git.
|
||||||
|
|
||||||
|
## vs turbo run
|
||||||
|
|
||||||
|
| Feature | `turbo run` | `turbo watch` |
|
||||||
|
| ----------------- | ----------- | ------------- |
|
||||||
|
| Runs once | Yes | No |
|
||||||
|
| Re-runs on change | No | Yes |
|
||||||
|
| Caching | Full | Experimental |
|
||||||
|
| Use case | CI, one-off | Development |
|
||||||
|
|
||||||
|
## Common Patterns
|
||||||
|
|
||||||
|
### Development Workflow
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Run dev servers and watch for build changes
|
||||||
|
turbo watch dev build
|
||||||
|
```
|
||||||
|
|
||||||
|
### Type Checking During Development
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Watch and re-run type checks
|
||||||
|
turbo watch check-types
|
||||||
|
```
|
||||||
@@ -1,13 +1,7 @@
|
|||||||
# Ultracite Code Standards
|
# My Code Standards
|
||||||
|
|
||||||
This project uses **Ultracite**, a zero-config preset that enforces strict code quality standards through automated formatting and linting.
|
|
||||||
|
|
||||||
## Quick Reference
|
## Quick Reference
|
||||||
|
|
||||||
- **Format code**: `pnpm dlx ultracite fix`
|
|
||||||
- **Check for issues**: `pnpm dlx ultracite check`
|
|
||||||
- **Diagnose setup**: `pnpm dlx ultracite doctor`
|
|
||||||
|
|
||||||
ESLint + Prettier + Stylelint (the underlying engine) provides robust linting and formatting. Most issues are automatically fixable.
|
ESLint + Prettier + Stylelint (the underlying engine) provides robust linting and formatting. Most issues are automatically fixable.
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -87,20 +81,10 @@ Write code that is **accessible, performant, type-safe, and maintainable**. Focu
|
|||||||
|
|
||||||
### Framework-Specific Guidance
|
### Framework-Specific Guidance
|
||||||
|
|
||||||
**Next.js:**
|
|
||||||
|
|
||||||
- Use Next.js `<Image>` component for images
|
|
||||||
- Use `next/head` or App Router metadata API for head elements
|
|
||||||
- Use Server Components for async data fetching instead of async Client Components
|
|
||||||
|
|
||||||
**React 19+:**
|
**React 19+:**
|
||||||
|
|
||||||
- Use ref as a prop instead of `React.forwardRef`
|
- Use ref as a prop instead of `React.forwardRef`
|
||||||
|
|
||||||
**Solid/Svelte/Vue/Qwik:**
|
|
||||||
|
|
||||||
- Use `class` and `for` attributes (not `className` or `htmlFor`)
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Testing
|
## Testing
|
||||||
@@ -120,7 +104,3 @@ ESLint + Prettier + Stylelint's linter will catch most issues automatically. Foc
|
|||||||
4. **Edge cases** - Handle boundary conditions and error states
|
4. **Edge cases** - Handle boundary conditions and error states
|
||||||
5. **User experience** - Accessibility, performance, and usability considerations
|
5. **User experience** - Accessibility, performance, and usability considerations
|
||||||
6. **Documentation** - Add comments for complex logic, but prefer self-documenting code
|
6. **Documentation** - Add comments for complex logic, but prefer self-documenting code
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
Most formatting and common issues are automatically fixed by ESLint + Prettier + Stylelint. Run `pnpm dlx ultracite fix` before committing to ensure compliance.
|
|
||||||
|
|||||||
1
.claude/skills/turborepo
Symbolic link
1
.claude/skills/turborepo
Symbolic link
@@ -0,0 +1 @@
|
|||||||
|
../../.agents/skills/turborepo
|
||||||
3
apps/web/eslint.config.mjs
Normal file
3
apps/web/eslint.config.mjs
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
import reactConfig from "@zendegi/eslint-config/react";
|
||||||
|
|
||||||
|
export default reactConfig;
|
||||||
@@ -5,7 +5,8 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "vite build",
|
"build": "vite build",
|
||||||
"serve": "vite preview",
|
"serve": "vite preview",
|
||||||
"dev": "vite dev"
|
"dev": "vite dev",
|
||||||
|
"lint": "eslint ."
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@base-ui/react": "^1.0.0",
|
"@base-ui/react": "^1.0.0",
|
||||||
@@ -42,6 +43,8 @@
|
|||||||
"@types/react-dom": "19.2.3",
|
"@types/react-dom": "19.2.3",
|
||||||
"@vitejs/plugin-react": "^5.0.4",
|
"@vitejs/plugin-react": "^5.0.4",
|
||||||
"@zendegi/config": "workspace:*",
|
"@zendegi/config": "workspace:*",
|
||||||
|
"@zendegi/eslint-config": "workspace:*",
|
||||||
|
"eslint": "^9.17.0",
|
||||||
"jsdom": "^26.0.0",
|
"jsdom": "^26.0.0",
|
||||||
"typescript": "catalog:",
|
"typescript": "catalog:",
|
||||||
"vite": "^7.0.2",
|
"vite": "^7.0.2",
|
||||||
|
|||||||
@@ -3,14 +3,17 @@ import { useNavigate } from "@tanstack/react-router";
|
|||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import z from "zod";
|
import z from "zod";
|
||||||
|
|
||||||
import { authClient } from "@/lib/auth-client";
|
|
||||||
|
|
||||||
import Loader from "./loader";
|
import Loader from "./loader";
|
||||||
import { Button } from "./ui/button";
|
import { Button } from "./ui/button";
|
||||||
import { Input } from "./ui/input";
|
import { Input } from "./ui/input";
|
||||||
import { Label } from "./ui/label";
|
import { Label } from "./ui/label";
|
||||||
|
import { authClient } from "@/lib/auth-client";
|
||||||
|
|
||||||
export default function SignInForm({ onSwitchToSignUp }: { onSwitchToSignUp: () => void }) {
|
export default function SignInForm({
|
||||||
|
onSwitchToSignUp,
|
||||||
|
}: {
|
||||||
|
onSwitchToSignUp: () => void;
|
||||||
|
}) {
|
||||||
const navigate = useNavigate({
|
const navigate = useNavigate({
|
||||||
from: "/",
|
from: "/",
|
||||||
});
|
});
|
||||||
@@ -37,7 +40,7 @@ export default function SignInForm({ onSwitchToSignUp }: { onSwitchToSignUp: ()
|
|||||||
onError: (error) => {
|
onError: (error) => {
|
||||||
toast.error(error.error.message || error.error.statusText);
|
toast.error(error.error.message || error.error.statusText);
|
||||||
},
|
},
|
||||||
},
|
}
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
validators: {
|
validators: {
|
||||||
|
|||||||
@@ -3,14 +3,17 @@ import { useNavigate } from "@tanstack/react-router";
|
|||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import z from "zod";
|
import z from "zod";
|
||||||
|
|
||||||
import { authClient } from "@/lib/auth-client";
|
|
||||||
|
|
||||||
import Loader from "./loader";
|
import Loader from "./loader";
|
||||||
import { Button } from "./ui/button";
|
import { Button } from "./ui/button";
|
||||||
import { Input } from "./ui/input";
|
import { Input } from "./ui/input";
|
||||||
import { Label } from "./ui/label";
|
import { Label } from "./ui/label";
|
||||||
|
import { authClient } from "@/lib/auth-client";
|
||||||
|
|
||||||
export default function SignUpForm({ onSwitchToSignIn }: { onSwitchToSignIn: () => void }) {
|
export default function SignUpForm({
|
||||||
|
onSwitchToSignIn,
|
||||||
|
}: {
|
||||||
|
onSwitchToSignIn: () => void;
|
||||||
|
}) {
|
||||||
const navigate = useNavigate({
|
const navigate = useNavigate({
|
||||||
from: "/",
|
from: "/",
|
||||||
});
|
});
|
||||||
@@ -39,7 +42,7 @@ export default function SignUpForm({ onSwitchToSignIn }: { onSwitchToSignIn: ()
|
|||||||
onError: (error) => {
|
onError: (error) => {
|
||||||
toast.error(error.error.message || error.error.statusText);
|
toast.error(error.error.message || error.error.statusText);
|
||||||
},
|
},
|
||||||
},
|
}
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
validators: {
|
validators: {
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import type { VariantProps } from "class-variance-authority";
|
|
||||||
|
|
||||||
import { Button as ButtonPrimitive } from "@base-ui/react/button";
|
import { Button as ButtonPrimitive } from "@base-ui/react/button";
|
||||||
import { cva } from "class-variance-authority";
|
import { cva } from "class-variance-authority";
|
||||||
|
import type { VariantProps } from "class-variance-authority";
|
||||||
|
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
@@ -37,7 +36,7 @@ const buttonVariants = cva(
|
|||||||
variant: "default",
|
variant: "default",
|
||||||
size: "default",
|
size: "default",
|
||||||
},
|
},
|
||||||
},
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
function Button({
|
function Button({
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import * as React from "react";
|
import type * as React from "react";
|
||||||
|
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
@@ -13,7 +13,7 @@ function Card({
|
|||||||
data-size={size}
|
data-size={size}
|
||||||
className={cn(
|
className={cn(
|
||||||
"ring-foreground/10 bg-card text-card-foreground gap-4 overflow-hidden rounded-none py-4 text-xs/relaxed ring-1 has-data-[slot=card-footer]:pb-0 has-[>img:first-child]:pt-0 data-[size=sm]:gap-2 data-[size=sm]:py-3 data-[size=sm]:has-data-[slot=card-footer]:pb-0 *:[img:first-child]:rounded-none *:[img:last-child]:rounded-none group/card flex flex-col",
|
"ring-foreground/10 bg-card text-card-foreground gap-4 overflow-hidden rounded-none py-4 text-xs/relaxed ring-1 has-data-[slot=card-footer]:pb-0 has-[>img:first-child]:pt-0 data-[size=sm]:gap-2 data-[size=sm]:py-3 data-[size=sm]:has-data-[slot=card-footer]:pb-0 *:[img:first-child]:rounded-none *:[img:last-child]:rounded-none group/card flex flex-col",
|
||||||
className,
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
@@ -26,7 +26,7 @@ function CardHeader({ className, ...props }: React.ComponentProps<"div">) {
|
|||||||
data-slot="card-header"
|
data-slot="card-header"
|
||||||
className={cn(
|
className={cn(
|
||||||
"gap-1 rounded-none px-4 group-data-[size=sm]/card:px-3 [.border-b]:pb-4 group-data-[size=sm]/card:[.border-b]:pb-3 group/card-header @container/card-header grid auto-rows-min items-start has-data-[slot=card-action]:grid-cols-[1fr_auto] has-data-[slot=card-description]:grid-rows-[auto_auto]",
|
"gap-1 rounded-none px-4 group-data-[size=sm]/card:px-3 [.border-b]:pb-4 group-data-[size=sm]/card:[.border-b]:pb-3 group/card-header @container/card-header grid auto-rows-min items-start has-data-[slot=card-action]:grid-cols-[1fr_auto] has-data-[slot=card-description]:grid-rows-[auto_auto]",
|
||||||
className,
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
@@ -37,7 +37,10 @@ function CardTitle({ className, ...props }: React.ComponentProps<"div">) {
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
data-slot="card-title"
|
data-slot="card-title"
|
||||||
className={cn("text-sm font-medium group-data-[size=sm]/card:text-sm", className)}
|
className={cn(
|
||||||
|
"text-sm font-medium group-data-[size=sm]/card:text-sm",
|
||||||
|
className
|
||||||
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
@@ -57,7 +60,10 @@ function CardAction({ className, ...props }: React.ComponentProps<"div">) {
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
data-slot="card-action"
|
data-slot="card-action"
|
||||||
className={cn("col-start-2 row-span-2 row-start-1 self-start justify-self-end", className)}
|
className={cn(
|
||||||
|
"col-start-2 row-span-2 row-start-1 self-start justify-self-end",
|
||||||
|
className
|
||||||
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
@@ -79,11 +85,19 @@ function CardFooter({ className, ...props }: React.ComponentProps<"div">) {
|
|||||||
data-slot="card-footer"
|
data-slot="card-footer"
|
||||||
className={cn(
|
className={cn(
|
||||||
"rounded-none border-t p-4 group-data-[size=sm]/card:p-3 flex items-center",
|
"rounded-none border-t p-4 group-data-[size=sm]/card:p-3 flex items-center",
|
||||||
className,
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export { Card, CardHeader, CardFooter, CardTitle, CardAction, CardDescription, CardContent };
|
export {
|
||||||
|
Card,
|
||||||
|
CardHeader,
|
||||||
|
CardFooter,
|
||||||
|
CardTitle,
|
||||||
|
CardAction,
|
||||||
|
CardDescription,
|
||||||
|
CardContent,
|
||||||
|
};
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ function Checkbox({ className, ...props }: CheckboxPrimitive.Root.Props) {
|
|||||||
data-slot="checkbox"
|
data-slot="checkbox"
|
||||||
className={cn(
|
className={cn(
|
||||||
"border-input dark:bg-input/30 data-checked:bg-primary data-checked:text-primary-foreground dark:data-checked:bg-primary data-checked:border-primary aria-invalid:aria-checked:border-primary aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 flex size-4 items-center justify-center rounded-none border transition-colors group-has-disabled/field:opacity-50 focus-visible:ring-1 aria-invalid:ring-1 peer relative shrink-0 outline-none after:absolute after:-inset-x-3 after:-inset-y-2 disabled:cursor-not-allowed disabled:opacity-50",
|
"border-input dark:bg-input/30 data-checked:bg-primary data-checked:text-primary-foreground dark:data-checked:bg-primary data-checked:border-primary aria-invalid:aria-checked:border-primary aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 flex size-4 items-center justify-center rounded-none border transition-colors group-has-disabled/field:opacity-50 focus-visible:ring-1 aria-invalid:ring-1 peer relative shrink-0 outline-none after:absolute after:-inset-x-3 after:-inset-y-2 disabled:cursor-not-allowed disabled:opacity-50",
|
||||||
className,
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { Menu as MenuPrimitive } from "@base-ui/react/menu";
|
import { Menu as MenuPrimitive } from "@base-ui/react/menu";
|
||||||
import { CheckIcon, ChevronRightIcon } from "lucide-react";
|
import { CheckIcon, ChevronRightIcon } from "lucide-react";
|
||||||
import * as React from "react";
|
import type * as React from "react";
|
||||||
|
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
@@ -24,7 +24,10 @@ function DropdownMenuContent({
|
|||||||
className,
|
className,
|
||||||
...props
|
...props
|
||||||
}: MenuPrimitive.Popup.Props &
|
}: MenuPrimitive.Popup.Props &
|
||||||
Pick<MenuPrimitive.Positioner.Props, "align" | "alignOffset" | "side" | "sideOffset">) {
|
Pick<
|
||||||
|
MenuPrimitive.Positioner.Props,
|
||||||
|
"align" | "alignOffset" | "side" | "sideOffset"
|
||||||
|
>) {
|
||||||
return (
|
return (
|
||||||
<MenuPrimitive.Portal>
|
<MenuPrimitive.Portal>
|
||||||
<MenuPrimitive.Positioner
|
<MenuPrimitive.Positioner
|
||||||
@@ -38,7 +41,7 @@ function DropdownMenuContent({
|
|||||||
data-slot="dropdown-menu-content"
|
data-slot="dropdown-menu-content"
|
||||||
className={cn(
|
className={cn(
|
||||||
"data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 ring-foreground/10 bg-popover text-popover-foreground min-w-32 rounded-none shadow-md ring-1 duration-100 z-50 max-h-(--available-height) w-(--anchor-width) origin-(--transform-origin) overflow-x-hidden overflow-y-auto outline-none data-closed:overflow-hidden",
|
"data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 ring-foreground/10 bg-popover text-popover-foreground min-w-32 rounded-none shadow-md ring-1 duration-100 z-50 max-h-(--available-height) w-(--anchor-width) origin-(--transform-origin) overflow-x-hidden overflow-y-auto outline-none data-closed:overflow-hidden",
|
||||||
className,
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
@@ -62,7 +65,10 @@ function DropdownMenuLabel({
|
|||||||
<MenuPrimitive.GroupLabel
|
<MenuPrimitive.GroupLabel
|
||||||
data-slot="dropdown-menu-label"
|
data-slot="dropdown-menu-label"
|
||||||
data-inset={inset}
|
data-inset={inset}
|
||||||
className={cn("text-muted-foreground px-2 py-2 text-xs data-[inset]:pl-8", className)}
|
className={cn(
|
||||||
|
"text-muted-foreground px-2 py-2 text-xs data-[inset]:pl-8",
|
||||||
|
className
|
||||||
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
@@ -84,7 +90,7 @@ function DropdownMenuItem({
|
|||||||
data-variant={variant}
|
data-variant={variant}
|
||||||
className={cn(
|
className={cn(
|
||||||
"focus:bg-accent focus:text-accent-foreground data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 dark:data-[variant=destructive]:focus:bg-destructive/20 data-[variant=destructive]:focus:text-destructive data-[variant=destructive]:*:[svg]:text-destructive not-data-[variant=destructive]:focus:**:text-accent-foreground gap-2 rounded-none px-2 py-2 text-xs [&_svg:not([class*='size-'])]:size-4 group/dropdown-menu-item relative flex cursor-default items-center outline-hidden select-none data-disabled:pointer-events-none data-disabled:opacity-50 data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0",
|
"focus:bg-accent focus:text-accent-foreground data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 dark:data-[variant=destructive]:focus:bg-destructive/20 data-[variant=destructive]:focus:text-destructive data-[variant=destructive]:*:[svg]:text-destructive not-data-[variant=destructive]:focus:**:text-accent-foreground gap-2 rounded-none px-2 py-2 text-xs [&_svg:not([class*='size-'])]:size-4 group/dropdown-menu-item relative flex cursor-default items-center outline-hidden select-none data-disabled:pointer-events-none data-disabled:opacity-50 data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0",
|
||||||
className,
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
@@ -109,7 +115,7 @@ function DropdownMenuSubTrigger({
|
|||||||
data-inset={inset}
|
data-inset={inset}
|
||||||
className={cn(
|
className={cn(
|
||||||
"focus:bg-accent focus:text-accent-foreground data-open:bg-accent data-open:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground gap-2 rounded-none px-2 py-2 text-xs [&_svg:not([class*='size-'])]:size-4 flex cursor-default items-center outline-hidden select-none data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0",
|
"focus:bg-accent focus:text-accent-foreground data-open:bg-accent data-open:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground gap-2 rounded-none px-2 py-2 text-xs [&_svg:not([class*='size-'])]:size-4 flex cursor-default items-center outline-hidden select-none data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0",
|
||||||
className,
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
@@ -132,7 +138,7 @@ function DropdownMenuSubContent({
|
|||||||
data-slot="dropdown-menu-sub-content"
|
data-slot="dropdown-menu-sub-content"
|
||||||
className={cn(
|
className={cn(
|
||||||
"data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 ring-foreground/10 bg-popover text-popover-foreground min-w-[96px] rounded-none shadow-lg ring-1 duration-100 w-auto",
|
"data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 ring-foreground/10 bg-popover text-popover-foreground min-w-[96px] rounded-none shadow-lg ring-1 duration-100 w-auto",
|
||||||
className,
|
className
|
||||||
)}
|
)}
|
||||||
align={align}
|
align={align}
|
||||||
alignOffset={alignOffset}
|
alignOffset={alignOffset}
|
||||||
@@ -154,7 +160,7 @@ function DropdownMenuCheckboxItem({
|
|||||||
data-slot="dropdown-menu-checkbox-item"
|
data-slot="dropdown-menu-checkbox-item"
|
||||||
className={cn(
|
className={cn(
|
||||||
"focus:bg-accent focus:text-accent-foreground focus:**:text-accent-foreground gap-2 rounded-none py-2 pr-8 pl-2 text-xs [&_svg:not([class*='size-'])]:size-4 relative flex cursor-default items-center outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0",
|
"focus:bg-accent focus:text-accent-foreground focus:**:text-accent-foreground gap-2 rounded-none py-2 pr-8 pl-2 text-xs [&_svg:not([class*='size-'])]:size-4 relative flex cursor-default items-center outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0",
|
||||||
className,
|
className
|
||||||
)}
|
)}
|
||||||
checked={checked}
|
checked={checked}
|
||||||
{...props}
|
{...props}
|
||||||
@@ -173,16 +179,25 @@ function DropdownMenuCheckboxItem({
|
|||||||
}
|
}
|
||||||
|
|
||||||
function DropdownMenuRadioGroup({ ...props }: MenuPrimitive.RadioGroup.Props) {
|
function DropdownMenuRadioGroup({ ...props }: MenuPrimitive.RadioGroup.Props) {
|
||||||
return <MenuPrimitive.RadioGroup data-slot="dropdown-menu-radio-group" {...props} />;
|
return (
|
||||||
|
<MenuPrimitive.RadioGroup
|
||||||
|
data-slot="dropdown-menu-radio-group"
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function DropdownMenuRadioItem({ className, children, ...props }: MenuPrimitive.RadioItem.Props) {
|
function DropdownMenuRadioItem({
|
||||||
|
className,
|
||||||
|
children,
|
||||||
|
...props
|
||||||
|
}: MenuPrimitive.RadioItem.Props) {
|
||||||
return (
|
return (
|
||||||
<MenuPrimitive.RadioItem
|
<MenuPrimitive.RadioItem
|
||||||
data-slot="dropdown-menu-radio-item"
|
data-slot="dropdown-menu-radio-item"
|
||||||
className={cn(
|
className={cn(
|
||||||
"focus:bg-accent focus:text-accent-foreground focus:**:text-accent-foreground gap-2 rounded-none py-2 pr-8 pl-2 text-xs [&_svg:not([class*='size-'])]:size-4 relative flex cursor-default items-center outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0",
|
"focus:bg-accent focus:text-accent-foreground focus:**:text-accent-foreground gap-2 rounded-none py-2 pr-8 pl-2 text-xs [&_svg:not([class*='size-'])]:size-4 relative flex cursor-default items-center outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0",
|
||||||
className,
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
@@ -199,7 +214,10 @@ function DropdownMenuRadioItem({ className, children, ...props }: MenuPrimitive.
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function DropdownMenuSeparator({ className, ...props }: MenuPrimitive.Separator.Props) {
|
function DropdownMenuSeparator({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: MenuPrimitive.Separator.Props) {
|
||||||
return (
|
return (
|
||||||
<MenuPrimitive.Separator
|
<MenuPrimitive.Separator
|
||||||
data-slot="dropdown-menu-separator"
|
data-slot="dropdown-menu-separator"
|
||||||
@@ -209,13 +227,16 @@ function DropdownMenuSeparator({ className, ...props }: MenuPrimitive.Separator.
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function DropdownMenuShortcut({ className, ...props }: React.ComponentProps<"span">) {
|
function DropdownMenuShortcut({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<"span">) {
|
||||||
return (
|
return (
|
||||||
<span
|
<span
|
||||||
data-slot="dropdown-menu-shortcut"
|
data-slot="dropdown-menu-shortcut"
|
||||||
className={cn(
|
className={cn(
|
||||||
"text-muted-foreground group-focus/dropdown-menu-item:text-accent-foreground ml-auto text-xs tracking-widest",
|
"text-muted-foreground group-focus/dropdown-menu-item:text-accent-foreground ml-auto text-xs tracking-widest",
|
||||||
className,
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Input as InputPrimitive } from "@base-ui/react/input";
|
import { Input as InputPrimitive } from "@base-ui/react/input";
|
||||||
import * as React from "react";
|
import type * as React from "react";
|
||||||
|
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
@@ -10,7 +10,7 @@ function Input({ className, type, ...props }: React.ComponentProps<"input">) {
|
|||||||
data-slot="input"
|
data-slot="input"
|
||||||
className={cn(
|
className={cn(
|
||||||
"dark:bg-input/30 border-input focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 disabled:bg-input/50 dark:disabled:bg-input/80 h-8 rounded-none border bg-transparent px-2.5 py-1 text-xs transition-colors file:h-6 file:text-xs file:font-medium focus-visible:ring-1 aria-invalid:ring-1 md:text-xs file:text-foreground placeholder:text-muted-foreground w-full min-w-0 outline-none file:inline-flex file:border-0 file:bg-transparent disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50",
|
"dark:bg-input/30 border-input focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 disabled:bg-input/50 dark:disabled:bg-input/80 h-8 rounded-none border bg-transparent px-2.5 py-1 text-xs transition-colors file:h-6 file:text-xs file:font-medium focus-visible:ring-1 aria-invalid:ring-1 md:text-xs file:text-foreground placeholder:text-muted-foreground w-full min-w-0 outline-none file:inline-flex file:border-0 file:bg-transparent disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50",
|
||||||
className,
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,16 +1,17 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import * as React from "react";
|
import type * as React from "react";
|
||||||
|
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
function Label({ className, ...props }: React.ComponentProps<"label">) {
|
function Label({ className, ...props }: React.ComponentProps<"label">) {
|
||||||
return (
|
return (
|
||||||
|
// eslint-disable-next-line jsx-a11y/label-has-associated-control -- htmlFor is passed via ...props
|
||||||
<label
|
<label
|
||||||
data-slot="label"
|
data-slot="label"
|
||||||
className={cn(
|
className={cn(
|
||||||
"gap-2 text-xs leading-none group-data-[disabled=true]:opacity-50 peer-disabled:opacity-50 flex items-center select-none group-data-[disabled=true]:pointer-events-none peer-disabled:cursor-not-allowed",
|
"gap-2 text-xs leading-none group-data-[disabled=true]:opacity-50 peer-disabled:opacity-50 flex items-center select-none group-data-[disabled=true]:pointer-events-none peer-disabled:cursor-not-allowed",
|
||||||
className,
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
function Skeleton({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {
|
function Skeleton({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.HTMLAttributes<HTMLDivElement>) {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
data-slot="skeleton"
|
data-slot="skeleton"
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
import type { ToasterProps } from "sonner";
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
CircleCheckIcon,
|
CircleCheckIcon,
|
||||||
InfoIcon,
|
InfoIcon,
|
||||||
@@ -9,6 +7,7 @@ import {
|
|||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import { useTheme } from "next-themes";
|
import { useTheme } from "next-themes";
|
||||||
import { Toaster as Sonner } from "sonner";
|
import { Toaster as Sonner } from "sonner";
|
||||||
|
import type { ToasterProps } from "sonner";
|
||||||
|
|
||||||
const Toaster = ({ ...props }: ToasterProps) => {
|
const Toaster = ({ ...props }: ToasterProps) => {
|
||||||
const { theme = "system" } = useTheme();
|
const { theme = "system" } = useTheme();
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import { Link, useNavigate } from "@tanstack/react-router";
|
import { Link, useNavigate } from "@tanstack/react-router";
|
||||||
|
|
||||||
|
import { Button } from "./ui/button";
|
||||||
|
import { Skeleton } from "./ui/skeleton";
|
||||||
import {
|
import {
|
||||||
DropdownMenu,
|
DropdownMenu,
|
||||||
DropdownMenuContent,
|
DropdownMenuContent,
|
||||||
@@ -11,9 +13,6 @@ import {
|
|||||||
} from "@/components/ui/dropdown-menu";
|
} from "@/components/ui/dropdown-menu";
|
||||||
import { authClient } from "@/lib/auth-client";
|
import { authClient } from "@/lib/auth-client";
|
||||||
|
|
||||||
import { Button } from "./ui/button";
|
|
||||||
import { Skeleton } from "./ui/skeleton";
|
|
||||||
|
|
||||||
export default function UserMenu() {
|
export default function UserMenu() {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { data: session, isPending } = authClient.useSession();
|
const { data: session, isPending } = authClient.useSession();
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { clsx, type ClassValue } from "clsx";
|
import { clsx } from "clsx";
|
||||||
import { twMerge } from "tailwind-merge";
|
import { twMerge } from "tailwind-merge";
|
||||||
|
import type { ClassValue } from "clsx";
|
||||||
|
|
||||||
export function cn(...inputs: ClassValue[]) {
|
export function cn(...inputs: Array<ClassValue>) {
|
||||||
return twMerge(clsx(inputs));
|
return twMerge(clsx(inputs));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
import { createMiddleware } from "@tanstack/react-start";
|
import { createMiddleware } from "@tanstack/react-start";
|
||||||
import { auth } from "@zendegi/auth";
|
import { auth } from "@zendegi/auth";
|
||||||
|
|
||||||
export const authMiddleware = createMiddleware().server(async ({ next, request }) => {
|
export const authMiddleware = createMiddleware().server(
|
||||||
|
async ({ next, request }) => {
|
||||||
const session = await auth.api.getSession({
|
const session = await auth.api.getSession({
|
||||||
headers: request.headers,
|
headers: request.headers,
|
||||||
});
|
});
|
||||||
return next({
|
return next({
|
||||||
context: { session },
|
context: { session },
|
||||||
});
|
});
|
||||||
});
|
}
|
||||||
|
);
|
||||||
|
|||||||
@@ -1,10 +1,14 @@
|
|||||||
import { HeadContent, Outlet, Scripts, createRootRouteWithContext } from "@tanstack/react-router";
|
import {
|
||||||
|
HeadContent,
|
||||||
|
Outlet,
|
||||||
|
Scripts,
|
||||||
|
createRootRouteWithContext,
|
||||||
|
} from "@tanstack/react-router";
|
||||||
import { TanStackRouterDevtools } from "@tanstack/react-router-devtools";
|
import { TanStackRouterDevtools } from "@tanstack/react-router-devtools";
|
||||||
|
|
||||||
import { Toaster } from "@/components/ui/sonner";
|
|
||||||
|
|
||||||
import Header from "../components/header";
|
import Header from "../components/header";
|
||||||
import appCss from "../index.css?url";
|
import appCss from "../index.css?url";
|
||||||
|
import { Toaster } from "@/components/ui/sonner";
|
||||||
|
|
||||||
export interface RouterAppContext {}
|
export interface RouterAppContext {}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +0,0 @@
|
|||||||
import core from "ultracite/eslint/core";
|
|
||||||
import react from "ultracite/eslint/react";
|
|
||||||
|
|
||||||
export default [...core, ...react];
|
|
||||||
@@ -20,8 +20,8 @@
|
|||||||
"db:watch": "turbo -F @zendegi/db db:watch",
|
"db:watch": "turbo -F @zendegi/db db:watch",
|
||||||
"db:stop": "turbo -F @zendegi/db db:stop",
|
"db:stop": "turbo -F @zendegi/db db:stop",
|
||||||
"db:down": "turbo -F @zendegi/db db:down",
|
"db:down": "turbo -F @zendegi/db db:down",
|
||||||
"check": "ultracite check",
|
"check": "turbo run lint check-types && prettier --check .",
|
||||||
"fix": "ultracite fix"
|
"fix": "turbo run lint -- --fix && prettier --write ."
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@zendegi/env": "workspace:*",
|
"@zendegi/env": "workspace:*",
|
||||||
@@ -31,12 +31,11 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "catalog:",
|
"@types/node": "catalog:",
|
||||||
"@zendegi/config": "workspace:*",
|
"@zendegi/config": "workspace:*",
|
||||||
"eslint": "latest",
|
"eslint": "^9.17.0",
|
||||||
"prettier": "latest",
|
"prettier": "latest",
|
||||||
"stylelint": "latest",
|
"stylelint": "latest",
|
||||||
"turbo": "^2.6.3",
|
"turbo": "^2.6.3",
|
||||||
"typescript": "catalog:",
|
"typescript": "catalog:"
|
||||||
"ultracite": "7.1.5"
|
|
||||||
},
|
},
|
||||||
"packageManager": "pnpm@10.29.2"
|
"packageManager": "pnpm@10.29.2"
|
||||||
}
|
}
|
||||||
|
|||||||
3
packages/auth/eslint.config.mjs
Normal file
3
packages/auth/eslint.config.mjs
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
import baseConfig from "@zendegi/eslint-config/base";
|
||||||
|
|
||||||
|
export default baseConfig;
|
||||||
@@ -9,7 +9,9 @@
|
|||||||
"default": "./src/*.ts"
|
"default": "./src/*.ts"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"scripts": {},
|
"scripts": {
|
||||||
|
"lint": "eslint ."
|
||||||
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@zendegi/db": "workspace:*",
|
"@zendegi/db": "workspace:*",
|
||||||
"@zendegi/env": "workspace:*",
|
"@zendegi/env": "workspace:*",
|
||||||
@@ -19,6 +21,8 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@zendegi/config": "workspace:*",
|
"@zendegi/config": "workspace:*",
|
||||||
|
"@zendegi/eslint-config": "workspace:*",
|
||||||
|
"eslint": "^9.17.0",
|
||||||
"typescript": "catalog:"
|
"typescript": "catalog:"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
3
packages/db/eslint.config.mjs
Normal file
3
packages/db/eslint.config.mjs
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
import baseConfig from "@zendegi/eslint-config/base";
|
||||||
|
|
||||||
|
export default baseConfig;
|
||||||
@@ -14,6 +14,7 @@
|
|||||||
"db:generate": "drizzle-kit generate",
|
"db:generate": "drizzle-kit generate",
|
||||||
"db:studio": "drizzle-kit studio",
|
"db:studio": "drizzle-kit studio",
|
||||||
"db:migrate": "drizzle-kit migrate",
|
"db:migrate": "drizzle-kit migrate",
|
||||||
|
"lint": "eslint .",
|
||||||
"db:start": "docker compose up -d",
|
"db:start": "docker compose up -d",
|
||||||
"db:watch": "docker compose up",
|
"db:watch": "docker compose up",
|
||||||
"db:stop": "docker compose stop",
|
"db:stop": "docker compose stop",
|
||||||
@@ -29,7 +30,9 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/pg": "^8.16.0",
|
"@types/pg": "^8.16.0",
|
||||||
"@zendegi/config": "workspace:*",
|
"@zendegi/config": "workspace:*",
|
||||||
|
"@zendegi/eslint-config": "workspace:*",
|
||||||
"drizzle-kit": "^0.31.8",
|
"drizzle-kit": "^0.31.8",
|
||||||
|
"eslint": "^9.17.0",
|
||||||
"typescript": "catalog:"
|
"typescript": "catalog:"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { relations } from "drizzle-orm";
|
import { relations } from "drizzle-orm";
|
||||||
import { pgTable, text, timestamp, boolean, index } from "drizzle-orm/pg-core";
|
import { boolean, index, pgTable, text, timestamp } from "drizzle-orm/pg-core";
|
||||||
|
|
||||||
export const user = pgTable("user", {
|
export const user = pgTable("user", {
|
||||||
id: text("id").primaryKey(),
|
id: text("id").primaryKey(),
|
||||||
@@ -30,7 +30,7 @@ export const session = pgTable(
|
|||||||
.notNull()
|
.notNull()
|
||||||
.references(() => user.id, { onDelete: "cascade" }),
|
.references(() => user.id, { onDelete: "cascade" }),
|
||||||
},
|
},
|
||||||
(table) => [index("session_userId_idx").on(table.userId)],
|
(table) => [index("session_userId_idx").on(table.userId)]
|
||||||
);
|
);
|
||||||
|
|
||||||
export const account = pgTable(
|
export const account = pgTable(
|
||||||
@@ -54,7 +54,7 @@ export const account = pgTable(
|
|||||||
.$onUpdate(() => /* @__PURE__ */ new Date())
|
.$onUpdate(() => /* @__PURE__ */ new Date())
|
||||||
.notNull(),
|
.notNull(),
|
||||||
},
|
},
|
||||||
(table) => [index("account_userId_idx").on(table.userId)],
|
(table) => [index("account_userId_idx").on(table.userId)]
|
||||||
);
|
);
|
||||||
|
|
||||||
export const verification = pgTable(
|
export const verification = pgTable(
|
||||||
@@ -70,7 +70,7 @@ export const verification = pgTable(
|
|||||||
.$onUpdate(() => /* @__PURE__ */ new Date())
|
.$onUpdate(() => /* @__PURE__ */ new Date())
|
||||||
.notNull(),
|
.notNull(),
|
||||||
},
|
},
|
||||||
(table) => [index("verification_identifier_idx").on(table.identifier)],
|
(table) => [index("verification_identifier_idx").on(table.identifier)]
|
||||||
);
|
);
|
||||||
|
|
||||||
export const userRelations = relations(user, ({ many }) => ({
|
export const userRelations = relations(user, ({ many }) => ({
|
||||||
|
|||||||
3
packages/env/eslint.config.mjs
vendored
Normal file
3
packages/env/eslint.config.mjs
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
import baseConfig from "@zendegi/eslint-config/base";
|
||||||
|
|
||||||
|
export default baseConfig;
|
||||||
5
packages/env/package.json
vendored
5
packages/env/package.json
vendored
@@ -7,6 +7,9 @@
|
|||||||
"./server": "./src/server.ts",
|
"./server": "./src/server.ts",
|
||||||
"./web": "./src/web.ts"
|
"./web": "./src/web.ts"
|
||||||
},
|
},
|
||||||
|
"scripts": {
|
||||||
|
"lint": "eslint ."
|
||||||
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@t3-oss/env-core": "^0.13.1",
|
"@t3-oss/env-core": "^0.13.1",
|
||||||
"dotenv": "catalog:",
|
"dotenv": "catalog:",
|
||||||
@@ -15,6 +18,8 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "catalog:",
|
"@types/node": "catalog:",
|
||||||
"@zendegi/config": "workspace:*",
|
"@zendegi/config": "workspace:*",
|
||||||
|
"@zendegi/eslint-config": "workspace:*",
|
||||||
|
"eslint": "^9.17.0",
|
||||||
"typescript": "catalog:"
|
"typescript": "catalog:"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
4
packages/env/src/server.ts
vendored
4
packages/env/src/server.ts
vendored
@@ -8,7 +8,9 @@ export const env = createEnv({
|
|||||||
BETTER_AUTH_SECRET: z.string().min(32),
|
BETTER_AUTH_SECRET: z.string().min(32),
|
||||||
BETTER_AUTH_URL: z.url(),
|
BETTER_AUTH_URL: z.url(),
|
||||||
CORS_ORIGIN: z.url(),
|
CORS_ORIGIN: z.url(),
|
||||||
NODE_ENV: z.enum(["development", "production", "test"]).default("development"),
|
NODE_ENV: z
|
||||||
|
.enum(["development", "production", "test"])
|
||||||
|
.default("development"),
|
||||||
},
|
},
|
||||||
runtimeEnv: process.env,
|
runtimeEnv: process.env,
|
||||||
emptyStringAsUndefined: true,
|
emptyStringAsUndefined: true,
|
||||||
|
|||||||
3
packages/eslint-config/base.js
Normal file
3
packages/eslint-config/base.js
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
import { tanstackConfig } from "@tanstack/eslint-config";
|
||||||
|
|
||||||
|
export default [...tanstackConfig];
|
||||||
20
packages/eslint-config/package.json
Normal file
20
packages/eslint-config/package.json
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"name": "@zendegi/eslint-config",
|
||||||
|
"version": "0.0.0",
|
||||||
|
"private": true,
|
||||||
|
"type": "module",
|
||||||
|
"exports": {
|
||||||
|
"./base": "./base.js",
|
||||||
|
"./react": "./react.js"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@tanstack/eslint-config": "^0.3.4",
|
||||||
|
"eslint-plugin-jsx-a11y": "^6.10.2",
|
||||||
|
"eslint-plugin-react": "^7.37.0",
|
||||||
|
"eslint-plugin-react-hooks": "^5.1.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"eslint": "^9.0.0",
|
||||||
|
"typescript": "^5.0.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
38
packages/eslint-config/react.js
vendored
Normal file
38
packages/eslint-config/react.js
vendored
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
import jsxA11y from "eslint-plugin-jsx-a11y";
|
||||||
|
import react from "eslint-plugin-react";
|
||||||
|
import reactHooks from "eslint-plugin-react-hooks";
|
||||||
|
import baseConfig from "./base.js";
|
||||||
|
|
||||||
|
export default [
|
||||||
|
...baseConfig,
|
||||||
|
{
|
||||||
|
files: ["**/*.tsx", "**/*.jsx"],
|
||||||
|
...react.configs.flat.recommended,
|
||||||
|
...react.configs.flat["jsx-runtime"],
|
||||||
|
settings: {
|
||||||
|
react: { version: "detect" },
|
||||||
|
},
|
||||||
|
rules: {
|
||||||
|
...react.configs.flat.recommended.rules,
|
||||||
|
...react.configs.flat["jsx-runtime"].rules,
|
||||||
|
"react/react-in-jsx-scope": "off",
|
||||||
|
"react/prop-types": "off",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
files: ["**/*.tsx", "**/*.jsx"],
|
||||||
|
plugins: { "react-hooks": reactHooks },
|
||||||
|
rules: {
|
||||||
|
"react-hooks/rules-of-hooks": "error",
|
||||||
|
"react-hooks/exhaustive-deps": "warn",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
files: ["**/*.tsx", "**/*.jsx"],
|
||||||
|
...jsxA11y.flatConfigs.recommended,
|
||||||
|
rules: {
|
||||||
|
...jsxA11y.flatConfigs.recommended.rules,
|
||||||
|
"jsx-a11y/label-has-associated-control": ["error", { assert: "either" }],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
9840
pnpm-lock.yaml
generated
9840
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -1 +1,14 @@
|
|||||||
export { default } from "ultracite/prettier";
|
/** @type {import('prettier').Config} */
|
||||||
|
const config = {
|
||||||
|
tabWidth: 2,
|
||||||
|
useTabs: false,
|
||||||
|
semi: true,
|
||||||
|
singleQuote: false,
|
||||||
|
trailingComma: "es5",
|
||||||
|
bracketSpacing: true,
|
||||||
|
arrowParens: "always",
|
||||||
|
proseWrap: "never",
|
||||||
|
printWidth: 80,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default config;
|
||||||
|
|||||||
@@ -7,11 +7,14 @@
|
|||||||
"inputs": ["$TURBO_DEFAULT$", ".env*"],
|
"inputs": ["$TURBO_DEFAULT$", ".env*"],
|
||||||
"outputs": ["dist/**"]
|
"outputs": ["dist/**"]
|
||||||
},
|
},
|
||||||
|
"transit": {
|
||||||
|
"dependsOn": ["^transit"]
|
||||||
|
},
|
||||||
"lint": {
|
"lint": {
|
||||||
"dependsOn": ["^lint"]
|
"dependsOn": ["transit"]
|
||||||
},
|
},
|
||||||
"check-types": {
|
"check-types": {
|
||||||
"dependsOn": ["^check-types"]
|
"dependsOn": ["transit"]
|
||||||
},
|
},
|
||||||
"dev": {
|
"dev": {
|
||||||
"cache": false,
|
"cache": false,
|
||||||
|
|||||||
Reference in New Issue
Block a user