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

@arthur2079/rangutopia

v0.19.0

Published

A CLI to manage our scripts across the organization.

Readme

rangutopia

A CLI to manage our scripts across the organization.

library build

Builds one library package. Run it from inside the package — the nearest package.json walking up from the current working directory is the package that gets built.

By default it runs two tools in parallel:

  • tsc emits the type declarations (--declaration --emitDeclarationOnly --project tsconfig.build.json).
  • esbuild emits the bundled, minified ESM JavaScript.

Passing --tsc-only drops the esbuild half — see tsc-only builds below.

Usage

rangutopia library build
rangutopia library build --inputs src/mod.ts
rangutopia library build --inputs src/index.ts,src/helpers/index.ts
rangutopia library build --splitting
rangutopia library build --external react,react-dom
rangutopia library build --external-all-except @walletconnect/modal
rangutopia library build --tsc-only

Per-package requirements

  • A tsconfig.build.json in the package root. Its outDir decides where tsc writes.
  • An entry point. Without --inputs the first of src/index.ts, src/mod.ts that exists is used.
  • typescript and esbuild as dependencies somewhere up the tree — rangutopia library check verifies this and the build runs it first. A tsc-only build only needs typescript.

Flags

  • --inputs <paths> — comma-separated entry points, relative to the package root (e.g. src/main.ts,src/net.ts). Defaults to the first existing path of src/index.ts, src/mod.ts.
  • --external <names> — comma-separated packages to leave unbundled. See https://esbuild.github.io/api/#external.
  • --external-all-except <names> — invert the above: every entry in dependencies is external except the listed ones. Use it when you want a mostly-external bundle that still inlines a few packages. Mutually exclusive with --external.
  • --splitting — enable esbuild code splitting.
  • --tsc-only — skip esbuild and build with tsc alone.

With neither --external nor --external-all-except, esbuild runs with packages: "external", so nothing from node_modules is bundled.

Output

Everything lands in the package's dist/, and nothing else: the build writes the compiler output only. Notably it does not persist esbuild's metafile — if you want one for bundle-size tooling, generate it separately rather than expecting a .build.json in dist/.

tsc-only builds

--tsc-only skips the esbuild step and lets tsc produce the JavaScript as well as the declarations. Use it for packages that should ship as plain per-file ESM rather than as a bundle — for example packages whose consumers need to resolve individual modules, or packages where bundling breaks the runtime.

What changes:

| | default (tsc + esbuild) | --tsc-only | | --------------------- | ------------------------------------- | ---------------------------------------------------- | | JavaScript emitted by | esbuild | tsc | | Shape of the output | one bundle per entry point, minified | one .js per source file, unminified | | Declarations | tsc | tsc | | Entry points | --inputs or the default entry point | not used — tsc compiles what the tsconfig includes | | Requires esbuild | yes | no |

Because tsc becomes the only emitter, the build passes --emitDeclarationOnly false explicitly. That matters: it overrides a tsconfig.build.json (or a base config it extends) that turns emitDeclarationOnly on. Without the override you'd get declarations and no JavaScript.

--inputs, --external, --external-all-except and --splitting only configure esbuild, so they do nothing here. Passing any of them together with --tsc-only logs a warning naming them; the build still succeeds.

Enabling it per package

The flag is set per package, in that package's own build script — packages that don't pass it keep the default tsc + esbuild pipeline. Nothing is configured centrally, so opting one package in never touches another.

{
  "name": "@scope/some-library",
  "scripts": {
    "build": "rangutopia library build --tsc-only"
  }
}

while its siblings stay on the default:

{
  "name": "@scope/another-library",
  "scripts": {
    "build": "rangutopia library build --inputs src/mod.ts"
  }
}

The release flow

Releasing is split into small commands, so a CI pipeline can run them as separate steps (or even separate jobs) and do its own work in between:

# 1. what should the next versions be?
rangutopia library version --prod --deferred
# 2. can we publish and release them?
rangutopia library version check
# 3. version the repository root and the client apps, optional
rangutopia client version --prod --root --clients @rango-dev/widget-app
# 4. changelog of the repository, optional
rangutopia changelog generate --root --mention @rango-dev/widget --save
# 5. npm, changelogs, github releases, commit and tag
rangutopia library publish --prod

Every step but 3 detects the same set of packages: the ones with new commits since the last release, plus the ones affected through the dependency graph. Nothing is committed before step 5, so the steps see the same git history and agree on the list. Step 3 detects nothing, it versions exactly what it's told — see client version.

