From Node.js + npm
This is the easiest migration: 3va runs Node-style code (CommonJS, ESM,
require("node:*")) and reads the node_modules npm already installed.
1. Run it on your existing node_modules
Keep installing with npm for now, and run the entry point with 3va:
npm install # as today
3va run server.js --allow-read=./node_modules --allow-net=127.0.0.1Tested with an Express app on npm 11:
up 3456
$ curl 127.0.0.1:3456
{"lodash":"4.17.20","express":"4.22.3"}The flags are the whole migration at this step:
--allow-read=./node_modules: loading a package is a file read. Without it:Permission denied: --allow-read=…/node_modules/express/index.js is required.--allow-net=<host>: for the server and any outbound calls. See HTTP APIs.--allow-env=NAME,…: everyprocess.env.Xyou read. Without a grant the variable reads asundefined, soPORTorDATABASE_URLfall back to their defaults, but the read is reported (see below).
When a run is missing a grant, 3va lists what was refused on exit, with the exact flag to add:
[!] 2 permissions were denied during this run:
connect to example.com:443
grant with: --allow-net=example.com:443
bind a local server on 0.0.0.0
needs any --allow-net grant (e.g. --allow-net=127.0.0.1)
read environment variable DATABASE_URL
grant with: --allow-env=DATABASE_URLAdd --trace-denials to see each refusal as it happens. To get the full
set in one go, run the app once under observation and let 3va write it
into package.json:
3va permissions learn server.js --writeIt merges into the root "." scope and leaves per-package scopes alone.
It also records a couple of variables the runtime itself reads
(CLUSTER_WORKER, NODE_ENV), which you can delete. The result looks like
this, and every command (3va run, 3va start, 3va test) picks it up:
{
"3va": {
"no-prompt": true,
"permissions": {
".": {
"allow-read": ["./node_modules"],
"allow-net": ["127.0.0.1"],
"allow-env": ["PORT", "DATABASE_URL"]
}
}
}
}2. Scripts
3va <name> runs package.json scripts when <name> isn’t a 3va
command, through npm itself, outside the sandbox:
3va lint # → npm run lintBecause it runs outside the sandbox, it asks [y/N] first. In CI (no TTY)
it is denied unless you pass --yes or set "3va": { "no-prompt": true }.
To run a script inside the sandbox, call the file directly:
"start": "3va run server.js".
3. Switching installs to 3va
Convert the lockfile first, so you keep exactly the versions you have:
3va migrate --dry-run # Would migrate package-lock.json → 3va-lock.json (68 package(s))
3va migrate
3va install --allow-net=registry.npmjs.orgIn our test, express was locked at 4.18.2 under a ^4.18.0 range, and
3va install kept 4.18.2. Without a lockfile, ranges resolve to the
highest matching version, as with npm.
In CI, use 3va ci. It installs exactly what 3va-lock.json records,
checks integrity hashes, and fails if package.json has drifted:
Error: 3va-lock.json does not match package.json:
lodash: package.json asks for 4.17.21, lockfile pins 4.17.20
Run `3va install` and commit the updated lockfile.3va install scans every package for malware and doesn’t run lifecycle
scripts unless you allow them. What to check before you switch:
- The malware scanner can still block legitimate packages. Vite,
rolldown and
@types/nodenow pass, butoxlint,commanderand several Expo packages don’t. After reviewing one, trust that exact version with"3va": { "trusted": ["oxlint@1.85.0"] }. It’s scanned again when the version changes. - Lifecycle scripts don’t run unless the package is in
"3va": { "onlyBuiltDependencies": [...] }, and even then only when the script is a single JS file (it runs sandboxed). Shell commands likebetter-sqlite3’sprebuild-install || node-gyp rebuildare refused, sobetter-sqlite3,sharp,bcryptand Puppeteer’s Chrome still end up without their binary. Packages that ship prebuilt binaries as platform-specific optional dependencies (esbuild, SWC, rollup, lightningcss) are installed. node_modules/.binis created, but many CLIs in it (vite,tsc,next) don’t run under 3va yet. Keep calling them through Node.
Details and workarounds are in the Migration Guide.
Things to keep in mind
3va runhas no--watch. Use3va watch server.js.- The process manager replaces pm2:
3va start server.js --name api. Its permissions come frompackage.json, not flags (Process Manager). engines.nodeis ignored. 3va doesn’t emulate a specific Node version. Test the APIs you rely on.