xref: /expo/packages/@expo/cli/src/utils/ora.ts (revision d2dcff5d)
1import chalk from 'chalk';
2import oraReal, { Ora } from 'ora';
3
4// import * as Log from '../log';
5import { env } from './env';
6import { isInteractive } from './interactive';
7
8const logReal = console.log;
9const infoReal = console.info;
10const warnReal = console.warn;
11const errorReal = console.error;
12
13const runningSpinners: oraReal.Ora[] = [];
14
15export function getAllSpinners() {
16  return runningSpinners;
17}
18
19/**
20 * A custom ora spinner that sends the stream to stdout in CI, non-TTY, or expo's non-interactive flag instead of stderr (the default).
21 *
22 * @param options
23 * @returns
24 */
25export function ora(options?: oraReal.Options | string): oraReal.Ora {
26  const inputOptions = typeof options === 'string' ? { text: options } : options || {};
27  const disabled = !isInteractive() || env.EXPO_DEBUG;
28  const spinner = oraReal({
29    // Ensure our non-interactive mode emulates CI mode.
30    isEnabled: !disabled,
31    // In non-interactive mode, send the stream to stdout so it prevents looking like an error.
32    stream: disabled ? process.stdout : process.stderr,
33    ...inputOptions,
34  });
35
36  const oraStart = spinner.start.bind(spinner);
37  const oraStop = spinner.stop.bind(spinner);
38  const oraStopAndPersist = spinner.stopAndPersist.bind(spinner);
39
40  const logWrap = (method: any, args: any[]): void => {
41    oraStop();
42    method(...args);
43    spinner.start();
44  };
45
46  const wrapNativeLogs = (): void => {
47    console.log = (...args: any) => logWrap(logReal, args);
48    console.info = (...args: any) => logWrap(infoReal, args);
49    console.warn = (...args: any) => logWrap(warnReal, args);
50    console.error = (...args: any) => logWrap(errorReal, args);
51
52    runningSpinners.push(spinner);
53  };
54
55  const resetNativeLogs = (): void => {
56    console.log = logReal;
57    console.info = infoReal;
58    console.warn = warnReal;
59    console.error = errorReal;
60
61    const index = runningSpinners.indexOf(spinner);
62    if (index >= 0) {
63      runningSpinners.splice(index, 1);
64    }
65  };
66
67  spinner.start = (text): Ora => {
68    wrapNativeLogs();
69    return oraStart(text);
70  };
71
72  spinner.stopAndPersist = (options): Ora => {
73    const result = oraStopAndPersist(options);
74    resetNativeLogs();
75    return result;
76  };
77
78  spinner.stop = (): Ora => {
79    const result = oraStop();
80    resetNativeLogs();
81    return result;
82  };
83
84  // Always make the central logging module aware of the current spinner
85  // Log.setSpinner(spinner);
86
87  return spinner;
88}
89
90/**
91 * Create a unified section spinner.
92 *
93 * @param title
94 * @returns
95 */
96export function logNewSection(title: string) {
97  const spinner = ora(chalk.bold(title));
98  // Prevent the spinner from clashing with debug logs
99  spinner.start();
100  return spinner;
101}
102