@vzn/vx 0.0.498 → 0.0.500
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
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vzn/vx",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.500",
|
|
4
4
|
"description": "The fastest 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.500",
|
|
25
|
+
"@vzn/vx-linux-arm64": "0.0.500",
|
|
26
|
+
"@vzn/vx-darwin-x64": "0.0.500",
|
|
27
|
+
"@vzn/vx-darwin-arm64": "0.0.500"
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
30
|
"@anthropic-ai/sandbox-runtime": "^0.0.76"
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
import { isGroupTask, type TaskNode, type TaskOutcome } from '../graph/index.js'
|
|
29
29
|
import { maskedCommand } from '../util/index.js'
|
|
30
30
|
import { paint, type ColorSupport } from './colors.js'
|
|
31
|
-
import { formatDuration } from './summary.js'
|
|
31
|
+
import { formatDuration, neverStarted } from './summary.js'
|
|
32
32
|
import { outcomeLabel, skippedReason } from './events.js'
|
|
33
33
|
|
|
34
34
|
const NO_COLOR: ColorSupport = { enabled: false }
|
|
@@ -54,6 +54,7 @@ const TASK = '#f472b6' // pink-400 — task part of an id
|
|
|
54
54
|
const SUCCESS = '#22c55e' // green-500 — success / fresh
|
|
55
55
|
const WARN = '#eab308' // yellow-500 — skipped
|
|
56
56
|
const ERROR = '#ef4444' // red-500 — failed
|
|
57
|
+
const ABORTED = '#f97316' // orange-500 — killed by a shutdown signal
|
|
57
58
|
const LOCAL = '#38bdf8' // sky-400 — local cache hit
|
|
58
59
|
const REMOTE = '#2563eb' // blue-600 — remote cache hit
|
|
59
60
|
|
|
@@ -210,13 +211,13 @@ function paintTaskId(node: TaskNode, colors: ColorSupport, opts: { bold?: boolea
|
|
|
210
211
|
|
|
211
212
|
// ── Reported-line grid: glyph · time · status · cache · name ────────
|
|
212
213
|
// glyph SHAPE encodes the cache axis (⏺ miss · ► fresh · ⇢ local · ⇣
|
|
213
|
-
// remote; ◼ failed · ⊘ skipped), glyph COLOR encodes the
|
|
214
|
+
// remote; ◼ failed · ⊘ skipped · ✗ aborted · ◌ not run), glyph COLOR encodes the
|
|
214
215
|
// task axis (green/red/yellow/cyan). The status and cache WORDS spell
|
|
215
216
|
// the two axes out, each in its own color. Time is right-aligned in a
|
|
216
217
|
// fixed cell so durations line up and a ticking elapsed never shifts
|
|
217
218
|
// the row. All detail (exit code, output) lives in the framed block.
|
|
218
219
|
export const TIME_COL = 7 // "0ms" … "999.99s"
|
|
219
|
-
const STATUS_COL = 7 // "success" / "running" / "skipped"
|
|
220
|
+
const STATUS_COL = 7 // "success" / "running" / "skipped" / "aborted" / "not run"
|
|
220
221
|
const CACHE_COL = 8 // "no-cache" / "remote" / "local" / "fresh" / "miss"
|
|
221
222
|
|
|
222
223
|
/** Right-align the duration in a TIME_COL cell (pad on the left). */
|
|
@@ -381,6 +382,41 @@ export function formatTaskSkippedLine(
|
|
|
381
382
|
)
|
|
382
383
|
}
|
|
383
384
|
|
|
385
|
+
/**
|
|
386
|
+
* One row for a task a shutdown signal killed (`aborted`, its time so far)
|
|
387
|
+
* or one the stop reached before it ran (`not run`, no time). Neither
|
|
388
|
+
* reached the cache, so the cache cell is blank, as a skip's is.
|
|
389
|
+
*/
|
|
390
|
+
export function formatTaskAbortedLine(
|
|
391
|
+
node: TaskNode,
|
|
392
|
+
o: TaskOutcome,
|
|
393
|
+
colors: ColorSupport = NO_COLOR,
|
|
394
|
+
): string {
|
|
395
|
+
const id = paintTaskId(node, colors)
|
|
396
|
+
if (neverStarted(o)) {
|
|
397
|
+
return formatTaskRow(
|
|
398
|
+
paint('', '\u25cc', colors, { dim: true }),
|
|
399
|
+
null,
|
|
400
|
+
'not run',
|
|
401
|
+
'',
|
|
402
|
+
'',
|
|
403
|
+
'',
|
|
404
|
+
id,
|
|
405
|
+
colors,
|
|
406
|
+
)
|
|
407
|
+
}
|
|
408
|
+
return formatTaskRow(
|
|
409
|
+
paint(ABORTED, '\u2717', colors),
|
|
410
|
+
o.durationMs,
|
|
411
|
+
'aborted',
|
|
412
|
+
ABORTED,
|
|
413
|
+
'',
|
|
414
|
+
'',
|
|
415
|
+
id,
|
|
416
|
+
colors,
|
|
417
|
+
)
|
|
418
|
+
}
|
|
419
|
+
|
|
384
420
|
/**
|
|
385
421
|
* Live-frame brackets for focused mode: the requested task's output
|
|
386
422
|
* streams raw between an opening line (id + command) and a closing
|
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
formatFrameOpen,
|
|
6
6
|
formatKeptLines,
|
|
7
7
|
formatPersistentTailBlock,
|
|
8
|
+
formatTaskAbortedLine,
|
|
8
9
|
formatTaskBlock,
|
|
9
10
|
formatTaskExecutedLine,
|
|
10
11
|
formatTaskHitLine,
|
|
@@ -17,7 +18,7 @@ import {
|
|
|
17
18
|
type StatusStream,
|
|
18
19
|
type WorkerSlot,
|
|
19
20
|
} from './status-line.js'
|
|
20
|
-
import { formatDuration, formatSummarySection, type RunContext } from './summary.js'
|
|
21
|
+
import { formatDuration, formatSummarySection, neverStarted, type RunContext } from './summary.js'
|
|
21
22
|
import { isGroupTask } from '../graph/index.js'
|
|
22
23
|
import { appendTail, createTail, resetTail, tailText, type Tail } from '../util/index.js'
|
|
23
24
|
import { isCacheHit } from './telemetry.js'
|
|
@@ -583,9 +584,9 @@ export function defaultLogger(
|
|
|
583
584
|
// The pre-ready window closed either way: a failed task is over, and
|
|
584
585
|
// a ready one starts a fresh post-ready tail below.
|
|
585
586
|
persistentTails.delete(node.id)
|
|
586
|
-
// An aborted task (
|
|
587
|
-
//
|
|
588
|
-
//
|
|
587
|
+
// An aborted task (killed by a shutdown signal, or reached by the stop
|
|
588
|
+
// before it ran) frees its worker slot but is never counted live: the
|
|
589
|
+
// run is tearing down and it has no honest outcome.
|
|
589
590
|
if (outcome.status === 'aborted') {
|
|
590
591
|
const si = slots.findIndex((s) => s !== null && s.id === node.id)
|
|
591
592
|
if (si >= 0) slots[si] = slotQueue.shift() ?? null
|
|
@@ -594,24 +595,32 @@ export function defaultLogger(
|
|
|
594
595
|
if (qi >= 0) slotQueue.splice(qi, 1)
|
|
595
596
|
}
|
|
596
597
|
refresh(true)
|
|
598
|
+
if (isGroupTask(node)) return
|
|
597
599
|
// What it printed while it stopped (a trap's cleanup) still reaches
|
|
598
600
|
// the terminal where a frame would have: a task that traps Ctrl-C
|
|
599
601
|
// and exits 0 is aborted since item 962, and its frame was dropped
|
|
600
|
-
// with the count.
|
|
602
|
+
// with the count. A task that never started opened no frame.
|
|
601
603
|
const framed =
|
|
602
|
-
|
|
603
|
-
(view.mode === '
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
}
|
|
611
|
-
emitFrameClose(formatFrameClose(node, outcome, colors))
|
|
612
|
-
} else if (stdout.length > 0 || stderr.length > 0) {
|
|
613
|
-
emitBlock(formatTaskBlock(node, outcome, { stdout, stderr, ...dropped }, colors, true))
|
|
604
|
+
!neverStarted(outcome) &&
|
|
605
|
+
(view.mode === 'full' ||
|
|
606
|
+
(view.mode === 'focused' && isPrimary(node)) ||
|
|
607
|
+
(framesTerminal && holdsTerminal(node)))
|
|
608
|
+
if (framed && streamsLive(node)) {
|
|
609
|
+
if (streamMidLine) {
|
|
610
|
+
writer.write('\n')
|
|
611
|
+
streamMidLine = false
|
|
614
612
|
}
|
|
613
|
+
emitFrameClose(formatFrameClose(node, outcome, colors))
|
|
614
|
+
return
|
|
615
|
+
}
|
|
616
|
+
if (framed && (stdout.length > 0 || stderr.length > 0)) {
|
|
617
|
+
emitBlock(formatTaskBlock(node, outcome, { stdout, stderr, ...dropped }, colors, true))
|
|
618
|
+
return
|
|
619
|
+
}
|
|
620
|
+
// Otherwise a row in the task list, in every view that lists a
|
|
621
|
+
// failure: the run exits red, and the row names why.
|
|
622
|
+
if (view.mode !== 'none' && view.mode !== 'hash-only') {
|
|
623
|
+
emitLine(formatTaskAbortedLine(node, outcome, colors))
|
|
615
624
|
}
|
|
616
625
|
return
|
|
617
626
|
}
|
package/src/orchestrator/run.ts
CHANGED
|
@@ -90,12 +90,7 @@ import { assembleRunRecords } from './run-records.js'
|
|
|
90
90
|
import { hasEnded, selectKeepAlive, shutdownPersistent } from './persistent.js'
|
|
91
91
|
import { writeRunProfile, writeRunSummary } from './run-artifacts.js'
|
|
92
92
|
import { createSaveLane } from './save-lane.js'
|
|
93
|
-
import {
|
|
94
|
-
formatAbortedSection,
|
|
95
|
-
formatFlakySection,
|
|
96
|
-
formatRunSummary,
|
|
97
|
-
formatSkippedSection,
|
|
98
|
-
} from './summary.js'
|
|
93
|
+
import { formatFlakySection, formatRunSummary, formatSkippedSection } from './summary.js'
|
|
99
94
|
import { detectFlaky, type FlakyCandidate } from './failure-mode.js'
|
|
100
95
|
import type { RunOptions, RunSummary } from './options.js'
|
|
101
96
|
|
|
@@ -1063,10 +1058,7 @@ async function runOnBus(
|
|
|
1063
1058
|
for (const line of formatPersistentList(stillUp, colors)) log.status(line)
|
|
1064
1059
|
}
|
|
1065
1060
|
for (const line of formatRunSummary(list, totalMs, colors, runContext)) log.status(line)
|
|
1066
|
-
// A
|
|
1067
|
-
// `ok` false — name it, or the red exit is undiagnosable.
|
|
1068
|
-
for (const line of formatAbortedSection(list)) log.status(line)
|
|
1069
|
-
// And a dependency-only server that died before the end of the graph
|
|
1061
|
+
// A dependency-only server that died before the end of the graph
|
|
1070
1062
|
// stopped it: the footer counts it failed, this says why.
|
|
1071
1063
|
// A signal death is named as its exit code, as the keep-alive wait
|
|
1072
1064
|
// names it: `code SIGTERM` read beside `code 143` for one event (item
|
|
@@ -19,6 +19,7 @@ const WARN = '#eab308'
|
|
|
19
19
|
const LOCAL = '#38bdf8' // sky-400
|
|
20
20
|
const REMOTE = '#2563eb' // blue-600
|
|
21
21
|
const ERROR = '#ef4444'
|
|
22
|
+
const ABORTED = '#f97316' // orange-500: red's neighbour, not a failure
|
|
22
23
|
|
|
23
24
|
const BAR_WIDTH = 50
|
|
24
25
|
// Brand gradient for the rule: identity violet → pink (the project /
|
|
@@ -99,6 +100,10 @@ export interface SummaryStats {
|
|
|
99
100
|
/** Ended OK — executed successes AND cache hits. */
|
|
100
101
|
successful: number
|
|
101
102
|
skipped: number
|
|
103
|
+
/** Killed by a shutdown signal (final summary only). Not in `total`. */
|
|
104
|
+
aborted?: number
|
|
105
|
+
/** Reached by the stop before they ran (final summary only). Not in `total`. */
|
|
106
|
+
notRun?: number
|
|
102
107
|
total: number
|
|
103
108
|
upToDate: number
|
|
104
109
|
restoredLocal: number
|
|
@@ -143,6 +148,8 @@ export function formatSummarySection(
|
|
|
143
148
|
const miss = stats.miss
|
|
144
149
|
const noCache = stats.noCache ?? 0
|
|
145
150
|
const left = stats.left ?? 0
|
|
151
|
+
const aborted = stats.aborted ?? 0
|
|
152
|
+
const notRun = stats.notRun ?? 0
|
|
146
153
|
const remainder = { n: left, color: '', glyph: '\u25b1', dim: true }
|
|
147
154
|
|
|
148
155
|
const dim = (txt: string) => paint('', txt, colors, { dim: true })
|
|
@@ -157,6 +164,12 @@ export function formatSummarySection(
|
|
|
157
164
|
if (stats.successful > 0) taskParts.push(paint(SUCCESS, `${stats.successful} success`, colors))
|
|
158
165
|
if (stats.skipped > 0) taskParts.push(paint(WARN, `${stats.skipped} skipped`, colors))
|
|
159
166
|
if (stats.total > 0) taskParts.push(dim(`${stats.total} total`))
|
|
167
|
+
// Rows in the task list, but outside `total`: history and telemetry count
|
|
168
|
+
// the same tasks the total does, and an aborted task did no work.
|
|
169
|
+
const uncounted: string[] = []
|
|
170
|
+
if (aborted > 0) uncounted.push(paint(ABORTED, `${aborted} aborted`, colors))
|
|
171
|
+
if (notRun > 0) uncounted.push(dim(`${notRun} not run`))
|
|
172
|
+
if (uncounted.length > 0) taskParts.push(`${dim('not counted:')} ${uncounted.join(dim(', '))}`)
|
|
160
173
|
const taskBar = segmentBar(
|
|
161
174
|
[
|
|
162
175
|
{ n: stats.failed, color: ERROR },
|
|
@@ -201,6 +214,8 @@ export function formatSummarySection(
|
|
|
201
214
|
if (taskParts.length > 0) lines.push(legend(taskParts))
|
|
202
215
|
} else {
|
|
203
216
|
lines.push(row('tasks', dim('0 tasks')))
|
|
217
|
+
// A stop before any task finished still names what it took down.
|
|
218
|
+
if (taskParts.length > 0) lines.push(legend(taskParts))
|
|
204
219
|
}
|
|
205
220
|
|
|
206
221
|
// Cache meter + numbers, fixed order: miss · up-to-date · local ·
|
|
@@ -324,11 +339,14 @@ export function formatRunSummary(
|
|
|
324
339
|
.filter((o) => (o.status === 'success' || o.status === 'failed') && !isGroupTask(o.node))
|
|
325
340
|
.map((o) => o.durationMs)
|
|
326
341
|
const heldOutcomes = outcomes.filter((o) => o.admissionHeldMs !== undefined)
|
|
342
|
+
const notRun = outcomes.filter((o) => neverStarted(o) && !isGroupTask(o.node)).length
|
|
327
343
|
return formatSummarySection(
|
|
328
344
|
{
|
|
329
345
|
failed: t.failed,
|
|
330
346
|
successful: t.successful,
|
|
331
347
|
skipped: t.skipped,
|
|
348
|
+
aborted: t.aborted - notRun,
|
|
349
|
+
notRun,
|
|
332
350
|
total: t.total,
|
|
333
351
|
upToDate: t.upToDate,
|
|
334
352
|
restoredLocal: t.restoredLocal,
|
|
@@ -360,35 +378,12 @@ export function formatRunSummary(
|
|
|
360
378
|
}
|
|
361
379
|
|
|
362
380
|
/**
|
|
363
|
-
*
|
|
364
|
-
*
|
|
365
|
-
*
|
|
366
|
-
* without this the user reads a red exit over a fully green summary that
|
|
367
|
-
* names nothing. Empty when nothing aborted.
|
|
381
|
+
* An `aborted` outcome the stop reached before dispatch. The scheduler gives
|
|
382
|
+
* it an invented exit 1; only a started task carries a wall-clock start, so
|
|
383
|
+
* that is what tells "never ran" from "killed" (item 1062).
|
|
368
384
|
*/
|
|
369
|
-
export function
|
|
370
|
-
|
|
371
|
-
// with an invented exit 1: listed as killed, a task that never ran read as
|
|
372
|
-
// one the signal cut short (item 1062). Only a started one carries a
|
|
373
|
-
// wall-clock start. A group is no task here, as in the skipped section.
|
|
374
|
-
const aborted = outcomes.filter((o) => o.status === 'aborted' && !isGroupTask(o.node))
|
|
375
|
-
const killed = aborted.filter((o) => o.wallclockStartNs !== undefined)
|
|
376
|
-
const unstarted = aborted.filter((o) => o.wallclockStartNs === undefined)
|
|
377
|
-
const tasks = (n: number): string => `${n} task${n === 1 ? '' : 's'}`
|
|
378
|
-
const lines: string[] = []
|
|
379
|
-
if (killed.length > 0) {
|
|
380
|
-
lines.push(
|
|
381
|
-
'',
|
|
382
|
-
` Aborted: ${tasks(killed.length)} killed by a shutdown signal — not counted above`,
|
|
383
|
-
)
|
|
384
|
-
for (const o of killed) lines.push(` ✗ ${o.node.id} — exit ${o.exitCode}, nothing cached`)
|
|
385
|
-
}
|
|
386
|
-
if (unstarted.length > 0) {
|
|
387
|
-
const they = unstarted.length === 1 ? 'it' : 'they'
|
|
388
|
-
lines.push('', ` Not started: ${tasks(unstarted.length)} the run stopped before ${they} ran`)
|
|
389
|
-
for (const o of unstarted) lines.push(` · ${o.node.id}`)
|
|
390
|
-
}
|
|
391
|
-
return lines
|
|
385
|
+
export function neverStarted(o: TaskOutcome): boolean {
|
|
386
|
+
return o.status === 'aborted' && o.wallclockStartNs === undefined
|
|
392
387
|
}
|
|
393
388
|
|
|
394
389
|
/**
|