snpmv2026.5.16

Workspaces

Monorepo support, selectors, and workspace configuration

snpm has first-class workspace support: a single lockfile at the workspace root, fan-out commands, dependency-graph aware selectors, and shared catalogs.

Discovery

snpm discovers a workspace by walking up from the current directory and looking for, in order:

  1. snpm-workspace.yaml
  2. pnpm-workspace.yaml
  3. package.json with a workspaces field (string array or { packages: [...] } form)

Whichever is found first wins, and its parent directory becomes the workspace root.

snpm-workspace.yaml

snpm-workspace.yaml
packages:
  - "packages/*"
  - "apps/*"

# Optional shared catalogs
catalog:
  react: ^18.2.0
  typescript: ^5.4.0

catalogs:
  build:
    vite: ^5.0.0
  testing:
    vitest: ^1.0.0

# Optional: hoisting override for the workspace
hoisting: single-version

# Optional: dependency script allow/deny lists
onlyBuiltDependencies:
  - esbuild
  - sharp
ignoredBuiltDependencies:
  - fsevents

# Optional: packages that must stay in the project-local virtual store
disableGlobalVirtualStoreForPackages:
  - next
  - vite

pnpm-workspace.yaml is parsed with the same shape, plus catalog / catalogs aliases.

Example layout

my-monorepo/
├── snpm-workspace.yaml
├── package.json
├── snpm-lock.yaml           ← one lockfile at the root
├── packages/
│   ├── ui/package.json
│   └── utils/package.json
└── apps/
    └── web/package.json

workspace: protocol

Local packages can reference each other with workspace::

apps/web/package.json
{
  "dependencies": {
    "@acme/ui": "workspace:*",
    "@acme/utils": "workspace:^"
  }
}
VersionMeaning
workspace:*Any version of the local package
workspace:^Caret range of the local package's current version
workspace:~Tilde range of the local package's current version
workspace:1.2.3Specific local version

On publish, these specifiers are rewritten to concrete versions.

Common commands

# install everything from the workspace root
snpm install

# install for a single workspace project
snpm install -w @acme/api

# run a script in every workspace project
snpm run build -r

# run a script in a subset (see selector syntax below)
snpm run test --filter "@acme/*"

# add/remove across workspaces
snpm add -r -D vitest
snpm remove -r lodash

# inspect across workspaces
snpm outdated -r
snpm why typescript -r
snpm list -r

Every workspace-aware command supports the same selectors: add, remove, run, exec, upgrade, outdated, list, why, and publish.

Selector syntax

--filter and --filter-prod accept a dependency-graph-aware selector grammar. Multiple --filter values combine — positive selectors union, negative selectors subtract.

PatternMeaning
pkg-nameExact project name
@scope/*, foo-?Glob over project names
./packages/api, ../lib, /abs/pathMatch by project path (relative to the workspace root, or absolute)
pkg...The package and everything it transitively depends on
...pkgThe package and everything that transitively depends on it
^pkg... / pkg^...Same as above, but exclude the seed itself
[<git-ref>]Projects with files changed since <git-ref> (runs git diff --name-only <ref> from the workspace root)
!pkgExclude (must be combined with at least one positive selector)

--filter-prod uses the same grammar but walks only dependencies / optionalDependencies for graph operators, so it ignores devDependency edges.

Examples

# every project under packages/api
snpm run test --filter ./packages/api

# api plus its dependencies
snpm run build --filter api...

# everything that depends on the shared utils package
snpm run test --filter ...@acme/utils

# everything affected since the last main commit
snpm run test --filter "[origin/main]"

# everything in the @acme scope except docs
snpm run lint --filter "@acme/*" --filter "!@acme/docs"

Catalogs

Define dependency versions once at the workspace root and reference them with the catalog: protocol:

snpm-catalog.yaml
catalog:
  react: ^18.2.0
  typescript: ^5.4.0

catalogs:
  build:
    vite: ^5.0.0
  testing:
    vitest: ^1.0.0
apps/web/package.json
{
  "dependencies": {
    "react": "catalog:"
  },
  "devDependencies": {
    "vite": "catalog:build",
    "vitest": "catalog:testing"
  }
}

When snpm-workspace.yaml and snpm-catalog.yaml both define a key, the workspace file wins. See Catalog for the full reference.

Overrides

Pin transitive dependency versions for the whole workspace via either snpm-overrides.yaml or package.json:

snpm-overrides.yaml
overrides:
  lodash: ^4.17.21
  "@babel/core": ^7.23.0
package.json
{
  "snpm": {
    "overrides": { "lodash": "^4.17.21" }
  },
  "pnpm": {
    "overrides": { "axios": "^1.6.0" }
  }
}

Both snpm.overrides and pnpm.overrides are honored.

Per-project install

You can also cd into a workspace project and run snpm install from there. snpm will still discover the workspace root and write a single lockfile at the top.

Best practices

  • Scope your packages (@acme/ui, @acme/utils) so selectors and link names stay tidy.

  • Use catalogs for shared deps to eliminate version drift across packages.

  • Reach for [git-ref] selectors in CI to only build what changed: snpm run test --filter "[origin/main]".

  • Pin the snpm version in the workspace root package.json:

    { "packageManager": "snpm@2026.5.16" }

    Combined with snpm-switch, every contributor and CI job will run the same snpm build.

On this page