fix: capture typst diagnostics emitted after the status line
Build and Deploy Verso / deploy (push) Has been cancelled
Build and Deploy Verso / deploy (push) Has been cancelled
typst watch outputs the "[HH:MM:SS] compiled with errors" status line FIRST, then the full diagnostic output (file:line:col, source snippets, hints) AFTERWARDS. The previous code resolved the pending compile promise as soon as COMPILE_DONE_RE fired, discarding all post-status diagnostic lines. Those lines then got cleared by the next cycle's COMPILE_START_RE, so output.log only ever contained the bare status line — explaining the "zero verbosity" symptom. Fix: introduce a two-phase buffering model. When COMPILE_DONE_RE fires, enter "post-done" phase (storing doneResult) and keep accumulating into currentLines. _finalizeCompile() is called either when the next COMPILE_START_RE arrives (zero added latency) or after FLUSH_DELAY_MS (150 ms fallback for the last compile). It concatenates pre-done and post-done lines before resolving, so output.log now contains the full diagnostic output. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -37,6 +37,13 @@ const FILE_ID_EXHAUSTION_RE = /ran out of file ids/i
|
||||
// Typst uses ~65 IDs per compile; 1000 compiles ≈ 65 000 — safely under 65 535.
|
||||
const MAX_COMPILES_BEFORE_RESTART = 1000
|
||||
|
||||
// typst watch emits the "[HH:MM:SS] compiled with errors" status line FIRST,
|
||||
// then the full diagnostic output (file:line:col, code snippets) AFTERWARDS.
|
||||
// We buffer post-done lines and resolve after this delay if no new compile
|
||||
// cycle starts sooner. 150 ms is well above the ~1 chunk latency for typst's
|
||||
// diagnostic flush and imperceptible on top of a typical compile time.
|
||||
const FLUSH_DELAY_MS = 150
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// State (module-level, never exported)
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -53,7 +60,13 @@ const ProcessTable = {}
|
||||
// compilationCount total successful compile cycles on this watcher
|
||||
// restartPending flag to restart at the next runTypst call
|
||||
// accumulator incomplete trailing line from the last data chunk
|
||||
// currentLines lines accumulated since the last compile-done event
|
||||
// currentLines lines accumulated in the current phase (pre-done or
|
||||
// post-done, see _onWatcherData for the two-phase logic)
|
||||
// doneResult { preLines, compiledWithErrors } held between the
|
||||
// COMPILE_DONE_RE line and the post-done diagnostic flush;
|
||||
// null when not in post-done phase
|
||||
// flushTimeout timeout handle that finalises doneResult when no next
|
||||
// compile cycle starts within FLUSH_DELAY_MS
|
||||
// pendingResolvers Array<{resolve, reject, timeoutHandle}>
|
||||
// pendingResult compile result cached when typst finished before a
|
||||
// resolver was registered (race-condition safety net)
|
||||
@@ -142,6 +155,8 @@ async function _startWatcher(compileName, options) {
|
||||
restartPending: false,
|
||||
accumulator: '',
|
||||
currentLines: [],
|
||||
doneResult: null,
|
||||
flushTimeout: null,
|
||||
pendingResolvers: [],
|
||||
pendingResult: null,
|
||||
}
|
||||
@@ -187,6 +202,7 @@ async function _startWatcher(compileName, options) {
|
||||
function _killWatchEntry(compileName) {
|
||||
const entry = WatchTable[compileName]
|
||||
if (!entry) return
|
||||
clearTimeout(entry.flushTimeout)
|
||||
delete WatchTable[compileName]
|
||||
try {
|
||||
_killedWatchPids.add(entry.proc.pid)
|
||||
@@ -211,9 +227,15 @@ function _onWatcherData(compileName, chunk) {
|
||||
|
||||
for (const line of lines) {
|
||||
if (COMPILE_START_RE.test(line)) {
|
||||
// New compile cycle — discard any output left over from a previous
|
||||
// failed compile that didn't emit a "compiled with errors" footer.
|
||||
entry.currentLines = []
|
||||
// A new compile cycle is starting. If we were in the post-done phase
|
||||
// (collecting diagnostic lines that typst emits AFTER the status line),
|
||||
// finalise the previous result now — all diagnostics have arrived.
|
||||
if (entry.doneResult) {
|
||||
_finalizeCompile(compileName)
|
||||
}
|
||||
// Start fresh for the new cycle.
|
||||
entry.currentLines = [line]
|
||||
continue
|
||||
}
|
||||
|
||||
entry.currentLines.push(line)
|
||||
@@ -228,8 +250,6 @@ function _onWatcherData(compileName, chunk) {
|
||||
|
||||
if (COMPILE_DONE_RE.test(line)) {
|
||||
entry.compilationCount++
|
||||
const stdout = entry.currentLines.join('\n')
|
||||
entry.currentLines = []
|
||||
|
||||
if (entry.compilationCount >= MAX_COMPILES_BEFORE_RESTART) {
|
||||
logger.info(
|
||||
@@ -239,14 +259,48 @@ function _onWatcherData(compileName, chunk) {
|
||||
entry.restartPending = true
|
||||
}
|
||||
|
||||
_resolveAllPending(compileName, {
|
||||
stdout,
|
||||
// typst watch outputs the "[HH:MM:SS] compiled with errors" status
|
||||
// line FIRST, then the full diagnostics (file:line:col, code snippets)
|
||||
// AFTERWARDS. Enter post-done phase: keep accumulating into currentLines
|
||||
// and flush after FLUSH_DELAY_MS (or immediately when the next compile
|
||||
// cycle's COMPILE_START_RE arrives, whichever comes first).
|
||||
entry.doneResult = {
|
||||
preLines: entry.currentLines,
|
||||
compiledWithErrors: /compiled with errors/.test(line),
|
||||
})
|
||||
}
|
||||
entry.currentLines = []
|
||||
|
||||
clearTimeout(entry.flushTimeout)
|
||||
entry.flushTimeout = setTimeout(
|
||||
() => _finalizeCompile(compileName),
|
||||
FLUSH_DELAY_MS
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Combines the pre-done lines (up to/including the status line) with any
|
||||
// post-done diagnostic lines and resolves all pending waiters.
|
||||
function _finalizeCompile(compileName) {
|
||||
const entry = WatchTable[compileName]
|
||||
if (!entry || !entry.doneResult) return
|
||||
|
||||
clearTimeout(entry.flushTimeout)
|
||||
entry.flushTimeout = null
|
||||
|
||||
const { preLines, compiledWithErrors } = entry.doneResult
|
||||
entry.doneResult = null
|
||||
|
||||
// Merge: status line(s) first, then the post-done diagnostics.
|
||||
const allLines = preLines.concat(entry.currentLines)
|
||||
entry.currentLines = []
|
||||
|
||||
_resolveAllPending(compileName, {
|
||||
stdout: allLines.join('\n'),
|
||||
compiledWithErrors,
|
||||
})
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Resolver helpers
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user