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