PNPM Multi-Package Patterns
Examples of managing Node.js tools that require plugins or peer dependencies.
Basic PNPM App
A simple Node.js tool with no extra dependencies:
const mapOfApps: BinManager.MapOfApps = {
mmdc: {
node: {
packageName: "@mermaid-js/mermaid-cli",
binPath: "node_modules/.bin/mmdc",
version: "11.12.0",
},
},
};
This generates a package.json in the isolated environment:
{
"dependencies": {
"@mermaid-js/mermaid-cli": "11.12.0"
}
}
Apps with Plugin Dependencies
Many Node.js tools rely on plugins installed as siblings in node_modules.
Use the dependencies field to install them together:
const mapOfApps: BinManager.MapOfApps = {
eslint: {
node: {
packageName: "eslint",
binPath: "node_modules/.bin/eslint",
version: "9.17.0",
dependencies: {
"eslint-plugin-import": "2.31.0",
"eslint-plugin-react": "7.37.3",
"@typescript-eslint/eslint-plugin": "8.18.2",
"@typescript-eslint/parser": "8.18.2",
},
},
},
};
Generated package.json:
{
"dependencies": {
"eslint": "9.17.0",
"eslint-plugin-import": "2.31.0",
"eslint-plugin-react": "7.37.3",
"@typescript-eslint/eslint-plugin": "8.18.2",
"@typescript-eslint/parser": "8.18.2"
}
}
All packages are installed in the same node_modules, so ESLint can discover
its plugins through normal Node.js resolution.
Spectral with Custom Rulesets
Spectral (OpenAPI linter) often needs custom ruleset packages:
const mapOfApps: BinManager.MapOfApps = {
spectral: {
node: {
packageName: "@stoplight/spectral-cli",
binPath: "node_modules/.bin/spectral",
version: "6.14.2",
dependencies: {
"@stoplight/spectral-owasp-ruleset": "2.0.1",
},
},
},
};
Lock File for Reproducibility
Pin the exact dependency tree with a lock file:
const mapOfApps: BinManager.MapOfApps = {
eslint: {
node: {
packageName: "eslint",
binPath: "node_modules/.bin/eslint",
version: "10.0.0",
lockFile: "br:...", // brotli-compressed lock file content
},
},
};
When lockFile is present:
- PNPM runs with
--frozen-lockfile, refusing to modifypnpm-lock.yaml - The lock file content is written to the app directory before installation
- Content prefixed with
br:is brotli-compressed and base64-encoded
Without lockFile, PNPM resolves dependencies fresh and generates a new lockfile.
To generate lock file content:
- Run
datamitsu config lockfile <appName>to generate compressed lock file content - Add the output to your config's
lockFilefield
Workspace Overrides for Packages with Build Scripts
datamitsu writes a secure pnpm-workspace.yaml automatically before every node-app
install. When a package legitimately needs to run a build script — common
offenders include puppeteer, sharp, esbuild, and native modules — installs
fail with ERR_PNPM_IGNORED_BUILDS. Allowlist the package via
App.files["pnpm-workspace.yaml"]; your overrides are shallow-merged on top of
the secure baseline, so unspecified keys keep their hardened values.
const mapOfApps: BinManager.MapOfApps = {
mmdc: {
files: {
"pnpm-workspace.yaml": YAML.stringify({
allowBuilds: { puppeteer: true },
}),
},
node: {
packageName: "@mermaid-js/mermaid-cli",
binPath: "node_modules/.bin/mmdc",
version: "11.15.0",
lockFile: "br:...",
},
},
};
After adding allowBuilds, regenerate the lock file so pnpm records the approval:
pnpm exec datamitsu config lockfile mmdc
User-supplied keys always win on conflicts. Setting strictDepBuilds: false would
override the default — only do this if you fully trust every transitive dependency.
For the full list of baseline settings, the rationale behind each, and the merged YAML format, see the Supply Chain Security guide.
PNPM Store Isolation
Each app environment has isolated PNPM store paths:
~/.cache/datamitsu/.apps/node/eslint/{hash}/
package.json
pnpm-lock.yaml
node_modules/
.bin/eslint # Executable symlink
eslint/
eslint-plugin-*/
.pnpm-store/ # Content-addressable store (shared dedup)
PNPM's content-addressable store means identical packages across apps are hard-linked rather than duplicated, saving disk space while maintaining isolation.
Environment Variables
datamitsu sets these PNPM environment variables for isolation:
npm_config_store_dir- PNPM content-addressable store locationnpm_config_virtual_store_dir- Virtual store for the projectnpm_config_global_dir- Global packages directory
These prevent any interference with system-level PNPM configurations.