CI/CD
Use snpm in continuous integration and deployment pipelines
snpm is built for CI: deterministic installs, a hot path that exits in milliseconds when nothing changed, parallelism that scales with available bandwidth, and SARIF audit output for security tabs.
Quick start
# Install snpm
npm install -g snpm
# Reproducible install
snpm install --frozen-lockfile
# Build / test
snpm run build
snpm run testIn a monorepo:
snpm install --frozen-lockfile
snpm run build -r
snpm run test --filter "[origin/main]" # only what changedGitHub Actions
name: CI
on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- name: Install snpm
run: npm install -g snpm
- name: Cache snpm store
uses: actions/cache@v4
with:
path: ~/.local/share/snpm
key: ${{ runner.os }}-snpm-${{ hashFiles('**/snpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-snpm-
- name: Install dependencies
env:
SNPM_MIN_PACKAGE_AGE_DAYS: "7"
SNPM_ALLOW_SCRIPTS: "esbuild,sharp"
run: snpm install --frozen-lockfile
- name: Build
run: snpm run build
- name: Test
run: snpm run testAudit with SARIF
- name: Audit dependencies
run: snpm audit --format sarif > snpm-audit.sarif
continue-on-error: true
- name: Upload SARIF
uses: github/codeql-action/upload-sarif@v3
if: always()
with:
sarif_file: snpm-audit.sarifGitLab CI
image: node:20
cache:
key:
files:
- snpm-lock.yaml
paths:
- ~/.local/share/snpm
stages:
- install
- test
install:
stage: install
script:
- npm install -g snpm
- snpm install --frozen-lockfile
test:
stage: test
variables:
SNPM_MIN_PACKAGE_AGE_DAYS: "7"
script:
- npm install -g snpm
- snpm install --frozen-lockfile
- snpm run testCircleCI
version: 2.1
jobs:
build:
docker:
- image: cimg/node:20.0
steps:
- checkout
- restore_cache:
keys:
- snpm-{{ checksum "snpm-lock.yaml" }}
- snpm-
- run: npm install -g snpm
- run: snpm install --frozen-lockfile
- run: snpm run build
- run: snpm run test
- save_cache:
paths:
- ~/.local/share/snpm
key: snpm-{{ checksum "snpm-lock.yaml" }}
workflows:
build-and-test:
jobs:
- buildDocker
FROM node:20-slim AS deps
WORKDIR /app
RUN npm install -g snpm
COPY package.json snpm-lock.yaml ./
COPY snpm-workspace.yaml* snpm-catalog.yaml* ./
COPY packages packages
COPY apps apps
RUN snpm install --production --frozen-lockfile
FROM node:20-slim
WORKDIR /app
COPY --from=deps /app /app
CMD ["node", "apps/web/dist/index.js"]For monorepos you can copy only the workspace metadata first to cache the install layer, then copy source files for the build step.
Common patterns
Production install
snpm install --production --frozen-lockfileSkips devDependencies and refuses to touch the lockfile.
Min package age
SNPM_MIN_PACKAGE_AGE_DAYS=7 snpm install --frozen-lockfileRefuses to use versions published within the last 7 days. Combine with --frozen-lockfile to be sure CI never silently bumps to a fresh release.
Bumping concurrency
SNPM_REGISTRY_CONCURRENCY defaults to 128. Most CI runners can push it higher on fast networks; lower it on memory-constrained runners or when the registry rate-limits.
Caching the store
| Cache | What to cache |
|---|---|
| Shared package store | ~/.local/share/snpm (Linux), ~/Library/Application Support/io.snpm.snpm (macOS) — or whatever snpm config reports as the data dir |
| Metadata cache | Already inside the data dir |
Key the cache on snpm-lock.yaml so it invalidates when dependencies change. snpm only writes new entries; cache restore is purely additive.
Workspaces
# install everything once
snpm install --frozen-lockfile
# build everything
snpm run build -r
# only run tests for projects whose files changed since main
snpm run test --filter "[origin/main]"snpm publish -r --filter ./packages publishes every project under packages/ (handy for release pipelines).
Authenticating to a private registry
GitHub Actions example:
- name: Install dependencies
env:
SNPM_CONFIG_REGISTRY: https://npm.mycompany.com
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
run: snpm install --frozen-lockfileOr commit a .snpmrc that interpolates env vars:
registry=https://npm.mycompany.com/
//npm.mycompany.com/:_authToken=${NPM_TOKEN}Troubleshooting
--frozen-lockfile fails
The lockfile is out of date. Run snpm install locally, commit the resulting snpm-lock.yaml, and push.
Install scripts blocked
snpm blocks dependency lifecycle scripts by default. Add the trusted package to the allow-list:
SNPM_ALLOW_SCRIPTS="esbuild,sharp" snpm install --frozen-lockfileAfter widening the allow-list, run snpm rebuild to apply it to packages that already extracted.
Slow cold installs
- Make sure the store cache key is correct (
hashFiles('**/snpm-lock.yaml')). - Bump
SNPM_REGISTRY_CONCURRENCYif the network has headroom. - Use
--prefer-frozen-lockfileon retries to reuse the lockfile but re-resolve only what changed.
Authentication failures
- Verify the auth token is exported in the job's environment.
- Confirm
snpm configshows the expected default registry anddefault authstatus. - For scoped registries, make sure both
@scope:registry=...and the matching//host:_authToken=...are set.