@oclif/multi-stage-output
Advanced tools
Comparing version 0.5.9 to 0.6.0
@@ -89,3 +89,3 @@ import { getLogger } from '@oclif/core/logger'; | ||
React.createElement(Text, null, " "), | ||
React.createElement(Timer, { color: "dim", isStopped: status === 'completed', unit: timerUnit })))), | ||
React.createElement(Timer, { color: "dim", isStopped: status === 'completed' || status === 'paused', unit: timerUnit })))), | ||
compactionLevel === 0 && | ||
@@ -212,4 +212,4 @@ stageSpecificBlock && | ||
const levels = [ | ||
// 1: only show one stage at a time, with stage specific info nested under the stage | ||
(remainingHeight) => remainingHeight - stagesHeight + 1, | ||
// 1: only current stages, with stage specific info nested under the stage | ||
(remainingHeight) => remainingHeight - stagesHeight + Math.max(stageTracker.current.length, 1), | ||
// 2: hide the elapsed time | ||
@@ -237,3 +237,4 @@ (remainingHeight) => remainingHeight - 1, | ||
// If so, we need to bump the compaction level up to 7 so that the stage specific info is hidden | ||
if (cLevel === 6 && stageTracker.current && calculateWidthOfCompactStage(stageTracker.current) >= columns) { | ||
if (cLevel === 6 && | ||
stageTracker.current.map((c) => calculateWidthOfCompactStage(c)).reduce((acc, width) => acc + width, 0) >= columns) { | ||
cLevel = 7; | ||
@@ -334,3 +335,3 @@ } | ||
React.createElement(Box, { flexDirection: "column", marginLeft: 1, paddingBottom: padding }, | ||
React.createElement(ErrorBoundary, { getFallbackText: () => stageTracker.current ?? 'unknown' }, | ||
React.createElement(ErrorBoundary, { getFallbackText: () => stageTracker.current[0] ?? 'unknown' }, | ||
React.createElement(StageEntries, { compactionLevel: actualLevelOfCompaction, design: design, error: error, hasStageTime: hasStageTime, stageSpecificBlock: stageSpecific, stageTracker: stageTracker, timerUnit: timerUnit }))), | ||
@@ -337,0 +338,0 @@ postStages && postStages.length > 0 && (React.createElement(Box, { flexDirection: "column", marginLeft: 1 }, |
@@ -1,4 +0,5 @@ | ||
import { KeyValuePair, SimpleMessage, StageInfoBlock } from './components/stages.js'; | ||
import { Design } from './design.js'; | ||
import { StageStatus } from './stage-tracker.js'; | ||
import { Instance } from 'ink'; | ||
import { FormattedKeyValue, InfoBlock, KeyValuePair, SimpleMessage, StageInfoBlock, StagesProps } from './components/stages.js'; | ||
import { Design, RequiredDesign } from './design.js'; | ||
import { StageStatus, StageTracker } from './stage-tracker.js'; | ||
export type MultiStageOutputOptions<T extends Record<string, unknown>> = { | ||
@@ -54,4 +55,3 @@ /** | ||
}; | ||
export declare class MultiStageOutput<T extends Record<string, unknown>> implements Disposable { | ||
private readonly ciInstance; | ||
declare class CIMultiStageOutput<T extends Record<string, unknown>> { | ||
private data?; | ||
@@ -61,12 +61,33 @@ private readonly design; | ||
private readonly hasStageTime?; | ||
private readonly inkInstance; | ||
private lastUpdateTime; | ||
private readonly messageTimeout; | ||
private readonly postStagesBlock?; | ||
private readonly preStagesBlock?; | ||
private readonly seenStages; | ||
private readonly stages; | ||
private readonly stageSpecificBlock?; | ||
private readonly stageTracker; | ||
private stopped; | ||
private readonly timerUnit?; | ||
private readonly title?; | ||
constructor({ data, design, jsonEnabled, postStagesBlock, preStagesBlock, showElapsedTime, showStageTime, stageSpecificBlock, stages, timerUnit, title, }: MultiStageOutputOptions<T>); | ||
private readonly startTime; | ||
private readonly startTimes; | ||
private readonly timerUnit; | ||
constructor({ data, design, postStagesBlock, preStagesBlock, showElapsedTime, showStageTime, stageSpecificBlock, stages, timerUnit, title, }: MultiStageOutputOptions<T>); | ||
stop(stageTracker: StageTracker): void; | ||
update(stageTracker: StageTracker, data?: Partial<T>): void; | ||
private printInfo; | ||
} | ||
declare class MultiStageOutputBase<T extends Record<string, unknown>> implements Disposable { | ||
protected readonly ciInstance: CIMultiStageOutput<T> | undefined; | ||
protected data?: Partial<T>; | ||
protected readonly design: RequiredDesign; | ||
protected readonly hasElapsedTime?: boolean; | ||
protected readonly hasStageTime?: boolean; | ||
protected readonly inkInstance: Instance | undefined; | ||
protected readonly postStagesBlock?: InfoBlock<T>; | ||
protected readonly preStagesBlock?: InfoBlock<T>; | ||
protected readonly stages: readonly string[] | string[]; | ||
protected readonly stageSpecificBlock?: StageInfoBlock<T>; | ||
protected readonly stageTracker: StageTracker; | ||
protected stopped: boolean; | ||
protected readonly timerUnit?: 'ms' | 's'; | ||
protected readonly title?: string; | ||
constructor({ data, design, jsonEnabled, postStagesBlock, preStagesBlock, showElapsedTime, showStageTime, stageSpecificBlock, stages, timerUnit, title, }: MultiStageOutputOptions<T>, allowParallelTasks?: boolean); | ||
/** | ||
@@ -76,3 +97,29 @@ * Stop multi-stage output from running with a failed status. | ||
error(): void; | ||
protected formatKeyValuePairs(infoBlock: InfoBlock<T> | StageInfoBlock<T> | undefined): FormattedKeyValue[]; | ||
/** shared method to populate everything needed for Stages cmp */ | ||
protected generateStagesInput(opts?: { | ||
compactionLevel?: number; | ||
}): StagesProps; | ||
protected rerender(): void; | ||
/** | ||
* Stop multi-stage output from running. | ||
* | ||
* The stage currently running will be changed to the provided `finalStatus`. | ||
* | ||
* @param finalStatus - The status to set the current stage to. | ||
* @returns void | ||
*/ | ||
stop(finalStatus?: StageStatus): void; | ||
[Symbol.dispose](): void; | ||
/** | ||
* Updates the data of the component. | ||
* | ||
* @param data - The partial data object to update the component's data with. | ||
* @returns void | ||
*/ | ||
updateData(data: Partial<T>): void; | ||
} | ||
export declare class MultiStageOutput<T extends Record<string, unknown>> extends MultiStageOutputBase<T> { | ||
constructor(options: MultiStageOutputOptions<T>); | ||
/** | ||
* Go to a stage, marking any stages in between the current stage and the provided stage as completed. | ||
@@ -108,24 +155,12 @@ * | ||
skipTo(stage: string, data?: Partial<T>): void; | ||
/** | ||
* Stop multi-stage output from running. | ||
* | ||
* The stage currently running will be changed to the provided `finalStatus`. | ||
* | ||
* @param finalStatus - The status to set the current stage to. | ||
* @returns void | ||
*/ | ||
stop(finalStatus?: StageStatus): void; | ||
[Symbol.dispose](): void; | ||
/** | ||
* Updates the data of the component. | ||
* | ||
* @param data - The partial data object to update the component's data with. | ||
* @returns void | ||
*/ | ||
updateData(data: Partial<T>): void; | ||
private formatKeyValuePairs; | ||
/** shared method to populate everything needed for Stages cmp */ | ||
private generateStagesInput; | ||
private rerender; | ||
private update; | ||
} | ||
export declare class ParallelMultiStageOutput<T extends Record<string, unknown>> extends MultiStageOutputBase<T> { | ||
constructor(options: MultiStageOutputOptions<T>); | ||
pauseStage(stage: string, data?: Partial<T>): void; | ||
resumeStage(stage: string, data?: Partial<T>): void; | ||
startStage(stage: string, data?: Partial<T>): void; | ||
stopStage(stage: string, data?: Partial<T>): void; | ||
private update; | ||
} | ||
export {}; |
@@ -153,3 +153,3 @@ import { ux } from '@oclif/core/ux'; | ||
} | ||
export class MultiStageOutput { | ||
class MultiStageOutputBase { | ||
ciInstance; | ||
@@ -169,3 +169,3 @@ data; | ||
title; | ||
constructor({ data, design, jsonEnabled = false, postStagesBlock, preStagesBlock, showElapsedTime, showStageTime, stageSpecificBlock, stages, timerUnit, title, }) { | ||
constructor({ data, design, jsonEnabled = false, postStagesBlock, preStagesBlock, showElapsedTime, showStageTime, stageSpecificBlock, stages, timerUnit, title, }, allowParallelTasks) { | ||
this.data = data; | ||
@@ -180,3 +180,3 @@ this.design = constructDesignParams(design); | ||
this.timerUnit = timerUnit ?? 'ms'; | ||
this.stageTracker = new StageTracker(stages); | ||
this.stageTracker = new StageTracker(stages, { allowParallelTasks }); | ||
this.stageSpecificBlock = stageSpecificBlock; | ||
@@ -210,3 +210,86 @@ if (jsonEnabled) | ||
} | ||
formatKeyValuePairs(infoBlock) { | ||
return (infoBlock?.map((info) => { | ||
const formattedData = info.get ? info.get(this.data) : undefined; | ||
return { | ||
color: info.color, | ||
isBold: info.bold, | ||
neverCollapse: info.neverCollapse, | ||
type: info.type, | ||
value: formattedData, | ||
...(info.type === 'message' ? {} : { label: info.label }), | ||
...('stage' in info ? { stage: info.stage } : {}), | ||
}; | ||
}) ?? []); | ||
} | ||
/** shared method to populate everything needed for Stages cmp */ | ||
generateStagesInput(opts) { | ||
const { compactionLevel } = opts ?? {}; | ||
return { | ||
compactionLevel, | ||
design: this.design, | ||
hasElapsedTime: this.hasElapsedTime, | ||
hasStageTime: this.hasStageTime, | ||
postStagesBlock: this.formatKeyValuePairs(this.postStagesBlock), | ||
preStagesBlock: this.formatKeyValuePairs(this.preStagesBlock), | ||
stageSpecificBlock: this.formatKeyValuePairs(this.stageSpecificBlock), | ||
stageTracker: this.stageTracker, | ||
timerUnit: this.timerUnit, | ||
title: this.title, | ||
}; | ||
} | ||
rerender() { | ||
if (isInCi) { | ||
this.ciInstance?.update(this.stageTracker, this.data); | ||
} | ||
else { | ||
this.inkInstance?.rerender(React.createElement(Stages, { ...this.generateStagesInput() })); | ||
} | ||
} | ||
/** | ||
* Stop multi-stage output from running. | ||
* | ||
* The stage currently running will be changed to the provided `finalStatus`. | ||
* | ||
* @param finalStatus - The status to set the current stage to. | ||
* @returns void | ||
*/ | ||
stop(finalStatus = 'completed') { | ||
if (this.stopped) | ||
return; | ||
this.stopped = true; | ||
this.stageTracker.stop(this.stageTracker.current[0] ?? this.stages[0], finalStatus); | ||
if (isInCi) { | ||
this.ciInstance?.stop(this.stageTracker); | ||
return; | ||
} | ||
// The underlying components expect an Error, although they don't currently use anything on the error - they check if it exists. | ||
// Instead of refactoring the components to take a boolean, we pass in a placeholder Error, | ||
// which, gives us the flexibility in the future to pass in an actual Error if we want | ||
const error = finalStatus === 'failed' ? new Error('Error') : undefined; | ||
const stagesInput = { ...this.generateStagesInput({ compactionLevel: 0 }), ...(error ? { error } : {}) }; | ||
this.inkInstance?.rerender(React.createElement(Stages, { ...stagesInput, compactionLevel: 0 })); | ||
this.inkInstance?.unmount(); | ||
} | ||
[Symbol.dispose]() { | ||
this.inkInstance?.unmount(); | ||
} | ||
/** | ||
* Updates the data of the component. | ||
* | ||
* @param data - The partial data object to update the component's data with. | ||
* @returns void | ||
*/ | ||
updateData(data) { | ||
if (this.stopped) | ||
return; | ||
this.data = { ...this.data, ...data }; | ||
this.rerender(); | ||
} | ||
} | ||
export class MultiStageOutput extends MultiStageOutputBase { | ||
constructor(options) { | ||
super(options); | ||
} | ||
/** | ||
* Go to a stage, marking any stages in between the current stage and the provided stage as completed. | ||
@@ -229,3 +312,3 @@ * | ||
// prevent going to a previous stage | ||
if (this.stages.indexOf(stage) < this.stages.indexOf(this.stageTracker.current ?? this.stages[0])) | ||
if (this.stages.indexOf(stage) < this.stages.indexOf(this.stageTracker.current[0] ?? this.stages[0])) | ||
return; | ||
@@ -243,3 +326,3 @@ this.update(stage, 'completed', data); | ||
return; | ||
const nextStageIndex = this.stages.indexOf(this.stageTracker.current ?? this.stages[0]) + 1; | ||
const nextStageIndex = this.stages.indexOf(this.stageTracker.current[0] ?? this.stages[0]) + 1; | ||
if (nextStageIndex < this.stages.length) { | ||
@@ -267,91 +350,39 @@ this.update(this.stages[nextStageIndex], 'completed', data); | ||
// prevent going to a previous stage | ||
if (this.stages.indexOf(stage) < this.stages.indexOf(this.stageTracker.current ?? this.stages[0])) | ||
if (this.stages.indexOf(stage) < this.stages.indexOf(this.stageTracker.current[0] ?? this.stages[0])) | ||
return; | ||
this.update(stage, 'skipped', data); | ||
} | ||
/** | ||
* Stop multi-stage output from running. | ||
* | ||
* The stage currently running will be changed to the provided `finalStatus`. | ||
* | ||
* @param finalStatus - The status to set the current stage to. | ||
* @returns void | ||
*/ | ||
stop(finalStatus = 'completed') { | ||
if (this.stopped) | ||
return; | ||
this.stopped = true; | ||
this.stageTracker.refresh(this.stageTracker.current ?? this.stages[0], { | ||
finalStatus, | ||
}); | ||
if (isInCi) { | ||
this.ciInstance?.stop(this.stageTracker); | ||
return; | ||
} | ||
// The underlying components expect an Error, although they don't currently use anything on the error - they check if it exists. | ||
// Instead of refactoring the components to take a boolean, we pass in a placeholder Error, | ||
// which, gives us the flexibility in the future to pass in an actual Error if we want | ||
const error = finalStatus === 'failed' ? new Error('Error') : undefined; | ||
const stagesInput = { ...this.generateStagesInput({ compactionLevel: 0 }), ...(error ? { error } : {}) }; | ||
this.inkInstance?.rerender(React.createElement(Stages, { ...stagesInput, compactionLevel: 0 })); | ||
this.inkInstance?.unmount(); | ||
} | ||
[Symbol.dispose]() { | ||
this.inkInstance?.unmount(); | ||
} | ||
/** | ||
* Updates the data of the component. | ||
* | ||
* @param data - The partial data object to update the component's data with. | ||
* @returns void | ||
*/ | ||
updateData(data) { | ||
if (this.stopped) | ||
return; | ||
update(stage, bypassStatus, data) { | ||
this.data = { ...this.data, ...data }; | ||
this.stageTracker.refresh(stage, { bypassStatus }); | ||
this.rerender(); | ||
} | ||
formatKeyValuePairs(infoBlock) { | ||
return (infoBlock?.map((info) => { | ||
const formattedData = info.get ? info.get(this.data) : undefined; | ||
return { | ||
color: info.color, | ||
isBold: info.bold, | ||
neverCollapse: info.neverCollapse, | ||
type: info.type, | ||
value: formattedData, | ||
...(info.type === 'message' ? {} : { label: info.label }), | ||
...('stage' in info ? { stage: info.stage } : {}), | ||
}; | ||
}) ?? []); | ||
} | ||
export class ParallelMultiStageOutput extends MultiStageOutputBase { | ||
constructor(options) { | ||
super(options, true); | ||
} | ||
/** shared method to populate everything needed for Stages cmp */ | ||
generateStagesInput(opts) { | ||
const { compactionLevel } = opts ?? {}; | ||
return { | ||
compactionLevel, | ||
design: this.design, | ||
hasElapsedTime: this.hasElapsedTime, | ||
hasStageTime: this.hasStageTime, | ||
postStagesBlock: this.formatKeyValuePairs(this.postStagesBlock), | ||
preStagesBlock: this.formatKeyValuePairs(this.preStagesBlock), | ||
stageSpecificBlock: this.formatKeyValuePairs(this.stageSpecificBlock), | ||
stageTracker: this.stageTracker, | ||
timerUnit: this.timerUnit, | ||
title: this.title, | ||
}; | ||
pauseStage(stage, data) { | ||
this.update(stage, 'paused', data); | ||
} | ||
rerender() { | ||
if (isInCi) { | ||
this.ciInstance?.update(this.stageTracker, this.data); | ||
} | ||
else { | ||
this.inkInstance?.rerender(React.createElement(Stages, { ...this.generateStagesInput() })); | ||
} | ||
resumeStage(stage, data) { | ||
this.update(stage, 'current', data); | ||
} | ||
update(stage, bypassStatus, data) { | ||
startStage(stage, data) { | ||
this.update(stage, 'current', data); | ||
} | ||
stopStage(stage, data) { | ||
this.update(stage, 'completed', data); | ||
} | ||
update(stage, status, data) { | ||
if (this.stopped) | ||
return; | ||
if (!this.stages.includes(stage)) | ||
return; | ||
if (this.stageTracker.get(stage) === 'completed') | ||
return; | ||
this.data = { ...this.data, ...data }; | ||
this.stageTracker.refresh(stage, { bypassStatus }); | ||
this.stageTracker.update(stage, status); | ||
this.rerender(); | ||
} | ||
} |
export type StageStatus = 'aborted' | 'async' | 'completed' | 'current' | 'failed' | 'paused' | 'pending' | 'skipped' | 'warning'; | ||
export declare class StageTracker { | ||
private stages; | ||
current: string | undefined; | ||
current: string[]; | ||
private allowParallelTasks; | ||
private map; | ||
private markers; | ||
constructor(stages: readonly string[] | string[]); | ||
constructor(stages: readonly string[] | string[], opts?: { | ||
allowParallelTasks?: boolean; | ||
}); | ||
get size(): number; | ||
entries(): IterableIterator<[string, StageStatus]>; | ||
get(stage: string): StageStatus | undefined; | ||
getCurrent(): { | ||
stage: string; | ||
status: StageStatus; | ||
} | undefined; | ||
indexOf(stage: string): number; | ||
@@ -21,4 +20,6 @@ refresh(nextStage: string, opts?: { | ||
set(stage: string, status: StageStatus): void; | ||
stop(currentStage: string, finalStatus: StageStatus): void; | ||
update(stage: string, status: StageStatus): void; | ||
values(): IterableIterator<StageStatus>; | ||
private stopMarker; | ||
private stopStage; | ||
} |
import { Performance } from '@oclif/core/performance'; | ||
export class StageTracker { | ||
stages; | ||
current; | ||
current = []; | ||
allowParallelTasks; | ||
map = new Map(); | ||
markers = new Map(); | ||
constructor(stages) { | ||
constructor(stages, opts) { | ||
this.stages = stages; | ||
this.map = new Map(stages.map((stage) => [stage, 'pending'])); | ||
this.allowParallelTasks = opts?.allowParallelTasks ?? false; | ||
} | ||
@@ -20,10 +22,2 @@ get size() { | ||
} | ||
getCurrent() { | ||
if (this.current) { | ||
return { | ||
stage: this.current, | ||
status: this.map.get(this.current), | ||
}; | ||
} | ||
} | ||
indexOf(stage) { | ||
@@ -41,4 +35,3 @@ return this.stages.indexOf(stage); | ||
if (nextStage === stage && opts?.finalStatus) { | ||
this.set(stage, opts.finalStatus); | ||
this.stopMarker(stage); | ||
this.stopStage(stage, opts.finalStatus); | ||
continue; | ||
@@ -62,4 +55,3 @@ } | ||
if (stages.indexOf(nextStage) > stages.indexOf(stage)) { | ||
this.set(stage, 'completed'); | ||
this.stopMarker(stage); | ||
this.stopStage(stage, 'completed'); | ||
continue; | ||
@@ -73,10 +65,36 @@ } | ||
if (status === 'current') { | ||
this.current = stage; | ||
if (!this.current.includes(stage)) { | ||
this.current.push(stage); | ||
} | ||
} | ||
else { | ||
this.current = this.current.filter((s) => s !== stage); | ||
} | ||
this.map.set(stage, status); | ||
} | ||
stop(currentStage, finalStatus) { | ||
if (this.allowParallelTasks) { | ||
for (const [stage, status] of this.entries()) { | ||
if (status === 'current') { | ||
this.stopStage(stage, finalStatus); | ||
} | ||
} | ||
} | ||
else { | ||
this.refresh(currentStage, { finalStatus }); | ||
} | ||
} | ||
update(stage, status) { | ||
if (status === 'completed' || status === 'failed' || status === 'aborted') { | ||
this.stopStage(stage, status); | ||
} | ||
else { | ||
this.set(stage, status); | ||
} | ||
} | ||
values() { | ||
return this.map.values(); | ||
} | ||
stopMarker(stage) { | ||
stopStage(stage, status) { | ||
this.set(stage, status); | ||
const marker = this.markers.get(stage); | ||
@@ -83,0 +101,0 @@ if (marker && !marker.stopped) { |
{ | ||
"name": "@oclif/multi-stage-output", | ||
"description": "Terminal output for oclif commands with multiple stages", | ||
"version": "0.5.9", | ||
"version": "0.6.0", | ||
"author": "Salesforce", | ||
@@ -6,0 +6,0 @@ "bugs": "https://github.com/oclif/multi-stage-output/issues", |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
70117
1499