Quick Start
# Install vlt globally$ npm install -g vlt
# In your existing project, run:
$ vlt install $ vlt buildvlt reads your existing package.json files and resolves
dependencies. The pnpm-lock.yaml file is not migrated — vlt performs
a fresh resolution and creates vlt-lock.json.
Command Mapping
| pnpm | vlt | Notes |
|---|---|---|
pnpm install | vlt install | Does not run lifecycle scripts |
pnpm add <pkg> | vlt install <pkg> | |
pnpm add -D <pkg> | vlt install -D <pkg> | |
pnpm remove <pkg> | vlt uninstall <pkg> | |
pnpm run <script> | vlt run <script> | |
pnpm <script> | vlt run <script> | See fallback-command |
pnpm dlx <pkg> | vlx <pkg> | Run remote packages |
pnpm exec <cmd> | vlt exec <cmd> | |
pnpm init | vlt init | |
pnpm pack | vlt pack | |
pnpm publish | vlt publish | |
pnpm login | vlt login | |
pnpm whoami | vlt whoami | |
pnpm list | vlt list | |
pnpm why <pkg> | vlt query '#<pkg>' | DSS query; see Selectors |
pnpm install --frozen-lockfile | vlt install --frozen-lockfile | |
pnpm audit | vlt query ':malware' | More powerful; see Security |
pnpm config set <key> <val> | vlt config set <key>=<val> |
Shorthand Script Execution
pnpm lets you run scripts without run (e.g., pnpm build). vlt
supports this via the fallback-command config:
$ vlt config set fallback-command=run-execAfter setting this, vlt build will first check for a vlt command
named build, and if none matches, look for a package.json script.
Configuration
.npmrc + pnpm-workspace.yaml → vlt.json
pnpm reads registry config from .npmrc and workspace config from
pnpm-workspace.yaml. vlt consolidates everything into vlt.json.
pnpm configuration:
# .npmrc (pnpm)registry=https://registry.internal.company.com/@mycompany:registry=https://npm.mycompany.com/auto-install-peers=true# pnpm-workspace.yamlpackages:- "packages/*"- "apps/*"vlt equivalent (single file):
{ "registry": "https://registry.internal.company.com/", "scope-registries": { "@mycompany": "https://npm.mycompany.com/" }, "workspaces": [ "packages/*", "apps/*" ]}Registry Configuration
Scoped Registries
pnpm’s scoped registries (from .npmrc) map directly to vlt’s
scope-registries:
{ "scope-registries": { "@mycompany": "https://npm.mycompany.com/" }}Named Registry Aliases
vlt also supports named registry aliases which remove the ambiguity of scope-based mapping:
{ "registries": { "internal": "https://npm.mycompany.com/" }}Then reference packages explicitly in package.json:
{ "dependencies": { "@mycompany/utils": "internal:@mycompany/utils@^2.0" }}Authentication
pnpm stores auth tokens in .npmrc, same as npm. vlt stores them in
an XDG-compliant keychain file, keeping secrets out of project config
files.
# Log in to default registry$ vlt login
# Log in to custom registry
$ vlt login --registry=https://npm.mycompany.com/CI Environments
# pnpmNPM_TOKEN=abc123 pnpm install
# vlt
VLT_TOKEN=abc123 vlt installSee Authentication for full details.
Lockfile
pnpm uses pnpm-lock.yaml. vlt uses vlt-lock.json.
When you first run vlt install, vlt creates vlt-lock.json from a
fresh resolution of your package.json files. The pnpm-lock.yaml is
not read.
What to do:
- Run
vlt installto generatevlt-lock.json - Commit
vlt-lock.json - Optionally remove
pnpm-lock.yamlonce you’ve fully switched
Install Script Protection
pnpm runs lifecycle scripts by default during install (same as npm).
pnpm v9+ added onlyBuiltDependencies in package.json as an
allowlist, but scripts still run by default for listed packages.
vlt takes a different approach: vlt install runs no scripts at
all by default. Building is a separate, explicit step:
# Phase 1: Install (no code executes)$ vlt install
# Phase 2: Build (runs scripts, skipping known malware)
$ vlt buildBy default, vlt build uses the target
:scripts:not(:built):not(:malware) — it automatically skips packages
flagged as malware by Socket.
pnpm onlyBuiltDependencies vs vlt build —target
{ "pnpm": { "onlyBuiltDependencies": [ "esbuild", "node-gyp" ] }}$ vlt build --target='#esbuild, #node-gyp'You can persist the target:
$ vlt config set "command.build.target=#esbuild, #node-gyp"See vlt build for full details.
Workspaces
pnpm
pnpm defines workspaces in pnpm-workspace.yaml:
packages:- "packages/*"- "apps/*"vlt
vlt defines workspaces in vlt.json:
{ "workspaces": [ "packages/*", "apps/*" ]}vlt also supports named workspace groups:
{ "workspaces": { "apps": "apps/*", "libs": [ "packages/*", "shared/*" ] }}Workspace Commands
| pnpm | vlt |
|---|---|
pnpm --filter <name> <cmd> | vlt <cmd> -w <path> or cd <path> && vlt <cmd> |
pnpm --filter ./packages/* <cmd> | vlt <cmd> -w packages/* |
pnpm -r <cmd> | vlt <cmd> --recursive |
pnpm -r run test | vlt run test --recursive |
Note: vlt’s --workspace (-w) flag takes paths or glob
patterns, not package names.
# Run tests in a specific workspace$ vlt run test -w packages/core
# Run build across a workspace group
$ vlt run build -g libsSee Workspaces for full details.
Catalogs
If you use pnpm’s catalog feature (pnpm-workspace.yaml), vlt has
direct support for catalogs with compatible syntax.
pnpm:
# pnpm-workspace.yamlpackages:- "packages/*"
catalog: typescript: "^5.0.0" eslint: "^8.0.0"
catalogs: testing: vitest: "^1.0.0"vlt:
{ "workspaces": [ "packages/*" ], "catalog": { "typescript": "^5.0.0", "eslint": "^8.0.0" }, "catalogs": { "testing": { "vitest": "^1.0.0" } }}The catalog: protocol in package.json works the same way:
{ "devDependencies": { "typescript": "catalog:", "vitest": "catalog:testing" }}See Catalogs for full details.
Overrides → Graph Modifiers
pnpm uses pnpm.overrides in package.json. vlt uses
Graph Modifiers in vlt.json.
{ "pnpm": { "overrides": { "lodash": "^4.17.21", "express>qs": "6.10.0" } }}{ "modifiers": { "#lodash": "^4.17.21", ":root > #express > #qs": "=6.10.0" }}Graph Modifiers use DSS selectors, giving you more precise control (e.g., target by path, workspace, or semver range).
node_modules Layout
pnpm uses a content-addressable store with symlinks to create a strict
node_modules layout where packages can only access their declared
dependencies.
vlt also creates a node_modules directory but uses a different
internal layout (under node_modules/.vlt/). The result is similar in
that packages resolve correctly at runtime — your application code
doesn’t need to change.
Features Not in vlt
Some pnpm-specific features don’t have direct equivalents:
- Content-addressable store — vlt uses its own on-disk cache but doesn’t hard-link from a global store
- Side-effects cache — Not available in vlt
pnpm patch— Not yet available in vltpnpm deploy— Not available; use standard deployment toolingpnpm.peerDependencyRules— vlt handles peer dependencies automatically with context isolation
Migration Checklist
- Install vlt:
npm install -g vlt - Create
vlt.jsoncombining your.npmrcregistry config andpnpm-workspace.yamlworkspace definitions - Move scoped registry config from
.npmrctovlt.json - Move catalogs from
pnpm-workspace.yamltovlt.json(catalogandcatalogsfields) - Move
pnpm.overridesfrompackage.jsontomodifiersinvlt.json - Run
vlt installthenvlt build - Commit
vlt-lock.json - Update CI scripts: replace
pnpm install --frozen-lockfilewithvlt install --frozen-lockfile && vlt build - Update CI auth: replace
NPM_TOKENwithVLT_TOKEN - Update any
pnpm dlxusage tovlx - Remove
pnpm-workspace.yamland pnpm-specific.npmrcsettings