This document describes the Turborepo integration for the ILN Smart Contract monorepo, providing intelligent task caching, parallel execution, and improved build times.
Turborepo is a high-performance build system for JavaScript/TypeScript monorepos that:
- Caches task outputs locally and remotely
- Runs tasks in parallel respecting dependencies
- Only rebuilds what changed using content-based hashing
- Provides remote caching for CI/CD and team collaboration
- Sequential execution: Tasks ran one after another
- No caching: Every task ran from scratch on every execution
- Manual dependency management: Had to manually ensure SDK built before CLI
- Estimated CI time: ~8-12 minutes for full test suite
- Parallel execution: Independent tasks run simultaneously
- Intelligent caching: Unchanged code uses cached results
- Automatic dependency resolution: Turbo ensures SDK builds before CLI automatically
- Expected CI time: ~3-5 minutes for full test suite (60%+ improvement on cache hit)
- Local development: Near-instant rebuilds for unchanged packages
ILN-Smart-Contract/
├── package.json # Root workspace config
├── turbo.json # Turborepo pipeline configuration
├── .turborc # Remote cache configuration
├── sdk/ # @iln/sdk - TypeScript SDK (Jest)
├── cli/ # @iln/cli - Command-line interface (Jest)
├── indexer/ # @iln/indexer - REST API indexer (Vitest)
└── notifications/ # @iln/notifications - Notification service (Vitest)
CLI depends on SDK (must build SDK first)
├── SDK (build) → CLI (build)
├── SDK (test) runs in parallel with CLI (test)
└── Indexer and Notifications are independent
The turbo.json file defines task pipelines with dependencies:
{
"pipeline": {
"build": {
"dependsOn": ["^build"], // Wait for dependencies to build first
"outputs": ["dist/**"], // Cache these directories
"env": ["NODE_ENV"] // Invalidate cache if env changes
},
"test": {
"dependsOn": ["build"], // Run after local build
"outputs": ["coverage/**"], // Cache coverage reports
"cache": true
},
"test:ci": {
"dependsOn": ["build"],
"outputs": ["coverage/**"],
"env": ["NODE_ENV", "CI"] // CI-specific configuration
},
"lint": {
"outputs": [], // No outputs to cache
"cache": true // But cache the lint results
},
"typecheck": {
"dependsOn": ["^build"], // Need built types from dependencies
"cache": true
}
}
}^build- Wait forbuildin dependencies to finish firstbuild- Wait forbuildin same package to finish first- No operator - Can run in parallel with other tasks
Each package has standardized scripts:
{
"scripts": {
"build": "...", // Build the package
"test": "...", // Run tests
"test:ci": "...", // Run tests in CI mode
"lint": "...", // Run linter
"typecheck": "tsc --noEmit", // Type checking without emit
"clean": "rm -rf dist coverage node_modules/.cache"
}
}npm installThis installs Turborepo at the root and all workspace dependencies.
npm run buildTurborepo will:
- Build SDK first (no dependencies)
- Build CLI after SDK completes (depends on SDK)
- Build indexer and notifications in parallel
npm run testRuns tests across all packages in parallel, respecting build dependencies.
npm run test:ciUses CI-specific test configurations (no watch mode, coverage reports, etc.).
npm run typecheckRuns TypeScript type checking across all packages.
npm run lintRuns linters across all packages (currently only CLI has ESLint configured).
npm run devStarts all services in development/watch mode in parallel.
npm run cleanRemoves all build artifacts, coverage reports, and caches.
Run tasks for specific packages:
# Build only SDK
npx turbo run build --filter=@iln/sdk
# Test SDK and CLI
npx turbo run test --filter=@iln/sdk --filter=@iln/cli
# Build SDK and everything that depends on it
npx turbo run build --filter=...@iln/sdk# Run with verbose output
npx turbo run build --verbose
# Output will show:
# ✓ @iln/sdk:build [CACHE HIT] <-- Used cached result
# ⠋ @iln/cli:build [RUNNING] <-- Building fresh# Clear turbo cache
rm -rf .turbo
# Or use turbo command
npx turbo prune --force# Run without cache
npx turbo run test --force
# Skip cache for specific package
npx turbo run build --filter=@iln/sdk --forceRemote caching shares build artifacts across team members and CI runs.
-
Sign up for Vercel (free for open source):
npx turbo login
-
Link your repository:
npx turbo link
-
Configure team (updates
.turborc): The.turborcfile stores your team configuration. -
In CI, add token:
env: TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }} TURBO_TEAM: team_iln
For private deployments, you can run your own cache server:
-
Deploy turbo-cache server:
docker run -p 3000:3000 \ -e PORT=3000 \ -e STORAGE_PROVIDER=s3 \ -e S3_BUCKET=your-turbo-cache \ ghcr.io/ducktors/turbo-cache
-
Update
.turborc:{ "apiUrl": "https://your-cache-server.com", "teamId": "your-team" } -
Set token in environment:
export TURBO_TOKEN=your-auth-token
The updated .github/workflows/ci.yml uses Turborepo:
- name: Setup Turborepo cache
uses: actions/cache@v4
with:
path: .turbo
key: ${{ runner.os }}-turbo-${{ github.sha }}
restore-keys: |
${{ runner.os }}-turbo-
- name: Run typecheck (Turbo)
run: npm run typecheck
- name: Run build (Turbo)
run: npm run build
- name: Run tests (Turbo)
run: npm run test:ci- Faster Builds: Cache hits from previous runs
- Parallel Execution: Multiple jobs run simultaneously
- Smart Rebuilds: Only changed packages rebuild
- Consistent Results: Same cache key = same output
Run without cache:
time (cd sdk && npm run build && npm run test && cd ../cli && npm run build && npm run test && cd ../indexer && npm run build && npm run test && cd ../notifications && npm run build && npm run test)Cold cache (no speedup expected):
time npm run build && npm run testWarm cache (expect 60-90% speedup):
# Make a change to one package
echo "// comment" >> sdk/src/index.ts
# Rebuild everything
time npm run build && npm run test| Scenario | Before | After | Improvement |
|---|---|---|---|
| Cold build (no cache) | ~8 min | ~5 min | 37% faster (parallelization) |
| Warm build (cache hit) | ~8 min | ~30 sec | 94% faster (caching) |
| Single package change | ~8 min | ~2 min | 75% faster (incremental) |
| CI with remote cache | ~12 min | ~4 min | 67% faster |
Document actual measurements here after implementation:
# Measure cold build
npm run clean
time npm run build && npm run test
# Measure warm build (no changes)
time npm run build && npm run test
# Measure incremental build (small change)
echo "// comment" >> sdk/src/index.ts
time npm run build && npm run testProblem: Tasks always rebuild, never hit cache
Solutions:
- Check
.turbodirectory exists - Verify
outputsinturbo.jsonmatch actual build outputs - Check for dynamic environment variables (use
globalEnv) - Look for non-deterministic builds (timestamps, random values)
Problem: CLI builds before SDK is ready
Solutions:
- Verify
^builddependency inturbo.json - Check package.json workspace configuration
- Run with
--dry-runto see execution order:npx turbo run build --dry-run
Problem: Can't push/pull from remote cache
Solutions:
- Verify
TURBO_TOKENis set - Check
.turborcconfiguration - Test connection:
npx turbo login - Check network/firewall settings
Problem: .turbo directory growing too large
Solutions:
- Clean periodically:
rm -rf .turbo - Configure pruning in CI:
- name: Prune cache run: npx turbo prune --force
- Limit cache size (future feature)
Before:
cd sdk && npm run build && cd ..
cd cli && npm run build && cd ..
cd sdk && npm run test &
cd cli && npm run test &
waitAfter:
npm run build # Handles dependencies automatically
npm run test # Runs in parallel automaticallyTurborepo is simpler and faster:
- Remove Lerna/Nx config files
- Add
turbo.json - Update
package.jsonscripts to useturbo run - No need for complex workspace topology files
Keep your workspace config, add Turbo on top:
- Keep existing
package.jsonworkspaces - Add
turbo.jsonfor task orchestration - Update scripts to use
turbo run - Enjoy caching and parallelization
Ensure builds produce the same output for the same input:
- Don't embed timestamps or random values
- Use fixed version dependencies
- Avoid relying on system state
List all build artifacts in outputs:
{
"build": {
"outputs": [
"dist/**",
"build/**",
".next/**",
"!.next/cache/**" // Exclude cache directories
]
}
}Use globalEnv for shared variables:
{
"globalEnv": ["NODE_ENV", "API_URL"],
"pipeline": {
"test": {
"env": ["TEST_DATABASE_URL"] // Task-specific
}
}
}- Development: Use local cache only
- CI: Use remote cache with GitHub Actions cache
- Production: Use remote cache with Vercel or self-hosted
Use --dry-run to see what will execute:
npx turbo run build --dry-run=jsonUse --graph to visualize dependencies:
npx turbo run build --graphFor issues or questions:
- Check this documentation
- Review Turborepo docs
- Check GitHub issues
- Ask in team chat/discussions
- Installed Turborepo v2.3.3
- Created
turbo.jsonwith pipeline configuration - Updated all package.json scripts
- Updated CI workflow with Turbo integration
- Configured remote caching
- Documented performance expectations