Skip to content

vznjs/vx

Repository files navigation

vx

The fastest way to build a monorepo.

vx runs your task graph, remembers every result, and never does the same work twice. Fully cached runs finish in milliseconds — 144 ms across 100 packages, 0.62 s across a 1090-package graph of 3,270 tasks. Measured, reproducible, on hardware you own.

One binary. No daemon. No Node. Nothing to babysit.

📖 Documentation site → — guides, architecture, caching, and the full CLI / config reference.

# From npm — ships the prebuilt standalone binary (no Bun required):
npm install -g @vzn/vx      # or: pnpm add -g @vzn/vx · bun add -g @vzn/vx
// vx.config.ts
import { defineProject } from '@vzn/vx'

export default defineProject({
  tasks: {
    build: {
      exec: { command: 'tsc -b' },
      dependsOn: ['^build'],
      cache: {
        inputs: { files: ['src/**'] },
        outputs: { files: ['dist/**'] },
      },
    },
    test: {
      exec: { command: 'bun test' },
      dependsOn: ['build'],
      cache: { inputs: { files: ['src/**', 'tests/**'] }, outputs: { files: [] } },
    },
    ci: { dependsOn: ['lint', 'test'] }, // umbrella; runs both
  },
})
vx run build              # cwd project + its workspace deps
vx run test --all         # every project that declares `test`
vx run ci --affected      # only what changed since origin/HEAD
vx watch lint             # re-run on file changes
vx run build --dry        # show the plan, don't execute

A cache that actually understands your build

Every task runner caches. vx caches correctly — and stops work others would redo:

  • Config is code, and the cache knows it. vx.config.ts is evaluated before hashing, so imports, presets, and computed values all participate in cache identity. Change a shared preset, and exactly the right tasks re-run.
  • Outputs are owned. Declared outputs are wiped before every execution and every restore. Your tree ends each run bit-identical to the cached snapshot — stale files cannot exist.
  • Hashes come from git. On a clean tree, deriving every cache key costs zero file reads, zero stats, zero database lookups. At 15,000 files that's a 3.2× faster warm path.

Speed is a design discipline

Exact bitset graph algorithms for scheduling. One bulk git enumeration per run, partitioned by binary search. Restores that skip extraction entirely when the tree already matches. In-process tar (no subprocess on the hot path). Atomic artifact publishes. Single-transaction metadata writes. Every optimization is recorded with the invariant that keeps it valid — docs/optimizations.md is the ledger, and bench/ reproduces the numbers.

Built for trust

  • Provable cache correctness. vx run --verify re-runs each cacheable task and byte-compares outputs (determinism); --verify=inputs sandboxes it against the declared inputs and fails loud naming any undeclared read (completeness). No other runner can prove a cache entry safe.
  • Corruption can't go live. A remote artifact is verified against its content digest and validated before it enters the store (zstd-bomb and oversize downloads refused); bad bytes degrade to a cache miss, never a wrong hit and never a crash.
  • Clean exits. SIGINT/SIGTERM reap every child process — no orphaned dev servers in CI.
  • Readiness you can bound. Persistent tasks gate downstream work on a readyWhen signal; exec.timeout bounds any task (with --timeout / workspace defaults), exec.retries + --retry absorb flakes — and retried-then-passed tasks are flagged flaky.
  • Kernel-level sandboxing, opt-in per task, that fails the build on violation instead of hiding it.

Reproducible graphs, when you want them

Configs are TypeScript — powerful, but a program's output can vary with its environment. vx lock freezes the fully-resolved task graph into a committed vx-lock.json, pnpm-style:

vx lock                      # evaluate everything once, write vx-lock.json
vx lock --check && vx run ci --frozen     # CI: audit, then run EXACTLY that graph
Command Evaluates configs Uses lock
vx run always, live never — local truth has no asterisks
vx run --frozen never yes; refuses if absent or a config file changed since locking
vx lock --check full graph compares — catches env and import drift that byte hashes cannot

Env values read at lock time are frozen by design — cache keys become reproducible across machines. Bonus: --frozen runs skip config evaluation entirely (~120 ms back per 1,000 packages). No other runner has an equivalent.

Everything you need, nothing to configure twice

