vlt / docs

  • PricingBenchmarks (opens in new window)Community (opens in new window)Feedback
  • Overview
  • bugs
  • build
  • cache
  • ci
  • config
  • create
  • deploy
  • docs
  • exec-cache
  • exec-local
  • exec
  • help
  • init
  • install
  • list
  • login
  • logout
  • outdated
  • pack
  • ping
  • pkg
  • publish
  • query
  • registry
  • repo
  • run-exec
  • run
  • setup
  • skills
  • token
  • uninstall
  • version
  • view
  • whoami
  • Breaking the Traditional Model
  • Basic Usage
  • Allow scripts
  • Default query
  • Examples
  • Excluding malware and any package not from a specific scope
  • Exact dependency names
  • Options
  • --target
  • --allow-scripts=<query>
  • -y, --yes
  • --config=<user | project>
  • --global, -g
  • Build State Persistence
  • Best Practices
  • Migration from Traditional Package Managers
  1. Client
  2. /
  3. Commands
  4. /
  5. build

vlt build

Usage:

Terminal
$ vlt build [<query>] [options]

The vlt build command represents a fundamental shift in how JavaScript package managers handle post-installation tasks. By separating the build phase from installation, vlt gives you unprecedented control over which packages run lifecycle scripts in your project.

Breaking the Traditional Model

Traditional package managers conflate installation with building – when you run npm install, packages are downloaded, extracted, and their lifecycle scripts run automatically. This monolithic approach has several drawbacks:

  • Security risks: Malicious packages can execute arbitrary code during installation
  • Limited control: You can't selectively enable or disable scripts for specific packages

With vlt, we've reimagined this process as two distinct phases:

  1. vlt install – Downloads and extracts packages into node_modules
  2. vlt build – Runs lifecycle scripts and performs additional binary linking when needed

This separation leverages the full power of our Dependency Selector Syntax (DSS) query language, allowing you to precisely control which packages are allowed to execute scripts.

Basic Usage

The simplest workflow mirrors traditional package managers but with explicit steps:

Terminal
# Step 1: Install packages (no scripts run)
$ vlt install

# Step 2: Build the packages allow-scripts selects

$ vlt build

The build process is idempotent – it only performs work that's actually needed based on the current state of your dependency graph. If a package has already been built, vlt build skips it automatically.

With nothing stored, a bare vlt build in a terminal opens an editor prefilled with the default query; with no TTY (e.g. in CI) it builds the default without saving it. See Allow scripts.

Allow scripts

vlt build [<query>] sets, saves and applies allow-scripts, the DSS query that picks which packages run lifecycle scripts. It builds what the query selects, then saves the query when it changed: to the project vlt.json, or to the user one with --config=user. A failed build saves nothing. A query in the other file is never deleted: a project value shadows the user one.

A query is given as the positional argument, with --allow-scripts=<query> or with VLT_ALLOW_SCRIPTS. The positional wins.

Query givenStored-yTTYResult
yesnoanyanybuild it, save it
yesyesnoyesReplace it? [y/N]: y opens the editor with the query, N changes nothing
yesyesyesanybuild it, save it
yesyesnonoerror: pass -y to replace it
noyesnoyesEdit it? [y/N]: y opens the editor with the stored query, N builds it
noyesyesanybuild the stored query
noyesnonobuild the stored query
nononoyesthe editor, prefilled with the default query
nonoyesanybuild the default, save it
nonononobuild the default, not saved

A query equal to the stored one builds without asking or writing.

Terminal
$ vlt build "#esbuild"
🔨 Built 1 package successfully.
Saved allow-scripts="#esbuild" to vlt.json

$ vlt install sharp
$ vlt build
allow-scripts is set for this project (vlt.json): "#esbuild". Edit it? [y/N] y

⚠️ The new allow-scripts replaces the current one. Make sure it still
selects every package you trust: packages it no longer selects won't
run their scripts on install.

Current allow-scripts: #esbuild
New allow-scripts: #esbuild, #sharp
🔨 Built 1 package successfully.
Saved allow-scripts="#esbuild, #sharp" to vlt.json

In the editor, the query is prefilled: edit it and press Enter. An empty answer cancels with No changes made., and an invalid query prints the error and asks again. With nothing stored, it first warns that the answer is saved and installs run the scripts it selects. -y skips the prompts.

