Skip to content

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.

bash
polyrepo bump [options]
  1. Shows a checkbox of packages with their current version and the version they'd bump to (1.2.9 → 1.2.10 for a patch bump — --minor/--major bump that part instead, resetting the parts below it to 0, 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.
  2. After confirming, for each selected package, one at a time:
    1. git fetch origingit 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);
    2. 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;
    3. if package.json isn'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 a CHANGELOG.md, a draft ## [x.y.z] - YYYY-MM-DD entry (Keep a Changelog style) is added too, listing commits since the last tag — a starting draft to review, not a finished changelog;
    4. gh pr create (GitHub) or glab mr create (GitLab) against the default branch (if there isn't one already);
    5. 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;
    6. gh pr merge / glab mr merge — through a PR/MR, not a direct push;
    7. local default branch is synced to the just-merged PR/MR;
    8. git tag v<new-version> is created and pushed if it doesn't already exist.
  3. Any failure along the way marks that package ✗ with a clear message and moves on to the next one, without aborting the whole run.
  4. 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.92.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:

bash
# 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 --yes

tag

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.

bash
polyrepo tag [options]
  1. Checks each package (in parallel) for whether a tag v<local version> already exists.
  2. 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).
  3. After confirming, for each selected package: syncs the default branch (same as bump), then creates and pushes the tag if it's missing.
  4. 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 as release for 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:

bash
polyrepo tag --packages vue-toast-kit,os-detect --yes --release

release

Pick packages and create a release for their current version's tag.

bash
polyrepo release [options]
  1. Checks each package (in parallel) for a v<local version> tag on origin (the one bump or tag creates) and whether that tag already has a release.
  2. Shows a checkbox: package and its tag. Packages with no tag for their current version show up disabled ("run bump first"); already-released ones are selectable but unchecked, in case you want to re-run it.
  3. After confirming, for each selected package: gh release create <tag> --verify-tag (GitHub) or glab release create <tag> (GitLab) — the checkbox already only offers packages with an existing tag, so this never invents one; --verify-tag is GitHub's own second guarantee of the same thing, GitLab has no equivalent flag. Release notes come from the matching CHANGELOG.md section 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:

bash
polyrepo release --packages vue-toast-kit,os-detect --yes

publish

Pick packages and run npm publish — pre-selects ones ahead of the registry.

bash
polyrepo publish [options]
  1. Checks each package's registry version (npm view <pkg> version) against its local version — in parallel.
  2. 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).
  3. After confirming, checks npm whoami once for the whole batch (skipped under --dry-run) — not logged in runs npm 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's npm publish.
  4. For each selected package, one at a time: npm publish (or npm publish --dry-run with the --dry-run flag). 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:

bash
polyrepo publish --packages vue-toast-kit,os-detect --yes