For pnpm users
scpm should be a drop-in replacement for pnpm projects. There are only minor differences in behavior.
Behavior differences
A handful of commands behave differently in a way that's worth knowing before you ship an scpm-based workflow:
| Command | Difference |
|---|---|
scpm run <script> | Checks install staleness and auto-installs before running. pnpm run does not. |
scpm test | Auto-installs first, then runs the test script — equivalent to pnpm install-test in one command. |
scpm exec <bin> | Auto-installs on stale state before running. pnpm exec does not install. |
scpm install (new project) | Creates scpm-lock.yaml if there's no existing lockfile. pnpm creates pnpm-lock.yaml. In an existing pnpm project, scpm reads and writes pnpm-lock.yaml in place. |
Everything else — add, remove, update, dlx, list, why, pack,
publish, approve-builds — matches pnpm's behavior.
Command map
Do not translate every pnpm install && pnpm run ... habit literally.
scpmr <script>, scpm test, and scpm exec <bin> check install freshness
and install first only when needed. Use scpmx <pkg> for one-off tools.
| pnpm | scpm | Notes |
|---|---|---|
pnpm install | scpm install | Reads and updates an existing pnpm-lock.yaml in place. Only new projects (no supported lockfile on disk yet) default to scpm-lock.yaml. |
pnpm add react | scpm add react | Supports dependency sections, exact pins, peer deps, workspace root adds, and globals. |
pnpm remove react | scpm remove react | Removes from the manifest and relinks. |
pnpm update | scpm update | Updates all or named direct dependencies. |
pnpm run build | scpm run build | Runs scripts with an auto-install staleness check first. |
pnpm test | scpm test | Shortcut for the test script; scpm auto-installs first (equivalent to pnpm install-test). |
pnpm exec vitest | scpm exec vitest | Runs local binaries with project node_modules/.bin on PATH. |
pnpm dlx cowsay hi | scpmx cowsay hi | Installs into a throwaway environment and runs the binary. |
pnpm list | scpm list | Supports depth, JSON, parseable, long, prod/dev, and global modes. |
pnpm why debug | scpm why debug | Shows reverse dependency paths. |
pnpm pack | scpm pack | Creates a publishable tarball with npm-style file selection. |
pnpm publish | scpm publish | Publishes to the configured registry; workspace fanout is available via filters. |
pnpm approve-builds | scpm approve-builds | Records packages allowed to run lifecycle build scripts. |
Files and directories
| Concept | pnpm | scpm |
|---|---|---|
| Default lockfile (new projects) | pnpm-lock.yaml | scpm-lock.yaml |
| Virtual store | node_modules/.pnpm/ | node_modules/.scpm/ |
| Global content-addressable store | ~/.pnpm-store/ | $XDG_DATA_HOME/scpm/store/v1/ (defaulting to ~/.local/share/scpm/store/v1/). Run scpm store path to see the resolved location. |
| Install state | node_modules/.modules.yaml | node_modules/.scpm-state |
| Workspace manifest | pnpm-workspace.yaml | scpm-workspace.yaml |
scpm reads pnpm v11 YAML files for compatibility. scpm-lock.yaml and
scpm-workspace.yaml use pnpm-compatible shapes today but are the long-term
contract and may diverge over time.
scpm never touches pnpm's node_modules/.pnpm/ or ~/.pnpm-store/. The two
virtual stores can coexist under node_modules. For the lockfile and
workspace YAML, scpm reads and writes whichever file already exists on disk
— pnpm-lock.yaml keeps getting updates in place, and an existing
pnpm-workspace.yaml is mutated in place (scpm does not spawn a parallel
scpm-workspace.yaml alongside it). When neither workspace yaml exists,
scpm creates scpm-workspace.yaml.
What's different
- Separate install locations. Installs go into
node_modules/.scpm/and$XDG_DATA_HOME/scpm/store/(defaulting to~/.local/share/scpm/store/) instead of pnpm's.pnpm/and~/.pnpm-store/. If a project already has a pnpm-builtnode_modules, scpm installs alongside — the two virtual stores live side by side. - Default YAML filenames for new projects. A project with no lockfile
yet gets
scpm-lock.yaml. If it already haspnpm-lock.yaml(or any other supported lockfile —package-lock.json,npm-shrinkwrap.json,yarn.lock,bun.lock), scpm reads and writes that file in place. Install auto-adds unreviewed dependency builds to the workspace yaml'sallowBuildsmap withfalse;scpm approve-buildsflips reviewed entries totrue(matching pnpm v11). When no workspace yaml exists, scpm createsscpm-workspace.yaml; an existingpnpm-workspace.yamlis mutated in place. - Build approvals. Dependency lifecycle script approval follows pnpm
v11's allowlist model. Use explicit policy fields in
package.jsonorscpm-workspace.yamlto opt in. scpm can also run approved dependency builds in a jail with package glob permissions for env, path, and network exceptions. - Speed. See the benchmarks.
Supported pnpm lockfile versions
scpm reads and writes pnpm-lock.yaml at lockfile version 9 — the
format shipped by pnpm v9 and later. Older pnpm lockfiles (versions 5, 6,
7, and 8, used by pnpm 7.x and 8.x) are not supported and will cause scpm
to refuse the install.
To upgrade an older pnpm lockfile, run a modern pnpm once to convert it:
npx pnpm@latest install
That rewrites pnpm-lock.yaml at v9. Commit the result, then switch to
scpm install.
Out of scope
scpm does not manage Node.js itself. Runtime-management commands like
pnpm env, pnpm runtime, pnpm setup, and pnpm self-update are
intentionally not implemented — use mise to
install and switch Node versions:
mise use node@22
