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

tsxmts

v0.3.0

Published

tsx, no setup — run standalone .mts/.mjs scripts with on-demand npm deps.

Readme

tsxmts

CI

tsx, no setup.

Run standalone .mts/.mjs scripts that pull in npm packages by themselves — no npm install, no package.json, no node_modules. Just a script file you can drop anywhere (or send to someone else) and run.

#!/usr/bin/env -S npx -y tsxmts
// hello.mts
import pc from 'npm:picocolors@^1.1.1';

console.log(pc.green('it just works'));
chmod +x hello.mts
./hello.mts
# [tsxmts] installing picocolors@^1.1.1...
# it just works

Run it again (or from a different directory) and it starts instantly — the package is cached, not reinstalled.

Why

Write .mts/.mjs scripts the same way you'd write a .sh script — drop it in your PATH, run it — except you get real npm packages instead of curl | jq gymnastics.

A single script file with no node_modules next to it normally can't import an npm package at all — Node's module resolution has nothing to walk up to. tsxmts fixes that without a build step, a bundler, or a project directory: write the npm:pkg@version specifier, run it with tsxmts, and the package gets installed once into a shared cache and resolved from there on every future run — from any directory, in any script.

This is for small personal scripts and one-off tools: the kind of thing you chmod +x and put in your PATH, not a project you npm init for.

Install

npm install -g tsxmts

(Or run it without installing anything via npx tsxmts.)

Usage

tsxmts your-script.mts
tsxmts your-script.mjs
tsxmts typecheck your-script.mts  # see "Typechecking" below

Or, more in the spirit of a standalone script, give it a shebang and run it directly:

#!/usr/bin/env -S npx -y tsxmts
chmod +x your-script.mts
./your-script.mts

