Skip to content

Commit 9390158

Browse files
cp89-cyberwraithgar
authored andcommitted
fix: preserve npm run output without trailing newline
1 parent d352e27 commit 9390158

File tree

2 files changed

+51
-0
lines changed

2 files changed

+51
-0
lines changed

lib/utils/display.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -427,6 +427,7 @@ class Progress {
427427
#lastUpdate = 0
428428
#interval
429429
#timeout
430+
#rendered = false
430431

431432
// We are rendering is enabled option is set and we are not waiting for the render timeout
432433
get #rendering () {
@@ -511,12 +512,17 @@ class Progress {
511512
}
512513
this.#clearSpinner()
513514
this.#stream.write(this.#spinner.frames[this.#frameIndex])
515+
this.#rendered = true
514516
}
515517

516518
#clearSpinner () {
519+
if (!this.#rendered) {
520+
return
521+
}
517522
// Move to the start of the line and clear the rest of the line
518523
this.#stream.cursorTo(0)
519524
this.#stream.clearLine(1)
525+
this.#rendered = false
520526
}
521527
}
522528

test/lib/utils/display.js

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ const mockDisplay = async (t, { mocks, load } = {}) => {
2929
...procLog,
3030
display,
3131
displayLoad,
32+
streams: logs.streams,
3233
...logs.logs,
3334
}
3435
}
@@ -101,6 +102,50 @@ t.test('can do progress', async (t) => {
101102
t.strictSame(outputs, ['before input', 'during input', 'after input'])
102103
})
103104

105+
t.test('progress resume does not clear output when spinner inactive', async (t) => {
106+
const { input, output, outputs, streams } = await mockDisplay(t, {
107+
load: {
108+
progress: true,
109+
},
110+
})
111+
112+
const origClearLine = streams.stderr.clearLine
113+
const origCursorTo = streams.stderr.cursorTo
114+
let clearCalls = 0
115+
let cursorCalls = 0
116+
streams.stderr.clearLine = (...args) => {
117+
clearCalls++
118+
return origClearLine.call(streams.stderr, ...args)
119+
}
120+
streams.stderr.cursorTo = (...args) => {
121+
cursorCalls++
122+
return origCursorTo.call(streams.stderr, ...args)
123+
}
124+
125+
t.teardown(() => {
126+
streams.stderr.clearLine = origClearLine
127+
streams.stderr.cursorTo = origCursorTo
128+
})
129+
130+
// ensure the spinner has rendered at least once so progress.off clears it
131+
await timers.setTimeout(300)
132+
133+
const endInput = input.start()
134+
await timers.setTimeout(0)
135+
136+
clearCalls = 0
137+
cursorCalls = 0
138+
139+
output.standard('no trailing newline')
140+
141+
endInput()
142+
await timers.setTimeout(0)
143+
144+
t.equal(clearCalls, 0, 'resume does not clear the line when spinner inactive')
145+
t.equal(cursorCalls, 0, 'resume does not reposition cursor when spinner inactive')
146+
t.equal(outputs.at(-1), 'no trailing newline', 'output remains visible')
147+
})
148+
104149
t.test('handles log throwing', async (t) => {
105150
class ThrowInspect {
106151
#crashes = 0;

0 commit comments

Comments
 (0)