Monorepos
Release each package independently — per-package tags, scoped changelogs, and scoped bump suggestions from one repo.
VerBump supports monorepos as a per-package release tool: you release one package at a time by running VerBump from inside that package's directory, and everything — the bump suggestion, the changelog, the release notes, the safety preflights — scopes to that package. Orchestration (which packages to release, when, in what order) stays with you, typically as scripts in your repo root.
VerBump deliberately does not do dependency-graph bumping, workspace version rewriting, or "release everything that changed" detection. If you need interdependent workspace packages versioned together, use changesets or release-please — they solve a bigger problem. VerBump's niche is making the single-package release correct, with no Node toolchain in the release environment.
Setup
Give each package its own .verbumprc with a distinct tag prefix:
TAG_PREFIX=pkg-a-vTAG_PREFIX=pkg-b-vThat's the whole setup. Each package now has:
- Its own tag series —
pkg-a-v1.2.3andpkg-b-v0.4.0never collide; every tag lookup is prefix-anchored. - Scoped commit analysis — the bump suggestion
and changelog only consider commits touching the
package's directory, so a
feat:inpkg-bcan't inflatepkg-a's bump or pollute its changelog. - A package-local
CHANGELOG.md— written in the directory you run from.
The scoping comes from the COMMIT_PATHS config key, which defaults to "."
resolved against the .verbumprc's own directory. A .verbumprc at the repo
root (or none at all) resolves to the whole repo — exactly today's
single-package behaviour.
TAG_PREFIX=pkg-a-v
# Space-separated git pathspecs, resolved against this file's directory.
# Quote multi-value keys — the rc is shell-sourced.
COMMIT_PATHS=". ../../shared"When a scope is active, VerBump prints it
(Package scope: commit analysis restricted to <packages/pkg-a>) so a run is
never silently scoped.
Releasing a package
Run VerBump from the package directory:
cd packages/pkg-a && verbump --minorMost teams wrap this in root-level scripts, which become the release entry points for the whole repo:
{
"scripts": {
"bump:pkg-a": "cd packages/pkg-a && verbump",
"bump:pkg-a:minor": "cd packages/pkg-a && verbump --minor",
"bump:pkg-b": "cd packages/pkg-b && verbump",
"release:pkg-a": "cd packages/pkg-a && verbump --release"
}
}What scopes, what doesn't
| Check / step | Under a package scope |
|---|---|
| Bump suggestion, changelog, PR body | Only commits touching COMMIT_PATHS. |
| Nothing-to-release gate | Only commits touching COMMIT_PATHS — releasing an unchanged package is a clean no-op even when siblings moved. |
| Dirty-tree preflight | Uncommitted changes inside the package still block. Staged changes anywhere in the repo still block (the bump commit would sweep them in). Unstaged edits in other packages no longer block. |
| Branch / remote-sync / tag-collision preflights | Repo-wide, unchanged — they're properties of the branch, not the package. |
GitHub releases in a monorepo
With --release, a scoped run doesn't use GitHub's
auto-generated notes (they can't be path-scoped and would list other
packages' PRs). Instead the release notes are the package's own changelog
entry in the grouped style — sections, commit links, and a compare link —
regardless of your CHANGELOG_STYLE, and even when you skip the changelog
write with -c. A custom VERBUMP_RELEASE_NOTES_CMD still overrides
everything.
Two GitHub-side caveats worth knowing:
- The "Latest release" badge is repo-global. Whichever package released
most recently wears it — inherent to monorepos on GitHub, not specific to
VerBump. Pass
--latest=falsetoghyourself if you care (via a custom notes flow), or just ignore the badge. - Release branches can collide.
--branch/--prname branchesrelease-<version>, so two packages at the same version would fight over the name. Set a per-packageREL_PREFIXif you use those workflows:
TAG_PREFIX=pkg-a-v
REL_PREFIX=release-pkg-a-Scripting: the release preview
--dry-run --json payloads gain an optional
scope member when a package scope is active, so orchestration loops can
tell packages' payloads apart:
{
"schema": "verbump.dry-run/v1",
"tag": "pkg-a-v1.3.0",
"scope": { "paths": ["packages/pkg-a"] }
}Whole-repo runs emit exactly the same payload as before — no scope member.
Adopting prefixes in an existing repo
Already have unprefixed v1.2.3-style tags? Nothing needs renaming:
-
Keep the root series as-is. Old tags are inert to the new per-package series — tag matching is prefix-anchored in both directions.
-
Baseline each package before its first prefixed run:
git tag pkg-a-v1.2.3 <commit-of-current-released-state>This gives the first real release a correct changelog range.
-
If you skip the baseline, the package's first changelog entry spans its full history — expected behaviour, not a bug.