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