npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

release-patch

v1.0.9

Published

Bump the patch version, sync master, commit, push and publish an npm package with a single command.

Downloads

3,570

Readme

release-patch

Bump the patch version, sync master, commit, tag, push and publish an npm package — all with a single command.

release-patch wraps the repetitive steps of cutting a patch release so any package can adopt the same flow just by installing it.

Git tags are the source of truth for versioning. The next version is derived from the latest annotated vX.Y.Z tag, never from the version field in package.json.

What it does

When run from a package's root directory, a normal patch release:

  1. Verifies the working tree is clean (git status --porcelain) and aborts on uncommitted changes, before touching any branch, so stray edits can never leak into the release commit and syncing master can never clobber uncommitted work.
  2. Syncs master with origin/master (git checkout master, git fetch, then a fast-forward-only git merge --ff-only origin/master). If local master has diverged from origin, the sync fails loudly instead of inventing a merge commit that would then be tagged and published as a release.
  3. Reads and validates package.json from the synced master checkout (never stale feature-branch metadata): it must declare a name that npm itself accepts (validated with validate-npm-package-name, so scoped names pass and bad characters, capitals, length or reserved names fail) and must not be marked "private": true. A missing, invalid or private name fails here, before any release mutation.
  4. Logs in to npm if you are not already authenticated (npm login).
  5. Fetches tags (git fetch origin --tags) and enumerates them with git for-each-ref, keeping only annotated vX.Y.Z tag objects. Lightweight tags, pre-release/build tags and malformed tags are ignored — an annotated v1.0.0 wins over a lightweight v9.9.9. The latest annotated tag is the current released baseline. If no valid annotated release tag exists, it fails clearly with separate instructions for a brand-new package or an already-published no-tag package instead of guessing.
  6. Confirms the latest annotated tag is actually published on npm. A normal release never skips an unpublished latest tag: if the baseline tag is not on npm, it blocks with instructions to inspect it and, if it was tagged but never published, finish it with release-patch --resume.
  7. Derives the next version by incrementing the patch component of the published latest tag, and checks the registry for that exact <package>@<version> (npm view <package>@<version> version), passing the package name as a process argument, never through a shell. If that exact version already exists, it fails as a duplicate. Only an unambiguous npm E404 code means the version is available; any other outcome — a network/auth/registry error, or an E404 mixed with another error code — is treated as blocking uncertainty, and the release aborts before any mutation rather than risk an overwrite or a race.
  8. Installs dependencies (npm install, or npm install --no-package-lock when neither package-lock.json nor npm-shrinkwrap.json exists).
  9. Writes the exact derived version without creating a git tag (npm version <version> --no-git-tag-version).
  10. Runs npm run build only if the package defines a build script and its version/postversion lifecycle scripts do not already run build.
  11. Runs a publish dry-run (npm publish --dry-run) as a gate — before creating the release commit or tag — so a bad tarball or packaging error surfaces while everything is still local. The dry-run pushes no Git refs and publishes no package, though its lifecycle scripts (prepublishOnly, prepack, prepare) may still have external side effects. Running it before the commit and tag means a failed dry-run leaves no local tag behind to poison the next version derivation.
  12. Commits exactly package.json, package-lock.json and npm-shrinkwrap.json when present (chore: bump patch version), each staged by name — never git add -A. This commit is the exact release commit.
  13. Refuses to continue if any tracked or untracked, non-ignored change remains in the working tree after that commit. A build or dry-run lifecycle script that emits generated files or secrets outside the intended commit blocks the release with an actionable error rather than being swept into the tag and push.
  14. Creates an annotated tag on that release commit (git tag -a v<version> -m v<version>).
  15. Pushes the release commit and its exact tag to origin atomically in a single non-force push (git push --atomic origin master v<version>), so master is never published without its matching release tag.
  16. Publishes to npm (npm publish). If the publish fails after the atomic push, the commit and tag are already public, so it surfaces precise recovery instructions to finish with release-patch --resume and preserves the non-zero exit.
  17. Verifies the published version is live on the registry (npm view <package>@<version> version). Because npm's read path can lag a successful publish, verification makes up to 10 attempts across a bounded 150-second window (waiting 1, 2, 4, 8, 15, then 30 seconds between checks) and prints progress before each wait. Only an unambiguous E404 is retried; network, authentication, mixed-code, and other ambiguous registry errors fail closed immediately. If the version remains unavailable after the bounded window, the command exits non-zero and directs recovery through release-patch --resume; it never publishes again as a fallback. The package name comes from package.json and is passed as a process argument, never through a shell, so hostile metadata cannot inject commands.

