The complete solution for transferring pnpm dependencies to air-gapped environments.
Getting pnpm projects into secure, offline, or air-gapped environments is challenging:
- No tooling for pnpm lockfiles - Existing airgap tools only work with npm's package-lock.json
- Complex dependency trees - pnpm's advanced resolution (peer deps, optionals, workspaces) makes manual approaches nearly impossible
- Registry population gap - No automated way to populate offline registries with pnpm project dependencies
pnpm-airgap is a standalone tool that:
- Reads
pnpm-lock.yamland downloads ALL dependencies - Publishes packages to any npm-compatible registry (Verdaccio, Nexus, Artifactory)
- Works as a single file - no
npm installrequired in airgap - Supports pnpm lockfile versions 5.x, 6.x, and 9.x
The CLI is a single file (~1.1MB) that runs with just Node.js:
# From npm (online)
npm pack pnpm-airgap
tar -xzf pnpm-airgap-*.tgz
# Use: node package/dist/cli.cjs
# Or build from source
pnpm install && pnpm build
# Use: node dist/cli.cjsnode cli.cjs fetch -l ./pnpm-lock.yaml -o ./packagesCopy the packages folder and cli.cjs to your air-gapped environment.
# Start your registry (e.g., Verdaccio)
verdaccio &
# Login
npm login --registry http://localhost:4873
# Publish all packages
node cli.cjs publish -p ./packages -r http://localhost:4873pnpm install --registry http://localhost:4873Run without arguments for a guided wizard:
node cli.cjs┌───────────────────────────────────────┐
│ pnpm-airgap v2.0.0 │
│ Transfer dependencies to air-gapped │
│ environments with ease │
└───────────────────────────────────────┘
? What would you like to do?
❯ 📦 Fetch dependencies from lockfile
📤 Publish packages to registry
🔄 Sync registries
📊 Export registry state
📖 Quick start guide
✖ Exit
node cli.cjs fetch [options]
Options:
-l, --lockfile <path> Path to pnpm-lock.yaml (default: ./pnpm-lock.yaml)
-o, --output <path> Output directory (default: ./airgap-packages)
-r, --registry <url> Source registry (default: https://registry.npmjs.org)
--registry-state <path> Registry state file for incremental fetching
--skip-optional Skip optional dependencies
--concurrency <number> Parallel downloads (default: 5)
--debug Enable debug outputnode cli.cjs publish [options]
Options:
-p, --packages <path> Packages directory (default: ./airgap-packages)
-r, --registry <url> Target registry (default: http://localhost:4873)
--concurrency <number> Parallel publishes (default: 3)
--no-skip-existing Publish all packages even if they exist
--dry-run Preview without publishing
--debug Enable debug outputnode cli.cjs sync [options]
Options:
-s, --source <url> Source registry URL
-d, --dest <url> Destination registry URL
-o, --output <path> Output directory
--scope <scope> Only sync packages in this scope
--download-only Only download, don't publish
--publish-only Only publish existing packages
--dry-run Preview without changesExport all packages from a registry to enable incremental fetching:
node cli.cjs registry-state export -r http://localhost:4873 -o registry-state.json
# Then use with fetch to skip existing packages
node cli.cjs fetch -l pnpm-lock.yaml --registry-state registry-state.jsonnode cli.cjs info ./packagesnode cli.cjs initCreate pnpm-airgap.config.json:
{
"fetch": {
"lockfilePath": "./pnpm-lock.yaml",
"outputDir": "./airgap-packages",
"concurrency": 5,
"registryUrl": "https://registry.npmjs.org",
"skipOptional": false
},
"publish": {
"packagesDir": "./airgap-packages",
"registryUrl": "http://localhost:4873",
"concurrency": 3,
"skipExisting": true
},
"sync": {
"sourceRegistry": "",
"destRegistry": "http://localhost:4873",
"outputDir": "./sync-packages",
"skipExisting": true
}
}| Feature | Description |
|---|---|
| Standalone Binary | Single 1.1MB file, runs with just Node.js - no npm install needed |
| Interactive Mode | Guided wizard for all commands |
| Auto-detection | Finds lockfiles and package directories automatically |
| Incremental Sync | Export registry state to skip already-synced packages |
| Smart Tagging | Auto-detects prerelease tags, handles version conflicts |
| Safety Blocks | Prevents accidental publish to public registries (npmjs.org) |
| Rate Limiting | Automatic backoff for 429 errors |
| Robust Parsing | Handles scoped packages, aliases, patches, peer deps |
Online Machine:
# Fetch all dependencies
node cli.cjs fetch -l pnpm-lock.yaml -o ./packages
# Create transfer archive
tar -czf transfer.tar.gz packages/ cli.cjsOffline Machine:
# Extract
tar -xzf transfer.tar.gz
# Start registry and login
verdaccio &
npm login --registry http://localhost:4873
# Publish
node cli.cjs publish -p ./packages -r http://localhost:4873
# Install your project
echo "registry=http://localhost:4873" > .npmrc
pnpm installAvoid re-downloading packages that already exist:
# Export state from airgap registry
node cli.cjs registry-state export -r http://verdaccio:4873 -o state.json
# Transfer state.json to online machine
# Fetch only missing packages
node cli.cjs fetch -l pnpm-lock.yaml --registry-state state.json -o ./packages
# Result: If lockfile needs 500 packages but 450 exist, only 50 are downloadedimport { fetchDependencies, publishPackages } from 'pnpm-airgap';
// Fetch
await fetchDependencies({
lockfilePath: './pnpm-lock.yaml',
outputDir: './packages',
registryUrl: 'https://registry.npmjs.org',
concurrency: 5,
});
// Publish
await publishPackages({
packagesDir: './packages',
registryUrl: 'http://localhost:4873',
concurrency: 3,
skipExisting: true,
});| Component | Supported Versions |
|---|---|
| Node.js | 18.0.0 or higher |
| pnpm lockfile | v5, v6, v9 |
| Registries | Verdaccio, Nexus, Artifactory, any npm-compatible |
| Platforms | Windows, Linux, macOS |
Both fetch and publish commands generate JSON reports:
metadata.json- Package list and metadatabundle-info.json- Download statisticspublish-report.json- Publishing results
# Verify you're logged in
npm whoami --registry http://localhost:4873
# Re-login if needed
npm login --registry http://localhost:4873Check bundle-info.json for download failures and ensure lockfile is current.
The tool automatically handles:
- Version conflicts (uses version-specific tags)
- Prerelease versions (applies correct tags)
- Already-existing packages (skips by default)
# Install dependencies
pnpm install
# Build
pnpm build
# Run tests
pnpm test
# Lint
pnpm lintMIT
Contributions are welcome! Please feel free to submit a Pull Request.