@vzn/vx 0.0.650 → 0.0.652
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 +7 -23
- package/package.json +5 -5
- package/schemas/last.json +15 -0
- package/schemas/show.json +14 -20
- package/src/bin.ts +21 -31
- package/src/cache/archive.ts +1 -1
- package/src/cache/cache.ts +221 -30
- package/src/cache/file-hashes.ts +3 -0
- package/src/cache/git-inputs.ts +188 -39
- package/src/cache/history-stream.ts +134 -0
- package/src/cache/history-writer.ts +76 -0
- package/src/cache/index.ts +3 -0
- package/src/cache/inputs.ts +77 -14
- package/src/cache/layer.ts +17 -6
- package/src/cache/layered-cache.ts +4 -0
- package/src/cache/linked-inputs.ts +3 -0
- package/src/cache/lockfile-memo.ts +5 -3
- package/src/cache/output-index.ts +5 -2
- package/src/cache/remote-lane.ts +2 -1
- package/src/cache/run-history.ts +56 -17
- package/src/cache/schema.ts +15 -2
- package/src/cli/cache.ts +28 -18
- package/src/cli/completions.ts +4 -2
- package/src/cli/docs.ts +10 -11
- package/src/cli/help.ts +14 -25
- package/src/cli/index.ts +28 -9
- package/src/cli/info.ts +16 -8
- package/src/cli/init.ts +23 -15
- package/src/cli/last.ts +246 -204
- package/src/cli/lock.ts +55 -5
- package/src/cli/plan-format.ts +100 -118
- package/src/cli/plugin-commands.ts +8 -2
- package/src/cli/plugin-templates.ts +36 -45
- package/src/cli/run.ts +46 -27
- package/src/cli/select.ts +43 -5
- package/src/cli/show.ts +45 -52
- package/src/cli/task-verb.ts +22 -0
- package/src/cli/upgrade.ts +3 -1
- package/src/cli/watch-fs.ts +6 -1
- package/src/cli/watch-judge.ts +3 -2
- package/src/cli/watch-set.ts +4 -4
- package/src/cli/watch.ts +94 -52
- package/src/cli/why.ts +123 -89
- package/src/cli/workspace-config.ts +2 -1
- package/src/config.ts +47 -30
- package/src/exec/executor.ts +42 -2
- package/src/exec/index.ts +1 -0
- package/src/exec/kill-tree.ts +58 -5
- package/src/exec/local-executor.ts +1 -1
- package/src/exec/pipes.ts +152 -0
- package/src/exec/runner.ts +199 -182
- package/src/exec/sandbox-binds.ts +1 -1
- package/src/exec/sandbox-paths.ts +3 -1
- package/src/exec/sandbox-runtime.ts +317 -23
- package/src/exec/spawn-off.ts +154 -0
- package/src/exec/task-proxy.ts +93 -0
- package/src/exec/warm-shell.ts +146 -0
- package/src/graph/critical-path.ts +40 -28
- package/src/graph/definition.ts +16 -3
- package/src/graph/priorities.ts +98 -44
- package/src/graph/scheduler.ts +17 -11
- package/src/graph/task-graph.ts +51 -24
- package/src/handoff.ts +10 -7
- package/src/history/failure-mode.ts +24 -1
- package/src/history/history.ts +5 -0
- package/src/history/index.ts +1 -0
- package/src/history/metrics.ts +14 -6
- package/src/index.ts +7 -3
- package/src/orchestrator/admission.ts +1 -1
- package/src/orchestrator/command-windows.ts +1 -1
- package/src/orchestrator/deferred-outputs.ts +1 -1
- package/src/orchestrator/doctor.ts +1 -1
- package/src/orchestrator/download-policy.ts +5 -7
- package/src/orchestrator/excluded-keys.ts +1 -1
- package/src/orchestrator/execute-task.ts +67 -20
- package/src/orchestrator/graph-memo.ts +50 -1
- package/src/orchestrator/graph-stage.ts +6 -0
- package/src/orchestrator/hit-restore.ts +5 -6
- package/src/orchestrator/index.ts +3 -0
- package/src/orchestrator/keyed-projects.ts +28 -0
- package/src/orchestrator/live-usage.ts +16 -11
- package/src/orchestrator/local-shortcircuit.ts +2 -2
- package/src/orchestrator/locked-digests.ts +63 -0
- package/src/orchestrator/lockfile-claim.ts +108 -29
- package/src/orchestrator/miss-save.ts +58 -11
- package/src/orchestrator/persistent-task.ts +5 -1
- package/src/orchestrator/placement.ts +1 -1
- package/src/orchestrator/plan.ts +7 -3
- package/src/orchestrator/plugin-host.ts +67 -2
- package/src/orchestrator/plugin.ts +53 -2
- package/src/orchestrator/prepare.ts +16 -1
- package/src/orchestrator/projects.ts +4 -1
- package/src/orchestrator/remote-prefetch.ts +1 -1
- package/src/orchestrator/run-context.ts +29 -10
- package/src/orchestrator/run-failures.ts +62 -13
- package/src/orchestrator/run-keys.ts +1 -1
- package/src/orchestrator/run-records.ts +88 -59
- package/src/orchestrator/run-report.ts +1 -1
- package/src/orchestrator/run.ts +145 -41
- package/src/orchestrator/sandbox-request.ts +52 -9
- package/src/orchestrator/shell-verdict.ts +9 -0
- package/src/orchestrator/signals.ts +8 -5
- package/src/orchestrator/stable-keys.ts +17 -18
- package/src/orchestrator/task-artifacts.ts +1 -1
- package/src/orchestrator/task-hash.ts +21 -7
- package/src/orchestrator/telemetry.ts +5 -1
- package/src/report/events.ts +6 -14
- package/src/report/forecast.ts +10 -32
- package/src/report/framed-output.ts +283 -175
- package/src/report/index.ts +8 -3
- package/src/report/logger.ts +74 -39
- package/src/report/plain-output.ts +3 -1
- package/src/report/status-line.ts +49 -357
- package/src/report/summary.ts +105 -136
- package/src/settings.ts +8 -8
- package/src/ui/ansi.ts +48 -0
- package/src/ui/components.ts +506 -0
- package/src/ui/index.ts +79 -0
- package/src/ui/kit.ts +37 -0
- package/src/ui/live-region.ts +363 -0
- package/src/{util/refusal-notice.ts → ui/refusal.ts} +7 -5
- package/src/ui/terminal.ts +51 -0
- package/src/ui/theme.ts +224 -0
- package/src/ui/version-line.ts +28 -0
- package/src/util/config-frame.ts +191 -14
- package/src/util/edit-distance.ts +6 -1
- package/src/util/index.ts +2 -15
- package/src/util/paths.ts +21 -3
- package/src/util/secret-mask.ts +39 -3
- package/src/util/settle.ts +33 -1
- package/src/util/tail.ts +4 -1
- package/src/util/timing.ts +4 -16
- package/src/workspace/affected.ts +89 -30
- package/src/workspace/bracket-literal.ts +2 -2
- package/src/workspace/config-schema.ts +70 -63
- package/src/workspace/dependencies-lock.ts +191 -0
- package/src/workspace/filter.ts +36 -1
- package/src/workspace/index.ts +9 -0
- package/src/workspace/lockfile.ts +159 -6
- package/src/workspace/migration.ts +16 -9
- package/src/workspace/workspace.ts +3 -0
- package/src/util/colors.ts +0 -133
- package/src/util/report.ts +0 -44
- package/src/util/ui.ts +0 -258
- /package/src/{report/path-links.ts → ui/links.ts} +0 -0
- /package/src/{report → ui}/terminal-signals.ts +0 -0
package/src/report/index.ts
CHANGED
|
@@ -30,9 +30,14 @@ export {
|
|
|
30
30
|
resolveOutputView,
|
|
31
31
|
warnOf,
|
|
32
32
|
} from './logger.js'
|
|
33
|
-
export {
|
|
34
|
-
|
|
35
|
-
|
|
33
|
+
export {
|
|
34
|
+
formatPersistentList,
|
|
35
|
+
formatRecordedRow,
|
|
36
|
+
planList,
|
|
37
|
+
type PlannedRow,
|
|
38
|
+
taskList,
|
|
39
|
+
} from './framed-output.js'
|
|
40
|
+
export { createForecast } from './forecast.js'
|
|
36
41
|
export { plainOutput } from './plain-output.js'
|
|
37
42
|
export type { LiveUsage } from './status-line.js'
|
|
38
43
|
export { formatOutcomeTable, formatRunSummary } from './summary.js'
|
package/src/report/logger.ts
CHANGED
|
@@ -6,26 +6,22 @@ import {
|
|
|
6
6
|
formatKeptLines,
|
|
7
7
|
formatPersistentTailBlock,
|
|
8
8
|
formatListHeader,
|
|
9
|
+
usualCell,
|
|
9
10
|
formatTaskAbortedLine,
|
|
10
11
|
formatTaskBlock,
|
|
11
12
|
executedCells,
|
|
12
|
-
waited,
|
|
13
13
|
formatTaskExecutedLine,
|
|
14
14
|
flakyNote,
|
|
15
15
|
pathNote,
|
|
16
16
|
formatTaskHashLine,
|
|
17
17
|
formatTaskHitLine,
|
|
18
18
|
formatTaskSkippedLine,
|
|
19
|
-
leavesStyle,
|
|
20
|
-
RESET,
|
|
21
19
|
type OutputChunk,
|
|
22
20
|
} from './framed-output.js'
|
|
23
21
|
import {
|
|
24
|
-
createOutputWriter,
|
|
25
22
|
formatFailureLine,
|
|
26
23
|
formatStatusRegion,
|
|
27
24
|
formatStepLine,
|
|
28
|
-
type StatusStream,
|
|
29
25
|
type WorkerSlot,
|
|
30
26
|
} from './status-line.js'
|
|
31
27
|
import {
|
|
@@ -44,21 +40,26 @@ import {
|
|
|
44
40
|
resetTail,
|
|
45
41
|
tailText,
|
|
46
42
|
type Tail,
|
|
47
|
-
detectColors,
|
|
48
43
|
envFlagOn,
|
|
49
|
-
type ColorSupport,
|
|
50
|
-
ui,
|
|
51
44
|
} from '../util/index.js'
|
|
52
|
-
import
|
|
53
|
-
import { failedLabel, outcomeWord, ranNoCache } from './events.js'
|
|
54
|
-
import type { BlockedTask, Forecast } from './forecast.js'
|
|
45
|
+
import * as ui from '../ui/index.js'
|
|
55
46
|
import {
|
|
47
|
+
createOutputWriter,
|
|
48
|
+
detectColors,
|
|
49
|
+
hasEscape,
|
|
50
|
+
leavesStyle,
|
|
56
51
|
notifyEscape,
|
|
57
52
|
PROGRESS_CLEAR,
|
|
58
53
|
progressEscape,
|
|
54
|
+
STYLE_END,
|
|
59
55
|
terminalLinks,
|
|
60
56
|
terminalSignals,
|
|
61
|
-
|
|
57
|
+
type ColorSupport,
|
|
58
|
+
type StatusStream,
|
|
59
|
+
} from '../ui/index.js'
|
|
60
|
+
import { isCacheHit } from '../graph/index.js'
|
|
61
|
+
import { failedLabel, outcomeWord, ranNoCache } from './events.js'
|
|
62
|
+
import type { BlockedTask, Forecast } from './forecast.js'
|
|
62
63
|
|
|
63
64
|
export interface Logger {
|
|
64
65
|
/** Header / footer / status text. Written verbatim, one trailing \n added. */
|
|
@@ -92,8 +93,7 @@ export interface Logger {
|
|
|
92
93
|
/** The run's canonical start (epoch ms) — equals the final summary's `startedAt`. */
|
|
93
94
|
startedAtMs?: number
|
|
94
95
|
}): void
|
|
95
|
-
|
|
96
|
-
taskStart?(node: TaskNode, info?: { waitedMs?: number }): void
|
|
96
|
+
taskStart?(node: TaskNode): void
|
|
97
97
|
/** The task graph finished (any outcome); vx's own closing steps follow. Idempotent. */
|
|
98
98
|
runEnd?(): void
|
|
99
99
|
/**
|
|
@@ -355,9 +355,7 @@ export function defaultLogger(
|
|
|
355
355
|
// Tasks the region showed on the critical path: their finished rows say so too.
|
|
356
356
|
const onPath = new Set<string>()
|
|
357
357
|
let blockedCount = 0
|
|
358
|
-
let expected: ReadonlyMap<string, number> | undefined
|
|
359
358
|
// Each task's usual time once history is read: the results' `±usual`.
|
|
360
|
-
let usualOf: ReadonlyMap<string, number> | undefined
|
|
361
359
|
let blocked: readonly BlockedTask[] | undefined
|
|
362
360
|
let blockedBy: ReadonlyMap<string, number> | undefined
|
|
363
361
|
let etaAtMs = 0
|
|
@@ -375,7 +373,7 @@ export function defaultLogger(
|
|
|
375
373
|
let slots: (WorkerSlot | null)[] = []
|
|
376
374
|
const slotQueue: WorkerSlot[] = []
|
|
377
375
|
let succeeded = 0
|
|
378
|
-
// Executed
|
|
376
|
+
// Executed without `cache: true`: never a miss, counted apart.
|
|
379
377
|
let noCache = 0
|
|
380
378
|
let upToDate = 0
|
|
381
379
|
let restoredLocal = 0
|
|
@@ -522,10 +520,8 @@ export function defaultLogger(
|
|
|
522
520
|
: Math.min(deadlineMs ?? now, now)
|
|
523
521
|
critical = f.critical
|
|
524
522
|
blockedCount = f.waiting
|
|
525
|
-
expected = f.expected
|
|
526
523
|
blocked = f.blocked
|
|
527
524
|
blockedBy = f.blockedBy
|
|
528
|
-
usualOf = f.usual
|
|
529
525
|
}
|
|
530
526
|
const etaMs = deadlineMs === undefined ? undefined : deadlineMs - now
|
|
531
527
|
if (signals.progress && total > 0) {
|
|
@@ -610,7 +606,7 @@ export function defaultLogger(
|
|
|
610
606
|
summaryLines,
|
|
611
607
|
...(runContext?.whereRunning !== undefined ? { where: runContext.whereRunning } : {}),
|
|
612
608
|
...(runContext?.usage !== undefined ? { usage: runContext.usage } : {}),
|
|
613
|
-
...(
|
|
609
|
+
...(runContext?.usual !== undefined ? { usual: runContext.usual } : {}),
|
|
614
610
|
...(critical !== undefined ? { critical } : {}),
|
|
615
611
|
},
|
|
616
612
|
colors,
|
|
@@ -660,7 +656,7 @@ export function defaultLogger(
|
|
|
660
656
|
// task`, and took its colour.
|
|
661
657
|
let streamStyled = false
|
|
662
658
|
const endStreamLine = (): void => {
|
|
663
|
-
const end = (streamStyled ?
|
|
659
|
+
const end = (streamStyled ? STYLE_END : '') + (streamMidLine ? '\n' : '')
|
|
664
660
|
streamStyled = false
|
|
665
661
|
streamMidLine = false
|
|
666
662
|
if (end.length > 0) logWrite(end)
|
|
@@ -672,10 +668,11 @@ export function defaultLogger(
|
|
|
672
668
|
const streamWrite = (chunk: string): void => {
|
|
673
669
|
streamedSinceBlock = true
|
|
674
670
|
if (chunk.length > 0) streamMidLine = !chunk.endsWith('\n')
|
|
675
|
-
if (chunk
|
|
671
|
+
if (hasEscape(chunk)) streamStyled = leavesStyle(chunk)
|
|
676
672
|
logWrite(chunk)
|
|
677
673
|
}
|
|
678
674
|
const logWrite = (chunk: string): void => {
|
|
675
|
+
if (panelOpen !== undefined && !inPanel && chunk.length > 0) closePanel()
|
|
679
676
|
if (chunk.length > 0) endsBlank = chunk.endsWith('\n\n')
|
|
680
677
|
if (!logOpened && chunk.length > 0) {
|
|
681
678
|
logOpened = true
|
|
@@ -688,15 +685,33 @@ export function defaultLogger(
|
|
|
688
685
|
taskWrite(`${line}\n`)
|
|
689
686
|
lineEmitted = true
|
|
690
687
|
}
|
|
691
|
-
// The
|
|
692
|
-
//
|
|
693
|
-
|
|
688
|
+
// The open panel: rows of one kind share one box, the Tasks list on the
|
|
689
|
+
// log's stream and the Warnings on stderr; anything else said closes it
|
|
690
|
+
// first, and the next row opens it again. Painted, a box; plain on a
|
|
691
|
+
// terminal, the Tasks rule and header over the rows, as the live region
|
|
692
|
+
// heads its own (2026-10-21); piped, the bare rows a script reads.
|
|
693
|
+
const panels = colors.enabled || writer.enabled
|
|
694
|
+
let panelOpen: 'Tasks' | 'Warnings' | undefined
|
|
695
|
+
let inPanel = false
|
|
696
|
+
function closePanel(): void {
|
|
697
|
+
const was = panelOpen
|
|
698
|
+
if (was === undefined) return
|
|
699
|
+
panelOpen = undefined
|
|
700
|
+
}
|
|
694
701
|
const emitRow = (line: string): void => {
|
|
695
|
-
if (!
|
|
696
|
-
|
|
702
|
+
if (!panels) {
|
|
703
|
+
emitLine(line)
|
|
704
|
+
return
|
|
705
|
+
}
|
|
706
|
+
if (panelOpen === 'Warnings') closePanel()
|
|
707
|
+
inPanel = true
|
|
708
|
+
if (panelOpen !== 'Tasks') {
|
|
709
|
+
emitLine(ui.panelTop('Tasks', colors))
|
|
697
710
|
emitLine(formatListHeader(colors))
|
|
711
|
+
panelOpen = 'Tasks'
|
|
698
712
|
}
|
|
699
713
|
emitLine(line)
|
|
714
|
+
inPanel = false
|
|
700
715
|
}
|
|
701
716
|
// formatTaskBlock returns '' for group tasks (no exec) — skip the
|
|
702
717
|
// write so a stray newline doesn't sneak into the output.
|
|
@@ -739,14 +754,14 @@ export function defaultLogger(
|
|
|
739
754
|
|
|
740
755
|
// How many tasks are downstream of each: every results row's `blocks`.
|
|
741
756
|
const blocksOf = (id: string): number | undefined => runContext?.blocks?.().get(id)
|
|
757
|
+
// A row's usual time: null for a task history never timed, undefined with no history to ask.
|
|
758
|
+
const usualMsOf = (id: string): number | null | undefined =>
|
|
759
|
+
runContext?.usual === undefined ? undefined : (runContext.usual(id) ?? null)
|
|
742
760
|
const failureRow = (node: TaskNode, outcome: TaskOutcome): string =>
|
|
743
|
-
formatFailureLine(
|
|
744
|
-
node.id,
|
|
745
|
-
outcome.durationMs,
|
|
746
|
-
|
|
747
|
-
cacheWordOf(outcome),
|
|
748
|
-
executedCells(outcome, blocksOf(node.id)),
|
|
749
|
-
) +
|
|
761
|
+
formatFailureLine(node.id, outcome.durationMs, colors, cacheWordOf(outcome), {
|
|
762
|
+
...executedCells(outcome, blocksOf(node.id)),
|
|
763
|
+
usual: usualCell(outcome.durationMs, usualMsOf(node.id), colors),
|
|
764
|
+
}) +
|
|
750
765
|
(onPath.has(node.id) ? pathNote(colors) : '') +
|
|
751
766
|
flakyNote(outcome, colors)
|
|
752
767
|
|
|
@@ -764,6 +779,7 @@ export function defaultLogger(
|
|
|
764
779
|
// A live-streamed server keeps writing after its frame closes, so a
|
|
765
780
|
// summary row or "exited with code" can follow its partial line.
|
|
766
781
|
endStreamLine()
|
|
782
|
+
closePanel()
|
|
767
783
|
if (line === '' && endsBlank) return
|
|
768
784
|
endsBlank = line === ''
|
|
769
785
|
writer.write(`${line}\n`)
|
|
@@ -771,7 +787,20 @@ export function defaultLogger(
|
|
|
771
787
|
warn(line) {
|
|
772
788
|
flushKept()
|
|
773
789
|
endStreamLine()
|
|
774
|
-
|
|
790
|
+
if (!colors.enabled) {
|
|
791
|
+
writer.writeTo(errOut, `${ui.tagged('warn', line, colors)}\n`)
|
|
792
|
+
return
|
|
793
|
+
}
|
|
794
|
+
if (panelOpen === 'Tasks') closePanel()
|
|
795
|
+
if (panelOpen !== 'Warnings') {
|
|
796
|
+
writer.writeTo(errOut, `${ui.panelTop('Warnings', colors)}\n`)
|
|
797
|
+
panelOpen = 'Warnings'
|
|
798
|
+
}
|
|
799
|
+
const rows = line
|
|
800
|
+
.replace(/\n+$/, '')
|
|
801
|
+
.split('\n')
|
|
802
|
+
.map((l) => ui.tagged('warn', l, colors))
|
|
803
|
+
writer.writeTo(errOut, `${rows.join('\n')}\n`)
|
|
775
804
|
},
|
|
776
805
|
runStart(info) {
|
|
777
806
|
total = info.total
|
|
@@ -779,7 +808,13 @@ export function defaultLogger(
|
|
|
779
808
|
runContext = info.context
|
|
780
809
|
startedAtMs = Date.now()
|
|
781
810
|
started = true
|
|
782
|
-
slots
|
|
811
|
+
// No more slots than tasks: one per allowed worker, every frame walked
|
|
812
|
+
// them all (`--concurrency=100000000` over six tasks took 80 s, and
|
|
813
|
+
// 2^32 threw here).
|
|
814
|
+
slots = Array.from(
|
|
815
|
+
{ length: Math.max(1, Math.min(info.concurrency ?? 10, info.total)) },
|
|
816
|
+
() => null,
|
|
817
|
+
)
|
|
783
818
|
tick()
|
|
784
819
|
refresh(true)
|
|
785
820
|
},
|
|
@@ -811,7 +846,7 @@ export function defaultLogger(
|
|
|
811
846
|
tail = false
|
|
812
847
|
killStatus()
|
|
813
848
|
},
|
|
814
|
-
taskStart(node
|
|
849
|
+
taskStart(node) {
|
|
815
850
|
if (isGroupTask(node)) return
|
|
816
851
|
if (holdsTerminal(node)) {
|
|
817
852
|
killStatus()
|
|
@@ -837,7 +872,6 @@ export function defaultLogger(
|
|
|
837
872
|
id: node.id,
|
|
838
873
|
startedMs: Date.now(),
|
|
839
874
|
cache: ranNoCache({ node }) ? 'no-cache' : 'miss',
|
|
840
|
-
...(waited(info?.waitedMs) ? { waitedMs: info!.waitedMs! } : {}),
|
|
841
875
|
}
|
|
842
876
|
const free = slots.indexOf(null)
|
|
843
877
|
if (free >= 0) slots[free] = slot
|
|
@@ -930,6 +964,7 @@ export function defaultLogger(
|
|
|
930
964
|
flushedFailures = true
|
|
931
965
|
for (const block of deferredFailures) emitBlock(fenced(block))
|
|
932
966
|
}
|
|
967
|
+
closePanel()
|
|
933
968
|
// The summary and the CLI's own lines follow on the stream directly:
|
|
934
969
|
// nothing may still be held, and nothing after this is.
|
|
935
970
|
writer.settle()
|
|
@@ -1161,7 +1196,7 @@ export function defaultLogger(
|
|
|
1161
1196
|
colors,
|
|
1162
1197
|
onPath.has(node.id),
|
|
1163
1198
|
blocksOf(node.id),
|
|
1164
|
-
|
|
1199
|
+
usualMsOf(node.id),
|
|
1165
1200
|
),
|
|
1166
1201
|
)
|
|
1167
1202
|
} else if (outcome.status === 'skipped') {
|
|
@@ -42,7 +42,7 @@ export function plainOutput(sink: Logger): Logger {
|
|
|
42
42
|
status: (line) => sink.status(line),
|
|
43
43
|
...(sink.warn && { warn: (line) => sink.warn!(line) }),
|
|
44
44
|
...(sink.runStart && { runStart: (info) => sink.runStart!(info) }),
|
|
45
|
-
...(sink.taskStart && { taskStart: (node
|
|
45
|
+
...(sink.taskStart && { taskStart: (node) => sink.taskStart!(node) }),
|
|
46
46
|
...(sink.runEnd && { runEnd: () => sink.runEnd!() }),
|
|
47
47
|
...(sink.stepStart && { stepStart: (id) => sink.stepStart!(id) }),
|
|
48
48
|
...(sink.stepEnd && { stepEnd: (id, info) => sink.stepEnd!(id, info) }),
|
|
@@ -57,6 +57,8 @@ export function plainOutput(sink: Logger): Logger {
|
|
|
57
57
|
if (text !== '' || chunk === '') sink.taskStderr(node, text)
|
|
58
58
|
},
|
|
59
59
|
taskComplete(node, outcome) {
|
|
60
|
+
// Nearly every task ends with no escape held: its keys go unbuilt.
|
|
61
|
+
if (held.size === 0) return sink.taskComplete(node, outcome)
|
|
60
62
|
flush(node, `${node.id}\0out`, (n, c) => sink.taskStdout(n, c))
|
|
61
63
|
flush(node, `${node.id}\0err`, (n, c) => sink.taskStderr(n, c))
|
|
62
64
|
sink.taskComplete(node, outcome)
|