Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve ora.promise() #181

Merged
merged 11 commits into from
Aug 23, 2021
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
83 changes: 48 additions & 35 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,23 +21,23 @@ declare namespace ora {

interface Options {
/**
Text to display after the spinner.
*/
Text to display after the spinner.
*/
SrBrahma marked this conversation as resolved.
Show resolved Hide resolved
readonly text?: string;

/**
Text or a function that returns text to display before the spinner. No prefix text will be displayed if set to an empty string.
*/
Text or a function that returns text to display before the spinner. No prefix text will be displayed if set to an empty string.
*/
readonly prefixText?: string | PrefixTextGenerator;

/**
Name of one of the provided spinners. See [`example.js`](https://github.com/BendingBender/ora/blob/main/example.js) in this repo if you want to test out different spinners. On Windows, it will always use the line spinner as the Windows command-line doesn't have proper Unicode support.
Name of one of the provided spinners. See [`example.js`](https://github.com/BendingBender/ora/blob/main/example.js) in this repo if you want to test out different spinners. On Windows, it will always use the line spinner as the Windows command-line doesn't have proper Unicode support.

@default 'dots'
@default 'dots'

Or an object like:
Or an object like:

@example
@example
```
{
interval: 80, // Optional
Expand All @@ -55,25 +55,25 @@ declare namespace ora {
readonly color?: Color;

/**
Set to `false` to stop Ora from hiding the cursor.
Set to `false` to stop Ora from hiding the cursor.

@default true
@default true
*/
readonly hideCursor?: boolean;

/**
Indent the spinner with the given number of spaces.
Indent the spinner with the given number of spaces.

@default 0
@default 0
*/
readonly indent?: number;

/**
Interval between each frame.
Interval between each frame.

Spinners provide their own recommended interval, so you don't really need to specify this.
Spinners provide their own recommended interval, so you don't really need to specify this.

Default: Provided by the spinner or `100`.
Default: Provided by the spinner or `100`.
*/
readonly interval?: number;

Expand All @@ -87,32 +87,45 @@ declare namespace ora {
readonly stream?: NodeJS.WritableStream;

/**
Force enable/disable the spinner. If not specified, the spinner will be enabled if the `stream` is being run inside a TTY context (not spawned or piped) and/or not in a CI environment.
Force enable/disable the spinner. If not specified, the spinner will be enabled if the `stream` is being run inside a TTY context (not spawned or piped) and/or not in a CI environment.

Note that `{isEnabled: false}` doesn't mean it won't output anything. It just means it won't output the spinner, colors, and other ansi escape codes. It will still log text.
Note that `{isEnabled: false}` doesn't mean it won't output anything. It just means it won't output the spinner, colors, and other ansi escape codes. It will still log text.
*/
readonly isEnabled?: boolean;

/**
Disable the spinner and all log text. All output is suppressed and `isEnabled` will be considered `false`.
Disable the spinner and all log text. All output is suppressed and `isEnabled` will be considered `false`.

@default false
*/
@default false
*/
readonly isSilent?: boolean;

/**
Discard stdin input (except Ctrl+C) while running if it's TTY. This prevents the spinner from twitching on input, outputting broken lines on `Enter` key presses, and prevents buffering of input while the spinner is running.
Discard stdin input (except Ctrl+C) while running if it's TTY. This prevents the spinner from twitching on input, outputting broken lines on `Enter` key presses, and prevents buffering of input while the spinner is running.

This has no effect on Windows as there's no good way to implement discarding stdin properly there.
This has no effect on Windows as there's no good way to implement discarding stdin properly there.

@default true
*/
@default true
*/
readonly discardStdin?: boolean;
}

type PromiseOptions<T = unknown> = Options & {
/**
The new text of the spinner when the promise is resolved.

If undefined, will keep the initial text. */
successText?: ((resp: T) => string) | string;
/**
The new text of the spinner when the promise is rejected.

If undefined, will keep the initial text. */
failText?: ((error: Error) => string) | string;
};

interface PersistOptions {
/**
Symbol to replace the spinner with.
Symbol to replace the spinner with.

@default ' '
*/
Expand All @@ -126,17 +139,17 @@ declare namespace ora {
readonly text?: string;

/**
Text or a function that returns text to be persisted before the symbol. No prefix text will be displayed if set to an empty string.
Text or a function that returns text to be persisted before the symbol. No prefix text will be displayed if set to an empty string.

Default: Current `prefixText`.
*/
Default: Current `prefixText`.
*/
readonly prefixText?: string | PrefixTextGenerator;
}

interface Ora {
/**
A boolean of whether the instance is currently spinning.
*/
A boolean of whether the instance is currently spinning.
*/
readonly isSpinning: boolean;

/**
Expand Down Expand Up @@ -262,16 +275,16 @@ declare const ora: {
(options?: ora.Options | string): ora.Ora;

/**
Starts a spinner for a promise. The spinner is stopped with `.succeed()` if the promise fulfills or with `.fail()` if it rejects.
Starts a spinner for a function or a promise. The spinner is stopped with `.succeed()` if the promise fulfills or with `.fail()` if it rejects.
SrBrahma marked this conversation as resolved.
Show resolved Hide resolved

@param action - The promise to start the spinner for.
@param options - If a string is provided, it is treated as a shortcut for `options.text`.
@returns The spinner instance.
*/
promise(
action: PromiseLike<unknown>,
options?: ora.Options | string
): ora.Ora;
promise<T>(
action: ((spinner: ora.Ora) => PromiseLike<T>) | PromiseLike<T>,
options?: ora.PromiseOptions<T> | string
): Promise<T>;
};

export = ora;
42 changes: 27 additions & 15 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -385,23 +385,35 @@ const oraFactory = function (options) {

module.exports = oraFactory;

module.exports.promise = (action, options) => {
// https://github.com/sindresorhus/ora/issues/169#issuecomment-873269524
module.exports.promise = async (action, options) => {
const actionIsFunction = typeof action === 'function';
// eslint-disable-next-line promise/prefer-await-to-then
if (typeof action.then !== 'function') {
throw new TypeError('Parameter `action` must be a Promise');
}
const actionIsPromise = typeof action.then === 'function';

const spinner = new Ora(options);
spinner.start();
if (!actionIsFunction && !actionIsPromise) {
throw new TypeError('Parameter `action` must be a Function or a Promise');
}

(async () => {
try {
await action;
spinner.succeed();
} catch {
spinner.fail();
}
})();
const {successText, failText} = typeof options === 'object' ?
options :
{successText: undefined, failText: undefined};

return spinner;
const spinner = new Ora(options); // Set the initial string or ora options.
spinner.start();
try {
const promise = actionIsFunction ? action(spinner) : action;
const result = await promise;
spinner.succeed(successText === undefined ?
undefined :
(typeof successText === 'string' ? successText : successText(result))
);
return result;
} catch (error) {
spinner.fail(failText === undefined ?
undefined :
(typeof failText === 'string' ? failText : failText(error))
);
throw error;
}
};
20 changes: 18 additions & 2 deletions index.test-d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,11 +45,27 @@ spinner.render();
spinner.frame();

const resolves = Promise.resolve(1);
promise(resolves, 'foo');
promise(resolves, {
void promise(resolves, 'foo');
void promise(resolves, {
stream: new PassThroughStream(),
text: 'foo',
color: 'blue',
isEnabled: true,
isSilent: false
});
void promise(async () => {
await resolves;
}, 'foo');
void promise(async spinner => {
spinner.prefixText = 'foo';
await resolves;
return 7;
}, {
stream: new PassThroughStream(),
text: 'foo',
color: 'blue',
isEnabled: true,
isSilent: false,
successText: result => `Resolved with number ${result}`,
failText: 'bar'
});
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "ora",
"version": "5.4.1",
"version": "6.0.0",
SrBrahma marked this conversation as resolved.
Show resolved Hide resolved
"description": "Elegant terminal spinner",
"license": "MIT",
"repository": "sindresorhus/ora",
Expand Down
19 changes: 17 additions & 2 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -226,11 +226,26 @@ Change the spinner indent.
### ora.promise(action, text)
### ora.promise(action, options)

Starts a spinner for a promise. The spinner is stopped with `.succeed()` if the promise fulfills or with `.fail()` if it rejects. Returns the spinner instance.
Starts a spinner for a promise. The spinner is stopped with `.succeed()` if the promise fulfills or with `.fail()` if it rejects. Returns a Promise.

#### action

Type: `Promise`
Type: `((spinner: ora.Ora) => Promise) | Promise`

#### options

Type:
```ts
ora.Options & {
/** The new text of the spinner when the promise is resolved.

If undefined, will keep the initial text. */
successText?: ((resp: T) => string) | string;
/** The new text of the spinner when the promise is rejected.

If undefined, will keep the initial text. */
failText?: ((error: Error) => string) | string;
```
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The readme docs should use the same formatting as used here: https://github.com/sindresorhus/boxen#options (Not just a copy paste of the TS types).


## FAQ

Expand Down