@vzn/vx 0.0.59 → 0.0.61
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/package.json +5 -5
- package/src/cache/inputs.ts +40 -5
- package/src/orchestrator/events.ts +11 -14
- package/src/orchestrator/telemetry.ts +0 -4
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vzn/vx",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.61",
|
|
4
4
|
"description": "An open, extensible monorepo task runner.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -17,10 +17,10 @@
|
|
|
17
17
|
"node": ">=18"
|
|
18
18
|
},
|
|
19
19
|
"optionalDependencies": {
|
|
20
|
-
"@vzn/vx-linux-x64": "0.0.
|
|
21
|
-
"@vzn/vx-linux-arm64": "0.0.
|
|
22
|
-
"@vzn/vx-darwin-x64": "0.0.
|
|
23
|
-
"@vzn/vx-darwin-arm64": "0.0.
|
|
20
|
+
"@vzn/vx-linux-x64": "0.0.61",
|
|
21
|
+
"@vzn/vx-linux-arm64": "0.0.61",
|
|
22
|
+
"@vzn/vx-darwin-x64": "0.0.61",
|
|
23
|
+
"@vzn/vx-darwin-arm64": "0.0.61"
|
|
24
24
|
},
|
|
25
25
|
"dependencies": {
|
|
26
26
|
"@anthropic-ai/sandbox-runtime": "^0.0.76"
|
package/src/cache/inputs.ts
CHANGED
|
@@ -386,6 +386,9 @@ async function runRuntimeCommand(
|
|
|
386
386
|
stdin: 'ignore',
|
|
387
387
|
stdout: 'pipe',
|
|
388
388
|
stderr: 'pipe',
|
|
389
|
+
// Its own group, so the probe's whole tree can be taken down with vx
|
|
390
|
+
// (`killProbesOnExit`).
|
|
391
|
+
detached: true,
|
|
389
392
|
})
|
|
390
393
|
} catch (err) {
|
|
391
394
|
// The probe runs through `sh -c` like a task: a box without sh names
|
|
@@ -397,11 +400,18 @@ async function runRuntimeCommand(
|
|
|
397
400
|
}
|
|
398
401
|
throw new UserError(`cache.inputs runtime command failed to spawn: ${command} (cwd: ${cwd})`)
|
|
399
402
|
}
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
403
|
+
liveProbes.add(proc)
|
|
404
|
+
killProbesOnExit()
|
|
405
|
+
let stdout, stderr, exitCode
|
|
406
|
+
try {
|
|
407
|
+
;[stdout, stderr, exitCode] = await Promise.all([
|
|
408
|
+
new Response(proc.stdout).text(),
|
|
409
|
+
new Response(proc.stderr).text(),
|
|
410
|
+
proc.exited,
|
|
411
|
+
])
|
|
412
|
+
} finally {
|
|
413
|
+
liveProbes.delete(proc)
|
|
414
|
+
}
|
|
405
415
|
const output = `${stdout}${stderr}`.trim()
|
|
406
416
|
if (exitCode !== 0) {
|
|
407
417
|
throw new UserError(
|
|
@@ -412,6 +422,31 @@ async function runRuntimeCommand(
|
|
|
412
422
|
return output
|
|
413
423
|
}
|
|
414
424
|
|
|
425
|
+
/**
|
|
426
|
+
* Runtime probes still running. A probe that outlives its answer's need — a
|
|
427
|
+
* run stopped by Ctrl-C, or ended by a refusal, while `node -e …` or a hung
|
|
428
|
+
* `git` still ran — was left behind: vx exited and the probe's shell and
|
|
429
|
+
* what it started ran on under init (A-9). Every probe is its own group, and
|
|
430
|
+
* the ones still listed when vx exits are SIGKILLed with their trees. A
|
|
431
|
+
* `kill -9` of vx runs no exit hook; that residual is the task groups' guard's
|
|
432
|
+
* (exec/kill-tree.ts), which probes are not on.
|
|
433
|
+
*/
|
|
434
|
+
const liveProbes = new Set<ReturnType<typeof Bun.spawn>>()
|
|
435
|
+
let probeExitHooked = false
|
|
436
|
+
function killProbesOnExit(): void {
|
|
437
|
+
if (probeExitHooked) return
|
|
438
|
+
probeExitHooked = true
|
|
439
|
+
process.on('exit', () => {
|
|
440
|
+
for (const p of liveProbes) {
|
|
441
|
+
try {
|
|
442
|
+
process.kill(-p.pid, 'SIGKILL')
|
|
443
|
+
} catch {
|
|
444
|
+
// the group is gone
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
})
|
|
448
|
+
}
|
|
449
|
+
|
|
415
450
|
/**
|
|
416
451
|
* Resolve a list of runtime-input commands to sorted [command, output]
|
|
417
452
|
* pairs. Dedups via the shared `memo` (Promise per key): the first
|
|
@@ -91,6 +91,9 @@ export function createEventBus(): EventBus {
|
|
|
91
91
|
* renderer subscribes via `terminalSubscriber`.
|
|
92
92
|
*/
|
|
93
93
|
export function busLogger(bus: EventBus): Logger {
|
|
94
|
+
// run() reaches runEnd on its success path, in its finally and from a
|
|
95
|
+
// signal; a plugin's onRunEnd hears the first only.
|
|
96
|
+
let ended = false
|
|
94
97
|
return {
|
|
95
98
|
status: (line) => bus.emit({ kind: 'run:status', line }),
|
|
96
99
|
taskStdout: (node, chunk) => bus.emit({ kind: 'task:stdout', node, chunk }),
|
|
@@ -98,7 +101,11 @@ export function busLogger(bus: EventBus): Logger {
|
|
|
98
101
|
taskComplete: (node, outcome) => bus.emit({ kind: 'task:complete', node, outcome }),
|
|
99
102
|
runStart: (info) => bus.emit({ kind: 'run:start', info }),
|
|
100
103
|
taskStart: (node) => bus.emit({ kind: 'task:start', node }),
|
|
101
|
-
runEnd: () =>
|
|
104
|
+
runEnd: () => {
|
|
105
|
+
if (ended) return
|
|
106
|
+
ended = true
|
|
107
|
+
bus.emit({ kind: 'run:end' })
|
|
108
|
+
},
|
|
102
109
|
}
|
|
103
110
|
}
|
|
104
111
|
|
|
@@ -142,19 +149,12 @@ export function terminalSubscriber(sink: Logger): RunEventSubscriber {
|
|
|
142
149
|
* it to `send` — the shared run→consumer forwarding path for whatever an
|
|
143
150
|
* embedder builds on it (an NDJSON stream, an enveloped protocol; core
|
|
144
151
|
* ships neither). Group-task start/complete events are dropped (no command,
|
|
145
|
-
* pure scheduling noise).
|
|
146
|
-
*
|
|
147
|
-
* quiet, so a consumer sees exactly one terminal frame per run. `send`
|
|
152
|
+
* pure scheduling noise). Summary status lines that follow run:end are
|
|
153
|
+
* forwarded too. `send`
|
|
148
154
|
* must be fire-and-forget and tolerant of its own failures (a dead
|
|
149
155
|
* consumer can never break the run); the bus already isolates throws.
|
|
150
156
|
*/
|
|
151
157
|
export function wireForwarder(send: (event: WireEvent) => void): RunEventSubscriber {
|
|
152
|
-
// run() emits run:end TWICE (normal + finally), with the summary footer
|
|
153
|
-
// (run:status lines) emitted in BETWEEN. Drop the duplicate run:end, but
|
|
154
|
-
// keep forwarding everything else — including those post-run:end status
|
|
155
|
-
// lines, so a consumer that renders them gets the footer, and one that
|
|
156
|
-
// ignores run:status is unaffected.
|
|
157
|
-
let endForwarded = false
|
|
158
158
|
// Which tasks the consumer has been told about. A skipped task never
|
|
159
159
|
// reaches the scheduler's onStart, so its completion arrives with no
|
|
160
160
|
// preceding task:start — and `createWireRenderer` resolves a completion's
|
|
@@ -164,10 +164,7 @@ export function wireForwarder(send: (event: WireEvent) => void): RunEventSubscri
|
|
|
164
164
|
// fidelity (real requested/surfaced/command) instead of a stand-in.
|
|
165
165
|
const started = new Set<string>()
|
|
166
166
|
return (event) => {
|
|
167
|
-
if (
|
|
168
|
-
if (endForwarded) return
|
|
169
|
-
endForwarded = true
|
|
170
|
-
} else if (
|
|
167
|
+
if (
|
|
171
168
|
(event.kind === 'task:start' || event.kind === 'task:complete') &&
|
|
172
169
|
isGroupTask(event.node)
|
|
173
170
|
) {
|
|
@@ -413,7 +413,6 @@ export function createTelemetrySource(args: {
|
|
|
413
413
|
}
|
|
414
414
|
}
|
|
415
415
|
|
|
416
|
-
let endEmitted = false
|
|
417
416
|
const subscriber: RunEventSubscriber = (event: RunEvent) => {
|
|
418
417
|
const ts = Date.now()
|
|
419
418
|
switch (event.kind) {
|
|
@@ -472,9 +471,6 @@ export function createTelemetrySource(args: {
|
|
|
472
471
|
case 'run:status':
|
|
473
472
|
return // status lines are terminal-rendering noise, not telemetry
|
|
474
473
|
case 'run:end':
|
|
475
|
-
// run() emits run:end twice (normal + finally); emit one record.
|
|
476
|
-
if (endEmitted) return
|
|
477
|
-
endEmitted = true
|
|
478
474
|
deliver({ v: TELEMETRY_SCHEMA_VERSION, kind: 'run.end', runId, ts })
|
|
479
475
|
return
|
|
480
476
|
}
|