jpm
jpm
A fast, small, secure-by-default package manager for JavaScript
macOS / LinuxWindows
`$ curl -fsSL https://getjpm.sh | sh`Copy
`> irm https://getjpm.sh/install.ps1 | iex`Copy
Every line written by Claude Code. Directed by one engineer. Checked against everyone else's tests.
- \~2 MBone binary, no runtime to install
- 1.22 scold nuxt install on GitHub CI
- 34 MBmemory used to install nuxt on GitHub CI
- `package-lock.json` and `npm-shrinkwrap.json` (npm 7+), `pnpm-lock.yaml` (pnpm 9+) and `bun.lock` are carried over as they are: the same versions and the same tree, with no registry lookups.
- `yarn.lock`, from yarn 1 or yarn 2 and later, keeps every version yarn picked. jpm asks the registry only for what yarn.lock doesn't record: peers, platforms and bins.
- Your old lockfile is left untouched. Delete it when you're ready.
- CI keeps working during the switch: `jpm ci` and `jpm install --frozen-lockfile` install from the old lockfile as it is until `jpm.lock` is committed.
- It also reads workspaces, `pnpm-workspace.yaml`, catalogs, overrides and resolutions, and patches.
- npm`package-lock.json`
- pnpm`pnpm-lock.yaml`
- yarn`yarn.lock`
- bun`bun.lock`
Speed
Fast where your CI spends its time
Three real projects, four install phases, eight package managers, against the live npm registry. On GitHub CI jpm is the fastest in 9 of 12 cells; the other three are shown as measured.
Machine
GitHub CI, LinuxmacOS, M4 MaxWindows 11
Phase
coldwarmcirepeat
Project
nitronuxtnext
No cache, no lockfile, no node\_modules.
Repeat installs were not measured on Windows.
Install wall time, shorter is fasterGitHub CI, Linux · nuxt · 591 packages
1. jpm1.22 s 2. bun1.28 s 3. pnpm1.72 s 4. aube3.00 s 5. upm3.52 s 6. deno7.64 s 7. yarn9.63 s 8. npm18.54 s
| GitHub CI, Linux, cold, nuxt · 591 packages| Package manager | Wall time | | ------------------------------------------------------------- | --------- | | jpm | 1.22 s | | bun 1.4.2 | 1.28 s | | pnpm 12.8.1 | 1.72 s | | aube 2.6.0 | 3.00 s | | upm 1.3.1 | 3.52 s | | deno 2.9.6 | 7.64 s | | yarn 4.18.1 | 9.63 s | | npm 12.1.0 | 18.54 s |
jpm is the fastest here, 15× faster than npm.
Defender scans every file an install writes, and it scans files written by node.exe (npm, upm) far more cheaply than files written by native tools like jpm, aube and pnpm. On Windows jpm is the fastest on next, not yet on nuxt.
Wall time, medians: ubuntu-latest with 4 cores (5 runs), macOS on an M4 Max (5 runs) and Windows 11 with Defender on (3 runs), 2026-09-30 and 2026-10-01. Full tables, with CPU time, in docs/benchmarks.md.
Get started
Switch in one command
Run `jpm install` in your project. jpm imports the lockfile you have into its own `jpm.lock`, with the same versions.
Older formats, out-of-date lockfiles and the rest: docs/migrating.md.
jpm`jpm.lock`
jpm-lock 2
package @babel/[email protected]
integrity sha512-…
dep @babel/generator 7.29.8$ cd your-project
$ jpm install # reads package-lock.json / pnpm-lock.yaml / yarn.lock / bun.lock → writes jpm.lockLean
Small enough to forget it's there
One binary with nothing behind it, an install that holds little in memory, and a store that keeps each file once.
Not every number goes jpm's way: npm and yarn keep compressed tarballs, so their caches are smaller. jpm keeps its files unpacked, ready to link.
GitHub CI, Linux, 2026-09-30. Binary sizes are the README's round figures.
Binary size, approximate
1. jpm\~2 MB 2. pnpm\~60 MB 3. bun\~80 MB 4. aube\~150 MB
| Binary size, approximate| jpm | \~2 MB | | ------------------------------ | -------- | | pnpm | \~60 MB | | bun | \~80 MB | | aube | \~150 MB |
Peak memory, nuxt ci install
1. jpm34 MB 2. bun89 MB 3. pnpm184 MB 4. npm387 MB
| Peak memory, nuxt ci install| jpm | 34 MB | | ---------------------------------- | ------ | | bun | 89 MB | | pnpm | 184 MB | | npm | 387 MB |
Disk after a cold nuxt install, node\_modules and store
1. jpm244 MB 2. bun261 MB 3. pnpm290 MB 4. npm443 MB
| Disk after a cold nuxt install, node\_modules and store| jpm | 244 MB | | ------------------------------------------------------------- | ------ | | bun | 261 MB | | pnpm | 290 MB | | npm | 443 MB |
CI cache after a cold next install
1. npm194 MB 2. yarn328 MB 3. jpm347 MB 4. pnpm433 MB 5. bun466 MB
| CI cache after a cold next install| npm | 194 MB | | ---------------------------------------- | ------ | | yarn | 328 MB | | jpm | 347 MB | | pnpm | 433 MB | | bun | 466 MB |
Storage
Each file stored once. Each package built once.
jpm keeps one store per machine and builds each package's directory once, for every project that uses it. Installing again is mostly making links.
1. ### A content-addressed store Each tarball is checked against its integrity, then unpacked once into `~/.jpm/store`. Every file is kept once, by its hash, read-only. 2. ### A global virtual store Each package is built once as an entry: `name@version`, plus a short digest of its dependency graph when it has dependencies. Its files are hardlinked from the store, and its dependencies are linked beside it, so it can import only what it declared. 3. ### Your node\_modules A project's `node_modules` links to those entries. A warm install makes only these few links: 4 ms for nitro on GitHub CI. 4. ### Every other project The next project on the machine links to the same entries. When nothing changed, a repeat install checks a few links and is done in 1–2 ms.
\~/.jpm/storefiles, by hash3fa19c07e4b271d80b5ec6a3hardlinks\~/.jpm/store/v1/linksentries, built once[\[email protected\]](https://getjpm.sh/cdn-cgi/l/email-protection)node\_modules/debugnode\_modules/ms[\[email protected\]](https://getjpm.sh/cdn-cgi/l/email-protection)node\_modules/msapp-one/app-two/node\_modules/debugnode\_modules/debuglinksPause the animation
Next.js and Nuxt: inside the project
Next's Turbopack compiles nothing outside the project, and Nuxt imports packages it doesn't declare. For a project that depends on next or nuxt, jpm builds the entries in `node_modules/.jpm` instead, still hardlinked from the store.
node_modules/next -> .jpm/[email protected]…/node_modules/next
node_modules/.jpm/[email protected]…/node_modules/next/
node_modules/.jpm/[email protected]/node_modules/react/The full picture: docs/how-it-works.md.
Security
Secure by default
The safe setting is the one you get without asking. Loosening one is your call, made in your own settings, not a package's or a cloned repository's.
Secure by default isn't a sandbox: a script you approve runs as you. The details: docs/install-scripts.md and docs/configuration.md.
The story
Written by Claude Code. Checked by everyone else's tests.
It started with an episode of the Syntax podcast that held up upm as faster and smaller than the rest. Small, because it runs on Node.js. So: what if it were ported to Rust and made small for real? And what would it take to turn an AI's one-shot into something you'd trust in CI?
Claude Code, running Claude Opus 5.5, wrote every line, its own TLS and crypto included. JT Turner, an engineer of 27 years, made the calls. When Claude pushed back on writing our own TLS, the condition was: test it like we didn't trust it. So every "it works" is checked against someone else's tests: Wycheproof, the RFCs, and the suites of npm, pnpm, yarn and bun.
The numbers decide. An HTTP/2 client was built, benchmarked on GitHub's runners and a desktop, found slower and hungrier for CPU, and deleted.
Is jpm perfect? No. Perfect doesn't exist. There's good enough, there's great, and there's tested enough to know which one you have.
Try jpm in your CI. Then go build the thing you didn't think you had time for.
By the numbers
Installs
What this counts
Each time getjpm.sh hands out an install script, it adds one to a count kept for that script, the path asked for and the day. Nothing else is recorded: no IP address, no browser details, no cookies, no identifiers. We can't tell who you are or tell one person from another, and a download isn't always an install. The binary count is GitHub's own tally of downloads of jpm's release files. Both numbers refresh about once an hour.
