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

@remcostoeten/releaser

v0.3.2

Published

Safe, resumable npm and GitHub release CLI

Readme

Most release tools do the work and tell you afterwards. This one builds an immutable ReleasePlan first — the exact version, every file it will touch, every commit, tag, push, publish, and release it will perform — shows it to you as a diff, and only then asks. Planning cannot mutate anything, because planning has no access to anything that writes.

The second half matters more. A release is six or seven irreversible-ish steps against three separate systems, and the interesting case is the one where your laptop sleeps between step 5 and step 6. Every step is journalled before it is attempted and checks whether it already happened before it runs, so releaser resume finishes the job instead of duplicating it.

Install

Requires Node.js 24 or newer.

npm install --global @remcostoeten/releaser
releaser --help

Run without a global install:

npx @remcostoeten/releaser doctor

Before the first release, configure a Git remote and upstream branch. When npm publication is enabled, authenticate with npm login or a suitable npm token. When GitHub Releases are enabled, provide GitHub authentication with GITHUB_TOKEN.

Start read-only. Diagnose the repository, then preview a patch release:

releaser doctor
releaser plan --bump patch
releaser --bump patch --dry-run

Use

releaser          # interactive wizard
releaser init     # generate releaser.config.json for a non-npm project
releaser plan     # build and show a plan, execute nothing
releaser status   # repo, registry, and release state
releaser doctor   # run preflight checks
releaser scan     # find version occurrences in tracked files
releaser resume   # continue an interrupted release
releaser ship     # commit a feature, merge to the release branch, and release
releaser finalize # wait for the tag's CI, then publish the draft GitHub Release

When work is still uncommitted on a feature branch, ship provides the one-push workflow:

releaser ship --bump patch

It shows the feature commit and merge preparation before changing Git, asks for the commit message when needed, merges into the configured release branch, then shows the ordinary immutable release plan. The executor's branch push contains both the merge and release commit, so a deployment triggered by a push to master or main runs once. Merge conflicts are aborted locally and the command returns to the feature branch without pushing or publishing.

For non-interactive use, approval and a feature commit message are explicit:

releaser ship --target master --message "feat: checkout" --bump minor --yes

Shell completion

Enable completion for the current shell:

source <(releaser completion bash)
eval "$(releaser completion zsh)"
releaser completion fish | source

Non-interactive, for CI:

releaser --bump patch --yes
releaser --version 2.0.0 --dry-run
releaser --bump prerelease --tag beta --no-interactive

Human output is concise terminal text. Under --json, stdout contains one valid JSON value and nothing else. Progress and diagnostics use stderr, so piping to jq remains safe:

releaser status --json | jq .

--yes approves prompts and explicitly overridable preflight checks. It never bypasses non-overridable blockers. --otp is sensitive; prefer npm authentication that does not require putting an OTP in shell history, and pass it only when npm requires one.

Exit codes and recovery

| Code | Meaning | |---:|---| | 0 | Success | | 1 | Unexpected internal error | | 2 | Invalid arguments or flags | | 3 | Preflight failed | | 4 | Cancelled before release execution | | 5 | Partial release; resume required | | 6 | Authentication failure |

After an interruption or partial release, fix the reported cause and continue the journalled plan:

releaser resume
releaser resume --cwd /path/to/repository

What it refuses to do

The design is mostly a list of things that go wrong at 2am.

It never runs npm unpublish. Not on failure, not on rollback, not behind a flag. Once a version is on the registry the release is not reversible, and pretending otherwise is how you end up with a yanked version and a broken consumer. A failure after publication reports exactly which steps completed and how to finish.

It never retries a failed npm publish blindly. A network timeout after the registry accepted the tarball looks identical to a rejection. A failed publish is recorded as unknown, then resolved by asking the registry whether the version exists. The registry is the only authority on that question.

It never trusts HEAD alone to decide a plan is still valid. Uncommitted edits change file content without changing the SHA. A plan carries a fingerprint of the head SHA, a digest of git status, the manifest version, and the upstream SHA; if any of them moved, the plan is rejected rather than quietly re-planned.

