Releasing & Publishing
The full release cycle: bump a version through a PR, tag it, create a GitHub 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.
bump
Pick packages, bump their version (patch by default), open a PR, 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, an open PR, 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 PR; - 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 createagainst the default branch (if there isn't one already);- with
--wait-checks: waits for the PR's CI checks before merging; no checks configured isn't an error; gh pr merge --merge— through a PR, not a direct push;- local default branch is synced to the just-merged PR;
- 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.--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 the PR's CI checks (if any are configured) before merging; don't merge if they fail.
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 GitHub 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 GitHub 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 GitHub 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—--verify-tagguarantees this never invents a new tag, only ever uses one that already exists. Release notes come from the matchingCHANGELOG.mdsection when there is one, otherwise from--generate-notes.
Example output:
[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