@vzn/vx 0.0.374 → 0.0.376
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/README.md +6 -6
- package/package.json +5 -5
- package/src/graph/index.ts +1 -0
- package/src/graph/scheduler.ts +22 -13
- package/src/orchestrator/run.ts +19 -7
package/README.md
CHANGED
|
@@ -35,7 +35,7 @@ Same graph, commands and concurrency: [how it is measured](https://vznjs.github.
|
|
|
35
35
|
## Why it is faster
|
|
36
36
|
|
|
37
37
|
- **Nothing boots per task:** no daemon, no Node process; vx spawns your command.
|
|
38
|
-
- **No file reads to hash:** on a clean tree, keys come from git's index.
|
|
38
|
+
- **No source-file reads to hash:** on a clean tree, keys come from git's index; each project's `package.json` and the lockfile are still read.
|
|
39
39
|
- **One pass:** one git walk per run; each stat and lookup happens once.
|
|
40
40
|
|
|
41
41
|
## Install
|
|
@@ -44,7 +44,7 @@ Same graph, commands and concurrency: [how it is measured](https://vznjs.github.
|
|
|
44
44
|
npm install -D @vzn/vx # or: pnpm add -D -w · yarn add -D (-W on Yarn 1) · bun add -d
|
|
45
45
|
```
|
|
46
46
|
|
|
47
|
-
A prebuilt binary for Linux and macOS (x64, arm64); no Bun needed. The
|
|
47
|
+
A prebuilt binary for Linux (glibc, not Alpine's musl) and macOS (x64, arm64); no Bun needed. The
|
|
48
48
|
[release](https://github.com/vznjs/vx/releases) binary needs no Node either.
|
|
49
49
|
Windows: use WSL.
|
|
50
50
|
|
|
@@ -103,9 +103,9 @@ vx config. [Migration guide](https://vznjs.github.io/vx/guides/migrate/).
|
|
|
103
103
|
- **A cache you can trust:** TypeScript configs are evaluated before
|
|
104
104
|
hashing, so imports and computed values are in the key. Outputs are
|
|
105
105
|
wiped before every run and restore: no stale file survives.
|
|
106
|
-
- **Sandboxed tasks:** opt in per task;
|
|
107
|
-
|
|
108
|
-
- **Clean exits:** Ctrl-C reaps
|
|
106
|
+
- **Sandboxed tasks:** opt in per task; a workspace file the task did
|
|
107
|
+
not declare is out of its reach, so it cannot poison the cache.
|
|
108
|
+
- **Clean exits:** Ctrl-C reaps each task's process group.
|
|
109
109
|
- **Plugins at every stage:** remote cache, remote execution, telemetry,
|
|
110
110
|
CLI verbs. Core ships none and needs none.
|
|
111
111
|
- **Free:** MIT. No paywall, no cloud, no account.
|
|
@@ -116,7 +116,7 @@ vx config. [Migration guide](https://vznjs.github.io/vx/guides/migrate/).
|
|
|
116
116
|
| ------------------------- | ----------------------------------------- | ------------------------------ | ---------------- |
|
|
117
117
|
| Config | TypeScript, evaluated into the cache key | JSON (static) | JSON (static) |
|
|
118
118
|
| Output ownership | **Strict**: wiped before exec and restore | Additive (stale files survive) | Additive |
|
|
119
|
-
| Clean-tree hashing | **
|
|
119
|
+
| Clean-tree hashing | **No source reads** (git index OIDs) | git OIDs | re-hash / daemon |
|
|
120
120
|
| Daemon required for speed | **No** | No (`turbo run` has none) | Yes |
|
|
121
121
|
| Per-task sandbox | **Yes**: kernel-level, opt-in | No | No |
|
|
122
122
|
| Plugin API | **Yes**: every pipeline stage | No | Yes (TS-tied) |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vzn/vx",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.376",
|
|
4
4
|
"description": "A fast, correct task runner and build cache for JavaScript monorepos.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -21,10 +21,10 @@
|
|
|
21
21
|
"node": ">=18"
|
|
22
22
|
},
|
|
23
23
|
"optionalDependencies": {
|
|
24
|
-
"@vzn/vx-linux-x64": "0.0.
|
|
25
|
-
"@vzn/vx-linux-arm64": "0.0.
|
|
26
|
-
"@vzn/vx-darwin-x64": "0.0.
|
|
27
|
-
"@vzn/vx-darwin-arm64": "0.0.
|
|
24
|
+
"@vzn/vx-linux-x64": "0.0.376",
|
|
25
|
+
"@vzn/vx-linux-arm64": "0.0.376",
|
|
26
|
+
"@vzn/vx-darwin-x64": "0.0.376",
|
|
27
|
+
"@vzn/vx-darwin-arm64": "0.0.376"
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
30
|
"@anthropic-ai/sandbox-runtime": "^0.0.76"
|
package/src/graph/index.ts
CHANGED
package/src/graph/scheduler.ts
CHANGED
|
@@ -338,6 +338,26 @@ class ReadyHeap {
|
|
|
338
338
|
}
|
|
339
339
|
}
|
|
340
340
|
|
|
341
|
+
/**
|
|
342
|
+
* The dead server a dependency stands for: itself, or one a group reaches.
|
|
343
|
+
* A group is a name for its deps, and it finished the moment the server
|
|
344
|
+
* was ready, so a task behind it ran against the dead one.
|
|
345
|
+
*/
|
|
346
|
+
export function deadServerBehind(
|
|
347
|
+
nodes: ReadonlyMap<string, TaskNode>,
|
|
348
|
+
serverDied: (id: string) => boolean,
|
|
349
|
+
id: string,
|
|
350
|
+
): string | undefined {
|
|
351
|
+
if (serverDied(id)) return id
|
|
352
|
+
const n = nodes.get(id)
|
|
353
|
+
if (n === undefined || !isGroupTask(n)) return undefined
|
|
354
|
+
for (const d of n.deps) {
|
|
355
|
+
const dead = deadServerBehind(nodes, serverDied, d)
|
|
356
|
+
if (dead !== undefined) return dead
|
|
357
|
+
}
|
|
358
|
+
return undefined
|
|
359
|
+
}
|
|
360
|
+
|
|
341
361
|
/**
|
|
342
362
|
* Run the task graph. Independent tasks run in parallel up to `concurrency`.
|
|
343
363
|
* If a task fails, its dependents are marked `skipped` but unrelated tasks
|
|
@@ -572,19 +592,8 @@ export async function runGraph(options: ScheduleOptions): Promise<Map<string, Ta
|
|
|
572
592
|
// could fail anyway.
|
|
573
593
|
const aborted = (): boolean => options.signal?.aborted === true
|
|
574
594
|
const serverDied = options.serverDied ?? ((): boolean => false)
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
// the server was ready, so a task behind it ran against the dead one.
|
|
578
|
-
const deadServerVia = (id: string): string | undefined => {
|
|
579
|
-
if (serverDied(id)) return id
|
|
580
|
-
const n = nodes.get(id)
|
|
581
|
-
if (n === undefined || !isGroupTask(n)) return undefined
|
|
582
|
-
for (const d of n.deps) {
|
|
583
|
-
const dead = deadServerVia(d)
|
|
584
|
-
if (dead !== undefined) return dead
|
|
585
|
-
}
|
|
586
|
-
return undefined
|
|
587
|
-
}
|
|
595
|
+
const deadServerVia = (id: string): string | undefined =>
|
|
596
|
+
deadServerBehind(nodes, serverDied, id)
|
|
588
597
|
const servers =
|
|
589
598
|
options.serverDied === undefined
|
|
590
599
|
? []
|
package/src/orchestrator/run.ts
CHANGED
|
@@ -22,6 +22,7 @@ import { DeferredOutputs } from './deferred-outputs.js'
|
|
|
22
22
|
import { resolveDownloadModes } from './download-policy.js'
|
|
23
23
|
import type { TaskExecutor } from '../exec/index.js'
|
|
24
24
|
import {
|
|
25
|
+
deadServerBehind,
|
|
25
26
|
isGroupTask,
|
|
26
27
|
markSurfacedDeps,
|
|
27
28
|
runGraph,
|
|
@@ -873,12 +874,28 @@ async function runOnBus(
|
|
|
873
874
|
`[vx] --exclude-dependencies: ${excluded.unsaved} cached task(s) build on a skipped dependency; what they build is not saved`,
|
|
874
875
|
)
|
|
875
876
|
}
|
|
876
|
-
|
|
877
|
+
// A server that ended on its own is failed (the end of the run says
|
|
878
|
+
// so); what has not yet started hears it before it dispatches (C-88).
|
|
879
|
+
const serverDied = (id: string): boolean => {
|
|
880
|
+
const child = persistentRegistry.get(id)
|
|
881
|
+
return child !== undefined && hasEnded(child) && child.exitCode !== 0
|
|
882
|
+
}
|
|
883
|
+
// Under `always` a task that starts while a server it depends on is
|
|
884
|
+
// dead runs on a failure the outcomes do not hold yet (the server's
|
|
885
|
+
// says `success` until the run ends), so it is seeded here, at its
|
|
886
|
+
// dispatch, and the tracker carries it to what is built on it.
|
|
887
|
+
const taintSeeds = new Set(excluded.seeds)
|
|
888
|
+
const taint = taintTracker(options.continueMode === 'always', taintSeeds, nodes)
|
|
877
889
|
const dependedOn = new Set<string>()
|
|
878
890
|
for (const n of nodes.values()) for (const d of n.deps) dependedOn.add(d)
|
|
879
891
|
|
|
880
892
|
const buildExecuteArgs = (node: TaskNode, upstream: TaskOutcome[], reuseProbe = true) => {
|
|
881
893
|
const probe = reuseProbe ? shortCircuit.preProbed.get(node.id) : undefined
|
|
894
|
+
if (
|
|
895
|
+
options.continueMode === 'always' &&
|
|
896
|
+
node.deps.some((d) => deadServerBehind(nodes, serverDied, d) !== undefined)
|
|
897
|
+
)
|
|
898
|
+
taintSeeds.add(node.id)
|
|
882
899
|
const tainted = taint.judge(node, upstream)
|
|
883
900
|
return {
|
|
884
901
|
node,
|
|
@@ -940,12 +957,7 @@ async function runOnBus(
|
|
|
940
957
|
...(hasPooledExecutor(executors) ? { poolOf: poolOfPlacement(placements) } : {}),
|
|
941
958
|
...(admit !== undefined ? { admit } : {}),
|
|
942
959
|
...(options.continueMode !== undefined ? { continueMode: options.continueMode } : {}),
|
|
943
|
-
|
|
944
|
-
// so); what has not yet started hears it before it dispatches (C-88).
|
|
945
|
-
serverDied: (id) => {
|
|
946
|
-
const child = persistentRegistry.get(id)
|
|
947
|
-
return child !== undefined && hasEnded(child) && child.exitCode !== 0
|
|
948
|
-
},
|
|
960
|
+
serverDied,
|
|
949
961
|
signal: stopRun.signal,
|
|
950
962
|
onStart: (node) => {
|
|
951
963
|
log.taskStart?.(node)
|