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

@williamthorsen/toolbelt.nodejs

v3.1.0

Published

Utilities for inspecting Node.js runtimes, toolchains, and the commands that they install

Readme

@williamthorsen/toolbelt.nodejs

Utilities for inspecting Node.js runtimes, toolchains, and the commands that they install.

Release notes — v3.1.0 (2026-09-17)

🎉 Features

  • Add the tb-node CLI that reports stranded asdf node shims (#347)

    • Adds to @williamthorsen/toolbelt.nodejs the tb-node CLI, whose asdf-shims subcommand reports every command stranded by an asdf nodejs version switch: one installed with npm install --global under an earlier version, whose shim stays on PATH and fails when it is invoked.
    • Exports the functions behind the report from @williamthorsen/toolbelt.nodejs/candidate, among them listStrandedAsdfShims, findExecutableOnPath, and resolveNpmPackageOfBin.

Installation

pnpm add @williamthorsen/toolbelt.nodejs

Requires Node.js 24 or later.

CLI

The package ships a tb-node command. Its first subcommand reports the asdf shims that a nodejs version switch has stranded: A CLI installed with npm install --global under an earlier version keeps its shim on PATH, and the shim fails when invoked under a version that lacks the package.

pnpm add --global @williamthorsen/toolbelt.nodejs   # puts tb-node on PATH
npx @williamthorsen/toolbelt.nodejs asdf-shims      # or run it without installing

The short npx form works because the package declares exactly one bin, which npm falls back to, whatever its name.

tb-node --help, each subcommand's --help, and tb-node --version report the surface and the installed version.

tb-node asdf-shims

Reports every asdf shim that names the nodejs plugin but not the active version, classifying each as an orphan, which shadows another executable of that name on PATH, or as having no other provider, and prints the commands that provide or remove it.

tb-node asdf-shims
# nodejs 24.20.0 (asdf): 1 stranded shim in /Users/me/.asdf/shims
#
# pn: stranded, no other provider on PATH
#   provided by nodejs 24.18.1 (npm package pnpm)
#   to provide it under 24.20.0:
#     npm install --global pnpm
#     asdf reshim nodejs
#   to remove it:
#     ASDF_NODEJS_VERSION=24.18.1 npm uninstall --global pnpm
#     asdf reshim nodejs

The active version is the one running the command, read from its install path, so the check spawns no process and needs no repository. A node outside asdf, such as one from Homebrew, is reported as not applicable rather than as clean.

The remedies end with asdf reshim nodejs in its versionless form on purpose: It removes every shim and regenerates them from the installed versions, whereas asdf reshim nodejs <version> merges into an existing shim and keeps its stale lines. A shim that corepack provides gets corepack enable and corepack disable in place of the npm commands, led by npm install --global corepack where the active version ships no corepack, as Node 25 and later do not. A shim that an installed version of another asdf plugin also provides is not reported, since asdf may resolve the command there. One whose package cannot be read from the install gets the removal of the executable itself.

Exit codes

| Code | Meaning | | ---- | ------------------------------------------------------------------ | | 0 | No stranded shim | | 1 | One or more stranded shims, with the report on stdout | | 2 | Usage or validation error, with the message on stderr | | 3 | Not applicable: the running node is not an asdf install, on stderr |

if ! tb-node asdf-shims >/dev/null; then
  echo 'stranded shims: run `tb-node asdf-shims` for the remedies' >&2
fi

listStrandedAsdfShims

listStrandedAsdfShims(options: {
  dataDir: string;
  pathDirs: readonly string[];
  plugin: string;
  version: string;
}): StrandedAsdfShim[];

interface StrandedAsdfShim {
  readonly backingPackage: string | undefined;
  readonly name: string;
  readonly otherProvider: string | undefined;
  readonly providingVersions: readonly string[];
  readonly shimPath: string;
}

Lists the shims of a plugin that a version of it does not provide, which is what the CLI reports. Each entry names the versions that provide the command, the first other executable of that name on pathDirs outside the shims directory, and the npm package that installed it under a providing version.

import { findAsdfInstall, listStrandedAsdfShims } from '@williamthorsen/toolbelt.nodejs/candidate';

const install = findAsdfInstall(process.execPath);
if (install !== undefined) {
  const shims = listStrandedAsdfShims({ ...install, pathDirs: process.env['PATH']?.split(':') ?? [] });
  // [{ name: 'pn', providingVersions: ['24.18.1'], backingPackage: 'pnpm', otherProvider: undefined, ... }]
}

The function is generic over the plugin except for backingPackage, which reads the bin symlink that npm leaves; under another plugin it is undefined.

findAsdfInstall

findAsdfInstall(execPath: string): { dataDir: string; plugin: string; version: string } | undefined;

Reads the asdf data directory, plugin, and version out of an executable's install path, or returns undefined for an executable that asdf does not manage.

import { findAsdfInstall } from '@williamthorsen/toolbelt.nodejs/candidate';

findAsdfInstall('/Users/me/.asdf/installs/nodejs/24.20.0/bin/node');
// { dataDir: '/Users/me/.asdf', plugin: 'nodejs', version: '24.20.0' }

findAsdfInstall('/opt/homebrew/bin/node');
// undefined

Under asdf, process.execPath is the real binary rather than the shim, so a running program can identify its own install without spawning asdf which.

parseAsdfShim

parseAsdfShim(contents: string): { plugin: string; version: string }[];

Lists the providers that a shim's # asdf-plugin: header declares, in file order, under asdf's own parsing rule.

import { parseAsdfShim } from '@williamthorsen/toolbelt.nodejs/candidate';

parseAsdfShim('#!/usr/bin/env bash\n# asdf-plugin: nodejs 24.20.0\n# asdf-plugin: nodejs 24.19.0\nexec asdf exec "pnpm" "$@"');
// [{ plugin: 'nodejs', version: '24.20.0' }, { plugin: 'nodejs', version: '24.19.0' }]

The header is an asdf implementation detail rather than a documented format; asdf shimversions <command> is the supported query, printing one <plugin> <version> line per provider.

findExecutableOnPath

findExecutableOnPath(name: string, dirs: readonly string[], options?: { excludeDir?: string }): string | undefined;

Finds the first executable file named name in a list of directories, or undefined when none provides one. A directory whose real path is that of excludeDir is passed over, so a differently spelled PATH entry for it is excluded too.

import { findExecutableOnPath } from '@williamthorsen/toolbelt.nodejs/candidate';

findExecutableOnPath('node', process.env['PATH']?.split(':') ?? [], { excludeDir: '/Users/me/.asdf/shims' });
// '/opt/homebrew/bin/node', or undefined where only the shim provides it

resolveNpmPackageOfBin

resolveNpmPackageOfBin(binPath: string): string | undefined;

Names the npm package that installed a bin, from the symlink that npm leaves in a bin directory, or undefined where the path is not such a symlink.

import { resolveNpmPackageOfBin } from '@williamthorsen/toolbelt.nodejs/candidate';

resolveNpmPackageOfBin('/Users/me/.asdf/installs/nodejs/24.18.1/bin/pn');
// 'pnpm', from the target ../lib/node_modules/pnpm/bin/pnpm.mjs