TypeScript config with real imports · task graph with ^task resolution that bridges packages without the task · multi-task runs with one shared graph · pnpm-style filters and --affected · watch mode · --dry / --graph plans · persistent dev servers · retries, timeouts, --continue modes · per-layer cache control (--cache=local:r,remote:) · vx info, --summarize, --profile Chrome traces, --report · vx cache prune with TTL and size caps · vx migrate from turbo.json or an Nx graph.

Extensible by design — the core is provider-neutral

vx runs tasks and nothing else. A dashboard, a remote cache, distributed execution, and telemetry export all arrive through plugins declared in vx.workspace.ts, filling three seams the core exposes — backend (where a task runs), cache (a remote layer behind the local one), and telemetry (a read-only stream of the versioned TelemetryRecord / RunSummaryRecord contract). Core depends on none of them; the arrow only ever points plugin → core. First-party plugins ship the OpenTelemetry exporter (@vzn/vx-otel, OTLP traces + metrics with zero OTel-SDK deps) and vx Cloud (below) — and the same seams are open to anyone.

How it compares

vx Turborepo Nx
Fully cached, 100 pkgs¹ 144 ms 279 ms 583+ ms
Config TypeScript, evaluated into the cache key JSON (static) JSON (static)
Output ownership Strict — wiped before exec AND restore Additive (stale files survive) Additive
Clean-tree hashing Zero reads (git index OIDs) git OIDs re-hash / daemon
Daemon required for speed No Optional Yes
Per-task sandbox Yes — kernel-level, opt-in No No
Provable cache safety Yes--verify (determinism) + --verify=inputs (completeness) No No
MCP server for AI agents Yesvx mcp (stdio, reads your local cache) No No
Plugin API Yes — backend / cache / telemetry seams No Yes (TS-tied)
Predictive scheduling Yes (opt-in: predictive: true) No No
OTel CI/CD spans Yesotel() plugin, zero OTel-SDK deps No Paid
Self-hosted platform² Yes — dashboard, distributed CI, remote cache; Docker compose Vercel-only cache Paid (Nx Cloud)
Install Single binary — npm or 1 curl line, no Node/Bun needed npm + Node npm + Node

¹ Wall-clock, direct binaries, same machine and workspace — full methodology and more scenarios in docs/benchmarks.md. ² Optional, self-hosted, OSS — see vx Cloud.

Switching from another runner

Most projects can move in an afternoon. The mapping is mechanical:

// turbo.json (before)
{
  "tasks": {
    "build": {
      "dependsOn": ["^build"],
      "inputs": ["src/**"],
      "outputs": ["dist/**"],
      "env": ["NODE_ENV"],
    },
  },
}
// vx.config.ts (after)
import { defineProject } from '@vzn/vx'
export default defineProject({
  tasks: {
    build: {
      // Name the command (Turbo reads package.json scripts). The child
      // env is ISOLATED: a cache-input env var must also be passed
      // through, or the key would vary while the task can't see it.
      exec: { command: 'tsc -b', env: { passThrough: ['NODE_ENV'] } },
      dependsOn: ['^build'],
      cache: {
        inputs: { files: ['src/**'], env: ['NODE_ENV'] },
        outputs: { files: ['dist/**'] },
      },
    },
  },
})

Differences to know:

  • vx requires exec.command in the config — we don't read package.json scripts implicitly.
  • vx requires cache.inputs.files when caching is enabled (no default $TURBO_DEFAULT$).
  • vx defaults caching off; opt in per task by adding the cache block.
  • Persistent tasks: persistent: { readyWhen: 'regex' } (Turbo uses just persistent: true).
  • Remote caching is a plugin, not a built-in — connect one and every vx run reads through it.

Side-by-side feature matrix + every known gap: docs/comparison.md.

vx Cloud

An optional, self-hosted CI platform — the first-party service you run yourself. It is a fully independent app (accounts, roles, organizations, teams, multiple workspaces), with Postgres as the system of record and an S3-compatible bucket for artifacts; the controller keeps zero artifact bytes at rest. Core vx never depends on it — it connects to a deployment through the plugin seams above.

What a connected platform adds:

  • Dashboard. Runs, flamegraphs, a live run cockpit with the task DAG, per-task logs + artifacts, cache-key diffs ("why did this re-run?"), and flaky-task detection with the concrete fix.
  • Remote cache. The vx-native /v1/cache wire, tenant-partitioned per org/workspace, with trusted / untrusted tiers derived from the token — a fork-PR artifact can never feed a trusted build — and an always-on integrity digest the client verifies.
  • Distributed CI (DTE). Agents form a session-keyed pool; a vx run fans tasks out across same-commit agents, outputs propagate through the shared artifact store, and a standing pool multiplexes concurrent runs with fair scheduling.
  • MCP over the platform. POST /mcp (Postgres-backed, behind the token) exposes the analytics as typed tools for AI agents — alongside the core vx mcp stdio server that reads your local cache.

Deploy the stack (app + Postgres + object storage) with Docker Compose, then connect any workspace with one URL and a minted token:

# Deploy — open the URL, register the first admin, mint a CI token under Admin → Tokens:
VX_CLOUD_SECRET=$(openssl rand -hex 32) \
  docker compose -f packages/cloud/deploy/docker-compose.yml up

# Connect a workspace (remote cache + analytics + distribution):
vx-cloud connect https://ci.acme.dev --token vxc_…
# …or set VX_CLOUD_URL + VX_CLOUD_TOKEN in the environment.

Install the platform CLI with npm i -g @vzn/vx-cloud (a standalone binary, or the ghcr.io/vznjs/vx-cloud image). Full setup, RBAC, distributed CI, and the wire protocol live in the vx Cloud section of the documentation site.

Architecture (one paragraph)

bin.ts → cli/index.ts dispatches subcommands. orchestrator/run.ts:run() calls prepareRun() which discovers the workspace, loads configs, builds the package + task graph, opens the cache (local SQLite + an optional remote layer), and installs plugins from vx.workspace.ts. The two-tier scheduler runs the graph in topological order with bounded concurrency (confirmed cache hits restore ahead of their deps); each task hits the cache (hash → get → restore on hit; spawn → save on miss) or short-circuits as a group / persistent. Every observation flows through one event bus — the terminal renderer subscribes directly, and plugins receive the versioned telemetry contract (TelemetryRecord / RunSummaryRecord) — that's how telemetry and cache plugins export without core knowing them. Core never imports a plugin; the arrow only points plugin → core. Every module has a docs page; every interface is a swappable seam.

Read docs/architecture.md for the module map; the design record lives under docs/design/.

Documentation

Full technical docs live under docs/ and on the documentation site:

The full design record (including the platform-pivot notes) lives under docs/design/.

Status

Pre-alpha. The schema is settling; we bump CACHE_VERSION rather than maintain back-compat. 1,600+ tests (core + packages); CI green on every commit; the project dogfoods itself (vx run ci). Published on npm: @vzn/vx (a prebuilt standalone binary) and @vzn/vx-cloud.

Production readiness for the core task runner: the semantics are solid; it is dogfooded continuously. The main operational rough edge is Windows (unsupported).

Surface Maturity Notes
Core task runner + caching production-ready dogfooded continuously; 1,200+ core tests, all green
vx run --verify (provable cache correctness) shippable determinism + input-completeness proofs; CI-gate recipe in docs
vx mcp shippable live cache.db tools over stdio
Plugin API (backend / cache / telemetry) shippable crash-isolated; the cloud + OTel plugins are ordinary plugins
Predictive scheduling shippable as opt-in gated on predictive: true + observed data
OTel export (@vzn/vx-otel) shippable declare otel() in vx.workspace.ts; OTLP traces + metrics
vx Cloud platform (self-hosted) shippable for self-hosting accounts/RBAC/orgs on Postgres, S3 artifacts, dashboard, DTE, MCP

Development

git clone https://github.com/vznjs/vx && cd vx
bun install
bun src/bin.ts run ci          # format-check + lint + test
bun src/bin.ts run build       # cross-target binaries → dist/

vx is self-hosted: every dev task routes through bun src/bin.ts run <task> per the repo's own vx.config.ts. No package.json scripts; CI invokes vx directly.

License

MIT — see LICENSE.

About

No description, website, or topics provided.

Resources

License

Stars

2 stars

Watchers

0 watching

Forks

Packages

 
 
 

Contributors

Languages