env -S splits npx -y tsxmts back into separate arguments for the kernel — this is the same pattern plain tsx scripts use (#!/usr/bin/env -S npx tsx), just with dependency resolution included. npx finds tsxmts on your PATH if it's installed globally, or fetches it transparently otherwise; -y skips npx's "ok to install?" prompt so the script doesn't stall on first run.

Both extensions run through the same pipeline (TypeScript syntax — including enum, decorators, etc. — is always transpiled via tsx), so .mjs files work identically to .mts files; the file just happens not to use any TypeScript syntax.

Inside the script, import any npm package with an explicit version using the npm: specifier (matching Deno's npm: specifier):

import { execa } from 'npm:execa@^9.3.0';
import parseArgs from 'npm:minimist@^1.2.8'; // default export from a CJS package, no `.default` needed
import debounce from 'npm:lodash-es@^4.17.21';

Scoped packages work the same way:

import { z } from 'npm:@sindresorhus/is@^7.0.0';

A package subpath (an alternate entry point declared in its exports map) comes after the version, same as Deno's npm: specifier:

import { parse } from 'npm:csv-parse@^5.5.0/sync';

Plain imports (node:*, relative paths, already-installed packages) are left completely untouched — only npm: specifiers are intercepted.

Why a version is required

The specifier is deliberately explicit (npm:pkg@version, not just pkg) so that a script keeps working the same way every time you run it, on any machine, without silently drifting to whatever latest happens to resolve to that day. The version also doubles as inline documentation of what the script depends on — no separate package.json to keep in sync. Omitting the version (npm:pkg) is a hard error, not a latest fallback.

Cache

Packages are installed once into a shared cache directory, keyed by package name and version range — every tsxmts script on your machine shares it, and two scripts asking for incompatible ranges of the same package (e.g. npm:zod@^3 and npm:zod@^4) each get their own install instead of one silently overwriting the other:

  • Default: ~/.cache/tsxmts
  • Override: set TSXMTS_CACHE=/some/path

Delete the directory any time to force a clean reinstall.

Typechecking

tsxmts runs .mts scripts via tsx, which transpiles but never typechecks — a script with type errors runs anyway. tsc can't typecheck an npm:pkg@version specifier directly either, since it isn't a specifier Node or npm understands outside of tsxmts's own resolve hook.

tsxmts typecheck script.mts

installs the script's npm: dependencies into a throwaway project (rewriting each specifier to the plain package import tsc can resolve) and runs tsc against it, reporting errors against the original file. It exits non-zero on a type error, so it composes with CI or a pre-commit check:

tsxmts typecheck script.mts && ./script.mts

Editor support

Since a plain editor's TypeScript doesn't understand npm:pkg@version either, it reports TS2307: Cannot find module on every npm: import — noise tsxmts typecheck already accounts for. If your script lives in a project with its own tsconfig.json (rather than a standalone script with no project at all), install tsxmts as a dev dependency and add its Language Service Plugin to silence just that noise, without touching the scripts themselves:

npm install -D tsxmts
{
  "compilerOptions": {
    "plugins": [{ "name": "tsxmts/ts-plugin" }]
  }
}

The plugin only hides TS2307 for a string literal that's both prefixed with npm: and actually used as a module specifier (an import/export from, or a dynamic import(...)) — a genuinely unresolved import still gets flagged, and every other diagnostic (including real type errors) is untouched. It only changes what the editor shows; a plain tsc run never loads plugins, so tsxmts typecheck remains the real check.

This relies on the classic TypeScript Language Service Plugin API, which TypeScript 7's native (Go) compiler doesn't expose yet — the plugin only takes effect while your editor's TypeScript is still running the classic (JS) implementation.

Examples

  • examples/qr.mts — the simplest shape for a tsxmts script: one dependency, minimist argument parsing, and a --help/usage message. Prints a scannable QR code to the terminal.

    ./examples/qr.mts "https://example.com"
    ./examples/qr.mts --help
  • examples/gh-repos.mts — TypeScript. Looks up a GitHub user's most-starred repos and prints a formatted table, combining two npm packages (minimist for flags, columnify for the table) with a typed fetch response.

    ./examples/gh-repos.mts sindresorhus --limit 5
  • examples/disk-usage.mjs — plain JavaScript. A colored bar chart of the biggest subdirectories under a path, shelling out to du with execa and rendering with picocolors. Same pipeline as the .mts example, no TypeScript syntax needed.

    ./examples/disk-usage.mjs ~/Downloads

All three are executable and carry a #!/usr/bin/env -S npx -y tsxmts shebang, so they run directly — no tsxmts prefix needed.

None of the examples has a package.json or node_modules next to it — the only files involved are the scripts themselves. This is the kind of small, personal CLI tool tsxmts is for: things you'd normally either skip writing (too much setup for a 20-line script) or leave permanently half-npm installed in some folder you can't remember the location of.

How it works

tsx transpiles TypeScript using Node's Module Customization Hooks API (node:module's register()). tsxmts registers one more hook alongside it that intercepts npm:pkg@version specifiers in the resolve step: if the package isn't in the shared cache yet, it runs npm install --prefix <cache> once, then resolves straight to the installed file. Everything after that — loading, CJS/ESM interop — is handled by Node's normal module loading, so default exports and interop behave exactly like a regular node_modules install.

Requirements

Node.js >= 20.6 (node:module's register() API).

Development

npm install
npm run check   # lint + typecheck + test
  • Lint/format: Biome (npm run lint, npm run lint:fix)
  • Types: tsc --noEmit (npm run typecheck) — dev-only; hooks.mjs/bin.mjs never go through a build step, tsx transpiles at runtime as usual
  • Tests: the built-in node:test runner (npm run test) — the CLI tests spawn bin.mjs against a real npm package in a temporary cache directory, so they need network access on first run
  • CI: .github/workflows/ci.yml runs npm run check on a Node 20.6 / 22 / 24 matrix for every push and pull request

Releasing

Publishing (.github/workflows/publish.yml) runs on every published GitHub Release, authenticating to npm via trusted publishing (OIDC) — no NPM_TOKEN secret involved.

One-time setup, because npm requires a package to already exist before a trusted publisher can be registered for it:

  1. Publish the first version manually: npm login && npm publish.
  2. On npmjs.com, go to the package's Settings → Trusted Publisher, add a GitHub Actions publisher for this repo with workflow filename publish.yml.

From then on, releasing is just:

  1. Bump version in package.json to X.Y.Z and commit.
  2. Create a GitHub Release with tag vX.Y.Z (the workflow checks that the tag matches package.json's version before publishing).

License

MIT