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

amplify-prompts

Package Overview
Dependencies
Maintainers
1
Versions
194
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

amplify-prompts - npm Package Compare versions

Comparing version 2.6.4-cdkv2.1 to 2.6.4-cdkv2.3

16

CHANGELOG.md

@@ -6,2 +6,18 @@ # Change Log

## [2.6.4-cdkv2.3](https://github.com/aws-amplify/amplify-cli/compare/amplify-prompts@2.6.4-cdkv2.2...amplify-prompts@2.6.4-cdkv2.3) (2023-02-17)
**Note:** Version bump only for package amplify-prompts
## [2.6.4-cdkv2.2](https://github.com/aws-amplify/amplify-cli/compare/amplify-prompts@2.6.4-cdkv2.1...amplify-prompts@2.6.4-cdkv2.2) (2023-02-17)
**Note:** Version bump only for package amplify-prompts
## [2.6.4-cdkv2.1](https://github.com/aws-amplify/amplify-cli/compare/amplify-prompts@2.6.4-cdkv2.0...amplify-prompts@2.6.4-cdkv2.1) (2023-02-15)

@@ -8,0 +24,0 @@

4

lib/demo/demo.js

@@ -23,3 +23,3 @@ "use strict";

printer_1.printer.info('To get an input type besides a string, specify a transform function');
const result1 = await prompter_1.prompter.input('How many Skittles do you want?', { transform: input => Number.parseInt(input, 10) });
const result1 = await prompter_1.prompter.input('How many Skittles do you want?', { transform: (input) => Number.parseInt(input, 10) });
printResult(result1);

@@ -31,3 +31,3 @@ printTypeofResult(result1);

printResult(await prompter_1.prompter.input('How many Skittles do you want', {
transform: input => Number.parseInt(input, 10),
transform: (input) => Number.parseInt(input, 10),
validate: (0, validators_1.integer)(),

@@ -34,0 +34,0 @@ }));

@@ -6,5 +6,5 @@ "use strict";

exports.isSilent = process.argv.includes('--silent');
exports.isYes = !!['--yes', '-y'].find(yesFlag => process.argv.includes(yesFlag));
exports.isYes = !!['--yes', '-y'].find((yesFlag) => process.argv.includes(yesFlag));
exports.isInteractiveShell = process.stdin.isTTY;
exports.isHeadless = process.argv.includes('--headless');
//# sourceMappingURL=flags.js.map

@@ -8,3 +8,3 @@ "use strict";

this.printer = printer;
this.list = (items) => items.forEach(item => this.printer.info(`- ${item}`));
this.list = (items) => items.forEach((item) => this.printer.info(`- ${item}`));
}

@@ -11,0 +11,0 @@ }

@@ -64,3 +64,3 @@ "use strict";

getBar(name) {
return this.bars.find(obj => obj.name === name);
return this.bars.find((obj) => obj.name === name);
}

