Development Guide
Monorepo setup with JIT packages, pnpm workspaces, Turbo tasks, and troubleshooting
Development Tooling Guide
This guide explains the monorepo tooling setup for the Artifacts platform.
Table of Contents
- Just-in-Time (JIT) Package Architecture - How packages work without builds
- Package Manager: pnpm + Workspaces - Workspace protocol, catalog dependencies
- Common pnpm Commands - Installation, adding dependencies, filtering
- Task Orchestration: Turborepo - How Turbo manages tasks and caching
- Common Development Tasks - Dev servers, type checking, deployment
- Infrastructure Scripts - reset-modules.sh
- Troubleshooting - Common errors and solutions
- Quick Reference - Command cheatsheet
- Key Principles - Essential guidelines
Just-in-Time (JIT) Package Architecture
Critical concept: This monorepo uses Just-in-Time packages - packages are consumed directly from TypeScript source files, not pre-built artifacts.
How It Works
Packages (packages/*):
- Export TypeScript source files directly (see
package.jsonexports:"./src/index.ts") - No build step required
- No
dist/orbuild/directories - Next.js apps transpile package sources on-demand during their own build
Apps (apps/*):
chatgpt-app- Built by Vercel on deploymentexpo-app- Built by EAS or Vercel (for web)storybook-chatgpt- Development-only, runs in dev mode for ChatGPT-specific component previews
Local Development:
- You never run
turbo run buildlocally for packages - Dev servers (
pnpm dev) consume source files directly - Type checking uses source files via TypeScript's project references
- Only apps build, and only when deployed to Vercel
Why JIT?
- Faster local development (no build step between package changes)
- Simpler workflow (edit code → see changes immediately)
- Single source of truth (TypeScript source files)
- Next.js handles all transpilation efficiently
What this means for you:
- ❌ Don't run
turbo run buildfor packages - ❌ Don't expect
dist/directories in packages - ✅ Run
pnpm devand start coding - ✅ Type check with
turbo run check-types - ✅ Let Vercel handle production builds
Package Manager: pnpm + Workspaces
Workspace Structure
The repository uses pnpm workspaces with catalog for shared dependencies:
# pnpm-workspace.yaml
packages:
- 'apps/*'
- 'packages/*'
- '!examples/**'
catalog:
react: ^19.1.1
next: ^15.5.5
zod: ^3.25.76
# ... other shared versionsWhy pnpm?
- Efficient disk usage via symlinks (shared
node_modules) - Strict dependency resolution (no phantom dependencies)
- Workspace protocol for internal packages
- Catalog for consistent versioning across packages
Workspace Protocol
Internal dependencies use workspace:* protocol:
{
"dependencies": {
"@chatgpt-artifacts/ui": "workspace:*",
"@chatgpt-artifacts/artifact-kit": "workspace:*"
}
}What this means:
workspace:*= "use latest version from workspace"- Resolved to
file:protocol duringpnpm install - Changes in one package immediately visible to dependents (via symlinks)
Catalog Dependencies
Shared dependencies use catalog: protocol:
{
"dependencies": {
"react": "catalog:",
"next": "catalog:",
"zod": "catalog:"
}
}What this means:
catalog:= "use version from pnpm-workspace.yaml catalog"- Ensures same version of React, Tailwind, Zod, etc. across all packages
- Update one place (catalog) to update everywhere
Common pnpm Commands
Installation
# Install all workspace dependencies
pnpm install
# Install in specific package
cd apps/chatgpt-app
pnpm installWhen to use:
- After
git pullifpnpm-lock.yamlchanged - After adding/removing dependencies
- Fresh checkout
Adding Dependencies
# Add to specific package (from root)
pnpm --filter chatgpt-app add lodash
# Add dev dependency
pnpm --filter artifact-kit add -D @types/node
# Add workspace dependency
pnpm --filter expo-app add @chatgpt-artifacts/ui
# Add catalog dependency (manually edit package.json)
# In packages/foo/package.json:
{
"dependencies": {
"react": "catalog:" // Add this line
}
}
# Then run: pnpm installPattern:
- Use
--filter <package-name>to target specific package - Package name =
namefield inpackage.json(not directory name) - Example:
chatgpt-app(notapps/chatgpt-app)
Filtering Packages
Both turbo and pnpm support filtering to target specific packages:
# Single package
turbo run check-types --filter chatgpt-app
# Multiple packages
turbo run lint --filter chatgpt-app --filter expo-app
# All apps
turbo run check-types --filter "./apps/*"
# Package + dependencies
pnpm --filter chatgpt-app... install
# Package + dependents
pnpm --filter ...chatgpt-app installWhen to use turbo vs pnpm:
turbo run- For tasks inturbo.json(check-types, lint)pnpm --filter- For dev servers and dependency management
Task Orchestration: Turborepo
What Turbo Does
Turbo orchestrates tasks across the monorepo (type checking, linting) with smart caching and dependency ordering.
Important: The build task exists for Vercel deployments only. Locally, you run check-types and lint.
{
"tasks": {
"build": {
"dependsOn": ["^build"],
"outputs": [".next/**", "!.next/cache/**"]
},
"lint": {
"dependsOn": ["^lint"]
},
"check-types": {
"dependsOn": ["^check-types"]
},
"dev": {
"cache": false,
"persistent": true
}
}
}Task Dependencies: ^lint and ^check-types
The ^ prefix means "run this task in dependencies first":
"check-types": {
"dependsOn": ["^check-types"]
}Example:
- You run:
turbo run check-types --filter chatgpt-app - Turbo sees
chatgpt-appdepends onartifact-kit,ui,artifact-service - Turbo type checks dependencies first
- Then type checks
chatgpt-app
Why?
- Dependencies must be type-safe before dependents can type check correctly
- Turbo ensures correct execution order automatically
Caching Behavior
Turbo caches task outputs to avoid redundant work:
# First type check: runs tsc in all packages
turbo run check-types
# Second type check (no changes): instant (uses cache)
turbo run check-typesCache key includes:
- File contents
- TypeScript configuration
- Task outputs from dependencies
When cache helps:
- Repeated type checks without code changes
- CI/CD pipelines
Note: Since packages use JIT, there's no build cache to worry about. Turbo only caches type check and lint results.
Common Development Tasks
Running Development Servers
# All apps in parallel (uses Turbo)
pnpm dev
# Specific app only
cd apps/chatgpt-app
pnpm dev
# Or from root:
pnpm --filter chatgpt-app devPreference:
- Use
cd apps/X && pnpm devfor single app development - Use
pnpm devfrom root when working across multiple packages
Type Checking & Linting
# Type check all packages
turbo run check-types
# Lint all packages
turbo run lint
# Both (recommended before committing)
turbo run check-types && turbo run lint
# Lint with auto-fix
pnpm --filter chatgpt-app run lint --fixUse filtering (see "Filtering Packages" section above) to target specific packages.
Deployment (Vercel Only)
Never run builds locally. Vercel handles builds automatically:
- Push to git → Vercel detects changes
- Vercel runs
turbo run build --filter <app> - Next.js transpiles JIT packages during build
- Deploys to production
If deployment fails: Check Vercel logs and run turbo run check-types && turbo run lint locally to catch issues before pushing.
Adding a New Package
- Create directory structure:
mkdir -p packages/new-package/src
cd packages/new-package- Initialize package.json:
{
"name": "@chatgpt-artifacts/new-package",
"version": "0.1.0",
"private": true,
"type": "module",
"exports": {
".": "./src/index.ts"
},
"scripts": {
"lint": "npx eslint .",
"check-types": "tsc --noEmit"
},
"dependencies": {
"zod": "catalog:"
},
"devDependencies": {
"@chatgpt-artifacts/typescript-config": "workspace:*",
"@chatgpt-artifacts/eslint-config": "workspace:*"
}
}- Add TypeScript config (tsconfig.json):
{
"extends": "@chatgpt-artifacts/typescript-config/base.json",
"include": ["src/**/*"],
"exclude": ["node_modules"]
}- Install dependencies:
pnpm install- Use in other packages:
pnpm --filter chatgpt-app add @chatgpt-artifacts/new-packageAdding a New App
Same as adding a package, but:
- Use
apps/directory instead ofpackages/ - Include framework-specific setup (Next.js, Vite, etc.)
- Add
devscript for development server
Infrastructure Scripts
reset-modules.sh
Deletes all node_modules directories, removes pnpm-lock.yaml, and reinstalls dependencies from scratch.
When to use:
- Phantom dependency issues (importing packages not in
package.json) - Corrupted
node_modulesafter failed install - Switching between Node versions
- Weird TypeScript errors that don't make sense
Usage:
pnpm run reset:modulesWarning: Slow operation (re-downloads all dependencies).
Troubleshooting
"Cannot find module '@chatgpt-artifacts/ui'"
Cause: Workspace dependency not installed.
Solution:
# Ensure all dependencies installed
pnpm installNote: No build needed - packages use JIT and consume source files directly.
Type errors in IDE won't go away
Cause: TypeScript language server cache issue.
Solution:
# Verify types are actually correct:
turbo run check-types
# If check-types passes but IDE still shows errors, inform the user
# that they may need to restart their TypeScript language server"The inferred type cannot be named without a reference..."
Cause: TypeScript can't resolve types from workspace dependency.
Solution:
# Verify the dependency is installed
pnpm install
# Type check to see actual errors
turbo run check-types --filter chatgpt-appNote: This is usually a TypeScript configuration issue, not a build issue.
pnpm install fails with ENOENT
Cause: Corrupted lockfile or node_modules.
Solution:
pnpm run reset:modulesDev server shows old code after package changes
Cause: Next.js dev server hasn't detected file changes.
Solution:
# Clear Next.js cache and ask user to restart dev server:
rm -rf apps/chatgpt-app/.next
# Then inform user to restart their dev serverNote: You cannot restart background processes. Inform the user that they need to restart the dev server after clearing the cache.
Quick Reference
Common Commands
| Task | Command |
|---|---|
| Install dependencies | pnpm install |
| Run dev (all apps) | pnpm dev |
| Run dev (one app) | pnpm --filter <app> dev |
| Type check & lint | turbo run check-types && turbo run lint |
| Add dependency | pnpm --filter <pkg> add <dep> |
| Clear cache | rm -rf apps/*/.next |
| Reset workspace | pnpm run reset:modules |
Common Scenarios
| Scenario | What to Do |
|---|---|
| Before committing | turbo run check-types && turbo run lint |
| Type errors persist | Run turbo run check-types, then inform user if passes |
| Dev server stale | Clear cache, inform user to restart server |
| Dependency issues | pnpm run reset:modules |
| Deployment failed | Check Vercel logs, run checks locally |
Key Principles
- Packages use JIT - No build step needed, consume TypeScript source directly
- Use workspace protocol (
workspace:*) for internal dependencies - Use catalog protocol (
catalog:) for shared external dependencies - Filter by package name, not directory path
- Type check before committing - Run
turbo run check-types && turbo run lint - Don't build locally - Let Vercel handle builds on deployment
- Clear Next.js cache when dev server shows stale code
- Reset modules as last resort for dependency issues
- Inform user when they need to take manual actions (restart servers, etc.)