@vzn/vx 0.0.645 → 0.0.648
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 +39 -50
- package/launcher.cjs +9 -2
- package/launcher.sh +2 -2
- package/package.json +5 -5
- package/schemas/cache.json +5 -5
- package/schemas/error.json +1 -1
- package/schemas/info.json +16 -2
- package/schemas/last.json +127 -5
- package/schemas/plan.json +28 -2
- package/schemas/show.json +10 -1
- package/skills/vx/SKILL.md +19 -3
- package/src/bin.ts +54 -13
- package/src/cache/archive.ts +324 -23
- package/src/cache/cache.ts +330 -65
- package/src/cache/git-inputs.ts +274 -97
- package/src/cache/index.ts +7 -2
- package/src/cache/inputs.ts +515 -83
- package/src/cache/key-fold.ts +15 -6
- package/src/cache/layer.ts +74 -11
- package/src/cache/layered-cache.ts +154 -59
- package/src/cache/linked-inputs.ts +1 -1
- package/src/cache/output-index.ts +44 -14
- package/src/cache/plugin-cache.ts +49 -23
- package/src/cache/remote-hits.ts +72 -0
- package/src/cache/remote-lane.ts +100 -15
- package/src/cache/run-history.ts +77 -23
- package/src/cache/run-store.ts +11 -10
- package/src/cache/schema.ts +42 -6
- package/src/cache/tar-stream.ts +5 -0
- package/src/cache/umask.ts +1 -2
- package/src/cli/cache.ts +58 -27
- package/src/cli/completions.ts +12 -5
- package/src/cli/core-alias.ts +5 -2
- package/src/cli/docs.ts +42 -16
- package/src/cli/help.ts +62 -25
- package/src/cli/index.ts +79 -45
- package/src/cli/info.ts +12 -6
- package/src/cli/init.ts +222 -34
- package/src/cli/last.ts +443 -47
- package/src/cli/lock.ts +197 -52
- package/src/cli/plan-format.ts +91 -49
- package/src/cli/plugin-commands.ts +160 -11
- package/src/cli/plugin-templates.ts +13 -13
- package/src/cli/registry.ts +209 -0
- package/src/cli/run.ts +111 -41
- package/src/cli/select.ts +148 -49
- package/src/cli/show.ts +59 -16
- package/src/cli/task-verb.ts +16 -6
- package/src/cli/upgrade.ts +155 -54
- package/src/cli/watch-cycle.ts +10 -5
- package/src/cli/watch-filter.ts +71 -3
- package/src/cli/watch-fs.ts +22 -6
- package/src/cli/watch-judge.ts +48 -6
- package/src/cli/watch.ts +163 -42
- package/src/cli/why.ts +211 -73
- package/src/cli/workspace-config.ts +54 -15
- package/src/config.ts +13 -1
- package/src/exec/env.ts +7 -2
- package/src/exec/executor.ts +262 -15
- package/src/exec/index.ts +8 -2
- package/src/exec/kill-tree.ts +15 -0
- package/src/exec/local-executor.ts +1 -1
- package/src/exec/proc-sample.ts +70 -6
- package/src/exec/runner.ts +256 -53
- package/src/exec/sandbox-binds.ts +7 -3
- package/src/exec/sandbox-paths.ts +1 -1
- package/src/exec/sandbox-runtime.ts +222 -28
- package/src/exec/sandbox-violations.ts +52 -20
- package/src/graph/critical-path.ts +79 -0
- package/src/graph/default-build.ts +19 -0
- package/src/graph/definition.ts +38 -0
- package/src/graph/folds.ts +38 -5
- package/src/graph/index.ts +4 -1
- package/src/graph/priorities.ts +74 -8
- package/src/graph/scheduler.ts +326 -65
- package/src/graph/task-graph.ts +228 -35
- package/src/handoff.ts +97 -9
- package/src/history/failure-mode.ts +6 -3
- package/src/history/history.ts +268 -50
- package/src/history/index.ts +2 -0
- package/src/history/metrics.ts +142 -45
- package/src/index.ts +42 -10
- package/src/orchestrator/admission.ts +10 -6
- package/src/orchestrator/affected-tasks.ts +11 -6
- package/src/orchestrator/deferred-outputs.ts +2 -0
- package/src/orchestrator/doctor.ts +134 -42
- package/src/orchestrator/execute-args.ts +6 -0
- package/src/orchestrator/execute-task.ts +155 -42
- package/src/orchestrator/fingerprint-watch.ts +1 -1
- package/src/orchestrator/graph-memo.ts +140 -0
- package/src/orchestrator/graph-stage.ts +18 -1
- package/src/orchestrator/held-servers.ts +86 -0
- package/src/orchestrator/hit-restore.ts +227 -122
- package/src/orchestrator/index.ts +20 -3
- package/src/orchestrator/local-floor.ts +198 -0
- package/src/orchestrator/local-shortcircuit.ts +51 -8
- package/src/orchestrator/lockfile-claim.ts +96 -16
- package/src/orchestrator/miss-save.ts +52 -12
- package/src/orchestrator/options.ts +8 -6
- package/src/orchestrator/output-host.ts +43 -14
- package/src/orchestrator/output-log.ts +90 -10
- package/src/orchestrator/persistent-task.ts +97 -28
- package/src/orchestrator/persistent.ts +16 -4
- package/src/orchestrator/placement.ts +257 -14
- package/src/orchestrator/plan.ts +64 -7
- package/src/orchestrator/plugin-host.ts +230 -40
- package/src/orchestrator/plugin.ts +150 -37
- package/src/orchestrator/prepare.ts +178 -32
- package/src/orchestrator/projects.ts +65 -40
- package/src/orchestrator/remote-prefetch.ts +119 -25
- package/src/orchestrator/run-artifacts.ts +95 -32
- package/src/orchestrator/run-context.ts +39 -41
- package/src/orchestrator/run-failures.ts +65 -23
- package/src/orchestrator/run-id.ts +6 -3
- package/src/orchestrator/run-keys.ts +300 -0
- package/src/orchestrator/run-live.ts +294 -0
- package/src/orchestrator/run-lock.ts +13 -7
- package/src/orchestrator/run-records.ts +35 -23
- package/src/orchestrator/run-report.ts +3 -3
- package/src/orchestrator/run-steps.ts +58 -22
- package/src/orchestrator/run-stop.ts +22 -5
- package/src/orchestrator/run.ts +409 -79
- package/src/orchestrator/sandbox-fix.ts +7 -5
- package/src/orchestrator/sandbox-request.ts +78 -22
- package/src/orchestrator/shell-verdict.ts +5 -5
- package/src/orchestrator/signals.ts +26 -1
- package/src/orchestrator/stable-keys.ts +92 -8
- package/src/orchestrator/steps.ts +2 -2
- package/src/orchestrator/task-artifacts.ts +206 -0
- package/src/orchestrator/task-hash.ts +89 -14
- package/src/orchestrator/task-log-buffer.ts +72 -36
- package/src/orchestrator/task-process.ts +22 -0
- package/src/orchestrator/telemetry-host.ts +24 -3
- package/src/orchestrator/telemetry.ts +113 -38
- package/src/orchestrator/upstream.ts +15 -0
- package/src/report/events.ts +32 -13
- package/src/report/forecast.ts +36 -35
- package/src/report/framed-output.ts +173 -46
- package/src/report/index.ts +1 -0
- package/src/report/logger.ts +217 -186
- package/src/report/path-links.ts +12 -4
- package/src/report/plain-output.ts +13 -5
- package/src/report/status-line.ts +94 -46
- package/src/report/summary.ts +174 -38
- package/src/report/terminal-signals.ts +13 -2
- package/src/settings.ts +14 -6
- package/src/util/bun-version.ts +4 -1
- package/src/util/colors.ts +20 -3
- package/src/util/config-frame.ts +41 -4
- package/src/{graph → util}/dependency-spec.ts +22 -13
- package/src/util/edit-distance.ts +13 -1
- package/src/util/env-flag.ts +9 -0
- package/src/util/errors.ts +59 -3
- package/src/util/essential-env.ts +57 -0
- package/src/util/git-ignored.ts +1 -1
- package/src/util/hash.ts +35 -0
- package/src/util/hide-env.ts +45 -5
- package/src/util/index.ts +22 -5
- package/src/util/num.ts +8 -0
- package/src/util/paths.ts +121 -21
- package/src/util/report.ts +6 -1
- package/src/util/secret-mask.ts +62 -1
- package/src/util/secrets.ts +7 -2
- package/src/util/size.ts +11 -3
- package/src/util/tail.ts +6 -1
- package/src/util/timing.ts +13 -1
- package/src/util/ui.ts +65 -4
- package/src/util/which.ts +1 -1
- package/src/util/write-file.ts +67 -0
- package/src/workspace/affected.ts +710 -107
- package/src/workspace/bracket-literal.ts +84 -0
- package/src/workspace/canonical-dir.ts +37 -0
- package/src/workspace/config-bytecode.ts +282 -0
- package/src/workspace/config-eval.ts +29 -8
- package/src/workspace/config-imports.ts +199 -58
- package/src/workspace/config-schema.ts +242 -39
- package/src/workspace/filter.ts +37 -8
- package/src/workspace/index.ts +11 -2
- package/src/workspace/json-data.ts +3 -4
- package/src/workspace/lockfile.ts +57 -8
- package/src/workspace/migration.ts +72 -49
- package/src/workspace/package-graph.ts +5 -4
- package/src/workspace/project-loader.ts +314 -47
- package/src/workspace/repo-id.ts +1 -1
- package/src/workspace/workspace.ts +176 -33
- package/src/cli/foreign-flags.ts +0 -727
package/src/report/logger.ts
CHANGED
|
@@ -5,12 +5,18 @@ import {
|
|
|
5
5
|
formatFrameOpen,
|
|
6
6
|
formatKeptLines,
|
|
7
7
|
formatPersistentTailBlock,
|
|
8
|
+
formatListHeader,
|
|
8
9
|
formatTaskAbortedLine,
|
|
9
10
|
formatTaskBlock,
|
|
10
11
|
formatTaskExecutedLine,
|
|
11
12
|
flakyNote,
|
|
13
|
+
pathNote,
|
|
14
|
+
whereNote,
|
|
15
|
+
formatTaskHashLine,
|
|
12
16
|
formatTaskHitLine,
|
|
13
17
|
formatTaskSkippedLine,
|
|
18
|
+
leavesStyle,
|
|
19
|
+
RESET,
|
|
14
20
|
type OutputChunk,
|
|
15
21
|
} from './framed-output.js'
|
|
16
22
|
import {
|
|
@@ -26,8 +32,8 @@ import {
|
|
|
26
32
|
formatSummarySection,
|
|
27
33
|
neverStarted,
|
|
28
34
|
ranUnsaved,
|
|
29
|
-
type CacheBucket,
|
|
30
35
|
type RunContext,
|
|
36
|
+
type TaskBucket,
|
|
31
37
|
} from './summary.js'
|
|
32
38
|
import { isGroupTask } from '../graph/index.js'
|
|
33
39
|
import {
|
|
@@ -38,7 +44,9 @@ import {
|
|
|
38
44
|
tailText,
|
|
39
45
|
type Tail,
|
|
40
46
|
detectColors,
|
|
47
|
+
envFlagOn,
|
|
41
48
|
type ColorSupport,
|
|
49
|
+
ui,
|
|
42
50
|
} from '../util/index.js'
|
|
43
51
|
import { isCacheHit } from '../graph/index.js'
|
|
44
52
|
import { failedLabel, outcomeWord, ranNoCache } from './events.js'
|
|
@@ -164,11 +172,6 @@ export interface OutputView {
|
|
|
164
172
|
ci?: boolean
|
|
165
173
|
}
|
|
166
174
|
|
|
167
|
-
/** CI=0 / CI=false count as "not CI" — several vendors use CI=true. */
|
|
168
|
-
function truthyEnv(v: string | undefined): boolean {
|
|
169
|
-
return v !== undefined && v !== '' && v !== '0' && v !== 'false'
|
|
170
|
-
}
|
|
171
|
-
|
|
172
175
|
// ── GitHub Actions workflow-command escaping ────────────────────────
|
|
173
176
|
// The runner parses `::`-prefixed LINES out of the log, so anything
|
|
174
177
|
// interpolated into one has to be encoded or it changes the command's shape.
|
|
@@ -211,8 +214,8 @@ export function resolveOutputView(
|
|
|
211
214
|
},
|
|
212
215
|
env: Record<string, string | undefined> = process.env,
|
|
213
216
|
): OutputView {
|
|
214
|
-
const ci =
|
|
215
|
-
const gha =
|
|
217
|
+
const ci = envFlagOn(env['CI'])
|
|
218
|
+
const gha = envFlagOn(env['GITHUB_ACTIONS'])
|
|
216
219
|
const mk = (mode: OutputView['mode']): OutputView => ({
|
|
217
220
|
mode,
|
|
218
221
|
...(gha ? { gha: true } : {}),
|
|
@@ -231,7 +234,7 @@ export function resolveOutputView(
|
|
|
231
234
|
const FORECAST_AFTER_MS = 1000
|
|
232
235
|
/** A run shorter than this ends without a desktop notification. */
|
|
233
236
|
const NOTIFY_AFTER_MS = 10_000
|
|
234
|
-
// A step of vx's own quicker than this shows nowhere (
|
|
237
|
+
// A step of vx's own quicker than this shows nowhere (2026-10-15).
|
|
235
238
|
const STEP_SHOWN_MS = 150
|
|
236
239
|
|
|
237
240
|
export function defaultLogger(
|
|
@@ -344,14 +347,15 @@ export function defaultLogger(
|
|
|
344
347
|
let forecastFn: Forecast | undefined
|
|
345
348
|
let graphForecast: (() => Forecast) | undefined
|
|
346
349
|
let forecastTimer: ReturnType<typeof setTimeout> | null = null
|
|
347
|
-
|
|
350
|
+
// When the forecast says the run ends; past it, the region counts up.
|
|
351
|
+
let deadlineMs: number | undefined
|
|
348
352
|
let critical: string | undefined
|
|
349
|
-
|
|
353
|
+
// Tasks the region showed on the critical path: their finished rows say so too.
|
|
354
|
+
const onPath = new Set<string>()
|
|
355
|
+
let blockedCount = 0
|
|
350
356
|
let expected: ReadonlyMap<string, number> | undefined
|
|
351
357
|
let blocked: readonly BlockedTask[] | undefined
|
|
352
|
-
|
|
353
|
-
// task until it starts, in that row, or leaves the list, so nothing hops.
|
|
354
|
-
const heldRows: (string | null)[] = []
|
|
358
|
+
let blockedBy: ReadonlyMap<string, number> | undefined
|
|
355
359
|
let etaAtMs = 0
|
|
356
360
|
const signals = writer.enabled
|
|
357
361
|
? terminalSignals(opts.env ?? process.env)
|
|
@@ -360,18 +364,11 @@ export function defaultLogger(
|
|
|
360
364
|
let progressShown = -1
|
|
361
365
|
let progressRed = false
|
|
362
366
|
|
|
363
|
-
// Every interactive view renders the fixed-height
|
|
364
|
-
//
|
|
365
|
-
//
|
|
366
|
-
//
|
|
367
|
-
// 10 rows; excess running tasks queue for a freed slot and surface
|
|
368
|
-
// as `+k more` on the stats line. Focused flow keeps its lifecycle:
|
|
369
|
-
// the region dies the moment a requested node starts streaming.
|
|
367
|
+
// Every interactive view renders the fixed-height running list: one
|
|
368
|
+
// row per worker, sized at runStart from concurrency, so its height
|
|
369
|
+
// never changes; a task keeps its slot for its whole life. Excess
|
|
370
|
+
// running tasks queue for a freed slot, each shown after the slots.
|
|
370
371
|
let slots: (WorkerSlot | null)[] = []
|
|
371
|
-
// Rows shown: the most workers busy at once so far. An idle row past it
|
|
372
|
-
// was noise (a 10-core machine drew 9 on a one-chain run); it never
|
|
373
|
-
// shrinks, so a row never jumps.
|
|
374
|
-
let shownSlots = 0
|
|
375
372
|
const slotQueue: WorkerSlot[] = []
|
|
376
373
|
let succeeded = 0
|
|
377
374
|
// Executed with no `cache` block: never a miss, counted apart.
|
|
@@ -383,12 +380,12 @@ export function defaultLogger(
|
|
|
383
380
|
let notSaved = 0
|
|
384
381
|
let skippedCount = 0
|
|
385
382
|
// Each finished task's cache bucket by its key: its remote traffic moves it.
|
|
386
|
-
const buckets = new Map<string,
|
|
383
|
+
const buckets = new Map<string, TaskBucket>()
|
|
387
384
|
// The graph is done and vx's own closing steps run: the region stays,
|
|
388
|
-
// footer and step rows, until the footer replaces it (
|
|
385
|
+
// footer and step rows, until the footer replaces it (2026-10-15).
|
|
389
386
|
let tail = false
|
|
390
387
|
// The run was stopped: the region stays, its running rows say
|
|
391
|
-
// `aborting`, and nothing is forecast or held (
|
|
388
|
+
// `aborting`, and nothing is forecast or held (2026-10-17).
|
|
392
389
|
let stopping = false
|
|
393
390
|
// Counted live once the run is stopping, as the footer counts them.
|
|
394
391
|
let stopped = 0
|
|
@@ -410,7 +407,8 @@ export function defaultLogger(
|
|
|
410
407
|
// deferred to runEnd (owner: '✗ line, continue, all full frames at
|
|
411
408
|
// the end'). 'full'/CI keeps frames inline.
|
|
412
409
|
const deferredFailures: string[] = []
|
|
413
|
-
|
|
410
|
+
// Each ready server's outcome: one whose child has exited (`serverExit`) is no longer pinned.
|
|
411
|
+
const pinnedPersistent: TaskOutcome[] = []
|
|
414
412
|
let flushedFailures = false
|
|
415
413
|
// A persistent task (dev server, watcher, daemon) is the one task kind
|
|
416
414
|
// nothing bounds: a one-shot command's output ends when it exits, but a
|
|
@@ -467,21 +465,44 @@ export function defaultLogger(
|
|
|
467
465
|
}
|
|
468
466
|
const printsKept = (tail: { outcome?: TaskOutcome }): boolean =>
|
|
469
467
|
flushedPersistent && tail.outcome !== undefined && view.mode !== 'errors-only'
|
|
468
|
+
// A server writing before this run started its task is one an earlier run
|
|
469
|
+
// left running (`vx watch` keeps its dev server): it streams as a kept
|
|
470
|
+
// one, live above the rows. Buffered as a task yet to run, it was never shown.
|
|
471
|
+
const heldOver = (node: TaskNode): boolean =>
|
|
472
|
+
node.config.exec?.persistent !== undefined && view.mode !== 'errors-only'
|
|
470
473
|
|
|
474
|
+
// A forced refresh the region has not been built for yet: the build
|
|
475
|
+
// takes the forecast pass the event asked for.
|
|
476
|
+
let forcePending = false
|
|
477
|
+
// The region is built when the writer draws it (`setRegion`'s function
|
|
478
|
+
// form), from the state as it is then: the events a forced-redraw floor
|
|
479
|
+
// coalesces build nothing.
|
|
471
480
|
const refresh = (forced: boolean): void => {
|
|
472
481
|
if (statusDead) return
|
|
473
|
-
|
|
482
|
+
const first = forecastFn === undefined && graphForecast !== undefined && !tail && !stopping
|
|
483
|
+
forcePending ||= forced || first
|
|
484
|
+
writer.setRegion(buildRegion, { force: forced || first })
|
|
485
|
+
}
|
|
486
|
+
const buildRegion = (): readonly string[] => {
|
|
487
|
+
let force = forcePending
|
|
488
|
+
forcePending = false
|
|
474
489
|
const now = Date.now()
|
|
475
|
-
// A
|
|
476
|
-
//
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
490
|
+
// A task its executor still holds has not begun: its clock starts at
|
|
491
|
+
// the first frame a machine runs it.
|
|
492
|
+
const where = runContext?.whereRunning
|
|
493
|
+
if (where !== undefined)
|
|
494
|
+
for (const x of [...slots, ...slotQueue]) {
|
|
495
|
+
if (x === null) continue
|
|
496
|
+
x.waiting = where(x.id) === null
|
|
497
|
+
if (x.waiting) x.startedMs = now
|
|
498
|
+
}
|
|
499
|
+
// The rows' blocked counts and planned tasks from the first frame: the
|
|
500
|
+
// graph knows, without history.
|
|
501
|
+
if (forecastFn === undefined && graphForecast !== undefined && !tail && !stopping) {
|
|
502
|
+
forecastFn = graphForecast()
|
|
503
|
+
graphForecast = undefined
|
|
504
|
+
force = true
|
|
505
|
+
}
|
|
485
506
|
// Twice a second at most: one pass over the unfinished graph.
|
|
486
507
|
if (forecastFn !== undefined && !tail && !stopping && (force || now - etaAtMs >= 500)) {
|
|
487
508
|
etaAtMs = now
|
|
@@ -489,16 +510,23 @@ export function defaultLogger(
|
|
|
489
510
|
for (const s of slots) if (s !== null) running.set(s.id, s.startedMs)
|
|
490
511
|
for (const s of slotQueue) running.set(s.id, s.startedMs)
|
|
491
512
|
const f = forecastFn(finished, running, now)
|
|
492
|
-
|
|
513
|
+
deadlineMs =
|
|
514
|
+
f.etaMs === undefined
|
|
515
|
+
? undefined
|
|
516
|
+
: f.etaMs > 0
|
|
517
|
+
? now + f.etaMs
|
|
518
|
+
: Math.min(deadlineMs ?? now, now)
|
|
493
519
|
critical = f.critical
|
|
494
|
-
|
|
520
|
+
blockedCount = f.waiting
|
|
495
521
|
expected = f.expected
|
|
496
522
|
blocked = f.blocked
|
|
523
|
+
blockedBy = f.blockedBy
|
|
497
524
|
}
|
|
525
|
+
const etaMs = deadlineMs === undefined ? undefined : deadlineMs - now
|
|
498
526
|
if (signals.progress && total > 0) {
|
|
499
527
|
const elapsed = now - startedAtMs
|
|
500
528
|
const pct = Math.floor(
|
|
501
|
-
etaMs !== undefined &&
|
|
529
|
+
etaMs !== undefined && etaMs > 0
|
|
502
530
|
? (elapsed / (elapsed + etaMs)) * 100
|
|
503
531
|
: (done / total) * 100,
|
|
504
532
|
)
|
|
@@ -509,6 +537,16 @@ export function defaultLogger(
|
|
|
509
537
|
out.write(progressEscape(progressShown, progressRed))
|
|
510
538
|
}
|
|
511
539
|
}
|
|
540
|
+
let running = 0
|
|
541
|
+
let scheduled = 0
|
|
542
|
+
// Stopping, the rows say `aborting`: nothing is running or scheduled.
|
|
543
|
+
if (!tail && !stopping)
|
|
544
|
+
for (const x of [...slots, ...slotQueue]) {
|
|
545
|
+
if (x === null) continue
|
|
546
|
+
if (x.waiting === true) scheduled++
|
|
547
|
+
else running++
|
|
548
|
+
}
|
|
549
|
+
if (critical !== undefined && running > 0) onPath.add(critical)
|
|
512
550
|
// The live summary IS the final summary's section, built from the
|
|
513
551
|
// same formatter so the region visually becomes the printout.
|
|
514
552
|
const summaryLines =
|
|
@@ -530,7 +568,14 @@ export function defaultLogger(
|
|
|
530
568
|
miss: succeeded + failed - noCache,
|
|
531
569
|
noCache,
|
|
532
570
|
left: total - done - stopped - notRun,
|
|
533
|
-
...(
|
|
571
|
+
...(running > 0 ? { running } : {}),
|
|
572
|
+
...(scheduled > 0 ? { scheduled } : {}),
|
|
573
|
+
...(blockedCount > 0 && !tail && !stopping ? { blocked: blockedCount } : {}),
|
|
574
|
+
...(etaMs !== undefined && !tail && !stopping
|
|
575
|
+
? etaMs > 0
|
|
576
|
+
? { etaMs }
|
|
577
|
+
: { overMs: -etaMs }
|
|
578
|
+
: {}),
|
|
534
579
|
buckets,
|
|
535
580
|
spread:
|
|
536
581
|
spreadCount > 0
|
|
@@ -541,59 +586,30 @@ export function defaultLogger(
|
|
|
541
586
|
colors,
|
|
542
587
|
runContext,
|
|
543
588
|
)
|
|
544
|
-
const held = blocked === undefined || stopping ? undefined : holdRows(blocked)
|
|
545
589
|
const shownSteps: WorkerSlot[] = []
|
|
546
590
|
for (const [id, startedMs] of steps)
|
|
547
591
|
if (now - startedMs >= STEP_SHOWN_MS) shownSteps.push({ id, startedMs })
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
},
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
592
|
+
return formatStatusRegion(
|
|
593
|
+
{
|
|
594
|
+
pinnedPersistent: pinnedPersistent
|
|
595
|
+
.filter((o) => o.serverExit === undefined)
|
|
596
|
+
.map((o) => o.node.id),
|
|
597
|
+
// Every running task has a row: one beyond the slots follows them.
|
|
598
|
+
slots: [...slots, ...slotQueue],
|
|
599
|
+
size: slots.length,
|
|
600
|
+
...(blocked !== undefined && !stopping ? { next: blocked } : {}),
|
|
601
|
+
...(blockedBy !== undefined && !stopping ? { blockedBy } : {}),
|
|
602
|
+
...(stopping ? { stopping } : {}),
|
|
603
|
+
...(shownSteps.length > 0 ? { steps: shownSteps } : {}),
|
|
604
|
+
nowMs: Date.now(),
|
|
605
|
+
summaryLines,
|
|
606
|
+
...(runContext?.whereRunning !== undefined ? { where: runContext.whereRunning } : {}),
|
|
607
|
+
...(expected !== undefined ? { expected } : {}),
|
|
608
|
+
...(critical !== undefined ? { critical } : {}),
|
|
609
|
+
},
|
|
610
|
+
colors,
|
|
566
611
|
)
|
|
567
612
|
}
|
|
568
|
-
// Each idle worker row holds a blocked task, in scheduler order. A row
|
|
569
|
-
// with none left to hold leaves the region (M, 2026-10-15): blocked tasks
|
|
570
|
-
// only dwindle, so it is never needed again, and the rows below close up.
|
|
571
|
-
const holdRows = (list: readonly BlockedTask[]): (BlockedTask | null)[] => {
|
|
572
|
-
const started = new Set<string>()
|
|
573
|
-
for (const s of slots) if (s !== null) started.add(s.id)
|
|
574
|
-
for (const s of slotQueue) started.add(s.id)
|
|
575
|
-
const open = new Map<string, BlockedTask>()
|
|
576
|
-
for (const b of list) if (!started.has(b.id)) open.set(b.id, b)
|
|
577
|
-
const rows: (BlockedTask | null)[] = []
|
|
578
|
-
for (let i = 0; i < shownSlots; i++) {
|
|
579
|
-
const kept = slots[i] === null ? open.get(heldRows[i] ?? '') : undefined
|
|
580
|
-
rows.push(kept ?? null)
|
|
581
|
-
if (kept !== undefined) open.delete(kept.id)
|
|
582
|
-
}
|
|
583
|
-
const rest = open.values()
|
|
584
|
-
for (let i = 0; i < shownSlots; i++)
|
|
585
|
-
if (slots[i] === null && rows[i] === null) rows[i] = rest.next().value ?? null
|
|
586
|
-
for (let i = shownSlots - 1; i >= 0; i--) {
|
|
587
|
-
if (slots[i] !== null || rows[i] !== null) continue
|
|
588
|
-
rows.splice(i, 1)
|
|
589
|
-
slots.splice(i, 1)
|
|
590
|
-
slots.push(null)
|
|
591
|
-
shownSlots--
|
|
592
|
-
}
|
|
593
|
-
heldRows.length = 0
|
|
594
|
-
for (const r of rows) heldRows.push(r?.id ?? null)
|
|
595
|
-
return rows
|
|
596
|
-
}
|
|
597
613
|
const tick = (): void => {
|
|
598
614
|
if (!writer.enabled || statusDead || ticker !== null) return
|
|
599
615
|
// Keeps the per-worker elapsed time ticking between task events
|
|
@@ -624,14 +640,36 @@ export function defaultLogger(
|
|
|
624
640
|
}
|
|
625
641
|
|
|
626
642
|
// The task log opens with one blank line before its first row, on a
|
|
627
|
-
// terminal and off one (
|
|
643
|
+
// terminal and off one (2026-10-14). A note vx prints before it is
|
|
628
644
|
// no task row (`status`). Without a footer, the caller frames the log
|
|
629
645
|
// (`vx watch` opens each cycle with its own line).
|
|
630
646
|
let logOpened = opts.footer === false
|
|
631
647
|
// The log ends on a blank line (a block's own): a blank status line
|
|
632
648
|
// after it (the footer's) would make two; one blank line apart is the rule.
|
|
633
649
|
let endsBlank = false
|
|
650
|
+
// A live-streamed task's partial line, and any colour it left on, end
|
|
651
|
+
// before anything of vx's own: a row or a block written after it (a step
|
|
652
|
+
// that ends while the task streams, another task's failure, a kept
|
|
653
|
+
// server's line) was glued to it, `building... time status cache
|
|
654
|
+
// task`, and took its colour.
|
|
655
|
+
let streamStyled = false
|
|
656
|
+
const endStreamLine = (): void => {
|
|
657
|
+
const end = (streamStyled ? RESET : '') + (streamMidLine ? '\n' : '')
|
|
658
|
+
streamStyled = false
|
|
659
|
+
streamMidLine = false
|
|
660
|
+
if (end.length > 0) logWrite(end)
|
|
661
|
+
}
|
|
634
662
|
const taskWrite = (chunk: string): void => {
|
|
663
|
+
endStreamLine()
|
|
664
|
+
logWrite(chunk)
|
|
665
|
+
}
|
|
666
|
+
const streamWrite = (chunk: string): void => {
|
|
667
|
+
streamedSinceBlock = true
|
|
668
|
+
if (chunk.length > 0) streamMidLine = !chunk.endsWith('\n')
|
|
669
|
+
if (chunk.includes('\x1b[')) streamStyled = leavesStyle(chunk)
|
|
670
|
+
logWrite(chunk)
|
|
671
|
+
}
|
|
672
|
+
const logWrite = (chunk: string): void => {
|
|
635
673
|
if (chunk.length > 0) endsBlank = chunk.endsWith('\n\n')
|
|
636
674
|
if (!logOpened && chunk.length > 0) {
|
|
637
675
|
logOpened = true
|
|
@@ -644,10 +682,21 @@ export function defaultLogger(
|
|
|
644
682
|
taskWrite(`${line}\n`)
|
|
645
683
|
lineEmitted = true
|
|
646
684
|
}
|
|
685
|
+
// The list's header, once over its first row, where the live region
|
|
686
|
+
// heads its own rows the same way (2026-10-21).
|
|
687
|
+
let headed = !writer.enabled
|
|
688
|
+
const emitRow = (line: string): void => {
|
|
689
|
+
if (!headed) {
|
|
690
|
+
headed = true
|
|
691
|
+
emitLine(formatListHeader(runContext?.whereRunning !== undefined, colors))
|
|
692
|
+
}
|
|
693
|
+
emitLine(line)
|
|
694
|
+
}
|
|
647
695
|
// formatTaskBlock returns '' for group tasks (no exec) — skip the
|
|
648
696
|
// write so a stray newline doesn't sneak into the output.
|
|
649
697
|
const emitBlock = (block: string): void => {
|
|
650
698
|
if (block.length === 0) return
|
|
699
|
+
endStreamLine()
|
|
651
700
|
const lead = lineEmitted || streamedSinceBlock ? '\n' : ''
|
|
652
701
|
taskWrite(`${lead}${block}\n`)
|
|
653
702
|
lineEmitted = false
|
|
@@ -684,6 +733,8 @@ export function defaultLogger(
|
|
|
684
733
|
|
|
685
734
|
const failureRow = (node: TaskNode, outcome: TaskOutcome): string =>
|
|
686
735
|
formatFailureLine(node.id, outcome.durationMs, colors, cacheWordOf(outcome)) +
|
|
736
|
+
(onPath.has(node.id) ? pathNote(colors) : '') +
|
|
737
|
+
whereNote(outcome, colors) +
|
|
687
738
|
flakyNote(outcome, colors)
|
|
688
739
|
|
|
689
740
|
return {
|
|
@@ -699,21 +750,15 @@ export function defaultLogger(
|
|
|
699
750
|
flushKept()
|
|
700
751
|
// A live-streamed server keeps writing after its frame closes, so a
|
|
701
752
|
// summary row or "exited with code" can follow its partial line.
|
|
702
|
-
|
|
703
|
-
writer.write('\n')
|
|
704
|
-
streamMidLine = false
|
|
705
|
-
}
|
|
753
|
+
endStreamLine()
|
|
706
754
|
if (line === '' && endsBlank) return
|
|
707
755
|
endsBlank = line === ''
|
|
708
756
|
writer.write(`${line}\n`)
|
|
709
757
|
},
|
|
710
758
|
warn(line) {
|
|
711
759
|
flushKept()
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
streamMidLine = false
|
|
715
|
-
}
|
|
716
|
-
writer.writeTo(errOut, `${line}\n`)
|
|
760
|
+
endStreamLine()
|
|
761
|
+
writer.writeTo(errOut, `${ui.tagged('warn', line, colors)}\n`)
|
|
717
762
|
},
|
|
718
763
|
runStart(info) {
|
|
719
764
|
total = info.total
|
|
@@ -721,9 +766,7 @@ export function defaultLogger(
|
|
|
721
766
|
runContext = info.context
|
|
722
767
|
startedAtMs = Date.now()
|
|
723
768
|
started = true
|
|
724
|
-
|
|
725
|
-
slots = Array.from({ length: cap }, () => null)
|
|
726
|
-
shownSlots = 0
|
|
769
|
+
slots = Array.from({ length: Math.max(1, info.concurrency ?? 10) }, () => null)
|
|
727
770
|
tick()
|
|
728
771
|
refresh(true)
|
|
729
772
|
},
|
|
@@ -735,15 +778,20 @@ export function defaultLogger(
|
|
|
735
778
|
stepEnd(id, { durationMs, ok }) {
|
|
736
779
|
steps.delete(id)
|
|
737
780
|
if (durationMs < STEP_SHOWN_MS) return
|
|
738
|
-
|
|
739
|
-
|
|
781
|
+
// `hash-only` is one line per task: only a step that failed adds one.
|
|
782
|
+
if (
|
|
783
|
+
ok
|
|
784
|
+
? view.mode !== 'none' && view.mode !== 'errors-only' && view.mode !== 'hash-only'
|
|
785
|
+
: view.mode !== 'none'
|
|
786
|
+
)
|
|
787
|
+
emitRow(formatStepLine(id, durationMs, ok, colors))
|
|
740
788
|
refresh(true)
|
|
741
789
|
},
|
|
742
790
|
runStop() {
|
|
743
791
|
if (stopping) return
|
|
744
792
|
stopping = true
|
|
745
793
|
blocked = undefined
|
|
746
|
-
|
|
794
|
+
blockedBy = undefined
|
|
747
795
|
refresh(true)
|
|
748
796
|
},
|
|
749
797
|
runDone() {
|
|
@@ -757,34 +805,25 @@ export function defaultLogger(
|
|
|
757
805
|
// What is coalesced lands before the task writes past it.
|
|
758
806
|
writer.settle()
|
|
759
807
|
}
|
|
760
|
-
// Focused flow:
|
|
761
|
-
//
|
|
762
|
-
//
|
|
808
|
+
// Focused flow: a requested node streams raw, and the region stays
|
|
809
|
+
// under its output (the writer erases and redraws it around each
|
|
810
|
+
// write): taken down, the summary and the run's id were gone until
|
|
811
|
+
// the task ended.
|
|
763
812
|
if (streamsLive(node)) {
|
|
764
|
-
killStatus()
|
|
765
813
|
// Open the live frame: full task info even when the command
|
|
766
814
|
// streams nothing (or the hit replays nothing).
|
|
767
815
|
emitLine(formatFrameOpen(node, colors, forwardArgs))
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
// its outcome would leave a never-ready one uncapped forever.
|
|
772
|
-
if (node.config.exec?.persistent !== undefined && !persistentTails.has(node.id)) {
|
|
816
|
+
} else if (node.config.exec?.persistent !== undefined && !persistentTails.has(node.id)) {
|
|
817
|
+
// Bound a persistent task's output from its FIRST chunk. Waiting for
|
|
818
|
+
// its outcome would leave a never-ready one uncapped forever.
|
|
773
819
|
persistentTails.set(node.id, { node, out: createTail(), err: createTail() })
|
|
774
820
|
}
|
|
821
|
+
// The rows are the status region's; with none, nothing reads them.
|
|
822
|
+
if (statusDead) return
|
|
775
823
|
const slot: WorkerSlot = { id: node.id, startedMs: Date.now() }
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
let free = own >= 0 && slots[own] === null ? own : -1
|
|
780
|
-
if (free < 0)
|
|
781
|
-
free = slots.findIndex((s, i) => s === null && i < shownSlots && heldRows[i] == null)
|
|
782
|
-
if (free < 0) free = slots.findLastIndex((s, i) => s === null && i < shownSlots)
|
|
783
|
-
if (free < 0) free = slots.indexOf(null)
|
|
784
|
-
if (free >= 0) {
|
|
785
|
-
slots[free] = slot
|
|
786
|
-
shownSlots = Math.max(shownSlots, free + 1)
|
|
787
|
-
} else slotQueue.push(slot)
|
|
824
|
+
const free = slots.indexOf(null)
|
|
825
|
+
if (free >= 0) slots[free] = slot
|
|
826
|
+
else slotQueue.push(slot)
|
|
788
827
|
refresh(true)
|
|
789
828
|
},
|
|
790
829
|
holdStatus: () => writer.hold(),
|
|
@@ -821,7 +860,7 @@ export function defaultLogger(
|
|
|
821
860
|
tail = true
|
|
822
861
|
slots = []
|
|
823
862
|
blocked = undefined
|
|
824
|
-
|
|
863
|
+
blockedBy = undefined
|
|
825
864
|
slotQueue.length = 0
|
|
826
865
|
refresh(true)
|
|
827
866
|
} else if (!tail) killStatus()
|
|
@@ -880,9 +919,7 @@ export function defaultLogger(
|
|
|
880
919
|
taskStdout(node, chunk) {
|
|
881
920
|
if (discardsOutput) return
|
|
882
921
|
if (streamsLive(node)) {
|
|
883
|
-
|
|
884
|
-
if (chunk.length > 0) streamMidLine = !chunk.endsWith('\n')
|
|
885
|
-
taskWrite(chunk)
|
|
922
|
+
streamWrite(chunk)
|
|
886
923
|
return
|
|
887
924
|
}
|
|
888
925
|
const tail = persistentTails.get(node.id)
|
|
@@ -891,14 +928,13 @@ export function defaultLogger(
|
|
|
891
928
|
else appendTail(tail.out, chunk)
|
|
892
929
|
return
|
|
893
930
|
}
|
|
931
|
+
if (heldOver(node)) return streamKept(node, chunk)
|
|
894
932
|
pushChunk(node.id, chunk, false)
|
|
895
933
|
},
|
|
896
934
|
taskStderr(node, chunk) {
|
|
897
935
|
if (discardsOutput) return
|
|
898
936
|
if (streamsLive(node)) {
|
|
899
|
-
|
|
900
|
-
if (chunk.length > 0) streamMidLine = !chunk.endsWith('\n')
|
|
901
|
-
taskWrite(chunk)
|
|
937
|
+
streamWrite(chunk)
|
|
902
938
|
return
|
|
903
939
|
}
|
|
904
940
|
const tail = persistentTails.get(node.id)
|
|
@@ -907,6 +943,7 @@ export function defaultLogger(
|
|
|
907
943
|
else appendTail(tail.err, chunk)
|
|
908
944
|
return
|
|
909
945
|
}
|
|
946
|
+
if (heldOver(node)) return streamKept(node, chunk)
|
|
910
947
|
pushChunk(node.id, chunk, true)
|
|
911
948
|
},
|
|
912
949
|
taskComplete(node, outcome) {
|
|
@@ -957,23 +994,21 @@ export function defaultLogger(
|
|
|
957
994
|
(view.mode === 'focused' && isPrimary(node)) ||
|
|
958
995
|
(framesTerminal && holdsTerminal(node)))
|
|
959
996
|
if (framed && streamsLive(node)) {
|
|
960
|
-
if (streamMidLine) {
|
|
961
|
-
taskWrite('\n')
|
|
962
|
-
streamMidLine = false
|
|
963
|
-
}
|
|
964
997
|
emitFrameClose(formatFrameClose(node, outcome, colors))
|
|
965
998
|
return
|
|
966
999
|
}
|
|
967
1000
|
if (framed && output.some((c) => c.text.length > 0)) {
|
|
968
1001
|
emitBlock(
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
1002
|
+
fenced(
|
|
1003
|
+
formatTaskBlock(
|
|
1004
|
+
node,
|
|
1005
|
+
outcome,
|
|
1006
|
+
{ output, ...dropped },
|
|
1007
|
+
colors,
|
|
1008
|
+
true,
|
|
1009
|
+
forwardArgs,
|
|
1010
|
+
links,
|
|
1011
|
+
),
|
|
977
1012
|
),
|
|
978
1013
|
)
|
|
979
1014
|
return
|
|
@@ -981,7 +1016,7 @@ export function defaultLogger(
|
|
|
981
1016
|
// Otherwise a row in the task list, in every view that lists a
|
|
982
1017
|
// failure: the run exits red, and the row names why.
|
|
983
1018
|
if (view.mode !== 'none' && view.mode !== 'hash-only') {
|
|
984
|
-
|
|
1019
|
+
emitRow(formatTaskAbortedLine(node, outcome, colors))
|
|
985
1020
|
}
|
|
986
1021
|
return
|
|
987
1022
|
}
|
|
@@ -995,7 +1030,7 @@ export function defaultLogger(
|
|
|
995
1030
|
// A persistent task's outcome arrives at READY; the child
|
|
996
1031
|
// keeps running until the orchestrator SIGTERMs it at run
|
|
997
1032
|
// end — pin it so its liveness stays visible.
|
|
998
|
-
pinnedPersistent.push(
|
|
1033
|
+
pinnedPersistent.push(outcome)
|
|
999
1034
|
// …and capture what it writes from here on. A live-streaming
|
|
1000
1035
|
// node already owns the terminal, so buffering it would emit
|
|
1001
1036
|
// its output twice.
|
|
@@ -1005,16 +1040,19 @@ export function defaultLogger(
|
|
|
1005
1040
|
}
|
|
1006
1041
|
// Free the task's slot; the longest-waiting queued task
|
|
1007
1042
|
// (if any) takes it over, keeping lowest-index-first reuse.
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1043
|
+
if (!statusDead) {
|
|
1044
|
+
const si = slots.findIndex((s) => s !== null && s.id === node.id)
|
|
1045
|
+
if (si >= 0) slots[si] = slotQueue.shift() ?? null
|
|
1046
|
+
else {
|
|
1047
|
+
const qi = slotQueue.findIndex((s) => s.id === node.id)
|
|
1048
|
+
if (qi >= 0) slotQueue.splice(qi, 1)
|
|
1049
|
+
}
|
|
1013
1050
|
}
|
|
1014
1051
|
const bucket = cacheBucketOf(outcome)
|
|
1015
1052
|
if (bucket !== undefined) {
|
|
1016
|
-
|
|
1017
|
-
buckets.set(
|
|
1053
|
+
const entry = { task: node.id, bucket }
|
|
1054
|
+
if (outcome.hash !== undefined) buckets.set(outcome.hash, entry)
|
|
1055
|
+
buckets.set(node.id, entry)
|
|
1018
1056
|
}
|
|
1019
1057
|
switch (outcome.status) {
|
|
1020
1058
|
case 'success':
|
|
@@ -1045,10 +1083,6 @@ export function defaultLogger(
|
|
|
1045
1083
|
}
|
|
1046
1084
|
if (isGroupTask(node)) return
|
|
1047
1085
|
if (holdsTerminal(node) && streamsLive(node) && outcome.status !== 'skipped') {
|
|
1048
|
-
if (streamMidLine) {
|
|
1049
|
-
taskWrite('\n')
|
|
1050
|
-
streamMidLine = false
|
|
1051
|
-
}
|
|
1052
1086
|
emitFrameClose(formatFrameClose(node, outcome, colors))
|
|
1053
1087
|
return
|
|
1054
1088
|
}
|
|
@@ -1060,17 +1094,16 @@ export function defaultLogger(
|
|
|
1060
1094
|
// The word carries the outcome (skips/failures included); the
|
|
1061
1095
|
// key is what this mode exists to show — absent for a task
|
|
1062
1096
|
// that never computed one (skipped, uncacheable).
|
|
1063
|
-
|
|
1064
|
-
emitLine(`${outcomeWord(outcome)} ${node.id}${key}`)
|
|
1097
|
+
emitLine(formatTaskHashLine(node, outcome, colors))
|
|
1065
1098
|
return
|
|
1066
1099
|
}
|
|
1067
1100
|
case 'errors-only':
|
|
1068
1101
|
if (outcome.status === 'skipped') {
|
|
1069
|
-
|
|
1102
|
+
emitRow(formatTaskSkippedLine(node, colors, outcome))
|
|
1070
1103
|
return
|
|
1071
1104
|
}
|
|
1072
1105
|
if (outcome.status !== 'failed') return
|
|
1073
|
-
|
|
1106
|
+
emitRow(failureRow(node, outcome))
|
|
1074
1107
|
deferredFailures.push(
|
|
1075
1108
|
formatTaskBlock(
|
|
1076
1109
|
node,
|
|
@@ -1090,7 +1123,7 @@ export function defaultLogger(
|
|
|
1090
1123
|
// surface in the end-of-run summary.
|
|
1091
1124
|
if (outcome.status === 'failed') {
|
|
1092
1125
|
// ◼ row now; the full frame replays at runEnd.
|
|
1093
|
-
|
|
1126
|
+
emitRow(failureRow(node, outcome))
|
|
1094
1127
|
deferredFailures.push(
|
|
1095
1128
|
formatTaskBlock(
|
|
1096
1129
|
node,
|
|
@@ -1103,9 +1136,9 @@ export function defaultLogger(
|
|
|
1103
1136
|
),
|
|
1104
1137
|
)
|
|
1105
1138
|
} else if (outcome.status === 'success') {
|
|
1106
|
-
|
|
1139
|
+
emitRow(formatTaskExecutedLine(node, outcome, colors, onPath.has(node.id)))
|
|
1107
1140
|
} else if (outcome.status === 'skipped') {
|
|
1108
|
-
|
|
1141
|
+
emitRow(formatTaskSkippedLine(node, colors, outcome))
|
|
1109
1142
|
}
|
|
1110
1143
|
return
|
|
1111
1144
|
case 'focused':
|
|
@@ -1114,7 +1147,7 @@ export function defaultLogger(
|
|
|
1114
1147
|
// stopped), so no frame-open fired — and a skip has no output,
|
|
1115
1148
|
// so a one-liner carries everything a frame would.
|
|
1116
1149
|
if (outcome.status === 'skipped') {
|
|
1117
|
-
|
|
1150
|
+
emitRow(formatTaskSkippedLine(node, colors, outcome))
|
|
1118
1151
|
return
|
|
1119
1152
|
}
|
|
1120
1153
|
if (streamsLive(node)) {
|
|
@@ -1123,10 +1156,6 @@ export function defaultLogger(
|
|
|
1123
1156
|
// this close — full task info for every outcome, cached
|
|
1124
1157
|
// and up-to-date included (owner: "always full frame
|
|
1125
1158
|
// for a single task").
|
|
1126
|
-
if (streamMidLine) {
|
|
1127
|
-
taskWrite('\n')
|
|
1128
|
-
streamMidLine = false
|
|
1129
|
-
}
|
|
1130
1159
|
emitFrameClose(formatFrameClose(node, outcome, colors))
|
|
1131
1160
|
return
|
|
1132
1161
|
}
|
|
@@ -1135,7 +1164,7 @@ export function defaultLogger(
|
|
|
1135
1164
|
// to runEnd like everywhere else; everything else emits
|
|
1136
1165
|
// ONE atomic block from the buffered output.
|
|
1137
1166
|
if (outcome.status === 'failed') {
|
|
1138
|
-
|
|
1167
|
+
emitRow(failureRow(node, outcome))
|
|
1139
1168
|
deferredFailures.push(
|
|
1140
1169
|
formatTaskBlock(
|
|
1141
1170
|
node,
|
|
@@ -1152,14 +1181,16 @@ export function defaultLogger(
|
|
|
1152
1181
|
// forceCommand: a requested task's frame shows `$ cmd`
|
|
1153
1182
|
// whether it ran or was cached — same frame every run.
|
|
1154
1183
|
emitBlock(
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1184
|
+
fenced(
|
|
1185
|
+
formatTaskBlock(
|
|
1186
|
+
node,
|
|
1187
|
+
outcome,
|
|
1188
|
+
{ output, ...dropped },
|
|
1189
|
+
colors,
|
|
1190
|
+
true,
|
|
1191
|
+
forwardArgs,
|
|
1192
|
+
links,
|
|
1193
|
+
),
|
|
1163
1194
|
),
|
|
1164
1195
|
)
|
|
1165
1196
|
return
|
|
@@ -1167,9 +1198,9 @@ export function defaultLogger(
|
|
|
1167
1198
|
// Dependency-pulled nodes: silent on success; a skip gets its
|
|
1168
1199
|
// row, failures the ◼ row now and their frame replayed at runEnd.
|
|
1169
1200
|
if (outcome.status === 'skipped') {
|
|
1170
|
-
|
|
1201
|
+
emitRow(formatTaskSkippedLine(node, colors, outcome))
|
|
1171
1202
|
} else if (outcome.status === 'failed') {
|
|
1172
|
-
|
|
1203
|
+
emitRow(failureRow(node, outcome))
|
|
1173
1204
|
deferredFailures.push(
|
|
1174
1205
|
formatTaskBlock(
|
|
1175
1206
|
node,
|
|
@@ -1191,11 +1222,11 @@ export function defaultLogger(
|
|
|
1191
1222
|
// point); misses/failures are always framed. One-liners
|
|
1192
1223
|
// stay outside ::group:: — there's nothing to collapse.
|
|
1193
1224
|
if (isHit && output.every((c) => c.text.trim().length === 0)) {
|
|
1194
|
-
|
|
1225
|
+
emitRow(formatTaskHitLine(node, outcome, colors, onPath.has(node.id)))
|
|
1195
1226
|
return
|
|
1196
1227
|
}
|
|
1197
1228
|
if (outcome.status === 'skipped') {
|
|
1198
|
-
|
|
1229
|
+
emitRow(formatTaskSkippedLine(node, colors, outcome))
|
|
1199
1230
|
return
|
|
1200
1231
|
}
|
|
1201
1232
|
const block = formatTaskBlock(
|