@@ -114,3 +114,3 @@ updateBar(name, updateObj) {

finishAllBars() {
this.bars.forEach(bar => this.finishBar(bar.name));
this.bars.forEach((bar) => this.finishBar(bar.name));
}

@@ -125,3 +125,3 @@ create(bars) {

}
bars.forEach(config => {
bars.forEach((config) => {
const newBar = new progressbar_1.ProgressBar(this.options);

@@ -161,3 +161,3 @@ newBar.start(config.total, config.value, config.payload);

this.writeLines(initLine);
this.bars.forEach(bar => bar.bar.stop());
this.bars.forEach((bar) => bar.bar.stop());
this.bars = [];

@@ -164,0 +164,0 @@ this.count = 0;

@@ -18,4 +18,4 @@ "use strict";

this.barSize = options.barSize || DEFAULT_BAR_SIZE;
this.barCompleteString = (new Array(this.barSize + 1).join(options.barCompleteChar || '='));
this.barIncompleteString = (new Array(this.barSize + 1).join(options.barIncompleteChar || '-'));
this.barCompleteString = new Array(this.barSize + 1).join(options.barCompleteChar || '=');
this.barIncompleteString = new Array(this.barSize + 1).join(options.barIncompleteChar || '-');
}

@@ -25,4 +25,3 @@ createBarString() {

const incompleteSize = this.barSize - completeSize;
const bar = this.barCompleteString.slice(0, completeSize)
+ this.barIncompleteString.slice(0, incompleteSize);
const bar = this.barCompleteString.slice(0, completeSize) + this.barIncompleteString.slice(0, incompleteSize);
return ` [ ${bar} ] ${this.value}/${this.total}`;

@@ -56,3 +55,3 @@ }

isFailed() {
return this.items.some(item => this.options.itemFailedStatus.includes(item.status));
return this.items.some((item) => this.options.itemFailedStatus.includes(item.status));
}

@@ -84,3 +83,3 @@ start(total, startValue, payload) {

getItem(name) {
return this.items.find(item => item.name === name);
return this.items.find((item) => item.name === name);
}

@@ -98,3 +97,3 @@ addItem(name, itemPayload) {

updateItem(name, newPayload) {
const newItemsSet = this.items.map(item => {
const newItemsSet = this.items.map((item) => {
let obj = null;

@@ -101,0 +100,0 @@ if (item.name === name) {

@@ -20,6 +20,8 @@ "use strict";

}
const lines = [{
const lines = [
{
renderString: `${this.frames[this.frameCount]} ${this.prefixText}`,
color: '',
}];
},
];
this.frameCount = ++this.frameCount % this.frames.length;

@@ -48,6 +50,8 @@ this.terminal.writeLines(lines);

}
const lines = [{
const lines = [
{
renderString: text || '',
color: success ? 'green' : 'red',
}];
},
];
clearTimeout(this.timer);

@@ -54,0 +58,0 @@ this.terminal.writeLines(lines);

@@ -44,3 +44,3 @@ "use strict";

this.stream.write(cursorUp(this.lastHeight));
lines.forEach(line => {
lines.forEach((line) => {
const { renderString, color } = line;

@@ -47,0 +47,0 @@ let truncatedLine = renderString.substring(0, Math.min(renderString.length, this.width - BUFFER_LENGTH));

@@ -83,3 +83,3 @@ "use strict";

message,
format: value => (submitted ? (value ? 'yes' : 'no') : ''),
format: (value) => (submitted ? (value ? 'yes' : 'no') : ''),
onSubmit: () => {

@@ -142,7 +142,7 @@ submitted = true;

?
choices.map(choice => ({ name: choice, value: choice }))
choices.map((choice) => ({ name: choice, value: choice }))
: choices;
const initialIndexes = initialOptsToIndexes(genericChoices.map(choice => choice.value), opts.initial);
const initialIndexes = initialOptsToIndexes(genericChoices.map((choice) => choice.value), opts.initial);
const choiceValueMap = new Map();
const enquirerChoices = genericChoices.map(choice => {
const enquirerChoices = genericChoices.map((choice) => {
choiceValueMap.set(choice.name, choice.value);

@@ -159,3 +159,3 @@ const enqResult = { name: choice.name, disabled: choice.disabled, hint: choice.hint };

else if ('pickAtLeast' in opts && (opts.pickAtLeast || 0) >= choices.length) {
result = genericChoices.map(choice => choice.name);
result = genericChoices.map((choice) => choice.name);
this.print.info(`Must pick at least ${opts.pickAtLeast} of ${choices.length} options. Selecting all options [${result}]`);

@@ -171,3 +171,3 @@ }

else {
result = initialIndexes.map(idx => genericChoices[idx].name);
result = initialIndexes.map((idx) => genericChoices[idx].name);
}

@@ -211,3 +211,3 @@ }

if (Array.isArray(result)) {
loggedRet = result.map(item => choiceValueMap.get(item));
loggedRet = result.map((item) => choiceValueMap.get(item));
}

@@ -233,6 +233,6 @@ else {

exports.prompter = new AmplifyPrompter();
const byValues = (selection, equals = defaultEquals) => (choices) => selection.map(sel => choices.findIndex(choice => equals(choice, sel))).filter(idx => idx >= 0);
const byValues = (selection, equals = defaultEquals) => (choices) => selection.map((sel) => choices.findIndex((choice) => equals(choice, sel))).filter((idx) => idx >= 0);
exports.byValues = byValues;
const byValue = (selection, equals = defaultEquals) => (choices) => {
const idx = choices.findIndex(choice => equals(choice, selection));
const idx = choices.findIndex((choice) => equals(choice, selection));
return idx < 0 ? undefined : idx;

@@ -245,4 +245,4 @@ };

}
const validationList = await Promise.all(input.map(part => part.trim()).map(async (part) => ({ part, result: await validator(part) })));
const firstInvalid = validationList.find(v => typeof v.result === 'string');
const validationList = await Promise.all(input.map((part) => part.trim()).map(async (part) => ({ part, result: await validator(part) })));
const firstInvalid = validationList.find((v) => typeof v.result === 'string');
if (firstInvalid) {

@@ -249,0 +249,0 @@ return `${firstInvalid.part} did not satisfy requirement ${firstInvalid.result}`;

@@ -23,4 +23,3 @@ "use strict";

};
this.getElapsedMilliseconds = () => this._slices
.reduce((accumulator, currentValue) => accumulator + ((currentValue.stop || Date.now()) - currentValue.start), 0);
this.getElapsedMilliseconds = () => this._slices.reduce((accumulator, currentValue) => accumulator + ((currentValue.stop || Date.now()) - currentValue.start), 0);
}

@@ -27,0 +26,0 @@ }

@@ -8,3 +8,3 @@ "use strict";

exports.matchRegex = matchRegex;
const integer = (message = 'Input must be a number') => (input) => (/^[0-9]+$/.test(input) ? true : message);
const integer = (message = 'Input must be a number') => (input) => /^[0-9]+$/.test(input) ? true : message;
exports.integer = integer;

@@ -11,0 +11,0 @@ const maxLength = (maxLen, message) => (input) => input.length > maxLen ? message || `Input must be less than ${maxLen} characters long` : true;

{
"name": "amplify-prompts",
"version": "2.6.4-cdkv2.1",
"version": "2.6.4-cdkv2.3",
"description": "Utility functions for Amplify CLI terminal I/O",

@@ -30,3 +30,3 @@ "main": "lib/index.js",

"dependencies": {
"amplify-cli-shared-interfaces": "1.1.1-cdkv2.2",
"amplify-cli-shared-interfaces": "1.1.1-cdkv2.4",
"chalk": "^4.1.1",

@@ -57,3 +57,3 @@ "enquirer": "^2.3.6"

},
"gitHead": "46a85163e642f443d5cfd910d6e83fe3b82f645f"
"gitHead": "39982ca61aa4ad108354a67c942aad902e242c71"
}
/* eslint-disable jest/no-conditional-expect */
import { MultiProgressBar as MultiBar } from '../progressbars/multibar';
import {
BarOptions,
ItemPayload,
ProgressPayload,
} from '../progressbars/progressbar';
import { BarOptions, ItemPayload, ProgressPayload } from '../progressbars/progressbar';
const options : BarOptions = {
progressBarFormatter: (payload : ProgressPayload) => payload.progressName,
itemFormatter: (payload : ItemPayload) => ({ renderString: payload.ResourceStatus, color: '' }),
const options: BarOptions = {
progressBarFormatter: (payload: ProgressPayload) => payload.progressName,
itemFormatter: (payload: ItemPayload) => ({ renderString: payload.ResourceStatus, color: '' }),
loneWolf: false,

@@ -26,15 +22,17 @@ hideCursor: true,

describe('Bar update operations', () => {
let multiBar : MultiBar;
let multiBar: MultiBar;
beforeEach(() => {
multiBar = new MultiBar(options);
multiBar.create([{
name: 'test-bar',
value: 0,
total: 2,
payload: {
progressName: 'test',
envName: 'dev',
multiBar.create([
{
name: 'test-bar',
value: 0,
total: 2,
payload: {
progressName: 'test',
envName: 'dev',
},
},
}]);
]);
});

@@ -41,0 +39,0 @@

import { AmplifyPrinter } from '../printer';
import * as flags from '../flags';
const writeStream_stub = ({
const writeStream_stub = {
write: jest.fn(),
} as unknown) as jest.Mocked<NodeJS.WritableStream>;
} as unknown as jest.Mocked<NodeJS.WritableStream>;

@@ -7,0 +7,0 @@ jest.mock('../flags');

@@ -5,5 +5,5 @@ /* eslint-disable jest/no-conditional-expect */

const options : BarOptions = {
progressBarFormatter: payload => payload.progressName,
itemFormatter: payload => ({ renderString: payload.ResourceStatus, color: '' }),
const options: BarOptions = {
progressBarFormatter: (payload) => payload.progressName,
itemFormatter: (payload) => ({ renderString: payload.ResourceStatus, color: '' }),
loneWolf: false,

@@ -22,3 +22,3 @@ hideCursor: true,

describe('Check item add/update operations', () => {
let bar : Bar;
let bar: Bar;

@@ -58,3 +58,3 @@ beforeEach(async () => {

describe('Test progressBar status', () => {
let bar : Bar;
let bar: Bar;

@@ -61,0 +61,0 @@ beforeEach(async () => {

@@ -71,3 +71,3 @@ import { prompt } from 'enquirer';

await expect(() => prompter.input('test message')).rejects.toThrowErrorMatchingInlineSnapshot(
"\"Cannot prompt for [test message] when '--yes' flag is set\"",
'"Cannot prompt for [test message] when \'--yes\' flag is set"',
);

@@ -99,3 +99,3 @@ });

promptMock.mockResolvedValueOnce({ result: promptResponse });
expect(await prompter.input('test message', { transform: _ => transformedValue })).toEqual(transformedValue);
expect(await prompter.input('test message', { transform: (_) => transformedValue })).toEqual(transformedValue);
});

@@ -105,5 +105,6 @@

promptMock.mockResolvedValueOnce({ result: ['10', '20'] });
expect(
await prompter.input<'many'>('test message', { returnSize: 'many', transform: input => `${input}suffix` }),
).toEqual(['10suffix', '20suffix']);
expect(await prompter.input<'many'>('test message', { returnSize: 'many', transform: (input) => `${input}suffix` })).toEqual([
'10suffix',
'20suffix',
]);
});

@@ -116,3 +117,3 @@ });

await expect(() => prompter.pick('test message', ['opt1', 'opt2'])).rejects.toThrowErrorMatchingInlineSnapshot(
"\"Cannot prompt for [test message] when '--yes' flag is set\"",
'"Cannot prompt for [test message] when \'--yes\' flag is set"',
);

@@ -173,11 +174,7 @@ });

promptMock.mockResolvedValueOnce({ result: mockResult });
expect(
await prompter.pick<'many'>('test message', ['val1', 'val2', 'val3'], { returnSize: 'many' }),
).toEqual(mockResult);
expect(await prompter.pick<'many'>('test message', ['val1', 'val2', 'val3'], { returnSize: 'many' })).toEqual(mockResult);
});
it('returns array of single item if only one choice specified and must pick at least 1', async () => {
expect(
await prompter.pick<'many'>('test message', ['hello'], { returnSize: 'many', pickAtLeast: 1 }),
).toEqual(['hello']);
expect(await prompter.pick<'many'>('test message', ['hello'], { returnSize: 'many', pickAtLeast: 1 })).toEqual(['hello']);
expect(promptMock).toHaveBeenCalledTimes(0);

@@ -187,5 +184,3 @@ });

it('returns array of all choices if must pick at lest that many options', async () => {
expect(
await prompter.pick<'many'>('test message', ['hello', 'hey'], { returnSize: 'many', pickAtLeast: 3 }),
).toEqual(['hello', 'hey']);
expect(await prompter.pick<'many'>('test message', ['hello', 'hey'], { returnSize: 'many', pickAtLeast: 3 })).toEqual(['hello', 'hey']);
expect(promptMock).toHaveBeenCalledTimes(0);

@@ -196,5 +191,3 @@ });

promptMock.mockResolvedValueOnce({ result: ['hello'] });
expect(
await prompter.pick<'many'>('test message', ['hello'], { returnSize: 'many' }),
).toEqual(['hello']);
expect(await prompter.pick<'many'>('test message', ['hello'], { returnSize: 'many' })).toEqual(['hello']);
expect(promptMock).toHaveBeenCalled();

@@ -205,5 +198,6 @@ });

promptMock.mockResolvedValueOnce({ result: ['hello', 'hey'] });
expect(
await prompter.pick<'many'>('test message', ['hello', 'hey', 'hi'], { returnSize: 'many', pickAtLeast: 2 }),
).toEqual(['hello', 'hey']);
expect(await prompter.pick<'many'>('test message', ['hello', 'hey', 'hi'], { returnSize: 'many', pickAtLeast: 2 })).toEqual([
'hello',
'hey',
]);
expect(promptMock).toHaveBeenCalled();

@@ -210,0 +204,0 @@ });

@@ -7,3 +7,5 @@ import { Stopwatch } from '../stopwatch';

stopwatch.start();
await new Promise((resolve, __reject) => { setTimeout(resolve, 300); });
await new Promise((resolve, __reject) => {
setTimeout(resolve, 300);
});
stopwatch.pause();

@@ -13,3 +15,5 @@ expect(stopwatch.getElapsedMilliseconds()).toBeGreaterThanOrEqual(295);

stopwatch.start();
await new Promise((resolve, __reject) => { setTimeout(resolve, 300); });
await new Promise((resolve, __reject) => {
setTimeout(resolve, 300);
});
stopwatch.pause();

@@ -16,0 +20,0 @@ expect(stopwatch.getElapsedMilliseconds()).toBeGreaterThanOrEqual(595);

@@ -69,7 +69,9 @@ import { alphanumeric, and, integer, maxLength, minLength, not, or, matchRegex } from '../validators';

it('returns true if all validators return true', async () => {
expect(await and([input => true, input => true])('anything')).toBe(true);
expect(await and([(input) => true, (input) => true])('anything')).toBe(true);
});
it('returns first error message', async () => {
expect(await and([input => true, input => 'first error', input => 'second error'])('anything')).toMatchInlineSnapshot(`"first error"`);
expect(await and([(input) => true, (input) => 'first error', (input) => 'second error'])('anything')).toMatchInlineSnapshot(
`"first error"`,
);
});

@@ -79,3 +81,3 @@

expect(
await and([input => true, input => 'first error', input => 'second error'], 'custom error message')('anything'),
await and([(input) => true, (input) => 'first error', (input) => 'second error'], 'custom error message')('anything'),
).toMatchInlineSnapshot(`"custom error message"`);

@@ -87,11 +89,11 @@ });

it('returns true if one validator returns true', async () => {
expect(await or([input => 'first error', input => true])('anything')).toBe(true);
expect(await or([(input) => 'first error', (input) => true])('anything')).toBe(true);
});
it('returns last error message if all validators return error', async () => {
expect(await or([input => 'first error', input => 'second error'])('anything')).toMatchInlineSnapshot(`"second error"`);
expect(await or([(input) => 'first error', (input) => 'second error'])('anything')).toMatchInlineSnapshot(`"second error"`);
});
it('returns override error mmessage if all validators return error', async () => {
expect(await or([input => 'first error', input => 'second error'], 'custom message')('anything')).toMatchInlineSnapshot(
expect(await or([(input) => 'first error', (input) => 'second error'], 'custom message')('anything')).toMatchInlineSnapshot(
`"custom message"`,

@@ -104,7 +106,7 @@ );

it('returns error message if validator returns true', async () => {
expect(await not(input => true, 'custom error message')('anything')).toMatchInlineSnapshot(`"custom error message"`);
expect(await not((input) => true, 'custom error message')('anything')).toMatchInlineSnapshot(`"custom error message"`);
});
it('returns true when validator returns error message', async () => {
expect(await not(input => 'error message', 'other message')('anything')).toBe(true);
expect(await not((input) => 'error message', 'other message')('anything')).toBe(true);
});

@@ -111,0 +113,0 @@ });

@@ -40,3 +40,3 @@ import { printer } from '../printer';

printer.info('To get an input type besides a string, specify a transform function');
const result1 = await prompter.input('How many Skittles do you want?', { transform: input => Number.parseInt(input, 10) });
const result1 = await prompter.input('How many Skittles do you want?', { transform: (input) => Number.parseInt(input, 10) });
printResult(result1);

@@ -50,3 +50,3 @@ printTypeofResult(result1);

await prompter.input<'one', number>('How many Skittles do you want', {
transform: input => Number.parseInt(input, 10),
transform: (input) => Number.parseInt(input, 10),
validate: integer(),

@@ -53,0 +53,0 @@ }),

@@ -13,3 +13,3 @@ /**

*/
export const isYes = !!['--yes', '-y'].find(yesFlag => process.argv.includes(yesFlag));
export const isYes = !!['--yes', '-y'].find((yesFlag) => process.argv.includes(yesFlag));

@@ -16,0 +16,0 @@ /**

@@ -8,3 +8,3 @@ import { Printer, printer as defaultPrinter } from './printer';

constructor(private readonly printer: Printer = defaultPrinter) {}
list = (items: string[]) => items.forEach(item => this.printer.info(`- ${item}`));
list = (items: string[]) => items.forEach((item) => this.printer.info(`- ${item}`));
}

@@ -11,0 +11,0 @@

@@ -9,1 +9,3 @@ export * from './formatter';

export * from './flags';
// version bump
/**
* This class has been made as generic as possible to suit all use cases.
* But it is not without influence from the nuances of CloudFormation.
*/
*/
import { AmplifyTerminal as Terminal, TerminalLine } from './terminal';
import {
ProgressBar as Bar,
BarOptions,
ItemPayload,
ProgressPayload,
} from './progressbar';
import { ProgressBar as Bar, BarOptions, ItemPayload, ProgressPayload } from './progressbar';

@@ -17,250 +12,252 @@ /**

export class MultiProgressBar {
private count: number;
private terminal: Terminal;
private options: BarOptions;
private bars: { name: string, bar: Bar }[];
private lastDrawnTime: number;
isActive: boolean;
private refreshRate: number;
private frameCount: number;
private frames: string[];
private timer!: ReturnType<typeof setTimeout>;
private prefixText: string; // Header text goes in front of an animated spinner
private updated: boolean;
private lastDrawnStrings: TerminalLine[];
private count: number;
private terminal: Terminal;
private options: BarOptions;
private bars: { name: string; bar: Bar }[];
private lastDrawnTime: number;
isActive: boolean;
private refreshRate: number;
private frameCount: number;
private frames: string[];
private timer!: ReturnType<typeof setTimeout>;
private prefixText: string; // Header text goes in front of an animated spinner
private updated: boolean;
private lastDrawnStrings: TerminalLine[];
constructor(options: BarOptions) {
this.terminal = new Terminal();
this.options = options;
this.bars = [];
constructor(options: BarOptions) {
this.terminal = new Terminal();
this.options = options;
this.bars = [];
if (this.options.hideCursor === true) {
this.terminal.cursor(false);
}
if (this.options.hideCursor === true) {
this.terminal.cursor(false);
}
this.lastDrawnTime = Date.now();
this.isActive = false;
this.count = 0;
this.lastDrawnTime = Date.now();
this.isActive = false;
this.count = 0;
this.frameCount = 0;
this.frameCount = 0;
// simulate spinner
this.frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
this.prefixText = options.prefixText;
this.refreshRate = 200;
// simulate spinner
this.frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
this.prefixText = options.prefixText;
this.refreshRate = 200;
// Tracks when an update into one of the the bars happens
this.updated = false;
this.lastDrawnStrings = [];
}
// Tracks when an update into one of the the bars happens
this.updated = false;
this.lastDrawnStrings = [];
}
/**
* Checks if the environment is tty
*/
isTTY(): boolean {
return this.terminal.isTTY();
/**
* Checks if the environment is tty
*/
isTTY(): boolean {
return this.terminal.isTTY();
}
/**
* Writes lines into the re writable block
*/
writeLines(terminalLine: TerminalLine): void {
let barStrings: TerminalLine[] = [];
let stringsToRender: TerminalLine[] = [];
if (Object.keys(terminalLine).length !== 0) {
stringsToRender.push(terminalLine);
}
// Only call on the render strings for the individual bar if an update happened.
if (this.updated) {
barStrings = this.bars.reduce((prev, _current) => prev.concat(_current.bar.getRenderStrings()), barStrings);
stringsToRender = stringsToRender.concat(barStrings);
this.lastDrawnStrings = barStrings;
} else {
// Use the cached last drawn strings if no update
stringsToRender = stringsToRender.concat(this.lastDrawnStrings);
}
this.terminal.writeLines(stringsToRender);
}
/**
* Writes lines into the re writable block
*/
writeLines(terminalLine: TerminalLine): void {
let barStrings: TerminalLine[] = [];
let stringsToRender: TerminalLine[] = [];
if (Object.keys(terminalLine).length !== 0) {
stringsToRender.push(terminalLine);
}
// Only call on the render strings for the individual bar if an update happened.
if (this.updated) {
barStrings = this.bars.reduce((prev, _current) => prev.concat(_current.bar.getRenderStrings()), barStrings);
stringsToRender = stringsToRender.concat(barStrings);
this.lastDrawnStrings = barStrings;
} else {
// Use the cached last drawn strings if no update
stringsToRender = stringsToRender.concat(this.lastDrawnStrings);
}
this.terminal.writeLines(stringsToRender);
/**
* Render function which is called repeatedly
*/
render(): void {
const initLine: TerminalLine = {
renderString: '',
color: '',
};
if (this.timer) {
clearTimeout(this.timer);
}
// Init line is prefix text plus spinner
if (this.prefixText.length) {
initLine.renderString = `${this.prefixText} ${this.frames[this.frameCount]}`;
}
this.writeLines(initLine);
/**
* Render function which is called repeatedly
*/
render(): void {
const initLine: TerminalLine = {
renderString: '',
color: '',
};
if (this.timer) {
clearTimeout(this.timer);
}
// Init line is prefix text plus spinner
if (this.prefixText.length) {
initLine.renderString = `${this.prefixText} ${this.frames[this.frameCount]}`;
}
this.writeLines(initLine);
this.frameCount = ++this.frameCount % this.frames.length;
this.lastDrawnTime = Date.now();
this.frameCount = ++this.frameCount % this.frames.length;
this.lastDrawnTime = Date.now();
// Enter a newLine in nonTTY mode
if (!this.isTTY()) {
this.terminal.newLine();
}
// Reset updated flag
this.updated = false;
// Enter a newLine in nonTTY mode
if (!this.isTTY()) {
this.terminal.newLine();
}
// Reset updated flag
this.updated = false;
this.timer = setTimeout(this.render.bind(this), this.refreshRate);
}
this.timer = setTimeout(this.render.bind(this), this.refreshRate);
}
/**
* Returns bar indexed by name
*/
getBar(name: string): { name: string; bar: Bar } | undefined {
return this.bars.find((obj) => obj.name === name);
}
/**
* Returns bar indexed by name
*/
getBar(name: string): { name: string, bar: Bar } | undefined {
return this.bars.find(obj => obj.name === name);
/**
* Updates a progress bar by adding/updating item or increments the bar
*/
updateBar(name: string, updateObj: { name: string; payload: ItemPayload }): void {
const barDetails = this.getBar(name);
if (!barDetails) {
return;
}
const barToUpdate = barDetails.bar;
const item = barToUpdate.getItem(updateObj.name);
/**
* Updates a progress bar by adding/updating item or increments the bar
*/
updateBar(name: string, updateObj: { name: string, payload: ItemPayload }): void {
const barDetails = this.getBar(name);
if (!barDetails) {
return;
}
const barToUpdate = barDetails.bar;
const item = barToUpdate.getItem(updateObj.name);
let finishedStatus = false;
let itemFailure = false;
let finishedStatus = false;
let itemFailure = false;
if (item) {
finishedStatus = item.finished;
itemFailure = this.options.itemFailedStatus.includes(item.status);
// We do not update the item status if it has already failed.
if (item.status !== updateObj.payload.ResourceStatus && !itemFailure) {
barToUpdate.updateItem(updateObj.name, updateObj.payload);
this.updated = true;
}
} else {
barToUpdate.addItem(updateObj.name, updateObj.payload);
if (item) {
finishedStatus = item.finished;
itemFailure = this.options.itemFailedStatus.includes(item.status);
// We do not update the item status if it has already failed.
if (item.status !== updateObj.payload.ResourceStatus && !itemFailure) {
barToUpdate.updateItem(updateObj.name, updateObj.payload);
this.updated = true;
}
// Since items can have multiple finished states (CloudFormation nuance),
// we do not increment the progress bar if it has already failed/succeeded.
if (this.options.itemCompleteStatus.includes(updateObj.payload.ResourceStatus) && !finishedStatus && !itemFailure) {
barToUpdate.increment();
this.updated = true;
}
} else {
barToUpdate.addItem(updateObj.name, updateObj.payload);
this.updated = true;
}
/**
* Increments value of a bar indexed by name
*/
incrementBar(name: string, value: number): void {
const barDetails = this.getBar(name);
if (!barDetails) {
return;
}
const barToUpdate = barDetails.bar;
barToUpdate.increment(value);
// Since items can have multiple finished states (CloudFormation nuance),
// we do not increment the progress bar if it has already failed/succeeded.
if (this.options.itemCompleteStatus.includes(updateObj.payload.ResourceStatus) && !finishedStatus && !itemFailure) {
barToUpdate.increment();
this.updated = true;
}
}
/**
* Finishes a bar indexed by name
*/
finishBar(name: string): void {
const barDetails = this.getBar(name);
if (!barDetails) {
return;
}
const barToUpdate = barDetails.bar;
if (!barToUpdate.isFinished() && !barToUpdate.isFailed()) {
barToUpdate.finish();
this.updated = true;
}
/**
* Increments value of a bar indexed by name
*/
incrementBar(name: string, value: number): void {
const barDetails = this.getBar(name);
if (!barDetails) {
return;
}
const barToUpdate = barDetails.bar;
barToUpdate.increment(value);
this.updated = true;
}
/**
* Finish all bars
*/
finishAllBars(): void {
this.bars.forEach(bar => this.finishBar(bar.name));
/**
* Finishes a bar indexed by name
*/
finishBar(name: string): void {
const barDetails = this.getBar(name);
if (!barDetails) {
return;
}
const barToUpdate = barDetails.bar;
if (!barToUpdate.isFinished() && !barToUpdate.isFailed()) {
barToUpdate.finish();
this.updated = true;
}
}
/**
* Creates a set of progress bars under the multi bar
*/
create(bars: {
name: string,
value: number,
total: number,
payload: ProgressPayload
}[]): void {
if (!this.bars.length) {
this.terminal.newLine();
if (this.options.hideCursor === true) {
this.terminal.cursor(false);
}
this.isActive = true;
/**
* Finish all bars
*/
finishAllBars(): void {
this.bars.forEach((bar) => this.finishBar(bar.name));
}
/**
* Creates a set of progress bars under the multi bar
*/
create(
bars: {
name: string;
value: number;
total: number;
payload: ProgressPayload;
}[],
): void {
if (!this.bars.length) {
this.terminal.newLine();
if (this.options.hideCursor === true) {
this.terminal.cursor(false);
}
bars.forEach(config => {
const newBar = new Bar(this.options);
newBar.start(config.total, config.value, config.payload);
this.bars.push({ name: config.name, bar: newBar });
this.count += 1;
});
if (bars.length) {
this.updated = true;
this.render();
}
this.count += bars.length;
this.isActive = true;
}
/**
* Update the header text that has a trailing spinner
*/
updatePrefixText(newPrefixText: string): void {
this.prefixText = newPrefixText;
bars.forEach((config) => {
const newBar = new Bar(this.options);
newBar.start(config.total, config.value, config.payload);
this.bars.push({ name: config.name, bar: newBar });
this.count += 1;
});
if (bars.length) {
this.updated = true;
this.render();
}
this.count += bars.length;
}
/**
* Returns count of progress bars under the multi bar
*/
getBarCount(): number {
return this.count;
}
/**
* Update the header text that has a trailing spinner
*/
updatePrefixText(newPrefixText: string): void {
this.prefixText = newPrefixText;
}
/**
* Stop all progress bars under the multi bar
*/
stop(): void {
this.isActive = false;
clearTimeout(this.timer);
/**
* Returns count of progress bars under the multi bar
*/
getBarCount(): number {
return this.count;
}
// Change prefix text according to success/failure
let initLine: TerminalLine = {
renderString: this.options.successText || '',
color: 'green',
};
for (const { bar } of this.bars) {
if (bar.isFailed()) {
initLine = {
renderString: this.options.failureText || '',
color: 'red',
};
break;
}
}
this.writeLines(initLine);
/**
* Stop all progress bars under the multi bar
*/
stop(): void {
this.isActive = false;
clearTimeout(this.timer);
// Stop each bar and also enter a new line.
this.bars.forEach(bar => bar.bar.stop());
this.bars = [];
this.count = 0;
if (this.options.hideCursor) {
this.terminal.cursor(true);
// Change prefix text according to success/failure
let initLine: TerminalLine = {
renderString: this.options.successText || '',
color: 'green',
};
for (const { bar } of this.bars) {
if (bar.isFailed()) {
initLine = {
renderString: this.options.failureText || '',
color: 'red',
};
break;
}
this.terminal.newLine();
}
this.writeLines(initLine);
// Stop each bar and also enter a new line.
this.bars.forEach((bar) => bar.bar.stop());
this.bars = [];
this.count = 0;
if (this.options.hideCursor) {
this.terminal.cursor(true);
}
this.terminal.newLine();
}
}

@@ -7,20 +7,20 @@ import { AmplifyTerminal as Terminal, TerminalLine } from './terminal';

export type BarOptions = {
// Custom formatter functions for main progress bar and item's underneath it
progressBarFormatter: (payload: ProgressPayload, value: number, total: number) => string,
itemFormatter: (payload: ItemPayload) => { renderString: string, color: string },
// Indicates if the progress bar is part of a multiBar or standalone
loneWolf: boolean,
hideCursor: boolean,
// Specify characters for a completed/incomplete bar
barCompleteChar: string,
barIncompleteChar : string,
barSize: number,
// status which indicates item completion/failure
itemCompleteStatus: string[],
itemFailedStatus: string[],
// Text which appear on top of the progress bar indicating current status along with a spinner.
prefixText: string,
successText: string,
failureText: string
}
// Custom formatter functions for main progress bar and item's underneath it
progressBarFormatter: (payload: ProgressPayload, value: number, total: number) => string;
itemFormatter: (payload: ItemPayload) => { renderString: string; color: string };
// Indicates if the progress bar is part of a multiBar or standalone
loneWolf: boolean;
hideCursor: boolean;
// Specify characters for a completed/incomplete bar
barCompleteChar: string;
barIncompleteChar: string;
barSize: number;
// status which indicates item completion/failure
itemCompleteStatus: string[];
itemFailedStatus: string[];
// Text which appear on top of the progress bar indicating current status along with a spinner.
prefixText: string;
successText: string;
failureText: string;
};

@@ -31,5 +31,5 @@ /**

export type ProgressPayload = {
progressName: string,
envName: string
}
progressName: string;
envName: string;
};

@@ -40,15 +40,15 @@ /**

export type ItemPayload = {
LogicalResourceId: string,
ResourceType: string,
ResourceStatus: string,
Timestamp: string,
}
LogicalResourceId: string;
ResourceType: string;
ResourceStatus: string;
Timestamp: string;
};
type Item = {
name: string,
status: string,
renderString: string,
color: string,
finished: boolean
}
name: string;
status: string;
renderString: string;
color: string;
finished: boolean;
};

@@ -61,191 +61,194 @@ const DEFAULT_BAR_SIZE = 40;

export class ProgressBar {
private value: number;
private total: number;
private terminal: Terminal | undefined;
private payload!: ProgressPayload;
private isActive: boolean;
private options: BarOptions;
private lastDrawnTime: number;
private items: Item[];
private value: number;
private total: number;
private terminal: Terminal | undefined;
private payload!: ProgressPayload;
private isActive: boolean;
private options: BarOptions;
private lastDrawnTime: number;
private items: Item[];
barCompleteString: string;
barIncompleteString: string;
barSize: number;
barCompleteString: string;
barIncompleteString: string;
barSize: number;
constructor(options: BarOptions) {
if (options.loneWolf) {
this.terminal = new Terminal();
}
this.value = 0;
this.total = 1;
this.options = options;
this.items = [];
this.isActive = false;
this.lastDrawnTime = 0;
this.barSize = options.barSize || DEFAULT_BAR_SIZE;
this.barCompleteString = (new Array(this.barSize + 1).join(options.barCompleteChar || '='));
this.barIncompleteString = (new Array(this.barSize + 1).join(options.barIncompleteChar || '-'));
constructor(options: BarOptions) {
if (options.loneWolf) {
this.terminal = new Terminal();
}
this.value = 0;
this.total = 1;
this.options = options;
this.items = [];
this.isActive = false;
this.lastDrawnTime = 0;
this.barSize = options.barSize || DEFAULT_BAR_SIZE;
/**
* Create bar strings according to progress using the complete and incomplete bar string.
*/
createBarString(): string {
const completeSize = Math.round((this.value / this.total) * this.barSize);
const incompleteSize = this.barSize - completeSize;
this.barCompleteString = new Array(this.barSize + 1).join(options.barCompleteChar || '=');
this.barIncompleteString = new Array(this.barSize + 1).join(options.barIncompleteChar || '-');
}
// generate bar string by stripping the pre-rendered strings
const bar = this.barCompleteString.slice(0, completeSize)
+ this.barIncompleteString.slice(0, incompleteSize);
/**
* Create bar strings according to progress using the complete and incomplete bar string.
*/
createBarString(): string {
const completeSize = Math.round((this.value / this.total) * this.barSize);
const incompleteSize = this.barSize - completeSize;
return ` [ ${bar} ] ${this.value}/${this.total}`;
}
// generate bar string by stripping the pre-rendered strings
const bar = this.barCompleteString.slice(0, completeSize) + this.barIncompleteString.slice(0, incompleteSize);
/**
* Render strings are made by concatenating the progress bar strings with the item strings.
*/
getRenderStrings(): TerminalLine[] {
let finalStrings: TerminalLine[] = [];
const progressBar = this.options.progressBarFormatter.call(this, this.payload, this.value, this.total) + this.createBarString();
finalStrings.push({
renderString: progressBar,
color: '',
});
finalStrings = this.items.reduce((prev, _current) => prev.concat({
renderString: `\t${_current.renderString}`,
color: `${_current.color}`,
}), finalStrings);
return finalStrings;
}
return ` [ ${bar} ] ${this.value}/${this.total}`;
}
/**
* Return current value.
*/
getValue(): number {
return this.value;
}
/**
* Render strings are made by concatenating the progress bar strings with the item strings.
*/
getRenderStrings(): TerminalLine[] {
let finalStrings: TerminalLine[] = [];
const progressBar = this.options.progressBarFormatter.call(this, this.payload, this.value, this.total) + this.createBarString();
finalStrings.push({
renderString: progressBar,
color: '',
});
finalStrings = this.items.reduce(
(prev, _current) =>
prev.concat({
renderString: `\t${_current.renderString}`,
color: `${_current.color}`,
}),
finalStrings,
);
return finalStrings;
}
/**
* Render function
*/
render(): void {
if (this.terminal) {
const stringsToRender = this.getRenderStrings();
this.terminal.writeLines(stringsToRender);
}
}
/**
* Return current value.
*/
getValue(): number {
return this.value;
}
/**
* Checks if progress bar finished.
*/
isFinished(): boolean {
return this.value === this.total;
/**
* Render function
*/
render(): void {
if (this.terminal) {
const stringsToRender = this.getRenderStrings();
this.terminal.writeLines(stringsToRender);
}
}
/**
* Checks if progress bar failed.
*/
isFailed(): boolean {
return this.items.some(item => this.options.itemFailedStatus.includes(item.status));
}
/**
* Checks if progress bar finished.
*/
isFinished(): boolean {
return this.value === this.total;
}
/**
* Starts a progress bar and calls render function.
*/
start(total: number, startValue: number, payload: ProgressPayload): void {
this.value = startValue || 0;
this.total = total >= 0 ? total : this.total;
/**
* Checks if progress bar failed.
*/
isFailed(): boolean {
return this.items.some((item) => this.options.itemFailedStatus.includes(item.status));
}
this.payload = payload || {};
this.lastDrawnTime = Date.now();
/**
* Starts a progress bar and calls render function.
*/
start(total: number, startValue: number, payload: ProgressPayload): void {
this.value = startValue || 0;
this.total = total >= 0 ? total : this.total;
this.isActive = true;
this.payload = payload || {};
this.lastDrawnTime = Date.now();
if (this.terminal) {
if (this.options.hideCursor === true) {
this.terminal.cursor(false);
}
this.render();
this.isActive = true;
if (this.terminal) {
if (this.options.hideCursor === true) {
this.terminal.cursor(false);
}
this.render();
}
}
/**
* Stops the progress bar
*/
stop(): void {
this.isActive = false;
if (this.terminal) {
if (this.options.hideCursor) {
this.terminal.cursor(true);
}
/**
* Stops the progress bar
*/
stop(): void {
this.isActive = false;
if (this.terminal) {
if (this.options.hideCursor) {
this.terminal.cursor(true);
}
}
}
/**
* Checks if item exists
*/
hasItem(name: string): boolean {
return !!this.getItem(name);
}
/**
* Checks if item exists
*/
hasItem(name: string): boolean {
return !!this.getItem(name);
}
/**
* Returns item if it exists
*/
getItem(name: string): Item | undefined {
return this.items.find(item => item.name === name);
}
/**
* Returns item if it exists
*/
getItem(name: string): Item | undefined {
return this.items.find((item) => item.name === name);
}
/**
* Add a new item
*/
addItem(name: string, itemPayload: ItemPayload): void {
const status = itemPayload.ResourceStatus;
this.items.push({
name,
status,
...this.options.itemFormatter.call(this, itemPayload),
finished: this.options.itemCompleteStatus.includes(status),
});
this.render();
}
/**
* Add a new item
*/
addItem(name: string, itemPayload: ItemPayload): void {
const status = itemPayload.ResourceStatus;
this.items.push({
name,
status,
...this.options.itemFormatter.call(this, itemPayload),
finished: this.options.itemCompleteStatus.includes(status),
});
this.render();
}
/**
* Updates an item if it exists.
*/
updateItem(name: string, newPayload: ItemPayload): void {
const newItemsSet = this.items.map(item => {
let obj = null;
if (item.name === name) {
obj = {
name: item.name,
status: newPayload.ResourceStatus,
// Do not update if item has already finished (CloudFormation nuance)
finished: item.finished || this.options.itemCompleteStatus.includes(newPayload.ResourceStatus),
...this.options.itemFormatter.call(this, newPayload),
};
}
return obj || item;
});
this.items = newItemsSet;
/**
* Updates an item if it exists.
*/
updateItem(name: string, newPayload: ItemPayload): void {
const newItemsSet = this.items.map((item) => {
let obj = null;
if (item.name === name) {
obj = {
name: item.name,
status: newPayload.ResourceStatus,
// Do not update if item has already finished (CloudFormation nuance)
finished: item.finished || this.options.itemCompleteStatus.includes(newPayload.ResourceStatus),
...this.options.itemFormatter.call(this, newPayload),
};
}
return obj || item;
});
this.items = newItemsSet;
this.render();
}
/**
* Increments the progress bar
*/
increment(value = 1): void {
this.value += value;
if (this.options.loneWolf) {
this.render();
}
}
/**
* Increments the progress bar
*/
increment(value = 1): void {
this.value += value;
if (this.options.loneWolf) {
this.render();
}
}
/**
* Finishes the progress bar
*/
finish(): void {
const diff = this.total - this.value;
this.increment(diff);
}
/**
* Finishes the progress bar
*/
finish(): void {
const diff = this.total - this.value;
this.increment(diff);
}
}

@@ -37,6 +37,8 @@ /**

}
const lines = [{
renderString: `${this.frames[this.frameCount]} ${this.prefixText}`,
color: '',
}];
const lines = [
{
renderString: `${this.frames[this.frameCount]} ${this.prefixText}`,
color: '',
},
];
this.frameCount = ++this.frameCount % this.frames.length;

@@ -77,6 +79,8 @@ this.terminal.writeLines(lines);

}
const lines: TerminalLine[] = [{
renderString: text || '',
color: success ? 'green' : 'red',
}];
const lines: TerminalLine[] = [
{
renderString: text || '',
color: success ? 'green' : 'red',
},
];

@@ -83,0 +87,0 @@ clearTimeout(this.timer);

@@ -8,5 +8,5 @@ import os from 'os';

export type TerminalLine = {
renderString: string,
color: string
}
renderString: string;
color: string;
};

@@ -16,3 +16,3 @@ const ESC = '\u001b';

const cursorUp = (n : number) : string => {
const cursorUp = (n: number): string => {
const dy = typeof n === 'number' ? n : 1;

@@ -22,7 +22,7 @@ return dy > 0 ? `${ESC}[${dy}A` : '';

const clearLine = () : string => `${ESC}[K`;
const clearLine = (): string => `${ESC}[K`;
const SHOW_CURSOR = '\x1b[?25h';
const HIDE_CURSOR = '\x1b[?25l';
const getColoredLine = (line: string, color: string) : string => {
const getColoredLine = (line: string, color: string): string => {
if (color) {

@@ -38,86 +38,86 @@ return chalk.keyword(color)(line);

export class AmplifyTerminal {
private lastHeight : number;
private trailingEmptyLines : number;
private readonly stream: NodeJS.WriteStream;
private lastHeight: number;
private trailingEmptyLines: number;
private readonly stream: NodeJS.WriteStream;
constructor() {
this.lastHeight = 0;
this.trailingEmptyLines = 0;
this.stream = process.stdout;
}
constructor() {
this.lastHeight = 0;
this.trailingEmptyLines = 0;
this.stream = process.stdout;
}
/**
* Checks if the environment is tty
*/
isTTY(): boolean {
return this.stream.isTTY;
}
/**
* Checks if the environment is tty
*/
isTTY(): boolean {
return this.stream.isTTY;
}
/**
* Width of terminal
*/
private get width() : number {
return this.stream.columns;
}
/**
* Width of terminal
*/
private get width(): number {
return this.stream.columns;
}
/**
* Height of last printed block
*/
public getLastHeight() : number {
return this.lastHeight;
}
/**
* Height of last printed block
*/
public getLastHeight(): number {
return this.lastHeight;
}
/**
* Height of terminal
*/
private get height() : number {
return this.stream.rows;
}
/**
* Height of terminal
*/
private get height(): number {
return this.stream.rows;
}
/**
* Write array of lines into block.
*/
public writeLines(lines: TerminalLine[]): void {
// Go back to beginning of last written block
this.stream.write(cursorUp(this.lastHeight));
/**
* Write array of lines into block.
*/
public writeLines(lines: TerminalLine[]): void {
// Go back to beginning of last written block
this.stream.write(cursorUp(this.lastHeight));
lines.forEach(line => {
// Truncating to width-2 of terminal
const { renderString, color } = line;
let truncatedLine = renderString.substring(0, Math.min(renderString.length, this.width - BUFFER_LENGTH));
if (truncatedLine.length) {
truncatedLine = getColoredLine(truncatedLine, color);
}
this.stream.write(`${clearLine()}${truncatedLine}${os.EOL}`);
});
lines.forEach((line) => {
// Truncating to width-2 of terminal
const { renderString, color } = line;
let truncatedLine = renderString.substring(0, Math.min(renderString.length, this.width - BUFFER_LENGTH));
if (truncatedLine.length) {
truncatedLine = getColoredLine(truncatedLine, color);
}
this.stream.write(`${clearLine()}${truncatedLine}${os.EOL}`);
});
this.trailingEmptyLines = Math.max(0, this.lastHeight - lines.length);
this.trailingEmptyLines = Math.max(0, this.lastHeight - lines.length);
// Clear trailing lines if last written block has more height.
for (let i = 0; i < this.trailingEmptyLines; i++) {
this.stream.write(`${clearLine()}${os.EOL}`);
}
this.lastHeight = lines.length;
// Clear trailing lines if last written block has more height.
for (let i = 0; i < this.trailingEmptyLines; i++) {
this.stream.write(`${clearLine()}${os.EOL}`);
}
this.lastHeight = lines.length;
}
/**
* Hide/Show cursor. Only for a tty console.
*/
public cursor(enabled: boolean): void {
if (!this.isTTY()) {
return;
}
if (enabled) {
this.stream.write(SHOW_CURSOR);
} else {
this.stream.write(HIDE_CURSOR);
}
/**
* Hide/Show cursor. Only for a tty console.
*/
public cursor(enabled: boolean): void {
if (!this.isTTY()) {
return;
}
if (enabled) {
this.stream.write(SHOW_CURSOR);
} else {
this.stream.write(HIDE_CURSOR);
}
}
/**
* Write a new line
*/
public newLine() : void {
this.stream.write(os.EOL);
}
/**
* Write a new line
*/
public newLine(): void {
this.stream.write(os.EOL);
}
}

@@ -93,3 +93,3 @@ /* eslint-disable @typescript-eslint/ban-types */

// eslint-disable-next-line no-nested-ternary
format: value => (submitted ? (value ? 'yes' : 'no') : ''),
format: (value) => (submitted ? (value ? 'yes' : 'no') : ''),
onSubmit: () => {

@@ -159,7 +159,7 @@ submitted = true;

if (Array.isArray(result)) {
functionResult = ((await Promise.all(
result.map(async part => (opts.transform as Function)(part) as T),
)) as unknown) as PromptReturn<RS, T>;
functionResult = (await Promise.all(
result.map(async (part) => (opts.transform as Function)(part) as T),
)) as unknown as PromptReturn<RS, T>;
} else {
functionResult = (opts.transform(result as string) as unknown) as PromptReturn<RS, T>;
functionResult = opts.transform(result as string) as unknown as PromptReturn<RS, T>;
}

@@ -171,3 +171,3 @@ this.pushInteractiveFlow(message, functionResult, enquirerPromptType == EnquirerPromptType.INVISIBLE);

this.pushInteractiveFlow(message, result, enquirerPromptType == EnquirerPromptType.INVISIBLE);
return (result as unknown) as PromptReturn<RS, T>;
return result as unknown as PromptReturn<RS, T>;
};

@@ -204,7 +204,7 @@

? // this assertion is safe because the choice array can only be a string[] if the generic type is a string
(((choices as string[]).map(choice => ({ name: choice, value: choice })) as unknown) as GenericChoice<T>[])
((choices as string[]).map((choice) => ({ name: choice, value: choice })) as unknown as GenericChoice<T>[])
: (choices as GenericChoice<T>[]);
const initialIndexes = initialOptsToIndexes(
genericChoices.map(choice => choice.value),
genericChoices.map((choice) => choice.value),
opts.initial,

@@ -216,3 +216,3 @@ );

const choiceValueMap = new Map<string, T>();
const enquirerChoices = genericChoices.map(choice => {
const enquirerChoices = genericChoices.map((choice) => {
choiceValueMap.set(choice.name, choice.value);

@@ -231,3 +231,3 @@ const enqResult = { name: choice.name, disabled: choice.disabled, hint: choice.hint };

// if you have to pick at least as many options as are available, select all of them and return without prompting
result = genericChoices.map(choice => choice.name);
result = genericChoices.map((choice) => choice.name);
this.print.info(`Must pick at least ${opts.pickAtLeast} of ${choices.length} options. Selecting all options [${result}]`);

@@ -241,3 +241,3 @@ } else if (isYes) {

} else {
result = initialIndexes.map(idx => genericChoices[idx].name);
result = initialIndexes.map((idx) => genericChoices[idx].name);
}

@@ -306,3 +306,3 @@ } else {

// result is an array
loggedRet = result.map(item => choiceValueMap.get(item) as T) as PromptReturn<RS, T>;
loggedRet = result.map((item) => choiceValueMap.get(item) as T) as PromptReturn<RS, T>;
} else {

@@ -328,4 +328,6 @@ // result is a string

*/
export const byValues = <T>(selection: T[], equals: EqualsFunction<T> = defaultEquals): MultiFilterFunction<T> => (choices: T[]) =>
selection.map(sel => choices.findIndex(choice => equals(choice, sel))).filter(idx => idx >= 0);
export const byValues =
<T>(selection: T[], equals: EqualsFunction<T> = defaultEquals): MultiFilterFunction<T> =>
(choices: T[]) =>
selection.map((sel) => choices.findIndex((choice) => equals(choice, sel))).filter((idx) => idx >= 0);

@@ -338,6 +340,8 @@ /**

*/
export const byValue = <T>(selection: T, equals: EqualsFunction<T> = defaultEquals): SingleFilterFunction<T> => (choices: T[]) => {
const idx = choices.findIndex(choice => equals(choice, selection));
return idx < 0 ? undefined : idx;
};
export const byValue =
<T>(selection: T, equals: EqualsFunction<T> = defaultEquals): SingleFilterFunction<T> =>
(choices: T[]) => {
const idx = choices.findIndex((choice) => equals(choice, selection));
return idx < 0 ? undefined : idx;
};

@@ -348,4 +352,4 @@ const validateEachWith = (validator?: Validator) => async (input: string[]) => {

}
const validationList = await Promise.all(input.map(part => part.trim()).map(async part => ({ part, result: await validator(part) })));
const firstInvalid = validationList.find(v => typeof v.result === 'string');
const validationList = await Promise.all(input.map((part) => part.trim()).map(async (part) => ({ part, result: await validator(part) })));
const firstInvalid = validationList.find((v) => typeof v.result === 'string');
if (firstInvalid) {

@@ -352,0 +356,0 @@ return `${firstInvalid.part} did not satisfy requirement ${firstInvalid.result}`;

@@ -5,40 +5,40 @@ /**

export class Stopwatch {
private _slices: Slice [] = []
private _slices: Slice[] = [];
start = (): void => {
const now = Date.now();
this._slices.push({
start: now,
});
}
start = (): void => {
const now = Date.now();
this._slices.push({
start: now,
});
};
/**
* stops the time and reset
*/
stop = () : void => {
this._slices.length = 0;
/**
* stops the time and reset
*/
stop = (): void => {
this._slices.length = 0;
};
/**
* pauses the time call start to resume
*/
pause = (): void => {
const latestSlice = this._slices[this._slices.length - 1];
if (!latestSlice) {
return;
}
/**
* pauses the time call start to resume
*/
pause = (): void => {
const latestSlice = this._slices[this._slices.length - 1];
if (!latestSlice) {
return;
}
latestSlice.stop = Date.now();
};
latestSlice.stop = Date.now();
}
/**
* get the total elapsed time
*/
getElapsedMilliseconds = (): number => this._slices
.reduce((accumulator, currentValue) => accumulator + ((currentValue.stop || Date.now()) - currentValue.start), 0);
/**
* get the total elapsed time
*/
getElapsedMilliseconds = (): number =>
this._slices.reduce((accumulator, currentValue) => accumulator + ((currentValue.stop || Date.now()) - currentValue.start), 0);
}
type Slice = {
start: number;
stop?: number;
}
start: number;
stop?: number;
};

@@ -15,21 +15,36 @@ export type Validator = (value: string) => true | string | Promise<true | string>;

export const alphanumeric = (message = 'Input must be alphanumeric'): Validator => (input: string) =>
/^[a-zA-Z0-9]+$/.test(input) ? true : message;
export const alphanumeric =
(message = 'Input must be alphanumeric'): Validator =>
(input: string) =>
/^[a-zA-Z0-9]+$/.test(input) ? true : message;
export const matchRegex = (validatorRegex: RegExp, message?: string): Validator => (input: string) =>
validatorRegex.test(input) ? true : message || `Input does not match the regular expression ${validatorRegex}`;
export const matchRegex =
(validatorRegex: RegExp, message?: string): Validator =>
(input: string) =>
validatorRegex.test(input) ? true : message || `Input does not match the regular expression ${validatorRegex}`;
export const integer = (message = 'Input must be a number'): Validator => (input: string) => (/^[0-9]+$/.test(input) ? true : message);
export const integer =
(message = 'Input must be a number'): Validator =>
(input: string) =>
/^[0-9]+$/.test(input) ? true : message;
export const maxLength = (maxLen: number, message?: string): Validator => (input: string) =>
input.length > maxLen ? message || `Input must be less than ${maxLen} characters long` : true;
export const maxLength =
(maxLen: number, message?: string): Validator =>
(input: string) =>
input.length > maxLen ? message || `Input must be less than ${maxLen} characters long` : true;
export const minLength = (minLen: number, message?: string): Validator => (input: string) =>
input.length < minLen ? message || `Input must be more than ${minLen} characters long` : true;
export const minLength =
(minLen: number, message?: string): Validator =>
(input: string) =>
input.length < minLen ? message || `Input must be more than ${minLen} characters long` : true;
export const exact = (expected: string, message?: string): Validator => (input: string) =>
input === expected ? true : message ?? 'Input does not match expected value';
export const exact =
(expected: string, message?: string): Validator =>
(input: string) =>
input === expected ? true : message ?? 'Input does not match expected value';
export const between = (min: number, max: number, message?: string): Validator => (input: string) =>
parseInt(input) >= min && parseInt(input) <= max ? true : message || `Input must be between ${min} and ${max}`;
export const between =
(min: number, max: number, message?: string): Validator =>
(input: string) =>
parseInt(input) >= min && parseInt(input) <= max ? true : message || `Input must be between ${min} and ${max}`;

@@ -40,11 +55,13 @@ /**

*/
export const and = (validators: [Validator, Validator, ...Validator[]], message?: string): Validator => async (input: string) => {
for (const validator of validators) {
const result = await validator(input);
if (typeof result === 'string') {
return message ?? result;
export const and =
(validators: [Validator, Validator, ...Validator[]], message?: string): Validator =>
async (input: string) => {
for (const validator of validators) {
const result = await validator(input);
if (typeof result === 'string') {
return message ?? result;
}
}
}
return true;
};
return true;
};

@@ -55,12 +72,14 @@ /**

*/
export const or = (validators: [Validator, Validator, ...Validator[]], message?: string): Validator => async (input: string) => {
let result: string | true = true;
for (const validator of validators) {
result = await validator(input);
if (result === true) {
return true;
export const or =
(validators: [Validator, Validator, ...Validator[]], message?: string): Validator =>
async (input: string) => {
let result: string | true = true;
for (const validator of validators) {
result = await validator(input);
if (result === true) {
return true;
}
}
}
return message ?? result;
};
return message ?? result;
};

@@ -71,3 +90,5 @@ /**

*/
export const not = (validator: Validator, message: string): Validator => async (input: string) =>
typeof (await validator(input)) === 'string' ? true : message;
export const not =
(validator: Validator, message: string): Validator =>
async (input: string) =>
typeof (await validator(input)) === 'string' ? true : message;

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

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