Quickstart
This guide takes you from nothing to a cached, parallel task graph. It assumes Bun ≥ 1.3 and a workspace under git (vx hashes inputs via git’s index, so a repo is required).
Already have a monorepo with Turborepo or Nx? Jump to Add vx to an existing repo or the migration guides (Turborepo, Nx).
1. Install
Section titled “1. Install”bun add -d @vzn/vx# …or grab the standalone binary (no Node or Bun required):curl -fsSL https://raw.githubusercontent.com/vznjs/vx/main/install.sh | shThis puts the vx binary in your workspace. vx prepends each package’s
node_modules/.bin to PATH per task, so tsc, vite, eslint, etc.
resolve from a bare command — no npx needed.
2. Describe a task
Section titled “2. Describe a task”Drop a vx.config.ts next to any package’s package.json:
import { defineProject } from '@vzn/vx'
export default defineProject({ tasks: { build: { exec: { command: 'tsc -b' }, cache: { inputs: { files: ['src/**', 'tsconfig.json'] }, outputs: { files: ['dist/**'] }, }, }, },})Two things to internalize early:
- Caching is opt-in and explicit. When you add a
cacheblock, bothinputsandoutputsare required. No hidden globs — you say exactly what the task reads and produces. (Omitcacheentirely and the task always runs.) - One command per task.
exec.commandis a single shell command. Chain steps with&&, or split them into separate tasks wired withdependsOnso each step caches independently.
defineProject is just an identity function for TypeScript autocomplete
and validation — it has zero runtime effect.
3. Run it
Section titled “3. Run it”vx run buildThe first run executes tsc and stores the result. Run it again:
vx run build # ◌ cache hit — restored in millisecondsvx restored dist/** and the captured logs from cache without running
tsc. Change a file under src/ and re-run — vx detects the changed
input and rebuilds, then caches the new result.
4. Add a second task and a dependency
Section titled “4. Add a second task and a dependency”export default defineProject({ tasks: { build: { exec: { command: 'tsc -b' }, cache: { inputs: { files: ['src/**'] }, outputs: { files: ['dist/**'] } }, }, test: { dependsOn: ['build'], // build must succeed first exec: { command: 'bun test' }, cache: { inputs: { files: ['src/**', 'tests/**'] }, outputs: { files: [] } }, }, },})vx run test # runs build → test, in order, then caches bothoutputs: { files: [] } is correct for tasks like test and lint that
produce no files — you still cache the successful no-op so the next run
is instant.
5. Go wide across packages
Section titled “5. Go wide across packages”Use the ^ prefix to depend on the same task in your workspace
dependencies — the universal monorepo pattern:
build: { dependsOn: ['^build'], // build my deps before me exec: { command: 'tsc -b' }, cache: { inputs: { files: ['src/**'] }, outputs: { files: ['dist/**'] } },}Now run across the whole workspace:
vx run build --all # every package, in dependency ordervx run build --filter "@app/*" # only packages matching a filtervx run test --affected # only what changed vs the base branch6. See what vx will do (without doing it)
Section titled “6. See what vx will do (without doing it)”vx run build --all --dry # predicted cache hits/misses, no executionvx run build --graph # the task graph (text or Graphviz DOT)Where to go next
Section titled “Where to go next”- Configuring tasks — the full shape of
vx.config.ts. - Caching tasks — get inputs and outputs right so runs are always correct.
- Running & filtering tasks — filters,
--affected, argument forwarding, watch mode. - Continuous integration — wire vx into CI with remote caching.