Steps 1, 2 and 5 are not optional: versions are always determined with --deferred first, then checked, and only then published. Doing all of it in one command is what --immediate will be, and it isn't implemented yet.

Steps 3 and 4 are for a monorepo released as a whole (client apps and the packages they embed): the root and the clients get a version too, and the repository gets a changelog. 3 runs before 4, so the root changelog carries the new root version, and what they write is yours to commit before 5 — see Nothing is committed.

library version apply is not part of this flow. library publish applies the versions itself, one package at a time, right before publishing it, so it needs them still saved: a package it has to publish with no determined version left is a MissingDeferredVersionError. A full apply before publish is what causes that, since it forgets every package it writes. apply is for writing versions outside a release, and apply --package <name> for a package this release won't publish.

Limitations

The split flow trusts you to run it as one sequence, on one machine, without touching the repository in between. The sharp edges to know about:

  • One release at a time per machine. The state file (rangutopia/library-version.json in the OS temp directory) is one file per machine, not per repository, and it doesn't record which checkout it was computed in. Releasing two repositories — or two checkouts of the same repository — at the same time makes the steps read each other's versions. Don't interleave two releases.
  • A failed version --deferred must be re-run. The state file is written as the versions are determined, so a failure can leave a partial state behind, and check / apply can't tell a partial state from a complete one — a package the failed run didn't reach would silently keep its old version. After a failure in version, run version --deferred again instead of continuing.
  • library publish consumes the state file. It reads the determined versions from it, forgets each package as it applies it, and deletes the file when the command is done — including when it bailed out half way, since what is left can't be re-applied on its own. After a failed publish, run version --deferred again before trying once more.
  • A root changelog mentions a version out of the state file. changelog generate --root --mention <package> takes that package's version from what step 1 determined, since step 5 is what writes it on the package.json files. Run it on the same machine, between steps 1 and 5, or the mention falls back on the package.json version — the one of the previous release.
  • The channel is not checked across steps. version saves its channel, and library publish restores it when it applies each version, but library publish also takes its own --prod / --next / --experimental flag — which is what detects the packages — and nothing verifies the two agree. Pass the same flag to both commands — a CI pipeline should feed them from a single variable, like publish.yml does with PUBLISH_FLAG.
  • Nothing may be committed between the steps. Every command detects the changed packages from the git history on its own, and nothing checks that version and publish agreed on the same list. A commit in between can make them diverge. The exception is a commit touching nothing but the root and the private packages — what committing steps 3 and 4 looks like — the libraries to publish stay the same, and the private packages it touches join the release commit anyway.

Channels

--prod, --next and --experimental are required by library version, library publish and client version (exactly one of them). They decide how the next version is built and what a release does:

| Flag | Next version | npm dist-tag | changelog & github release | commit | tag | | ---------------- | ------------------------- | -------------- | -------------------------- | ------ | --- | | --prod | conventional commits bump | latest | yes | yes | yes | | --next | x.y.z-next.N prerelease | next | no | yes | no | | --experimental | 0.0.0-experimental-… | experimental | no | no | no |

The commit and tag columns are what library publish does. client version builds its versions the same way, but never commits or tags.

--since-start makes the detection consider every package of the workspace instead of only the changed ones. Use it for a first release.

library version

Determines the next version of every changed public package, from its conventional commits.

It takes a mode, exactly one of --deferred or --immediate — there is no default. --immediate (determine, check and write in one command) is not implemented yet, so --deferred is the way to version packages today.

Usage

rangutopia library version --prod --deferred
rangutopia library version --next --since-start --deferred
rangutopia library version check
rangutopia library version apply

Deferred versions

--deferred stops before touching any file: the versions are saved on a state file, and check / apply pick it up later. Only the conventional commits are read here, nothing is asked to npm or github yet. apply is what writes the versions on the package.json files — in a monorepo, every package that depends on a bumped package gets its dependency range updated (^<new version>) as well.

$ rangutopia library version --prod --deferred
Versions will be saved on /tmp/rangutopia/library-version.json

The state file remembers the channel, so check and apply don't need the channel flags again.

  • library version check — looks each determined version up on npm, in the git tags and in the github releases, saves what it finds next to the version, and fails when any of them is already taken. The npm lookup only compares against the channel's dist-tag (latest / next): a version that was published before but isn't the dist-tag anymore passes the check and fails later, at npm publish. The lookups run on every channel, but only --prod refuses to proceed on a conflict — on --next and --experimental the check passes. This is the guard library publish used to run itself, and the only step that needs network access (GH_TOKEN for the github lookup).
  • library version apply — writes the saved versions on the package.json files. A package is forgotten as soon as its version is written, so the state only ever holds what is still pending, and the file is deleted once nothing is left. --package <name> narrows it to one package and keeps the rest saved for later — apply a package only once everything it depends on has been applied, otherwise it's written against a stale dependency range. It's what library publish runs (silently) for each package it publishes, which is why a full apply doesn't belong in a release flow.

