Skip to content

Commit b380d15

Browse files
committed
feat: add deduping to notices unless in verbose+ mode
1 parent 0c0faae commit b380d15

File tree

2 files changed

+82
-0
lines changed

2 files changed

+82
-0
lines changed

lib/utils/display.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,8 @@ class Display {
177177
#stdout
178178
#stderr
179179

180+
#seenNotices = new Set()
181+
180182
constructor ({ stdout, stderr }) {
181183
this.#stdout = setBlocking(stdout)
182184
this.#stderr = setBlocking(stderr)
@@ -195,6 +197,7 @@ class Display {
195197
this.#outputState.buffer.length = 0
196198
process.off('input', this.#inputHandler)
197199
this.#progress.off()
200+
this.#seenNotices.clear()
198201
}
199202

200203
get chalk () {
@@ -404,6 +407,14 @@ class Display {
404407
// notice logs typically come from `npm-notice` headers in responses. Some of them have 2fa login links so we skip redaction.
405408
if (level === 'notice') {
406409
writeOpts.redact = false
410+
// Deduplicate notices within a single command execution, unless in verbose mode
411+
if (this.#levelIndex < LEVEL_OPTIONS.verbose.index) {
412+
const noticeKey = JSON.stringify([title, ...args])
413+
if (this.#seenNotices.has(noticeKey)) {
414+
return
415+
}
416+
this.#seenNotices.add(noticeKey)
417+
}
407418
}
408419
this.#write(this.#stderr, writeOpts, ...args)
409420
}

test/lib/utils/display.js

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,77 @@ t.test('incorrect levels', async t => {
132132
t.strictSame(outputs, [], 'output is ignored')
133133
})
134134

135+
t.test('notice deduplication', async t => {
136+
const { log, logs, display } = await mockDisplay(t, {
137+
load: { loglevel: 'notice' },
138+
})
139+
140+
// Log the same notice multiple times - should be deduplicated
141+
log.notice('', 'This is a duplicate notice')
142+
log.notice('', 'This is a duplicate notice')
143+
log.notice('', 'This is a duplicate notice')
144+
145+
// Should only appear once in logs
146+
t.equal(logs.notice.length, 1, 'notice appears only once')
147+
t.strictSame(logs.notice, ['This is a duplicate notice'])
148+
149+
// Different notice should appear
150+
log.notice('', 'This is a different notice')
151+
t.equal(logs.notice.length, 2, 'different notice is shown')
152+
t.strictSame(logs.notice, [
153+
'This is a duplicate notice',
154+
'This is a different notice',
155+
])
156+
157+
// Same notice with different title should appear
158+
log.notice('title', 'This is a duplicate notice')
159+
t.equal(logs.notice.length, 3, 'notice with different title is shown')
160+
t.match(logs.notice[2], /title.*This is a duplicate notice/)
161+
162+
// Log the first notice again - should still be deduplicated
163+
log.notice('', 'This is a duplicate notice')
164+
t.equal(logs.notice.length, 3, 'original notice still deduplicated')
165+
166+
// Call off() to simulate end of command and clear deduplication
167+
display.off()
168+
169+
// Create a new display instance to simulate a new command
170+
const logsResult = mockLogs()
171+
const Display = tmock(t, '{LIB}/utils/display')
172+
const display2 = new Display(logsResult.streams)
173+
await display2.load({
174+
loglevel: 'silly',
175+
stderrColor: false,
176+
stdoutColor: false,
177+
heading: 'npm',
178+
})
179+
t.teardown(() => display2.off())
180+
181+
// Log the same notice again - should appear since deduplication was cleared
182+
log.notice('', 'This is a duplicate notice')
183+
t.equal(logsResult.logs.logs.notice.length, 1, 'notice appears after display.off() clears deduplication')
184+
t.strictSame(logsResult.logs.logs.notice, ['This is a duplicate notice'])
185+
})
186+
187+
t.test('notice deduplication does not apply in verbose mode', async t => {
188+
const { log, logs } = await mockDisplay(t, {
189+
load: { loglevel: 'verbose' },
190+
})
191+
192+
// Log the same notice multiple times in verbose mode
193+
log.notice('', 'Repeated notice')
194+
log.notice('', 'Repeated notice')
195+
log.notice('', 'Repeated notice')
196+
197+
// Should appear all three times in verbose mode
198+
t.equal(logs.notice.length, 3, 'all notices appear in verbose mode')
199+
t.strictSame(logs.notice, [
200+
'Repeated notice',
201+
'Repeated notice',
202+
'Repeated notice',
203+
])
204+
})
205+
135206
t.test('Display.clean', async (t) => {
136207
const { output, outputs, clearOutput } = await mockDisplay(t)
137208

0 commit comments

Comments
 (0)