Pragmatic template for a
TypeScriptmonorepo withpnpm,turborepo, andvitest.
Tested with:
- Node.js v24.13.0
- pnpm v10.28.2
- vitest v4.0.18
- What's Included
- Available Scripts
- Publishing (OIDC)
- Test Structure
- FAQ
- Author
- Show your support
- License
pnpmworkspace, whose configuration is stored inpnpm-workspace.yaml. Two example packages are included,common-utilsandexample, with the latter importingcommon-utilsas a dependency. All local packages are decorated with a@jkomyno/*scope (you may want to substitute these instances in thenameentries of anypackage.jsonwith yours or your company's name).tsdownbundler, whose configuration is stored intsdown.config.base.ts.turborepo, whose configuration is stored inturbo.json- an example
Dockerfilethat can be built and used as a base image for your Node.js Docker containers. - the
vitesttest engine, whose configuration is stored invitest.config.ts. - opinionated linting setups via
biome, whose configuration is defined in thebiome.jsoncfile. - Changesets for versioning and changelogs; the Release workflow opens a "Version Packages" PR when changesets land on
main, and publishes to npm when that PR is merged using npm trusted publishing (OIDC)—no long-lived tokens. See Publishing (OIDC) below. - pkg.pr.new for continuous preview releases: each PR gets installable preview packages (install the GitHub App on the repo first).
pnpm install: install the dependencies needed for each package.pnpm build: typecheck and transpile the local TypeScript packages to JavaScript.pnpm build:watch: transpile the local TypeScript packages to JavaScript, and watch for changes.pnpm check:exports: check that theexportsfield in thepackage.jsonfiles of each exported package is correctly set, using@arethetypeswrong/cli.pnpm typecheck: run type checks for packages that define atypecheckscript.pnpm lint:ci: check that the code follows thebiomeguidelines.pnpm lint: check that the code follows thebiomeguidelines, and override it to follow them if possible.pnpm lint:fix: same aspnpm lint; included as a clearer alias.pnpm test:unit: run unit tests.pnpm test:integration: run integration tests.pnpm test: run all tests.pnpm bench: run benchmarks for packages that define abenchscript.pnpm bench:watch: run benchmarks in watch mode for packages that definebench:watch.pnpm changeset: add a new changeset (version bump + changelog entry).pnpm version-packages: apply changesets (bump versions, update changelogs, thenpnpm install). Used by the Release workflow.
Releases use npm trusted publishing (OIDC), so you do not need an NPM_TOKEN secret. You configure npm to trust this repo's workflow once per package:
Template note: the release workflow is scaffolded but disabled by default in
release.yaml. After you configure npm trusted publishing (OIDC), removeif: falseand uncomment the Changesets step in that file.
- Publish the package manually once (if it has never been published), so it exists on npm.
- On npmjs.com, open the package → Package settings → Trusted Publisher.
- Choose GitHub Actions and set:
- Organization or user: your GitHub org or username (e.g.
jkomyno) - Repository: this repo name (e.g.
pnpm-monorepo-template) - Workflow filename:
release.yaml(must match exactly, including extension)
- Organization or user: your GitHub org or username (e.g.
- Save. Future publishes from the Release workflow will use OIDC; no tokens required.
Repeat for each publishable package in the monorepo. Optional: under Publishing access, enable “Require two-factor authentication and disallow tokens” so only the trusted workflow can publish.
We follow an opinionated convention for storing and running tests.
All tests should be written in the __tests__ directory of a local package.
Moreover, unit tests should be placed in the __tests__/unit folder; similarly, integration tests should be placed in the __tests__/integration folder.
This allows for easily running groups of tests (for instance, you might want to run unit tests locally, while deferring integration tests - that will probably need access to external services like Docker containers - to the CI only).
- How do I add a new package to the local workspace?
- Create a new folder
$packageNameinpackages/. Initialize it with atsconfig.jsonfile (which will reference thetsconfig.base.node.jsonfile at the root level) and apackage.jsonfile similarly to how it's done in thecommon-utilspackage.
- How do I add a new dependency that should be available to each package in the local workspace?
pnpm add -w $dependencyName
Hi, I'm Alberto Schiabel, you can follow me on:
Give a ⭐️ if this project helped or inspired you!
Built with ❤️ by Alberto Schiabel.
This project is MIT licensed.