The state file is always rangutopia/library-version.json in the OS temp directory, so check and apply find it without being told where it is. It's one file per machine — see Limitations for what that implies.

On GitHub Actions

library version sets a count step output: how many packages it is versioning, 0 when nothing has changed. A workflow can use it to skip the steps that only make sense when something is released — a repository-level version bump, for instance — while check, apply and library publish are safe to run either way (they do nothing on an empty state).

- name: Determine versions
  id: version
  run: yarn run rangutopia library version --deferred --prod
- name: Bump the repository version
  if: steps.version.outputs.count != '0'
  run: ...

Comparing against '0' rather than testing for a non-empty value keeps the gate fail-open: with an older rangutopia that doesn't set the output, the step still runs.

Flags

  • --prod / --next / --experimental — the release channel, exactly one is required.
  • --since-start — consider every package of the workspace, not only the changed ones.
  • --deferred — save the versions for check and apply, without writing them on package.json files.
  • --immediate — determine, check and write the versions in one command. Not implemented yet, it throws.

check takes no flag at all, everything it needs is in the saved state. apply takes one:

  • --package <name> — only write the version of this package, and keep the saved versions of the others for a later run. Without it, every saved version is written and the state file is deleted.

library publish

Publishes the public packages on npm and releases them. The versions come from what library version --deferred has saved: a package it has to publish without a determined version is an error (MissingDeferredVersionError), never something to publish as it is. So library version has to have run for the same set of packages, and library version apply must not have consumed the versions in between.

Every package is built first, in one go: the whole list is handed to the build tool, which runs the builds in parallel while respecting the dependency graph. Nothing is published if the build fails.

Then, for each package in dependency order: writes its version on the package.json files (a silent library version apply --package, so a dependent is published with the new version of what it depends on), makes its CHANGELOG.md (--prod only), publishes it on npm, and stages the changed files. Then, for everything that reached npm: a chore(release): publish commit, one annotated tag per package (package-name@version, without the npm scope), a push, and a github release per tag whose notes are the generated changelog.

The first package that can't be published stops the loop. Its version goes back to the one it was on, along with the dependency ranges pointing at it, so nothing is left carrying a version that was never released — the packages published before it keep theirs and are committed, tagged and released as usual. Its CHANGELOG.md entry is written before the npm publish is attempted, so that one is left on the working tree, unstaged.

The release commit also picks up yarn.lock, the private packages whose dependency ranges have been bumped, and the root CHANGELOG.md when step 5 of the flow made one. It never picks up the root package.json: when client version has bumped it, commit it yourself before publishing.

Usage

rangutopia library publish --prod
rangutopia library publish --next
rangutopia library publish --prod --since-start

Requirements

  • gh — the github CLI, authenticated (GH_TOKEN), for the release step. Checking existing releases is library version check's job, not this command's.
  • npm authentication for the publish step. It runs npm publish (not yarn npm publish) so npm's trusted publishing (OIDC) works.

Private packages are never published. In a monorepo their package.json is still part of the release commit, since their dependency ranges have been bumped.

changelog generate

Generates a changelog out of conventional commits, from the last release tag to HEAD.

Usage

rangutopia changelog generate --root
rangutopia changelog generate --root --save
rangutopia changelog generate --root --mention @rango-dev/widget --save
rangutopia changelog generate --client --save

--root generates the changelog of the repository root: the root package.json version, and every commit since the last name@version tag, whatever package that tag belongs to. It's what a monorepo released as a whole (a client app and the packages it embeds) needs, and it's independent from the per-package changelogs library publish writes.

--mention <package> mentions the current version of a workspace package right under the version header:

# 1.4.0 (2026-08-15)

_includes `@rango-dev/[email protected]`_

### Features
…

The version is the one this release bumps that package to: library version --deferred saves it, and this command reads it back, so the mention carries what is about to be published rather than what is out already. A package the release doesn't change is mentioned on the version of its package.json, and so is one whose new version has been written there already (library version apply and library publish both forget a package as soon as they write it). See Limitations. It needs a monorepo, and the package has to be part of the workspace — an unknown name is an error, not a silently skipped line.

The version on the header is the root package.json's, so when the root is versioned by rangutopia client version --root, that runs before this command too.