It never replaces text it did not expect to find. Every configured replacement declares how many matches it should have. Zero matches and thirty matches both fail planning. There is no replace-all mode, because that is the single fastest way for a release tool to corrupt a repository.

Its journal lives outside your repository. In $XDG_STATE_HOME, not in the working tree and not in .git/ — a journal inside the tree gets swept up by the release commit, or deleted by git clean -xfd during exactly the recovery it exists to serve.

It never prints your token. Redaction is applied centrally, at the logger and the journal writer, rather than at each call site where one missed call leaks a credential.

How a release runs

mutate-files → commit → tag → push-branch → push-tag → npm-publish → github-release
                                                            ▲
                                                     irreversible

The order is not arbitrary; it is sorted by how cheaply each step can be undone. Everything freely reversible happens first. npm-publish sits as late as possible while still preceding github-release, because a GitHub Release pointing at a version nobody can install misleads people, whereas a published version with no GitHub Release is a one-command repair.

--dry-run invokes no write boundary at all — not the filesystem, not the network, not even the journal.

Configuration

releaser.config.json at the repository root, or a releaser key in package.json. Unknown keys are an error, not a warning: a typo in a config key must never silently disable a safety check.

{
  "releaseBranch": null,
  "remote": "origin",
  "versionFile": "package.json",
  "versionPattern": null,
  "tagPrefix": "v",
  "commitMessage": "chore(release): {{version}}",
  "npm": { "publish": true, "access": "public", "tag": null },
  "github": { "release": true, "draft": false },
  "replacements": [
    {
      "file": "src/version.ts",
      "find": "export const VERSION = '{{previousVersion}}'",
      "replace": "export const VERSION = '{{version}}'",
      "expectedMatches": 1
    }
  ]
}

releaseBranch: null detects the remote's default branch, so master and main repositories both work without configuration.

versionFile selects the JSON file containing the repository release version. It defaults to package.json. Set npm.publish to false to release a private desktop application or another repository-level product without running npm availability, authentication, registry, pack, or publish checks. Explicit, match-count-checked replacements can synchronize Cargo, Tauri, and nested package versions. See the Skriuw and Dora examples.

When the version does not live in JSON at all — a PKGBUILD, a Makefile, a configure.ac — set versionPattern and the versionFile is read as text. The version is the pattern's first capture group, and only that capture group's bytes are rewritten:

{
  "versionFile": "packaging/pkgbuild/PKGBUILD",
  "versionPattern": { "pattern": "^pkgver=(.+)$", "flags": "m" },
  "npm": { "publish": false }
}

The pattern must match exactly once. Zero matches and two matches are both planning failures, for the same reason a replacement declares expectedMatches: a version source with two authorities is how a release tool corrupts a repository. Such a repository needs no package.json — with none present the release has no package name and is identified by version alone. See the Remcorder example.

Run releaser init to generate this file automatically. It detects Cargo, Python (PEP 621 or Poetry), CMake, and PKGBUILD projects and writes a matching versionFile/versionPattern; if nothing is recognized it creates a plain VERSION file and points the config at that instead.

When CI owns artifact builds and creates the draft GitHub Release itself, releaser finalize closes the last mile: it polls the workflow runs triggered by the release tag, refuses to publish while any run is pending or failed, and flips the draft to published once everything is green. It is idempotent and safe to re-run after a timeout.

Scope

One version and tag per run, optionally published to npm and GitHub. Repository workspaces are permitted for non-npm releases, but independently versioned or multi-package publication remains unsupported. No other registries, no other forges, no plugin system. The full list of deliberate exclusions is SPEC.md §2.

Prereleases never touch the latest dist-tag by default — 1.2.0-beta.1 publishes to beta.

Development

Bun 1.3 and Node.js 24.

bun install
bun run check   # typecheck + lint + format + test + build

TypeScript 7 for typechecking and build, oxlint and oxfmt for lint and format, Vitest for tests. No ESLint, no Prettier, no bundler.

SPEC.md is the normative contract and wins over any other document. AGENTS.md covers working conventions. prompts/ sequences the implementation across eight tasks.

License

MIT