There is no unbuild: narrowing allow-scripts only changes what runs from then on. Packages already built stay built.

vlt install, vlt ci and vlt update run the scripts of the packages allow-scripts selects, so a fresh clone needs no separate vlt build. Trust packages one at a time:

Terminal
$ vlt install esbuild
$ vlt build "#esbuild"   # saved to vlt.json
$ vlt install sharp
$ vlt build
# Edit it? y
# New allow-scripts: #esbuild, #sharp
# on another machine, install runs both scripts
$ vlt ci

Default query

With no query given and nothing stored, vlt build uses :scripts:not(:built):not(:malware): packages not built yet that have lifecycle scripts (or a root binding.gyp) and no malware alerts. It prefills the editor; -y builds and saves it.

npm registry packages with no security data, because the request timed out or Socket has not scanned them yet, are skipped and reported as not built. See Understanding Security Data.

Examples

Excluding malware and any package not from a specific scope

In this example we're only allowing scripts from packages that do not have a malware alert associated to it and packages from a specific, well known scope name.

Terminal
# Install without running any scripts
$ vlt install

# Look up if there are packages with malware alerts in my current install

$ vlt query ":malware"

# Look up what packages have build scripts and are not from a known scope

$ vlt query ":scripts:not([name^=@myscope])"

# Build (and save) only packages from my known scope that are NOT flagged as malware

$ vlt build "*[name^=@myscope]:not(:malware)"

Exact dependency names

Maybe you only trust certain critical build tools to run scripts:

Terminal
# Install packages
$ vlt install

# Only build the specific tools you trust (saved as allow-scripts)

$ vlt build "#esbuild, #typescript"

Options

--target

Builds a DSS query once: no prompt, never saved. It wins over the query argument, --allow-scripts and the stored allow-scripts. command.build.target in vlt.json acts the same.

Terminal
# Build only direct dependencies
$ vlt build --target=":root > *"

# Build only packages with native bindings

$ vlt build --target=":has-binary"

--allow-scripts=<query>

Same as the query argument.

-y, --yes

Skip the prompts: replace or keep the stored allow-scripts without asking.

--config=<user | project>

The vlt.json to save allow-scripts to. Default: project.

--global, -g

Build packages of the global project, e.g. vlt build -g. It then re-links their bins and prints where.

Build State Persistence

The build state is tracked in vlt's node_modules metadata. Each package node stores its build state (needed, built, or failed).

It's possible to retrieve the already built files using the :built query selector:

Terminal
# Query all built packages
$ vlt query ":built"

# Return all packages that have scripts that needs to be built

$ vlt query ":scripts:not(:built)"

Best Practices

  1. Start with vlt install – Always install first, then decide what to build
  2. Use queries for precision – Target specific packages or patterns rather than building everything
  3. Persist your choices – vlt build <query> saves it as allow-scripts, so installs run the same scripts everywhere
  4. Review regularly – Periodically audit your allow-scripts to ensure it matches your security requirements, continuing to maintain and evolve it as you add and update packages

Migration from Traditional Package Managers

If you're coming from npm, running install followed by build is the functional equivalent of the traditional package manager behavior:

Terminal
# Traditional approach
$ vlt install
$ vlt build

The difference being that, as mentioned previously, vlt build by default protects you from running scripts of dependencies with known malware. In a terminal, it first asks for the query to save (see Allow scripts).

If you are really trying to emulate the legacy npm behavior of running all lifecycle scripts, you can use the --allow-scripts="*" options to opt-in to all scripts and have the vlt client work as a traditional package manager would:

Terminal
# Bad: Legacy npm-style installation (not recommended)
$ vlt install --allow-scripts="*"

It is saved to vlt.json, so every later install runs all scripts.


PreviousbugsNextcache

On this page

  • Breaking the Traditional Model
  • Basic Usage
  • Allow scripts
  • Default query
  • Examples
  • Excluding malware and any package not from a specific scope
  • Exact dependency names
  • Options
  • --target
  • --allow-scripts=<query>
  • -y, --yes
  • --config=<user | project>
  • --global, -g
  • Build State Persistence
  • Best Practices
  • Migration from Traditional Package Managers
Edit this page

Deploy your package on vlt.io

Publish scoped and private packages, manage organizations and access, and give every developer and CI environment a consistent source for public and private JavaScript dependencies.

Publish now