@vzn/vx 0.0.18 → 0.0.22
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +1 -1
- package/README.md +107 -70
- package/package.json +10 -20
- package/src/bin.ts +64 -5
- package/src/cache/archive.ts +279 -28
- package/src/cache/cache.ts +952 -1584
- package/src/cache/chained-cache.ts +14 -10
- package/src/cache/config-evals.ts +112 -0
- package/src/cache/file-hashes.ts +290 -0
- package/src/cache/git-inputs.ts +966 -0
- package/src/cache/index.ts +23 -7
- package/src/cache/inputs.ts +553 -739
- package/src/cache/key-fold.ts +232 -0
- package/src/cache/layer.ts +674 -0
- package/src/cache/layered-cache.ts +211 -49
- package/src/cache/output-index.ts +323 -0
- package/src/cache/policy.ts +84 -0
- package/src/cache/run-history.ts +152 -0
- package/src/cache/zstd.ts +183 -0
- package/src/cli/cache.ts +88 -30
- package/src/cli/completions.ts +104 -0
- package/src/cli/core-alias.ts +25 -0
- package/src/cli/format.ts +4 -15
- package/src/cli/help.ts +76 -38
- package/src/cli/index.ts +82 -17
- package/src/cli/info.ts +162 -91
- package/src/cli/init.ts +87 -0
- package/src/cli/last.ts +130 -23
- package/src/cli/lock.ts +36 -18
- package/src/cli/plan-format.ts +18 -7
- package/src/cli/plugin-commands.ts +34 -12
- package/src/cli/run.ts +138 -313
- package/src/cli/select.ts +342 -0
- package/src/cli/show.ts +151 -71
- package/src/cli/upgrade.ts +136 -15
- package/src/cli/watch.ts +1175 -116
- package/src/cli/why.ts +51 -29
- package/src/cli/workspace-config.ts +138 -0
- package/src/config.ts +183 -126
- package/src/exec/env.ts +11 -0
- package/src/exec/executor.ts +62 -31
- package/src/exec/index.ts +16 -2
- package/src/exec/kill-tree.ts +281 -0
- package/src/exec/local-executor.ts +41 -0
- package/src/exec/runner.ts +361 -77
- package/src/exec/sandbox-binds.ts +211 -0
- package/src/exec/sandbox-paths.ts +70 -0
- package/src/exec/sandbox-runtime.ts +1152 -415
- package/src/exec/sandbox-violations.ts +293 -0
- package/src/graph/dependency-spec.ts +14 -14
- package/src/graph/index.ts +6 -4
- package/src/graph/priorities.ts +130 -0
- package/src/graph/scheduler.ts +332 -329
- package/src/graph/task-graph.ts +552 -188
- package/src/index.ts +111 -102
- package/src/orchestrator/admission.ts +161 -0
- package/src/orchestrator/colors.ts +8 -3
- package/src/orchestrator/deferred-outputs.ts +25 -7
- package/src/orchestrator/doctor.ts +363 -0
- package/src/orchestrator/events.ts +111 -8
- package/src/orchestrator/excluded-keys.ts +155 -0
- package/src/orchestrator/execute-task.ts +450 -565
- package/src/orchestrator/failure-mode.ts +169 -28
- package/src/orchestrator/failure-recap.ts +121 -0
- package/src/orchestrator/fingerprint-watch.ts +89 -0
- package/src/orchestrator/framed-output.ts +121 -49
- package/src/orchestrator/history.ts +123 -104
- package/src/orchestrator/hit-restore.ts +285 -0
- package/src/orchestrator/index.ts +42 -5
- package/src/orchestrator/keyed-projects.ts +89 -0
- package/src/orchestrator/local-shortcircuit.ts +83 -23
- package/src/orchestrator/lockfile-claim.ts +401 -0
- package/src/orchestrator/logger.ts +128 -41
- package/src/orchestrator/metrics.ts +201 -72
- package/src/orchestrator/miss-save.ts +232 -0
- package/src/orchestrator/options.ts +63 -34
- package/src/orchestrator/persistent.ts +99 -0
- package/src/orchestrator/placement.ts +212 -0
- package/src/orchestrator/plan.ts +24 -11
- package/src/orchestrator/plugin-host.ts +200 -49
- package/src/orchestrator/plugin.ts +190 -14
- package/src/orchestrator/prepare.ts +168 -137
- package/src/orchestrator/projects.ts +270 -0
- package/src/orchestrator/remote-prefetch.ts +22 -2
- package/src/orchestrator/run-artifacts.ts +34 -3
- package/src/orchestrator/run-context.ts +20 -5
- package/src/orchestrator/run-lock.ts +349 -0
- package/src/orchestrator/run-records.ts +132 -0
- package/src/orchestrator/run-report.ts +6 -4
- package/src/orchestrator/run.ts +467 -611
- package/src/orchestrator/sandbox-request.ts +569 -0
- package/src/orchestrator/save-lane.ts +44 -0
- package/src/orchestrator/shell-verdict.ts +117 -0
- package/src/orchestrator/signals.ts +167 -0
- package/src/orchestrator/stable-keys.ts +259 -10
- package/src/orchestrator/status-line.ts +35 -2
- package/src/orchestrator/summary.ts +115 -25
- package/src/orchestrator/task-hash.ts +103 -21
- package/src/orchestrator/task-log-buffer.ts +15 -18
- package/src/orchestrator/telemetry-host.ts +27 -8
- package/src/orchestrator/telemetry.ts +71 -44
- package/src/orchestrator/upstream.ts +91 -45
- package/src/util/bun-version.ts +43 -0
- package/src/util/cgroup.ts +160 -0
- package/src/util/edit-distance.ts +49 -0
- package/src/util/errors.ts +86 -0
- package/src/util/hash.ts +10 -1
- package/src/util/index.ts +42 -5
- package/src/util/paths.ts +168 -5
- package/src/util/procfs.ts +20 -0
- package/src/util/settle.ts +23 -2
- package/src/util/size.ts +33 -0
- package/src/util/task-id.ts +20 -0
- package/src/util/timing.ts +11 -1
- package/src/util/verbs.ts +39 -0
- package/src/util/which.ts +48 -0
- package/src/workspace/affected.ts +368 -60
- package/src/workspace/config-cache.ts +271 -32
- package/src/workspace/config-eval.ts +60 -16
- package/src/workspace/config-imports.ts +131 -9
- package/src/workspace/config-schema.ts +1033 -0
- package/src/workspace/filter.ts +69 -17
- package/src/workspace/fingerprint.ts +59 -7
- package/src/workspace/index.ts +37 -3
- package/src/workspace/json-data.ts +90 -0
- package/src/workspace/load-reads.ts +32 -0
- package/src/workspace/lockfile.ts +41 -14
- package/src/workspace/migrate-scripts.ts +187 -0
- package/src/workspace/migration.ts +353 -0
- package/src/workspace/package-graph.ts +239 -31
- package/src/workspace/project-loader.ts +295 -815
- package/src/workspace/workspace.ts +266 -31
- package/plugins/local-cache/index.ts +0 -2
- package/plugins/local-executor/index.ts +0 -2
- package/plugins/schedule-history/index.ts +0 -2
- package/src/cache/cas-backend.ts +0 -115
- package/src/cache/digest.ts +0 -32
- package/src/cli/migrate-emit.ts +0 -21
- package/src/cli/migrate-nx.ts +0 -473
- package/src/cli/migrate-scripts.ts +0 -127
- package/src/cli/migrate-turbo.ts +0 -401
- package/src/cli/migrate.ts +0 -305
- package/src/cli/prune.ts +0 -291
- package/src/orchestrator/missing-plugin.ts +0 -9
- package/src/orchestrator/resources.ts +0 -45
- package/src/orchestrator/verify.ts +0 -174
- package/src/plugins/local-cache/index.ts +0 -11
- package/src/plugins/local-executor/index.ts +0 -45
- package/src/plugins/schedule-history/index.ts +0 -109
package/LICENSE
CHANGED
package/README.md
CHANGED
|
@@ -3,15 +3,33 @@
|
|
|
3
3
|
**The fastest way to build a monorepo.**
|
|
4
4
|
|
|
5
5
|
vx runs your task graph, remembers every result, and never does the
|
|
6
|
-
same work twice.
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
(
|
|
13
|
-
|
|
14
|
-
|
|
6
|
+
same work twice.
|
|
7
|
+
|
|
8
|
+
<!-- bench:start — generated by packages/vx-bench/update-site.ts from results.json; do not hand-edit -->
|
|
9
|
+
|
|
10
|
+
The runner adds seconds to a cold build where others add minutes. On a
|
|
11
|
+
1,090-package graph of 3,270 tasks whose ideal schedule is 3m 38s,
|
|
12
|
+
vx finishes in 3m 46s (+0:08), Turborepo in 5m 13s (+1:35) and Nx in
|
|
13
|
+
34m 44s (+31:06) — 8 ms of overhead per package against 88 ms and
|
|
14
|
+
1,712 ms, so the graph can grow and the runner stays in seconds. The cold build
|
|
15
|
+
burns 35 s of CPU in vx, 73 s in Turborepo and 114 minutes in Nx; a
|
|
16
|
+
fully cached run replays the graph in 510 ms.
|
|
17
|
+
<!-- bench:end -->
|
|
18
|
+
|
|
19
|
+
On a real Turbo monorepo (`solidjs/solid`, its own `turbo.json`, no
|
|
20
|
+
config rewritten) vx replays a cached build in 51 ms where Turbo takes
|
|
21
|
+
95, restores every output in 66 ms against 127, and builds cold in
|
|
22
|
+
40.6 s against 45.5 s. Measured, reproducible, on hardware you own
|
|
23
|
+
([benchmarks](packages/vx/docs/benchmarks.md)).
|
|
24
|
+
|
|
25
|
+
One binary. No daemon, no cloud, no account, no paywall. Sandboxed
|
|
26
|
+
tasks, a plugin at every stage, seconds of overhead at a thousand
|
|
27
|
+
packages.
|
|
28
|
+
|
|
29
|
+
vx does one thing — run and cache a task graph, correctly — and stops
|
|
30
|
+
there. Remote caches, remote execution, telemetry, AI agents, learned
|
|
31
|
+
scheduling and your own verbs are plugins on documented seams, not
|
|
32
|
+
features inside: what Nx would be if it weren't a product.
|
|
15
33
|
|
|
16
34
|
📖 **[Documentation site →](https://vznjs.github.io/vx/)** — guides,
|
|
17
35
|
architecture, caching, and the full CLI / config reference.
|
|
@@ -49,7 +67,7 @@ export default defineProject({
|
|
|
49
67
|
vx init # scaffold vx.workspace.ts + a vx.config.ts per package from scripts
|
|
50
68
|
vx run build # cwd project + its workspace deps
|
|
51
69
|
vx run test --all # every project that declares `test`
|
|
52
|
-
vx run ci --affected #
|
|
70
|
+
vx run ci --affected # changed since origin/HEAD, and what depends on it
|
|
53
71
|
vx watch lint # re-run on file changes
|
|
54
72
|
vx run build --dry # show the plan, don't execute
|
|
55
73
|
```
|
|
@@ -78,16 +96,11 @@ skip extraction entirely when the tree already matches. In-process
|
|
|
78
96
|
tar (no subprocess on the hot path). Atomic artifact publishes.
|
|
79
97
|
Single-transaction metadata writes. Every optimization is recorded
|
|
80
98
|
with the invariant that keeps it valid —
|
|
81
|
-
[`docs/optimizations.md`](docs/optimizations.md) is the ledger, and
|
|
82
|
-
[`bench/`](bench/) reproduces the numbers.
|
|
99
|
+
[`packages/vx/docs/optimizations.md`](packages/vx/docs/optimizations.md) is the ledger, and
|
|
100
|
+
[`packages/vx-bench/`](packages/vx-bench/) reproduces the numbers.
|
|
83
101
|
|
|
84
102
|
## Built for trust
|
|
85
103
|
|
|
86
|
-
- **Provable cache correctness.** `vx run --verify` re-runs each
|
|
87
|
-
cacheable task and byte-compares outputs (determinism);
|
|
88
|
-
`--verify=inputs` sandboxes it against the declared inputs and fails
|
|
89
|
-
loud naming any undeclared read (completeness). No other runner can
|
|
90
|
-
prove a cache entry safe.
|
|
91
104
|
- **Corruption can't go live.** A remote artifact is verified against
|
|
92
105
|
its content digest and validated before it enters the store
|
|
93
106
|
(zstd-bomb and oversize downloads refused); bad bytes degrade to a
|
|
@@ -97,7 +110,11 @@ with the invariant that keeps it valid —
|
|
|
97
110
|
- **Readiness you can bound.** Persistent tasks gate downstream work
|
|
98
111
|
on a `readyWhen` signal; `exec.timeout` bounds any task (with
|
|
99
112
|
`--timeout` / workspace defaults), `exec.retries` + `--retry` absorb
|
|
100
|
-
flakes
|
|
113
|
+
flakes.
|
|
114
|
+
- **Flaky tasks are found, not guessed.** The same inputs both passing
|
|
115
|
+
and failing is the definition, and vx holds every hash and outcome
|
|
116
|
+
locally: a run names them under its footer, `--summarize` types them
|
|
117
|
+
per task, `vx info` lists them. No service.
|
|
101
118
|
- **Kernel-level sandboxing**, opt-in per task, that fails the build
|
|
102
119
|
on violation instead of hiding it.
|
|
103
120
|
|
|
@@ -119,9 +136,11 @@ vx lock --check && vx run ci --frozen # CI: audit, then run EXACTLY that gra
|
|
|
119
136
|
| `vx lock --check` | full graph | compares — catches env and import drift that byte hashes cannot |
|
|
120
137
|
|
|
121
138
|
Env values read at lock time are frozen by design — cache keys become
|
|
122
|
-
reproducible across machines.
|
|
123
|
-
|
|
124
|
-
|
|
139
|
+
reproducible across machines. Take it for that, not for speed: measured
|
|
140
|
+
on the 1,000-project bench, a plain warm run and a frozen one are a tie
|
|
141
|
+
(177 ms against 165, 2026-09-12), because the config-evaluation cache
|
|
142
|
+
already serves a pure config without evaluating it. No other runner has
|
|
143
|
+
an equivalent.
|
|
125
144
|
|
|
126
145
|
## Everything you need, nothing to configure twice
|
|
127
146
|
|
|
@@ -132,9 +151,9 @@ watch mode · `--dry` / `--graph` plans · persistent dev servers ·
|
|
|
132
151
|
retries, timeouts, `--continue` modes · per-layer cache control
|
|
133
152
|
(`--cache=local:r,remote:`) · `vx why` explains a re-run from the
|
|
134
153
|
persisted input fingerprints · `vx last` replays a recorded run ·
|
|
135
|
-
`vx
|
|
154
|
+
`vx info`,
|
|
136
155
|
`--summarize`, `--profile` Chrome traces, `--report` · `vx cache prune`
|
|
137
|
-
with TTL and size caps · `vx
|
|
156
|
+
with TTL and size caps · `bunx @vzn/vx-migrate` from turbo.json or an Nx graph.
|
|
138
157
|
|
|
139
158
|
## A pipeline with seams — plugins decide what happens
|
|
140
159
|
|
|
@@ -155,37 +174,50 @@ declared in `vx.workspace.ts` hook each stage, Vite-style, on one
|
|
|
155
174
|
| observe | `telemetry(ctx)` / `setup` | receive every run record, or the raw event bus |
|
|
156
175
|
| cli | `commands` | add verbs to `vx` |
|
|
157
176
|
|
|
158
|
-
Core applies **no plugin by default**
|
|
159
|
-
|
|
160
|
-
|
|
177
|
+
Core applies **no plugin by default** and names none. Running here and
|
|
178
|
+
caching here are its floor — the tail of every executor list and cache
|
|
179
|
+
chain — so a workspace with no `vx.workspace.ts` runs and caches, and a
|
|
180
|
+
plugin that declines a task hands it back to this machine. First-party
|
|
181
|
+
plugins:
|
|
161
182
|
[`@vzn/vx-reapi`](packages/vx-reapi) (Bazel Remote Execution API —
|
|
162
183
|
remote cache and remote execution against NativeLink, BuildBuddy,
|
|
163
184
|
Buildbarn or bazel-remote), [`@vzn/vx-otel`](packages/vx-otel)
|
|
164
185
|
(OpenTelemetry traces + metrics + logs, zero SDK deps),
|
|
165
186
|
[`@vzn/vx-github`](packages/vx-github) (Actions job summary + Checks
|
|
166
187
|
API), [`@vzn/vx-mcp`](packages/vx-mcp) (`vx mcp` — a read-only Model
|
|
167
|
-
Context Protocol server for AI coding agents, no SDK),
|
|
168
|
-
`@vzn/vx
|
|
169
|
-
|
|
188
|
+
Context Protocol server for AI coding agents, no SDK),
|
|
189
|
+
[`@vzn/vx-migrate`](packages/vx-migrate) (adoption in one package:
|
|
190
|
+
`turbo()` runs a Turbo repo under vx with nothing written, `bunx
|
|
191
|
+
@vzn/vx-migrate` writes configs from `turbo.json` or an Nx graph, and
|
|
192
|
+
`turboCache()` / `nxCache()` keep a remote cache speaking Turbo's
|
|
193
|
+
`/v8/artifacts` API or Nx's self-hosted spec — the wire is theirs, the
|
|
194
|
+
artifacts are vx's),
|
|
195
|
+
[`@vzn/vx-lockfile`](packages/vx-lockfile) (`pnpm()`, `bun()`, `npm()`,
|
|
196
|
+
`yarn()`: the lockfile keyed per project, so one install re-keys only the
|
|
197
|
+
projects it reaches, and `--affected` follows), and [`@vzn/vx-schedule-history`](packages/vx-schedule-history)
|
|
198
|
+
(order by learned critical path) and [`@vzn/vx-migrate`](packages/vx-migrate)
|
|
199
|
+
(`bunx @vzn/vx-migrate`: turbo.json or an Nx graph → vx.config.ts, through
|
|
200
|
+
core's migration seam). Core ships no plugin and reads no other runner's
|
|
201
|
+
format; nothing
|
|
202
|
+
distributed ships in this repo; the seams are how you build it.
|
|
170
203
|
|
|
171
204
|
## How it compares
|
|
172
205
|
|
|
173
|
-
| | vx
|
|
174
|
-
| ------------------------- |
|
|
175
|
-
| Fully cached, 100 pkgs¹ | **144 ms**
|
|
176
|
-
| Config | TypeScript, evaluated into the cache key
|
|
177
|
-
| Output ownership | **Strict** — wiped before exec AND restore
|
|
178
|
-
| Clean-tree hashing | **Zero reads** (git index OIDs)
|
|
179
|
-
| Daemon required for speed | **No**
|
|
180
|
-
| Per-task sandbox | **Yes** — kernel-level, opt-in
|
|
181
|
-
|
|
|
182
|
-
|
|
|
183
|
-
|
|
|
184
|
-
| Install | **Single binary** — npm or 1 curl line, no Node/Bun needed | npm + Node | npm + Node |
|
|
206
|
+
| | vx | Turborepo | Nx |
|
|
207
|
+
| ------------------------- | ---------------------------------------------------------------- | ------------------------------ | ---------------- |
|
|
208
|
+
| Fully cached, 100 pkgs¹ | **144 ms** | 279 ms | 583+ ms |
|
|
209
|
+
| Config | TypeScript, evaluated into the cache key | JSON (static) | JSON (static) |
|
|
210
|
+
| Output ownership | **Strict** — wiped before exec AND restore | Additive (stale files survive) | Additive |
|
|
211
|
+
| Clean-tree hashing | **Zero reads** (git index OIDs) | git OIDs | re-hash / daemon |
|
|
212
|
+
| Daemon required for speed | **No** | Optional | Yes |
|
|
213
|
+
| Per-task sandbox | **Yes** — kernel-level, opt-in | No | No |
|
|
214
|
+
| Plugin API | **Yes** — executor / cache / telemetry seams | No | Yes (TS-tied) |
|
|
215
|
+
| OTel CI/CD spans | **Yes** — `otel()` plugin, zero OTel-SDK deps | No | Paid |
|
|
216
|
+
| Install | **Single binary** — npm, or one release file; no Node/Bun needed | npm + Node | npm + Node |
|
|
185
217
|
|
|
186
218
|
¹ Wall-clock, direct binaries, same machine and workspace — full
|
|
187
219
|
methodology and more scenarios in
|
|
188
|
-
[`docs/benchmarks.md`](docs/benchmarks.md).
|
|
220
|
+
[`packages/vx/docs/benchmarks.md`](packages/vx/docs/benchmarks.md).
|
|
189
221
|
|
|
190
222
|
## Switching from another runner
|
|
191
223
|
|
|
@@ -233,7 +265,9 @@ Differences to know:
|
|
|
233
265
|
- Persistent tasks: `persistent: { readyWhen: 'regex' }` (Turbo uses just `persistent: true`).
|
|
234
266
|
- Remote caching is a plugin, not a built-in — connect one and every `vx run` reads through it.
|
|
235
267
|
|
|
236
|
-
Side-by-side feature matrix + every known gap: [`docs/comparison.md`](
|
|
268
|
+
Side-by-side feature matrix + every known gap: [`packages/vx/docs/comparison.md`](packages/vx/docs/comparison.md).
|
|
269
|
+
What a Turbo or Nx user relies on, spelled in vx and pinned by a test:
|
|
270
|
+
[`packages/vx/docs/parity.md`](packages/vx/docs/parity.md).
|
|
237
271
|
|
|
238
272
|
## Architecture (one paragraph)
|
|
239
273
|
|
|
@@ -252,47 +286,50 @@ versioned telemetry contract (`TelemetryRecord` / `RunSummaryRecord`)
|
|
|
252
286
|
them. Core never imports a plugin; the arrow only points plugin → core.
|
|
253
287
|
Every module has a docs page; every interface is a swappable seam.
|
|
254
288
|
|
|
255
|
-
Read [`docs/architecture.md`](
|
|
256
|
-
map; the design record lives under [`docs/design/`](
|
|
289
|
+
Read [`packages/vx/docs/architecture.md`](packages/vx/docs/architecture.md) for the module
|
|
290
|
+
map; the design record lives under [`packages/vx/docs/design/`](packages/vx/docs/design/).
|
|
257
291
|
|
|
258
292
|
## Documentation
|
|
259
293
|
|
|
260
|
-
Full technical docs live under [`docs/`](
|
|
294
|
+
Full technical docs live under [`packages/vx/docs/`](packages/vx/docs/) and on the
|
|
261
295
|
[documentation site](https://vznjs.github.io/vx/):
|
|
262
296
|
|
|
263
|
-
- [`docs/architecture.md`](
|
|
264
|
-
- [`docs/schema.md`](
|
|
265
|
-
- [`docs/caching.md`](
|
|
266
|
-
- [`docs/execution.md`](
|
|
267
|
-
- [`docs/cli.md`](
|
|
268
|
-
- [`docs/comparison.md`](
|
|
269
|
-
- [`docs/modules/`](
|
|
297
|
+
- [`packages/vx/docs/architecture.md`](packages/vx/docs/architecture.md) — module map + data flow
|
|
298
|
+
- [`packages/vx/docs/schema.md`](packages/vx/docs/schema.md) — every config field
|
|
299
|
+
- [`packages/vx/docs/caching.md`](packages/vx/docs/caching.md) — cache-key derivation + invalidation table
|
|
300
|
+
- [`packages/vx/docs/execution.md`](packages/vx/docs/execution.md) — `vx run` lifecycle
|
|
301
|
+
- [`packages/vx/docs/cli.md`](packages/vx/docs/cli.md) — every flag
|
|
302
|
+
- [`packages/vx/docs/comparison.md`](packages/vx/docs/comparison.md) — Turbo / Nx / vite-task feature matrix
|
|
303
|
+
- [`packages/vx/docs/modules/`](packages/vx/docs/modules/) — one reference page per source module
|
|
270
304
|
|
|
271
|
-
The design record lives under [`docs/design/`](
|
|
272
|
-
maintainers' handoff is [`docs/STATUS.md`](
|
|
305
|
+
The design record lives under [`packages/vx/docs/design/`](packages/vx/docs/design/); the
|
|
306
|
+
maintainers' handoff is [`packages/vx/docs/STATUS.md`](packages/vx/docs/STATUS.md).
|
|
273
307
|
|
|
274
308
|
## Status
|
|
275
309
|
|
|
276
310
|
**Pre-alpha.** The schema is settling; we bump `CACHE_VERSION` rather
|
|
277
|
-
than maintain back-compat.
|
|
311
|
+
than maintain back-compat. What 1.0 will promise is in
|
|
312
|
+
[`versioning-1.0.md`](packages/vx/docs/design/versioning-1.0.md). **~3,000 core tests plus the package suites; CI green on every commit**;
|
|
278
313
|
the project dogfoods itself (`vx run ci`). Published on npm:
|
|
279
314
|
[`@vzn/vx`](https://www.npmjs.com/package/@vzn/vx) (a prebuilt standalone
|
|
280
315
|
binary).
|
|
281
316
|
|
|
282
317
|
Production readiness for the **core task runner**: the semantics are
|
|
283
|
-
solid; it is dogfooded continuously.
|
|
284
|
-
is
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
|
288
|
-
|
|
|
289
|
-
|
|
|
290
|
-
| Plugin pipeline (
|
|
291
|
-
| `vx init` /
|
|
292
|
-
| REAPI remote cache + execution (`@vzn/vx-reapi`)
|
|
293
|
-
| OTel export (`@vzn/vx-otel`)
|
|
294
|
-
| GitHub Actions (`@vzn/vx-github`)
|
|
295
|
-
| MCP server (`@vzn/vx-mcp`)
|
|
318
|
+
solid; it is dogfooded continuously. Linux and macOS, x64 and arm64;
|
|
319
|
+
Windows runs vx under WSL (POSIX shell is the API), with no native
|
|
320
|
+
build.
|
|
321
|
+
|
|
322
|
+
| Surface | Maturity | Notes |
|
|
323
|
+
| -------------------------------------------------- | -------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
324
|
+
| Core task runner + caching | **production-ready** | dogfooded continuously; ~3,000 core tests + the package suites, green |
|
|
325
|
+
| Plugin pipeline (13 hooks, `commands` included) | **shippable** | crash-isolated, re-validated; the local executor + cache are the floor under every plugin |
|
|
326
|
+
| `vx init` / `@vzn/vx-migrate` (scripts; Turbo, Nx) | **shippable** | one config per package, TODOs where a source cannot say |
|
|
327
|
+
| REAPI remote cache + execution (`@vzn/vx-reapi`) | **shippable** | Bazel AC + CAS + Execute; NativeLink / BuildBuddy / Buildbarn / bazel-remote |
|
|
328
|
+
| OTel export (`@vzn/vx-otel`) | **shippable** | OTLP traces + metrics + logs, zero SDK deps |
|
|
329
|
+
| GitHub Actions (`@vzn/vx-github`) | **shippable** | job summary + Checks API run |
|
|
330
|
+
| MCP server (`@vzn/vx-mcp`) | **shippable** | `vx mcp` — read-only tools for AI agents, no SDK |
|
|
331
|
+
| Adoption (`@vzn/vx-migrate`) | **shippable** | `turbo()`: a `turbo.json` workspace runs with no `vx.config` written; `turboCache()` / `nxCache()`: any `/v8/artifacts` or Nx `/v1/cache` server; the migrate CLI |
|
|
332
|
+
| Lockfile keys (`@vzn/vx-lockfile`) | **shippable** | `pnpm()` `bun()` `npm()` `yarn()`: per-project dependency-closure keys; `--affected` follows |
|
|
296
333
|
|
|
297
334
|
## Development
|
|
298
335
|
|
|
@@ -303,7 +340,7 @@ bun packages/vx/src/bin.ts run ci --all # lint + test + docs build, every pa
|
|
|
303
340
|
bun packages/vx/src/bin.ts run build --filter @vzn/vx # cross-target binaries → packages/vx/dist/
|
|
304
341
|
```
|
|
305
342
|
|
|
306
|
-
vx is self-hosted: every dev task routes through `bun packages/vx/src/bin.ts run <task>` per each package's own `vx.config.ts`. No `package.json` scripts; CI invokes vx directly. Start with [`docs/STATUS.md`](docs/STATUS.md) — the living handoff — and `CLAUDE.md`.
|
|
343
|
+
vx is self-hosted: every dev task routes through `bun packages/vx/src/bin.ts run <task>` per each package's own `vx.config.ts`. No `package.json` scripts; CI invokes vx directly. Start with [`packages/vx/docs/STATUS.md`](packages/vx/docs/STATUS.md) — the living handoff — and `CLAUDE.md`.
|
|
307
344
|
|
|
308
345
|
## License
|
|
309
346
|
|
package/package.json
CHANGED
|
@@ -1,24 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vzn/vx",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.22",
|
|
4
4
|
"description": "An open, extensible monorepo task runner.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
7
7
|
".": {
|
|
8
8
|
"types": "./src/index.ts",
|
|
9
9
|
"import": "./src/index.ts"
|
|
10
|
-
},
|
|
11
|
-
"./plugins/local-executor": {
|
|
12
|
-
"types": "./src/plugins/local-executor/index.ts",
|
|
13
|
-
"import": "./src/plugins/local-executor/index.ts"
|
|
14
|
-
},
|
|
15
|
-
"./plugins/local-cache": {
|
|
16
|
-
"types": "./src/plugins/local-cache/index.ts",
|
|
17
|
-
"import": "./src/plugins/local-cache/index.ts"
|
|
18
|
-
},
|
|
19
|
-
"./plugins/schedule-history": {
|
|
20
|
-
"types": "./src/plugins/schedule-history/index.ts",
|
|
21
|
-
"import": "./src/plugins/schedule-history/index.ts"
|
|
22
10
|
}
|
|
23
11
|
},
|
|
24
12
|
"types": "./src/index.ts",
|
|
@@ -29,23 +17,25 @@
|
|
|
29
17
|
"node": ">=18"
|
|
30
18
|
},
|
|
31
19
|
"optionalDependencies": {
|
|
32
|
-
"@vzn/vx-linux-x64": "0.0.
|
|
33
|
-
"@vzn/vx-linux-arm64": "0.0.
|
|
34
|
-
"@vzn/vx-darwin-x64": "0.0.
|
|
35
|
-
"@vzn/vx-darwin-arm64": "0.0.
|
|
20
|
+
"@vzn/vx-linux-x64": "0.0.22",
|
|
21
|
+
"@vzn/vx-linux-arm64": "0.0.22",
|
|
22
|
+
"@vzn/vx-darwin-x64": "0.0.22",
|
|
23
|
+
"@vzn/vx-darwin-arm64": "0.0.22"
|
|
36
24
|
},
|
|
37
25
|
"dependencies": {
|
|
38
|
-
"@anthropic-ai/sandbox-runtime": "^0.0.
|
|
26
|
+
"@anthropic-ai/sandbox-runtime": "^0.0.76"
|
|
39
27
|
},
|
|
40
28
|
"files": [
|
|
41
29
|
"index.ts",
|
|
42
|
-
"plugins",
|
|
43
30
|
"src",
|
|
44
31
|
"launcher.mjs",
|
|
45
32
|
"README.md",
|
|
46
33
|
"LICENSE"
|
|
47
34
|
],
|
|
48
|
-
"repository":
|
|
35
|
+
"repository": {
|
|
36
|
+
"type": "git",
|
|
37
|
+
"url": "git+https://github.com/vznjs/vx.git"
|
|
38
|
+
},
|
|
49
39
|
"homepage": "https://github.com/vznjs/vx#readme",
|
|
50
40
|
"bugs": "https://github.com/vznjs/vx/issues",
|
|
51
41
|
"license": "MIT",
|
package/src/bin.ts
CHANGED
|
@@ -1,25 +1,84 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
|
-
import { run } from './cli/index.js'
|
|
3
|
-
import { isUserError } from './util/index.js'
|
|
2
|
+
import { registerCoreAlias, run } from './cli/index.js'
|
|
3
|
+
import { fsRefusalHint, isFsRefusal, isUserError } from './util/index.js'
|
|
4
|
+
|
|
5
|
+
// Every `import … from '@vzn/vx'` this process evaluates — a plugin
|
|
6
|
+
// package, a workspace or project config — resolves to THIS core, not to
|
|
7
|
+
// a second copy from node_modules (core-alias.ts says why and what it
|
|
8
|
+
// costs). Registered before any verb runs; the façade loads on first use.
|
|
9
|
+
registerCoreAlias(() => import('./index.js') as Promise<Record<string, unknown>>)
|
|
4
10
|
|
|
5
11
|
// Wrapped in an explicit async main so `bun build --compile` accepts
|
|
6
12
|
// the file. The compile target doesn't allow top-level await.
|
|
13
|
+
let settled = false
|
|
14
|
+
|
|
7
15
|
async function main(): Promise<void> {
|
|
8
16
|
try {
|
|
9
|
-
|
|
17
|
+
const code = await run(process.argv.slice(2))
|
|
18
|
+
// NOTHING calls `process.exit` here, and that is the fix rather than a
|
|
19
|
+
// simplification. Bun drops what a pipe has not yet taken when
|
|
20
|
+
// `process.exit` follows a large write: 300 KB written then exit
|
|
21
|
+
// delivered 64 KiB (128 KiB after a tick), and `vx history --format
|
|
22
|
+
// json` on a 300-project workspace was cut mid-string (2026-09-15).
|
|
23
|
+
// `stdout.end(cb)` fixed that on 1.4.2 — and on 1.3.11 the callback
|
|
24
|
+
// still fires early: 2 MiB written, 214 KB delivered (2026-09-20).
|
|
25
|
+
// Setting the code and letting the loop drain is the one form that
|
|
26
|
+
// does not depend on when a runtime decides a pipe is flushed; it
|
|
27
|
+
// costs a hang if a verb leaves a handle open, which the suite's
|
|
28
|
+
// several hundred spawns of this binary would show at once.
|
|
29
|
+
process.exitCode = code
|
|
10
30
|
} catch (err) {
|
|
11
31
|
// UserError (workspace not found, cycle, config invalid, ...) —
|
|
12
32
|
// print the message only; the stack is noise the user can't act
|
|
13
33
|
// on. Everything else gets the full stack so internal bugs are
|
|
14
34
|
// debuggable.
|
|
15
35
|
if (isUserError(err)) {
|
|
16
|
-
|
|
36
|
+
// A message that already names the tool (`vx why: …`, thrown by a verb
|
|
37
|
+
// that wants its own name in the line) is printed as it is; prefixing
|
|
38
|
+
// it produced `vx: vx why: …` (walkthrough, 2026-09-04).
|
|
39
|
+
const m = err.message
|
|
40
|
+
process.stderr.write(m.startsWith('vx ') ? `${m}\n` : `vx: ${m}\n`)
|
|
41
|
+
} else if (isFsRefusal(err)) {
|
|
42
|
+
// The file system's refusal names the path; the stack would name
|
|
43
|
+
// the verb's write, which the reader cannot act on either.
|
|
44
|
+
process.stderr.write(`vx: ${err.message} — ${fsRefusalHint(err)}\n`)
|
|
17
45
|
} else {
|
|
18
46
|
const message = err instanceof Error ? (err.stack ?? err.message) : String(err)
|
|
19
47
|
process.stderr.write(`vx: ${message}\n`)
|
|
20
48
|
}
|
|
21
|
-
|
|
49
|
+
// Same reason as the success path above: a large stderr is truncated by
|
|
50
|
+
// `process.exit` too, and an error message cut in half is the one a
|
|
51
|
+
// reader most needs whole.
|
|
52
|
+
process.exitCode = 1
|
|
22
53
|
}
|
|
54
|
+
settled = true
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// The loop drained while the verb was still pending: something it awaits
|
|
58
|
+
// can never settle (a plugin hook whose promise is never resolved), and
|
|
59
|
+
// with no exit code set Bun exits 0 — a run whose task failed reported
|
|
60
|
+
// green, and its task had not even run (item 921). An exit that no
|
|
61
|
+
// verdict reached is a failure, and says so.
|
|
62
|
+
process.on('beforeExit', () => {
|
|
63
|
+
if (settled) return
|
|
64
|
+
settled = true
|
|
65
|
+
process.stderr.write(
|
|
66
|
+
'vx: the run stopped before it finished: something it awaited can never settle (a plugin hook?)\n',
|
|
67
|
+
)
|
|
68
|
+
process.exitCode = 1
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
// A reader that leaves is not the run's failure. `vx run build | head -1`
|
|
72
|
+
// closes the pipe after one line; every later write gets EPIPE, which Bun
|
|
73
|
+
// raises as an `error` event on the stream, and an `error` nobody listens
|
|
74
|
+
// for is an uncaught exception: the run died with a stack after its task
|
|
75
|
+
// had succeeded, exit 1, its lock directory left behind (2026-09-16). With
|
|
76
|
+
// a listener the write returns false, the run finishes, saves, releases,
|
|
77
|
+
// and exits with its own verdict; what was going to the reader goes
|
|
78
|
+
// nowhere. Same for stderr (`2>&1 | head`). Here and not in the logger:
|
|
79
|
+
// an embedder's streams are the embedder's.
|
|
80
|
+
for (const stream of [process.stdout, process.stderr]) {
|
|
81
|
+
stream.on('error', () => {})
|
|
23
82
|
}
|
|
24
83
|
|
|
25
84
|
void main()
|