@oclif/multi-stage-output 0.1.4 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/components/divider.d.ts +3 -3
- package/lib/components/divider.js +2 -2
- package/lib/components/icon.d.ts +11 -0
- package/lib/components/icon.js +11 -0
- package/lib/components/spinner.d.ts +7 -7
- package/lib/components/spinner.js +4 -4
- package/lib/components/stages.d.ts +65 -0
- package/lib/components/stages.js +86 -0
- package/lib/design.d.ts +76 -0
- package/lib/design.js +63 -0
- package/lib/index.d.ts +1 -1
- package/lib/index.js +1 -1
- package/lib/multi-stage-output.d.ts +74 -0
- package/lib/{components/multi-stage-output.js → multi-stage-output.js} +36 -91
- package/lib/stage-tracker.d.ts +6 -2
- package/lib/stage-tracker.js +17 -7
- package/package.json +1 -1
- package/lib/components/multi-stage-output.d.ts +0 -130
- package/lib/design-elements.d.ts +0 -9
- package/lib/design-elements.js +0 -12
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
export declare function Divider({ dividerChar, dividerColor, padding,
|
|
2
|
+
export declare function Divider({ dividerChar, dividerColor, padding, textColor, textPadding: titlePadding, title, width, }: {
|
|
3
3
|
readonly title?: string;
|
|
4
4
|
readonly width?: number | 'full';
|
|
5
5
|
readonly padding?: number;
|
|
6
|
-
readonly
|
|
7
|
-
readonly
|
|
6
|
+
readonly textColor?: string;
|
|
7
|
+
readonly textPadding?: number;
|
|
8
8
|
readonly dividerChar?: string;
|
|
9
9
|
readonly dividerColor?: string;
|
|
10
10
|
}): React.ReactNode;
|
|
@@ -3,7 +3,7 @@ import React from 'react';
|
|
|
3
3
|
const getSideDividerWidth = (width, titleWidth) => (width - titleWidth) / 2;
|
|
4
4
|
const getNumberOfCharsPerWidth = (char, width) => width / char.length;
|
|
5
5
|
const PAD = ' ';
|
|
6
|
-
export function Divider({ dividerChar = '─', dividerColor = 'dim', padding = 1,
|
|
6
|
+
export function Divider({ dividerChar = '─', dividerColor = 'dim', padding = 1, textColor = 'white', textPadding: titlePadding = 1, title = '', width = 50, }) {
|
|
7
7
|
const titleString = title ? `${PAD.repeat(titlePadding) + title + PAD.repeat(titlePadding)}` : '';
|
|
8
8
|
const titleWidth = titleString.length;
|
|
9
9
|
const terminalWidth = process.stdout.columns ?? 80;
|
|
@@ -16,7 +16,7 @@ export function Divider({ dividerChar = '─', dividerColor = 'dim', padding = 1
|
|
|
16
16
|
React.createElement(Text, null,
|
|
17
17
|
paddingString,
|
|
18
18
|
React.createElement(Text, { color: dividerColor }, dividerSideString),
|
|
19
|
-
React.createElement(Text, { color:
|
|
19
|
+
React.createElement(Text, { color: textColor }, titleString),
|
|
20
20
|
React.createElement(Text, { color: dividerColor }, dividerSideString),
|
|
21
21
|
paddingString)));
|
|
22
22
|
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
export type IconProps = {
|
|
3
|
+
figure?: string;
|
|
4
|
+
paddingLeft?: number;
|
|
5
|
+
paddingRight?: number;
|
|
6
|
+
color?: string | false;
|
|
7
|
+
};
|
|
8
|
+
export declare function Icon({ children, icon, }: {
|
|
9
|
+
readonly children?: React.ReactNode;
|
|
10
|
+
readonly icon: IconProps;
|
|
11
|
+
}): React.ReactNode;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { Box, Text } from 'ink';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
export function Icon({ children, icon, }) {
|
|
4
|
+
if (!icon)
|
|
5
|
+
return false;
|
|
6
|
+
return (React.createElement(Box, null,
|
|
7
|
+
React.createElement(Box, { paddingLeft: icon.paddingLeft, paddingRight: icon.paddingRight },
|
|
8
|
+
icon.color && React.createElement(Text, { color: icon.color }, icon.figure),
|
|
9
|
+
!icon.color && React.createElement(Text, null, icon.figure)),
|
|
10
|
+
React.createElement(Box, null, children)));
|
|
11
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { type SpinnerName } from 'cli-spinners';
|
|
2
2
|
import React from 'react';
|
|
3
|
-
|
|
3
|
+
import { IconProps } from './icon.js';
|
|
4
|
+
type UseSpinnerProps = {
|
|
4
5
|
/**
|
|
5
6
|
* Type of a spinner.
|
|
6
7
|
* See [cli-spinners](https://github.com/sindresorhus/cli-spinners) for available spinners.
|
|
@@ -9,11 +10,7 @@ export type UseSpinnerProps = {
|
|
|
9
10
|
*/
|
|
10
11
|
readonly type?: SpinnerName;
|
|
11
12
|
};
|
|
12
|
-
|
|
13
|
-
frame: string;
|
|
14
|
-
};
|
|
15
|
-
export declare function useSpinner({ type }: UseSpinnerProps): UseSpinnerResult;
|
|
16
|
-
export type SpinnerProps = UseSpinnerProps & {
|
|
13
|
+
type SpinnerProps = UseSpinnerProps & {
|
|
17
14
|
/**
|
|
18
15
|
* Label to show near the spinner.
|
|
19
16
|
*/
|
|
@@ -22,10 +19,13 @@ export type SpinnerProps = UseSpinnerProps & {
|
|
|
22
19
|
readonly labelPosition?: 'left' | 'right';
|
|
23
20
|
};
|
|
24
21
|
export declare function Spinner({ isBold, label, labelPosition, type }: SpinnerProps): React.ReactElement;
|
|
25
|
-
export declare function SpinnerOrError({ error, labelPosition, ...props }: SpinnerProps & {
|
|
22
|
+
export declare function SpinnerOrError({ error, failedIcon, labelPosition, ...props }: SpinnerProps & {
|
|
26
23
|
readonly error?: Error;
|
|
24
|
+
readonly failedIcon: IconProps;
|
|
27
25
|
}): React.ReactElement;
|
|
28
26
|
export declare function SpinnerOrErrorOrChildren({ children, error, ...props }: SpinnerProps & {
|
|
29
27
|
readonly children?: React.ReactNode;
|
|
30
28
|
readonly error?: Error;
|
|
29
|
+
readonly failedIcon: IconProps;
|
|
31
30
|
}): React.ReactElement;
|
|
31
|
+
export {};
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import spinners from 'cli-spinners';
|
|
2
2
|
import { Box, Text } from 'ink';
|
|
3
3
|
import React, { useEffect, useState } from 'react';
|
|
4
|
-
import {
|
|
5
|
-
|
|
4
|
+
import { Icon } from './icon.js';
|
|
5
|
+
function useSpinner({ type = 'dots' }) {
|
|
6
6
|
const [frame, setFrame] = useState(0);
|
|
7
7
|
const spinner = spinners[type];
|
|
8
8
|
useEffect(() => {
|
|
@@ -31,13 +31,13 @@ export function Spinner({ isBold, label, labelPosition = 'right', type }) {
|
|
|
31
31
|
" ",
|
|
32
32
|
label)));
|
|
33
33
|
}
|
|
34
|
-
export function SpinnerOrError({ error, labelPosition = 'right', ...props }) {
|
|
34
|
+
export function SpinnerOrError({ error, failedIcon, labelPosition = 'right', ...props }) {
|
|
35
35
|
if (error) {
|
|
36
36
|
return (React.createElement(Box, null,
|
|
37
37
|
props.label && labelPosition === 'left' && React.createElement(Text, null,
|
|
38
38
|
props.label,
|
|
39
39
|
" "),
|
|
40
|
-
React.createElement(
|
|
40
|
+
React.createElement(Icon, { icon: failedIcon }),
|
|
41
41
|
props.label && labelPosition === 'right' && React.createElement(Text, null,
|
|
42
42
|
" ",
|
|
43
43
|
props.label)));
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { RequiredDesign } from '../design.js';
|
|
3
|
+
import { StageTracker } from '../stage-tracker.js';
|
|
4
|
+
type Info<T extends Record<string, unknown>> = {
|
|
5
|
+
/**
|
|
6
|
+
* key-value: Display a key-value pair with a spinner.
|
|
7
|
+
* static-key-value: Display a key-value pair without a spinner.
|
|
8
|
+
* message: Display a message.
|
|
9
|
+
*/
|
|
10
|
+
type: 'dynamic-key-value' | 'static-key-value' | 'message';
|
|
11
|
+
/**
|
|
12
|
+
* Color of the value.
|
|
13
|
+
*/
|
|
14
|
+
color?: string;
|
|
15
|
+
/**
|
|
16
|
+
* Get the value to display. Takes the data property on the MultiStageComponent as an argument.
|
|
17
|
+
* Useful if you want to apply some logic (like rendering a link) to the data before displaying it.
|
|
18
|
+
*
|
|
19
|
+
* @param data The data property on the MultiStageComponent.
|
|
20
|
+
* @returns {string | undefined}
|
|
21
|
+
*/
|
|
22
|
+
get: (data?: T) => string | undefined;
|
|
23
|
+
/**
|
|
24
|
+
* Whether the value should be bold.
|
|
25
|
+
*/
|
|
26
|
+
bold?: boolean;
|
|
27
|
+
};
|
|
28
|
+
export type KeyValuePair<T extends Record<string, unknown>> = Info<T> & {
|
|
29
|
+
/**
|
|
30
|
+
* Label of the key-value pair.
|
|
31
|
+
*/
|
|
32
|
+
label: string;
|
|
33
|
+
type: 'dynamic-key-value' | 'static-key-value';
|
|
34
|
+
};
|
|
35
|
+
export type SimpleMessage<T extends Record<string, unknown>> = Info<T> & {
|
|
36
|
+
type: 'message';
|
|
37
|
+
};
|
|
38
|
+
export type InfoBlock<T extends Record<string, unknown>> = Array<KeyValuePair<T> | SimpleMessage<T>>;
|
|
39
|
+
export type StageInfoBlock<T extends Record<string, unknown>> = Array<(KeyValuePair<T> & {
|
|
40
|
+
stage: string;
|
|
41
|
+
}) | (SimpleMessage<T> & {
|
|
42
|
+
stage: string;
|
|
43
|
+
})>;
|
|
44
|
+
export type FormattedKeyValue = {
|
|
45
|
+
readonly color?: string;
|
|
46
|
+
readonly isBold?: boolean;
|
|
47
|
+
readonly label?: string;
|
|
48
|
+
readonly value: string | undefined;
|
|
49
|
+
readonly stage?: string;
|
|
50
|
+
readonly type: 'dynamic-key-value' | 'static-key-value' | 'message';
|
|
51
|
+
};
|
|
52
|
+
export type StagesProps = {
|
|
53
|
+
readonly design?: RequiredDesign;
|
|
54
|
+
readonly error?: Error | undefined;
|
|
55
|
+
readonly postStagesBlock?: FormattedKeyValue[];
|
|
56
|
+
readonly preStagesBlock?: FormattedKeyValue[];
|
|
57
|
+
readonly stageSpecificBlock?: FormattedKeyValue[];
|
|
58
|
+
readonly title?: string;
|
|
59
|
+
readonly hasElapsedTime?: boolean;
|
|
60
|
+
readonly hasStageTime?: boolean;
|
|
61
|
+
readonly timerUnit?: 'ms' | 's';
|
|
62
|
+
readonly stageTracker: StageTracker;
|
|
63
|
+
};
|
|
64
|
+
export declare function Stages({ design, error, hasElapsedTime, hasStageTime, postStagesBlock, preStagesBlock, stageSpecificBlock, stageTracker, timerUnit, title, }: StagesProps): React.ReactNode;
|
|
65
|
+
export {};
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { capitalCase } from 'change-case';
|
|
2
|
+
import { Box, Text } from 'ink';
|
|
3
|
+
import React from 'react';
|
|
4
|
+
import { constructDesignParams } from '../design.js';
|
|
5
|
+
import { Divider } from './divider.js';
|
|
6
|
+
import { Icon } from './icon.js';
|
|
7
|
+
import { SpinnerOrError, SpinnerOrErrorOrChildren } from './spinner.js';
|
|
8
|
+
import { Timer } from './timer.js';
|
|
9
|
+
function StaticKeyValue({ color, isBold, label, value }) {
|
|
10
|
+
if (!value)
|
|
11
|
+
return false;
|
|
12
|
+
return (React.createElement(Box, { key: label },
|
|
13
|
+
React.createElement(Text, { bold: isBold },
|
|
14
|
+
label,
|
|
15
|
+
": "),
|
|
16
|
+
React.createElement(Text, { color: color }, value)));
|
|
17
|
+
}
|
|
18
|
+
function SimpleMessage({ color, isBold, value }) {
|
|
19
|
+
if (!value)
|
|
20
|
+
return false;
|
|
21
|
+
return (React.createElement(Text, { bold: isBold, color: color }, value));
|
|
22
|
+
}
|
|
23
|
+
function StageInfos({ design, error, keyValuePairs, stage, }) {
|
|
24
|
+
return keyValuePairs
|
|
25
|
+
.filter((kv) => kv.stage === stage)
|
|
26
|
+
.map((kv) => {
|
|
27
|
+
const key = `${kv.label}-${kv.value}`;
|
|
28
|
+
if (kv.type === 'message') {
|
|
29
|
+
return (React.createElement(Box, { key: key, flexDirection: "row" },
|
|
30
|
+
React.createElement(Icon, { icon: design.icons.info }),
|
|
31
|
+
React.createElement(SimpleMessage, { ...kv })));
|
|
32
|
+
}
|
|
33
|
+
if (kv.type === 'dynamic-key-value') {
|
|
34
|
+
return (React.createElement(Box, { key: key },
|
|
35
|
+
React.createElement(Icon, { icon: design.icons.info }),
|
|
36
|
+
React.createElement(SpinnerOrErrorOrChildren, { error: error, label: `${kv.label}:`, labelPosition: "left", type: design.spinners.info, failedIcon: design.icons.failed }, kv.value && (React.createElement(Text, { bold: kv.isBold, color: kv.color }, kv.value)))));
|
|
37
|
+
}
|
|
38
|
+
if (kv.type === 'static-key-value') {
|
|
39
|
+
return (React.createElement(Box, { key: key },
|
|
40
|
+
React.createElement(Icon, { icon: design.icons.info }),
|
|
41
|
+
React.createElement(StaticKeyValue, { key: key, ...kv })));
|
|
42
|
+
}
|
|
43
|
+
return false;
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
function Infos({ design, error, keyValuePairs, }) {
|
|
47
|
+
return keyValuePairs.map((kv) => {
|
|
48
|
+
const key = `${kv.label}-${kv.value}`;
|
|
49
|
+
if (kv.type === 'message') {
|
|
50
|
+
return React.createElement(SimpleMessage, { key: key, ...kv });
|
|
51
|
+
}
|
|
52
|
+
if (kv.type === 'dynamic-key-value') {
|
|
53
|
+
return (React.createElement(SpinnerOrErrorOrChildren, { key: key, error: error, label: `${kv.label}:`, labelPosition: "left", type: design.spinners.info, failedIcon: design.icons.failed }, kv.value && (React.createElement(Text, { bold: kv.isBold, color: kv.color }, kv.value))));
|
|
54
|
+
}
|
|
55
|
+
if (kv.type === 'static-key-value') {
|
|
56
|
+
return React.createElement(StaticKeyValue, { key: key, ...kv });
|
|
57
|
+
}
|
|
58
|
+
return false;
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
export function Stages({ design = constructDesignParams(), error, hasElapsedTime = true, hasStageTime = true, postStagesBlock, preStagesBlock, stageSpecificBlock, stageTracker, timerUnit = 'ms', title, }) {
|
|
62
|
+
return (React.createElement(Box, { flexDirection: "column", paddingTop: 1, paddingBottom: 1 },
|
|
63
|
+
title && React.createElement(Divider, { title: title, ...design.title }),
|
|
64
|
+
preStagesBlock && preStagesBlock.length > 0 && (React.createElement(Box, { flexDirection: "column", marginLeft: 1, paddingTop: 1 },
|
|
65
|
+
React.createElement(Infos, { design: design, error: error, keyValuePairs: preStagesBlock }))),
|
|
66
|
+
React.createElement(Box, { flexDirection: "column", marginLeft: 1, paddingTop: 1 }, [...stageTracker.entries()].map(([stage, status]) => (React.createElement(Box, { key: stage, flexDirection: "column" },
|
|
67
|
+
React.createElement(Box, null,
|
|
68
|
+
(status === 'current' || status === 'failed') && (React.createElement(SpinnerOrError, { error: error, label: capitalCase(stage), type: design.spinners.stage, failedIcon: design.icons.failed })),
|
|
69
|
+
status === 'skipped' && (React.createElement(Icon, { icon: design.icons.skipped },
|
|
70
|
+
React.createElement(Text, { color: "dim" },
|
|
71
|
+
capitalCase(stage),
|
|
72
|
+
" - Skipped"))),
|
|
73
|
+
status === 'completed' && (React.createElement(Icon, { icon: design.icons.completed },
|
|
74
|
+
React.createElement(Text, null, capitalCase(stage)))),
|
|
75
|
+
status === 'pending' && (React.createElement(Icon, { icon: design.icons.pending },
|
|
76
|
+
React.createElement(Text, null, capitalCase(stage)))),
|
|
77
|
+
status !== 'pending' && status !== 'skipped' && hasStageTime && (React.createElement(Box, null,
|
|
78
|
+
React.createElement(Text, null, " "),
|
|
79
|
+
React.createElement(Timer, { color: "dim", isStopped: status === 'completed', unit: timerUnit })))),
|
|
80
|
+
stageSpecificBlock && stageSpecificBlock.length > 0 && status !== 'pending' && status !== 'skipped' && (React.createElement(StageInfos, { design: design, error: error, keyValuePairs: stageSpecificBlock, stage: stage })))))),
|
|
81
|
+
postStagesBlock && postStagesBlock.length > 0 && (React.createElement(Box, { flexDirection: "column", marginLeft: 1, paddingTop: 1 },
|
|
82
|
+
React.createElement(Infos, { design: design, error: error, keyValuePairs: postStagesBlock }))),
|
|
83
|
+
hasElapsedTime && (React.createElement(Box, { marginLeft: 1 },
|
|
84
|
+
React.createElement(Text, null, "Elapsed Time: "),
|
|
85
|
+
React.createElement(Timer, { unit: timerUnit })))));
|
|
86
|
+
}
|
package/lib/design.d.ts
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { type SpinnerName } from 'cli-spinners';
|
|
2
|
+
import { IconProps } from './components/icon.js';
|
|
3
|
+
export type Design = {
|
|
4
|
+
icons?: {
|
|
5
|
+
/**
|
|
6
|
+
* Icon to display for a completed stage. Defaults to '✔'
|
|
7
|
+
*/
|
|
8
|
+
completed?: IconProps;
|
|
9
|
+
/**
|
|
10
|
+
* Icon to display for the current stage in CI environments. Defaults to '▶'
|
|
11
|
+
*
|
|
12
|
+
* Non-CI environments will display the spinner instead.
|
|
13
|
+
*/
|
|
14
|
+
current?: IconProps;
|
|
15
|
+
/**
|
|
16
|
+
* Icon to display for a failed stage. Defaults to '✘'
|
|
17
|
+
*/
|
|
18
|
+
failed?: IconProps;
|
|
19
|
+
/**
|
|
20
|
+
* Icon to display for a pending stage. Defaults to '◼'
|
|
21
|
+
*/
|
|
22
|
+
pending?: IconProps;
|
|
23
|
+
/**
|
|
24
|
+
* Icon to display for a skipped stage. Defaults to '◯'
|
|
25
|
+
*/
|
|
26
|
+
skipped?: IconProps;
|
|
27
|
+
/**
|
|
28
|
+
* Icon to display for stage specific information. Defaults to '▸'
|
|
29
|
+
*/
|
|
30
|
+
info?: IconProps;
|
|
31
|
+
};
|
|
32
|
+
title?: {
|
|
33
|
+
/**
|
|
34
|
+
* Character to use as a divider for the title. Defaults to '─'
|
|
35
|
+
*/
|
|
36
|
+
dividerChar?: string;
|
|
37
|
+
/**
|
|
38
|
+
* Color of the divider. Defaults to 'dim'
|
|
39
|
+
*/
|
|
40
|
+
dividerColor?: string;
|
|
41
|
+
/**
|
|
42
|
+
* Padding to add above and below the title. Defaults to 1
|
|
43
|
+
*/
|
|
44
|
+
padding?: number;
|
|
45
|
+
/**
|
|
46
|
+
* Color of the title. Defaults to 'white'
|
|
47
|
+
*/
|
|
48
|
+
textColor?: string;
|
|
49
|
+
/**
|
|
50
|
+
* Padding to add to the left and right of the title. Defaults to 1
|
|
51
|
+
*/
|
|
52
|
+
textPadding?: number;
|
|
53
|
+
/**
|
|
54
|
+
* Width of the title. Defaults to 50
|
|
55
|
+
*
|
|
56
|
+
* The `full` value will use the terminal width minus the title padding.
|
|
57
|
+
*/
|
|
58
|
+
width?: number | 'full';
|
|
59
|
+
};
|
|
60
|
+
spinners?: {
|
|
61
|
+
/**
|
|
62
|
+
* Spinner to display for dynamic info blocks. Defaults to 'line' on Windows and 'arc' on other platforms
|
|
63
|
+
*/
|
|
64
|
+
info?: SpinnerName;
|
|
65
|
+
/**
|
|
66
|
+
* Spinner to display for stages. Defaults to 'line' on Windows and 'dots2' on other platforms
|
|
67
|
+
*/
|
|
68
|
+
stage?: SpinnerName;
|
|
69
|
+
};
|
|
70
|
+
};
|
|
71
|
+
type RecursiveRequired<T> = Required<{
|
|
72
|
+
[P in keyof T]: T[P] extends object | undefined ? RecursiveRequired<Required<T[P]>> : T[P];
|
|
73
|
+
}>;
|
|
74
|
+
export type RequiredDesign = RecursiveRequired<Design>;
|
|
75
|
+
export declare function constructDesignParams(design?: Design): RequiredDesign;
|
|
76
|
+
export {};
|
package/lib/design.js
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import figures from 'figures';
|
|
2
|
+
export function constructDesignParams(design) {
|
|
3
|
+
return {
|
|
4
|
+
icons: {
|
|
5
|
+
completed: {
|
|
6
|
+
color: 'green',
|
|
7
|
+
figure: figures.tick,
|
|
8
|
+
paddingLeft: 0,
|
|
9
|
+
paddingRight: 0,
|
|
10
|
+
...design?.icons?.completed,
|
|
11
|
+
},
|
|
12
|
+
current: {
|
|
13
|
+
color: 'yellow',
|
|
14
|
+
figure: figures.play,
|
|
15
|
+
paddingLeft: 0,
|
|
16
|
+
paddingRight: 0,
|
|
17
|
+
...design?.icons?.current,
|
|
18
|
+
},
|
|
19
|
+
failed: {
|
|
20
|
+
color: 'red',
|
|
21
|
+
figure: figures.cross,
|
|
22
|
+
paddingLeft: 0,
|
|
23
|
+
paddingRight: 0,
|
|
24
|
+
...design?.icons?.failed,
|
|
25
|
+
},
|
|
26
|
+
info: {
|
|
27
|
+
color: false,
|
|
28
|
+
figure: figures.triangleRightSmall,
|
|
29
|
+
paddingLeft: 2,
|
|
30
|
+
paddingRight: 1,
|
|
31
|
+
...design?.icons?.info,
|
|
32
|
+
},
|
|
33
|
+
pending: {
|
|
34
|
+
color: 'dim',
|
|
35
|
+
figure: figures.squareSmallFilled,
|
|
36
|
+
paddingLeft: 0,
|
|
37
|
+
paddingRight: 0,
|
|
38
|
+
...design?.icons?.pending,
|
|
39
|
+
},
|
|
40
|
+
skipped: {
|
|
41
|
+
color: 'dim',
|
|
42
|
+
figure: figures.circle,
|
|
43
|
+
paddingLeft: 0,
|
|
44
|
+
paddingRight: 1,
|
|
45
|
+
...design?.icons?.skipped,
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
spinners: {
|
|
49
|
+
info: process.platform === 'win32' ? 'line' : 'arc',
|
|
50
|
+
stage: process.platform === 'win32' ? 'line' : 'dots2',
|
|
51
|
+
...design?.spinners,
|
|
52
|
+
},
|
|
53
|
+
title: {
|
|
54
|
+
dividerChar: '─',
|
|
55
|
+
dividerColor: 'dim',
|
|
56
|
+
padding: 1,
|
|
57
|
+
textColor: 'white',
|
|
58
|
+
textPadding: 1,
|
|
59
|
+
width: 50,
|
|
60
|
+
...design?.title,
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
}
|
package/lib/index.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { MultiStageOutput } from './
|
|
1
|
+
export { MultiStageOutput, MultiStageOutputOptions } from './multi-stage-output.js';
|
package/lib/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { MultiStageOutput } from './
|
|
1
|
+
export { MultiStageOutput } from './multi-stage-output.js';
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { KeyValuePair, SimpleMessage, StageInfoBlock } from './components/stages.js';
|
|
2
|
+
import { Design } from './design.js';
|
|
3
|
+
export type MultiStageOutputOptions<T extends Record<string, unknown>> = {
|
|
4
|
+
/**
|
|
5
|
+
* Stages to render.
|
|
6
|
+
*/
|
|
7
|
+
readonly stages: readonly string[] | string[];
|
|
8
|
+
/**
|
|
9
|
+
* Title to display at the top of the stages component.
|
|
10
|
+
*/
|
|
11
|
+
readonly title?: string;
|
|
12
|
+
/**
|
|
13
|
+
* Information to display at the bottom of the stages component.
|
|
14
|
+
*/
|
|
15
|
+
readonly postStagesBlock?: Array<KeyValuePair<T> | SimpleMessage<T>>;
|
|
16
|
+
/**
|
|
17
|
+
* Information to display below the title but above the stages.
|
|
18
|
+
*/
|
|
19
|
+
readonly preStagesBlock?: Array<KeyValuePair<T> | SimpleMessage<T>>;
|
|
20
|
+
/**
|
|
21
|
+
* Whether to show the total elapsed time. Defaults to true
|
|
22
|
+
*/
|
|
23
|
+
readonly showElapsedTime?: boolean;
|
|
24
|
+
/**
|
|
25
|
+
* Whether to show the time spent on each stage. Defaults to true
|
|
26
|
+
*/
|
|
27
|
+
readonly showStageTime?: boolean;
|
|
28
|
+
/**
|
|
29
|
+
* Whether to show the title. Defaults to true
|
|
30
|
+
*/
|
|
31
|
+
readonly showTitle?: boolean;
|
|
32
|
+
/**
|
|
33
|
+
* Information to display for a specific stage. Each object must have a stage property set.
|
|
34
|
+
*/
|
|
35
|
+
readonly stageSpecificBlock?: StageInfoBlock<T>;
|
|
36
|
+
/**
|
|
37
|
+
* The unit to use for the timer. Defaults to 'ms'
|
|
38
|
+
*/
|
|
39
|
+
readonly timerUnit?: 'ms' | 's';
|
|
40
|
+
/**
|
|
41
|
+
* Data to display in the stages component. This data will be passed to the get function in the info object.
|
|
42
|
+
*/
|
|
43
|
+
readonly data?: Partial<T>;
|
|
44
|
+
/**
|
|
45
|
+
* Design options to customize the output.
|
|
46
|
+
*/
|
|
47
|
+
readonly design?: Design;
|
|
48
|
+
};
|
|
49
|
+
export declare class MultiStageOutput<T extends Record<string, unknown>> implements Disposable {
|
|
50
|
+
private ciInstance;
|
|
51
|
+
private data?;
|
|
52
|
+
private readonly design;
|
|
53
|
+
private readonly hasElapsedTime?;
|
|
54
|
+
private readonly hasStageTime?;
|
|
55
|
+
private inkInstance;
|
|
56
|
+
private readonly postStagesBlock?;
|
|
57
|
+
private readonly preStagesBlock?;
|
|
58
|
+
private readonly stages;
|
|
59
|
+
private readonly stageSpecificBlock?;
|
|
60
|
+
private stageTracker;
|
|
61
|
+
private stopped;
|
|
62
|
+
private readonly timerUnit?;
|
|
63
|
+
private readonly title?;
|
|
64
|
+
constructor({ data, design, postStagesBlock, preStagesBlock, showElapsedTime, showStageTime, stageSpecificBlock, stages, timerUnit, title, }: MultiStageOutputOptions<T>);
|
|
65
|
+
goto(stage: string, data?: Partial<T>): void;
|
|
66
|
+
next(data?: Partial<T>): void;
|
|
67
|
+
stop(error?: Error): void;
|
|
68
|
+
[Symbol.dispose](): void;
|
|
69
|
+
updateData(data: Partial<T>): void;
|
|
70
|
+
private formatKeyValuePairs;
|
|
71
|
+
/** shared method to populate everything needed for Stages cmp */
|
|
72
|
+
private generateStagesInput;
|
|
73
|
+
private update;
|
|
74
|
+
}
|
|
@@ -1,84 +1,19 @@
|
|
|
1
1
|
import { ux } from '@oclif/core/ux';
|
|
2
2
|
import { capitalCase } from 'change-case';
|
|
3
|
-
import {
|
|
3
|
+
import { render } from 'ink';
|
|
4
4
|
import { env } from 'node:process';
|
|
5
5
|
import React from 'react';
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import { SpinnerOrError, SpinnerOrErrorOrChildren } from './spinner.js';
|
|
11
|
-
import { Timer } from './timer.js';
|
|
6
|
+
import { Stages, } from './components/stages.js';
|
|
7
|
+
import { constructDesignParams } from './design.js';
|
|
8
|
+
import { StageTracker } from './stage-tracker.js';
|
|
9
|
+
import { readableTime } from './utils.js';
|
|
12
10
|
// Taken from https://github.com/sindresorhus/is-in-ci
|
|
13
11
|
const isInCi = env.CI !== '0' &&
|
|
14
12
|
env.CI !== 'false' &&
|
|
15
13
|
('CI' in env || 'CONTINUOUS_INTEGRATION' in env || Object.keys(env).some((key) => key.startsWith('CI_')));
|
|
16
|
-
function StaticKeyValue({ color, isBold, label, value }) {
|
|
17
|
-
if (!value)
|
|
18
|
-
return;
|
|
19
|
-
return (React.createElement(Box, { key: label },
|
|
20
|
-
React.createElement(Text, { bold: isBold },
|
|
21
|
-
label,
|
|
22
|
-
": "),
|
|
23
|
-
React.createElement(Text, { color: color }, value)));
|
|
24
|
-
}
|
|
25
|
-
function SimpleMessage({ color, isBold, value }) {
|
|
26
|
-
if (!value)
|
|
27
|
-
return;
|
|
28
|
-
return (React.createElement(Text, { bold: isBold, color: color }, value));
|
|
29
|
-
}
|
|
30
|
-
function Infos({ error, keyValuePairs, stage, }) {
|
|
31
|
-
return (keyValuePairs
|
|
32
|
-
// If stage is provided, only show info for that stage
|
|
33
|
-
// otherwise, show all infos that don't have a specified stage
|
|
34
|
-
.filter((kv) => (stage ? kv.stage === stage : !kv.stage))
|
|
35
|
-
.map((kv) => {
|
|
36
|
-
const key = `${kv.label}-${kv.value}`;
|
|
37
|
-
if (kv.type === 'message') {
|
|
38
|
-
return React.createElement(SimpleMessage, { key: key, ...kv });
|
|
39
|
-
}
|
|
40
|
-
if (kv.type === 'dynamic-key-value') {
|
|
41
|
-
return (React.createElement(SpinnerOrErrorOrChildren, { key: key, error: error, label: `${kv.label}:`, labelPosition: "left", type: spinners.info }, kv.value && (React.createElement(Text, { bold: kv.isBold, color: kv.color }, kv.value))));
|
|
42
|
-
}
|
|
43
|
-
if (kv.type === 'static-key-value') {
|
|
44
|
-
return React.createElement(StaticKeyValue, { key: key, ...kv });
|
|
45
|
-
}
|
|
46
|
-
return null;
|
|
47
|
-
}));
|
|
48
|
-
}
|
|
49
|
-
export function Stages({ error, hasElapsedTime = true, hasStageTime = true, postStagesBlock, preStagesBlock, stageSpecificBlock, stageTracker, timerUnit = 'ms', title, }) {
|
|
50
|
-
return (React.createElement(Box, { flexDirection: "column", paddingTop: 1, paddingBottom: 1 },
|
|
51
|
-
React.createElement(Divider, { title: title }),
|
|
52
|
-
preStagesBlock && preStagesBlock.length > 0 && (React.createElement(Box, { flexDirection: "column", marginLeft: 1, paddingTop: 1 },
|
|
53
|
-
React.createElement(Infos, { error: error, keyValuePairs: preStagesBlock }))),
|
|
54
|
-
React.createElement(Box, { flexDirection: "column", marginLeft: 1, paddingTop: 1 }, [...stageTracker.entries()].map(([stage, status]) => (React.createElement(Box, { key: stage, flexDirection: "column" },
|
|
55
|
-
React.createElement(Box, null,
|
|
56
|
-
(status === 'current' || status === 'failed') && (React.createElement(SpinnerOrError, { error: error, label: capitalCase(stage), type: spinners.stage })),
|
|
57
|
-
status === 'skipped' && (React.createElement(Text, { color: "dim" },
|
|
58
|
-
icons.skipped,
|
|
59
|
-
" ",
|
|
60
|
-
capitalCase(stage),
|
|
61
|
-
" - Skipped")),
|
|
62
|
-
status === 'completed' && (React.createElement(Box, null,
|
|
63
|
-
React.createElement(Text, { color: "green" }, icons.completed),
|
|
64
|
-
React.createElement(Text, null, capitalCase(stage)))),
|
|
65
|
-
status === 'pending' && (React.createElement(Text, { color: "dim" },
|
|
66
|
-
icons.pending,
|
|
67
|
-
" ",
|
|
68
|
-
capitalCase(stage))),
|
|
69
|
-
status !== 'pending' && status !== 'skipped' && hasStageTime && (React.createElement(Box, null,
|
|
70
|
-
React.createElement(Text, null, " "),
|
|
71
|
-
React.createElement(Timer, { color: "dim", isStopped: status === 'completed', unit: timerUnit })))),
|
|
72
|
-
stageSpecificBlock && stageSpecificBlock.length > 0 && status !== 'pending' && status !== 'skipped' && (React.createElement(Box, { flexDirection: "column", marginLeft: 5 },
|
|
73
|
-
React.createElement(Infos, { error: error, keyValuePairs: stageSpecificBlock, stage: stage }))))))),
|
|
74
|
-
postStagesBlock && postStagesBlock.length > 0 && (React.createElement(Box, { flexDirection: "column", marginLeft: 1, paddingTop: 1 },
|
|
75
|
-
React.createElement(Infos, { error: error, keyValuePairs: postStagesBlock }))),
|
|
76
|
-
hasElapsedTime && (React.createElement(Box, { marginLeft: 1, paddingTop: 1 },
|
|
77
|
-
React.createElement(Text, null, "Elapsed Time: "),
|
|
78
|
-
React.createElement(Timer, { unit: timerUnit })))));
|
|
79
|
-
}
|
|
80
14
|
class CIMultiStageOutput {
|
|
81
15
|
data;
|
|
16
|
+
design;
|
|
82
17
|
hasElapsedTime;
|
|
83
18
|
hasStageTime;
|
|
84
19
|
lastUpdateTime;
|
|
@@ -91,9 +26,8 @@ class CIMultiStageOutput {
|
|
|
91
26
|
startTime;
|
|
92
27
|
startTimes = new Map();
|
|
93
28
|
timerUnit;
|
|
94
|
-
title
|
|
95
|
-
|
|
96
|
-
this.title = title;
|
|
29
|
+
constructor({ data, design, postStagesBlock, preStagesBlock, showElapsedTime, showStageTime, stageSpecificBlock, stages, timerUnit, title, }) {
|
|
30
|
+
this.design = constructDesignParams(design);
|
|
97
31
|
this.stages = stages;
|
|
98
32
|
this.postStagesBlock = postStagesBlock;
|
|
99
33
|
this.preStagesBlock = preStagesBlock;
|
|
@@ -103,7 +37,8 @@ class CIMultiStageOutput {
|
|
|
103
37
|
this.timerUnit = timerUnit ?? 'ms';
|
|
104
38
|
this.data = data;
|
|
105
39
|
this.lastUpdateTime = Date.now();
|
|
106
|
-
|
|
40
|
+
if (title)
|
|
41
|
+
ux.stdout(`───── ${title} ─────`);
|
|
107
42
|
ux.stdout('Stages:');
|
|
108
43
|
for (const stage of this.stages) {
|
|
109
44
|
ux.stdout(`${this.stages.indexOf(stage) + 1}. ${capitalCase(stage)}`);
|
|
@@ -142,7 +77,7 @@ class CIMultiStageOutput {
|
|
|
142
77
|
this.lastUpdateTime = Date.now();
|
|
143
78
|
if (!this.startTimes.has(stage))
|
|
144
79
|
this.startTimes.set(stage, Date.now());
|
|
145
|
-
ux.stdout(`${icons.current} ${capitalCase(stage)}...`);
|
|
80
|
+
ux.stdout(`${this.design.icons.current} ${capitalCase(stage)}...`);
|
|
146
81
|
this.printInfo(this.preStagesBlock, 3);
|
|
147
82
|
this.printInfo(this.stageSpecificBlock?.filter((info) => info.stage === stage), 3);
|
|
148
83
|
this.printInfo(this.postStagesBlock, 3);
|
|
@@ -156,16 +91,16 @@ class CIMultiStageOutput {
|
|
|
156
91
|
const startTime = this.startTimes.get(stage);
|
|
157
92
|
const elapsedTime = startTime ? Date.now() - startTime : 0;
|
|
158
93
|
const displayTime = readableTime(elapsedTime, this.timerUnit);
|
|
159
|
-
ux.stdout(`${icons[status]} ${capitalCase(stage)} (${displayTime})`);
|
|
94
|
+
ux.stdout(`${this.design.icons[status]} ${capitalCase(stage)} (${displayTime})`);
|
|
160
95
|
this.printInfo(this.preStagesBlock, 3);
|
|
161
96
|
this.printInfo(this.stageSpecificBlock?.filter((info) => info.stage === stage), 3);
|
|
162
97
|
this.printInfo(this.postStagesBlock, 3);
|
|
163
98
|
}
|
|
164
99
|
else if (status === 'skipped') {
|
|
165
|
-
ux.stdout(`${icons[status]} ${capitalCase(stage)} - Skipped`);
|
|
100
|
+
ux.stdout(`${this.design.icons[status]} ${capitalCase(stage)} - Skipped`);
|
|
166
101
|
}
|
|
167
102
|
else {
|
|
168
|
-
ux.stdout(`${icons[status]} ${capitalCase(stage)}`);
|
|
103
|
+
ux.stdout(`${this.design.icons[status]} ${capitalCase(stage)}`);
|
|
169
104
|
this.printInfo(this.preStagesBlock, 3);
|
|
170
105
|
this.printInfo(this.stageSpecificBlock?.filter((info) => info.stage === stage), 3);
|
|
171
106
|
this.printInfo(this.postStagesBlock, 3);
|
|
@@ -197,6 +132,7 @@ class CIMultiStageOutput {
|
|
|
197
132
|
export class MultiStageOutput {
|
|
198
133
|
ciInstance;
|
|
199
134
|
data;
|
|
135
|
+
design;
|
|
200
136
|
hasElapsedTime;
|
|
201
137
|
hasStageTime;
|
|
202
138
|
inkInstance;
|
|
@@ -208,8 +144,9 @@ export class MultiStageOutput {
|
|
|
208
144
|
stopped = false;
|
|
209
145
|
timerUnit;
|
|
210
146
|
title;
|
|
211
|
-
constructor({ data,
|
|
147
|
+
constructor({ data, design, postStagesBlock, preStagesBlock, showElapsedTime, showStageTime, stageSpecificBlock, stages, timerUnit, title, }) {
|
|
212
148
|
this.data = data;
|
|
149
|
+
this.design = constructDesignParams(design);
|
|
213
150
|
this.stages = stages;
|
|
214
151
|
this.title = title;
|
|
215
152
|
this.postStagesBlock = postStagesBlock;
|
|
@@ -219,12 +156,10 @@ export class MultiStageOutput {
|
|
|
219
156
|
this.timerUnit = timerUnit ?? 'ms';
|
|
220
157
|
this.stageTracker = new StageTracker(stages);
|
|
221
158
|
this.stageSpecificBlock = stageSpecificBlock;
|
|
222
|
-
if (jsonEnabled)
|
|
223
|
-
return;
|
|
224
159
|
if (isInCi) {
|
|
225
160
|
this.ciInstance = new CIMultiStageOutput({
|
|
226
161
|
data,
|
|
227
|
-
|
|
162
|
+
design,
|
|
228
163
|
postStagesBlock,
|
|
229
164
|
preStagesBlock,
|
|
230
165
|
showElapsedTime,
|
|
@@ -236,7 +171,7 @@ export class MultiStageOutput {
|
|
|
236
171
|
});
|
|
237
172
|
}
|
|
238
173
|
else {
|
|
239
|
-
this.inkInstance = render(React.createElement(Stages, {
|
|
174
|
+
this.inkInstance = render(React.createElement(Stages, { ...this.generateStagesInput() }));
|
|
240
175
|
}
|
|
241
176
|
}
|
|
242
177
|
goto(stage, data) {
|
|
@@ -267,12 +202,8 @@ export class MultiStageOutput {
|
|
|
267
202
|
this.ciInstance?.stop(this.stageTracker);
|
|
268
203
|
return;
|
|
269
204
|
}
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
}
|
|
273
|
-
else {
|
|
274
|
-
this.inkInstance?.rerender(React.createElement(Stages, { 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 }));
|
|
275
|
-
}
|
|
205
|
+
const stagesInput = { ...this.generateStagesInput(), ...(error ? { error } : {}) };
|
|
206
|
+
this.inkInstance?.rerender(React.createElement(Stages, { ...stagesInput }));
|
|
276
207
|
this.inkInstance?.unmount();
|
|
277
208
|
}
|
|
278
209
|
[Symbol.dispose]() {
|
|
@@ -297,6 +228,20 @@ export class MultiStageOutput {
|
|
|
297
228
|
};
|
|
298
229
|
}) ?? []);
|
|
299
230
|
}
|
|
231
|
+
/** shared method to populate everything needed for Stages cmp */
|
|
232
|
+
generateStagesInput() {
|
|
233
|
+
return {
|
|
234
|
+
design: this.design,
|
|
235
|
+
hasElapsedTime: this.hasElapsedTime,
|
|
236
|
+
hasStageTime: this.hasStageTime,
|
|
237
|
+
postStagesBlock: this.formatKeyValuePairs(this.postStagesBlock),
|
|
238
|
+
preStagesBlock: this.formatKeyValuePairs(this.preStagesBlock),
|
|
239
|
+
stageSpecificBlock: this.formatKeyValuePairs(this.stageSpecificBlock),
|
|
240
|
+
stageTracker: this.stageTracker,
|
|
241
|
+
timerUnit: this.timerUnit,
|
|
242
|
+
title: this.title,
|
|
243
|
+
};
|
|
244
|
+
}
|
|
300
245
|
update(stage, data) {
|
|
301
246
|
this.data = { ...this.data, ...data };
|
|
302
247
|
this.stageTracker.refresh(stage);
|
|
@@ -304,7 +249,7 @@ export class MultiStageOutput {
|
|
|
304
249
|
this.ciInstance?.update(this.stageTracker, this.data);
|
|
305
250
|
}
|
|
306
251
|
else {
|
|
307
|
-
this.inkInstance?.rerender(React.createElement(Stages, {
|
|
252
|
+
this.inkInstance?.rerender(React.createElement(Stages, { ...this.generateStagesInput() }));
|
|
308
253
|
}
|
|
309
254
|
}
|
|
310
255
|
}
|
package/lib/stage-tracker.d.ts
CHANGED
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
export type StageStatus = 'pending' | 'current' | 'completed' | 'skipped' | 'failed';
|
|
2
|
-
export declare class StageTracker
|
|
2
|
+
export declare class StageTracker {
|
|
3
3
|
current: string | undefined;
|
|
4
|
+
private map;
|
|
4
5
|
private markers;
|
|
5
6
|
constructor(stages: readonly string[] | string[]);
|
|
7
|
+
entries(): IterableIterator<[string, StageStatus]>;
|
|
8
|
+
get(stage: string): StageStatus | undefined;
|
|
6
9
|
refresh(nextStage: string, opts?: {
|
|
7
10
|
hasError?: boolean;
|
|
8
11
|
isStopping?: boolean;
|
|
9
12
|
}): void;
|
|
10
|
-
set(stage: string, status: StageStatus):
|
|
13
|
+
set(stage: string, status: StageStatus): void;
|
|
14
|
+
values(): IterableIterator<StageStatus>;
|
|
11
15
|
private stopMarker;
|
|
12
16
|
}
|
package/lib/stage-tracker.js
CHANGED
|
@@ -1,16 +1,23 @@
|
|
|
1
1
|
import { Performance } from '@oclif/core/performance';
|
|
2
|
-
export class StageTracker
|
|
2
|
+
export class StageTracker {
|
|
3
3
|
current;
|
|
4
|
+
map = new Map();
|
|
4
5
|
markers = new Map();
|
|
5
6
|
constructor(stages) {
|
|
6
|
-
|
|
7
|
+
this.map = new Map(stages.map((stage) => [stage, 'pending']));
|
|
8
|
+
}
|
|
9
|
+
entries() {
|
|
10
|
+
return this.map.entries();
|
|
11
|
+
}
|
|
12
|
+
get(stage) {
|
|
13
|
+
return this.map.get(stage);
|
|
7
14
|
}
|
|
8
15
|
refresh(nextStage, opts) {
|
|
9
|
-
const stages = [...this.keys()];
|
|
16
|
+
const stages = [...this.map.keys()];
|
|
10
17
|
for (const stage of stages) {
|
|
11
|
-
if (this.get(stage) === 'skipped')
|
|
18
|
+
if (this.map.get(stage) === 'skipped')
|
|
12
19
|
continue;
|
|
13
|
-
if (this.get(stage) === 'failed')
|
|
20
|
+
if (this.map.get(stage) === 'failed')
|
|
14
21
|
continue;
|
|
15
22
|
// .stop() was called with an error => set the stage to failed
|
|
16
23
|
if (nextStage === stage && opts?.hasError) {
|
|
@@ -34,7 +41,7 @@ export class StageTracker extends Map {
|
|
|
34
41
|
continue;
|
|
35
42
|
}
|
|
36
43
|
// any stage before the current stage should be marked as skipped if it's still pending
|
|
37
|
-
if (stages.indexOf(stage) < stages.indexOf(nextStage) && this.get(stage) === 'pending') {
|
|
44
|
+
if (stages.indexOf(stage) < stages.indexOf(nextStage) && this.map.get(stage) === 'pending') {
|
|
38
45
|
this.set(stage, 'skipped');
|
|
39
46
|
continue;
|
|
40
47
|
}
|
|
@@ -52,7 +59,10 @@ export class StageTracker extends Map {
|
|
|
52
59
|
if (status === 'current') {
|
|
53
60
|
this.current = stage;
|
|
54
61
|
}
|
|
55
|
-
|
|
62
|
+
this.map.set(stage, status);
|
|
63
|
+
}
|
|
64
|
+
values() {
|
|
65
|
+
return this.map.values();
|
|
56
66
|
}
|
|
57
67
|
stopMarker(stage) {
|
|
58
68
|
const marker = this.markers.get(stage);
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oclif/multi-stage-output",
|
|
3
3
|
"description": "Terminal output for oclif commands with multiple stages",
|
|
4
|
-
"version": "0.1
|
|
4
|
+
"version": "0.2.1",
|
|
5
5
|
"author": "Salesforce",
|
|
6
6
|
"bugs": "https://github.com/oclif/multi-stage-output/issues",
|
|
7
7
|
"dependencies": {
|
|
@@ -1,130 +0,0 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import { StageTracker } from '../stage-tracker.js';
|
|
3
|
-
type Info<T extends Record<string, unknown>> = {
|
|
4
|
-
/**
|
|
5
|
-
* key-value: Display a key-value pair with a spinner.
|
|
6
|
-
* static-key-value: Display a key-value pair without a spinner.
|
|
7
|
-
* message: Display a message.
|
|
8
|
-
*/
|
|
9
|
-
type: 'dynamic-key-value' | 'static-key-value' | 'message';
|
|
10
|
-
/**
|
|
11
|
-
* Color of the value.
|
|
12
|
-
*/
|
|
13
|
-
color?: string;
|
|
14
|
-
/**
|
|
15
|
-
* Get the value to display. Takes the data property on the MultiStageComponent as an argument.
|
|
16
|
-
* Useful if you want to apply some logic (like rendering a link) to the data before displaying it.
|
|
17
|
-
*
|
|
18
|
-
* @param data The data property on the MultiStageComponent.
|
|
19
|
-
* @returns {string | undefined}
|
|
20
|
-
*/
|
|
21
|
-
get: (data?: T) => string | undefined;
|
|
22
|
-
/**
|
|
23
|
-
* Whether the value should be bold.
|
|
24
|
-
*/
|
|
25
|
-
bold?: boolean;
|
|
26
|
-
};
|
|
27
|
-
type KeyValuePair<T extends Record<string, unknown>> = Info<T> & {
|
|
28
|
-
/**
|
|
29
|
-
* Label of the key-value pair.
|
|
30
|
-
*/
|
|
31
|
-
label: string;
|
|
32
|
-
type: 'dynamic-key-value' | 'static-key-value';
|
|
33
|
-
};
|
|
34
|
-
type SimpleMessage<T extends Record<string, unknown>> = Info<T> & {
|
|
35
|
-
type: 'message';
|
|
36
|
-
};
|
|
37
|
-
type StageInfoBlock<T extends Record<string, unknown>> = Array<(KeyValuePair<T> & {
|
|
38
|
-
stage: string;
|
|
39
|
-
}) | (SimpleMessage<T> & {
|
|
40
|
-
stage: string;
|
|
41
|
-
})>;
|
|
42
|
-
export type FormattedKeyValue = {
|
|
43
|
-
readonly color?: string;
|
|
44
|
-
readonly isBold?: boolean;
|
|
45
|
-
readonly label?: string;
|
|
46
|
-
readonly value: string | undefined;
|
|
47
|
-
readonly stage?: string;
|
|
48
|
-
readonly type: 'dynamic-key-value' | 'static-key-value' | 'message';
|
|
49
|
-
};
|
|
50
|
-
type MultiStageOutputOptions<T extends Record<string, unknown>> = {
|
|
51
|
-
/**
|
|
52
|
-
* Stages to render.
|
|
53
|
-
*/
|
|
54
|
-
readonly stages: readonly string[] | string[];
|
|
55
|
-
/**
|
|
56
|
-
* Title to display at the top of the stages component.
|
|
57
|
-
*/
|
|
58
|
-
readonly title: string;
|
|
59
|
-
/**
|
|
60
|
-
* Information to display at the bottom of the stages component.
|
|
61
|
-
*/
|
|
62
|
-
readonly postStagesBlock?: Array<KeyValuePair<T> | SimpleMessage<T>>;
|
|
63
|
-
/**
|
|
64
|
-
* Information to display below the title but above the stages.
|
|
65
|
-
*/
|
|
66
|
-
readonly preStagesBlock?: Array<KeyValuePair<T> | SimpleMessage<T>>;
|
|
67
|
-
/**
|
|
68
|
-
* Whether to show the total elapsed time. Defaults to true
|
|
69
|
-
*/
|
|
70
|
-
readonly showElapsedTime?: boolean;
|
|
71
|
-
/**
|
|
72
|
-
* Whether to show the time spent on each stage. Defaults to true
|
|
73
|
-
*/
|
|
74
|
-
readonly showStageTime?: boolean;
|
|
75
|
-
/**
|
|
76
|
-
* Information to display for a specific stage. Each object must have a stage property set.
|
|
77
|
-
*/
|
|
78
|
-
readonly stageSpecificBlock?: StageInfoBlock<T>;
|
|
79
|
-
/**
|
|
80
|
-
* The unit to use for the timer. Defaults to 'ms'
|
|
81
|
-
*/
|
|
82
|
-
readonly timerUnit?: 'ms' | 's';
|
|
83
|
-
/**
|
|
84
|
-
* Data to display in the stages component. This data will be passed to the get function in the info object.
|
|
85
|
-
*/
|
|
86
|
-
readonly data?: Partial<T>;
|
|
87
|
-
/**
|
|
88
|
-
* Whether JSON output is enabled. Defaults to false.
|
|
89
|
-
*
|
|
90
|
-
* Pass in this.jsonEnabled() from the command class to determine if JSON output is enabled.
|
|
91
|
-
*/
|
|
92
|
-
readonly jsonEnabled: boolean;
|
|
93
|
-
};
|
|
94
|
-
type StagesProps = {
|
|
95
|
-
readonly error?: Error | undefined;
|
|
96
|
-
readonly postStagesBlock?: FormattedKeyValue[];
|
|
97
|
-
readonly preStagesBlock?: FormattedKeyValue[];
|
|
98
|
-
readonly stageSpecificBlock?: FormattedKeyValue[];
|
|
99
|
-
readonly title: string;
|
|
100
|
-
readonly hasElapsedTime?: boolean;
|
|
101
|
-
readonly hasStageTime?: boolean;
|
|
102
|
-
readonly timerUnit?: 'ms' | 's';
|
|
103
|
-
readonly stageTracker: StageTracker;
|
|
104
|
-
};
|
|
105
|
-
declare function SimpleMessage({ color, isBold, value }: FormattedKeyValue): React.ReactNode;
|
|
106
|
-
export declare function Stages({ error, hasElapsedTime, hasStageTime, postStagesBlock, preStagesBlock, stageSpecificBlock, stageTracker, timerUnit, title, }: StagesProps): React.ReactNode;
|
|
107
|
-
export declare class MultiStageOutput<T extends Record<string, unknown>> implements Disposable {
|
|
108
|
-
private ciInstance;
|
|
109
|
-
private data?;
|
|
110
|
-
private readonly hasElapsedTime?;
|
|
111
|
-
private readonly hasStageTime?;
|
|
112
|
-
private inkInstance;
|
|
113
|
-
private readonly postStagesBlock?;
|
|
114
|
-
private readonly preStagesBlock?;
|
|
115
|
-
private readonly stages;
|
|
116
|
-
private readonly stageSpecificBlock?;
|
|
117
|
-
private stageTracker;
|
|
118
|
-
private stopped;
|
|
119
|
-
private readonly timerUnit?;
|
|
120
|
-
private readonly title;
|
|
121
|
-
constructor({ data, jsonEnabled, postStagesBlock, preStagesBlock, showElapsedTime, showStageTime, stageSpecificBlock, stages, timerUnit, title, }: MultiStageOutputOptions<T>);
|
|
122
|
-
goto(stage: string, data?: Partial<T>): void;
|
|
123
|
-
next(data?: Partial<T>): void;
|
|
124
|
-
stop(error?: Error): void;
|
|
125
|
-
[Symbol.dispose](): void;
|
|
126
|
-
updateData(data: Partial<T>): void;
|
|
127
|
-
private formatKeyValuePairs;
|
|
128
|
-
private update;
|
|
129
|
-
}
|
|
130
|
-
export {};
|
package/lib/design-elements.d.ts
DELETED
package/lib/design-elements.js
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import figures from 'figures';
|
|
2
|
-
export const icons = {
|
|
3
|
-
completed: figures.tick,
|
|
4
|
-
current: figures.play,
|
|
5
|
-
failed: figures.cross,
|
|
6
|
-
pending: figures.squareSmallFilled,
|
|
7
|
-
skipped: figures.circle,
|
|
8
|
-
};
|
|
9
|
-
export const spinners = {
|
|
10
|
-
info: process.platform === 'win32' ? 'line' : 'arc',
|
|
11
|
-
stage: process.platform === 'win32' ? 'line' : 'dots2',
|
|
12
|
-
};
|