Skip to content
vxvx
GitHubBlueskydev.toRSS
Releases

Restores in place, and sooner

A cache hit now rewrites only the outputs that changed, in place, and writes nothing a task did not declare. A local hit restores while its dependencies still run. A generated client in node_modules can be an input, vx upgrade -g moves only the global vx, and only vx decides what reaches a remote cache.

vx 0.0.641: Restores in place, and sooner

A cache hit no longer empties your dist/ before it fills it again. It rewrites only the files that changed, in place, so a dev server or a test reading the outputs never finds one missing. A hit in the local cache also restores at once, beside the tasks it depends on, even with a remote declared.

Read the breaking changes before you update if a CI keeps pull requests off a Turbo or Nx remote cache with their own variables, or a task compiles with bun build --compile.

Enjoy the release!

In this release

A hit deleted every declared output, then wrote the cached ones back. Anything reading dist/ in between, a dev server, a watcher or a test, found files missing. A restore that failed halfway, on a full disk, left the outputs gone. Every file also got a new inode, which woke watchers for files that had not changed.

Now each cached file is written to vx’s own .vx/tmp/ and renamed over the old one, so a reader sees the old file or the new one, never neither. A file that already holds the cached bytes stays where it is. vx compares bytes, not timestamps, because reproducible builds give every file the same mtime. Files the entry does not hold are removed only after every rename has landed, and a restore that fails leaves the outputs as they were.

Terminal window
$ echo edited > app/dist/a.ts && touch app/dist/old.txt
$ ls -i app/dist
1941905 a.ts 1941906 b.ts 1941907 client.txt 1941909 old.txt
$ vx run build --all
$ ls -i app/dist
1941913 a.ts 1941906 b.ts 1941907 client.txt

a.ts is back to the cached bytes, old.txt is gone, and b.ts and client.txt were not touched.

A restore also writes only what the task declares. Each file in the entry must match the task’s cache.outputs, and never sits under .git, .vx, a vx.lock.json or a node_modules no glob names. One file outside that set and nothing is written: the entry is dropped and the task runs. Remote execution with @vzn/vx-reapi writes through the same check. See Caching.

A local hit restores while its dependencies run

Section titled “A local hit restores while its dependencies run”

With a remote cache declared, a task whose entry sat in the local cache still waited for every task it depends on before it restored. On one real repo a build waited about 18 s on a types task, then restored in a moment. With no remote, the same hit restored right away.

Now vx asks the local cache what it holds before the run starts, with or without a remote. A local hit restores at once, beside the tasks still running. A local miss asks the remote in the background, as before, so no network request delays the first task.

Here build depends on an uncached codegen that takes two seconds:

Terminal window
$ rm -rf app/dist
$ vx run build --all &
$ sleep 1 && ls app/dist # codegen is still running
a.ts b.ts client.txt

On 0.0.640, app/dist was still missing at that point.

A generated client in node_modules is an input

Section titled “A generated client in node_modules is an input”

prisma generate writes its client to node_modules/.prisma/client. A build that listed it as an input never missed when the client changed: vx dropped every file under node_modules, even a path the config named outright, and replayed the old output.

Now a path under an ignored node_modules that an input names is read from disk and hashed, like any other ignored file you name. A broad glob such as src/** still never walks node_modules.

inputs: { files: ['src/**', 'node_modules/.prisma/client/**'] }
Terminal window
$ echo c2 > app/node_modules/.prisma/client/index.js
$ vx run build --all
⏺︎ 2.01s success no-cache app#codegen
⏺︎ 7ms success miss app#build

See Keys from git.

Inside a project, a global vx hands every call to the project’s own vx, so vx upgrade always moved the project’s dependency and plugins too. There was no way to update only the vx on your PATH.

vx upgrade -g (or --global) upgrades only the global vx, the way you installed it: a verified binary, or a global add with npm, pnpm, bun or yarn. It never touches a package.json or a plugin. Plain vx upgrade works as before.

Terminal window
$ vx upgrade -g
› vx upgrade global 0.0.640 → 0.0.641
npm install -g @vzn/vx@0.0.641 in /usr/local/lib

The repo stays on its own release until someone runs vx upgrade there. Run as the project’s own vx, -g refuses and says to install a global one. See CLI.

Only vx decides what reaches a remote cache

Section titled “Only vx decides what reaches a remote cache”

turboCache() and nxCache() also obeyed Turbo’s and Nx’s own switches, and any remote plugin could narrow vx’s policy, so the same workspace behaved differently depending on which tool’s variables a CI set.

Now a cache plugin only moves bytes. turboCache() no longer reads TURBO_CACHE or TURBO_REMOTE_CACHE_READ_ONLY, and nxCache() no longer reads NX_SKIP_REMOTE_CACHE or NX_DISABLE_REMOTE_CACHE. Which runs read or write the remote is vx’s cache.policy alone. A server that refuses an upload, such as one behind a read-only token, still turns uploads off with one warning.

# a fork's pull request: read the shared cache, never write it
- run: vx run build --cache-policy=local:rw,remote:r

See CI.

  • vx init installs every @vzn/vx-* plugin .vx/workspace.ts imports, in one install with vx. Re-run in a repo it had set up, it no longer fails at the lock with cannot find '@vzn/vx-lockfile'.
  • A project in a directory whose name holds ? loads. Bun read the rest of the path as a query, so vx lock failed for the whole workspace.
  • The run footer counts every task whose log or outputs reached the remote under remote ↑.
  • turboCache() and nxCache() → no longer read TURBO_CACHE, TURBO_REMOTE_CACHE_READ_ONLY, NX_SKIP_REMOTE_CACHE or NX_DISABLE_REMOTE_CACHE. A CI that kept untrusted pull requests off the shared cache with them sets --cache-policy=local:rw,remote:r or cache.policy instead.
  • Bun’s compile temp files → no longer left out of every input by core. A **/* input on a bun build --compile task, or on a task in the same directory, adds '!**/*.bun-build' and '!**/.????????????????-????????.tmp/**'.
  • A cached entry that holds a file outside the task’s declared outputs → a miss, and the task runs. If a task writes more than its cache.outputs globs name, those files are no longer restored.
  • Plugin API: the access field a remote cache plugin returned → removed. Cache.restoreOutputs and CacheLayer.restoreOutputs take the task’s declared outputs as a fourth argument, and write only what they select.

See also Upgrading.

Terminal window
npm install -D @vzn/vx@latest
npx @vzn/vx lock

A standalone binary updates itself with vx upgrade.

Turborepo, Nx and other product names are trademarks of their owners. vx is not affiliated with or endorsed by them.