This is a TypeScript monorepo starter template designed to be cloned and customized by developers.
A modern TypeScript monorepo using pnpm workspaces, Turborepo for build orchestration, Biome for linting/formatting, Vitest for testing, and Changesets for versioning. By default, all packages are private to prevent accidental publishing when users clone the template.
Tech Stack:
- Package manager: pnpm 8.15.4
- Build system: Turborepo
- Linter/Formatter: Biome
- Testing: Vitest (run from root)
- CI/CD: GitHub Actions (separate CI and Release workflows)
- Versioning: Changesets
- Node.js: >= 22 (LTS)
packages/
├── core/ # Core package with tests
├── utils/ # Utilities (depends on core)
└── feature-a/ # Feature package (depends on core and utils)
Each package has its own package.json, tsconfig.json, and src/ directory. All packages are currently marked as "private": true.
# Install dependencies (required first step)
pnpm install
# Build all packages (required before typecheck)
pnpm build
# Lint all packages
pnpm lint
# Type check all packages (must run AFTER build)
pnpm typecheck
# Run all tests (runs from root using centralized vitest config)
pnpm test
# Watch mode for tests
pnpm test:watch
# Clean all build outputs
pnpm clean# Build a specific package
pnpm -F "@monorepo/core" build
# Watch mode for development
pnpm -F "@monorepo/core" dev
# Run tests for a specific package (note: tests run from root)
cd packages/core && pnpm test:watchPackages with dependencies must be built in order. Turborepo handles this automatically, but be aware:
@monorepo/corehas no dependencies (builds first)@monorepo/utilsdepends on@monorepo/core@monorepo/feature-adepends on both@monorepo/coreand@monorepo/utils
Critical: Always run pnpm build before pnpm typecheck because TypeScript needs the built .d.ts files from dependencies.
Tests use Vitest with a centralized configuration at the root (vitest.config.ts).
- Test files:
**/*.test.tsor**/*.spec.tsinpackages/*/src/ - Run from root:
pnpm test(NOTturbo test) - The test script in root package.json runs
vitest run, notturbo test - Only
@monorepo/corecurrently has tests (packages/core/src/index.test.ts)
When adding tests:
- Place test files next to source files:
src/myModule.test.ts - Import from the source file:
import { MyClass } from './myModule' - Run
pnpm testfrom root to verify
- TypeScript strict mode enabled
- Biome handles linting and formatting (config in
biome.json) - Run
pnpm lintto check,pnpm formatto auto-fix - Conventional Commits enforced via Commitlint
- Husky hooks run typecheck on pre-commit
Commit message format:
type(scope): description
# Examples:
feat(core): add new authentication method
fix(utils): resolve import path issue
chore: update dependencies
- Triggers: Push to main OR Pull Request to main
- Node version: 22.x (LTS)
- Steps: Install → Lint → Build → Typecheck → Test
- Important: Build runs BEFORE typecheck (required for type definitions)
- Triggers: After CI succeeds on main branch (workflow_run)
- Uses Changesets to version and publish packages
- By default, does nothing because all packages are private
- To enable publishing: Set
"private": falsein package.json and addNPM_TOKENsecret
- Create directory:
packages/new-package/ - Add
package.json:{ "name": "@monorepo/new-package", "version": "0.1.0", "private": true, "main": "./dist/index.js", "types": "./dist/index.d.ts", "scripts": { "build": "tsc", "typecheck": "tsc --noEmit", "lint": "biome lint ./src" } } - Add
tsconfig.jsonextending from root - Create
src/index.ts - Add to
vitest.config.tsalias if needed - Run
pnpm installto link workspace packages
Use workspace protocol for internal dependencies:
"dependencies": {
"@monorepo/core": "workspace:*"
}Turborepo automatically builds dependencies in the correct order.
- Make changes to packages
- Run
pnpm changesetto document changes - Select packages that changed and semantic version bump
- Commit the changeset file
- On merge to main, Changesets creates a version PR
- Merge version PR to publish (only if packages are not private)
Template ships with all packages private by default.
To publish a package:
- In
packages/your-package/package.json:- Change
"private": trueto"private": false - Add
"publishConfig": { "access": "public" }
- Change
- Ensure unique package name or use scoped name
- Add
NPM_TOKENsecret in GitHub repo settings - Release workflow will publish on next version bump
pnpm -r <command> # Run in all packages
turbo <command> # Run via Turborepo with cachingpnpm -F "@monorepo/core" build
pnpm --filter "@monorepo/*" testpnpm list --depth 0 # See all workspace packagesTypeScript errors about missing types:
- Run
pnpm buildfirst to generate.d.tsfiles - Check that dependencies are listed in package.json
- Verify workspace links:
pnpm install
Tests not found:
- Tests must match
packages/**/*.{test,spec}.tspattern - Run from root:
pnpm test(NOT from package directory with turbo)
Build failures:
- Check dependency order (core → utils → feature-a)
- Clear cache:
pnpm cleanthenpnpm build - Remove node_modules:
pnpm clean && pnpm install
CI failures on typecheck:
- Ensure build step runs before typecheck in workflow
- Local:
pnpm build && pnpm typecheck
- Never commit
.envfiles or secrets - NPM_TOKEN is stored as GitHub secret (not in code)
- All packages default to private to prevent accidental publishing
- Review dependencies regularly:
pnpm audit
This template follows Semantic Versioning 2.0.0 at the repository level, not per-package.
Git tags use the format vMAJOR.MINOR.PATCH (e.g., v1.0.0, v1.1.0):
-
MAJOR: Breaking changes to template structure, tooling, or workflows
- Example: Changing package manager, build system, or major workflow restructuring
- Users must adapt their code when upgrading
-
MINOR: New features, packages, or improvements (backward compatible)
- Example: Adding new example packages, new GitHub Actions workflows, new tooling
- Users can adopt new features without breaking changes
-
PATCH: Bug fixes, documentation updates, dependency patches
- Example: Fixing CI configuration, updating README, security patches
- Safe to update without any code changes
Individual packages remain at 0.1.0 by default:
- Packages are private by default (
"private": true) - Users customize package names and versions after cloning
- Version according to Semantic Versioning once users start developing
Use pre-release tags for experimental features:
v2.0.0-alpha.1- Early testing, unstablev2.0.0-beta.1- Feature complete, testing phasev2.0.0-rc.1- Release candidate, final testing
When using this template:
- Update package names in all
package.jsonfiles (change@monorepo/*scope) - Update repository URL in root
package.json - Update LICENSE file with your information
- Customize README.md for your project
- Decide which packages should be public vs private
- Update GitHub secrets if publishing to npm
- Start versioning your packages from
0.1.0according to your development needs