diff --git a/src/multi-stage-output.tsx b/src/multi-stage-output.tsx index 22072b3..227cce1 100644 --- a/src/multi-stage-output.tsx +++ b/src/multi-stage-output.tsx @@ -97,16 +97,14 @@ export type MultiStageOutputOptions> = { } class CIMultiStageOutput> { + private readonly completedStages: Set = new Set() private data?: Partial private readonly design: RequiredDesign private readonly hasElapsedTime?: boolean private readonly hasStageTime?: boolean - private lastUpdateTime: number - private readonly messageTimeout = Number.parseInt(env.SF_CI_MESSAGE_TIMEOUT ?? '5000', 10) ?? 5000 private readonly postStagesBlock?: InfoBlock private readonly preStagesBlock?: InfoBlock private readonly seenInfo: Set = new Set() - private readonly seenStages: Set = new Set() private readonly stages: readonly string[] | string[] private readonly stageSpecificBlock?: StageInfoBlock private readonly startTime: number | undefined @@ -134,7 +132,6 @@ class CIMultiStageOutput> { this.stageSpecificBlock = stageSpecificBlock this.timerUnit = timerUnit ?? 'ms' this.data = data - this.lastUpdateTime = Date.now() if (title) ux.stdout(`───── ${title} ─────`) ux.stdout('Stages:') @@ -152,8 +149,8 @@ class CIMultiStageOutput> { public stop(stageTracker: StageTracker): void { this.update(stageTracker) ux.stdout() - this.printInfo(this.preStagesBlock, 0, true) - this.printInfo(this.postStagesBlock, 0, true) + this.maybePrintInfo(this.preStagesBlock, 0, true) + this.maybePrintInfo(this.postStagesBlock, 0, true) if (this.startTime) { const elapsedTime = Date.now() - this.startTime ux.stdout() @@ -167,7 +164,7 @@ class CIMultiStageOutput> { for (const [stage, status] of stageTracker.entries()) { // no need to re-render completed, failed, or skipped stages - if (this.seenStages.has(stage)) continue + if (this.completedStages.has(stage)) continue switch (status) { case 'pending': { @@ -177,15 +174,13 @@ class CIMultiStageOutput> { case 'current': { if (!this.startTimes.has(stage)) this.startTimes.set(stage, Date.now()) - if (Date.now() - this.lastUpdateTime < this.messageTimeout) break - this.lastUpdateTime = Date.now() - ux.stdout(`${this.design.icons.current.figure} ${stage}…`) - this.printInfo(this.preStagesBlock, 3) - this.printInfo( + this.maybeStdout(`${this.design.icons.current.figure} ${stage}…`) + this.maybePrintInfo(this.preStagesBlock, 3) + this.maybePrintInfo( this.stageSpecificBlock?.filter((info) => info.stage === stage), 3, ) - this.printInfo(this.postStagesBlock, 3) + this.maybePrintInfo(this.postStagesBlock, 3) break } @@ -196,28 +191,28 @@ class CIMultiStageOutput> { case 'async': case 'warning': case 'completed': { - this.seenStages.add(stage) + this.completedStages.add(stage) if (this.hasStageTime && status !== 'skipped') { const startTime = this.startTimes.get(stage) const elapsedTime = startTime ? Date.now() - startTime : 0 const displayTime = readableTime(elapsedTime, this.timerUnit) - ux.stdout(`${this.design.icons[status].figure} ${stage} (${displayTime})`) - this.printInfo(this.preStagesBlock, 3) - this.printInfo( + this.maybeStdout(`${this.design.icons[status].figure} ${stage} (${displayTime})`) + this.maybePrintInfo(this.preStagesBlock, 3) + this.maybePrintInfo( this.stageSpecificBlock?.filter((info) => info.stage === stage), 3, ) - this.printInfo(this.postStagesBlock, 3) + this.maybePrintInfo(this.postStagesBlock, 3) } else if (status === 'skipped') { - ux.stdout(`${this.design.icons[status].figure} ${stage} - Skipped`) + this.maybeStdout(`${this.design.icons[status].figure} ${stage} - Skipped`) } else { - ux.stdout(`${this.design.icons[status].figure} ${stage}`) - this.printInfo(this.preStagesBlock, 3) - this.printInfo( + this.maybeStdout(`${this.design.icons[status].figure} ${stage}`) + this.maybePrintInfo(this.preStagesBlock, 3) + this.maybePrintInfo( this.stageSpecificBlock?.filter((info) => info.stage === stage), 3, ) - this.printInfo(this.postStagesBlock, 3) + this.maybePrintInfo(this.postStagesBlock, 3) } break @@ -229,19 +224,23 @@ class CIMultiStageOutput> { } } - private printInfo(infoBlock: InfoBlock | StageInfoBlock | undefined, indent = 0, force = false): void { - const spaces = ' '.repeat(indent) + private maybePrintInfo(infoBlock: InfoBlock | StageInfoBlock | undefined, indent = 0, force = false): void { if (infoBlock?.length) { for (const info of infoBlock) { const formattedData = info.get ? info.get(this.data as T) : undefined if (!formattedData) continue const str = info.type === 'message' ? formattedData : `${info.label}: ${formattedData}` - if (!force && this.seenInfo.has(str)) continue - ux.stdout(`${spaces}${str}`) - this.seenInfo.add(str) + this.maybeStdout(str, indent, force) } } } + + private maybeStdout(str: string, indent = 0, force = false): void { + const spaces = ' '.repeat(indent) + if (!force && this.seenInfo.has(str)) return + ux.stdout(`${spaces}${str}`) + this.seenInfo.add(str) + } } class MultiStageOutputBase> implements Disposable {