@vzn/vx 0.0.0 → 0.0.12

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.
Files changed (90) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +332 -1
  3. package/launcher.mjs +84 -0
  4. package/package.json +44 -3
  5. package/src/bin.ts +25 -0
  6. package/src/cache/cache.ts +1832 -0
  7. package/src/cache/cas-backend.ts +97 -0
  8. package/src/cache/digest.ts +47 -0
  9. package/src/cache/index.ts +29 -0
  10. package/src/cache/inputs.ts +833 -0
  11. package/src/cache/layered-cache.ts +335 -0
  12. package/src/cache/remote-cache.ts +251 -0
  13. package/src/cache/tar.ts +343 -0
  14. package/src/cli/backend.ts +23 -0
  15. package/src/cli/cache.ts +101 -0
  16. package/src/cli/format.ts +15 -0
  17. package/src/cli/help.ts +123 -0
  18. package/src/cli/index.ts +85 -0
  19. package/src/cli/info.ts +83 -0
  20. package/src/cli/lock.ts +135 -0
  21. package/src/cli/mcp-rpc.ts +292 -0
  22. package/src/cli/mcp.ts +92 -0
  23. package/src/cli/migrate-nx.ts +450 -0
  24. package/src/cli/migrate-turbo.ts +364 -0
  25. package/src/cli/migrate.ts +219 -0
  26. package/src/cli/plan-format.ts +148 -0
  27. package/src/cli/run.ts +661 -0
  28. package/src/cli/show.ts +201 -0
  29. package/src/cli/upgrade.ts +103 -0
  30. package/src/cli/watch.ts +287 -0
  31. package/src/config.ts +421 -0
  32. package/src/exec/env.ts +74 -0
  33. package/src/exec/index.ts +13 -0
  34. package/src/exec/runner.ts +470 -0
  35. package/src/exec/sandbox-runtime.ts +578 -0
  36. package/src/graph/dependency-spec.ts +69 -0
  37. package/src/graph/index.ts +18 -0
  38. package/src/graph/scheduler.ts +482 -0
  39. package/src/graph/task-graph.ts +325 -0
  40. package/src/index.ts +197 -0
  41. package/src/orchestrator/colors.ts +63 -0
  42. package/src/orchestrator/devframe-surface.ts +74 -0
  43. package/src/orchestrator/events.ts +277 -0
  44. package/src/orchestrator/execute-task.ts +883 -0
  45. package/src/orchestrator/framed-output.ts +461 -0
  46. package/src/orchestrator/history.ts +148 -0
  47. package/src/orchestrator/index.ts +163 -0
  48. package/src/orchestrator/local-shortcircuit.ts +147 -0
  49. package/src/orchestrator/logger.ts +526 -0
  50. package/src/orchestrator/metrics.ts +1576 -0
  51. package/src/orchestrator/options.ts +176 -0
  52. package/src/orchestrator/plan.ts +129 -0
  53. package/src/orchestrator/plugin-host.ts +190 -0
  54. package/src/orchestrator/plugin.ts +283 -0
  55. package/src/orchestrator/predict.ts +106 -0
  56. package/src/orchestrator/prepare.ts +297 -0
  57. package/src/orchestrator/protocol.ts +142 -0
  58. package/src/orchestrator/remote-cache-setup.ts +60 -0
  59. package/src/orchestrator/remote-prefetch.ts +81 -0
  60. package/src/orchestrator/run-artifacts.ts +111 -0
  61. package/src/orchestrator/run-context.ts +205 -0
  62. package/src/orchestrator/run-report.ts +132 -0
  63. package/src/orchestrator/run-state.ts +145 -0
  64. package/src/orchestrator/run.ts +842 -0
  65. package/src/orchestrator/stable-keys.ts +177 -0
  66. package/src/orchestrator/status-line.ts +287 -0
  67. package/src/orchestrator/summary.ts +291 -0
  68. package/src/orchestrator/tally.ts +69 -0
  69. package/src/orchestrator/task-hash.ts +238 -0
  70. package/src/orchestrator/telemetry-host.ts +80 -0
  71. package/src/orchestrator/telemetry.ts +350 -0
  72. package/src/orchestrator/upstream.ts +93 -0
  73. package/src/orchestrator/verify.ts +141 -0
  74. package/src/orchestrator/wire-render.ts +92 -0
  75. package/src/orchestrator/wire.ts +212 -0
  76. package/src/util/errors.ts +11 -0
  77. package/src/util/hash.ts +25 -0
  78. package/src/util/index.ts +7 -0
  79. package/src/util/paths.ts +16 -0
  80. package/src/util/ulid.ts +15 -0
  81. package/src/version.ts +10 -0
  82. package/src/workspace/affected.ts +118 -0
  83. package/src/workspace/filter.ts +155 -0
  84. package/src/workspace/fingerprint.ts +41 -0
  85. package/src/workspace/index.ts +27 -0
  86. package/src/workspace/lockfile.ts +118 -0
  87. package/src/workspace/nested-dirs.ts +34 -0
  88. package/src/workspace/package-graph.ts +148 -0
  89. package/src/workspace/project-loader.ts +470 -0
  90. package/src/workspace/workspace.ts +176 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 nxt contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1 +1,332 @@
