Skip to content

Add vx to an existing repo

You don’t need to convert your whole monorepo at once. vx is happy to run a single task in a single package while the rest of your tooling stays exactly as it is. This guide shows the incremental path.

Migrating from a specific tool? Those guides generate config for you: from Turborepo, from Nx.

Terminal window
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 | sh

vx discovers your workspace automatically from pnpm-workspace.yaml, a package.json workspaces field (npm / yarn / Bun), or a bare single-package package.json. A package only becomes a vx project when it has a vx.config.ts — packages without one are still visible in the dependency graph but contribute no tasks. That’s what makes incremental adoption work.

Pick a package with a clear build step and add a vx.config.ts next to its package.json. Point the command at the script you already run:

import { defineProject } from '@vzn/vx'
export default defineProject({
tasks: {
build: {
// whatever your package.json "build" script already does
exec: { command: 'tsc -b' },
cache: {
inputs: { files: ['src/**', 'tsconfig.json', 'package.json'] },
outputs: { files: ['dist/**'] },
},
},
},
})
Terminal window
vx run build # from inside that package

Run it twice and confirm the second run is a cache hit. You’ve now got caching for that package with zero changes to anything else.

3. Verify caching is correct before you trust it

Section titled “3. Verify caching is correct before you trust it”

Stale cache hits are the one failure mode that matters, so check your input/output declarations on a real change:

Terminal window
vx run build --dry # shows predicted hit/miss + the resolved plan

Edit a source file → --dry should predict a miss. Edit a file not listed in inputs.files that the build actually reads → if --dry still predicts a hit, your inputs are too narrow. See Caching tasks for getting this right (and why vx folds package.json in automatically).

Add vx.config.ts to more packages and wire cross-package ordering with the ^ prefix:

build: {
dependsOn: ['^build'], // build workspace deps first
exec: { command: 'tsc -b' },
cache: { inputs: { files: ['src/**'] }, outputs: { files: ['dist/**'] } },
}

vx derives the cross-package edges from your package.json dependencies — you don’t redeclare the graph. A package that doesn’t declare build is transparently bridged to the nearest dependency that does, so sparse task coverage is fine.

Add a vx.workspace.ts at the root only if you need to override defaults:

import { defineWorkspace } from '@vzn/vx'
export default defineWorkspace({
concurrency: 8, // default: navigator.hardwareConcurrency
cacheDir: '.vx/cache', // default: .vx/cache (relative to root)
})
Terminal window
vx run build --all # every package that declares build
vx run test --affected # only packages changed vs the base branch

vx doesn’t touch your package.json scripts and writes only to its cache dir (.vx/ by default — add it to .gitignore). You can keep Turborepo or Nx running the rest of the repo while you evaluate vx on a few packages, then switch over when you’re confident.