Committing it is up to you, on a release flow as much as outside of one: the release commit of library publish doesn't stage the root CHANGELOG.md, the same way it leaves the root package.json alone — see Nothing is committed.

Without --save the changelog is printed on stdout instead of being written, which makes it easy to pipe somewhere else (release notes, a PR body, …). When there is no commit since the last tag, nothing is generated.

Flags

  • --root — generate the changelog of the repository root.
  • --mention <package> — mention this workspace package's version under the root version. Only usable with --root.
  • --client — the single-repo client mode. Requires a private root package.json.
  • --library — not implemented yet.
  • --save — write CHANGELOG.md (prepending to the existing one) instead of printing it.

client version

Determines the next version of the client packages you name and/or of the repository root, and writes them on their package.json files: library version and library version apply in one go, for what library version leaves out. A client is a private package of the workspace, the public ones belong to library version. Monorepo only, for now.

Usage

rangutopia client version --prod --root --clients @rango-dev/widget-app,@rango-dev/widget-playground
rangutopia client version --prod --root
rangutopia client version --next --clients @rango-dev/widget-app

Exactly one of --prod / --next / --experimental is required, and at least one of --clients / --root.

What it does

The root comes first (with --root), then the clients in the order they are given. Every version is determined before anything is written, so a bad name leaves every file untouched:

$ rangutopia client version --prod --root --clients @rango-dev/widget-app
Current state:
┌─────────┬─────────────────────────┬──────────────┬──────────┬─────────┐
│ (index) │ name                    │ location     │ version  │ private │
├─────────┼─────────────────────────┼──────────────┼──────────┼─────────┤
│ 0       │ 'rango-client'          │ '.'          │ '1.3.0'  │ true    │
│ 1       │ '@rango-dev/widget-app' │ 'widget/app' │ '0.42.0' │ true    │
└─────────┴─────────────────────────┴──────────────┴──────────┴─────────┘
Next state (prod):
┌─────────┬─────────────────────────┬──────────────┬──────────┬─────────┐
│ (index) │ name                    │ location     │ version  │ private │
├─────────┼─────────────────────────┼──────────────┼──────────┼─────────┤
│ 0       │ 'rango-client'          │ '.'          │ '1.4.0'  │ true    │
│ 1       │ '@rango-dev/widget-app' │ 'widget/app' │ '0.43.0' │ true    │
└─────────┴─────────────────────────┴──────────────┴──────────┴─────────┘

The next version follows the channel, with the same rules as library version:

  • --prod — a conventional commits bump. The commits since the last release tag of any package (the last name@version tag, whatever package it belongs to) decide the release type, the same range changelog generate --root covers. Neither this command nor library publish tags the root or the clients, so their own <name>@ tags could never bound the range — an untagged repository is bumped from its whole history.
  • --next — the x.y.z-next.N prerelease, counted up on every run.
  • --experimental0.0.0-experimental-<commit>-<date>, a throwaway version: it's a downgrade, and a --prod bump from it lands on 0.0.0. Never commit it — library publish doesn't either on this channel.

Then, exactly like library version apply: the version is written with yarn (yarn workspace <client> version, or yarn version at the root), and every workspace package that depends on a bumped client gets its dependency range updated (^<new version>). Nothing is detected: unlike library version, this command versions what it's told, every time it runs, and it never looks anything up on npm or github.

Nothing is committed

The changed package.json files (the root, the clients, their dependents) are left in the working tree, staged by nobody. Commit them yourself before library publish — its release commit only stages what it publishes, the affected private packages and yarn.lock, never the root package.json or the root CHANGELOG.md. A commit that touches nothing but the root and private packages doesn't disturb the release, see Limitations.

A failed run may have written some of the files (the root, then client by client), git checkout -- <file> puts them back; and nothing remembers a bump happened, so re-running the command bumps again.

Replacing a hand-written "bump the root" script looks like this, on --prod only:

rangutopia client version --prod --root --clients @rango-dev/widget-app
rangutopia changelog generate --root --mention @rango-dev/widget --save
git add package.json widget/app/package.json CHANGELOG.md
git commit -m "chore(release): bump the repo and client versions" -m "[skip ci]"
rangutopia library publish --prod

Flags

  • --prod / --next / --experimental — the release channel, exactly one is required.
  • --clients <names> — comma-separated names of the client packages to version, e.g. @rango-dev/widget-app,@rango-dev/widget-playground. Every name has to be a private package of the workspace: an unknown name is an error, a public one too (it points at library version), and so is the root's own name (that's --root). Duplicates are versioned once, and an empty list is an error rather than a no-op.
  • --root — version the root package.json as well.