The manifest is read and validated from the fast-forwarded master, and the baseline-published and duplicate preflights run, before any release mutation — so validation and duplicate detection reflect exactly what will be published, and no version bump, commit, tag, push, dry-run, or publish happens until the version is confirmed available. The clean-tree check and the master sync are the only Git side effects that precede validation. The install step runs before the version write so version lifecycle scripts and the pre-push build gate use dependencies from the synced package. The version is derived from tags before the install so the release fails fast when no valid tag exists.

Because the annotated tag is created on the release commit itself (rather than relying on npm version's implicit commit/tag), the tag always points at exactly the commit that records the version bump. Only package.json, package-lock.json and npm-shrinkwrap.json (when present) are committed; the npm run build step is a pre-push gate whose output is intentionally not committed (and, if it produces non-ignored files, is caught by the stray-change check). npm publish produces the published artifact from a fresh build via its own lifecycle scripts.

npm publish still runs npm lifecycle scripts such as prepublishOnly, prepack, and prepare. Those hooks run after release-patch pushes the version commit, so they do not replace the pre-push build gate.

Resuming an interrupted release

A release can be interrupted after the commit and tag are pushed but before the publish succeeds — for example a flaky network or a registry hiccup on npm publish. Because the tag now exists but the version is not on npm, a normal release would refuse to continue (it will not skip an unpublished latest tag). Finish the exact tagged version with:

release-patch --resume

--resume publishes the existing latest annotated tag without bumping, committing or creating another tag. It syncs master, requires the tag to be an ancestor of current master history, and requires current master's package name/version to match the tagged release identity. It then binds the exact synced master commit and annotated tag object, creates an isolated detached Git worktree at that tag's commit, validates the tagged manifest and re-runs the dependency, build and dry-run gates there. Immediately before the atomic push and again before publication, it requires both worktree HEAD and the clean tree to remain at that exact tagged commit. The non-force atomic push uses the validated object IDs as sources for refs/heads/master and the release tag, so concurrent local ref movement cannot change what reaches origin and conflicting remote movement is still rejected. This keeps later master changes out of the package while publishing and verifying the exact tagged version. The temporary worktree is removed and the caller's original branch or detached checkout is restored after every success or failure. SIGINT and SIGTERM are deferred through that cleanup and then re-delivered to preserve signal termination semantics; an accompanying release or cleanup error is still reported. If the version is already published, --resume is a verified no-op. Resume cannot be combined with another release mode.

Bootstrapping an already-published no-tag package

Use this one-time mode when a package is already published on npm but its repository has never had a semver release tag:

release-patch --bootstrap-published 0.0.16 --expected-git-head 407db4fab09a941984614167a1377f773531949c

Both values are mandatory. The version must be exact stable X.Y.Z; the expected Git head must be a full 40-character lowercase SHA independently reviewed from authoritative package history. Run the command from a clean master that can fast-forward to and then exactly equals origin/master.

Before changing release state, the helper fetches tags without pruning local state and fails unless there are no local or remote stable semver tags. It then reads version and gitHead together from npm for the exact package version and requires the registry values to match the supplied values. The commit must exist locally, be an ancestor of the verified current master commit, and contain a package.json whose name and version exactly match the current package and requested release.

After all checks pass, release-patch creates annotated v<version> at the authenticated historical commit, pushes only that immutable tag object with a non-force exact refspec, verifies both the remote tag object and its peeled commit, and creates a public, non-prerelease GitHub release with the exact tag title. It never advances master or runs npm publish in this mode. Tagging current HEAD is wrong when npm's immutable gitHead names an older commit: it would make the Git release claim different source than the published artifact.

Recovery is rerunning the exact same command. If a tag push has an uncertain outcome, the exact local tag is preserved. If the tag reached origin but GitHub release creation failed, the retry verifies the tag and finishes the release. The one exact annotated target tag is accepted as resumable state only when it still points to the authenticated commit; any other semver tag, moved/lightweight target tag, or mismatched existing GitHub release blocks. A retry never republishes npm, rewrites or force-pushes a tag, or edits/overwrites a release.

Reconciling an untagged published baseline

Use this guarded package-owned mode when npm already contains the patch immediately after the latest annotated release tag, but that published patch has no tag:

release-patch --reconcile-published 0.5.10 --expected-git-head 161a8291ffd9c66f5bdb4cd13f18e97d37e0648e

Both arguments are mandatory: the version must be exact X.Y.Z, and the expected Git commit must be its full 40-character lowercase SHA reviewed from the package's authoritative history. npm's gitHead is publisher-supplied metadata, not an independent proof of provenance. The operator-supplied SHA is therefore the trust anchor: the command reads version and gitHead together from npm for that exact package version and requires the registry value to match the expected SHA byte-for-byte before trusting it. The SHA authenticates only the one version named in that invocation; it is never reused or inferred for another published version.

The command then fails closed unless all of the following are true:

  • the requested version is exactly one patch after the latest annotated tag (or is the exact tag from a partially completed reconciliation);
  • the preceding annotated tag is already published on npm, preserving the normal release invariant;
  • npm returns that exact version and one full 40-character lowercase hexadecimal gitHead;
  • npm's gitHead exactly matches the operator-supplied --expected-git-head;
  • the commit exists after syncing master and fetching origin, and is an ancestor of authoritative origin/master;
  • package.json at that commit has the same package name as current synced master and exactly the requested version;
  • the baseline tag is absent, or is already the same annotated tag on that exact commit after an interrupted attempt; and
  • the registry can unambiguously determine whether the following patch is published.

Only after every check succeeds does the command create and non-force-push the missing annotated baseline tag. If the following patch is definitely unpublished, it then runs the ordinary release transaction unchanged, deriving and publishing that patch. If the following patch is already published, the invocation is a historical-gap step: it returns successfully after pushing only the verified baseline tag, without installing dependencies, bumping a version, building, committing, creating a next tag or publishing. An ambiguous network, authentication or registry failure while checking the following patch blocks before the baseline tag is created or pushed.

For several consecutive published gaps, invoke the helper sequentially from oldest to newest. Each invocation must name that version's independently reviewed SHA:

release-patch --reconcile-published 0.5.10 --expected-git-head 161a8291ffd9c66f5bdb4cd13f18e97d37e0648e
release-patch --reconcile-published 0.5.11 --expected-git-head 2b6c9c887eab9480d485d46f67e536c96c42717a
release-patch --reconcile-published 0.5.12 --expected-git-head 3f15b81af404ef0fdcb60bca7682353c89438869

If 0.5.11 and 0.5.12 are already published but 0.5.13 is not, the first two commands each push only one historical baseline tag. The third pushes v0.5.12 and then releases 0.5.13. Do not manually create missing tags or reuse one version's expected SHA for another version.

Recovery is rerunning the exact same helper invocation. If baseline tag creation fails, nothing was pushed and no release commit was made. If its push fails, an exact local tag may remain; the retry re-fetches authoritative tags and recreates or accepts only the verified exact tag. After the baseline tag is pushed, any failure during dependency installation, versioning, build, dry-run, commit, or release-tag creation automatically restores the clean pre-release master, removes the unpushed release tag and non-ignored files created by the attempt, and reports that the same invocation is safe to retry. Lifecycle scripts may still have external side effects that Git cannot undo. An atomic-push error has an ambiguous remote outcome, so the exact clean release commit/tag are preserved and recovery uses the printed release-patch --resume instruction instead of bumping again. Publishing or verification failures after a successful push use that same resume path.

Bootstrapping a brand-new package

The next version is always derived from the latest published annotated tag, so a brand-new package needs one published tag to start from. Create an annotated tag that matches package.json and the current HEAD, then publish that initial version with --resume:

git tag -a v0.0.0 -m v0.0.0   # package.json version must be 0.0.0 and HEAD must be the tagged commit
release-patch --resume        # pushes the tag and publishes v0.0.0

From then on, ordinary release-patch runs derive each next patch from the published tag.

Install

npm install --save-dev release-patch

Usage

Add a script to your package.json:

{
  "scripts": {
    "release:patch": "release-patch"
  }
}

Then cut a release:

npm run release:patch

You can also run it ad hoc without adding a script:

npx release-patch

Requirements

  • The package is a git repository with a master branch and an origin remote, and a clean working tree (no uncommitted changes) when you run the release.
  • package.json declares a name that npm accepts (validated with validate-npm-package-name) and is not marked "private": true.
  • At least one annotated release tag in vX.Y.Z form exists (for example v1.0.0), and that latest tag is published on npm. Lightweight tags are ignored. This published tag is the source of truth for the current released version. To bootstrap a brand-new package, see Bootstrapping a brand-new package.
  • An already-published package with no release tags instead uses the guarded published-baseline bootstrap, which also requires authenticated GitHub CLI access to create the matching GitHub release.
  • You have publish rights to the package on npm.

License

ISC