Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

@oclif/multi-stage-output

Package Overview
Dependencies
Maintainers
0
Versions
54
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@oclif/multi-stage-output - npm Package Compare versions

Comparing version 0.5.9 to 0.6.0

11

lib/components/stages.js

@@ -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",

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc