Changesets for versioning
When you change packages/ui, how do you know if it's a patch (0.0.1ā0.0.2), minor (0.1.0ā0.2.0), or major (1.0.0ā2.0.0) change? Manual versioning leads to mistakes: forgetting to bump versions, inconsistent changelogs, breaking changes without warning. Changesets automates semantic versioning and generates changelogs from your commits.
Outcome
Set up Changesets to manage package versions and changelogs automatically.
Fast track
- Install @changesets/cli
- Initialize changesets configuration
- Create first changeset
- Version and generate changelog
Hands-on exercise 9.2
1. Install dependencies
pnpm add -D @changesets/cli2. Initialize Changesets
pnpm changeset initOutput:
š¦ Thanks for choosing changesets to version your packages!
š¦ Generating .changeset folder and config
This creates:
.changeset/config.json- configuration.changeset/README.md- usage docs
3. Configure Changesets
Edit .changeset/config.json:
{
// TODO: Configure the following fields:
// - "$schema": "https://unpkg.com/@changesets/config@3.0.0/schema.json"
// - "changelog": "@changesets/cli/changelog"
// - "commit": false
// - "fixed": []
// - "linked": []
// - "access": "public"
// - "baseBranch": "main"
// - "updateInternalDependencies": "patch"
// - "ignore": []
}Solution
{
"$schema": "https://unpkg.com/@changesets/config@3.0.0/schema.json",
"changelog": "@changesets/cli/changelog",
"commit": false,
"fixed": [],
"linked": [],
"access": "public",
"baseBranch": "main",
"updateInternalDependencies": "patch",
"ignore": []
}Create first changeset
1. Make a change to UI package
Edit packages/ui/src/button.tsx:
export function Button({ children, variant = 'primary', onClick }: ButtonProps) {
const variants = {
primary: 'bg-blue-500 text-white hover:bg-blue-600',
secondary: 'bg-gray-200 text-gray-800 hover:bg-gray-300',
danger: 'bg-red-500 text-white hover:bg-red-600', // New variant!
}
return (
<button onClick={onClick} className={`${variants[variant]} px-4 py-2 rounded`}>
{children}
</button>
)
}Update types:
interface ButtonProps {
variant?: 'primary' | 'secondary' | 'danger' // Add 'danger'
onClick?: () => void
children: React.ReactNode
}2. Create changeset
pnpm changesetInteractive prompts:
š¦ What kind of change is this for @geniusgarage/ui?
⯠major (breaking change)
minor (new feature)
patch (bug fix)
Select minor (new feature - added danger variant).
š¦ Please enter a summary for this change:
⯠Add danger variant to Button component
Output:
š¦ Changeset created!
š¦ Generated changeset file: .changeset/cool-tigers-march.md
3. View changeset file
---
'@geniusgarage/ui': minor
---
Add danger variant to Button componentThis file tracks:
- Which package changed (
@geniusgarage/ui) - What type of change (
minor) - What changed (summary)
Commit this file with your code!
Version and changelog
1. Apply Changesets
pnpm changeset versionOutput:
š¦ All files have been updated. You're ready to publish!
This command:
- Updates package.json versions based on changesets
- Generates CHANGELOG.md with changes
- Deletes changeset files (they're applied)
2. Check updated versions
View packages/ui/package.json:
{
"name": "@geniusgarage/ui",
"version": "0.2.0", // Was 0.1.0, now 0.2.0 (minor bump)
"main": "./src/index.ts"
}View generated packages/ui/CHANGELOG.md:
# @geniusgarage/ui
## 0.2.0
### Minor changes
- abc123f: Add danger variant to Button componentAutomatic changelog from your changeset!
3. Commit version changes
git add -A
git commit -m "chore: version packages"
git pushTry it
1. Create multiple Changesets
Make a bug fix in utils:
export function formatDate(date: Date): string {
return new Intl.DateTimeFormat('en-US', {
dateStyle: 'medium', // Was 'long', fix to 'medium'
}).format(date)
}Create changeset:
pnpm changesetSelect patch (bug fix), summary: "Fix date formatting style".
Make a breaking change in typescript-config:
{
"compilerOptions": {
"strict": true,
"noUncheckedIndexedAccess": true // New strict rule (breaking)
}
}Create changeset:
pnpm changesetSelect major (breaking change), summary: "Add strict index access checking".
2. Version all packages
pnpm changeset versionOutput:
š¦ @geniusgarage/ui: 0.2.0 ā 0.2.0 (no change)
š¦ @geniusgarage/utils: 0.1.0 ā 0.1.1 (patch)
š¦ @geniusgarage/typescript-config: 0.1.0 ā 1.0.0 (major)
Check packages/typescript-config/CHANGELOG.md:
# @geniusgarage/typescript-config
## 1.0.0
### Major changes
- def456g: Add strict index access checking
BREAKING CHANGE: Enables noUncheckedIndexedAccess, may cause type errors in consuming packages.Automatic versioning and changelog generation for all packages!
3. Verify dependent apps bump versions
Check apps/web/package.json:
{
"name": "@geniusgarage/web",
"dependencies": {
"@geniusgarage/ui": "workspace:*",
"@geniusgarage/typescript-config": "workspace:*" // Still uses workspace protocol
}
}Workspace protocol means "always use local version" - no manual updates needed!
Semantic versioning rules
Patch (0.0.x):
- Bug fixes
- Performance improvements
- Internal refactoring
- Documentation updates
Minor (0.x.0):
- New features
- New components
- New optional parameters
- Deprecation warnings
Major (x.0.0):
- Breaking API changes
- Removed features
- Required parameter changes
- Major dependency upgrades
Publishing workflow (optional)
For teams publishing to npm, add publish script:
1. Add publish command
{
"scripts": {
"changeset": "changeset",
"version": "changeset version",
"publish-packages": "changeset publish"
}
}2. Publish to npm
# Build all packages
pnpm build
# Publish changed packages
pnpm publish-packagesChangesets publishes only packages with version bumps to npm!
3. GitHub Actions workflow
name: Release
on:
push:
branches:
- main
jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: pnpm/action-setup@v2
with:
version: 8
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
- run: pnpm install --frozen-lockfile
- run: pnpm build
- name: Create Release Pull Request
uses: changesets/action@v1
with:
version: pnpm changeset version
publish: pnpm publish-packages
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}This workflow:
- Detects changesets in commits
- Creates PR with version bumps and changelogs
- Publishes packages when PR merges
Benefits
Before Changesets:
- Manually edit package.json versions
- Forget which packages changed
- Write changelogs by hand
- Miss dependent version updates
With Changesets:
pnpm changesetcreates version intentpnpm changeset versionupdates everything- Automatic CHANGELOG.md generation
- Semantic versioning enforced
For 10-person team:
- 20+ packages versioned correctly
- Automated changelog for every release
- No more "forgot to bump version" bugs
Changeset best practices
Write clear summaries
# ā bad
pnpm changeset
# Summary: "updates"
# ā
good
pnpm changeset
# Summary: "add loading state to button component"Group related changes
Make multiple changes, create one changeset:
# Change button, card, and modal
git add .
# Create single changeset for all changes
pnpm changesetUse conventional commit style
---
'@geniusgarage/ui': minor
---
feat(button): add loading state with spinner
- Adds isLoading prop to Button
- Shows spinner icon when loading
- Disables click events during loadDone-when
Verify Changesets work:
- Installed @changesets/cli
- Ran
pnpm changeset init - Configured .changeset/config.json
- Made change to ui package
- Created changeset with
pnpm changeset - Selected minor version bump
- Ran
pnpm changeset version - Verified package.json version bumped (0.1.0 ā 0.2.0)
- Saw CHANGELOG.md generated automatically
- Created multiple changesets (patch, minor, major)
- Understood semantic versioning rules
- (Optional) Configured GitHub Actions workflow
What's Next
Changesets manage versions, but how do you enforce code boundaries? Final lesson: Code Governance - learn to use CODEOWNERS for team ownership and ESLint boundaries to prevent unauthorized cross-package imports.
Was this helpful?