1
- Reserved by the vx project. Real release to follow.
1
+ # vx
2
+
3
+ **The fastest way to build a monorepo.**
4
+
5
+ vx runs your task graph, remembers every result, and never does the
6
+ same work twice. Fully cached runs finish in milliseconds — 144 ms
7
+ across 100 packages, 0.62 s across a 1090-package graph of 3,270
8
+ tasks. Measured, reproducible, on hardware you own.
9
+
10
+ One binary. No daemon. No Node. Nothing to babysit.
11
+
12
+ 📖 **[Documentation site →](https://vznjs.github.io/vx/)** — guides,
13
+ architecture, caching, and the full CLI / config reference.
14
+
15
+ ```sh
16
+ # From npm — ships the prebuilt standalone binary (no Bun required):
17
+ npm install -g @vzn/vx # or: pnpm add -g @vzn/vx · bun add -g @vzn/vx
18
+
19
+ # Or the zero-dependency install script (no Node/npm needed):
20
+ curl -fsSL https://raw.githubusercontent.com/vznjs/vx/main/install.sh | sh
21
+ ```
22
+
23
+ ```ts
24
+ // vx.config.ts
25
+ import { defineProject } from '@vzn/vx'
26
+
27
+ export default defineProject({
28
+ tasks: {
29
+ build: {
30
+ exec: { command: 'tsc -b' },
31
+ dependsOn: ['^build'],
32
+ cache: {
33
+ inputs: { files: ['src/**'] },
34
+ outputs: { files: ['dist/**'] },
35
+ },
36
+ },
37
+ test: {
38
+ exec: { command: 'bun test' },
39
+ dependsOn: ['build'],
40
+ cache: { inputs: { files: ['src/**', 'tests/**'] }, outputs: { files: [] } },
41
+ },
42
+ ci: { dependsOn: ['lint', 'test'] }, // umbrella; runs both
43
+ },
44
+ })
45
+ ```
46
+
47
+ ```sh
48
+ vx run build # cwd project + its workspace deps
49
+ vx run test --all # every project that declares `test`
50
+ vx run ci --affected # only what changed since origin/HEAD
51
+ vx watch lint # re-run on file changes
52
+ vx run build --dry # show the plan, don't execute
53
+ ```
54
+
55
+ ## Beyond a task runner — what shipped in the 2026-06 platform arc
56
+
57
+ vx now ships an **open platform**, not just a CLI. Every surface
58
+ below is built into the binary; no external services required.
59
+
60
+ ```sh
61
+ vx mcp # Model Context Protocol server (stdio)
62
+ # — Claude Code / Cursor / Continue.dev talk to vx as a typed tool
63
+
64
+ vx coordinator build test --workers 4 # start a distributed-CI coordinator
65
+ vx run --worker ws://coord:5180 # pull tasks from a coordinator and execute them
66
+
67
+ vx serve --ui --open # unified backend + embedded dashboard + open browser
68
+ # /v1/* JSON, SSE events, WS run protocol, CORS *
69
+ ```
70
+
71
+ ### Open platform highlights
72
+
73
+ - **Wire protocol = JSON-RPC 2.0 + OTel LogRecord payload.** Any
74
+ JSON-RPC client works against `vx serve`. Three transports — WS,
75
+ SSE, NDJSON — off the same bus.
76
+ - **MCP server with live tools** (`getCacheStats`, `getRunHistory`,
77
+ `explainCacheKey`, `whyDidThisRerun`) that read your real
78
+ `cache.db`. Drop into any agent's stdio MCP slot.
79
+ - **Plugin API.** Declare `plugins: [...]` in `vx.workspace.ts`;
80
+ each plugin subscribes to bus lifecycle events with crash
81
+ isolation per hook.
82
+ - **Predictive scheduling.** Opt in with `predictive: true` in
83
+ `vx.workspace.ts` — the scheduler reads run history and picks
84
+ the task on the longest expected remaining critical path.
85
+ - **Distributed CI.** `vx coordinator` + `vx run --worker` over the
86
+ same protocol. Content-addressed: any worker producing
87
+ `<hash>` satisfies every consumer of `<hash>`.
88
+ - **OTel CI/CD spans (native).** Set `OTEL_EXPORTER_OTLP_ENDPOINT`,
89
+ install the three `@opentelemetry/*` peer deps — core speaks OTel
90
+ natively; every event flows to Grafana / Honeycomb / Datadog /
91
+ Tempo with zero bridge package.
92
+ - **Self-host vx serve.** Same backend everywhere — laptop, Docker,
93
+ any container runtime. JSON `/v1/*` metrics API + WebSocket run
94
+ protocol + SSE event stream + permissive CORS. One stack.
95
+ - **Dashboard embedded in the binary.** `vx serve --ui` serves a Solid
96
+ SPA at `/` — task averages, p50/p99, cache savings, recent runs,
97
+ flamegraphs — compiled into `vx` itself, nothing to install.
98
+ Connection picker switches between local and hosted backends.
99
+
100
+ Each lives behind a one-paragraph design doc under
101
+ `docs/design/*-2026-06.md`. Phase-by-phase implementation log:
102
+ `docs/progress/implementation-log-2026-06.md`.
103
+
104
+ ## A cache that actually understands your build
105
+
106
+ Every task runner caches. vx caches _correctly_ — and stops work
107
+ others would redo:
108
+
109
+ - **Config is code, and the cache knows it.** `vx.config.ts` is
110
+ evaluated before hashing, so imports, presets, and computed values
111
+ all participate in cache identity. Change a shared preset, and
112
+ exactly the right tasks re-run.
113
+ - **Outputs are owned.** Declared outputs are wiped before every
114
+ execution and every restore. Your tree ends each run bit-identical
115
+ to the cached snapshot — stale files cannot exist.
116
+ - **Hashes come from git.** On a clean tree, deriving every cache key
117
+ costs zero file reads, zero stats, zero database lookups. At
118
+ 15,000 files that's a 3.2× faster warm path.
119
+
120
+ ## Speed is a design discipline
121
+
122
+ Exact bitset graph algorithms for scheduling. One bulk git
123
+ enumeration per run, partitioned by binary search. Restores that
124
+ skip extraction entirely when the tree already matches. In-process
125
+ tar (no subprocess on the hot path). Atomic artifact publishes.
126
+ Single-transaction metadata writes. Every optimization is recorded
127
+ with the invariant that keeps it valid —
128
+ [`docs/optimizations.md`](docs/optimizations.md) is the ledger, and
129
+ [`bench/`](bench/) reproduces the numbers.
130
+
131
+ ## Built for trust
132
+
133
+ - **Signed artifacts.** HMAC signing on the remote-cache wire; with a
134
+ key configured, unsigned or tampered artifacts are rejected and the
135
+ task simply re-runs. A poisoned cache can't reach your machines.
136
+ - **Corruption can't go live.** Artifacts are validated before they
137
+ enter the store; bad bytes degrade to a cache miss, never a crash.
138
+ - **Clean exits.** SIGINT/SIGTERM reap every child process — no
139
+ orphaned dev servers in CI.
140
+ - **Readiness you can bound.** Persistent tasks gate downstream work
141
+ on a `readyWhen` signal with a `readyTimeoutMs` ceiling.
142
+ - **Kernel-level sandboxing**, opt-in per task, that fails the build
143
+ on violation instead of hiding it.
144
+
145
+ ## Reproducible graphs, when you want them
146
+
147
+ Configs are TypeScript — powerful, but a program's output can vary
148
+ with its environment. `vx lock` freezes the fully-resolved task graph
149
+ into a committed `vx-lock.json`, pnpm-style:
150
+
151
+ ```sh
152
+ vx lock # evaluate everything once, write vx-lock.json
153
+ vx lock --check && vx run ci --frozen # CI: audit, then run EXACTLY that graph
154
+ ```
155
+
156
+ | Command | Evaluates configs | Uses lock |
157
+ | ----------------- | ----------------- | --------------------------------------------------------------- |
158
+ | `vx run` | always, live | never — local truth has no asterisks |
159
+ | `vx run --frozen` | never | yes; refuses if absent or a config file changed since locking |
160
+ | `vx lock --check` | full graph | compares — catches env and import drift that byte hashes cannot |
161
+
162
+ Env values read at lock time are frozen by design — cache keys become
163
+ reproducible across machines. Bonus: `--frozen` runs skip config
164
+ evaluation entirely (~120 ms back per 1,000 packages). No other
165
+ runner has an equivalent.
166
+
167
+ ## Everything you need, nothing to configure twice
168
+
169
+ TypeScript config with real imports · task graph with `^task`
170
+ resolution that bridges packages without the task · multi-task runs
171
+ with one shared graph · pnpm-style filters and `--affected` ·
172
+ watch mode · `--dry` / `--graph` plans · persistent dev servers ·
173
+ remote caching via two env vars, wire-compatible with existing
174
+ artifact servers · `vx stats`, `--summarize`, `--profile` Chrome
175
+ traces · `vx cache prune` with TTL and size caps.
176
+
177
+ ## How it compares
178
+
179
+ | | vx | Turborepo | Nx |
180
+ | ------------------------- | -------------------------------------------------------------------- | ------------------------------ | ------------------- |
181
+ | Fully cached, 100 pkgs¹ | **144 ms** | 279 ms | 583+ ms |
182
+ | Config | TypeScript, evaluated into the cache key | JSON (static) | JSON (static) |
183
+ | Output ownership | **Strict** — wiped before exec AND restore | Additive (stale files survive) | Additive |
184
+ | Clean-tree hashing | **Zero reads** (git index OIDs) | git OIDs | re-hash / daemon |
185
+ | Daemon required for speed | **No** | Optional | Yes |
186
+ | Artifact signing | **Hard-fail** on unsigned | Soft | No |
187
+ | Per-task sandbox | **Yes** — kernel-level, opt-in | No | No |
188
+ | MCP server for AI agents | **Yes** (`vx mcp`, stdio) | No | No |
189
+ | Distributed CI execution | **Yes** — OSS, self-hostable (`vx coordinator` + `vx run --worker`) | No | Paid (Nx Cloud DTE) |
190
+ | Dashboard SPA | **Yes** — bundled into `vx serve --ui`, Solid + p50/p99 + sparklines | No | Paid |
191
+ | Self-hosted cloud | **Yes** — same `vx serve` in Docker; one stack | Vercel-only | No (proprietary) |
192
+ | Plugin API | **Yes** — Vite-style lifecycle hooks | No | Yes (TS-tied) |
193
+ | Predictive scheduling | **Yes** (opt-in: `predictive: true`) | No | No |
194
+ | OTel CI/CD spans | **Yes** (`OTEL_EXPORTER_OTLP_ENDPOINT`) | No | Paid |
195
+ | Install | **Single binary** — 1 curl line | npm + Node | npm + Node |
196
+
197
+ ¹ Wall-clock, direct binaries, same machine and workspace — full
198
+ methodology and more scenarios in
199
+ [`docs/benchmarks.md`](docs/benchmarks.md).
200
+
201
+ ## Switching from another runner
202
+
203
+ Most projects can move in an afternoon. The mapping is mechanical:
204
+
205
+ ```jsonc
206
+ // turbo.json (before)
207
+ {
208
+ "tasks": {
209
+ "build": {
210
+ "dependsOn": ["^build"],
211
+ "inputs": ["src/**"],
212
+ "outputs": ["dist/**"],
213
+ "env": ["NODE_ENV"],
214
+ },
215
+ },
216
+ }
217
+ ```
218
+
219
+ ```ts
220
+ // vx.config.ts (after)
221
+ import { defineProject } from '@vzn/vx'
222
+ export default defineProject({
223
+ tasks: {
224
+ build: {
225
+ // Name the command (Turbo reads package.json scripts). The child
226
+ // env is ISOLATED: a cache-input env var must also be passed
227
+ // through, or the key would vary while the task can't see it.
228
+ exec: { command: 'tsc -b', env: { passThrough: ['NODE_ENV'] } },
229
+ dependsOn: ['^build'],
230
+ cache: {
231
+ inputs: { files: ['src/**'], env: ['NODE_ENV'] },
232
+ outputs: { files: ['dist/**'] },
233
+ },
234
+ },
235
+ },
236
+ })
237
+ ```
238
+
239
+ Differences to know:
240
+
241
+ - vx requires `exec.command` in the config — we don't read `package.json` scripts implicitly.
242
+ - vx requires `cache.inputs.files` when caching is enabled (no default `$TURBO_DEFAULT$`).
243
+ - vx defaults caching **off**; opt in per task by adding the `cache` block.
244
+ - Persistent tasks: `persistent: { readyWhen: 'regex' }` (Turbo uses just `persistent: true`).
245
+ - Remote cache: same wire format. Existing `VERCEL_*` / Turbo-cache-server tokens work via `VX_REMOTE_CACHE_TOKEN`.
246
+
247
+ Side-by-side feature matrix + every known gap: [`docs/comparison.md`](./docs/comparison.md).
248
+
249
+ ## Architecture (one paragraph)
250
+
251
+ `bin.ts → cli/index.ts` dispatches subcommands.
252
+ `orchestrator/run.ts:run()` calls `prepareRun()` which discovers the
253
+ workspace, loads configs, builds the package + task graph, opens the
254
+ cache (local SQLite + optional remote layer), loads
255
+ `HistoryProvider` (if `predictive: true`), and installs plugins from
256
+ `vx.workspace.ts`. The scheduler runs the graph in topological order
257
+ with bounded concurrency; each task hits the cache (hash → get →
258
+ restore on hit; spawn → save on miss) or short-circuits as a group /
259
+ persistent. Every observation flows through one event bus —
260
+ terminal renderer, MCP server, OTel bridge, user plugins, and cloud
261
+ uploader all subscribe to it. The on-wire form (JSON-RPC 2.0 +
262
+ OTel-LogRecord-shaped payload) is the same across WS / SSE / NDJSON
263
+ on `vx serve` and across the distributed-CI coordinator. Every
264
+ module has a docs page; every interface is a swappable seam.
265
+
266
+ Read [`docs/architecture.md`](./docs/architecture.md) for the module
267
+ map. The 2026-06 platform arc is documented under
268
+ [`docs/design/`](./docs/design/) and
269
+ [`docs/progress/implementation-log-2026-06.md`](./docs/progress/implementation-log-2026-06.md).
270
+
271
+ ## Documentation
272
+
273
+ Full technical docs live under [`docs/`](./docs/):
274
+
275
+ **Core**
276
+
277
+ - [`docs/architecture.md`](./docs/architecture.md) — module map + data flow
278
+ - [`docs/schema.md`](./docs/schema.md) — every config field
279
+ - [`docs/caching.md`](./docs/caching.md) — cache-key derivation + invalidation table
280
+ - [`docs/execution.md`](./docs/execution.md) — `vx run` lifecycle
281
+ - [`docs/cli.md`](./docs/cli.md) — every flag
282
+ - [`docs/comparison.md`](./docs/comparison.md) — Turbo / Nx / vite-task feature matrix
283
+ - [`docs/modules/`](./docs/modules/) — one reference page per source module
284
+
285
+ **Design + 2026-06 platform arc** (`docs/design/`)
286
+
287
+ - [`architecture-north-star-2026-06.md`](./docs/design/architecture-north-star-2026-06.md) — the unified vision
288
+ - [`architecture-review-2026-06.md`](./docs/design/architecture-review-2026-06.md) — review + applied checklist
289
+ - [`wire-protocol-2026-06.md`](./docs/design/wire-protocol-2026-06.md) — JSON-RPC 2.0 + OTel envelope (shipped)
290
+ - [`distributed-ci-2026-06.md`](./docs/design/distributed-ci-2026-06.md) — coordinator + worker (Phase A-B shipped)
291
+ - [`vx-cloud-2026-06.md`](./docs/design/vx-cloud-2026-06.md) — original CF cloud (superseded; vx serve now runs in Docker)
292
+ - [`extension-protocol-2026-06.md`](./docs/design/extension-protocol-2026-06.md) — subscriber/inspector/driver/plugin (Phase 1 shipped)
293
+ - [`predictive-execution-2026-06.md`](./docs/design/predictive-execution-2026-06.md) — history-aware scheduling (Phase A-B shipped)
294
+ - [`docs/progress/implementation-log-2026-06.md`](./docs/progress/implementation-log-2026-06.md) — phase-by-phase narrative
295
+
296
+ ## Status
297
+
298
+ **Pre-alpha.** The schema is settling; we bump `CACHE_VERSION` rather
299
+ than maintain back-compat. **958+ tests across 70 files; CI green on
300
+ every commit**; the project dogfoods itself (`bun run ci` → `vx run ci`).
301
+
302
+ Production readiness for the **core task runner**: the semantics are
303
+ solid. The rough edges are operational (Windows unsupported, no
304
+ published versions on npm).
305
+
306
+ Production readiness for the **2026-06 platform layer**:
307
+
308
+ | Surface | Maturity | Notes |
309
+ | --------------------------------------------- | -------------------------------- | ------------------------------------------------------------------------ |
310
+ | Core task runner + caching | **production-ready** | dogfooded continuously; 836 tests pre-existing, all green |
311
+ | `vx mcp` | **shippable** | live cache.db tools, stdio; agents work today |
312
+ | `vx serve` (WS + SSE + NDJSON, JSON-RPC 2.0) | **shippable** | accepts both legacy + new envelope; `curl` works |
313
+ | `vx coordinator` + `vx run --worker` | **shippable for self-hosted CI** | content-addressed assignment, disconnect recovery |
314
+ | Plugin API | **shippable** | crash-isolated, lifecycle hooks fire end-to-end |
315
+ | Predictive scheduling | **shippable as opt-in** | gated on `predictive: true` + observed data |
316
+ | Dashboard SPA (`packages/cloud/ui`, embedded) | **scaffold** | served by `vx-cloud`; HTTP /v1/\* reads; pages need real-world iteration |
317
+ | OTel export (`@vzn/vx-otel` telemetry plugin) | **shippable** | declare `otel()` in `vx.workspace.ts`; OTLP traces + metrics |
318
+
319
+ ## Development
320
+
321
+ ```sh
322
+ git clone https://github.com/vznjs/vx && cd vx
323
+ bun install
324
+ bun src/bin.ts run ci # format-check + lint + test
325
+ bun src/bin.ts run build # cross-target binaries → dist/
326
+ ```
327
+
328
+ 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.
329
+
330
+ ## License
331
+
332
+ MIT — see [LICENSE](./LICENSE).
package/launcher.mjs ADDED
@@ -0,0 +1,84 @@
1
+ #!/usr/bin/env node
2
+ // npm launcher for the `vx` command — the entry the published `@vzn/vx`
3
+ // package's `bin` points at. It execs the prebuilt standalone binary shipped
4
+ // as a per-platform optionalDependency (@vzn/vx-<platform>), so end users get
5
+ // vx WITHOUT installing Bun. This file is authored for the PUBLISHED layout:
6
+ // at install time it sits at the package root, next to `src/`.
7
+ //
8
+ // Resolution order:
9
+ // 1. the matching @vzn/vx-<platform> optionalDependency's binary (the
10
+ // normal path — esbuild/turborepo/biome model, no install-time download);
11
+ // 2. a source fallback: `bun src/bin.ts` (works in a source checkout, or on a
12
+ // platform with no prebuilt binary if the user happens to have Bun).
13
+ // Anything else is a clear, actionable error.
14
+
15
+ import { spawnSync } from 'node:child_process'
16
+ import { existsSync } from 'node:fs'
17
+ import { createRequire } from 'node:module'
18
+ import { dirname, join } from 'node:path'
19
+ import { fileURLToPath } from 'node:url'
20
+
21
+ const require = createRequire(import.meta.url)
22
+ const here = dirname(fileURLToPath(import.meta.url))
23
+
24
+ const PLATFORM_PACKAGES = {
25
+ 'linux-x64': '@vzn/vx-linux-x64',
26
+ 'linux-arm64': '@vzn/vx-linux-arm64',
27
+ 'darwin-x64': '@vzn/vx-darwin-x64',
28
+ 'darwin-arm64': '@vzn/vx-darwin-arm64',
29
+ }
30
+
31
+ const key = `${process.platform}-${process.arch}`
32
+ const args = process.argv.slice(2)
33
+
34
+ function platformBinary() {
35
+ const pkg = PLATFORM_PACKAGES[key]
36
+ if (pkg === undefined) return undefined
37
+ try {
38
+ // No `exports` restriction on the platform packages, so package.json
39
+ // resolves; the binary sits beside it. Works under npm hoisting + pnpm.
40
+ const manifest = require.resolve(`${pkg}/package.json`)
41
+ const bin = join(dirname(manifest), 'vx')
42
+ return existsSync(bin) ? bin : undefined
43
+ } catch {
44
+ return undefined
45
+ }
46
+ }
47
+
48
+ function hasBun() {
49
+ const probe = spawnSync('bun', ['--version'], { stdio: 'ignore' })
50
+ return probe.status === 0
51
+ }
52
+
53
+ function run(cmd, cmdArgs) {
54
+ const res = spawnSync(cmd, cmdArgs, { stdio: 'inherit' })
55
+ if (res.error) {
56
+ process.stderr.write(`vx: failed to launch (${res.error.message})\n`)
57
+ process.exit(1)
58
+ }
59
+ // Mirror the child's exit; a signal death maps to the POSIX 128+signo code.
60
+ if (res.signal) process.exit(1)
61
+ process.exit(res.status ?? 0)
62
+ }
63
+
64
+ const bin = platformBinary()
65
+ if (bin !== undefined) {
66
+ run(bin, args)
67
+ }
68
+
69
+ // No prebuilt binary for this platform — fall back to the shipped source if Bun
70
+ // is available (a source checkout, or an unsupported platform + Bun installed).
71
+ const sourceEntry = join(here, 'src', 'bin.ts')
72
+ if (existsSync(sourceEntry) && hasBun()) {
73
+ run('bun', [sourceEntry, ...args])
74
+ }
75
+
76
+ const supported = Object.keys(PLATFORM_PACKAGES).join(', ')
77
+ process.stderr.write(
78
+ `vx: no prebuilt binary for ${key}.\n` +
79
+ ` Supported platforms: ${supported}.\n` +
80
+ ` If your platform should be supported, reinstall so npm fetches the\n` +
81
+ ` matching @vzn/vx-${key} optionalDependency, or install Bun (>=1.3) to\n` +
82
+ ` run vx from source.\n`,
83
+ )
84
+ process.exit(1)
package/package.json CHANGED
@@ -1,6 +1,47 @@
1
1
  {
2
2
  "name": "@vzn/vx",
3
- "version": "0.0.0",
4
- "description": "Placeholder to reserve the name and enable npm trusted publishing. Real release to follow.",
5
- "license": "MIT"
3
+ "version": "0.0.12",
4
+ "description": "An open, extensible monorepo task runner.",
5
+ "type": "module",
6
+ "exports": {
7
+ ".": {
8
+ "types": "./src/index.ts",
9
+ "import": "./src/index.ts"
10
+ }
11
+ },
12
+ "types": "./src/index.ts",
13
+ "bin": {
14
+ "vx": "./launcher.mjs"
15
+ },
16
+ "engines": {
17
+ "node": ">=18"
18
+ },
19
+ "optionalDependencies": {
20
+ "@vzn/vx-linux-x64": "0.0.12",
21
+ "@vzn/vx-linux-arm64": "0.0.12",
22
+ "@vzn/vx-darwin-x64": "0.0.12",
23
+ "@vzn/vx-darwin-arm64": "0.0.12"
24
+ },
25
+ "dependencies": {
26
+ "@anthropic-ai/sandbox-runtime": "^0.0.56"
27
+ },
28
+ "files": [
29
+ "src",
30
+ "launcher.mjs",
31
+ "README.md",
32
+ "LICENSE"
33
+ ],
34
+ "repository": "https://github.com/vznjs/vx",
35
+ "homepage": "https://github.com/vznjs/vx#readme",
36
+ "bugs": "https://github.com/vznjs/vx/issues",
37
+ "license": "MIT",
38
+ "keywords": [
39
+ "monorepo",
40
+ "task-runner",
41
+ "build",
42
+ "cache",
43
+ "bun",
44
+ "turborepo",
45
+ "nx"
46
+ ]
6
47
  }
package/src/bin.ts ADDED
@@ -0,0 +1,25 @@
1
+ #!/usr/bin/env bun
2
+ import { run } from './cli/index.js'
3
+ import { UserError } from './util/index.js'
4
+
5
+ // Wrapped in an explicit async main so `bun build --compile` accepts
6
+ // the file. The compile target doesn't allow top-level await.
7
+ async function main(): Promise<void> {
8
+ try {
9
+ process.exit(await run(process.argv.slice(2)))
10
+ } catch (err) {
11
+ // UserError (workspace not found, cycle, config invalid, ...) —
12
+ // print the message only; the stack is noise the user can't act
13
+ // on. Everything else gets the full stack so internal bugs are
14
+ // debuggable.
15
+ if (err instanceof UserError) {
16
+ process.stderr.write(`vx: ${err.message}\n`)
17
+ } else {
18
+ const message = err instanceof Error ? (err.stack ?? err.message) : String(err)
19
+ process.stderr.write(`vx: ${message}\n`)
20
+ }
21
+ process.exit(1)
22
+ }
23
+ }
24
+
25
+ void main()