xref: /expo/packages/@expo/cli/src/utils/errors.ts (revision 63fb203d)
1import { AssertionError } from 'assert';
2import chalk from 'chalk';
3
4import { exit } from '../log';
5
6const ERROR_PREFIX = 'Error: ';
7
8/**
9 * General error, formatted as a message in red text when caught by expo-cli (no stack trace is printed). Should be used in favor of `log.error()` in most cases.
10 */
11export class CommandError extends Error {
12  name = 'CommandError';
13  readonly isCommandError = true;
14
15  constructor(
16    public code: string,
17    message: string = ''
18  ) {
19    super('');
20    // If e.toString() was called to get `message` we don't want it to look
21    // like "Error: Error:".
22    if (message.startsWith(ERROR_PREFIX)) {
23      message = message.substring(ERROR_PREFIX.length);
24    }
25
26    this.message = message || code;
27  }
28}
29
30export class AbortCommandError extends CommandError {
31  constructor() {
32    super('ABORTED', 'Interactive prompt was cancelled.');
33  }
34}
35
36/**
37 * Used to end a CLI process without printing a stack trace in the Expo CLI. Should be used in favor of `process.exit`.
38 */
39export class SilentError extends CommandError {
40  constructor(messageOrError?: string | Error) {
41    const message =
42      (typeof messageOrError === 'string' ? messageOrError : messageOrError?.message) ??
43      'This error should fail silently in the CLI';
44    super('SILENT', message);
45    if (typeof messageOrError !== 'string') {
46      // forward the props of the incoming error for tests or processes outside of expo-cli that use expo cli internals.
47      this.stack = messageOrError?.stack ?? this.stack;
48      this.name = messageOrError?.name ?? this.name;
49    }
50  }
51}
52
53export function logCmdError(error: Error): never {
54  if (error instanceof AbortCommandError || error instanceof SilentError) {
55    // Do nothing, this is used for prompts or other cases that were custom logged.
56    process.exit(0);
57  } else if (
58    error instanceof CommandError ||
59    error instanceof AssertionError ||
60    error.name === 'ApiV2Error' ||
61    error.name === 'ConfigError'
62  ) {
63    // Print the stack trace in debug mode only.
64    exit(error);
65  }
66
67  const errorDetails = error.stack ? '\n' + chalk.gray(error.stack) : '';
68
69  exit(chalk.red(error.toString()) + errorDetails);
70}
71
72/** This should never be thrown in production. */
73export class UnimplementedError extends Error {
74  constructor() {
75    super('Unimplemented');
76    this.name = 'UnimplementedError';
77  }
78}
79