Releasing & Publishing
The full release cycle: bump a version through a PR/MR, tag it, create a release, and publish to npm — as four separate, composable steps. Bumping a batch of packages and then publishing or releasing only some of them are different decisions that don't always happen at the same time, so they're never forced together. Works against GitHub or GitLab, autodetected per repo — see Config & Discovery.
bump
Pick packages, bump their version (patch by default), open a PR/MR, merge it into the default branch, and tag the release.
polyrepo bump [options]- Shows a checkbox of packages with their current version and the version they'd bump to (
1.2.9 → 1.2.10for a patch bump —--minor/--majorbump that part instead, resetting the parts below it to0, same as any semver tool). Packages with a dirty working tree are marked — they'll be skipped. The highlighted package's description shows what's actually changed since the last git tag — computed for every package in parallel. - After confirming, for each selected package, one at a time:
git fetch origin→git checkout <default branch>→git merge --ff-only origin/<default branch>(the bump branch is always created from an up-to-date default branch, not whatever branch the repo happened to be on);- checks the state of a previous attempt — is there already a merged PR/MR, an open one, or just a pushed branch named
<new-version>-version-bump. Depending on what's found, it resumes from the right place instead of failing on "branch already exists" or opening a duplicate one; - if
package.jsonisn't bumped yet on the branch, its"version"field is updated (a text-level replace — formatting and field order are left alone). If the package already has aCHANGELOG.md, a draft## [x.y.z] - YYYY-MM-DDentry (Keep a Changelog style) is added too, listing commits since the last tag — a starting draft to review, not a finished changelog; gh pr create(GitHub) orglab mr create(GitLab) against the default branch (if there isn't one already);- with
--wait-checks: waits for CI before merging — see the option below, the exact mechanism differs by host; no checks configured isn't an error; gh pr merge/glab mr merge— through a PR/MR, not a direct push;- local default branch is synced to the just-merged PR/MR;
- git tag
v<new-version>is created and pushed if it doesn't already exist.
- Any failure along the way marks that package ✗ with a clear message and moves on to the next one, without aborting the whole run.
- Once every selected package is processed, a separate check across all packages (not just the ones just bumped) reports any local package whose dependency range no longer matches a bumped package's new version — nothing is changed automatically.
Example output — a --dry-run, nothing exists yet:
[1/1] os-detect 2.1.5 → 2.1.6
$ git fetch origin
$ git checkout master
Your branch is up to date with 'origin/master'.
Already on 'master'
$ git merge --ff-only origin/master
Already up to date.
✓ master is up to date.
[dry-run] would create branch 2.1.6-version-bump, ensure version 2.1.6 (+ a CHANGELOG.md entry if one exists), commit/push if needed, then open + merge a PR, then tag v2.1.6.Re-running bump on a package where a previous attempt already went all the way through is much shorter — everything already done is just confirmed, not redone:
[1/1] vue-toast-kit 1.0.7 → 1.0.8
✓ master is up to date.
✓ Already merged as PR #12 — master already has it.
✓ Tag v1.0.8 already exists on origin.(On a GitLab repo, this reads "Already merged as PR #12" the same way — the provider layer always calls it "PR" in these messages regardless of host, the underlying object is a merge request.)
--prerelease, or --preid paired with --major/--minor, compute the target version differently but go through the exact same branch → PR → merge → tag steps:
[1/1] os-detect 2.1.5 → 2.1.6-beta.0
$ git fetch origin
$ git checkout master
Your branch is up to date with 'origin/master'.
Already on 'master'
$ git merge --ff-only origin/master
Already up to date.
✓ master is up to date.
[dry-run] would create branch 2.1.6-beta.0-version-bump, ensure version 2.1.6-beta.0 (+ a CHANGELOG.md entry if one exists), commit/push if needed, then open + merge a PR, then tag v2.1.6-beta.0.Options
--dry-run
Prints the plan for each package, changes and pushes nothing — including the CHANGELOG.md entry, CI wait, and git tag.
--minor
Bump the minor version instead of patch (e.g. 1.2.9 → 1.3.0).
--major
Bump the major version instead of patch (e.g. 1.2.9 → 2.0.0).
--prerelease
Bump (or start) a prerelease instead of patch/minor/major (e.g. 1.2.9 → 1.2.10-alpha.0, or 1.2.10-alpha.0 → 1.2.10-alpha.1). Combine with --preid to name it — node-semver decides whether that means adding e.g. -alpha.0 to the current version or advancing an existing prerelease's number.
--preid <name>
Prerelease identifier for --prerelease, or paired with --minor/--major for a preminor/premajor prerelease instead (e.g. --major --preid beta on 1.2.9 → 2.0.0-beta.0). Defaults to alpha.
--custom-version <version>
Set an exact version instead of computing one. Only valid together with --packages naming a single package — applying one literal version to several packages at once is never actually what you want.
--packages <a,b,c>
Comma-separated package dir names instead of the interactive checkbox — for scripts. Unknown names are printed as a warning and skipped.
--yes
Skip the "proceed?" confirmation.
--wait-checks
Wait for CI before merging; don't merge if it fails. On GitHub this is gh pr checks --watch, scoped to the PR. GitLab has no equivalent scoped to an MR — this uses glab ci status --branch --wait on the bump branch's pipeline instead, same idea. No checks configured isn't an error either way — it just doesn't wait.
Example:
# dry run first — nothing is pushed, committed, merged, or tagged
polyrepo bump --dry-run
# bump or advance a beta prerelease
polyrepo bump --prerelease --preid beta
# start a premajor release candidate (e.g. 2.0.0-rc.0)
polyrepo bump --major --preid rc
# set an exact version for one package, e.g. for a hotfix
polyrepo bump --packages os-detect --custom-version 2.1.6-hotfix.1
# fully non-interactive, for a script/CI
polyrepo bump --packages vue-toast-kit,os-detect --yestag
For a package whose version was already bumped some other way (not through bump, or before it started tagging), release has nothing to work with — there's no tag for the current version yet. tag puts the missing tag on the current version without bumping it again or opening a PR.
polyrepo tag [options]- Checks each package (in parallel) for whether a tag
v<local version>already exists. - Shows a checkbox: package, version, tag. Untagged packages are pre-selected; already-tagged ones can still be picked manually (harmless — it just confirms the tag is there).
- After confirming, for each selected package: syncs the default branch (same as
bump), then creates and pushes the tag if it's missing. - If at least one package was actually tagged (and it wasn't a
--dry-run), it asks: "Create a release for the N package(s) just tagged?" — answering yes runs the same process asreleasefor exactly those packages.
Example output — the current version already has a tag, so there's nothing to do but confirm it:
[1/1] os-detect v2.1.5
$ git fetch origin
$ git checkout master
Your branch is up to date with 'origin/master'.
Already on 'master'
$ git merge --ff-only origin/master
Already up to date.
✓ master is up to date.
✓ Tag v2.1.5 already exists on origin.Options
--dry-run
Prints the plan; tags, pushes, and releases nothing.
--packages <a,b,c>
Package list instead of the interactive checkbox.
--yes
Skip the "proceed?" confirmation (the release question is skipped too — no release is created unless --release is also given).
--release
Create a release right after tagging, without asking — for scripts.
Example:
polyrepo tag --packages vue-toast-kit,os-detect --yes --releaserelease
Pick packages and create a release for their current version's tag.
polyrepo release [options]- Checks each package (in parallel) for a
v<local version>tag on origin (the onebumportagcreates) and whether that tag already has a release. - Shows a checkbox: package and its tag. Packages with no tag for their current version show up disabled ("run
bumpfirst"); already-released ones are selectable but unchecked, in case you want to re-run it. - After confirming, for each selected package:
gh release create <tag> --verify-tag(GitHub) orglab release create <tag>(GitLab) — the checkbox already only offers packages with an existing tag, so this never invents one;--verify-tagis GitHub's own second guarantee of the same thing, GitLab has no equivalent flag. Release notes come from the matchingCHANGELOG.mdsection when there is one. Otherwise: GitHub's own--generate-notes(a summary of merged PRs/commits); GitLab has no equivalent, so this falls back to a plain commit-log listing since the last tag instead.
Example output — on a GitHub repo:
[1/1] os-detect v2.1.5
✓ No changelog entry — using --generate-notes.
$ gh release create v2.1.5 --verify-tag --title os-detect@2.1.5 --generate-notes
✓ Created release os-detect@2.1.5.Options
--dry-run
Prints what would be created, without actually creating any release.
--packages <a,b,c>
Package list instead of the interactive checkbox.
--yes
Skip the "proceed?" confirmation.
Example:
polyrepo release --packages vue-toast-kit,os-detect --yespublish
Pick packages and run npm publish — pre-selects ones ahead of the registry.
polyrepo publish [options]- Checks each package's registry version (
npm view <pkg> version) against its local version — in parallel. - Shows a checkbox: registry version → local version. Packages where they differ are pre-selected; already-published ones stay selectable (e.g. to republish after an unpublish).
- After confirming, checks
npm whoamionce for the whole batch (skipped under--dry-run) — not logged in runsnpm login(a real terminal, so its browser-based OTP flow or credential prompt works normally) before publishing anything, instead of only finding out partway through the first package'snpm publish. - For each selected package, one at a time:
npm publish(ornpm publish --dry-runwith the--dry-runflag). Runs with a real terminal, not captured — an npm 2FA/OTP prompt works normally.
Example output — the middle section is npm's own build/test/pack output for the package, shown live in the real terminal, not something polyrepo captures or reformats:
[1/1] os-detect@2.1.5
$ npm publish --dry-run
… (the package's own build, tests, and npm's tarball contents listing)
✓ Published os-detect@2.1.5 (dry run).Options
--dry-run
npm publish --dry-run instead of a real publish.
--packages <a,b,c>
Package list instead of the interactive checkbox.
--yes
Skip the "proceed?" confirmation.
Example:
polyrepo publish --packages vue-toast-kit,os-detect --yes