1import { ExpoConfig } from '@expo/config-types';
2import chalk from 'chalk';
3import qrcode from 'qrcode-terminal';
4import wrapAnsi from 'wrap-ansi';
5
6import * as Log from '../../log';
7
8export const BLT = '\u203A';
9
10export type StartOptions = {
11  isWebSocketsEnabled?: boolean;
12  devClient?: boolean;
13  reset?: boolean;
14  nonPersistent?: boolean;
15  maxWorkers?: number;
16  platforms?: ExpoConfig['platforms'];
17};
18
19export const printHelp = (): void => {
20  logCommandsTable([{ key: '?', msg: 'show all commands' }]);
21};
22
23/** Print the world famous 'Expo QR Code'. */
24export function printQRCode(url: string) {
25  qrcode.generate(url, { small: true }, (code) => Log.log(code));
26}
27
28export const getTerminalColumns = () => process.stdout.columns || 80;
29export const printItem = (text: string): string =>
30  `${BLT} ` + wrapAnsi(text, getTerminalColumns()).trimStart();
31
32export function printUsage(
33  options: Pick<StartOptions, 'devClient' | 'isWebSocketsEnabled' | 'platforms'>,
34  { verbose }: { verbose: boolean }
35) {
36  const isMac = process.platform === 'darwin';
37
38  const { platforms = ['ios', 'android', 'web'] } = options;
39
40  const isAndroidDisabled = !platforms.includes('android');
41  const isIosDisabled = !platforms.includes('ios');
42  const isWebDisable = !platforms.includes('web');
43
44  const switchMsg = `switch to ${options.devClient === false ? 'development build' : 'Expo Go'}`;
45  const target = options.devClient === false ? `Expo Go` : 'development build';
46
47  Log.log();
48  Log.log(printItem(chalk`Using {cyan ${target}}`));
49
50  if (verbose) {
51    logCommandsTable([
52      { key: 's', msg: switchMsg },
53      {},
54      { key: 'a', msg: 'open Android', disabled: isAndroidDisabled },
55      { key: 'shift+a', msg: 'select a device or emulator', disabled: isAndroidDisabled },
56      isMac && { key: 'i', msg: 'open iOS simulator', disabled: isIosDisabled },
57      isMac && { key: 'shift+i', msg: 'select a simulator', disabled: isIosDisabled },
58      { key: 'w', msg: 'open web', disabled: isWebDisable },
59      {},
60      { key: 'r', msg: 'reload app' },
61      !!options.isWebSocketsEnabled && { key: 'j', msg: 'open debugger' },
62      !!options.isWebSocketsEnabled && { key: 'm', msg: 'toggle menu' },
63      !!options.isWebSocketsEnabled && { key: 'shift+m', msg: 'more tools' },
64      { key: 'o', msg: 'open project code in your editor' },
65      { key: 'c', msg: 'show project QR' },
66      {},
67    ]);
68  } else {
69    logCommandsTable([
70      { key: 's', msg: switchMsg },
71      {},
72      { key: 'a', msg: 'open Android', disabled: isAndroidDisabled },
73      isMac && { key: 'i', msg: 'open iOS simulator', disabled: isIosDisabled },
74      { key: 'w', msg: 'open web', disabled: isWebDisable },
75      {},
76      { key: 'j', msg: 'open debugger' },
77      { key: 'r', msg: 'reload app' },
78      !!options.isWebSocketsEnabled && { key: 'm', msg: 'toggle menu' },
79      { key: 'o', msg: 'open project code in your editor' },
80      {},
81    ]);
82  }
83}
84
85function logCommandsTable(
86  ui: (false | { key?: string; msg?: string; status?: string; disabled?: boolean })[]
87) {
88  Log.log(
89    ui
90      .filter(Boolean)
91      // @ts-ignore: filter doesn't work
92      .map(({ key, msg, status, disabled }) => {
93        if (!key) return '';
94        let view = `${BLT} `;
95        if (key.length === 1) view += 'Press ';
96        view += chalk`{bold ${key}} {dim │} `;
97        view += msg;
98        if (status) {
99          view += ` ${chalk.dim(`(${chalk.italic(status)})`)}`;
100        }
101        if (disabled) {
102          view = chalk.dim(view);
103        }
104        return view;
105      })
106      .join('\n')
107  );
108}
109