A generic TypeScript monorepo starter with pnpm workspaces, Turborepo, Changesets, and shared tooling. Use this as a base when starting a new monorepo project.
- Package manager: pnpm (workspaces + catalog)
- Build orchestration: Turborepo
- Releases: Changesets (versioning + changelog + publish)
- Linting: ESLint per package — each package has its own
eslint.config.mjsextending shared@repo/eslint-config; root config only lints repo-root files - Testing: Vitest
- TypeScript: Shared
@repo/typescript-config(base, nextjs, react-library, node-library) - Git hooks: Husky + lint-staged + Commitlint (conventional commits)
- CI: GitHub Actions (lint, typecheck, test, build; optional Codecov; Changesets release workflow)
- Node.js ≥ 20.12
- pnpm ≥ 10.16
# Install dependencies
pnpm install
# Run checks (typecheck, lint, test)
pnpm check
# Build all packages
pnpm build
# Run tests
pnpm test.
├── apps/ # Applications (Next.js, etc.) — add as needed
├── packages/ # Shared libraries and packages
│ └── example/ # Placeholder internal package (replace or remove)
├── tooling/ # Shared configs (consumed by packages)
│ ├── eslint-config/ # @repo/eslint-config
│ └── typescript-config/ # @repo/typescript-config
├── turbo/
│ └── generators/ # Turbo codegen (pnpm gen)
├── .changeset/ # Changeset config and pending changes
├── .github/workflows/ # CI and release workflows
└── .husky/ # Git hooks
ESLint is configured per package: each package has its own eslint.config.mjs that extends @repo/eslint-config. The root eslint.config.mjs only lints repo-root files (e.g. turbo/, config files); it ignores packages/ and apps/. When you run pnpm run lint or pnpm run lint:fix, Turbo runs the lint script in each package using that package’s config. New packages from pnpm gen get an eslint.config.mjs template.
Use Turbo generators (recommended):
# Internal package (src only, not published)
pnpm gen
# Choose "pkg-basic", set name, path, scope (e.g. myorg), description
# Publishable package (builds to dist/, publishable to npm)
pnpm gen
# Choose "pkg-publishable", set name, path, scope, descriptionOr copy packages/example and adjust name, scope, and code.
Flow: Add a changeset → commit it with your PR → merge to main → Release workflow opens a "Version Packages" PR → merge that PR → packages are published to npm.
Best practices:
- One release branch: Release workflow runs only on
main(single source of truth for versions). - One changeset per logical change: Run
pnpm changeset, pick affected packages and bump type (patch/minor/major), write a short summary. - Commit the changeset in the same PR as the code change.
- Publish: Requires
NPM_TOKENsecret in the repo. Publishable packages useprepublishOnly: "pnpm build"so they build before publish.
Full details and config options: .changeset/README.md.
| Script | Description |
|---|---|
pnpm build |
Build all packages |
pnpm dev |
Run dev in all packages (persistent) |
pnpm lint / pnpm lint:fix |
Lint / fix |
pnpm typecheck |
Type-check all packages |
pnpm test |
Run tests |
pnpm test:coverage |
Tests with coverage |
pnpm clean |
Remove build artifacts |
pnpm changeset |
Add a changeset |
pnpm version-packages |
Apply changesets (version bump) |
pnpm release |
Publish to npm (run after version-packages in CI) |
pnpm check |
typecheck + lint + test |
pnpm validate |
check + commitlint on last commit |
pnpm gen |
Run Turbo generators (new packages) |
pnpm ci |
typecheck, lint, test, build (CI pipeline) |
- pre-commit: Block direct commits to
main/master; lint-staged runs ESLint on staged files - commit-msg: Commitlint enforces conventional commits (e.g.
feat(scope): message) - pre-push: typecheck + lint (tests can be enabled in
.husky/pre-push)
- Rename repo: Change
namein rootpackage.jsonand update README. - Scope: New packages default to scope
myorgin generators; change inturbo/generators/config.tsor when runningpnpm gen. - Branches: CI runs on
mainanddevelop; Release (Changesets) runs onmainonly. Adjust in.github/workflows/*and.changeset/config.json. - Commitlint: Optional scope is allowed; tighten in
commitlint.config.cjsif you want required scopes. - Tooling: Extend
tooling/eslint-configandtooling/typescript-configfor your rules and tsconfig variants.
MIT (or your choice)