client deploy

Deploys private workspace packages via a provider plugin. Only the vercel provider is implemented today (--provider <name>, default vercel); passing any other name throws "not implemented" — the dispatch is in place so adding another provider is one new branch, not a rewrite. Works on nx and turborepo monorepos.

Usage

rangutopia client deploy --prod
rangutopia client deploy --next
rangutopia client deploy --preview
rangutopia client deploy --next --exclude @scope/a,@scope/b
rangutopia client deploy --next --vercel-config ./my-vercel.json
rangutopia client deploy --next --dry-run
rangutopia client deploy --next --provider vercel

Exactly one of --prod / --next / --preview is required:

| Flag | Vercel target | CLI args used | | ----------- | ------------------------- | ----------------------------------------------- | | --prod | production | vercel deploy --prebuilt --prod --skip-domain | | --next | custom next environment | vercel deploy --prebuilt --target next | | --preview | built-in preview | vercel deploy --prebuilt --target preview |

Required environment variables

| Variable | Purpose | Required for | | -------------------------- | ---------------------- | ---------------------------------------------------------- | | VERCEL_TOKEN | Vercel CLI auth token | non-dry-run only | | VERCEL_ORG_ID | Vercel organization ID | non-dry-run only | | VERCEL_PROJECT_ID_<NAME> | Per-package project ID | always — see "Project-ID requirement by environment" below |

--dry-run skips vercel pull and vercel deploy, so it never reads VERCEL_TOKEN / VERCEL_ORG_ID.

Project-ID convention

For each deployable package, the env var name is derived from the package name:

  1. Drop the npm scope (@scope/).
  2. Uppercase the result.
  3. Replace any character that is not [A-Z0-9] with _.
  4. Prefix with VERCEL_PROJECT_ID_.

Examples:

| Package | Env var | | ------------------------------- | -------------------------------------- | | @rango-dev/widget-app | VERCEL_PROJECT_ID_WIDGET_APP | | @rango-dev/queue-manager-demo | VERCEL_PROJECT_ID_QUEUE_MANAGER_DEMO | | rango-dapp | VERCEL_PROJECT_ID_RANGO_DAPP |

Per-package requirements

A package opts in by being "private": true in its package.json. The "main" field must point to a file inside the build-output directory — its directory will be uploaded to Vercel. Example:

{
  "name": "@scope/widget-app",
  "private": true,
  "main": "dist/index.html"
}

If main is missing, the deploy command throws MissingMainError naming the package.

Project-ID requirement by environment

  • --preview is strict. Every selected package must have a configured VERCEL_PROJECT_ID_<NAME>. A missing ID throws MissingVercelProjectIdError with the expected env var name. Rationale: preview is the deliberate per-package test path, and silent skipping would hide misconfiguration.
  • --prod and --next are lenient. Packages without a project ID are silently skipped — useful for workspaces where only some private packages are deployable apps.

vercel.json

Per invocation, the deploy resolves a vercel.json for each package in this order:

  1. --vercel-config <path> if passed — wins for every package, no per-package override.
  2. <pkg.location>/vercel.json if it exists — the package ships its own.
  3. templates/deploy/vercel.json — the shipped default, { "rewrites": [{ "source": "/(.*)", "destination": "/" }] }.

Supported keys at the top level of any vercel.json: routes, rewrites, redirects, headers, cleanUrls, trailingSlash — the input shape accepted by @vercel/routing-utils.getTransformedRoutes. Anything else throws.

A fingerprint cache-control rule (s-maxage=31536000, immutable for *.<hash>.{css,js,png,jpg,webp,avif,svg}) is prepended to the route list before being written to .vercel/output/config.json.

Flags

  • --provider <name> — deploy provider. Defaults to vercel. Any other value throws "not implemented".
  • --exclude <names> — comma-separated package names to skip (e.g. --exclude @scope/a,@scope/b).
  • --vercel-config <path> — explicit vercel.json used for every package, overriding per-package files and the shipped template. Resolved relative to the current working directory.
  • --dry-run — runs the filter and produces the build-output artifact at <pkg.location>/.vercel/output/{static,config.json}, but skips vercel pull and vercel deploy. Doesn't need VERCEL_TOKEN / VERCEL_ORG_ID.

Example GitHub Actions step

- name: Deploy
  env:
    VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }}
    VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }}
    VERCEL_PROJECT_ID_WIDGET_APP: ${{ secrets.VERCEL_PROJECT_ID_WIDGET_APP }}
  run: |
    yarn global add vercel
    rangutopia client deploy --preview