A monorepo setup for publishing npm packages using pnpm workspaces, with TypeScript, tsup, ESLint, Prettier, and semantic-release for automated versioning and changelogs.
- pnpm Workspaces: Efficient monorepo management
- TypeScript: Type-safe development with shared configurations
- tsup: Fast TypeScript bundler with ESM and CJS output
- ESLint: Code linting with TypeScript support
- Prettier: Consistent code formatting
- EditorConfig: Editor consistency across team members
- Semantic Release: Automated versioning and changelog generation
- Hybrid Versioning Strategy:
- Each package manages its own minor and patch releases independently
- Major version bumps happen at the root level and affect all packages simultaneously
release-oppa/
βββ packages/ # All publishable packages go here
β βββ [package-name]/ # Individual package directory
β βββ src/ # Package source code
β βββ dist/ # Built output (gitignored)
β βββ package.json # Package configuration
β βββ tsconfig.json # Package TypeScript config
β βββ tsup.config.ts # Package build config
β βββ .releaserc.json # Package release config
β βββ CHANGELOG.md # Package-specific changelog (auto-generated)
β βββ README.md # Package documentation
βββ scripts/ # Utility scripts
β βββ create-package.ts # Create new packages
β βββ bump-major.ts # Bump major versions
βββ .editorconfig # Editor configuration
βββ eslint.config.js # ESLint flat config (ESM)
βββ .prettierrc # Prettier configuration
βββ .releaserc.json # Root semantic-release configuration
βββ tsconfig.base.json # Base TypeScript configuration
βββ tsconfig.json # Root TypeScript configuration
βββ tsup.config.ts # Shared tsup configuration
βββ pnpm-workspace.yaml # pnpm workspace configuration
βββ package.json # Root package.json with scripts
Note: Each package maintains its own CHANGELOG.md file, which is automatically generated and updated by semantic-release on each release.
- Node.js >= 18.0.0
- pnpm >= 8.0.0
# Install pnpm if you haven't already
npm install -g pnpm
# Install dependencies
pnpm installpnpm build- Build all packagespnpm lint- Lint all packagespnpm lint:fix- Fix linting issuespnpm format- Format code with Prettierpnpm format:check- Check code formattingpnpm typecheck- Type-check all packagespnpm clean- Clean build artifactspnpm create-package <name>- Create a new package with boilerplatepnpm bump-major- Bump major version for ALL packages (for breaking changes)pnpm release- Manually trigger release for all packages (mainly for local testing)
Use the automated script to create a new package:
pnpm create-package my-packageThis will create a complete package structure with:
package.jsonwith proper configurationtsconfig.jsonextending base configtsup.config.tsfor building.releaserc.jsonfor semantic-releasesrc/index.tswith example codeREADME.mdwith basic documentation
To manually create a new package in the monorepo:
- Create a new directory in
packages/:
mkdir -p packages/my-package/src- Create a
package.json:
{
"name": "@release-oppa/my-package",
"version": "1.0.0",
"description": "Description of my package",
"type": "module",
"main": "./dist/index.cjs",
"module": "./dist/index.js",
"types": "./dist/index.d.ts",
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.js",
"require": "./dist/index.cjs"
}
},
"files": ["dist"],
"scripts": {
"build": "tsup",
"typecheck": "tsc --noEmit",
"clean": "rm -rf dist"
},
"keywords": [],
"author": "",
"license": "MIT"
}- Create a
tsconfig.json:
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"rootDir": "./src",
"outDir": "./dist"
},
"include": ["src"]
}- Create a
tsup.config.ts:
import { createTsupConfig } from '../../tsup.config';
export default createTsupConfig();- Create
.releaserc.jsonfor the package:
{
"branches": ["main", "next"],
"plugins": [
[
"@semantic-release/commit-analyzer",
{
"preset": "conventionalcommits",
"releaseRules": [
{ "breaking": true, "release": false },
{ "type": "feat", "release": "minor" },
{ "type": "fix", "release": "patch" },
{ "type": "perf", "release": "patch" }
]
}
],
[
"@semantic-release/release-notes-generator",
{
"preset": "conventionalcommits"
}
],
[
"@semantic-release/changelog",
{
"changelogFile": "CHANGELOG.md"
}
],
"@semantic-release/npm",
[
"@semantic-release/git",
{
"assets": ["package.json", "CHANGELOG.md"],
"message": "chore(release): ${nextRelease.gitTag} [skip ci]\n\n${nextRelease.notes}"
}
],
"@semantic-release/github"
],
"tagFormat": "${version}"
}Note: The changelogFile is set to CHANGELOG.md, which will be created in the package's directory.
6. Create your source code in `src/index.ts`
7. Build and test:
```bash
cd packages/my-package
pnpm build
This monorepo uses a hybrid versioning strategy with semantic-release:
- Minor and Patch releases: Each package manages these independently based on its own commits
- Major releases: Handled at the root level and applied to ALL packages simultaneously
Follow Conventional Commits:
feat:- A new feature (minor version bump for the affected package)fix:- A bug fix (patch version bump for the affected package)perf:- Performance improvement (patch version bump)docs:- Documentation changes (no version bump)style:- Code style changes (no version bump)refactor:- Code refactoring (no version bump)test:- Adding or updating tests (no version bump)chore:- Other changes (no version bump)
Important: Commits with BREAKING CHANGE: or ! suffix (e.g., feat!:) will NOT trigger automatic releases. Breaking changes must be managed manually using the major version bump script.
When you push commits to main branch:
- CI/CD analyzes commits for each package
- Determines if minor or patch release is needed
- Generates changelog for the package
- Publishes the package to npm
- Creates GitHub release
Each package releases independently based on its own changes.
For breaking changes affecting the entire monorepo:
-
Run the major bump script from the root:
pnpm bump-major
This will:
- Increment major version for ALL packages (e.g., 1.2.3 β 2.0.0)
- Update all CHANGELOG.md files
- Show you the changes to review
-
Review and commit:
git diff # Review all changes git add . git commit -m "chore: bump major version for all packages BREAKING CHANGE: <describe breaking changes>"
-
Push to trigger release:
git push origin main
-
Document breaking changes: Update release notes with migration guides
Scenario 1: Adding a new feature to one package
# In packages/example
git commit -m "feat(example): add new greeting function"
git push
# Result: @release-oppa/example goes from 1.0.0 β 1.1.0Scenario 2: Bug fix in multiple packages
git commit -m "fix(example): correct typo
fix(other): fix edge case"
git push
# Result: Both packages get patch bumps independentlyScenario 3: Breaking change across all packages
# From root
pnpm bump-major
# Review changes
git add .
git commit -m "chore: bump major version for all packages
BREAKING CHANGE: Updated API signature across all packages"
git push
# Result: ALL packages go from 1.x.x β 2.0.0Each package maintains its own CHANGELOG.md file:
- Automatic Generation: Changelogs are automatically generated by semantic-release based on conventional commits
- Per-Package: Each package has its own changelog tracking only its changes
- Format: Follows Keep a Changelog format
- Updates:
- For minor/patch releases: Changelog is updated automatically on each release
- For major releases: The
bump-majorscript adds a breaking change entry to all package changelogs
Example changelog location: packages/example/CHANGELOG.md
The changelog will include:
- Version number and release date
- Grouped changes by type (Features, Bug Fixes, etc.)
- Commit messages and links to commits
- Links to compare versions
This repository includes GitHub Actions workflows:
- CI Workflow (
.github/workflows/ci.yml): Runs on pull requests and pushes- Code formatting check
- Linting
- Type checking
- Build verification
- Release Workflow (
.github/workflows/release.yml): Runs on main/next branch- All CI checks
- Per-package semantic-release analysis
- Independent minor/patch version bumps
- Package publishing to npm
- GitHub release creation for each updated package
To enable automated releases:
- Add npm token: Create an npm token and add it as
NPM_TOKENsecret in GitHub repository settings - GitHub token: The
GITHUB_TOKENis automatically provided by GitHub Actions - Branch protection: Configure
mainbranch to require CI checks before merging - For major releases: Use
pnpm bump-majorlocally and commit the changes
- Create a new branch
- Make your changes
- Follow the commit convention
- Submit a pull request
MIT